diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7c1788d9..09aa4997 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -78,7 +78,7 @@ jobs: strategy: fail-fast: false matrix: - group: [1, 2, 3, 4, 5, 6, 7, 8] + group: [1, 2, 3, 4, 5, 6, 7, 8, 9] steps: - uses: actions/checkout@v4 with: @@ -86,8 +86,14 @@ jobs: - uses: ./.github/actions/setup - name: Enable Golem wasmtime fork run: bash .github/scripts/enable-wasmtime-fork.sh - - name: Runtime tests (group ${{ matrix.group }}/8) + - uses: actions/setup-node@v4 + if: matrix.group == 9 + with: + node-version: "22.14.0" + - name: Runtime tests (group ${{ matrix.group }}/9) run: cargo test --test runtime $CI_WASMTIME_FORK_FEATURES -- --report-time --format ctrf --logfile target/ctrf.json ':tag:group${{ matrix.group }}' + env: + NODE_MODULES_APP_STRICT_NODE_BASELINE: "1" - name: Publish Test Report uses: ctrf-io/github-test-reporter@v1 if: always() diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..7d41c735 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.14.0 diff --git a/AGENTS.md b/AGENTS.md index bb9a670e..95c6a139 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -149,6 +149,19 @@ The `tests/node_compat/` directory contains vendored Node.js test files used to Load the `fixing-node-compat-test` skill for the full workflow when making a test pass. +## Node Modules App Tests + +The `tests/node_modules_apps/` directory contains CI-enforced runtime tests for unbundled npm apps installed with real `node_modules` and attached to the component filesystem as `/app`. This suite is separate from `tests/libraries/`, which documents Rollup-bundled package compatibility. + +Important rules: + +- `tests/node_modules_apps/config.jsonc` is the source of truth for node modules app tests. Runtime tests in `tests/runtime/node_modules_apps.rs` are generated from it. +- Add app fixtures under `tests/node_modules_apps/apps//` with a `package.json`, `run-node.mjs`, and `test-*` files exporting `run()`. +- Node modules app tests run `npm install --install-links --ignore-scripts --no-audit --no-fund`, verify the raw test with host Node.js, then run it through wasm-rquickjs from `/app`. +- Keep this suite focused on real `node_modules` module loading, CJS/ESM interop, package maps, filesystem-backed package behavior, and high-value smoke tests. Do not use it for native `.node`, WASM artifact loading, subprocess-heavy, or live-network scenarios. +- CI runs node modules app tests as runtime `group9`; regular runtime tests use `group1` through `group8`. +- Before running node modules app runtime tests after skeleton changes, run `./cleanup-skeleton.sh`, then use `cargo test --test runtime --features use-golem-wasmtime -- ':tag:group9'` for the CI-like group, `cargo test --test runtime --features use-golem-wasmtime -- node_modules_app --nocapture` for the full node modules app suite, or a narrower node modules app filter. + ### ⚠️ Keeping `node_compat` and `node_compat_report` in sync The `tests/node_compat.rs` test harness and the `tests/node_compat_report.rs` report generator are **two separate runners** with independent Host types, linker setups, and WASI context configurations. **Whenever you change the WASI context, linker setup, or Host configuration in `tests/common/mod.rs` (used by `node_compat`), you MUST apply the same change to `tests/node_compat_report.rs`** — otherwise the two runners will produce different results. diff --git a/README.md b/README.md index 56269266..c25cc1e7 100644 --- a/README.md +++ b/README.md @@ -554,7 +554,8 @@ Compatibility stubs — no V8 inspector in WASM.
node:module -- `require`, `createRequire`, `builtinModules`, `isBuiltin`, `runMain`, `_nodeModulePaths` +- `require`, `require.resolve`, `createRequire`, `builtinModules`, `isBuiltin`, `runMain`, `_nodeModulePaths` +- Package resolution supports `package.json` `main`, `exports` root/subpath maps, wildcard `exports` patterns, `imports` maps, and wildcard `imports` patterns. CJS resolution recognizes `golem`, `node`, `require`, `module-sync`, and `default` conditions; ESM resolution recognizes `golem`, `node`, `import`, `module-sync`, and `default`. Package `imports` can target relative files, external packages, and `node:` builtins.
diff --git a/crates/wasm-rquickjs/skeleton/Cargo.lock b/crates/wasm-rquickjs/skeleton/Cargo.lock index 54b26a34..8da5cab8 100644 --- a/crates/wasm-rquickjs/skeleton/Cargo.lock +++ b/crates/wasm-rquickjs/skeleton/Cargo.lock @@ -104,9 +104,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -142,15 +142,15 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 1.3.0", "syn", ] [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -230,12 +230,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.58" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "num-traits", ] @@ -470,9 +470,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -616,9 +616,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" @@ -767,7 +767,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-core", "futures-io", "parking", @@ -952,6 +952,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" @@ -978,9 +984,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1011,12 +1017,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1024,9 +1031,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1037,9 +1044,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1051,15 +1058,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1071,15 +1078,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1115,9 +1122,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1125,12 +1132,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1171,11 +1178,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1217,9 +1225,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1250,15 +1258,15 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "md-5" @@ -1272,9 +1280,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -1319,7 +1327,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", "smallvec", "zeroize", @@ -1442,18 +1450,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -1506,9 +1514,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "poly1305" @@ -1535,9 +1543,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -1611,9 +1619,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -1622,9 +1630,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -1670,9 +1678,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1693,9 +1701,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "relative-path" @@ -1768,6 +1776,7 @@ dependencies = [ "golem-websocket", "hkdf", "hmac", + "indexmap", "k256", "md-5", "num-bigint-dig", @@ -1776,7 +1785,7 @@ dependencies = [ "p384", "pbkdf2", "pkcs8", - "rand 0.9.2", + "rand 0.9.4", "rand_core 0.6.4", "ripemd", "rquickjs", @@ -1784,6 +1793,8 @@ dependencies = [ "rusqlite", "scrypt", "sec1", + "serde", + "serde_json", "sha1", "sha2", "sha3", @@ -1861,9 +1872,9 @@ dependencies = [ [[package]] name = "rsqlite-vfs" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", "thiserror", @@ -1945,9 +1956,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -1956,6 +1967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -1980,9 +1992,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2027,9 +2039,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest", "keccak", @@ -2041,6 +2053,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signature" version = "2.2.0" @@ -2059,9 +2077,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -2071,9 +2089,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "spin" @@ -2093,9 +2111,9 @@ dependencies = [ [[package]] name = "sqlite-wasm-rs" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" dependencies = [ "cc", "js-sys", @@ -2159,9 +2177,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -2178,9 +2196,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.9+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime", @@ -2190,18 +2208,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -2211,9 +2229,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -2251,9 +2269,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -2295,11 +2313,11 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen 0.57.1", ] [[package]] @@ -2313,9 +2331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -2326,9 +2344,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2336,9 +2354,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -2349,9 +2367,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] @@ -2441,9 +2459,9 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -2464,10 +2482,18 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "bitflags", "wit-bindgen-rust-macro 0.51.0", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +dependencies = [ + "bitflags", +] + [[package]] name = "wit-bindgen-core" version = "0.42.1" @@ -2637,9 +2663,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wstd" @@ -2675,9 +2701,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -2686,9 +2712,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -2698,18 +2724,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -2718,18 +2744,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -2745,9 +2771,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -2756,9 +2782,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -2767,9 +2793,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/crates/wasm-rquickjs/skeleton/Cargo.toml_ b/crates/wasm-rquickjs/skeleton/Cargo.toml_ index 1735b89c..d23e2435 100644 --- a/crates/wasm-rquickjs/skeleton/Cargo.toml_ +++ b/crates/wasm-rquickjs/skeleton/Cargo.toml_ @@ -96,6 +96,9 @@ ecdsa = { version = "0.16", default-features = false, features = ["signing", "ve signature = { version = "2", optional = true } futures = { version = "0.3.31", features = [] } futures-concurrency = "7.6.3" +indexmap = { version = "2", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" url = "2.5.7" uuid = { version = "1.18.1", features = ["v4"] } rand = "0.9.2" @@ -132,4 +135,3 @@ golem-websocket = { version = "0.0.2", optional = true } [patch.crates-io] rusqlite = { git = "https://github.com/golemcloud/rusqlite", branch = "v0.38.0-patched" } libsqlite3-sys = { git = "https://github.com/golemcloud/rusqlite", branch = "v0.38.0-patched" } - diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/assert.js b/crates/wasm-rquickjs/skeleton/src/builtin/assert.js index be9e18e2..b80eba89 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/assert.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/assert.js @@ -11,6 +11,8 @@ const inspectDiffOptions = { showHidden: false, showProxy: false, }; +const RegExpPrototypeExec = RegExp.prototype.exec; +const RegExpPrototypeSourceGetter = Object.getOwnPropertyDescriptor(RegExp.prototype, 'source').get; function inspectForDiff(value) { return inspect(value, inspectDiffOptions); @@ -21,6 +23,21 @@ function isError(e) { (e !== null && typeof e === 'object' && Object.prototype.toString.call(e) === '[object Error]'); } +function isRegExp(value) { + if (value === null || typeof value !== 'object') return false; + try { + RegExpPrototypeSourceGetter.call(value); + RegExpPrototypeExec.call(value, ''); + return true; + } catch (_) { + return false; + } +} + +function regexpTest(regexp, string) { + return RegExpPrototypeExec.call(regexp, String(string)) !== null; +} + function copyError(source) { const target = Object.assign( { __proto__: Object.getPrototypeOf(source) }, @@ -1881,7 +1898,7 @@ function compareExceptionKey(actual, expected, key, message, keys, fn) { actualSubset[currentKey] = actual[currentKey]; } if (currentKey in expected) { - if (typeof actual[currentKey] === 'string' && expected[currentKey] instanceof RegExp && expected[currentKey].test(actual[currentKey])) { + if (typeof actual[currentKey] === 'string' && isRegExp(expected[currentKey]) && regexpTest(expected[currentKey], actual[currentKey])) { expectedSubset[currentKey] = actual[currentKey]; } else { expectedSubset[currentKey] = expected[currentKey]; @@ -1915,9 +1932,9 @@ function expectedException(actual, expected, message, fn) { let throwError = false; if (typeof expected !== 'function') { - if (expected instanceof RegExp) { + if (isRegExp(expected)) { const str = String(actual); - if (expected.test(str)) { + if (regexpTest(expected, str)) { return; } @@ -1951,7 +1968,7 @@ function expectedException(actual, expected, message, fn) { for (let keyIdx = 0; keyIdx < keys.length; keyIdx++) { const key = keys[keyIdx]; - if (typeof actual[key] === 'string' && expected[key] instanceof RegExp && expected[key].test(actual[key])) { + if (typeof actual[key] === 'string' && isRegExp(expected[key]) && regexpTest(expected[key], actual[key])) { continue; } compareExceptionKey(actual, expected, key, message, keys, fn); @@ -2137,8 +2154,8 @@ function expectsError(stackStartFn, actual, error, message) { function hasMatchingError(actual, expected) { if (typeof expected !== 'function') { - if (expected instanceof RegExp) { - return expected.test(String(actual)); + if (isRegExp(expected)) { + return regexpTest(expected, String(actual)); } throw new ERR_INVALID_ARG_TYPE('expected', ['Function', 'RegExp'], expected); } @@ -2221,7 +2238,7 @@ function ifError(value) { } function match(string, regexp, message) { - if (!(regexp instanceof RegExp)) { + if (!isRegExp(regexp)) { let err = new ERR_INVALID_ARG_TYPE('regexp', 'RegExp', regexp); throw err; } @@ -2244,7 +2261,7 @@ function match(string, regexp, message) { generatedMessage: true }); } - if (!regexp.test(string)) { + if (!regexpTest(regexp, string)) { innerFail({ actual: string, expected: regexp, @@ -2257,7 +2274,7 @@ function match(string, regexp, message) { } function doesNotMatch(string, regexp, message) { - if (!(regexp instanceof RegExp)) { + if (!isRegExp(regexp)) { let err = new ERR_INVALID_ARG_TYPE('regexp', 'RegExp', regexp); throw err; } @@ -2280,7 +2297,7 @@ function doesNotMatch(string, regexp, message) { generatedMessage: true }); } - if (regexp.test(string)) { + if (regexpTest(regexp, string)) { innerFail({ actual: string, expected: regexp, diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/child_process.js b/crates/wasm-rquickjs/skeleton/src/builtin/child_process.js index 1b1fab2b..c7cc8b89 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/child_process.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/child_process.js @@ -121,7 +121,26 @@ function isInlineEvalOption(value) { } function execArgTakesValue(arg) { - return arg === '--openssl-config' || arg === '--input-type' || arg === '--require' || arg === '-r'; + return arg === '--openssl-config' || arg === '--input-type' || arg === '--require' || arg === '-r' || + arg === '--conditions' || arg === '-C'; +} + +function packageConditionsFromExecArgv(execArgv) { + const conditions = []; + function add(condition) { + if (condition) conditions.push(condition); + } + for (let i = 0; i < execArgv.length; i++) { + const arg = String(execArgv[i]); + if (arg.indexOf('--conditions=') === 0) { + add(arg.slice('--conditions='.length)); + } else if (arg === '--conditions' || arg === '-C') { + if (i + 1 < execArgv.length) { + add(String(execArgv[++i])); + } + } + } + return conditions; } function splitExecArgvAndInvocationArgs(args) { @@ -646,6 +665,8 @@ function runInline(command, args, options) { const oldArgv = process.argv.slice(); const oldExecArgv = Array.isArray(process.execArgv) ? process.execArgv.slice() : []; + const hadPackageConditions = Object.prototype.hasOwnProperty.call(globalThis, '__wasm_rquickjs_package_conditions'); + const oldPackageConditions = globalThis.__wasm_rquickjs_package_conditions; const oldArgv0 = process.argv0; const oldRequireModuleFeature = process.features && process.features.require_module; const oldCwd = process.cwd; @@ -688,6 +709,7 @@ function runInline(command, args, options) { try { process.argv = [String(command)].concat(invocationArgs); process.execArgv = execArgv; + globalThis.__wasm_rquickjs_package_conditions = packageConditionsFromExecArgv(execArgv); process.argv0 = String(command); if (process.features) { process.features.require_module = execArgv.indexOf('--no-experimental-require-module') === -1; @@ -948,6 +970,11 @@ function runInline(command, args, options) { } finally { process.argv = oldArgv; process.execArgv = oldExecArgv; + if (hadPackageConditions) { + globalThis.__wasm_rquickjs_package_conditions = oldPackageConditions; + } else { + delete globalThis.__wasm_rquickjs_package_conditions; + } process.argv0 = oldArgv0; if (process.features) { process.features.require_module = oldRequireModuleFeature; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/diagnostics_channel.js b/crates/wasm-rquickjs/skeleton/src/builtin/diagnostics_channel.js index 8fb11267..3b3fd121 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/diagnostics_channel.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/diagnostics_channel.js @@ -110,6 +110,14 @@ const TRACE_EVENTS = ['start', 'end', 'asyncStart', 'asyncEnd', 'error']; const TRACING_CHANNEL_CREATED = Symbol.for('wasm-rquickjs.internal.tracing_channel.created'); const tracingChannelCreatedCh = channel(TRACING_CHANNEL_CREATED); +function markAsyncContext(context) { + Object.defineProperty(context, '__dc_async', { + value: true, + configurable: true, + writable: true, + }); +} + class TracingChannel { constructor(nameOrChannels) { if (typeof nameOrChannels === 'string' || typeof nameOrChannels === 'symbol') { @@ -197,7 +205,7 @@ class TracingChannel { return start.runStores(context, () => { try { const promise = fn.apply(thisArg, args); - context.__dc_async = true; + markAsyncContext(context); end.publish(context); return Promise.resolve(promise).then( (result) => { @@ -275,7 +283,7 @@ class TracingChannel { return start.runStores(context, () => { try { const result = fn.apply(thisArg, args); - context.__dc_async = true; + markAsyncContext(context); return result; } catch (err) { context.error = err; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/dns.js b/crates/wasm-rquickjs/skeleton/src/builtin/dns.js index 1aea5513..509b04f4 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/dns.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/dns.js @@ -1,5 +1,7 @@ // node:dns implementation backed by wasi:sockets/ip-name-lookup import { resolve as native_resolve } from '__wasm_rquickjs_builtin/dns_native'; +import { ERR_INVALID_ARG_VALUE } from '__wasm_rquickjs_builtin/internal/errors'; +import { validatePort } from '__wasm_rquickjs_builtin/internal/validators'; import { isIP, isIPv4, isIPv6 } from 'node:net'; const NOT_SUPPORTED_ERROR_MSG = 'dns record type queries are not supported in WebAssembly environment'; @@ -66,6 +68,13 @@ function invalidRrtypeError(rrtype) { return err; } +function validateLookupServiceArgs(address, port) { + if (isIP(address) === 0) { + throw new ERR_INVALID_ARG_VALUE('address', address); + } + validatePort(port); +} + function filterByFamily(results, family) { if (family === 0) return results; return results.filter(r => r.family === family); @@ -302,6 +311,7 @@ export function lookupService(address, port, callback) { if (typeof callback !== 'function') { throw new TypeError('callback must be a function'); } + validateLookupServiceArgs(address, port); queueMicrotask(() => callback(Object.assign( new Error(`getnameinfo ${NOT_SUPPORTED_ERROR_MSG}`), { code: 'ENOTIMP' } @@ -412,6 +422,7 @@ export const promises = { }, lookupService(address, port) { + validateLookupServiceArgs(address, port); return new Promise((resolve, reject) => { lookupService(address, port, (err, hostname, service) => { if (err) return reject(err); diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/events.js b/crates/wasm-rquickjs/skeleton/src/builtin/events.js index bf9041f3..c15f2562 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/events.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/events.js @@ -767,16 +767,53 @@ EventEmitter.addAbortListener = function(signal, listener) { }; }; +EventEmitter = new Proxy(EventEmitter, { + defineProperty(target, property, descriptor) { + if (property === 'defaultMaxListeners') { + const current = Reflect.getOwnPropertyDescriptor(target, property); + if (current && current.configurable === false) { + throw new TypeError('Cannot redefine property: defaultMaxListeners'); + } + } + return Reflect.defineProperty(target, property, descriptor); + }, +}); + EventEmitter.EventEmitter = EventEmitter; -const once = EventEmitter.once; -const on = EventEmitter.on; -const getEventListeners = EventEmitter.getEventListeners; -const getMaxListeners = EventEmitter.getMaxListeners; -const setMaxListeners = EventEmitter.setMaxListeners; -const addAbortListener = EventEmitter.addAbortListener; -const errorMonitor = EventEmitter.errorMonitor; -const captureRejections = EventEmitter.captureRejections; +const _default = EventEmitter; + +let once = EventEmitter.once; +let on = EventEmitter.on; +let getEventListeners = EventEmitter.getEventListeners; +let getMaxListeners = EventEmitter.getMaxListeners; +let setMaxListeners = EventEmitter.setMaxListeners; +let addAbortListener = EventEmitter.addAbortListener; +let errorMonitor = EventEmitter.errorMonitor; +let captureRejections = EventEmitter.captureRejections; +export let defaultMaxListeners = EventEmitter.defaultMaxListeners; + +const _syncBuiltinESMExportsRegistry = globalThis.__wasm_rquickjs_sync_builtin_esm_exports || + Object.defineProperty(globalThis, '__wasm_rquickjs_sync_builtin_esm_exports', { + value: Object.create(null), + configurable: true, + }).__wasm_rquickjs_sync_builtin_esm_exports; + +_syncBuiltinESMExportsRegistry.events = function syncEventsBuiltinESMExports() { + EventEmitter = _default.EventEmitter; + Event = _default.Event; + EventTarget = _default.EventTarget; + CustomEvent = _default.CustomEvent; + once = _default.once; + on = _default.on; + getEventListeners = _default.getEventListeners; + getMaxListeners = _default.getMaxListeners; + setMaxListeners = _default.setMaxListeners; + addAbortListener = _default.addAbortListener; + errorMonitor = _default.errorMonitor; + captureRejections = _default.captureRejections; + defaultMaxListeners = _default.defaultMaxListeners; +}; export { EventEmitter, @@ -794,4 +831,4 @@ export { _eventTrusted, }; -export default EventEmitter; +export default _default; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/fs.js b/crates/wasm-rquickjs/skeleton/src/builtin/fs.js index 1d9eb59d..f7424124 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/fs.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/fs.js @@ -47,6 +47,7 @@ let _Readable = null; let _Writable = null; let _EventEmitter = null; let _PathModule = null; +let _UrlModule = null; function getStreamClasses() { if (!_Readable) { const stream = require('node:stream'); @@ -67,6 +68,12 @@ function getPathModule() { } return _PathModule; } +function getUrlModule() { + if (!_UrlModule) { + _UrlModule = require('node:url'); + } + return _UrlModule; +} // --- Constants --- const F_OK = 0; @@ -128,7 +135,7 @@ const HAS_LCHMOD = false; const FILE_HANDLE_IN_USE_SYMBOL = Symbol.for('__wasm_rquickjs.filehandleInUse'); const FILE_HANDLE_IN_USE_COUNT_SYMBOL = Symbol.for('__wasm_rquickjs.filehandleInUseCount'); -export const constants = { +export let constants = { F_OK, R_OK, W_OK, X_OK, O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND, O_DIRECTORY, O_NOATIME, O_NOFOLLOW, @@ -429,8 +436,7 @@ function validatePath(path, propName) { // Delegate to fileURLToPath for proper validation - it throws // ERR_INVALID_URL_SCHEME, ERR_INVALID_FILE_URL_HOST, ERR_INVALID_FILE_URL_PATH // matching Node.js behavior. - const urlModule = require('node:url'); - const converted = urlModule.fileURLToPath(path); + const converted = getUrlModule().fileURLToPath(path); if (converted.indexOf('\u0000') !== -1) { const err = new TypeError(`The argument '${propName || 'path'}' must be a string, Uint8Array, or URL without null bytes. Received ${JSON.stringify(converted)}`); err.code = 'ERR_INVALID_ARG_VALUE'; @@ -460,7 +466,7 @@ function pathToString(path) { } if (path instanceof URL) { if (path.protocol !== 'file:') return path.toString(); - return require('node:url').fileURLToPath(path); + return getUrlModule().fileURLToPath(path); } return String(path); } @@ -577,7 +583,7 @@ internalFsBinding.readdir = function readdir(path, encoding, withFileTypes, req) // --- Stats class --- -export function Stats(devOrObj, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs) { +export let Stats = function Stats(devOrObj, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs) { if (!(this instanceof Stats)) { return new Stats(devOrObj, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs); } @@ -616,7 +622,7 @@ export function Stats(devOrObj, mode, nlink, uid, gid, rdev, blksize, ino, size, this._isFile = statObj.isFile; this._isDirectory = statObj.isDirectory; this._isSymlink = statObj.isSymlink; -} +}; Stats.prototype._toBigInt = function() { const s = new Stats({ @@ -659,7 +665,7 @@ Stats.prototype.isSocket = function() { return false; }; // --- Dirent class --- -export class Dirent { +export let Dirent = class Dirent { constructor(name, fileType, parentPath) { this.name = name; this.parentPath = parentPath; @@ -674,11 +680,11 @@ export class Dirent { isCharacterDevice() { return this._fileType === UV_DIRENT_CHAR; } isFIFO() { return this._fileType === UV_DIRENT_FIFO; } isSocket() { return this._fileType === UV_DIRENT_SOCKET; } -} +}; // --- Dir class --- -export class Dir { +export let Dir = class Dir { constructor(path, entries) { if (path === undefined) { const err = new TypeError('The "path" argument must be of type string. Received undefined'); @@ -808,7 +814,7 @@ export class Dir { } }; } -} +}; const validEncodings = new Set([ 'utf8', 'utf-8', 'ascii', 'base64', 'hex', @@ -850,7 +856,7 @@ function decodeFileResult(bytes, encoding) { // --- Sync functions --- -export function readFileSync(path, options) { +export let readFileSync = function readFileSync(path, options) { if (typeof path !== 'number') validatePath(path); if (typeof options === 'string') { options = {encoding: options}; @@ -915,9 +921,9 @@ export function readFileSync(path, options) { } finally { closeSync(fd); } -} +}; -export function writeFileSync(path, data, options) { +export let writeFileSync = function writeFileSync(path, data, options) { if (typeof path !== 'number') validatePath(path); if (typeof options === 'string') { options = {encoding: options}; @@ -967,9 +973,9 @@ export function writeFileSync(path, data, options) { } } } -} +}; -export function appendFileSync(path, data, options) { +export let appendFileSync = function appendFileSync(path, data, options) { if (typeof path === 'number') { validateFd(path); } else { @@ -997,9 +1003,9 @@ export function appendFileSync(path, data, options) { } } } -} +}; -export function openSync(path, flags, mode) { +export let openSync = function openSync(path, flags, mode) { validatePath(path); flags = flagsToNumber(flags !== undefined ? flags : 'r'); mode = validateMode(mode, 'mode', 0o666); @@ -1013,17 +1019,17 @@ export function openSync(path, flags, mode) { _notifyFSWatchers(fullPath, 'rename'); } return result.fd; -} +}; -export function closeSync(fd) { +export let closeSync = function closeSync(fd) { validateFd(fd); const error = native.fs_close(fd); if (error) { throw createSystemError(error); } -} +}; -export function readSync(fd, buffer, offsetOrOptions, length, position) { +export let readSync = function readSync(fd, buffer, offsetOrOptions, length, position) { validateFd(fd); const argCount = arguments.length; @@ -1083,9 +1089,9 @@ export function readSync(fd, buffer, offsetOrOptions, length, position) { buffer[offset + i] = src[i]; } return bytesRead; -} +}; -export function writeSync(fd, bufferOrString, offsetOrPosition, lengthOrEncoding, position) { +export let writeSync = function writeSync(fd, bufferOrString, offsetOrPosition, lengthOrEncoding, position) { validateFd(fd); if (typeof bufferOrString === 'string') { @@ -1137,9 +1143,9 @@ export function writeSync(fd, bufferOrString, offsetOrPosition, lengthOrEncoding throw createSystemError(result.error); } return result.bytesWritten; -} +}; -export function ftruncateSync(fd, len) { +export let ftruncateSync = function ftruncateSync(fd, len) { validateFd(fd); if (len === undefined) { len = 0; @@ -1150,25 +1156,25 @@ export function ftruncateSync(fd, len) { if (error) { throw createSystemError(error); } -} +}; -export function fsyncSync(fd) { +export let fsyncSync = function fsyncSync(fd) { validateFd(fd); const error = native.fs_fsync(fd); if (error) { throw createSystemError(error); } -} +}; -export function fdatasyncSync(fd) { +export let fdatasyncSync = function fdatasyncSync(fd) { validateFd(fd); const error = native.fs_fdatasync(fd); if (error) { throw createSystemError(error); } -} +}; -export function statSync(path, options) { +export let statSync = function statSync(path, options) { validatePath(path); const result = native.fs_stat(pathToString(path)); if (result.error) { @@ -1179,9 +1185,9 @@ export function statSync(path, options) { } const s = new Stats(result.stat); return (options && options.bigint) ? s._toBigInt() : s; -} +}; -export function lstatSync(path, options) { +export let lstatSync = function lstatSync(path, options) { validatePath(path); const result = native.fs_lstat(pathToString(path)); if (result.error) { @@ -1192,9 +1198,9 @@ export function lstatSync(path, options) { } const s = new Stats(result.stat); return (options && options.bigint) ? s._toBigInt() : s; -} +}; -export function fstatSync(fd, options) { +export let fstatSync = function fstatSync(fd, options) { validateFd(fd); const result = native.fs_fstat(fd); if (result.error) { @@ -1202,7 +1208,7 @@ export function fstatSync(fd, options) { } const s = new Stats(result.stat); return (options && options.bigint) ? s._toBigInt() : s; -} +}; function makeStatFsResult(bigint) { if (bigint) { @@ -1227,16 +1233,16 @@ function makeStatFsResult(bigint) { }; } -export function statfsSync(path, options) { +export let statfsSync = function statfsSync(path, options) { validatePath(path); const result = native.fs_stat(pathToString(path)); if (result.error) { throw createSystemError(result.error); } return makeStatFsResult(options && options.bigint); -} +}; -export function readdirSync(path, options) { +export let readdirSync = function readdirSync(path, options) { validatePath(path); const opts = getOptions(options, {}); if (opts.encoding) validateEncoding(opts.encoding, 'encoding', true); @@ -1283,25 +1289,25 @@ export function readdirSync(path, options) { return entries.map(e => getBuffer().from(e)); } return entries; -} +}; -export function accessSync(path, mode) { +export let accessSync = function accessSync(path, mode) { validatePath(path); mode = mode !== undefined ? mode : F_OK; const error = native.fs_access(pathToString(path), mode); if (error) { throw createSystemError(error); } -} +}; -export function existsSync(path) { +export let existsSync = function existsSync(path) { try { if (typeof path !== 'string') return false; return native.fs_exists(path); } catch { return false; } -} +}; function realpathSyncImpl(path, options, useNative) { validatePath(path); @@ -1330,9 +1336,9 @@ function realpathSyncImpl(path, options, useNative) { return result.result; } -export function realpathSync(path, options) { +export let realpathSync = function realpathSync(path, options) { return realpathSyncImpl(path, options, false); -} +}; function realpathSyncNative(path, options) { return realpathSyncImpl(path, options, true); @@ -1340,7 +1346,7 @@ function realpathSyncNative(path, options) { realpathSync.native = realpathSyncNative; -export function truncateSync(path, len) { +export let truncateSync = function truncateSync(path, len) { if (typeof path === 'number') { return ftruncateSync(path, len); } @@ -1354,9 +1360,9 @@ export function truncateSync(path, len) { if (error) { throw createSystemError(error); } -} +}; -export function copyFileSync(src, dest, mode) { +export let copyFileSync = function copyFileSync(src, dest, mode) { validatePath(src, 'src'); validatePath(dest, 'dest'); const copyMode = validateCopyFileMode(mode); @@ -1378,27 +1384,27 @@ export function copyFileSync(src, dest, mode) { throw createCopyFileErrorFromNative(error, srcPath, destPath); } _notifyFSWatchers(destPath, 'rename'); -} +}; -export function linkSync(existingPath, newPath) { +export let linkSync = function linkSync(existingPath, newPath) { validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); const error = native.fs_link(existingPath, newPath); if (error) { throw createSystemError(error); } -} +}; -export function symlinkSync(target, path, type) { +export let symlinkSync = function symlinkSync(target, path, type) { validatePath(target, 'target'); validatePath(path, 'path'); const error = native.fs_symlink(target, path); if (error) { throw createSystemError(error); } -} +}; -export function readlinkSync(path, options) { +export let readlinkSync = function readlinkSync(path, options) { validatePath(path); const opts = getOptions(options, {}); if (opts.encoding) validateEncoding(opts.encoding, 'encoding', true); @@ -1411,31 +1417,31 @@ export function readlinkSync(path, options) { return getBuffer().from(result.result); } return result.result; -} +}; -export function chmodSync(path, mode) { +export let chmodSync = function chmodSync(path, mode) { validatePath(path); mode = validateMode(mode, 'mode', undefined); const error = native.fs_chmod(path, mode); if (error) { throw createSystemError(error); } -} +}; -export function fchmodSync(fd, mode) { +export let fchmodSync = function fchmodSync(fd, mode) { validateFd(fd); mode = validateMode(mode, 'mode', undefined); const error = native.fs_fchmod(fd, mode); if (error) { throw createSystemError(error); } -} +}; -export function lchmodSync(path, mode) { +export let lchmodSync = function lchmodSync(path, mode) { chmodSync(path, mode); -} +}; -export function chownSync(path, uid, gid) { +export let chownSync = function chownSync(path, uid, gid) { validatePath(path); validateUid(uid, 'uid'); validateUid(gid, 'gid'); @@ -1443,9 +1449,9 @@ export function chownSync(path, uid, gid) { if (error) { throw createSystemError(error); } -} +}; -export function fchownSync(fd, uid, gid) { +export let fchownSync = function fchownSync(fd, uid, gid) { validateFd(fd); validateUid(uid, 'uid'); validateUid(gid, 'gid'); @@ -1453,9 +1459,9 @@ export function fchownSync(fd, uid, gid) { if (error) { throw createSystemError(error); } -} +}; -export function lchownSync(path, uid, gid) { +export let lchownSync = function lchownSync(path, uid, gid) { validatePath(path); validateUid(uid, 'uid'); validateUid(gid, 'gid'); @@ -1463,9 +1469,9 @@ export function lchownSync(path, uid, gid) { if (error) { throw createSystemError(error); } -} +}; -export function utimesSync(path, atime, mtime) { +export let utimesSync = function utimesSync(path, atime, mtime) { validatePath(path); const atimeSecs = (atime instanceof Date) ? atime.getTime() / 1000 : Number(atime); const mtimeSecs = (mtime instanceof Date) ? mtime.getTime() / 1000 : Number(mtime); @@ -1473,9 +1479,9 @@ export function utimesSync(path, atime, mtime) { if (error) { throw createSystemError(error); } -} +}; -export function futimesSync(fd, atime, mtime) { +export let futimesSync = function futimesSync(fd, atime, mtime) { validateFd(fd); const atimeSecs = (atime instanceof Date) ? atime.getTime() / 1000 : Number(atime); const mtimeSecs = (mtime instanceof Date) ? mtime.getTime() / 1000 : Number(mtime); @@ -1483,9 +1489,9 @@ export function futimesSync(fd, atime, mtime) { if (error) { throw createSystemError(error); } -} +}; -export function lutimesSync(path, atime, mtime) { +export let lutimesSync = function lutimesSync(path, atime, mtime) { validatePath(path); const atimeSecs = (atime instanceof Date) ? atime.getTime() / 1000 : Number(atime); const mtimeSecs = (mtime instanceof Date) ? mtime.getTime() / 1000 : Number(mtime); @@ -1493,9 +1499,9 @@ export function lutimesSync(path, atime, mtime) { if (error) { throw createSystemError(error); } -} +}; -export function unlinkSync(path) { +export let unlinkSync = function unlinkSync(path) { validatePath(path); const fullPath = pathToString(path); const error = native.unlink(fullPath); @@ -1503,9 +1509,9 @@ export function unlinkSync(path) { throw createSystemError(error); } _notifyFSWatchers(fullPath, 'rename'); -} +}; -export function renameSync(oldPath, newPath) { +export let renameSync = function renameSync(oldPath, newPath) { validatePath(oldPath, 'oldPath'); validatePath(newPath, 'newPath'); const oldPathString = pathToString(oldPath); @@ -1516,9 +1522,9 @@ export function renameSync(oldPath, newPath) { } _notifyFSWatchers(oldPathString, 'rename'); _notifyFSWatchers(newPathString, 'rename'); -} +}; -export function mkdirSync(path, options) { +export let mkdirSync = function mkdirSync(path, options) { validatePath(path); const { recursive, mode } = parseMkdirOptions(options); const pathString = pathToString(path); @@ -1531,7 +1537,7 @@ export function mkdirSync(path, options) { _notifyFSWatchers(pathString, 'rename'); if (recursive) return firstCreatedPath; return undefined; -} +}; function _rimrafSync(dirPath) { const entries = readdirSync(dirPath, { withFileTypes: true }); @@ -1547,7 +1553,7 @@ function _rimrafSync(dirPath) { _default.rmdirSync(dirPath); } -export function rmdirSync(path, options) { +export let rmdirSync = function rmdirSync(path, options) { validatePath(path); if (options && options.recursive) { path = pathToString(path); @@ -1570,9 +1576,9 @@ export function rmdirSync(path, options) { if (error) throw createSystemError(error); _notifyFSWatchers(pathString, 'rename'); } -} +}; -export function rmSync(path, options) { +export let rmSync = function rmSync(path, options) { validatePath(path); path = pathToString(path); const recursive = options && options.recursive || false; @@ -1582,9 +1588,9 @@ export function rmSync(path, options) { throw createSystemError(error); } _notifyFSWatchers(path, 'rename'); -} +}; -export function mkdtempSync(prefix, options) { +export let mkdtempSync = function mkdtempSync(prefix, options) { validateMkdtempPrefix(prefix); const opts = getOptions(options, {}); if (opts.encoding) validateEncoding(opts.encoding, 'encoding', true); @@ -1597,19 +1603,19 @@ export function mkdtempSync(prefix, options) { return getBuffer().from(result.result); } return result.result; -} +}; -export function opendirSync(path, options) { +export let opendirSync = function opendirSync(path, options) { validatePath(path); validateOpendirOptions(options); const recursive = options && options.recursive ? true : false; const entries = readdirSync(path, { withFileTypes: true, recursive }); return new Dir(path, entries); -} +}; // --- Callback (async) functions --- -export function readFile(path, optionsOrCallback, callback) { +export let readFile = function readFile(path, optionsOrCallback, callback) { if (typeof path !== 'number') validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -1659,9 +1665,9 @@ export function readFile(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function writeFile(path, data, optionsOrCallback, callback) { +export let writeFile = function writeFile(path, data, optionsOrCallback, callback) { if (typeof path !== 'number') validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -1719,9 +1725,9 @@ export function writeFile(path, data, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function appendFile(path, data, optionsOrCallback, callback) { +export let appendFile = function appendFile(path, data, optionsOrCallback, callback) { if (typeof path === 'number') { validateFd(path); } else { @@ -1764,9 +1770,9 @@ export function appendFile(path, data, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function open(path, flagsOrCallback, modeOrCallback, callback) { +export let open = function open(path, flagsOrCallback, modeOrCallback, callback) { validatePath(path); let flags = 'r'; let mode = 0o666; @@ -1799,9 +1805,9 @@ export function open(path, flagsOrCallback, modeOrCallback, callback) { cb(err); } }); -} +}; -export function close(fd, callback) { +export let close = function close(fd, callback) { validateFd(fd); if (callback !== undefined && typeof callback !== 'function') { const err = new TypeError(`The "callback" argument must be of type function. Received ${describeType(callback)}`); @@ -1820,9 +1826,9 @@ export function close(fd, callback) { cb(err); } }); -} +}; -export function read(fd, bufferOrOptions, offsetOrCallback, length, position, callback) { +export let read = function read(fd, bufferOrOptions, offsetOrCallback, length, position, callback) { validateFd(fd); let buffer, offset, cb; @@ -1913,9 +1919,9 @@ export function read(fd, bufferOrOptions, offsetOrCallback, length, position, ca cb(err, 0, buffer); } }); -} +}; -export function write(fd, bufferOrString, offsetOrPosition, lengthOrEncoding, positionOrCallback, callback) { +export let write = function write(fd, bufferOrString, offsetOrPosition, lengthOrEncoding, positionOrCallback, callback) { validateFd(fd); let cb; if (typeof bufferOrString === 'string') { @@ -2046,9 +2052,9 @@ export function write(fd, bufferOrString, offsetOrPosition, lengthOrEncoding, po cb(err, 0, bufferOrString); } }); -} +}; -export function stat(path, optionsOrCallback, callback) { +export let stat = function stat(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2064,9 +2070,9 @@ export function stat(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function lstat(path, optionsOrCallback, callback) { +export let lstat = function lstat(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2082,9 +2088,9 @@ export function lstat(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function statfs(path, optionsOrCallback, callback) { +export let statfs = function statfs(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2100,9 +2106,9 @@ export function statfs(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function fstat(fd, optionsOrCallback, callback) { +export let fstat = function fstat(fd, optionsOrCallback, callback) { validateFd(fd); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2118,9 +2124,9 @@ export function fstat(fd, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function ftruncate(fd, lenOrCallback, callback) { +export let ftruncate = function ftruncate(fd, lenOrCallback, callback) { validateFd(fd); let len = 0; let cb; @@ -2142,9 +2148,9 @@ export function ftruncate(fd, lenOrCallback, callback) { cb(err); } }); -} +}; -export function fsync(fd, callback) { +export let fsync = function fsync(fd, callback) { validateCallback(callback); queueMicrotask(() => { try { @@ -2154,9 +2160,9 @@ export function fsync(fd, callback) { callback(err); } }); -} +}; -export function fdatasync(fd, callback) { +export let fdatasync = function fdatasync(fd, callback) { validateCallback(callback); queueMicrotask(() => { try { @@ -2166,9 +2172,9 @@ export function fdatasync(fd, callback) { callback(err); } }); -} +}; -export function readdir(path, optionsOrCallback, callback) { +export let readdir = function readdir(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2253,9 +2259,9 @@ export function readdir(path, optionsOrCallback, callback) { } }; internalFsBinding.readdir(pathStr, opts.encoding, withFileTypes, req); -} +}; -export function access(path, modeOrCallback, callback) { +export let access = function access(path, modeOrCallback, callback) { validatePath(path); let mode = F_OK; let cb; @@ -2274,9 +2280,9 @@ export function access(path, modeOrCallback, callback) { cb(err); } }); -} +}; -export function exists(path, callback) { +export let exists = function exists(path, callback) { if (typeof callback !== 'function') { throw Object.assign( new TypeError(`Callback must be a function. Received ${typeof callback}`), @@ -2286,9 +2292,9 @@ export function exists(path, callback) { queueMicrotask(() => { callback(existsSync(path)); }); -} +}; -export function realpath(path, optionsOrCallback, callback) { +export let realpath = function realpath(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2316,7 +2322,7 @@ export function realpath(path, optionsOrCallback, callback) { } }); } -} +}; function realpathNative(path, optionsOrCallback, callback) { validatePath(path); @@ -2340,7 +2346,7 @@ function realpathNative(path, optionsOrCallback, callback) { realpath.native = realpathNative; -export function truncate(path, lenOrCallback, callback) { +export let truncate = function truncate(path, lenOrCallback, callback) { if (typeof path === 'number') { return ftruncate(path, lenOrCallback, callback); } @@ -2365,9 +2371,9 @@ export function truncate(path, lenOrCallback, callback) { cb(err); } }); -} +}; -export function copyFile(src, dest, modeOrCallback, callback) { +export let copyFile = function copyFile(src, dest, modeOrCallback, callback) { validatePath(src, 'src'); validatePath(dest, 'dest'); let mode = 0; @@ -2387,9 +2393,9 @@ export function copyFile(src, dest, modeOrCallback, callback) { cb(err); } }); -} +}; -export function link(existingPath, newPath, callback) { +export let link = function link(existingPath, newPath, callback) { validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); validateCallback(callback); @@ -2401,9 +2407,9 @@ export function link(existingPath, newPath, callback) { callback(err); } }); -} +}; -export function symlink(target, path, typeOrCallback, callback) { +export let symlink = function symlink(target, path, typeOrCallback, callback) { validatePath(target, 'target'); validatePath(path, 'path'); let cb; @@ -2421,9 +2427,9 @@ export function symlink(target, path, typeOrCallback, callback) { cb(err); } }); -} +}; -export function readlink(path, optionsOrCallback, callback) { +export let readlink = function readlink(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2441,9 +2447,9 @@ export function readlink(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function chmod(path, mode, callback) { +export let chmod = function chmod(path, mode, callback) { validatePath(path); validateCallback(callback); queueMicrotask(() => { @@ -2454,9 +2460,9 @@ export function chmod(path, mode, callback) { callback(err); } }); -} +}; -export function fchmod(fd, mode, callback) { +export let fchmod = function fchmod(fd, mode, callback) { validateFd(fd); mode = validateMode(mode, 'mode', undefined); validateCallback(callback); @@ -2468,9 +2474,9 @@ export function fchmod(fd, mode, callback) { callback(err); } }); -} +}; -export function lchmod(path, mode, callback) { +export let lchmod = function lchmod(path, mode, callback) { validateCallback(callback); queueMicrotask(() => { try { @@ -2480,9 +2486,9 @@ export function lchmod(path, mode, callback) { callback(err); } }); -} +}; -export function chown(path, uid, gid, callback) { +export let chown = function chown(path, uid, gid, callback) { validatePath(path); validateUid(uid, 'uid'); validateUid(gid, 'gid'); @@ -2495,9 +2501,9 @@ export function chown(path, uid, gid, callback) { callback(err); } }); -} +}; -export function fchown(fd, uid, gid, callback) { +export let fchown = function fchown(fd, uid, gid, callback) { validateFd(fd); validateUid(uid, 'uid'); validateUid(gid, 'gid'); @@ -2510,9 +2516,9 @@ export function fchown(fd, uid, gid, callback) { callback(err); } }); -} +}; -export function lchown(path, uid, gid, callback) { +export let lchown = function lchown(path, uid, gid, callback) { validatePath(path); validateUid(uid, 'uid'); validateUid(gid, 'gid'); @@ -2525,9 +2531,9 @@ export function lchown(path, uid, gid, callback) { callback(err); } }); -} +}; -export function utimes(path, atime, mtime, callback) { +export let utimes = function utimes(path, atime, mtime, callback) { validatePath(path); validateCallback(callback); queueMicrotask(() => { @@ -2538,9 +2544,9 @@ export function utimes(path, atime, mtime, callback) { callback(err); } }); -} +}; -export function futimes(fd, atime, mtime, callback) { +export let futimes = function futimes(fd, atime, mtime, callback) { validateFd(fd); validateCallback(callback); queueMicrotask(() => { @@ -2551,9 +2557,9 @@ export function futimes(fd, atime, mtime, callback) { callback(err); } }); -} +}; -export function lutimes(path, atime, mtime, callback) { +export let lutimes = function lutimes(path, atime, mtime, callback) { validatePath(path); validateCallback(callback); queueMicrotask(() => { @@ -2564,9 +2570,9 @@ export function lutimes(path, atime, mtime, callback) { callback(err); } }); -} +}; -export function unlink(path, callback) { +export let unlink = function unlink(path, callback) { validatePath(path); validateCallback(callback); const error = native.unlink(pathToString(path)); @@ -2575,9 +2581,9 @@ export function unlink(path, callback) { } else { queueMicrotask(() => callback(null)); } -} +}; -export function rename(oldPath, newPath, callback) { +export let rename = function rename(oldPath, newPath, callback) { validatePath(oldPath, 'oldPath'); validatePath(newPath, 'newPath'); validateCallback(callback); @@ -2589,9 +2595,9 @@ export function rename(oldPath, newPath, callback) { } else { queueMicrotask(() => callback(null)); } -} +}; -export function mkdir(path, optionsOrCallback, callback) { +export let mkdir = function mkdir(path, optionsOrCallback, callback) { validatePath(path); let cb; let options; @@ -2616,9 +2622,9 @@ export function mkdir(path, optionsOrCallback, callback) { cb(null, recursive ? firstCreatedPath : undefined); } }); -} +}; -export function rmdir(path, optionsOrCallback, callback) { +export let rmdir = function rmdir(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2634,9 +2640,9 @@ export function rmdir(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function rm(path, optionsOrCallback, callback) { +export let rm = function rm(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2652,9 +2658,9 @@ export function rm(path, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function mkdtemp(prefix, optionsOrCallback, callback) { +export let mkdtemp = function mkdtemp(prefix, optionsOrCallback, callback) { validateMkdtempPrefix(prefix); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2672,9 +2678,9 @@ export function mkdtemp(prefix, optionsOrCallback, callback) { cb(err); } }); -} +}; -export function opendir(path, optionsOrCallback, callback) { +export let opendir = function opendir(path, optionsOrCallback, callback) { validatePath(path); if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2691,7 +2697,7 @@ export function opendir(path, optionsOrCallback, callback) { cb(err); } }); -} +}; // --- FSWatcher (polling-based, since WASI has no native inotify/kqueue) --- // Synchronous notification registry: mutating fs operations notify active watchers @@ -2741,7 +2747,7 @@ function _snapshotDir(dir, recursive) { return entries; } -export class FSWatcher { +export let FSWatcher = class FSWatcher { constructor() { this._listeners = {}; this._timer = null; @@ -2858,7 +2864,7 @@ export class FSWatcher { if (this._timer && typeof this._timer.unref === 'function') this._timer.unref(); return this; } -} +}; const _statWatchers = new Map(); @@ -2879,7 +2885,7 @@ function _tryStat(filename) { return new Stats(result.stat); } -export class StatWatcher { +export let StatWatcher = class StatWatcher { constructor() { this._eventListeners = {}; this._timer = null; @@ -2960,9 +2966,9 @@ export class StatWatcher { if (this._timer) this._timer.unref(); return this; } -} +}; -export function watch(filename, optionsOrListener, listener) { +export let watch = function watch(filename, optionsOrListener, listener) { validatePath(filename, 'filename'); if (typeof optionsOrListener === 'function') { listener = optionsOrListener; @@ -3002,9 +3008,9 @@ export function watch(filename, optionsOrListener, listener) { } return watcher; -} +}; -export function watchFile(filename, optionsOrListener, listener) { +export let watchFile = function watchFile(filename, optionsOrListener, listener) { validatePath(filename, 'filename'); filename = pathToString(filename); @@ -3027,9 +3033,9 @@ export function watchFile(filename, optionsOrListener, listener) { } watcher.addListener('change', listener); return watcher; -} +}; -export function unwatchFile(filename, listener) { +export let unwatchFile = function unwatchFile(filename, listener) { validatePath(filename, 'filename'); filename = pathToString(filename); const watcher = _statWatchers.get(filename); @@ -3045,13 +3051,13 @@ export function unwatchFile(filename, listener) { watcher.stop(); _statWatchers.delete(filename); } -} +}; // --- ReadStream / WriteStream --- let _readStreamProtoInited = false; -export function ReadStream(path, options) { +export let ReadStream = function ReadStream(path, options) { if (!(this instanceof ReadStream)) return new ReadStream(path, options); if (options !== undefined && options !== null && typeof options !== 'object' && typeof options !== 'string') { @@ -3187,7 +3193,7 @@ export function ReadStream(path, options) { if (!self.destroyed) self.destroy(); }); } -} +}; ReadStream.prototype._construct = function(callback) { if (typeof this.fd === 'number') { @@ -3354,7 +3360,7 @@ Object.defineProperty(ReadStream.prototype, 'closed', { let _writeStreamProtoInited = false; -export function WriteStream(path, options) { +export let WriteStream = function WriteStream(path, options) { if (!(this instanceof WriteStream)) return new WriteStream(path, options); if (options !== undefined && options !== null && typeof options !== 'object' && typeof options !== 'string') { @@ -3457,7 +3463,7 @@ export function WriteStream(path, options) { if (!self.destroyed) self.destroy(); }); } -} +}; WriteStream.prototype._construct = function(callback) { if (typeof this.fd === 'number') { @@ -3612,17 +3618,17 @@ Object.defineProperty(WriteStream.prototype, 'closed', { configurable: true }); -export function createReadStream(path, options) { +export let createReadStream = function createReadStream(path, options) { return new ReadStream(path, options); -} +}; -export function createWriteStream(path, options) { +export let createWriteStream = function createWriteStream(path, options) { return new WriteStream(path, options); -} +}; // --- readv/writev stubs --- -export function readv(fd, buffers, positionOrCallback, callback) { +export let readv = function readv(fd, buffers, positionOrCallback, callback) { validateFd(fd); let position = null; let cb; @@ -3660,9 +3666,9 @@ export function readv(fd, buffers, positionOrCallback, callback) { cb(err, 0, buffers); } }); -} +}; -export function writev(fd, buffers, positionOrCallback, callback) { +export let writev = function writev(fd, buffers, positionOrCallback, callback) { validateFd(fd); let position = null; let cb; @@ -3698,9 +3704,9 @@ export function writev(fd, buffers, positionOrCallback, callback) { cb(err, 0, buffers); } }); -} +}; -export function readvSync(fd, buffers, position) { +export let readvSync = function readvSync(fd, buffers, position) { validateFd(fd); if (!Array.isArray(buffers)) { const err = new TypeError('The "buffers" argument must be an instance of Array. Received ' + describeType(buffers)); @@ -3724,9 +3730,9 @@ export function readvSync(fd, buffers, position) { if (bytesRead < buf.byteLength) break; } return totalRead; -} +}; -export function writevSync(fd, buffers, position) { +export let writevSync = function writevSync(fd, buffers, position) { validateFd(fd); if (!Array.isArray(buffers)) { const err = new TypeError('The "buffers" argument must be an instance of Array. Received ' + describeType(buffers)); @@ -3748,11 +3754,11 @@ export function writevSync(fd, buffers, position) { if (pos !== null) pos += written; } return totalWritten; -} +}; // --- cp stub --- -export function cpSync(src, dest, options) { +export let cpSync = function cpSync(src, dest, options) { const recursive = options && options.recursive; const srcStat = statSync(src); if (srcStat.isDirectory()) { @@ -3772,9 +3778,9 @@ export function cpSync(src, dest, options) { } else { copyFileSync(src, dest); } -} +}; -export function cp(src, dest, optionsOrCallback, callback) { +export let cp = function cp(src, dest, optionsOrCallback, callback) { if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; optionsOrCallback = {}; @@ -3789,7 +3795,7 @@ export function cp(src, dest, optionsOrCallback, callback) { cb(err); } }); -} +}; // --- util.promisify support --- @@ -3975,11 +3981,11 @@ class FileBackedBlobSlice { } } -export async function openAsBlob(path, options) { +export let openAsBlob = async function openAsBlob(path, options) { validatePath(path); const st = statSync(path); return new FileBackedBlob(pathToString(path), st.size, st.mtimeMs); -} +}; // Expose the symbol for structuredClone integration export { _kFileBackedBlob }; @@ -3987,7 +3993,7 @@ export { _kFileBackedBlob }; // Named re-export so `import { promises } from 'node:fs'` works. // We cannot call getPromises() at module evaluation time because `require` is // not yet available, so we export a proxy object that lazily delegates. -export const promises = new Proxy({}, { +export let promises = new Proxy({}, { get(_, prop) { return getPromises()[prop]; }, set(_, prop, value) { getPromises()[prop] = value; return true; }, has(_, prop) { return prop in getPromises(); }, @@ -3997,7 +4003,7 @@ export const promises = new Proxy({}, { // --- Internal helpers --- -function _toUnixTimestamp(time, name = 'time') { +export let _toUnixTimestamp = function _toUnixTimestamp(time, name = 'time') { if (typeof time === 'string' && +time == time) { return +time; } @@ -4011,7 +4017,7 @@ function _toUnixTimestamp(time, name = 'time') { return time.getTime() / 1000; } throw new ERR_INVALID_ARG_TYPE(name, ['Date', 'Time in seconds'], time); -} +}; // --- Default export --- @@ -4120,4 +4126,113 @@ const _default = { _toUnixTimestamp, }; +const _syncBuiltinESMExportsRegistry = globalThis.__wasm_rquickjs_sync_builtin_esm_exports || + Object.defineProperty(globalThis, '__wasm_rquickjs_sync_builtin_esm_exports', { + value: Object.create(null), + configurable: true, + }).__wasm_rquickjs_sync_builtin_esm_exports; + +_syncBuiltinESMExportsRegistry.fs = function syncFsBuiltinESMExports() { + constants = _default.constants; + Stats = _default.Stats; + Dirent = _default.Dirent; + Dir = _default.Dir; + FSWatcher = _default.FSWatcher; + StatWatcher = _default.StatWatcher; + readFileSync = _default.readFileSync; + writeFileSync = _default.writeFileSync; + appendFileSync = _default.appendFileSync; + openSync = _default.openSync; + closeSync = _default.closeSync; + readSync = _default.readSync; + writeSync = _default.writeSync; + ftruncateSync = _default.ftruncateSync; + fsyncSync = _default.fsyncSync; + fdatasyncSync = _default.fdatasyncSync; + statSync = _default.statSync; + lstatSync = _default.lstatSync; + fstatSync = _default.fstatSync; + statfsSync = _default.statfsSync; + readdirSync = _default.readdirSync; + accessSync = _default.accessSync; + existsSync = _default.existsSync; + realpathSync = _default.realpathSync; + truncateSync = _default.truncateSync; + copyFileSync = _default.copyFileSync; + linkSync = _default.linkSync; + symlinkSync = _default.symlinkSync; + readlinkSync = _default.readlinkSync; + chmodSync = _default.chmodSync; + fchmodSync = _default.fchmodSync; + lchmodSync = _default.lchmodSync; + chownSync = _default.chownSync; + fchownSync = _default.fchownSync; + lchownSync = _default.lchownSync; + utimesSync = _default.utimesSync; + futimesSync = _default.futimesSync; + lutimesSync = _default.lutimesSync; + unlinkSync = _default.unlinkSync; + renameSync = _default.renameSync; + mkdirSync = _default.mkdirSync; + rmdirSync = _default.rmdirSync; + rmSync = _default.rmSync; + mkdtempSync = _default.mkdtempSync; + opendirSync = _default.opendirSync; + readFile = _default.readFile; + writeFile = _default.writeFile; + appendFile = _default.appendFile; + open = _default.open; + close = _default.close; + read = _default.read; + write = _default.write; + stat = _default.stat; + lstat = _default.lstat; + statfs = _default.statfs; + fstat = _default.fstat; + ftruncate = _default.ftruncate; + fsync = _default.fsync; + fdatasync = _default.fdatasync; + readdir = _default.readdir; + access = _default.access; + exists = _default.exists; + realpath = _default.realpath; + truncate = _default.truncate; + copyFile = _default.copyFile; + link = _default.link; + symlink = _default.symlink; + readlink = _default.readlink; + chmod = _default.chmod; + fchmod = _default.fchmod; + lchmod = _default.lchmod; + chown = _default.chown; + fchown = _default.fchown; + lchown = _default.lchown; + utimes = _default.utimes; + futimes = _default.futimes; + lutimes = _default.lutimes; + unlink = _default.unlink; + rename = _default.rename; + mkdir = _default.mkdir; + rmdir = _default.rmdir; + rm = _default.rm; + mkdtemp = _default.mkdtemp; + opendir = _default.opendir; + watch = _default.watch; + watchFile = _default.watchFile; + unwatchFile = _default.unwatchFile; + ReadStream = _default.ReadStream; + WriteStream = _default.WriteStream; + createReadStream = _default.createReadStream; + createWriteStream = _default.createWriteStream; + readv = _default.readv; + writev = _default.writev; + readvSync = _default.readvSync; + writevSync = _default.writevSync; + cpSync = _default.cpSync; + cp = _default.cp; + openAsBlob = _default.openAsBlob; + promises = _default.promises; + _toUnixTimestamp = _default._toUnixTimestamp; +}; + export default _default; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/fs_promises.js b/crates/wasm-rquickjs/skeleton/src/builtin/fs_promises.js index ce2de3be..c92bf39e 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/fs_promises.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/fs_promises.js @@ -59,6 +59,7 @@ function getStats() { let _EventEmitter = null; let _PathModule = null; +let _UrlModule = null; function getEventEmitter() { if (!_EventEmitter) { const events = require('node:events'); @@ -73,6 +74,12 @@ function getPathModule() { } return _PathModule; } +function getUrlModule() { + if (!_UrlModule) { + _UrlModule = require('node:url'); + } + return _UrlModule; +} function wrapStat(statObj, options) { const S = getStats(); @@ -218,8 +225,7 @@ function pathToString(path) { if (typeof path === 'string') return path; if (getBuffer() && path instanceof getBuffer()) return path.toString(); if (path instanceof URL) { - if (path.protocol !== 'file:') return path.toString(); - return path.pathname; + return getUrlModule().fileURLToPath(path); } return String(path); } @@ -725,22 +731,23 @@ export async function appendFile(path, data, options) { return path.appendFile(data, options); } + const pathString = pathToString(path); const flush = options && typeof options === 'object' ? options.flush : undefined; validateFlush(flush); validateAppendFileData(data); let error; if (typeof data === 'string') { - error = native.fs_append_file_string(path, data); + error = native.fs_append_file_string(pathString, data); } else { const dataArray = new Uint8Array(data.buffer || data, data.byteOffset || 0, data.byteLength || data.length); - error = native.fs_append_file(path, dataArray); + error = native.fs_append_file(pathString, dataArray); } if (error) throw createSystemError(error); if (flush === true) { const fs = require('node:fs'); - const fd = fs.openSync(path, 'r'); + const fd = fs.openSync(pathString, 'r'); try { fs.fsyncSync(fd); } finally { @@ -750,12 +757,12 @@ export async function appendFile(path, data, options) { } export async function unlink(path) { - const error = native.unlink(path); + const error = native.unlink(pathToString(path)); if (error) throw createSystemError(error); } export async function rename(oldPath, newPath) { - const error = native.rename(oldPath, newPath); + const error = native.rename(pathToString(oldPath), pathToString(newPath)); if (error) throw createSystemError(error); } @@ -770,20 +777,21 @@ export async function mkdir(path, options) { } export async function rmdir(path, options) { + const pathString = pathToString(path); if (options && options.recursive) { - const st = native.fs_stat(path); + const st = native.fs_stat(pathString); if (!st.error && !st.stat.isDirectory) { - const err = new Error(`ENOTDIR: not a directory, rmdir '${path}'`); + const err = new Error(`ENOTDIR: not a directory, rmdir '${pathString}'`); err.code = 'ENOTDIR'; err.errno = -20; err.syscall = 'rmdir'; - err.path = path; + err.path = pathString; throw err; } - const error = native.fs_rm(path, true, false); + const error = native.fs_rm(pathString, true, false); if (error) throw createSystemError(error); } else { - const error = native.fs_rmdir(path); + const error = native.fs_rmdir(pathString); if (error) throw createSystemError(error); } } @@ -796,21 +804,22 @@ export async function rm(path, options) { } export async function stat(path, options) { - const result = native.fs_stat(path); + const result = native.fs_stat(pathToString(path)); if (result.error) throw createSystemError(result.error); return wrapStat(result.stat, options); } export async function lstat(path, options) { - const result = native.fs_lstat(path); + const result = native.fs_lstat(pathToString(path)); if (result.error) throw createSystemError(result.error); return wrapStat(result.stat, options); } export async function readdir(path, options) { + const pathString = pathToString(path); const withFileTypes = options && options.withFileTypes || false; const recursive = options && options.recursive || false; - const result = native.fs_readdir(path, withFileTypes); + const result = native.fs_readdir(pathString, withFileTypes); if (result.error) throw createSystemError(result.error); if (withFileTypes) { const sortedEntries = [...result.entries].sort((left, right) => { @@ -837,13 +846,13 @@ export async function readdir(path, options) { isSocket() { return this._fileType === 5; }, }; }; - const dirents = sortedEntries.map(e => makeDirent(e, path)); + const dirents = sortedEntries.map(e => makeDirent(e, pathString)); if (recursive) { const all = []; for (const dirent of dirents) { all.push(dirent); if (dirent.isDirectory()) { - const subPath = path + '/' + dirent.name; + const subPath = pathString + '/' + dirent.name; try { const subEntries = await readdir(subPath, { withFileTypes: true, recursive: true }); all.push(...subEntries); @@ -863,7 +872,7 @@ export async function readdir(path, options) { const all = []; for (const entry of entries) { all.push(entry); - const subPath = path + '/' + entry; + const subPath = pathString + '/' + entry; try { const st = native.fs_stat(subPath); if (!st.error && st.stat.isDirectory) { @@ -893,12 +902,12 @@ export async function access(path, mode) { err.code = 'ERR_OUT_OF_RANGE'; throw err; } - const error = native.fs_access(path, mode); + const error = native.fs_access(pathToString(path), mode); if (error) throw createSystemError(error); } export async function realpath(path, options) { - const result = native.fs_realpath(path); + const result = native.fs_realpath(pathToString(path)); if (result.error) throw createSystemError(result.error); return result.result; } @@ -915,28 +924,28 @@ export async function copyFile(src, dest, mode) { err.code = 'ERR_INVALID_ARG_TYPE'; throw err; } - const error = native.fs_copy_file(src, dest); + const error = native.fs_copy_file(pathToString(src), pathToString(dest)); if (error) throw createSystemError(error); } export async function link(existingPath, newPath) { - const error = native.fs_link(existingPath, newPath); + const error = native.fs_link(pathToString(existingPath), pathToString(newPath)); if (error) throw createSystemError(error); } export async function symlink(target, path, type) { - const error = native.fs_symlink(target, path); + const error = native.fs_symlink(pathToString(target), pathToString(path)); if (error) throw createSystemError(error); } export async function readlink(path, options) { - const result = native.fs_readlink(path); + const result = native.fs_readlink(pathToString(path)); if (result.error) throw createSystemError(result.error); return result.result; } export async function chmod(path, mode) { - const error = native.fs_chmod(path, mode); + const error = native.fs_chmod(pathToString(path), mode); if (error) throw createSystemError(error); } @@ -947,21 +956,21 @@ export async function lchmod(path, mode) { export async function chown(path, uid, gid) { validateUid(uid, 'uid'); validateUid(gid, 'gid'); - const error = native.fs_chown(path, uid, gid); + const error = native.fs_chown(pathToString(path), uid, gid); if (error) throw createSystemError(error); } export async function lchown(path, uid, gid) { validateUid(uid, 'uid'); validateUid(gid, 'gid'); - const error = native.fs_lchown(path, uid, gid); + const error = native.fs_lchown(pathToString(path), uid, gid); if (error) throw createSystemError(error); } export async function utimes(path, atime, mtime) { const atimeSecs = (atime instanceof Date) ? atime.getTime() / 1000 : Number(atime); const mtimeSecs = (mtime instanceof Date) ? mtime.getTime() / 1000 : Number(mtime); - const error = native.fs_utimes(path, atimeSecs, mtimeSecs); + const error = native.fs_utimes(pathToString(path), atimeSecs, mtimeSecs); if (error) throw createSystemError(error); } @@ -981,6 +990,8 @@ export async function mkdtemp(prefix, options) { } export async function cp(src, dest, options) { + src = pathToString(src); + dest = pathToString(dest); // Simple copy implementation const srcResult = native.fs_stat(src); if (srcResult.error) throw createSystemError(srcResult.error); @@ -1003,6 +1014,7 @@ export async function cp(src, dest, options) { export async function* watch(filename, options = {}) { validatePath(filename, 'filename'); + filename = pathToString(filename); if (options === null || typeof options !== 'object' || Array.isArray(options)) { const err = new TypeError(`The "options" argument must be of type Object. Received ${describeType(options)}`); @@ -1102,7 +1114,7 @@ export async function* watch(filename, options = {}) { } export async function statfs(path, options) { - const result = native.fs_stat(path); + const result = native.fs_stat(pathToString(path)); if (result.error) throw createSystemError(result.error); const bigint = options && options.bigint; // Return a statfs-like object with sensible defaults diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/internal_binding_util.rs b/crates/wasm-rquickjs/skeleton/src/builtin/internal_binding_util.rs index 1f8d6c02..3e50ecd6 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/internal_binding_util.rs +++ b/crates/wasm-rquickjs/skeleton/src/builtin/internal_binding_util.rs @@ -61,6 +61,16 @@ pub mod native_module { Ok(details.into_value()) } + #[rquickjs::function] + pub fn package_deprecation_warning_seen(key: String) -> bool { + crate::internal::node_package_deprecation_warning_seen(&key) + } + + #[rquickjs::function] + pub fn mark_package_deprecation_warning_seen(key: String) { + crate::internal::mark_node_package_deprecation_warning_seen(key); + } + fn get_proxy_target_or_null<'js>(ctx: &Ctx<'js>, proxy: &Value<'js>) -> Value<'js> { let raw = unsafe { qjs::JS_GetProxyTarget(ctx.as_raw().as_ptr(), proxy.as_raw()) }; value_or_null(ctx, raw) diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/mod.rs b/crates/wasm-rquickjs/skeleton/src/builtin/mod.rs index a9a5c739..dd1b2612 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/mod.rs +++ b/crates/wasm-rquickjs/skeleton/src/builtin/mod.rs @@ -128,7 +128,9 @@ pub fn add_module_resolvers( .with_module("__wasm_rquickjs_builtin/intl_native") .with_module("__wasm_rquickjs_builtin/intl") .with_module("node:util") + .with_module("node:util/types") .with_module("util") + .with_module("util/types") .with_module("__wasm_rquickjs_builtin/fs_native") .with_module("node:fs") .with_module("fs") @@ -371,7 +373,9 @@ pub fn module_loader() -> ( .with_module("__wasm_rquickjs_builtin/encoding", encoding::ENCODING_JS) .with_module("__wasm_rquickjs_builtin/intl", intl::INTL_JS) .with_module("node:util", util::UTIL_JS) - .with_module("util", util::REEXPORT_JS) + .with_module("node:util/types", util::UTIL_TYPES_JS) + .with_module("util", util::BARE_UTIL_REEXPORT_JS) + .with_module("util/types", util::UTIL_TYPES_JS) .with_module("base64-js", base64::BASE64_JS) .with_module("ieee754", ieee754::IEEE754_JS) .with_module("node:buffer", buffer::BUFFER_JS) @@ -540,9 +544,23 @@ pub fn wire_builtins() -> String { } const IMPORT_META_RESOLVE_JS: &str = r#"globalThis.__wasm_rquickjs_import_meta_resolve = function(baseUrl, specifier) { + baseUrl = String(baseUrl); + specifier = String(specifier); if (/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//.test(specifier) || specifier.startsWith('data:')) return specifier; if (specifier.startsWith('node:')) return specifier; - var NODE_BUILTINS = new Set(['fs','path','os','crypto','http','https','url','util','stream','events','buffer','querystring','string_decoder','zlib','assert','module','net','tls','child_process','timers','dns','dgram','cluster','constants','readline','tty','v8','vm','worker_threads','perf_hooks','async_hooks','diagnostics_channel','trace_events','inspector','punycode','console','process','test','sqlite','domain','http2','repl']); + var NODE_BUILTINS = new Set(); + var NODE_BUILTIN_NAMES = 'fs,path,os,crypto,http,https,url,util,stream,events,buffer,querystring,string_decoder,zlib,assert,module,net,tls,child_process,timers,dns,dgram,cluster,constants,readline,tty,v8,vm,worker_threads,perf_hooks,async_hooks,diagnostics_channel,trace_events,inspector,punycode,console,process,test,sqlite,domain,http2,repl'.split(','); + for (var i = 0; i < NODE_BUILTIN_NAMES.length; i++) NODE_BUILTINS.add(NODE_BUILTIN_NAMES[i]); + function codedError(message, code, typeError) { + var err = typeError ? new TypeError(message) : new Error(message); + err.code = code; + return err; + } + function ensureSupportedBase() { + if (baseUrl.startsWith('data:')) { + throw codedError('Failed to resolve module specifier "' + specifier + '" from "' + baseUrl + '": Invalid relative URL or base scheme is not hierarchical.', 'ERR_UNSUPPORTED_RESOLVE_REQUEST', false); + } + } function normalizePath(p) { var parts = p.split('/'); var out = []; for (var i = 0; i < parts.length; i++) { @@ -552,93 +570,240 @@ const IMPORT_META_RESOLVE_JS: &str = r#"globalThis.__wasm_rquickjs_import_meta_r } return '/' + out.join('/'); } + function splitSuffix(value) { + var query = value.indexOf('?'); + var hash = value.indexOf('#'); + var end = query < 0 ? hash : (hash < 0 ? query : Math.min(query, hash)); + return end < 0 ? [value, ''] : [value.substring(0, end), value.substring(end)]; + } + function preserveTrailingSlash(path, original) { + return original.endsWith('/') && !path.endsWith('/') ? path + '/' : path; + } if (specifier.startsWith('/')) { - var path = normalizePath(specifier); - return baseUrl.startsWith('file://') ? 'file://' + path : path; + ensureSupportedBase(); + var parts = splitSuffix(specifier); + var path = preserveTrailingSlash(normalizePath(parts[0]), parts[0]); + return (baseUrl.startsWith('file://') ? 'file://' + path : path) + parts[1]; } if (specifier.startsWith('.')) { + ensureSupportedBase(); var base = baseUrl; if (base.startsWith('file://')) base = base.slice(7); + base = splitSuffix(base)[0]; var dir = base.substring(0, base.lastIndexOf('/') + 1); - var path = normalizePath(dir + specifier); - return baseUrl.startsWith('file://') ? 'file://' + path : path; + var parts = splitSuffix(specifier); + var path = preserveTrailingSlash(normalizePath(dir + parts[0]), parts[0]); + return (baseUrl.startsWith('file://') ? 'file://' + path : path) + parts[1]; } if (NODE_BUILTINS.has(specifier)) return 'node:' + specifier; - throw new Error('Cannot resolve bare specifier "' + specifier + '" from "' + baseUrl + '"'); + ensureSupportedBase(); + if (specifier.endsWith('/') && baseUrl.startsWith('file://')) { + var base = splitSuffix(baseUrl.slice(7))[0]; + var dir = base.endsWith('/') ? base : base.substring(0, base.lastIndexOf('/') + 1); + var resolved = normalizePath(dir + 'node_modules/' + specifier); + return 'file://' + (resolved.endsWith('/') ? resolved : resolved + '/'); + } + throw codedError('Cannot find package "' + specifier + '" imported from ' + baseUrl, 'ERR_MODULE_NOT_FOUND', false); };"#; const IMPORT_ATTRS_VALIDATE_JS: &str = r#" -globalThis.__wasm_rquickjs_validate_import_attrs = function(specifier, options) { - var attrs = null; - if (options != null && typeof options === 'object') { +globalThis.__wasm_rquickjs_import_attr_read_options = function(options) { + var typeValue; + var unsupportedKey; + + if (options !== undefined) { + if (options === null || typeof options !== 'object') { + throw new TypeError('The second argument to import() must be an object'); + } var w = options['with']; - if (w != null && typeof w === 'object') { - attrs = w; + if (w !== undefined) { + if (w === null || typeof w !== 'object') { + throw new TypeError("The 'with' option must be an object"); + } + var attrs = w; + var keys = Object.keys(attrs); + for (var k = 0; k < keys.length; k++) { + if (keys[k] === 'type') { + typeValue = attrs.type; + if (typeof typeValue !== 'string') { + throw new TypeError('Import attribute value must be a string'); + } + } else if (unsupportedKey === undefined) { + unsupportedKey = keys[k]; + } + } } } + return { typeValue: typeValue, unsupportedKey: unsupportedKey }; +}; + +globalThis.__wasm_rquickjs_import_attr_prepare_from_options = function(value, parsedOptions, asyncSemanticErrors) { + value = String(value); + parsedOptions = parsedOptions || {}; + var typeValue = parsedOptions.typeValue; + var unsupportedKey = parsedOptions.unsupportedKey; + + function semanticError(error) { + if (!asyncSemanticErrors) throw error; + return 'data:text/javascript,' + encodeURIComponent( + 'await Promise.reject(Object.assign(new TypeError(' + + JSON.stringify(error.message) + '), { code: ' + JSON.stringify(error.code) + ' }));' + ); + } var format = null; - if (typeof specifier === 'string') { - if (specifier.startsWith('data:')) { - var rest = specifier.substring(5); - var ci = rest.indexOf(','); - if (ci >= 0) { - var meta = rest.substring(0, ci).split(';')[0].trim(); - if (meta === 'application/json') format = 'json'; - else if (meta === 'text/javascript' || meta === 'application/javascript') format = 'module'; - else if (meta === 'text/css') format = 'css'; - } - } else if (specifier.endsWith('.json')) { - format = 'json'; - } else if (specifier.endsWith('.js') || specifier.endsWith('.mjs') || specifier.endsWith('.cjs')) { - format = 'module'; + if (value.startsWith('data:')) { + var rest = value.substring(5); + var ci = rest.indexOf(','); + if (ci >= 0) { + var meta = rest.substring(0, ci).split(';')[0].trim(); + if (meta === 'application/json') format = 'json'; + else if (meta === 'text/javascript' || meta === 'application/javascript') format = 'module'; + else if (meta === 'text/css') format = 'css'; } + } else if (value.startsWith('node:')) { + format = 'module'; + } else if (value.endsWith('.json')) { + format = 'json'; + } else if (value.endsWith('.js') || value.endsWith('.mjs') || value.endsWith('.cjs')) { + format = 'module'; } - if (attrs) { - var typeValue; - var keys = Object.keys(attrs); - for (var k = 0; k < keys.length; k++) { - if (keys[k] === 'type') typeValue = attrs.type; - } - if (typeValue !== undefined) { - if (typeValue === 'json') { - if (format === 'module') { - return Promise.reject(Object.assign( - new TypeError('Cannot use import attributes to change the type of a JavaScript module'), - { code: 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE' } - )); - } - } else if (typeValue !== 'css') { - return Promise.reject(Object.assign( - new TypeError('Import attribute type "' + typeValue + '" is not supported'), - { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } + if (typeValue !== undefined) { + if (typeValue === 'json') { + if (format === 'module') { + return semanticError(Object.assign( + new TypeError('Cannot use import attributes to change the type of a JavaScript module'), + { code: 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE' } )); } + } else if (typeValue === 'css' && format === 'css') { + // Let the loader report unsupported CSS modules as an unknown format. + } else { + return semanticError(Object.assign( + new TypeError('Import attribute type "' + typeValue + '" is not supported'), + { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } + )); } } if (format === 'json') { - if (!attrs || attrs.type !== 'json') { - return Promise.reject(Object.assign( - new TypeError('Module "' + specifier + '" needs an import attribute of "type: json"'), + if (typeValue !== 'json') { + return semanticError(Object.assign( + new TypeError('Module "' + value + '" needs an import attribute of "type: json"'), { code: 'ERR_IMPORT_ATTRIBUTE_MISSING' } )); } } - if (attrs) { - var keys2 = Object.keys(attrs); - for (var j = 0; j < keys2.length; j++) { - if (keys2[j] !== 'type') { - return Promise.reject(Object.assign( - new TypeError('Import attribute "' + keys2[j] + '" is not supported'), - { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } - )); + if (unsupportedKey !== undefined) { + return semanticError(Object.assign( + new TypeError('Import attribute "' + unsupportedKey + '" is not supported'), + { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } + )); + } + + if (typeValue !== 'json') return value; + return globalThis.__wasm_rquickjs_register_import_attr_rewrite(value, 'json'); +}; + +globalThis.__wasm_rquickjs_import_attr_prepare = function(specifier, options, asyncSemanticErrors) { + var value = String(specifier); + var parsedOptions = globalThis.__wasm_rquickjs_import_attr_read_options(options); + return globalThis.__wasm_rquickjs_import_attr_prepare_from_options(value, parsedOptions, asyncSemanticErrors); +}; + +globalThis.__wasm_rquickjs_import_attr_prepare_for_base = async function(baseUrl, specifier, options, asyncSemanticErrors) { + var originalValue = String(specifier); + var parsedOptions = globalThis.__wasm_rquickjs_import_attr_read_options(options); + if ( + globalThis.__wasm_rquickjs_registered_loaders && + globalThis.__wasm_rquickjs_registered_loaders.length > 0 + ) { + var hooked = await globalThis.__wasm_rquickjs_run_registered_loaders(String(baseUrl), originalValue, parsedOptions); + if (hooked !== undefined) return hooked; + } + var value = originalValue; + if ( + value.startsWith('./') || + value.startsWith('../') || + value.startsWith('/') || + value.startsWith('file://') + ) { + value = globalThis.__wasm_rquickjs_import_meta_resolve(String(baseUrl), value); + } + return globalThis.__wasm_rquickjs_import_attr_prepare_from_options(value, parsedOptions, asyncSemanticErrors); +}; + +globalThis.__wasm_rquickjs_import_attr_dynamic_import = async function(baseUrl, specifier, options, asyncSemanticErrors, importer) { + var originalSpecifier = String(specifier); + var prepared = await globalThis.__wasm_rquickjs_import_attr_prepare_for_base(baseUrl, specifier, options, asyncSemanticErrors); + var key = String(prepared); + var completedKey = key; + var originalHasRewriteToken = originalSpecifier.indexOf('__wasm_rquickjs_import_type=') >= 0; + var tokenMatch = originalHasRewriteToken ? null : /^data:([^,]*);__wasm_rquickjs_import_type=([^;,]+)(,.*)$/.exec(key); + if (tokenMatch) { + completedKey = 'import-attr:' + tokenMatch[2].split('-')[0] + ':data:' + tokenMatch[1] + tokenMatch[3]; + } else { + tokenMatch = originalHasRewriteToken ? null : /([?#&])__wasm_rquickjs_import_type=([^&#]+)(&?)/.exec(key); + if (tokenMatch) { + var tokenStart = tokenMatch.index; + var tokenEnd = tokenStart + tokenMatch[0].length; + var prefix = key.slice(0, tokenStart); + var suffix = key.slice(tokenEnd); + var separator = tokenMatch[1]; + if (separator === '&') { + completedKey = prefix + (suffix ? '&' + suffix : ''); + } else if (tokenMatch[3] === '&') { + completedKey = prefix + separator + suffix; + } else { + completedKey = prefix + suffix; } + if (completedKey.endsWith('?') || completedKey.endsWith('#')) completedKey = completedKey.slice(0, -1); + completedKey = 'import-attr:' + tokenMatch[2].split('-')[0] + ':' + completedKey; } } - - return false; + var generatedRewriteToken = completedKey !== key && !originalHasRewriteToken; + function discardGeneratedRewriteToken() { + if (generatedRewriteToken && typeof globalThis.__wasm_rquickjs_discard_import_attr_rewrite === 'function') { + globalThis.__wasm_rquickjs_discard_import_attr_rewrite(key); + } + } + var importFn = typeof importer === 'function' ? importer : function(value) { return import(value); }; + if ( + typeof globalThis.__wasm_rquickjs_has_import_mock === 'function' && + globalThis.__wasm_rquickjs_has_import_mock(prepared, baseUrl) + ) { + try { + return await importFn(prepared); + } finally { + discardGeneratedRewriteToken(); + } + } + var cache = globalThis.__wasm_rquickjs_import_attr_inflight; + if (!cache) { + cache = Object.create(null); + globalThis.__wasm_rquickjs_import_attr_inflight = cache; + } + if (cache[completedKey] !== undefined) { + var cached = cache[completedKey]; + if (cached.preparedKey !== key) { + discardGeneratedRewriteToken(); + } + return cached.promise; + } + var promise = importFn(prepared); + var entry = { promise: promise, preparedKey: key }; + cache[completedKey] = entry; + try { + var result = await promise; + discardGeneratedRewriteToken(); + return result; + } catch (error) { + if (cache[completedKey] === entry) delete cache[completedKey]; + discardGeneratedRewriteToken(); + throw error; + } finally { + } }; "#; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/module.js b/crates/wasm-rquickjs/skeleton/src/builtin/module.js index c4d9f4a5..55852396 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/module.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/module.js @@ -19,6 +19,7 @@ import * as assertStrict from 'node:assert/strict'; import * as fsPromises from 'node:fs/promises'; import * as nodeTest from 'node:test'; import * as querystring from 'node:querystring'; +import * as punycode from 'node:punycode'; import * as nodeUrl from 'node:url'; import * as vm from 'node:vm'; import * as timers from 'node:timers'; @@ -49,7 +50,7 @@ import * as worker_threads from 'node:worker_threads'; import * as zlib from 'node:zlib'; import * as sqlite from 'node:sqlite'; import * as internalHttp from '__wasm_rquickjs_builtin/internal/http'; -import { ERR_INVALID_ARG_TYPE } from '__wasm_rquickjs_builtin/internal/errors'; +import { ERR_INVALID_ARG_TYPE, ERR_MISSING_ARGS } from '__wasm_rquickjs_builtin/internal/errors'; import * as internalErrors from '__wasm_rquickjs_builtin/internal/errors'; import * as internalFsUtils from '__wasm_rquickjs_builtin/internal/fs/utils'; import * as internalUrl from '__wasm_rquickjs_builtin/internal/url'; @@ -60,6 +61,10 @@ import * as internalStreamsAddAbortSignal from '__wasm_rquickjs_builtin/internal import * as internalStreamsState from '__wasm_rquickjs_builtin/internal/streams/state'; import * as internalTestBinding from '__wasm_rquickjs_builtin/internal/test/binding'; import { eval_with_filename as _evalWithFilename, require_esm as _requireEsm } from '__wasm_rquickjs_builtin/vm_native'; +import { + package_deprecation_warning_seen as _packageDeprecationWarningSeen, + mark_package_deprecation_warning_seen as _markPackageDeprecationWarningSeen, +} from '__wasm_rquickjs_builtin/internal/binding/util_native'; // CJS require() should return the default export (the "module object") when one // exists, not the ESM namespace wrapper. When the default export is a function @@ -102,6 +107,7 @@ const assertCjs = cjsExport(assert); const assertStrictCjs = cjsExport(assertStrict); const nodeTestCjs = cjsExport(nodeTest); const querystringCjs = cjsExport(querystring); +const punycodeCjs = cjsExport(punycode); const nodeUrlCjs = cjsExport(nodeUrl); const vmCjs = cjsExport(vm); const timersCjs = cjsExport(timers); @@ -111,6 +117,8 @@ const asyncHooksCjs = cjsExport(async_hooks); const clusterCjs = cjsExport(cluster); const dgramCjs = cjsExport(dgram); const diagnosticsChannelCjs = cjsExport(diagnostics_channel); +const moduleRequireTrace = diagnostics_channel.tracingChannel('module.require'); +const moduleImportTrace = diagnostics_channel.tracingChannel('module.import'); const dnsCjs = cjsExport(dns); const dnsPromisesCjs = cjsExport(dnsPromises); const domainCjs = cjsExport(domain); @@ -197,6 +205,7 @@ registerBuiltin(builtinModuleMap, 'assert', assertCjs); registerBuiltin(builtinModuleMap, 'assert/strict', assertStrictCjs); registerBuiltin(builtinModuleMap, 'test', nodeTestCjs); registerBuiltin(builtinModuleMap, 'querystring', querystringCjs); +registerBuiltin(builtinModuleMap, 'punycode', punycodeCjs); registerBuiltin(builtinModuleMap, 'url', nodeUrlCjs); registerBuiltin(builtinModuleMap, 'vm', vmCjs); registerBuiltin(builtinModuleMap, 'timers', timersCjs); @@ -406,10 +415,50 @@ function _resolveRequireMock(id) { return _moduleMockRegistry[key] || null; } +function _hasImportMock(specifier, base) { + const key = _mockCanonicalKey(specifier, base); + return !!(key && _moduleMockRegistry[key]); +} + globalThis.__wasm_rquickjs_mock_canonical_key = _mockCanonicalKey; globalThis.__wasm_rquickjs_register_module_mock = _registerModuleMock; globalThis.__wasm_rquickjs_resolve_require_mock = _resolveRequireMock; globalThis.__wasm_rquickjs_materialize_cjs_mock = _materializeCjsMock; +globalThis.__wasm_rquickjs_has_import_mock = _hasImportMock; + +function traceModuleRequire(id, parentFilename, fn) { + if (globalThis.__wasm_rquickjs_suppress_module_require_diagnostics) { + return fn(); + } + if (!moduleRequireTrace.hasSubscribers) { + return fn(); + } + return moduleRequireTrace.traceSync(fn, { + id, + parentFilename, + }); +} + +function traceModuleImport(url, parentFilename, fn) { + return moduleImportTrace.tracePromise(fn, { + url, + parentURL: nodeUrl.pathToFileURL(parentFilename).href, + }); +} +globalThis.__wasm_rquickjs_trace_module_import = traceModuleImport; +globalThis.__wasm_rquickjs_with_suppressed_module_require_diagnostics = function(fn) { + const previous = globalThis.__wasm_rquickjs_suppress_module_require_diagnostics; + globalThis.__wasm_rquickjs_suppress_module_require_diagnostics = true; + try { + return fn(); + } finally { + if (previous === undefined) { + delete globalThis.__wasm_rquickjs_suppress_module_require_diagnostics; + } else { + globalThis.__wasm_rquickjs_suppress_module_require_diagnostics = previous; + } + } +}; // Lookup mock entry by ID (for ESM source generation) globalThis.__wasm_rquickjs_get_mock_module_entry = function(mockId) { @@ -486,8 +535,16 @@ const builtinModuleNames = Object.keys(builtinModuleMap).filter( (name) => !name.startsWith('node:') && !name.startsWith('internal/') && !name.startsWith('_') ); +function setFromArray(values, mapper) { + const set = new Set(); + for (let i = 0; i < values.length; i++) { + set.add(mapper ? mapper(values, i) : values[i]); + } + return set; +} + // Modules that require the 'node:' prefix (cannot be required as bare specifiers) -const schemelessBlockList = new Set(['test', 'sqlite']); +const schemelessBlockList = setFromArray(['test', 'sqlite']); // Build public module ID sets matching Node.js semantics const publicBuiltinIdSet = new Set(); @@ -520,6 +577,15 @@ function isBuiltinResolveTarget(id) { // Module cache: resolved absolute path -> Module object const moduleCache = Object.create(null); +function shouldPreserveSymlinks(isMainModuleLoad) { + return hasExecArgvFlag(isMainModuleLoad ? '--preserve-symlinks-main' : '--preserve-symlinks'); +} + +function toCjsCanonicalFilename(filename, isMainModuleLoad) { + if (shouldPreserveSymlinks(isMainModuleLoad)) return filename; + return fsModule.realpathSync.native(filename); +} + function tryReadFile(filename) { try { return fsModule.readFileSync(filename, 'utf8'); @@ -528,12 +594,25 @@ function tryReadFile(filename) { } } +const packageJsonParseCache = Object.create(null); + +function readPackageJson(pkgJsonPath) { + if (Object.prototype.hasOwnProperty.call(packageJsonParseCache, pkgJsonPath)) { + return packageJsonParseCache[pkgJsonPath]; + } + const content = tryReadFile(pkgJsonPath); + if (content === null) return null; + const entry = { path: pkgJsonPath, content, pkg: JSON.parse(content) }; + packageJsonParseCache[pkgJsonPath] = entry; + return entry; +} + // Shared require.extensions registry (mirrors Node.js Module._extensions) const requireExtensions = Object.create(null); requireExtensions['.js'] = function _defaultJs(mod, filename) { /* built-in */ }; requireExtensions['.json'] = function _defaultJson(mod, filename) { /* built-in */ }; requireExtensions['.node'] = function _defaultNode(mod, filename) { /* built-in */ }; -const _defaultExtHandlers = new Set([requireExtensions['.js'], requireExtensions['.json'], requireExtensions['.node']]); +const _defaultExtHandlers = setFromArray([requireExtensions['.js'], requireExtensions['.json'], requireExtensions['.node']]); // Path cache (settable; used by tests to reset resolution state) let _pathCache = Object.create(null); @@ -552,23 +631,46 @@ function findLongestRegisteredExtension(filename) { } function getPackageScopeType(filename) { + const packageType = getPackageScopeExplicitType(filename); + return packageType || 'commonjs'; +} + +function getPackageScopeExplicitType(filename) { + const scope = getPackageScopeInfo(filename); + return scope ? scope.packageType : null; +} + +function getPackageScopeInfo(filename) { let dir = pathModule.dirname(filename); while (true) { + if (pathModule.basename(dir) === 'node_modules') return null; const pkgPath = pathModule.join(dir, 'package.json'); - const pkgContent = tryReadFile(pkgPath); - if (pkgContent !== null) { - try { - const pkg = JSON.parse(pkgContent); - return pkg.type || 'commonjs'; - } catch (e) { - return 'commonjs'; + try { + const entry = readPackageJson(pkgPath); + if (entry !== null) { + return { + packageType: entry.pkg.type || null, + isNodeModulesPackage: isNodeModulesPackageScope(dir), + }; } + } catch (e) { + return null; } const parent = pathModule.dirname(dir); if (parent === dir) break; dir = parent; } - return 'commonjs'; + return null; +} + +function isNodeModulesPackageScope(dir) { + const parent = pathModule.dirname(dir); + if (parent === dir) return false; + if (pathModule.basename(parent) === 'node_modules') return true; + const grandparent = pathModule.dirname(parent); + return pathModule.basename(parent).startsWith('@') && + grandparent !== parent && + pathModule.basename(grandparent) === 'node_modules'; } function isPathDirectory(filename) { @@ -605,335 +707,1443 @@ function loadAsDirectory(candidate, id, parentDir, seen) { seen[candidate] = true; const pkgJsonPath = pathModule.join(candidate, 'package.json'); - const pkgJson = tryReadFile(pkgJsonPath); - if (pkgJson !== null) { - try { - const pkg = JSON.parse(pkgJson); - if (Object.prototype.hasOwnProperty.call(pkg, 'main') && typeof pkg.main === 'string' && pkg.main.length > 0) { - const mainPath = pathModule.resolve(candidate, pkg.main); - let resolved = loadAsFile(mainPath, false); - if (resolved !== null) return resolved; - resolved = loadAsDirectory(mainPath, id, parentDir, seen); - if (resolved !== null) return resolved; - } - } catch (e) { - const pkgErr = new Error( - 'Invalid package config ' + pkgJsonPath + - ' while resolving "' + id + '" from ' + parentDir + '.' + - (e.message ? ' ' + e.message : '') - ); - pkgErr.code = 'ERR_INVALID_PACKAGE_CONFIG'; - throw pkgErr; + let packageJsonEntry; + let invalidMain = null; + try { + packageJsonEntry = readPackageJson(pkgJsonPath); + } catch (e) { + const pkgErr = new Error( + 'Invalid package config ' + pkgJsonPath + + ' while resolving "' + id + '" from ' + parentDir + '.' + + (e.message ? ' ' + e.message : '') + ); + pkgErr.code = 'ERR_INVALID_PACKAGE_CONFIG'; + throw pkgErr; + } + if (packageJsonEntry !== null) { + let pkg; + pkg = packageJsonEntry.pkg; + + if (Object.prototype.hasOwnProperty.call(pkg, 'main') && typeof pkg.main === 'string' && pkg.main.length > 0) { + const mainPath = pathModule.resolve(candidate, pkg.main); + let resolved = loadAsFile(mainPath, false); + if (resolved !== null) return resolved; + resolved = loadAsDirectory(mainPath, id, parentDir, seen); + if (resolved !== null) return resolved; + invalidMain = { field: pkg.main, path: mainPath }; } } - let content = tryReadFile(pathModule.join(candidate, 'index.js')); - if (content !== null) { - return { filename: pathModule.join(candidate, 'index.js'), content: content }; + const indexResolved = loadAsFile(pathModule.join(candidate, 'index'), false); + if (indexResolved !== null) { + emitInvalidMainWarning(pkgJsonPath, invalidMain); + return indexResolved; } - content = tryReadFile(pathModule.join(candidate, 'index.json')); - if (content !== null) { - return { filename: pathModule.join(candidate, 'index.json'), content: content }; + if (invalidMain !== null) { + const err = new Error("Cannot find module '" + invalidMain.path + "'. Please verify that the package.json has a valid \"main\" entry"); + err.code = 'MODULE_NOT_FOUND'; + err.path = pkgJsonPath; + err.requestPath = id; + throw err; } return null; } -function resolveFilename(id, parentDir) { - const hasTrailingSlash = /\/$/.test(id); - const forceDirectory = hasTrailingSlash || /(?:^|\/)\.\.?$/.test(id); - const candidate = pathModule.isAbsolute(id) - ? pathModule.normalize(id) - : pathModule.resolve(parentDir, id); +function emitInvalidMainWarning(pkgJsonPath, invalidMain) { + if (invalidMain === null) return; + const processObject = globalThis.process; + if (!processObject || typeof processObject.emitWarning !== 'function') return; + processObject.emitWarning( + "Invalid 'main' field in '" + pathModule.toNamespacedPath(pkgJsonPath) + "' of '" + invalidMain.field + "'. Please either fix that or report it to the module author", + 'DeprecationWarning', + 'DEP0128' + ); +} - let resolved = null; - if (!forceDirectory) { - resolved = loadAsFile(candidate, false); - if (resolved !== null) return resolved; - } +let packageDeprecationWarningsSuppressed = 0; - if (forceDirectory || isPathDirectory(candidate)) { - resolved = loadAsDirectory(candidate, id, parentDir); - if (resolved !== null) return resolved; +function emitPackageDeprecationWarning(message, code, key) { + if (packageDeprecationWarningsSuppressed > 0) return; + const warningKey = code === 'DEP0155' ? String(code) + ':' + String(key || message) : null; + const processObject = globalThis.process; + if (processObject && processObject.noDeprecation) return; + if (warningKey && _packageDeprecationWarningSeen(warningKey)) return; + if (warningKey) { + _markPackageDeprecationWarningSeen(warningKey); } - - const err = new Error("Cannot find module '" + id + "' from '" + parentDir + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; + if (!processObject || typeof processObject.emitWarning !== 'function') { + throw new Error('Internal process warning emitter is not initialized'); + } + processObject.emitWarning(message, 'DeprecationWarning', code); } -function hasAllowNativesSyntaxFlag() { - const runtimeFlags = globalThis.__wasm_rquickjs_v8_runtime_flags; - if (runtimeFlags && runtimeFlags.allowNativesSyntax === true) { - return true; +function withSuppressedPackageDeprecationWarnings(callback) { + packageDeprecationWarningsSuppressed += 1; + try { + return callback(); + } finally { + packageDeprecationWarningsSuppressed -= 1; } +} - const processObject = globalThis.process; - if (!processObject || !Array.isArray(processObject.execArgv)) { - return false; - } +const cjsDefaultPackageConditions = ['golem', 'node', 'require', 'module-sync', 'default']; +const esmDefaultPackageConditions = ['golem', 'node', 'module-sync', 'import', 'default']; +const loaderDefaultConditions = ['node', 'import', 'module-sync', 'node-addons']; - let enabled = false; - for (let i = 0; i < processObject.execArgv.length; i++) { - const arg = String(processObject.execArgv[i]).replace(/_/g, '-'); - if (arg === '--allow-natives-syntax') { - enabled = true; - continue; - } +function addPackageCondition(conditions, condition) { + if (typeof condition === 'string' && condition.length > 0) conditions.add(condition); +} - if (arg === '--noallow-natives-syntax' || arg === '--no-allow-natives-syntax') { - enabled = false; - } +function packageConditions(defaults) { + const conditions = setFromArray(defaults); + const userConditions = globalThis.__wasm_rquickjs_package_conditions; + if (!Array.isArray(userConditions)) { + return conditions; } - return enabled; + for (let i = 0; i < userConditions.length; i++) { + addPackageCondition(conditions, userConditions[i]); + } + + return conditions; } -function stripV8OptimizationIntrinsics(source) { - if (!hasAllowNativesSyntaxFlag()) { - return source; - } +function cjsPackageConditions() { + return packageConditions(cjsDefaultPackageConditions); +} - // QuickJS cannot parse V8-native `%...` syntax used in eval strings. - // These intrinsics only force optimization and are semantically no-ops. - return source - .replace(/eval\(\s*(['"])%PrepareFunctionForOptimization\([^'"\\\r\n]*\)\1\s*\)\s*;?/g, 'undefined;') - .replace(/eval\(\s*(['"])%OptimizeFunctionOnNextCall\([^'"\\\r\n]*\)\1\s*\)\s*;?/g, 'undefined;'); +function esmPackageConditions() { + return packageConditions(esmDefaultPackageConditions); } -function _iaSkipStr(s, i) { - const q = s.charCodeAt(i); - i++; - while (i < s.length) { - if (s.charCodeAt(i) === 0x5C) { i += 2; } - else if (s.charCodeAt(i) === q) { return i + 1; } - else { i++; } +function loaderHookConditions() { + return Array.from(packageConditions(loaderDefaultConditions)); +} +const packageTargetNoMatch = { __packageTargetNoMatch: true }; +const packageTargetBlocked = { __packageTargetBlocked: true }; + +function makePackagePathNotExportedError(packageName, subpath, noExportsMain) { + if (noExportsMain || !subpath) { + const err = new Error('No "exports" main defined in package ' + packageName); + err.code = 'ERR_PACKAGE_PATH_NOT_EXPORTED'; + return err; } - return i; + const suffix = subpath ? './' + subpath : '.'; + const err = new Error('Package subpath ' + JSON.stringify(suffix) + ' is not defined by "exports" in package ' + packageName); + err.code = 'ERR_PACKAGE_PATH_NOT_EXPORTED'; + return err; } -function _iaSkipTpl(s, i) { - i++; - while (i < s.length) { - let c = s.charCodeAt(i); - if (c === 0x5C) { i += 2; } - else if (c === 0x60) { return i + 1; } - else if (c === 0x24 && i + 1 < s.length && s.charCodeAt(i + 1) === 0x7B) { - i += 2; - let d = 1; - while (i < s.length && d > 0) { - c = s.charCodeAt(i); - if (c === 0x27 || c === 0x22) { i = _iaSkipStr(s, i); } - else if (c === 0x60) { i = _iaSkipTpl(s, i); } - else if (c === 0x7B || c === 0x28 || c === 0x5B) { d++; i++; } - else if (c === 0x7D || c === 0x29 || c === 0x5D) { d--; i++; } - else { i++; } - } - } else { i++; } - } - return i; +function makePackageImportNotDefinedError(specifier) { + const err = new Error('Package import specifier ' + JSON.stringify(specifier) + ' is not defined'); + err.code = 'ERR_PACKAGE_IMPORT_NOT_DEFINED'; + return err; } -function stripImportAttributes(source) { - const len = source.length; - const out = []; - let i = 0; - while (i < len) { - let ch = source.charCodeAt(i); - if (ch === 0x27 || ch === 0x22) { - const s = i; i = _iaSkipStr(source, i); out.push(source.substring(s, i)); continue; - } - if (ch === 0x60) { - const s = i; i = _iaSkipTpl(source, i); out.push(source.substring(s, i)); continue; - } - if (ch === 0x2F && i + 1 < len) { - const nc = source.charCodeAt(i + 1); - if (nc === 0x2F) { const s = i; while (i < len && source.charCodeAt(i) !== 0x0A) i++; out.push(source.substring(s, i)); continue; } - if (nc === 0x2A) { const s = i; i += 2; while (i + 1 < len && !(source.charCodeAt(i) === 0x2A && source.charCodeAt(i + 1) === 0x2F)) i++; if (i + 1 < len) i += 2; out.push(source.substring(s, i)); continue; } - } - if (ch === 0x69 && i + 7 <= len && source.substring(i, i + 7) === 'import(' && - (i === 0 || !((ch = source.charCodeAt(i - 1)) >= 48 && ch <= 57 || ch >= 65 && ch <= 90 || ch >= 97 && ch <= 122 || ch === 95 || ch === 36))) { - i += 7; - let depth = 1, commaPos = -1; - const argStart = i; - while (i < len && depth > 0) { - ch = source.charCodeAt(i); - if (ch === 0x27 || ch === 0x22) { i = _iaSkipStr(source, i); } - else if (ch === 0x60) { i = _iaSkipTpl(source, i); } - else if (ch === 0x2F && i + 1 < len && source.charCodeAt(i + 1) === 0x2F) { while (i < len && source.charCodeAt(i) !== 0x0A) i++; } - else if (ch === 0x2F && i + 1 < len && source.charCodeAt(i + 1) === 0x2A) { i += 2; while (i + 1 < len && !(source.charCodeAt(i) === 0x2A && source.charCodeAt(i + 1) === 0x2F)) i++; if (i + 1 < len) i += 2; } - else if (ch === 0x28 || ch === 0x5B || ch === 0x7B) { depth++; i++; } - else if (ch === 0x29 || ch === 0x5D || ch === 0x7D) { depth--; i++; } - else if (ch === 0x2C && depth === 1 && commaPos === -1) { commaPos = i; i++; } - else { i++; } - } - if (commaPos > -1) { - const firstArg = source.substring(argStart, commaPos); - const secondArg = source.substring(commaPos + 1, i - 1); - out.push('(globalThis.__wasm_rquickjs_validate_import_attrs('); - out.push(firstArg); - out.push(','); - out.push(secondArg); - out.push(') || import('); - out.push(firstArg); - out.push('))'); - } else { - const spec = source.substring(argStart, i - 1); - out.push('(globalThis.__wasm_rquickjs_validate_import_attrs('); - out.push(spec); - out.push(') || import('); - out.push(spec); - out.push('))'); - } - continue; - } - out.push(source[i]); - i++; - } - return out.join(''); +function makeInvalidModuleSpecifierError(specifier, message) { + const err = new TypeError('Invalid module ' + JSON.stringify(specifier) + ' ' + message); + err.code = 'ERR_INVALID_MODULE_SPECIFIER'; + return err; } -function hasExecArgvFlag(flag) { - const processObject = globalThis.process; - if (!processObject || !Array.isArray(processObject.execArgv)) { - return false; +function validatePackageImportSpecifier(specifier) { + if (specifier === '#' || specifier.startsWith('#/')) { + throw makeInvalidModuleSpecifierError(specifier, 'is not a valid internal imports specifier name'); } +} - const prefixed = flag + '='; - for (let i = 0; i < processObject.execArgv.length; i++) { - const arg = String(processObject.execArgv[i]); - if (arg === flag || arg.indexOf(prefixed) === 0) { - return true; - } +function makeInvalidPackageTargetError(target, kind) { + let message = kind ? 'Invalid "' + kind + '" target ' + JSON.stringify(target) : 'Invalid package target ' + JSON.stringify(target); + if (kind === 'exports' && typeof target === 'string' && !target.startsWith('./')) { + message += '; targets must start with "./"'; } + const err = new Error(message); + err.code = 'ERR_INVALID_PACKAGE_TARGET'; + return err; +} - return false; +function makeInvalidPackageConfigError(path, message) { + const err = new Error('Invalid package config ' + path + '. ' + message); + err.code = 'ERR_INVALID_PACKAGE_CONFIG'; + return err; } -function isExperimentalTransformTypesEnabled() { - return hasExecArgvFlag('--experimental-transform-types'); +function makeModuleNotFoundError(id) { + const err = new Error("Cannot find module '" + id + "'"); + err.code = 'MODULE_NOT_FOUND'; + return err; } -function isSourceMapsEnabled() { - if (hasExecArgvFlag('--no-enable-source-maps')) { - return false; +function addPackageErrorContext(err, specifier) { + if (err && typeof err.message === 'string' && err.message.indexOf(specifier) === -1) { + err.message += ' for ' + JSON.stringify(specifier); } + return err; +} - return hasExecArgvFlag('--enable-source-maps') || isExperimentalTransformTypesEnabled(); +function isBarePackageSpecifier(target) { + return typeof target === 'string' && + target.length > 0 && + !target.startsWith('.') && + !target.startsWith('/') && + !target.startsWith('#') && + !target.includes(':'); } -function getSimpleSourceMapRegistry() { - let registry = globalThis.__wasm_rquickjs_simple_source_maps; - if (!registry || typeof registry !== 'object') { - registry = Object.create(null); - globalThis.__wasm_rquickjs_simple_source_maps = registry; +function isInvalidPackageTargetSegment(segment) { + if (segment === '.' || segment === '..' || segment === 'node_modules') return true; + let decoded = segment; + try { + decoded = decodeURIComponent(segment); + } catch (_) { + // Keep the raw segment when percent decoding fails; invalid escapes are + // handled by the normal module-not-found path for now. } - return registry; + decoded = decoded.toLowerCase(); + return decoded === '.' || decoded === '..' || decoded === 'node_modules'; } -function getCjsLineOffsetRegistry() { - let registry = globalThis.__wasm_rquickjs_cjs_line_offsets; - if (!registry || typeof registry !== 'object') { - registry = Object.create(null); - globalThis.__wasm_rquickjs_cjs_line_offsets = registry; - } - return registry; +function hasEncodedSlashOrBackslash(value) { + return /%(?:2f|5c)/i.test(value); } -function countMatches(text, charCode) { - let count = 0; - for (let i = 0; i < text.length; i++) { - if (text.charCodeAt(i) === charCode) { - count += 1; - } +function isInvalidPackagePatternSubstitution(substitution) { + if (hasEncodedSlashOrBackslash(substitution)) return true; + const parts = substitution.split('/'); + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (part === '') continue; + if (isInvalidPackageTargetSegment(part)) return true; } - return count; + return false; } -function transpileTypeScriptModule(filename, source) { - if (!isExperimentalTransformTypesEnabled() || !filename.endsWith('.ts')) { - return source; +function invalidPackagePatternSubstitutionMessage(substitution, fallback) { + if (hasEncodedSlashOrBackslash(substitution)) { + return 'must not include encoded "/" or "\\" characters'; } + return fallback; +} - const lines = String(source).split('\n'); - const transformedLines = []; - const generatedLineToOriginalLine = Object.create(null); - let insideInterface = false; - let interfaceDepth = 0; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; +function hasDeprecatedDoubleSlash(value) { + return typeof value === 'string' && value.indexOf('//') !== -1; +} - if (insideInterface) { - interfaceDepth += countMatches(line, 123) - countMatches(line, 125); - if (interfaceDepth <= 0) { - insideInterface = false; - interfaceDepth = 0; - } - continue; - } +function hasDeprecatedLeadingOrTrailingSlash(substitution) { + return typeof substitution === 'string' && (substitution.startsWith('/') || substitution.endsWith('/')); +} - const trimmed = line.trim(); - if (/^interface\s+[A-Za-z_$][A-Za-z0-9_$]*\b/.test(trimmed)) { - interfaceDepth = countMatches(line, 123) - countMatches(line, 125); - if (interfaceDepth > 0) { - insideInterface = true; - } - continue; - } +function packageWarningLocation(kind, packageDir, importer) { + return ' in the "' + kind + '" field module resolution of the package at ' + + pathModule.join(packageDir, 'package.json') + + (importer ? ' imported from ' + importer : '') + '.'; +} - if (trimmed.length === 0) { - continue; - } +function matchedPackagePatternSuffix(patternKey) { + return patternKey ? ' matched to ' + JSON.stringify(patternKey) : ''; +} - transformedLines.push(line); - generatedLineToOriginalLine[transformedLines.length] = i + 1; +function emitDeprecatedPackageTargetWarning(kind, specifier, target, patternSubstitution, packageDir, patternKey, importer) { + if (kind === 'exports' && typeof patternSubstitution === 'string' && patternSubstitution.endsWith('/')) { + const location = packageWarningLocation(kind, packageDir, importer); + emitPackageDeprecationWarning( + 'Use of deprecated trailing slash pattern mapping ' + + JSON.stringify(specifier) + location + ' Mapping specifiers ending in "/" is no longer supported.', + 'DEP0155', + packageDir + ':' + specifier + ); + return; + } + if (hasDeprecatedDoubleSlash(target)) { + const location = packageWarningLocation(kind, packageDir, importer); + const matchedPattern = matchedPackagePatternSuffix(patternKey); + emitPackageDeprecationWarning( + 'Use of deprecated double slash resolving ' + JSON.stringify(target) + + ' for module request ' + JSON.stringify(specifier) + matchedPattern + location, + 'DEP0166', + packageDir + ':' + specifier + ':' + target + ); + return; + } + if (hasDeprecatedLeadingOrTrailingSlash(patternSubstitution)) { + const location = packageWarningLocation(kind, packageDir, importer); + const matchedPattern = matchedPackagePatternSuffix(patternKey); + emitPackageDeprecationWarning( + 'Use of deprecated leading or trailing slash matching resolving ' + JSON.stringify(target) + + ' for module request ' + JSON.stringify(specifier) + matchedPattern + location, + 'DEP0166', + packageDir + ':' + specifier + ':' + target + ); + return; + } + if (hasDeprecatedDoubleSlash(specifier)) { + const location = packageWarningLocation(kind, packageDir, importer); + const matchedPattern = matchedPackagePatternSuffix(patternKey); + emitPackageDeprecationWarning( + 'Use of deprecated double slash resolving ' + JSON.stringify(target) + + ' for module request ' + JSON.stringify(specifier) + matchedPattern + location, + 'DEP0166', + packageDir + ':' + specifier + ); } +} - const transformed = transformedLines.join('\n'); - const sourceMapRegistry = getSimpleSourceMapRegistry(); - if (isSourceMapsEnabled()) { - sourceMapRegistry[filename] = { - generatedLineToOriginalLine, - }; - } else { - delete sourceMapRegistry[filename]; +function validatePackageTargetPath(target) { + const rest = target.slice(2); + const parts = rest.split('/'); + if (parts.length === 0) return false; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (part === '') continue; + if (isInvalidPackageTargetSegment(part)) return false; } + return true; +} - return transformed; +function resolveExactPackageFile(filename) { + const content = tryReadFile(filename); + if (content !== null) return { filename, content }; + throw makeModuleNotFoundError(filename); } -function getArrowMessagePrivateSymbol() { - const privateSymbols = globalThis.__wasm_rquickjs_internal_private_symbols; - if (!privateSymbols || typeof privateSymbols !== 'object') { - return undefined; +function decodePackageTargetPath(target) { + try { + return decodeURIComponent(target); + } catch (_) { + return target; } - - const arrowMessageSymbol = privateSymbols.arrow_message_private_symbol; - return typeof arrowMessageSymbol === 'symbol' ? arrowMessageSymbol : undefined; } -function escapeRegExp(text) { - return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +function packagePatternKeyMatch(patternKey, key) { + const star = patternKey.indexOf('*'); + if (star === -1) return null; + const prefix = patternKey.slice(0, star); + const suffix = patternKey.slice(star + 1); + if (!key.startsWith(prefix) || !key.endsWith(suffix)) return null; + if (key.length <= prefix.length + suffix.length) return null; + return key.slice(prefix.length, key.length - suffix.length); } -function maybeSetArrowMessageOnSyntaxError(err, filename, source) { - if (!err || err.name !== 'SyntaxError') { - return; +function findBestPackagePattern(map, key) { + let bestKey = null; + let bestSubstitution = null; + const keys = Object.keys(map); + for (let i = 0; i < keys.length; i++) { + const patternKey = keys[i]; + if (patternKey.indexOf('*') === -1) continue; + const substitution = packagePatternKeyMatch(patternKey, key); + if (substitution === null) continue; + if (bestKey === null || packagePatternCompare(patternKey, bestKey) < 0) { + bestKey = patternKey; + bestSubstitution = substitution; + } } + return bestKey === null ? null : { key: bestKey, substitution: bestSubstitution }; +} - const arrowMessageSymbol = getArrowMessagePrivateSymbol(); - if (arrowMessageSymbol === undefined || err[arrowMessageSymbol] !== undefined) { - return; +function findPackageMapTarget(map, specifier, invalidPatternMessage) { + if (Object.prototype.hasOwnProperty.call(map, specifier)) { + return { target: map[specifier], patternSubstitution: undefined, patternKey: undefined }; + } + const pattern = findBestPackagePattern(map, specifier); + if (pattern === null) return null; + if (isInvalidPackagePatternSubstitution(pattern.substitution)) { + throw makeInvalidModuleSpecifierError( + specifier, + invalidPackagePatternSubstitutionMessage(pattern.substitution, invalidPatternMessage) + ); } + return { + target: map[pattern.key], + patternSubstitution: pattern.substitution, + patternKey: pattern.key + }; +} - let line = 1; - let column = 1; +function packagePatternCompare(a, b) { + const aStar = a.indexOf('*'); + const bStar = b.indexOf('*'); + const aBase = aStar === -1 ? a.length : aStar; + const bBase = bStar === -1 ? b.length : bStar; + if (aBase !== bBase) return bBase - aBase; + const aTrailer = aStar === -1 ? 0 : a.length - aStar - 1; + const bTrailer = bStar === -1 ? 0 : b.length - bStar - 1; + if (aTrailer !== bTrailer) return bTrailer - aTrailer; + if (a.length !== b.length) return b.length - a.length; + return a < b ? -1 : a > b ? 1 : 0; +} - if (typeof err.lineNumber === 'number' && Number.isFinite(err.lineNumber) && err.lineNumber > 0) { - line = Math.floor(err.lineNumber); - } - if (typeof err.columnNumber === 'number' && Number.isFinite(err.columnNumber) && err.columnNumber > 0) { - column = Math.floor(err.columnNumber); +function resolvePackageTargetValue(packageDir, target, conditions, seen, allowBareTarget, patternSubstitution, warningContext) { + seen = seen || new Set(); + if (target === null) return packageTargetBlocked; + if (target === false) { + throw makeInvalidPackageTargetError('false', allowBareTarget ? 'imports' : 'exports'); + } + + if (typeof target === 'string') { + if (patternSubstitution !== undefined && patternSubstitution !== null) { + target = target.replace(/\*/g, () => patternSubstitution); + } + if (warningContext) { + emitDeprecatedPackageTargetWarning( + warningContext.kind, + warningContext.specifier, + target, + patternSubstitution, + packageDir, + warningContext.patternKey, + warningContext.importer + ); + } + if (allowBareTarget && isBarePackageSpecifier(target)) { + const resolved = resolveFromNodeModules(target, packageDir, pathModule.join(packageDir, 'package.json'), conditions); + if (resolved !== null) return resolved; + throw makeModuleNotFoundError(target); + } + if (hasEncodedSlashOrBackslash(target)) { + throw makeInvalidModuleSpecifierError(target, 'must not include encoded "/" or "\\" characters'); + } + if (!target.startsWith('./')) { + throw makeInvalidPackageTargetError(target, allowBareTarget ? 'imports' : 'exports'); + } + if (!validatePackageTargetPath(target)) { + throw makeInvalidPackageTargetError(target, allowBareTarget ? 'imports' : 'exports'); + } + const candidate = pathModule.resolve(packageDir, decodePackageTargetPath(target)); + const relative = pathModule.relative(packageDir, candidate); + if (relative.startsWith('..') || pathModule.isAbsolute(relative)) { + throw makeInvalidPackageTargetError(target, allowBareTarget ? 'imports' : 'exports'); + } + return resolveExactPackageFile(candidate); + } + + if (Array.isArray(target)) { + let lastFallbackError = null; + for (let i = 0; i < target.length; i++) { + try { + const resolved = resolvePackageTargetValue(packageDir, target[i], conditions, seen, allowBareTarget, patternSubstitution, warningContext); + if (resolved === packageTargetBlocked) continue; + if (resolved !== packageTargetNoMatch) return resolved; + } catch (err) { + if (!err || err.code !== 'ERR_INVALID_PACKAGE_TARGET') throw err; + lastFallbackError = err; + } + } + if (lastFallbackError !== null) throw lastFallbackError; + return packageTargetNoMatch; + } + + if (target && typeof target === 'object') { + if (seen.has(target)) return null; + seen.add(target); + const keys = Object.keys(target); + for (let i = 0; i < keys.length; i++) { + const condition = keys[i]; + if (conditions.has(condition)) { + const resolved = resolvePackageTargetValue(packageDir, target[condition], conditions, seen, allowBareTarget, patternSubstitution, warningContext); + if (resolved === packageTargetNoMatch) continue; + return resolved; + } + } + return packageTargetNoMatch; + } + + throw makeInvalidPackageTargetError(target, allowBareTarget ? 'imports' : 'exports'); +} + +function resolvePackageTargetWithContext(packageDir, target, conditions, allowBareTarget, patternSubstitution, warningContext) { + try { + return resolvePackageTargetValue(packageDir, target, conditions, undefined, allowBareTarget, patternSubstitution, warningContext); + } catch (err) { + if (err && err.code === 'ERR_INVALID_PACKAGE_TARGET') { + throw addPackageErrorContext(err, warningContext.specifier); + } + throw err; + } +} + +function isPackageExportsConditionsObject(exportsField) { + if (!exportsField || typeof exportsField !== 'object' || Array.isArray(exportsField)) return false; + const keys = Object.keys(exportsField); + return keys.length > 0 && !keys.some((key) => key.startsWith('.')); +} + +function validatePackageExportsMap(pkgJsonPath, exportsField) { + if (!exportsField || typeof exportsField !== 'object' || Array.isArray(exportsField)) return; + const keys = Object.keys(exportsField); + for (let i = 0; i < keys.length; i++) { + if (/^(?:0|[1-9][0-9]*)$/.test(keys[i])) { + throw makeInvalidPackageConfigError(pkgJsonPath, '"exports" cannot contain numeric property keys'); + } + } + if (keys.length > 0) { + const hasSubpathKey = keys.some((key) => key.startsWith('.')); + const hasConditionKey = keys.some((key) => !key.startsWith('.')); + if (hasSubpathKey && hasConditionKey) { + throw makeInvalidPackageConfigError(pkgJsonPath, '"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.'); + } + } +} + +function resolvePackageExports(packageName, packageDir, pkg, subpath, conditions) { + if (!pkg || !Object.prototype.hasOwnProperty.call(pkg, 'exports')) return undefined; + const key = subpath ? './' + subpath : '.'; + const exportsField = pkg.exports; + let resolved = null; + + if (typeof exportsField === 'string' || Array.isArray(exportsField) || isPackageExportsConditionsObject(exportsField)) { + if (key === '.') { + resolved = resolvePackageTargetWithContext(packageDir, exportsField, conditions, false, undefined, { kind: 'exports', specifier: key }); + } + } else if (exportsField && typeof exportsField === 'object') { + const match = findPackageMapTarget(exportsField, key, 'is not a valid match in pattern'); + if (match !== null) { + resolved = resolvePackageTargetWithContext( + packageDir, + match.target, + conditions, + false, + match.patternSubstitution, + { kind: 'exports', specifier: key, patternKey: match.patternKey } + ); + } + } else if (exportsField !== null) { + throw addPackageErrorContext(makeInvalidPackageTargetError(exportsField, 'exports'), key); + } + + if (resolved !== null && resolved !== packageTargetNoMatch && resolved !== packageTargetBlocked) return resolved; + throw makePackagePathNotExportedError(packageName, subpath, key === '.' && isPackageExportsConditionsObject(exportsField)); +} + +function resolvePackageExportsEntry(parts, packageDir, pkg, pkgJsonPath, conditions) { + if (!pkg || !Object.prototype.hasOwnProperty.call(pkg, 'exports')) return undefined; + validatePackageExportsMap(pkgJsonPath, pkg.exports); + const resolved = resolvePackageExports(parts.name, packageDir, pkg, parts.subpath, conditions); + if (resolved !== undefined) { + resolved.packageDir = packageDir; + } + return resolved; +} + +function resolvePackageSelfReference(parts, parentDir, conditions) { + const scope = findPackageScope(parentDir); + if (!scope || !scope.pkg || scope.pkg.name !== parts.name) return undefined; + return resolvePackageExportsEntry(parts, scope.dir, scope.pkg, scope.pkgJsonPath, conditions); +} + +function readPackageDirectoryForExports(parts, packageDir, pkgJsonPath, conditions) { + const packageJsonEntry = readPackageJson(pkgJsonPath); + if (packageJsonEntry === null) return null; + const pkg = packageJsonEntry.pkg; + return { + pkg, + exportsResolved: resolvePackageExportsEntry(parts, packageDir, pkg, pkgJsonPath, conditions), + }; +} + +function readCjsPackageCandidate(filename, packageDir) { + const content = tryReadFile(filename); + return content === null ? null : { filename, content, packageDir }; +} + +function readCjsPackageFileCandidates(candidate, packageDir) { + let resolved = readCjsPackageCandidate(candidate, packageDir); + if (resolved !== null) return resolved; + resolved = readCjsPackageCandidate(candidate + '.js', packageDir); + if (resolved !== null) return resolved; + resolved = readCjsPackageCandidate(candidate + '.json', packageDir); + if (resolved !== null) return resolved; + return readCjsPackageCandidate(candidate + '.node', packageDir); +} + +function readCjsPackageIndexCandidates(candidate, packageDir) { + let resolved = readCjsPackageCandidate(pathModule.join(candidate, 'index.js'), packageDir); + if (resolved !== null) return resolved; + resolved = readCjsPackageCandidate(pathModule.join(candidate, 'index.json'), packageDir); + if (resolved !== null) return resolved; + return readCjsPackageCandidate(pathModule.join(candidate, 'index.node'), packageDir); +} + +function makeInvalidPackageConfigWhileImporting(pkgJsonPath, id, fromPart, cause) { + const pkgErr = new Error( + 'Invalid package config ' + pkgJsonPath + + ' while importing "' + id + '" from ' + fromPart + '.' + + (cause && cause.message ? ' ' + cause.message : '') + ); + pkgErr.code = 'ERR_INVALID_PACKAGE_CONFIG'; + return pkgErr; +} + +function resolveCjsPackageMain(pkgDir, pkg, pkgJsonPath, id, fromPart) { + if (pkg === null || !Object.prototype.hasOwnProperty.call(pkg, 'main') || typeof pkg.main !== 'string') { + return null; + } + + try { + const mainPath = pathModule.resolve(pkgDir, pkg.main); + let resolved = readCjsPackageFileCandidates(mainPath, pkgDir); + if (resolved !== null) return resolved; + return readCjsPackageIndexCandidates(mainPath, pkgDir); + } catch (e) { + throw makeInvalidPackageConfigWhileImporting(pkgJsonPath, id, fromPart, e); + } +} + +function resolveCjsPackageFallbacks(parts, pkgDir, pkg, pkgJsonPath, id, fromPart) { + if (parts.subpath.length > 0) { + const subCandidate = pathModule.join(pkgDir, parts.subpath); + let resolved = readCjsPackageFileCandidates(subCandidate, pkgDir); + if (resolved !== null) return resolved; + return readCjsPackageIndexCandidates(subCandidate, pkgDir); + } + + let resolved = readCjsPackageFileCandidates(pkgDir, pkgDir); + if (resolved !== null) return resolved; + + resolved = resolveCjsPackageMain(pkgDir, pkg, pkgJsonPath, id, fromPart); + if (resolved !== null) return resolved; + + return readCjsPackageIndexCandidates(pkgDir, pkgDir); +} + +const packageScopeCache = Object.create(null); + +function findPackageScope(startDir) { + let dir = pathModule.resolve(startDir || '/'); + while (true) { + if (pathModule.basename(dir) === 'node_modules') return null; + if (Object.prototype.hasOwnProperty.call(packageScopeCache, dir)) { + return packageScopeCache[dir]; + } + const pkgJsonPath = pathModule.join(dir, 'package.json'); + const packageJsonEntry = readPackageJson(pkgJsonPath); + if (packageJsonEntry !== null) { + const scope = { dir, pkg: packageJsonEntry.pkg, pkgJsonPath }; + packageScopeCache[dir] = scope; + return scope; + } + const parent = pathModule.dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +function resolvePackageImports(id, parentDir, conditions) { + const scope = findPackageScope(parentDir); + if (!scope || !scope.pkg || !scope.pkg.imports || typeof scope.pkg.imports !== 'object') { + throw makePackageImportNotDefinedError(id); + } + validatePackageImportSpecifier(id); + const match = findPackageMapTarget(scope.pkg.imports, id, 'request is not a valid match in pattern'); + if (match === null) { + throw makePackageImportNotDefinedError(id); + } + const resolved = resolvePackageTargetWithContext( + scope.dir, + match.target, + conditions, + true, + match.patternSubstitution, + { kind: 'imports', specifier: id, patternKey: match.patternKey } + ); + if (resolved !== packageTargetNoMatch && resolved !== packageTargetBlocked) return resolved; + throw makePackageImportNotDefinedError(id); +} + +function resolveFilename(id, parentDir) { + const hasTrailingSlash = /\/$/.test(id); + const forceDirectory = hasTrailingSlash || /(?:^|\/)\.\.?$/.test(id); + const candidate = pathModule.isAbsolute(id) + ? pathModule.normalize(id) + : pathModule.resolve(parentDir, id); + + let resolved = null; + if (!forceDirectory) { + resolved = loadAsFile(candidate, false); + if (resolved !== null) return resolved; + } + + if (forceDirectory || isPathDirectory(candidate)) { + resolved = loadAsDirectory(candidate, id, parentDir); + if (resolved !== null) return resolved; + } + + const err = new Error("Cannot find module '" + id + "' from '" + parentDir + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; +} + +function addRequireStackToModuleNotFound(err, request, parentFilename) { + if (!err || err.code !== 'MODULE_NOT_FOUND' || typeof parentFilename !== 'string') return err; + if (typeof err.path === 'string' && typeof err.requestPath === 'string') return err; + err.requireStack = [parentFilename]; + err.message = "Cannot find module '" + request + "'\nRequire stack:\n- " + parentFilename; + return err; +} + +function hasAllowNativesSyntaxFlag() { + const runtimeFlags = globalThis.__wasm_rquickjs_v8_runtime_flags; + if (runtimeFlags && runtimeFlags.allowNativesSyntax === true) { + return true; + } + + const processObject = globalThis.process; + if (!processObject || !Array.isArray(processObject.execArgv)) { + return false; + } + + let enabled = false; + for (let i = 0; i < processObject.execArgv.length; i++) { + const arg = String(processObject.execArgv[i]).replace(/_/g, '-'); + if (arg === '--allow-natives-syntax') { + enabled = true; + continue; + } + + if (arg === '--noallow-natives-syntax' || arg === '--no-allow-natives-syntax') { + enabled = false; + } + } + + return enabled; +} + +function stripV8OptimizationIntrinsics(source) { + if (!hasAllowNativesSyntaxFlag()) { + return source; + } + + // QuickJS cannot parse V8-native `%...` syntax used in eval strings. + // These intrinsics only force optimization and are semantically no-ops. + return source + .replace(/eval\(\s*(['"])%PrepareFunctionForOptimization\([^'"\\\r\n]*\)\1\s*\)\s*;?/g, 'undefined;') + .replace(/eval\(\s*(['"])%OptimizeFunctionOnNextCall\([^'"\\\r\n]*\)\1\s*\)\s*;?/g, 'undefined;'); +} + +function stripImportAttributes(source, filename) { + const len = source.length; + let out = []; + const filenameLiteral = JSON.stringify(filename); + const baseUrlLiteral = JSON.stringify(nodeUrl.pathToFileURL(filename).href); + function prevNonWs(pos) { + while (pos > 0) { + pos--; + const c = source.charCodeAt(pos); + if (c !== 0x20 && c !== 0x09 && c !== 0x0A && c !== 0x0D && c !== 0x0C && c !== 0x0B) return { pos, c }; + } + return null; + } + function prevWord(pos) { + let end = pos; + while (end > 0) { + const c = source.charCodeAt(end - 1); + if (c !== 0x20 && c !== 0x09 && c !== 0x0A && c !== 0x0D && c !== 0x0C && c !== 0x0B) break; + end--; + } + let start = end; + while (start > 0) { + const c = source.charCodeAt(start - 1); + if (!(c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95 || c === 36)) break; + start--; + } + return start < end ? { word: source.substring(start, end), start } : null; + } + function matchingParenEnd(openParen) { + let pos = openParen + 1; + let depth = 1; + while (pos < len) { + const c = source.charCodeAt(pos); + const skipped = skipNonCode(source, pos, true); + if (skipped !== null) { pos = skipped; continue; } + if (c === 0x28) depth++; + else if (c === 0x29 && --depth === 0) return pos + 1; + pos++; + } + return -1; + } + function nextNonWs(pos) { + while (pos < len) { + const c = source.charCodeAt(pos); + if (c !== 0x20 && c !== 0x09 && c !== 0x0A && c !== 0x0D && c !== 0x0C && c !== 0x0B) return c; + pos++; + } + return -1; + } + function methodPrefixBoundary(pos) { + const prev = prevNonWs(pos); + return !prev || prev.c === 0x7B || prev.c === 0x2C || prev.c === 0x3B; + } + function isImportMethodDefinition(importStart, openParen) { + const close = matchingParenEnd(openParen); + if (close < 0 || nextNonWs(close) !== 0x7B) return false; + const directWord = prevWord(importStart); + if (directWord && directWord.word === 'static') return true; + let pos = importStart; + for (;;) { + const prev = prevNonWs(pos); + if (!prev) return false; + if (prev.c === 0x7B || prev.c === 0x2C || prev.c === 0x3B) return true; + if (prev.c === 0x2A) { + pos = prev.pos; + continue; + } + const word = prevWord(pos); + if (word && (word.word === 'async' || word.word === 'get' || word.word === 'set' || word.word === 'static')) { + pos = word.start; + continue; + } + return false; + } + } + function skipWsComments(pos, end) { + let i = pos; + while (i < end) { + const c = source.charCodeAt(i); + if (c === 0x20 || c === 0x09 || c === 0x0A || c === 0x0D || c === 0x0C || c === 0x0B) { + i++; + continue; + } + if (c === 0x2F && i + 1 < end && source.charCodeAt(i + 1) === 0x2F) { + i += 2; + while (i < end && source.charCodeAt(i) !== 0x0A && source.charCodeAt(i) !== 0x0D) i++; + continue; + } + if (c === 0x2F && i + 1 < end && source.charCodeAt(i + 1) === 0x2A) { + i += 2; + while (i + 1 < end && !(source.charCodeAt(i) === 0x2A && source.charCodeAt(i + 1) === 0x2F)) i++; + i = Math.min(i + 2, end); + continue; + } + break; + } + return i; + } + function findTemplateExpressionEnd(start, end) { + let i = start; + let depth = 1; + while (i < end) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { i = skipped; continue; } + const c = source.charCodeAt(i); + if (c === 0x7B || c === 0x28 || c === 0x5B) { depth++; i++; } + else if (c === 0x7D || c === 0x29 || c === 0x5D) { + depth--; + if (depth === 0) return i; + i++; + } else { i++; } + } + return end; + } + function processTemplate(start, end) { + out.push('`'); + let i = start + 1; + let rawStart = i; + while (i < end) { + const c = source.charCodeAt(i); + if (c === 0x5C) { i += 2; continue; } + if (c === 0x60) { + out.push(source.substring(rawStart, i + 1)); + return i + 1; + } + if (c === 0x24 && i + 1 < end && source.charCodeAt(i + 1) === 0x7B) { + out.push(source.substring(rawStart, i + 2)); + const exprEnd = findTemplateExpressionEnd(i + 2, end); + processRange(i + 2, exprEnd); + if (exprEnd < end) { + out.push('}'); + i = exprEnd + 1; + } else { + i = exprEnd; + } + rawStart = i; + continue; + } + i++; + } + out.push(source.substring(rawStart, i)); + return i; + } + function processRange(start, end) { + let i = start; + while (i < end) { + let ch = source.charCodeAt(i); + if (ch === 0x60) { + i = processTemplate(i, end); + continue; + } + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + out.push(source.substring(i, skipped)); + i = skipped; + continue; + } + if (ch === 0x69 && i + 6 <= end && source.substring(i, i + 6) === 'import' && + (i === 0 || ((ch = source.charCodeAt(i - 1)) !== 46 && ch !== 35 && !isIdentifierContinueCode(ch))) && + (i + 6 >= end || !isIdentifierContinueCode(source.charCodeAt(i + 6)))) { + const openParen = skipWsComments(i + 6, end); + if (openParen < end && source.charCodeAt(openParen) === 0x28) { + if (isImportMethodDefinition(i, openParen)) { + out.push(source[i]); + i++; + continue; + } + i = openParen + 1; + let depth = 1, commaPos = -1; + const argStart = i; + while (i < end && depth > 0) { + ch = source.charCodeAt(i); + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { i = skipped; } + else if (ch === 0x28 || ch === 0x5B || ch === 0x7B) { depth++; i++; } + else if (ch === 0x29 || ch === 0x5D || ch === 0x7D) { depth--; i++; } + else if (ch === 0x2C && depth === 1 && commaPos === -1) { commaPos = i; i++; } + else { i++; } + } + if (commaPos > -1) { + const firstArg = processSubrange(argStart, commaPos); + const secondArg = processSubrange(commaPos + 1, i - 1); + out.push('((async(__wasm_rquickjs_specifier,__wasm_rquickjs_options)=>{const __wasm_rquickjs_url=String(__wasm_rquickjs_specifier);return globalThis.__wasm_rquickjs_trace_module_import(__wasm_rquickjs_url,'); + out.push(filenameLiteral); + out.push(',async()=>globalThis.__wasm_rquickjs_import_attr_dynamic_import('); + out.push(baseUrlLiteral); + out.push(',__wasm_rquickjs_url,__wasm_rquickjs_options,true,(__wasm_rquickjs_prepared)=>import(__wasm_rquickjs_prepared)));})('); + out.push(firstArg); + out.push(','); + out.push(secondArg); + out.push('))'); + } else { + const spec = processSubrange(argStart, i - 1); + out.push('((async(__wasm_rquickjs_specifier)=>{const __wasm_rquickjs_url=String(__wasm_rquickjs_specifier);return globalThis.__wasm_rquickjs_trace_module_import(__wasm_rquickjs_url,'); + out.push(filenameLiteral); + out.push(',async()=>globalThis.__wasm_rquickjs_import_attr_dynamic_import('); + out.push(baseUrlLiteral); + out.push(',__wasm_rquickjs_url,undefined,true,(__wasm_rquickjs_prepared)=>import(__wasm_rquickjs_prepared)));})('); + out.push(spec); + out.push('))'); + } + continue; + } + } + out.push(source[i]); + i++; + } + } + function processSubrange(start, end) { + const previousOut = out; + out = []; + processRange(start, end); + const rewritten = out.join(''); + out = previousOut; + return rewritten; + } + processRange(0, len); + return out.join(''); +} +function hasExecArgvFlag(flag) { + const processObject = globalThis.process; + if (!processObject || !Array.isArray(processObject.execArgv)) { + return false; + } + + const prefixed = flag + '='; + for (let i = 0; i < processObject.execArgv.length; i++) { + const arg = String(processObject.execArgv[i]); + if (arg === flag || arg.indexOf(prefixed) === 0) { + return true; + } + } + + return false; +} + +function isExperimentalTransformTypesEnabled() { + return hasExecArgvFlag('--experimental-transform-types'); +} + +function isSourceMapsEnabled() { + if (hasExecArgvFlag('--no-enable-source-maps')) { + return false; + } + + return hasExecArgvFlag('--enable-source-maps') || isExperimentalTransformTypesEnabled(); +} + +function getSimpleSourceMapRegistry() { + let registry = globalThis.__wasm_rquickjs_simple_source_maps; + if (!registry || typeof registry !== 'object') { + registry = Object.create(null); + globalThis.__wasm_rquickjs_simple_source_maps = registry; + } + return registry; +} + +function getCjsSourceMapOwnerRegistry() { + let registry = globalThis.__wasm_rquickjs_cjs_source_map_owners; + if (!registry || typeof registry !== 'object') { + registry = Object.create(null); + globalThis.__wasm_rquickjs_cjs_source_map_owners = registry; + } + return registry; +} + +function getCjsLineOffsetRegistry() { + let registry = globalThis.__wasm_rquickjs_cjs_line_offsets; + if (!registry || typeof registry !== 'object') { + registry = Object.create(null); + globalThis.__wasm_rquickjs_cjs_line_offsets = registry; + } + return registry; +} + +const cjsLineOffset = 6; + +function derefWeakRef(ref) { + if (ref === undefined || ref === null) return undefined; + try { + if (typeof ref.deref === 'function') return ref.deref(); + } catch (_) { + return ref; + } + try { + if (typeof WeakRef === 'function' && WeakRef.prototype && typeof WeakRef.prototype.deref === 'function') { + return WeakRef.prototype.deref.call(ref); + } + } catch (_) { + return ref; + } + return ref; +} + +function makeWeakRef(value) { + if (typeof WeakRef !== 'function') return undefined; + try { + return new WeakRef(value); + } catch (err) { + return undefined; + } +} + +const sourceMapVlqChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +const sourceMapVlqMap = Object.create(null); +for (let i = 0; i < sourceMapVlqChars.length; i++) { + sourceMapVlqMap[sourceMapVlqChars.charAt(i)] = i; +} + +function sourceMapInvalidPayloadError(payload) { + let received; + if (payload === null) { + received = ' Received null'; + } else if (typeof payload === 'number') { + received = ' Received type number (' + payload + ')'; + } else if (typeof payload === 'string') { + received = " Received type string ('" + payload + "')"; + } else { + received = ' Received type ' + typeof payload + ' (' + String(payload) + ')'; + } + const err = new TypeError('The "payload" argument must be of type object.' + received); + err.code = 'ERR_INVALID_ARG_TYPE'; + return err; +} + +function cloneSourceMapPayload(payload) { + return JSON.parse(JSON.stringify(payload)); +} + +function decodeSourceMapVlq(text, state) { + let result = 0; + let shift = 0; + let continuation = true; + while (continuation) { + if (state.index >= text.length) throw new Error('Unexpected end of source map VLQ'); + const value = sourceMapVlqMap[text.charAt(state.index++)]; + if (value === undefined) throw new Error('Invalid source map VLQ character'); + continuation = (value & 32) !== 0; + result += (value & 31) * Math.pow(2, shift); + shift += 5; + } + const negative = (result % 2) === 1; + result = Math.floor(result / 2); + if (negative && result === 0) return -2147483648; + return negative ? -result : result; +} + +function resolveSourceMapSource(source, sourceRoot, sourceBasePath) { + source = String(source); + if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(source)) return source; + if (sourceBasePath) { + const resolved = pathModule.resolve(sourceBasePath, sourceRoot || '', source); + return nodeUrl.pathToFileURL(resolved).href; + } + if (!sourceRoot) return source; + sourceRoot = String(sourceRoot); + if (sourceRoot.endsWith('/') || source.startsWith('/')) return sourceRoot + source; + return sourceRoot + '/' + source; +} + +function parseSourceMapMappings(payload, sourceBasePath) { + const mappings = String(payload.mappings); + const sources = Array.isArray(payload.sources) ? payload.sources : []; + const names = Array.isArray(payload.names) ? payload.names : []; + const sourceRoot = payload.sourceRoot || ''; + const lines = []; + let generatedLine = 0; + let previousGeneratedColumn = 0; + let previousSource = 0; + let previousOriginalLine = 0; + let previousOriginalColumn = 0; + let previousName = 0; + let i = 0; + + while (i <= mappings.length) { + if (!lines[generatedLine]) lines[generatedLine] = []; + if (i === mappings.length) break; + const ch = mappings.charAt(i); + if (ch === ';') { + generatedLine++; + previousGeneratedColumn = 0; + i++; + continue; + } + if (ch === ',') { + i++; + continue; + } + + const segmentStart = i; + while (i < mappings.length && mappings.charAt(i) !== ',' && mappings.charAt(i) !== ';') { + i++; + } + const segmentText = mappings.slice(segmentStart, i); + if (segmentText.length === 0) continue; + const state = { index: 0 }; + const generatedColumn = previousGeneratedColumn + decodeSourceMapVlq(segmentText, state); + previousGeneratedColumn = generatedColumn; + if (state.index >= segmentText.length) { + lines[generatedLine].push({ generatedLine, generatedColumn }); + continue; + } + + const sourceIndex = previousSource + decodeSourceMapVlq(segmentText, state); + const originalLine = previousOriginalLine + decodeSourceMapVlq(segmentText, state); + const originalColumn = previousOriginalColumn + decodeSourceMapVlq(segmentText, state); + previousSource = sourceIndex; + previousOriginalLine = originalLine; + previousOriginalColumn = originalColumn; + let name; + if (state.index < segmentText.length) { + const nameIndex = previousName + decodeSourceMapVlq(segmentText, state); + previousName = nameIndex; + name = names[nameIndex]; + } + + lines[generatedLine].push({ + generatedLine, + generatedColumn, + originalSource: resolveSourceMapSource(sources[sourceIndex], sourceRoot, sourceBasePath), + originalLine, + originalColumn, + name, + }); + } + + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + if (lines[lineIndex]) { + lines[lineIndex].sort((a, b) => a.generatedColumn - b.generatedColumn); + } + } + return lines; +} + +function parseIndexSourceMapMappings(payload, sourceBasePath) { + const lines = []; + const sections = Array.isArray(payload.sections) ? payload.sections : []; + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + if (!section || !section.map || !section.offset) continue; + const offsetLine = Number(section.offset.line) || 0; + const offsetColumn = Number(section.offset.column) || 0; + const sectionMap = parseSourceMapMappings(section.map, sourceBasePath); + for (let line = 0; line < sectionMap.length; line++) { + const segments = sectionMap[line]; + if (!segments) continue; + const targetLine = line + offsetLine; + if (!lines[targetLine]) lines[targetLine] = []; + for (let j = 0; j < segments.length; j++) { + const segment = Object.assign({}, segments[j]); + segment.generatedLine += offsetLine; + if (line === 0) segment.generatedColumn += offsetColumn; + lines[targetLine].push(segment); + } + } + } + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + if (lines[lineIndex]) { + lines[lineIndex].sort((a, b) => a.generatedColumn - b.generatedColumn); + } + } + return lines; +} + +function decodeSourceMapPayload(payload, sourceBasePath) { + try { + if (Array.isArray(payload.sections)) return parseIndexSourceMapMappings(payload, sourceBasePath); + return parseSourceMapMappings(payload, sourceBasePath); + } catch (_) { + return []; + } +} + +function cloneSourceMapEntry(entry) { + if (!entry || entry.originalSource === undefined) return {}; + return { + generatedLine: entry.generatedLine, + generatedColumn: entry.generatedColumn, + originalSource: entry.originalSource, + originalLine: entry.originalLine, + originalColumn: entry.originalColumn, + name: entry.name, + }; +} + +function findSourceMapMapping(lines, lineOffset, columnOffset) { + lineOffset = Math.floor(lineOffset); + columnOffset = Math.floor(columnOffset); + for (let lineIndex = lineOffset; lineIndex >= 0; lineIndex--) { + const line = lines[lineIndex]; + if (!line || line.length === 0) continue; + let match = null; + if (lineIndex === lineOffset) { + for (let i = 0; i < line.length; i++) { + if (line[i].generatedColumn <= columnOffset) match = line[i]; + else break; + } + if (match) return match; + } else { + return line[line.length - 1]; + } + } + return null; +} + +class SourceMap { + constructor(payload, options) { + if (payload === null || typeof payload !== 'object') { + throw sourceMapInvalidPayloadError(payload); + } + options = options || {}; + this.payload = cloneSourceMapPayload(payload); + if (options.lineLengths !== undefined) { + this.lineLengths = Array.prototype.slice.call(options.lineLengths); + } + this._decodedMappings = decodeSourceMapPayload(this.payload, options.sourceBasePath); + } + + findEntry(lineOffset, columnOffset) { + lineOffset = Number(lineOffset); + columnOffset = Number(columnOffset); + if (!Number.isFinite(lineOffset) || !Number.isFinite(columnOffset)) return {}; + return cloneSourceMapEntry(findSourceMapMapping(this._decodedMappings, lineOffset, columnOffset)); + } + + findOrigin(lineNumber, columnNumber) { + const generatedLine = Number(lineNumber) - 1; + const generatedColumn = Number(columnNumber) - 1; + if (!Number.isFinite(generatedLine) || !Number.isFinite(generatedColumn)) return {}; + const match = findSourceMapMapping(this._decodedMappings, generatedLine, generatedColumn); + if (!match) return {}; + return { + name: match.name, + fileName: match.originalSource, + lineNumber: match.originalLine + 1, + columnNumber: match.originalColumn + (generatedColumn - match.generatedColumn) + 1, + }; + } +} + +function findSourceMap(path) { + path = String(path); + const owners = getCjsSourceMapOwnerRegistry(); + const ownerRef = owners[path]; + if (ownerRef !== undefined && derefWeakRef(ownerRef) === undefined) { + delete owners[path]; + delete getSimpleSourceMapRegistry()[path]; + return undefined; + } + const registry = getSimpleSourceMapRegistry(); + return registry[path]; +} + +function sourceMapLineLengths(source) { + return String(source).split(/\r\n|[\n\r\u2028\u2029]/).map(line => line.length); +} + +function decodeInlineSourceMap(url) { + const marker = 'base64,'; + const idx = url.indexOf(marker); + if (idx === -1) return null; + try { + const encoded = url.slice(idx + marker.length); + const decoded = buffer.Buffer.from(encoded, 'base64').toString('utf8'); + return JSON.parse(decoded); + } catch (_) { + return null; + } +} + +function registerSourceMapForCjs(filename, source, moduleObject) { + const registry = getSimpleSourceMapRegistry(); + const owners = getCjsSourceMapOwnerRegistry(); + if (!isSourceMapsEnabled()) { + delete registry[filename]; + delete owners[filename]; + return; + } + + const sourceText = String(source); + const directiveRe = /\/\/[#@]\s*sourceMappingURL=([^\r\n]+)|\/\*[#@]\s*sourceMappingURL=([\s\S]*?)\*\//g; + let match; + let url = null; + while ((match = directiveRe.exec(sourceText)) !== null) { + url = (match[1] !== undefined ? match[1] : match[2]).trim(); + } + if (url === null) { + delete registry[filename]; + delete owners[filename]; + return; + } + + let payload = null; + let sourceBasePath = pathModule.dirname(filename); + if (url.startsWith('data:')) { + payload = decodeInlineSourceMap(url); + } else { + const mapPath = pathModule.resolve(pathModule.dirname(filename), url); + sourceBasePath = pathModule.dirname(mapPath); + const content = tryReadFile(mapPath); + if (content !== null) { + try { + payload = JSON.parse(content); + } catch (_) { + payload = null; + } + } + } + if (payload === null) { + delete registry[filename]; + delete owners[filename]; + return; + } + registry[filename] = new SourceMap(payload, { + lineLengths: sourceMapLineLengths(source), + sourceBasePath, + }); + if (moduleObject) { + const ownerRef = makeWeakRef(moduleObject); + if (ownerRef !== undefined) { + owners[filename] = ownerRef; + } else { + delete owners[filename]; + } + } else { + delete owners[filename]; + } +} + +function countMatches(text, charCode) { + let count = 0; + for (let i = 0; i < text.length; i++) { + if (text.charCodeAt(i) === charCode) { + count += 1; + } + } + return count; +} + +function transpileTypeScriptModule(filename, source) { + if (!isExperimentalTransformTypesEnabled() || !filename.endsWith('.ts')) { + return source; + } + + const lines = String(source).split('\n'); + const transformedLines = []; + const generatedLineToOriginalLine = Object.create(null); + let insideInterface = false; + let interfaceDepth = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (insideInterface) { + interfaceDepth += countMatches(line, 123) - countMatches(line, 125); + if (interfaceDepth <= 0) { + insideInterface = false; + interfaceDepth = 0; + } + continue; + } + + const trimmed = line.trim(); + if (/^interface\s+[A-Za-z_$][A-Za-z0-9_$]*\b/.test(trimmed)) { + interfaceDepth = countMatches(line, 123) - countMatches(line, 125); + if (interfaceDepth > 0) { + insideInterface = true; + } + continue; + } + + if (trimmed.length === 0) { + continue; + } + + transformedLines.push(line); + generatedLineToOriginalLine[transformedLines.length] = i + 1; + } + + const transformed = transformedLines.join('\n'); + const sourceMapRegistry = getSimpleSourceMapRegistry(); + if (isSourceMapsEnabled()) { + sourceMapRegistry[filename] = { + generatedLineToOriginalLine, + }; + } else { + delete sourceMapRegistry[filename]; + } + + return transformed; +} + +function getArrowMessagePrivateSymbol() { + const privateSymbols = globalThis.__wasm_rquickjs_internal_private_symbols; + if (!privateSymbols || typeof privateSymbols !== 'object') { + return undefined; + } + + const arrowMessageSymbol = privateSymbols.arrow_message_private_symbol; + return typeof arrowMessageSymbol === 'symbol' ? arrowMessageSymbol : undefined; +} + +function escapeRegExp(text) { + return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function maybeSetArrowMessageOnSyntaxError(err, filename, source) { + if (!err || err.name !== 'SyntaxError') { + return; + } + + const arrowMessageSymbol = getArrowMessagePrivateSymbol(); + if (arrowMessageSymbol === undefined || err[arrowMessageSymbol] !== undefined) { + return; + } + + let line = 1; + let column = 1; + + if (typeof err.lineNumber === 'number' && Number.isFinite(err.lineNumber) && err.lineNumber > 0) { + line = Math.floor(err.lineNumber); + } + if (typeof err.columnNumber === 'number' && Number.isFinite(err.columnNumber) && err.columnNumber > 0) { + column = Math.floor(err.columnNumber); } if (typeof err.stack === 'string') { @@ -946,113 +2156,2705 @@ function maybeSetArrowMessageOnSyntaxError(err, filename, source) { } } - const sourceLines = String(source).split('\n'); - let sourceLine = ''; - if (line >= 1 && line <= sourceLines.length) { - sourceLine = sourceLines[line - 1].replace(/\r$/, ''); + const sourceLines = String(source).split('\n'); + let sourceLine = ''; + if (line >= 1 && line <= sourceLines.length) { + sourceLine = sourceLines[line - 1].replace(/\r$/, ''); + } + + if (!Number.isFinite(column) || column < 1) { + column = 1; + } + + let arrowMessage = filename + ':' + line; + if (sourceLine.length > 0) { + arrowMessage += '\n' + sourceLine + '\n' + ' '.repeat(column - 1) + '^'; + } + + err[arrowMessageSymbol] = arrowMessage; +} + +function wrapEsmNamespace(ns) { + if (!ns || typeof ns !== 'object') return ns; + if (!Object.hasOwn(ns, 'default') || Object.hasOwn(ns, '__esModule')) return ns; + const wrapped = Object.create(null); + const namespaceKeys = Object.keys(ns); + Object.defineProperty(wrapped, '__esModule', { + value: true, + writable: true, + configurable: false, + enumerable: true, + }); + for (let i = 0; i < namespaceKeys.length; i++) { + const k = namespaceKeys[i]; + Object.defineProperty(wrapped, k, { + value: ns[k], + writable: true, + enumerable: true, + configurable: false, + }); + } + Object.defineProperty(wrapped, Symbol.toStringTag, { + value: 'Module', + writable: false, + configurable: false, + enumerable: false, + }); + Object.preventExtensions(wrapped); + function namespaceDescriptor(prop) { + if (prop === '__esModule') { + return { + value: true, + writable: true, + enumerable: true, + configurable: false, + }; + } + if (typeof prop === 'string' && Object.hasOwn(ns, prop)) { + return { + value: ns[prop], + writable: true, + enumerable: true, + configurable: false, + }; + } + return Object.getOwnPropertyDescriptor(wrapped, prop); + } + return new Proxy(wrapped, { + get: function(target, prop, receiver) { + if (prop === '__esModule') return true; + if (typeof prop === 'string' && Object.hasOwn(ns, prop)) return ns[prop]; + return Reflect.get(target, prop, receiver); + }, + getOwnPropertyDescriptor: function(_target, prop) { + return namespaceDescriptor(prop); + }, + set: function() { + return false; + }, + defineProperty: function() { + return false; + }, + deleteProperty: function() { + return false; + }, + }); +} + +// Normalize QuickJS SyntaxError messages for ESM keywords to match Node.js/V8 format. +// QuickJS: "unsupported keyword: export" → Node.js: "Unexpected token 'export'" +function normalizeEsmSyntaxError(err) { + if (!err || typeof err.message !== 'string') return; + const m = err.message.match(/^unsupported keyword: (\w+)$/); + if (m) { + err.message = "Unexpected token '" + m[1] + "'"; + } +} + +function markAsSyntaxError(err) { + if (!err || err.name === 'SyntaxError') return; + err.name = 'SyntaxError'; + if (typeof err.stack === 'string') { + err.stack = err.stack.replace(/^Error:/, 'SyntaxError:'); + } +} + +function isIdentifierContinueCode(code) { + return code === 0x5f || code === 0x24 || // _ $ + (code >= 0x30 && code <= 0x39) || + (code >= 0x41 && code <= 0x5a) || + (code >= 0x61 && code <= 0x7a) || + code >= 0x80; +} + +function isIdentifierStartCode(code) { + return code === 0x5f || code === 0x24 || // _ $ + (code >= 0x41 && code <= 0x5a) || + (code >= 0x61 && code <= 0x7a) || + code >= 0x80; +} + +function hasIdentifierBoundary(source, start, end) { + return (start === 0 || !isIdentifierContinueCode(source.charCodeAt(start - 1))) && + (end >= source.length || !isIdentifierContinueCode(source.charCodeAt(end))); +} + +function skipQuotedOrTemplate(source, start) { + const quote = source.charCodeAt(start); + let i = start + 1; + while (i < source.length) { + const code = source.charCodeAt(i); + if (code === 0x5c) { // backslash + i += 2; + } else if (quote === 0x60 && code === 0x24 && i + 1 < source.length && source.charCodeAt(i + 1) === 0x7b) { + i = skipTemplateExpression(source, i + 2); + } else if (code === quote) { + return i + 1; + } else { + i++; + } + } + return i; +} + +function skipTemplateExpression(source, start) { + let i = start; + let depth = 1; + while (i < source.length && depth > 0) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + + const code = source.charCodeAt(i); + if (code === 0x7b || code === 0x28 || code === 0x5b) { + depth++; + i++; + } else if (code === 0x7d || code === 0x29 || code === 0x5d) { + depth--; + i++; + } else { + i++; + } + } + return i; +} + +function previousSignificantChar(source, pos) { + for (let i = pos - 1; i >= 0; i--) { + const ch = source.charCodeAt(i); + if (ch !== 0x20 && ch !== 0x09 && ch !== 0x0a && ch !== 0x0d) return ch; + } + return -1; +} + +function previousSignificantCharOnSameLine(source, pos) { + for (let i = pos - 1; i >= 0; i--) { + const ch = source.charCodeAt(i); + if (ch === 0x0a || ch === 0x0d) return -1; + if (ch !== 0x20 && ch !== 0x09) return ch; + } + return -1; +} + +function previousRegexContextToken(source, pos) { + let token = null; + let i = 0; + for (let j = pos - 1; j >= 0; j--) { + const ch = source.charCodeAt(j); + if (ch === 0x0a || ch === 0x0d || ch === 0x3b || ch === 0x7b || ch === 0x7d) { + i = j + 1; + break; + } + } + while (i < pos) { + const skipped = skipNonCode(source, i, false); + if (skipped !== null) { + i = skipped; + continue; + } + + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d) { + i++; + continue; + } + if (isIdentifierContinueCode(ch)) { + const start = i; + i++; + while (i < pos && isIdentifierContinueCode(source.charCodeAt(i))) { + i++; + } + token = source.substring(start, i); + continue; + } + token = ch; + i++; + } + return token; +} + +function isRegexLiteralStartInSource(source, pos) { + const token = previousRegexContextToken(source, pos); + if (token === null) { + return true; + } + if (typeof token === 'number') { + return '({[=,:;!?&|+-*~^%>'.indexOf(String.fromCharCode(token)) >= 0; + } + return token === 'return' || token === 'throw' || token === 'case' || token === 'yield'; +} + +function skipRegexLiteralInSource(source, start) { + let i = start + 1; + let inClass = false; + while (i < source.length) { + const code = source.charCodeAt(i); + if (code === 0x5c) { + i += 2; + } else if (code === 0x5b) { + inClass = true; + i++; + } else if (code === 0x5d) { + inClass = false; + i++; + } else if (code === 0x2f && !inClass) { + i++; + while (i < source.length) { + const flag = source.charCodeAt(i); + if (!((flag >= 0x41 && flag <= 0x5a) || (flag >= 0x61 && flag <= 0x7a))) break; + i++; + } + return i; + } else if (code === 0x0a || code === 0x0d) { + return start + 1; + } else { + i++; + } + } + return start + 1; +} + +function skipWhitespace(source, start) { + let i = start; + while (i < source.length) { + const code = source.charCodeAt(i); + if (code !== 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) break; + i++; + } + return i; +} + +function skipWhitespaceAndCommentsImpl(source, start, trackLineTerminator) { + let i = start; + let hasLineTerminator = false; + while (i < source.length) { + const code = source.charCodeAt(i); + if (code === 0x0a || code === 0x0d) { + hasLineTerminator = true; + i++; + continue; + } + if (code === 0x20 || code === 0x09) { + i++; + continue; + } + if (code === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (code === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i += 2; + while (i + 1 < source.length && !(source.charCodeAt(i) === 0x2a && source.charCodeAt(i + 1) === 0x2f)) { + if (source.charCodeAt(i) === 0x0a || source.charCodeAt(i) === 0x0d) hasLineTerminator = true; + i++; + } + i = Math.min(i + 2, source.length); + continue; + } + break; + } + if (trackLineTerminator) return { pos: i, hasLineTerminator }; + return i; +} + +function skipWhitespaceAndComments(source, start) { + return skipWhitespaceAndCommentsImpl(source, start, false); +} + +function startsWithKeywordAt(source, keyword, pos) { + return source.startsWith(keyword, pos) && hasIdentifierBoundary(source, pos, pos + keyword.length); +} + +function readKeywordAt(source, keyword, pos) { + return startsWithKeywordAt(source, keyword, pos) ? pos + keyword.length : null; +} + +function readVariableDeclarationKeyword(source, pos) { + let end = readKeywordAt(source, 'const', pos); + if (end !== null) return end; + end = readKeywordAt(source, 'let', pos); + if (end !== null) return end; + return readKeywordAt(source, 'var', pos); +} + +function readLexicalVariableDeclarationKeyword(source, pos) { + let end = readKeywordAt(source, 'const', pos); + if (end !== null) return end; + return readKeywordAt(source, 'let', pos); +} + +function skipNonCode(source, pos, skipRegex) { + const code = source.charCodeAt(pos); + if (code === 0x27 || code === 0x22 || code === 0x60) { // ' " ` + return skipQuotedOrTemplate(source, pos); + } + if (code === 0x2f && pos + 1 < source.length && source.charCodeAt(pos + 1) === 0x2f) { + let i = pos + 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + return i; + } + if (code === 0x2f && pos + 1 < source.length && source.charCodeAt(pos + 1) === 0x2a) { + let i = pos + 2; + while (i + 1 < source.length && !(source.charCodeAt(i) === 0x2a && source.charCodeAt(i + 1) === 0x2f)) i++; + return Math.min(i + 2, source.length); + } + if (skipRegex && code === 0x2f && isRegexLiteralStartInSource(source, pos)) { + return skipRegexLiteralInSource(source, pos); + } + return null; +} + +function scanSourceCodePositions(source, options, visitor) { + const skipRegex = !options || options.skipRegex !== false; + let i = 0; + let previousCode = -1; + while (i < source.length) { + const skipped = skipNonCode(source, i, skipRegex); + if (skipped !== null) { + i = skipped; + continue; + } + + const code = source.charCodeAt(i); + const next = visitor(i, code, previousCode); + if (next === false) return false; + if (code !== 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) previousCode = code; + if (typeof next === 'number') { + i = next; + } else { + i++; + } + } + return true; +} + +function isStaticExportSyntax(source, pos) { + if (previousSignificantCharOnSameLine(source, pos) === 0x2e) return false; // member property + const next = skipWhitespaceAndComments(source, pos + 6); + if (source.charCodeAt(next) === 0x3a) return false; // object label/property + const ch = source.charCodeAt(next); + if (ch === 0x7b || ch === 0x2a) return true; // { or * + return startsWithKeywordAt(source, 'default', next) || + startsWithKeywordAt(source, 'const', next) || + startsWithKeywordAt(source, 'let', next) || + startsWithKeywordAt(source, 'var', next) || + startsWithKeywordAt(source, 'function', next) || + startsWithKeywordAt(source, 'class', next); +} + +function isStaticImportSyntax(source, pos) { + if (previousSignificantCharOnSameLine(source, pos) === 0x2e) return false; // member property + const next = skipWhitespaceAndComments(source, pos + 6); + if (source.charCodeAt(next) === 0x28 || source.charCodeAt(next) === 0x3a) return false; // dynamic import(...) or property label + const ch = source.charCodeAt(next); + return ch === 0x27 || ch === 0x22 || ch === 0x7b || ch === 0x2a || + isIdentifierStartCode(ch); +} + +function looksLikeEsmSource(source) { + let found = false; + scanSourceCodePositions(source, { skipRegex: true }, (i) => { + if (startsWithKeywordAt(source, 'export', i) && isStaticExportSyntax(source, i)) { + found = true; + return false; + } + if (startsWithKeywordAt(source, 'import', i)) { + if (isStaticImportSyntax(source, i)) { + found = true; + return false; + } + if (readImportMeta(source, i) !== null) { + found = true; + return false; + } + } + return undefined; + }); + return found || sourceHasTopLevelAwait(source); +} + +function sourceHasTopLevelAwait(source) { + let found = false; + let parenDepth = 0; + let bracketDepth = 0; + let functionDepth = 0; + let classDepth = 0; + let pendingFunctionBody = false; + let pendingClassBody = false; + let afterArrow = false; + let skipArrowExpression = null; + const braces = []; + + scanSourceCodePositions(source, { skipRegex: true }, (i, code) => { + if (afterArrow) { + afterArrow = false; + if (code === 0x7b) { + pendingFunctionBody = true; + } else { + skipArrowExpression = { parenDepth, bracketDepth, braceDepth: braces.length }; + } + } + + if (skipArrowExpression && + (code === 0x3b || + code === 0x2c || + (code === 0x29 && parenDepth <= skipArrowExpression.parenDepth) || + (code === 0x5d && bracketDepth <= skipArrowExpression.bracketDepth) || + (code === 0x7d && braces.length <= skipArrowExpression.braceDepth))) { + skipArrowExpression = null; + } + + if (code === 0x28) { + parenDepth++; + } else if (code === 0x29) { + parenDepth = Math.max(0, parenDepth - 1); + } else if (code === 0x5b) { + bracketDepth++; + } else if (code === 0x5d) { + bracketDepth = Math.max(0, bracketDepth - 1); + } else if (code === 0x3d && source.charCodeAt(i + 1) === 0x3e) { + afterArrow = true; + } else if (code === 0x7b) { + if (pendingFunctionBody) { + braces.push('function'); + functionDepth++; + pendingFunctionBody = false; + } else if (pendingClassBody) { + braces.push('class'); + classDepth++; + pendingClassBody = false; + } else { + braces.push('normal'); + } + } else if (code === 0x7d) { + const context = braces.pop(); + if (context === 'function') functionDepth = Math.max(0, functionDepth - 1); + if (context === 'class') classDepth = Math.max(0, classDepth - 1); + } + + if (skipArrowExpression) { + return undefined; + } + + if (startsWithKeywordAt(source, 'await', i) && functionDepth === 0 && classDepth === 0) { + found = true; + return false; + } + if (startsWithKeywordAt(source, 'function', i)) { + pendingFunctionBody = true; + } else if (startsWithKeywordAt(source, 'class', i)) { + pendingClassBody = true; + } + return undefined; + }); + return found; +} + +function isCreateRequireImportMetaUrlDeclaration(source, requirePos) { + let next = skipWhitespaceAndComments(source, requirePos + 7); + if (source.charCodeAt(next) !== 0x3d) return false; + next = skipWhitespaceAndComments(source, next + 1); + const createRequireEnd = readLoaderNamedIdentifier(source, next, 'createRequire'); + if (createRequireEnd === null) { + return false; + } + next = skipWhitespaceAndComments(source, createRequireEnd); + if (source.charCodeAt(next) !== 0x28) return false; + next = skipWhitespaceAndComments(source, next + 1); + return readImportMetaUrl(source, next) !== null; +} + +function readImportMetaUrl(source, pos) { + let i = readImportMeta(source, pos); + if (i === null) return null; + return readLoaderDotMember(source, i, 'url'); +} + +function readImportMeta(source, pos) { + if (previousSignificantChar(source, pos) === 0x2e) return null; + let i = readLoaderNamedIdentifier(source, pos, 'import'); + if (i === null) return null; + return readLoaderDotMember(source, i, 'meta'); +} + +const cjsWrapperGlobalNames = ['require', 'exports', 'module', '__filename', '__dirname']; + +function findVariableDeclarationEnd(source, pos) { + let i = pos; + let paren = 0; + let brace = 0; + let bracket = 0; + while (i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + const code = source.charCodeAt(i); + if (code === 0x28) paren++; + else if (code === 0x29) paren = Math.max(0, paren - 1); + else if (code === 0x7b) brace++; + else if (code === 0x7d) { + if (paren === 0 && brace === 0 && bracket === 0) return i; + brace = Math.max(0, brace - 1); + } else if (code === 0x5b) bracket++; + else if (code === 0x5d) bracket = Math.max(0, bracket - 1); + else if (code === 0x3b && paren === 0 && brace === 0 && bracket === 0) return i + 1; + i++; + } + return i; +} + +function objectPatternPropertyKeyWithoutBinding(source, pos) { + const i = skipWhitespaceAndComments(source, pos); + return source.charCodeAt(i) === 0x3a; +} + +function findMatchingBracket(source, start) { + let i = start; + let depth = 0; + while (i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + const code = source.charCodeAt(i); + if (code === 0x5b) depth++; + else if (code === 0x5d) { + depth = Math.max(0, depth - 1); + if (depth === 0) return i; + } + i++; + } + return null; +} + +function cjsGlobalIdentifierIsBindingName(source, pos, nameEnd) { + if (objectPatternPropertyKeyWithoutBinding(source, nameEnd)) return false; + const next = skipWhitespaceAndComments(source, nameEnd); + if (source.charCodeAt(next) === 0x28) return false; + const previous = previousSignificantChar(source, pos); + if (previous === 0x3d) return false; + if (previous === 0x5b && + source.charCodeAt(next) === 0x5d && + source.charCodeAt(skipWhitespaceAndComments(source, next + 1)) === 0x3a) { + return false; + } + return true; +} + +function collectCjsGlobalBindingNamesInVariableDeclaration(source, start, end) { + const names = []; + let i = start; + let inBinding = true; + let paren = 0; + let brace = 0; + let bracket = 0; + while (i < end && i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = Math.min(skipped, end); + continue; + } + const code = source.charCodeAt(i); + if (inBinding && code === 0x5b) { + const close = findMatchingBracket(source, i); + if (close !== null && + close < end && + source.charCodeAt(skipWhitespaceAndComments(source, close + 1)) === 0x3a) { + i = close + 1; + continue; + } + } + if (code === 0x28) paren++; + else if (code === 0x29) paren = Math.max(0, paren - 1); + else if (code === 0x7b) brace++; + else if (code === 0x7d) brace = Math.max(0, brace - 1); + else if (code === 0x5b) bracket++; + else if (code === 0x5d) bracket = Math.max(0, bracket - 1); + else if (code === 0x3d && paren === 0 && brace === 0 && bracket === 0) inBinding = false; + else if (code === 0x2c && paren === 0 && brace === 0 && bracket === 0) inBinding = true; + + if (inBinding) { + for (let n = 0; n < cjsWrapperGlobalNames.length; n++) { + const name = cjsWrapperGlobalNames[n]; + const nameEnd = readLoaderNamedIdentifier(source, i, name); + if (nameEnd !== null && + cjsGlobalIdentifierIsBindingName(source, i, nameEnd) && + !names.includes(name)) { + names.push(name); + break; + } + } + } + i++; + } + return names; +} + +function parseVariableDeclarationSpan(source, pos) { + const keywordEnd = readLexicalVariableDeclarationKeyword(source, pos); + if (keywordEnd === null) return null; + const start = skipWhitespaceAndComments(source, keywordEnd); + const end = findVariableDeclarationEnd(source, start); + return { + bindings: collectCjsGlobalBindingNamesInVariableDeclaration(source, start, end), + end, + }; +} + +function findClassDeclarationEnd(source, pos) { + let i = pos; + while (i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + if (source.charCodeAt(i) === 0x7b) { + let depth = 1; + i++; + while (i < source.length) { + const innerSkipped = skipNonCode(source, i, true); + if (innerSkipped !== null) { + i = innerSkipped; + continue; + } + const code = source.charCodeAt(i); + if (code === 0x7b) depth++; + else if (code === 0x7d) { + depth--; + if (depth === 0) return i + 1; + } + i++; + } + return i; + } + i++; + } + return i; +} + +function parseClassDeclarationSpan(source, pos) { + const classEnd = readKeywordAt(source, 'class', pos); + if (classEnd === null) return null; + const nameStart = skipWhitespaceAndComments(source, classEnd); + const nameEnd = readLoaderIdentifierEnd(source, nameStart); + const bindings = []; + if (nameEnd !== null) { + const name = source.slice(nameStart, nameEnd); + if (cjsWrapperGlobalNames.includes(name)) bindings.push(name); + } + return { bindings, end: findClassDeclarationEnd(source, nameStart) }; +} + +function hasCjsWrapperLexicalRedeclaration(source) { + let found = false; + let braceDepth = 0; + scanSourceCodePositions(source, { skipRegex: true }, (i, code) => { + if (code === 0x7b) { + braceDepth++; + return undefined; + } + if (code === 0x7d) { + braceDepth = Math.max(0, braceDepth - 1); + return undefined; + } + + if (braceDepth === 0) { + const declaration = parseVariableDeclarationSpan(source, i) || parseClassDeclarationSpan(source, i); + if (declaration === null) return undefined; + for (let n = 0; n < declaration.bindings.length; n++) { + const name = declaration.bindings[n]; + if (name === 'require') { + const keywordEnd = readLexicalVariableDeclarationKeyword(source, i); + const next = keywordEnd === null ? null : skipWhitespaceAndComments(source, keywordEnd); + if (next !== null && isCreateRequireImportMetaUrlDeclaration(source, next)) { + continue; + } + } + if (cjsWrapperGlobalNames.includes(name)) { + found = true; + return false; + } + } + return declaration.end; + } + return undefined; + }); + return found; +} + +function readStaticSpecifierString(source, start) { + const i = skipWhitespaceAndComments(source, start); + const quote = source.charCodeAt(i); + if (quote !== 0x27 && quote !== 0x22) return null; + let value = ''; + let p = i + 1; + while (p < source.length) { + const code = source.charCodeAt(p); + if (code === 0x5c && p + 1 < source.length) { + value += source[p + 1]; + p += 2; + } else if (code === quote) { + return { value, end: p + 1 }; + } else { + value += source[p]; + p++; + } + } + return null; +} + +function decodeStringLiteral(source, start, quote) { + let value = ''; + let i = start; + while (i < source.length && source.charCodeAt(i) !== quote) { + let ch = source.charCodeAt(i); + if (ch !== 0x5c) { + value += source[i++]; + continue; + } + i++; + if (i >= source.length) return null; + ch = source.charCodeAt(i++); + if (ch === 0x6e) value += '\n'; + else if (ch === 0x72) value += '\r'; + else if (ch === 0x74) value += '\t'; + else if (ch === 0x62) value += '\b'; + else if (ch === 0x66) value += '\f'; + else if (ch === 0x76) value += '\v'; + else if (ch === 0x78 && i + 2 <= source.length) { + const hex = source.substring(i, i + 2); + if (!/^[0-9a-fA-F]{2}$/.test(hex)) return null; + value += String.fromCharCode(parseInt(hex, 16)); + i += 2; + } else if (ch === 0x75 && source.charCodeAt(i) === 0x7b) { + const end = source.indexOf('}', i + 1); + if (end < 0) return null; + const hex = source.substring(i + 1, end); + if (!/^[0-9a-fA-F]+$/.test(hex)) return null; + const codePoint = parseInt(hex, 16); + if (codePoint > 0x10ffff) return null; + value += String.fromCodePoint(codePoint); + i = end + 1; + } else if (ch === 0x75 && i + 4 <= source.length) { + const hex = source.substring(i, i + 4); + if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null; + value += String.fromCharCode(parseInt(hex, 16)); + i += 4; + } else if (ch >= 0x30 && ch <= 0x37) { + let octal = String.fromCharCode(ch); + while (octal.length < 3 && i < source.length) { + const next = source.charCodeAt(i); + if (next < 0x30 || next > 0x37) break; + octal += source[i++]; + } + value += String.fromCharCode(parseInt(octal, 8)); + } else { + value += String.fromCharCode(ch); + } + } + return i < source.length ? { value, end: i } : null; +} + +function readLoaderCjsExportTarget(source, pos, allowBareExports) { + let i = pos; + const exportsEnd = readLoaderNamedIdentifier(source, i, 'exports'); + if (allowBareExports !== false && exportsEnd !== null) { + const previous = previousSignificantChar(source, pos); + if (previous === 0x2e || previous === 0x23) return null; + i = exportsEnd; + } else { + const moduleEnd = readLoaderNamedIdentifier(source, i, 'module'); + if (moduleEnd === null) return null; + const previous = previousSignificantChar(source, pos); + if (previous === 0x2e || previous === 0x23) return null; + i = skipWhitespaceAndComments(source, moduleEnd); + if (source.charCodeAt(i) !== 0x2e) return null; + i = skipWhitespaceAndComments(source, i + 1); + const moduleExportsEnd = readLoaderNamedIdentifier(source, i, 'exports'); + if (moduleExportsEnd === null) return null; + i = moduleExportsEnd; + } + return i; +} + +function readLoaderCjsExportName(source, pos) { + let i = readLoaderCjsExportTarget(source, pos); + if (i === null) return null; + + i = skipWhitespaceAndComments(source, i); + let name; + if (source.charCodeAt(i) === 0x2e) { + i = skipWhitespaceAndComments(source, i + 1); + const ident = readLoaderIdentifier(source, i); + if (ident === null) return null; + name = ident.name; + i = ident.end; + } else if (source.charCodeAt(i) === 0x5b) { + i = skipWhitespaceAndComments(source, i + 1); + const quote = source.charCodeAt(i); + if (quote !== 0x27 && quote !== 0x22) return null; + const decoded = decodeStringLiteral(source, i + 1, quote); + if (decoded === null) return null; + name = decoded.value; + i = skipWhitespaceAndComments(source, decoded.end + 1); + if (source.charCodeAt(i) !== 0x5d) return null; + i++; + } else { + return null; + } + + i = skipWhitespaceAndComments(source, i); + return readLoaderAssignmentOperator(source, i) === null ? null : name; +} + +function loaderFindMatchingParen(source, open) { + let depth = 0; + let i = open; + while (i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + const ch = source.charCodeAt(i); + if (ch === 0x28) depth++; + else if (ch === 0x29) { + depth--; + if (depth === 0) return i; + } + i++; + } + return -1; +} + +function loaderFindMatchingBrace(source, open) { + let depth = 0; + let i = open; + while (i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + const ch = source.charCodeAt(i); + if (ch === 0x7b) depth++; + else if (ch === 0x7d) { + depth--; + if (depth === 0) return i; + } + i++; + } + return -1; +} + +function skipLoaderObjectLiteralValue(source, pos, objectEnd) { + let i = pos; + let braceDepth = 0; + let parenDepth = 0; + let bracketDepth = 0; + while (i < objectEnd) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + const ch = source.charCodeAt(i); + if (ch === 0x7b) braceDepth++; + else if (ch === 0x7d) braceDepth = Math.max(0, braceDepth - 1); + else if (ch === 0x28) parenDepth++; + else if (ch === 0x29) parenDepth = Math.max(0, parenDepth - 1); + else if (ch === 0x5b) bracketDepth++; + else if (ch === 0x5d) bracketDepth = Math.max(0, bracketDepth - 1); + else if (ch === 0x2c && braceDepth === 0 && parenDepth === 0 && bracketDepth === 0) { + return i; + } + i++; + } + return objectEnd; +} + +function readLoaderObjectLiteralKey(source, pos) { + const ch = source.charCodeAt(pos); + if (ch === 0x27 || ch === 0x22) { + const decoded = decodeStringLiteral(source, pos + 1, ch); + if (decoded === null) return null; + return { name: decoded.value, keyIsIdent: false, end: decoded.end + 1 }; + } + const ident = readLoaderIdentifier(source, pos); + if (ident === null) return null; + return { name: ident.name, keyIsIdent: true, end: ident.end }; +} + +function loaderObjectLiteralValueExport(source, pos, objectEnd) { + const ident = readLoaderIdentifier(source, pos); + if (ident === null) return null; + let i = skipWhitespaceAndComments(source, ident.end); + if (i >= objectEnd || source.charCodeAt(i) === 0x2c) return { named: true, stop: false }; + if (ident.name === 'true' || ident.name === 'false' || ident.name === 'null' || ident.name === 'undefined') { + return { named: true, stop: false }; + } + return { named: true, stop: true }; +} + +function readLoaderModuleExportsObjectLiteralNames(source, pos) { + const targetEnd = readLoaderCjsExportTarget(source, pos, false); + if (targetEnd === null) return null; + let i = skipWhitespaceAndComments(source, targetEnd); + i = readLoaderAssignmentOperator(source, i); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + if (source.charCodeAt(i) !== 0x7b) return null; + const objectEnd = loaderFindMatchingBrace(source, i); + if (objectEnd < 0) return null; + + const names = []; + const reexports = []; + let cursor = skipWhitespaceAndComments(source, i + 1); + while (cursor < objectEnd) { + if (source.charCodeAt(cursor) === 0x2c) { + cursor = skipWhitespaceAndComments(source, cursor + 1); + continue; + } + if (isLoaderSpreadTokenAt(source, cursor)) { + const spreadStart = skipWhitespaceAndComments(source, cursor + 3); + const requireEnd = readLoaderNamedIdentifier(source, spreadStart, 'require'); + if (requireEnd !== null) { + const afterRequire = skipWhitespaceAndComments(source, requireEnd); + if (source.charCodeAt(afterRequire) !== 0x28) break; + const requireCall = readLoaderRequireString(source, spreadStart, true); + if (requireCall !== null) { + reexports.push(requireCall.specifier); + const afterRequireCall = skipWhitespaceAndComments(source, requireCall.end); + if (afterRequireCall < objectEnd && source.charCodeAt(afterRequireCall) !== 0x2c) break; + cursor = afterRequireCall; + } else { + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, spreadStart, objectEnd)); + } + } else { + const spreadKey = readLoaderObjectLiteralKey(source, spreadStart); + if (spreadKey === null || !spreadKey.keyIsIdent) break; + const afterIdent = skipWhitespaceAndComments(source, spreadKey.end); + if (afterIdent < objectEnd && source.charCodeAt(afterIdent) !== 0x2c) break; + cursor = afterIdent; + } + } else { + const key = readLoaderObjectLiteralKey(source, cursor); + if (key === null) break; + let next = skipWhitespaceAndComments(source, key.end); + if (source.charCodeAt(next) === 0x3a) { + next = skipWhitespaceAndComments(source, next + 1); + const valueExport = loaderObjectLiteralValueExport(source, next, objectEnd); + if (valueExport === null) break; + names.push(key.name); + if (valueExport.stop) break; + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, next, objectEnd)); + } else if (key.keyIsIdent) { + names.push(key.name); + cursor = next; + } else { + break; + } + } + const nextEntry = nextLoaderObjectLiteralEntry(source, cursor, objectEnd); + if (nextEntry === null) break; + cursor = nextEntry; + } + return { names, reexports, end: objectEnd + 1 }; +} + +function loaderDescriptorPropertyName(source, pos) { + const ch = source.charCodeAt(pos); + if (ch === 0x27 || ch === 0x22) { + const decoded = decodeStringLiteral(source, pos + 1, ch); + if (decoded === null) return null; + return { name: decoded.value, quoted: true, end: decoded.end + 1 }; + } + const ident = readLoaderObjectLiteralKey(source, pos); + if (ident === null || !ident.keyIsIdent) return null; + return { name: ident.name, quoted: false, end: ident.end }; +} + +function readLoaderDescriptorObject(source, start, end) { + const descriptorStart = skipWhitespaceAndComments(source, start); + if (source.charCodeAt(descriptorStart) !== 0x7b) return null; + const descriptorEnd = loaderFindMatchingBrace(source, descriptorStart); + if (descriptorEnd < 0 || descriptorEnd > end) return null; + return { cursor: skipWhitespaceAndComments(source, descriptorStart + 1), end: descriptorEnd }; +} + +function nextLoaderObjectLiteralEntry(source, cursor, objectEnd) { + if (cursor >= objectEnd) return objectEnd; + if (source.charCodeAt(cursor) !== 0x2c) return null; + return skipWhitespaceAndComments(source, cursor + 1); +} + +function isLoaderSpreadTokenAt(source, pos) { + return source.charCodeAt(pos) === 0x2e && source.charCodeAt(pos + 1) === 0x2e && source.charCodeAt(pos + 2) === 0x2e; +} + +function loaderDescriptorFunctionGetterBody(source, pos, descriptorEnd) { + const functionEnd = readLoaderNamedIdentifier(source, pos, 'function'); + if (functionEnd === null) return null; + let next = skipWhitespaceAndComments(source, functionEnd); + const functionName = readLoaderIdentifier(source, next); + if (functionName !== null) { + next = skipWhitespaceAndComments(source, functionName.end); + } + if (source.charCodeAt(next) !== 0x28) return null; + const body = loaderGetterBodyEnd(source, next, descriptorEnd); + if (body === null) return null; + return body; +} + +function loaderDescriptorFunctionGetterEnd(source, pos, descriptorEnd) { + const body = loaderDescriptorFunctionGetterBody(source, pos, descriptorEnd); + if (body === null || !loaderSimpleGetterBody(source, body.start, body.end)) return null; + return body.end + 1; +} + +function loaderDescriptorHasNamedProperty(source, start, end) { + const descriptor = readLoaderDescriptorObject(source, start, end); + if (descriptor === null) return false; + let foundKind = null; + let cursor = descriptor.cursor; + const descriptorEnd = descriptor.end; + while (cursor < descriptorEnd) { + if (source.charCodeAt(cursor) === 0x2c) { + cursor = skipWhitespaceAndComments(source, cursor + 1); + continue; + } + if (isLoaderSpreadTokenAt(source, cursor)) return false; + if (source.charCodeAt(cursor) === 0x5b) { + if (foundKind === 'value') { + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, cursor, descriptorEnd)); + cursor = nextLoaderObjectLiteralEntry(source, cursor, descriptorEnd); + if (cursor === null) return false; + continue; + } + return false; + } + const key = loaderDescriptorPropertyName(source, cursor); + if (key === null) return false; + let next = skipWhitespaceAndComments(source, key.end); + if (key.quoted) { + if (foundKind === 'value') { + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, next, descriptorEnd)); + } else { + return false; + } + } else if (key.name === 'value') { + if (foundKind === 'get') return false; + if (foundKind === 'value') { + const valueStart = source.charCodeAt(next) === 0x3a ? next + 1 : next; + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, valueStart, descriptorEnd)); + } else { + if (source.charCodeAt(next) !== 0x3a) return false; + foundKind = 'value'; + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, next + 1, descriptorEnd)); + } + } else if (key.name === 'get') { + if (foundKind !== null) return false; + if (source.charCodeAt(next) === 0x28) { + const body = loaderGetterBodyEnd(source, next, descriptorEnd); + if (body === null || !loaderSimpleGetterBody(source, body.start, body.end)) return false; + foundKind = 'get'; + cursor = skipWhitespaceAndComments(source, body.end + 1); + } else if (source.charCodeAt(next) === 0x3a) { + const functionEnd = loaderDescriptorFunctionGetterEnd(source, skipWhitespaceAndComments(source, next + 1), descriptorEnd); + if (functionEnd === null) return false; + foundKind = 'get'; + cursor = skipWhitespaceAndComments(source, functionEnd); + } else { + return false; + } + } else if (key.name === 'enumerable') { + if (source.charCodeAt(next) !== 0x3a) return false; + if (foundKind === 'value') { + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, next + 1, descriptorEnd)); + } else if (foundKind === 'get') { + return false; + } else { + const valueStart = skipWhitespaceAndComments(source, next + 1); + const trueEnd = readLoaderNamedIdentifier(source, valueStart, 'true'); + if (trueEnd === null) { + return false; + } + cursor = skipWhitespaceAndComments(source, trueEnd); + } + } else { + if (foundKind === 'value') { + cursor = skipWhitespaceAndComments(source, skipLoaderObjectLiteralValue(source, next, descriptorEnd)); + } else { + return false; + } + } + cursor = nextLoaderObjectLiteralEntry(source, cursor, descriptorEnd); + if (cursor === null) return false; + } + return foundKind !== null; +} + +const loaderGetterReturnInvalid = 0; +const loaderGetterReturnBare = 1; +const loaderGetterReturnDot = 2; +const loaderGetterReturnBracketString = 3; +const loaderGetterReturnBracketIdentifier = 4; + +function readLoaderIdentifierEnd(source, pos) { + if (!isIdentifierStartCode(source.charCodeAt(pos))) return null; + let i = pos + 1; + while (i < source.length && isIdentifierContinueCode(source.charCodeAt(i))) i++; + return i; +} + +function readLoaderGetterReturnMemberKind(source, start, end) { + let i = skipWhitespaceAndComments(source, start); + const returnEnd = readLoaderNamedIdentifier(source, i, 'return'); + if (returnEnd === null) return loaderGetterReturnInvalid; + i = skipWhitespaceAndComments(source, returnEnd); + const receiverEnd = readLoaderIdentifierEnd(source, i); + if (receiverEnd === null) return loaderGetterReturnInvalid; + i = skipWhitespaceAndComments(source, receiverEnd); + let kind = loaderGetterReturnBare; + if (source.charCodeAt(i) === 0x2e) { + i = skipWhitespaceAndComments(source, i + 1); + const propertyEnd = readLoaderIdentifierEnd(source, i); + if (propertyEnd === null) return loaderGetterReturnInvalid; + i = propertyEnd; + kind = loaderGetterReturnDot; + } else if (source.charCodeAt(i) === 0x5b) { + i = skipWhitespaceAndComments(source, i + 1); + const quote = source.charCodeAt(i); + if (quote === 0x27 || quote === 0x22) { + const decoded = decodeStringLiteral(source, i + 1, quote); + if (decoded === null) return loaderGetterReturnInvalid; + i = skipWhitespaceAndComments(source, decoded.end + 1); + kind = loaderGetterReturnBracketString; + } else { + const propertyEnd = readLoaderIdentifierEnd(source, i); + if (propertyEnd === null) return loaderGetterReturnInvalid; + i = skipWhitespaceAndComments(source, propertyEnd); + kind = loaderGetterReturnBracketIdentifier; + } + if (source.charCodeAt(i) !== 0x5d) return loaderGetterReturnInvalid; + i++; + } + i = skipWhitespaceAndComments(source, i); + if (source.charCodeAt(i) === 0x3b) i = skipWhitespaceAndComments(source, i + 1); + return i >= end ? kind : loaderGetterReturnInvalid; +} + +function loaderSimpleGetterBody(source, start, end) { + const kind = readLoaderGetterReturnMemberKind(source, start, end); + return kind !== loaderGetterReturnInvalid && kind !== loaderGetterReturnBracketIdentifier; +} + +function loaderGetterBodyEnd(source, paramsOpen, limit) { + const paramsEnd = loaderFindMatchingParen(source, paramsOpen); + if (paramsEnd < 0 || paramsEnd > limit) return null; + if (skipWhitespaceAndComments(source, paramsOpen + 1) !== paramsEnd) return null; + let i = skipWhitespaceAndComments(source, paramsEnd + 1); + if (source.charCodeAt(i) !== 0x7b) return null; + const bodyEnd = loaderFindMatchingBrace(source, i); + return bodyEnd >= 0 && bodyEnd <= limit ? { start: i + 1, end: bodyEnd } : null; +} + +function readLoaderDefinePropertyCall(source, pos, rejectMemberAccess) { + const objectEnd = readLoaderNamedIdentifier(source, pos, 'Object'); + if (objectEnd === null) return null; + if (rejectMemberAccess) { + const previous = previousSignificantChar(source, pos); + if (previous === 0x2e || previous === 0x23) return null; + } + let i = skipWhitespaceAndComments(source, objectEnd); + if (source.charCodeAt(i) !== 0x2e) return null; + i = skipWhitespaceAndComments(source, i + 1); + const definePropertyEnd = readLoaderNamedIdentifier(source, i, 'defineProperty'); + if (definePropertyEnd === null) return null; + i = skipWhitespaceAndComments(source, definePropertyEnd); + if (source.charCodeAt(i) !== 0x28) return null; + const open = i; + i = skipWhitespaceAndComments(source, i + 1); + return { open, next: i }; +} + +function readLoaderDefinePropertyExportName(source, pos) { + const call = readLoaderDefinePropertyCall(source, pos, true); + if (call === null) return null; + const open = call.open; + let i = call.next; + i = readLoaderCjsExportTarget(source, i); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + if (source.charCodeAt(i) !== 0x2c) return null; + i = skipWhitespaceAndComments(source, i + 1); + const quote = source.charCodeAt(i); + if (quote !== 0x27 && quote !== 0x22) return null; + const decoded = decodeStringLiteral(source, i + 1, quote); + if (decoded === null) return null; + i = skipWhitespaceAndComments(source, decoded.end + 1); + if (source.charCodeAt(i) !== 0x2c) return null; + const close = loaderFindMatchingParen(source, open); + if (close < 0 || !loaderDescriptorHasNamedProperty(source, i + 1, close)) return null; + return decoded.value; +} + +function readLoaderModuleExportsRequire(source, pos) { + if (readLoaderNamedIdentifier(source, pos, 'module') === null) return null; + const targetEnd = readLoaderCjsExportTarget(source, pos, false); + if (targetEnd === null) return null; + let i = skipWhitespaceAndComments(source, targetEnd); + i = readLoaderAssignmentOperator(source, i); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + const required = readLoaderRequireString(source, i); + return required === null ? null : required.specifier; +} + +function readLoaderRequireString(source, pos, allowSpreadPrefix) { + const requireEnd = readLoaderNamedIdentifier(source, pos, 'require'); + if (requireEnd === null) return null; + if (!allowSpreadPrefix && previousSignificantChar(source, pos) === 0x2e) return null; + let i = skipWhitespaceAndComments(source, requireEnd); + if (source.charCodeAt(i) !== 0x28) return null; + i = skipWhitespaceAndComments(source, i + 1); + const quote = source.charCodeAt(i); + if (quote !== 0x27 && quote !== 0x22) return null; + const decoded = decodeStringLiteral(source, i + 1, quote); + if (decoded === null) return null; + i = skipWhitespaceAndComments(source, decoded.end + 1); + if (source.charCodeAt(i) !== 0x29) return null; + return { specifier: decoded.value, end: i + 1 }; +} + +function skipWhitespaceAndCommentsWithLineTerminator(source, start) { + return skipWhitespaceAndCommentsImpl(source, start, true); +} + +function loaderIsStatementBoundary(source, pos) { + const skipped = skipWhitespaceAndCommentsWithLineTerminator(source, pos); + const i = skipped.pos; + if (i >= source.length) return true; + if (source.charCodeAt(i) === 0x3b || source.charCodeAt(i) === 0x7d) return true; + if (!skipped.hasLineTerminator) return false; + return !isLoaderAsiContinuationNext(source, i); +} + +function isLoaderAsiContinuationNext(source, pos) { + if (pos + 1 < source.length) { + const code = source.charCodeAt(pos); + const next = source.charCodeAt(pos + 1); + if ((code === 0x2b && next === 0x2b) || (code === 0x2d && next === 0x2d)) return false; + } + return isLoaderAsiContinuationOperator(source.charCodeAt(pos)); +} + +function isLoaderAsiContinuationOperator(code) { + return code === 0x60 || code === 0x28 || code === 0x5b || code === 0x2e || code === 0x2c || code === 0x3a || + code === 0x3f || code === 0x2b || code === 0x2d || code === 0x2a || code === 0x2f || code === 0x25 || + code === 0x26 || code === 0x7c || code === 0x5e || code === 0x3c || code === 0x3e || code === 0x3d; +} + +function readLoaderRequireBinding(source, pos) { + const declarationEnd = readVariableDeclarationKeyword(source, pos); + if (declarationEnd === null) return null; + let i = skipWhitespaceAndComments(source, declarationEnd); + const parsedBinding = readLoaderIdentifier(source, i); + if (parsedBinding === null) return null; + const binding = parsedBinding.name; + i = skipWhitespaceAndComments(source, parsedBinding.end); + i = readLoaderAssignmentOperator(source, i); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + let required = readLoaderRequireString(source, i); + if (required !== null) { + if (!loaderIsStatementBoundary(source, required.end)) return null; + return { binding, specifier: required.specifier, end: required.end }; + } + const interopEnd = readLoaderNamedIdentifier(source, i, '_interopRequireWildcard'); + if (interopEnd === null) return null; + i = skipWhitespaceAndComments(source, interopEnd); + if (source.charCodeAt(i) !== 0x28) return null; + required = readLoaderRequireString(source, skipWhitespaceAndComments(source, i + 1)); + if (required === null) return null; + i = skipWhitespaceAndComments(source, required.end); + if (source.charCodeAt(i) !== 0x29) return null; + if (!loaderIsStatementBoundary(source, i + 1)) return null; + return { binding, specifier: required.specifier, end: i + 1 }; +} + +function readLoaderBracketIdentifier(source, pos, ident) { + if (source.charCodeAt(pos) !== 0x5b) return null; + let i = skipWhitespaceAndComments(source, pos + 1); + const identEnd = readLoaderNamedIdentifier(source, i, ident); + if (identEnd === null) return null; + i = skipWhitespaceAndComments(source, identEnd); + if (source.charCodeAt(i) !== 0x5d) return null; + return i + 1; +} + +function readLoaderKeyStringComparison(source, pos, key, operator) { + const keyEnd = readLoaderNamedIdentifier(source, pos, key); + if (keyEnd === null) return null; + let i = skipWhitespaceAndComments(source, keyEnd); + i = readLoaderOperator(source, i, operator); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + const quote = source.charCodeAt(i); + if (quote !== 0x27 && quote !== 0x22) return null; + const decoded = decodeStringLiteral(source, i + 1, quote); + if (decoded === null) return null; + return { value: decoded.value, end: decoded.end + 1 }; +} + +function readLoaderKeyEqualsString(source, pos, key) { + return readLoaderKeyStringComparison(source, pos, key, '==='); +} + +function readLoaderKeyNotEqualsString(source, pos, key) { + return readLoaderKeyStringComparison(source, pos, key, '!=='); +} + +function readLoaderDotMember(source, pos, name) { + let i = skipWhitespaceAndComments(source, pos); + if (source.charCodeAt(i) !== 0x2e) return null; + i = skipWhitespaceAndComments(source, i + 1); + const end = readLoaderNamedIdentifier(source, i, name); + return end === null ? null : skipWhitespaceAndComments(source, end); +} + +function readLoaderIdentifier(source, pos) { + const end = readLoaderIdentifierEnd(source, pos); + return end === null ? null : { name: source.substring(pos, end), end }; +} + +function readLoaderNamedIdentifier(source, pos, name) { + return readKeywordAt(source, name, pos); +} + +function readLoaderOperator(source, pos, operator) { + for (let i = 0; i < operator.length; i++) { + if (source.charCodeAt(pos + i) !== operator.charCodeAt(i)) return null; + } + return pos + operator.length; +} + +function readLoaderAssignmentOperator(source, pos) { + if (source.charCodeAt(pos) !== 0x3d) return null; + const next = source.charCodeAt(pos + 1); + return next === 0x3d || next === 0x3e ? null : pos + 1; +} + +function readLoaderObjectHasOwnPropertyCall(source, pos, key, requirePrototype) { + let i = readLoaderNamedIdentifier(source, pos, 'Object'); + if (i === null) return null; + const prototype = readLoaderDotMember(source, i, 'prototype'); + if (prototype !== null) { + i = prototype; + } else if (requirePrototype) { + return null; + } + i = readLoaderDotMember(source, i, 'hasOwnProperty'); + if (i === null) return null; + i = readLoaderDotMember(source, i, 'call'); + if (i === null || source.charCodeAt(i) !== 0x28) return null; + i = skipWhitespaceAndComments(source, i + 1); + const target = readLoaderIdentifier(source, i); + if (target === null) return null; + i = skipWhitespaceAndComments(source, target.end); + if (source.charCodeAt(i) !== 0x2c) return null; + i = skipWhitespaceAndComments(source, i + 1); + const keyEnd = readLoaderNamedIdentifier(source, i, key); + if (keyEnd === null) return null; + i = skipWhitespaceAndComments(source, keyEnd); + if (source.charCodeAt(i) !== 0x29) return null; + return { target: target.name, end: i + 1 }; +} + +function readLoaderIfCondition(source, pos) { + const ifEnd = readLoaderNamedIdentifier(source, pos, 'if'); + if (ifEnd === null) return null; + let i = skipWhitespaceAndComments(source, ifEnd); + if (source.charCodeAt(i) !== 0x28) return null; + const conditionEnd = loaderFindMatchingParen(source, i); + if (conditionEnd < 0) return null; + return { + start: i + 1, + end: conditionEnd, + after: skipWhitespaceAndComments(source, conditionEnd + 1), + }; +} + +function readLoaderDefaultEsModuleReturnGuard(source, pos, key) { + const condition = readLoaderIfCondition(source, pos); + if (condition === null) return null; + let c = skipWhitespaceAndComments(source, condition.start); + const first = readLoaderKeyEqualsString(source, c, key); + if (first === null || first.value !== 'default') return null; + c = skipWhitespaceAndComments(source, first.end); + c = readLoaderOperator(source, c, '||'); + if (c === null) return null; + c = skipWhitespaceAndComments(source, c); + const second = readLoaderKeyEqualsString(source, c, key); + if (second === null || second.value !== '__esModule') return null; + if (skipWhitespaceAndComments(source, second.end) !== condition.end) return null; + return readLoaderNamedIdentifier(source, condition.after, 'return'); +} + +function readLoaderHasOwnPropertyKey(source, pos, key) { + const receiver = readLoaderIdentifier(source, pos); + if (receiver === null) return null; + if (receiver.name === 'Object') { + const objectCall = readLoaderObjectHasOwnPropertyCall(source, pos, key, false); + if (objectCall !== null) return objectCall.end; + } + + let i = readLoaderDotMember(source, receiver.end, 'hasOwnProperty'); + if (i === null || source.charCodeAt(i) !== 0x28) return null; + i = skipWhitespaceAndComments(source, i + 1); + const keyEnd = readLoaderNamedIdentifier(source, i, key); + if (keyEnd === null) return null; + i = skipWhitespaceAndComments(source, keyEnd); + if (source.charCodeAt(i) !== 0x29) return null; + return i + 1; +} + +function readLoaderExportsHasOwnPropertyKey(source, pos, key) { + const objectCall = readLoaderObjectHasOwnPropertyCall(source, pos, key, true); + return objectCall !== null && objectCall.target === 'exports' ? objectCall.end : null; +} + +function readLoaderDuplicateExportReturnGuard(source, pos, binding, key) { + const condition = readLoaderIfCondition(source, pos); + if (condition === null) return null; + let c = skipWhitespaceAndComments(source, condition.start); + const hasOwnEnd = readLoaderExportsHasOwnPropertyKey(source, c, key); + if (hasOwnEnd !== null && skipWhitespaceAndComments(source, hasOwnEnd) === condition.end) { + return readLoaderNamedIdentifier(source, condition.after, 'return'); + } + const keyEnd = readLoaderNamedIdentifier(source, c, key); + if (keyEnd === null) return null; + c = skipWhitespaceAndComments(source, keyEnd); + const inEnd = readLoaderNamedIdentifier(source, c, 'in'); + if (inEnd === null) return null; + c = skipWhitespaceAndComments(source, inEnd); + let targetEnd = readLoaderCjsExportTarget(source, c); + if (targetEnd === null) return null; + c = skipWhitespaceAndComments(source, targetEnd); + c = readLoaderOperator(source, c, '&&'); + if (c === null) return null; + c = skipWhitespaceAndComments(source, c); + targetEnd = readLoaderCjsExportTarget(source, c); + if (targetEnd === null) return null; + c = skipWhitespaceAndComments(source, targetEnd); + c = readLoaderBracketIdentifier(source, c, key); + if (c === null) return null; + c = skipWhitespaceAndComments(source, c); + c = readLoaderOperator(source, c, '==='); + if (c === null) return null; + c = skipWhitespaceAndComments(source, c); + const bindingEnd = readLoaderNamedIdentifier(source, c, binding); + if (bindingEnd === null) return null; + c = skipWhitespaceAndComments(source, bindingEnd); + c = readLoaderBracketIdentifier(source, c, key); + if (c === null || skipWhitespaceAndComments(source, c) !== condition.end) return null; + return readLoaderNamedIdentifier(source, condition.after, 'return'); +} + +function readLoaderHasOwnConditionalReexport(source, pos, binding, key) { + const condition = readLoaderIfCondition(source, pos); + if (condition === null) return null; + let c = skipWhitespaceAndComments(source, condition.start); + const keyCheck = readLoaderKeyNotEqualsString(source, c, key); + if (keyCheck === null || keyCheck.value !== 'default') return null; + c = skipWhitespaceAndComments(source, keyCheck.end); + c = readLoaderOperator(source, c, '&&'); + if (c === null) return null; + c = skipWhitespaceAndComments(source, c); + if (source.charCodeAt(c) !== 0x21) return null; + c = skipWhitespaceAndComments(source, c + 1); + const hasOwnEnd = readLoaderHasOwnPropertyKey(source, c, key); + if (hasOwnEnd === null || skipWhitespaceAndComments(source, hasOwnEnd) !== condition.end) return null; + return readLoaderDirectReexportAssignment(source, condition.after, binding, key); +} + +function readLoaderDirectReexportAssignment(source, pos, binding, key) { + let i = readLoaderCjsExportTarget(source, pos); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + i = readLoaderBracketIdentifier(source, i, key); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + i = readLoaderAssignmentOperator(source, i); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + const bindingEnd = readLoaderNamedIdentifier(source, i, binding); + if (bindingEnd === null) return null; + i = skipWhitespaceAndComments(source, bindingEnd); + i = readLoaderBracketIdentifier(source, i, key); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + return loaderIsStatementBoundary(source, i) ? i : null; +} + +function loaderGetterReturnsBindingKey(source, start, end, binding, key) { + let i = skipWhitespaceAndComments(source, start); + const returnEnd = readLoaderNamedIdentifier(source, i, 'return'); + if (returnEnd === null) return false; + i = skipWhitespaceAndComments(source, returnEnd); + const bindingEnd = readLoaderNamedIdentifier(source, i, binding); + if (bindingEnd === null) return false; + i = skipWhitespaceAndComments(source, bindingEnd); + i = readLoaderBracketIdentifier(source, i, key); + if (i === null) return false; + i = skipWhitespaceAndComments(source, i); + if (source.charCodeAt(i) === 0x3b) i = skipWhitespaceAndComments(source, i + 1); + return i >= end; +} + +function loaderDynamicReexportGetterBody(source, paramsOpen, limit, binding, key) { + const paramsEnd = loaderFindMatchingParen(source, paramsOpen); + if (paramsEnd < 0 || paramsEnd > limit) return null; + if (skipWhitespaceAndComments(source, paramsOpen + 1) !== paramsEnd) return null; + let i = skipWhitespaceAndComments(source, paramsEnd + 1); + if (source.charCodeAt(i) !== 0x7b) return null; + const bodyEnd = loaderFindMatchingBrace(source, i); + if (bodyEnd < 0 || bodyEnd > limit) return null; + return loaderGetterReturnsBindingKey(source, i + 1, bodyEnd, binding, key) ? bodyEnd + 1 : null; +} + +function loaderDescriptorHasDynamicReexportGetter(source, start, end, binding, key) { + const descriptor = readLoaderDescriptorObject(source, start, end); + if (descriptor === null) return false; + let seenEnumerable = false; + let found = false; + let cursor = descriptor.cursor; + const descriptorEnd = descriptor.end; + while (cursor < descriptorEnd) { + if (source.charCodeAt(cursor) === 0x2c) { + cursor = skipWhitespaceAndComments(source, cursor + 1); + continue; + } + if (isLoaderSpreadTokenAt(source, cursor) || source.charCodeAt(cursor) === 0x5b) return false; + const property = loaderDescriptorPropertyName(source, cursor); + if (property === null || property.quoted) return false; + let next = skipWhitespaceAndComments(source, property.end); + if (property.name === 'enumerable') { + if (seenEnumerable || found || source.charCodeAt(next) !== 0x3a) return false; + const valueStart = skipWhitespaceAndComments(source, next + 1); + const trueEnd = readLoaderNamedIdentifier(source, valueStart, 'true'); + if (trueEnd === null) return false; + seenEnumerable = true; + cursor = skipWhitespaceAndComments(source, trueEnd); + } else if (property.name === 'get') { + if (found) return false; + if (source.charCodeAt(next) === 0x28) { + const getterEnd = loaderDynamicReexportGetterBody(source, next, descriptorEnd, binding, key); + if (getterEnd === null) return false; + found = true; + cursor = skipWhitespaceAndComments(source, getterEnd); + } else if (source.charCodeAt(next) === 0x3a) { + const body = loaderDescriptorFunctionGetterBody(source, skipWhitespaceAndComments(source, next + 1), descriptorEnd); + if (body === null || !loaderGetterReturnsBindingKey(source, body.start, body.end, binding, key)) return false; + found = true; + cursor = skipWhitespaceAndComments(source, body.end + 1); + } else { + return false; + } + } else { + return false; + } + cursor = nextLoaderObjectLiteralEntry(source, cursor, descriptorEnd); + if (cursor === null) return false; + } + return found && seenEnumerable; +} + +function readLoaderDefinePropertyReexport(source, pos, binding, key) { + const call = readLoaderDefinePropertyCall(source, pos, false); + if (call === null) return null; + const open = call.open; + let i = call.next; + i = readLoaderCjsExportTarget(source, i); + if (i === null) return null; + i = skipWhitespaceAndComments(source, i); + if (source.charCodeAt(i) !== 0x2c) return null; + i = skipWhitespaceAndComments(source, i + 1); + const keyEnd = readLoaderNamedIdentifier(source, i, key); + if (keyEnd === null) return null; + i = skipWhitespaceAndComments(source, keyEnd); + if (source.charCodeAt(i) !== 0x2c) return null; + const close = loaderFindMatchingParen(source, open); + if (close < 0) return null; + if (!loaderDescriptorHasDynamicReexportGetter(source, i + 1, close, binding, key)) return null; + return close + 1; +} + +function loaderCallbackHasReexport(source, binding, key) { + let i = skipWhitespaceAndComments(source, 0); + const conditional = readLoaderHasOwnConditionalReexport(source, i, binding, key); + if (conditional !== null) return true; + const guarded = readLoaderDefaultEsModuleReturnGuard(source, i, key); + if (guarded === null) return false; + i = skipWhitespaceAndComments(source, guarded); + if (source.charCodeAt(i) === 0x3b) i = skipWhitespaceAndComments(source, i + 1); + for (;;) { + const nextGuard = readLoaderDuplicateExportReturnGuard(source, i, binding, key); + if (nextGuard === null) break; + i = skipWhitespaceAndComments(source, nextGuard); + if (source.charCodeAt(i) === 0x3b) i = skipWhitespaceAndComments(source, i + 1); + } + const direct = readLoaderDirectReexportAssignment(source, i, binding, key); + if (direct !== null) return true; + return readLoaderDefinePropertyReexport(source, i, binding, key) !== null; +} + +function readLoaderObjectKeysReexport(source, pos, requireBindings) { + const objectEnd = readLoaderNamedIdentifier(source, pos, 'Object'); + if (objectEnd === null) return null; + let i = skipWhitespaceAndComments(source, objectEnd); + if (source.charCodeAt(i) !== 0x2e) return null; + i = skipWhitespaceAndComments(source, i + 1); + const keysEnd = readLoaderNamedIdentifier(source, i, 'keys'); + if (keysEnd === null) return null; + i = skipWhitespaceAndComments(source, keysEnd); + if (source.charCodeAt(i) !== 0x28) return null; + i = skipWhitespaceAndComments(source, i + 1); + const parsedBinding = readLoaderIdentifier(source, i); + if (parsedBinding === null) return null; + const binding = parsedBinding.name; + const specifier = requireBindings[binding]; + if (specifier === undefined) return null; + i = skipWhitespaceAndComments(source, parsedBinding.end); + if (source.charCodeAt(i) !== 0x29) return null; + i = skipWhitespaceAndComments(source, i + 1); + if (source.charCodeAt(i) !== 0x2e) return null; + i = skipWhitespaceAndComments(source, i + 1); + const forEachEnd = readLoaderNamedIdentifier(source, i, 'forEach'); + if (forEachEnd === null) return null; + i = skipWhitespaceAndComments(source, forEachEnd); + if (source.charCodeAt(i) !== 0x28) return null; + const callEnd = loaderFindMatchingParen(source, i); + if (callEnd < 0) return null; + i = skipWhitespaceAndComments(source, i + 1); + const functionEnd = readLoaderNamedIdentifier(source, i, 'function'); + if (functionEnd === null) return null; + i = skipWhitespaceAndComments(source, functionEnd); + const functionName = readLoaderIdentifier(source, i); + if (functionName !== null) { + i = skipWhitespaceAndComments(source, functionName.end); + } + if (source.charCodeAt(i) !== 0x28) return null; + const paramsEnd = loaderFindMatchingParen(source, i); + if (paramsEnd < 0 || paramsEnd > callEnd) return null; + i = skipWhitespaceAndComments(source, i + 1); + const parsedKey = readLoaderIdentifier(source, i); + if (parsedKey === null) return null; + const key = parsedKey.name; + if (skipWhitespaceAndComments(source, parsedKey.end) !== paramsEnd) return null; + i = skipWhitespaceAndComments(source, paramsEnd + 1); + if (source.charCodeAt(i) !== 0x7b) return null; + const bodyEnd = loaderFindMatchingBrace(source, i); + if (bodyEnd < 0 || bodyEnd > callEnd || skipWhitespaceAndComments(source, bodyEnd + 1) !== callEnd) return null; + if (!loaderCallbackHasReexport(source.substring(i + 1, bodyEnd), binding, key)) return null; + return { specifier, end: callEnd + 1 }; +} + +function resolveLoaderCjsReexport(specifier, filename) { + if (!filename || isBuiltin(specifier) || specifier.startsWith('node:') || specifier.includes(':')) return null; + const parentDir = pathModule.dirname(filename); + if (specifier === '.' || specifier === '..' || specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('/')) { + return resolveFilename(specifier, parentDir); + } + if (specifier.startsWith('#')) { + return resolvePackageImports(specifier, parentDir, cjsPackageConditions()); + } + if (isBarePackageSpecifier(specifier)) { + return resolveFromNodeModules(specifier, parentDir, filename, cjsPackageConditions()); + } + return null; +} + +function scanLoaderCjsTopLevelPositions(source, visitor) { + let i = 0; + let braceDepth = 0; + let statementStart = true; + while (i < source.length) { + const skipped = skipNonCode(source, i, true); + if (skipped !== null) { + i = skipped; + continue; + } + + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d) { + const whitespace = skipWhitespaceAndCommentsWithLineTerminator(source, i); + if (whitespace.hasLineTerminator) { + const nextCode = source.charCodeAt(whitespace.pos); + if ('`([.,:?+-*/%&|^<>=!~'.indexOf(source[whitespace.pos]) < 0 && nextCode !== 0x3b) { + statementStart = true; + } + } + i = whitespace.pos; + continue; + } + + const next = visitor(i, braceDepth, statementStart); + if (next === false) return false; + if (next && typeof next === 'object') { + i = next.pos; + statementStart = next.statementStart === true; + continue; + } + if (typeof next === 'number') { + i = next; + statementStart = false; + continue; + } + + if (ch === 0x7b) { + braceDepth++; + statementStart = true; + } else if (ch === 0x7d) { + braceDepth = Math.max(0, braceDepth - 1); + statementStart = true; + } else if (ch === 0x3b) { + statementStart = true; + } else { + statementStart = false; + } + i++; + } + return true; +} + +function addLoaderCjsNames(names, nameSet, source, filename, seen) { + if (seen && filename && seen[filename]) return; + if (seen && filename) seen[filename] = true; + const requireBindings = Object.create(null); + scanLoaderCjsTopLevelPositions(source, (i, braceDepth, statementStart) => { + if (braceDepth === 0 && statementStart) { + const binding = readLoaderRequireBinding(source, i); + if (binding !== null) { + requireBindings[binding.binding] = binding.specifier; + return { pos: binding.end, statementStart: true }; + } + } + const name = readLoaderCjsExportName(source, i) || readLoaderDefinePropertyExportName(source, i); + if (name !== null && name !== 'default' && !nameSet.has(name)) { + nameSet.add(name); + names.push(name); + } + const objectLiteral = readLoaderModuleExportsObjectLiteralNames(source, i); + if (objectLiteral !== null) { + for (let j = 0; j < objectLiteral.names.length; j++) { + const objectName = objectLiteral.names[j]; + if (objectName !== 'default' && !nameSet.has(objectName)) { + nameSet.add(objectName); + names.push(objectName); + } + } + if (filename) { + for (let j = 0; j < objectLiteral.reexports.length; j++) { + const reexport = objectLiteral.reexports[j]; + try { + const resolved = resolveLoaderCjsReexport(reexport, filename); + if (resolved !== null) addLoaderCjsNames(names, nameSet, resolved.content, resolved.filename, seen || Object.create(null)); + } catch (_) {} + } + } + return objectLiteral.end; + } + const keysReexport = braceDepth === 0 && statementStart ? readLoaderObjectKeysReexport(source, i, requireBindings) : null; + const reexport = keysReexport !== null ? keysReexport.specifier : readLoaderModuleExportsRequire(source, i); + if (reexport !== null && filename) { + try { + const resolved = resolveLoaderCjsReexport(reexport, filename); + if (resolved !== null) addLoaderCjsNames(names, nameSet, resolved.content, resolved.filename, seen || Object.create(null)); + } catch (_) {} + } + if (keysReexport !== null) return keysReexport.end; + return undefined; + }); +} + +function loaderCjsNamedExports(source, filename) { + const names = []; + addLoaderCjsNames(names, new Set(), source, filename, Object.create(null)); + return names; +} + +function statementEndForStaticImport(source, start) { + let i = start; + let brace = 0; + let paren = 0; + while (i < source.length) { + const code = source.charCodeAt(i); + if (code === 0x27 || code === 0x22 || code === 0x60) { + i = skipQuotedOrTemplate(source, i); + continue; + } + if (code === 0x2f && i + 1 < source.length && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (code === 0x2f && i + 1 < source.length && source.charCodeAt(i + 1) === 0x2a) { + i += 2; + while (i + 1 < source.length && !(source.charCodeAt(i) === 0x2a && source.charCodeAt(i + 1) === 0x2f)) i++; + i = Math.min(i + 2, source.length); + continue; + } + if (code === 0x7b) brace++; + else if (code === 0x7d) brace = Math.max(0, brace - 1); + else if (code === 0x28) paren++; + else if (code === 0x29) paren = Math.max(0, paren - 1); + else if ((code === 0x3b || code === 0x0a || code === 0x0d) && brace === 0 && paren === 0) return i; + i++; + } + return source.length; +} + +function staticImportEdgeAt(source, pos) { + if (startsWithKeywordAt(source, 'import', pos)) { + const afterImport = skipWhitespaceAndComments(source, pos + 6); + const bare = readStaticSpecifierString(source, afterImport); + if (bare) { + return { specifier: bare.value }; + } + + const end = statementEndForStaticImport(source, afterImport); + let i = afterImport; + while (i < end) { + const code = source.charCodeAt(i); + if (code === 0x27 || code === 0x22 || code === 0x60) { + i = skipQuotedOrTemplate(source, i); + continue; + } + if (startsWithKeywordAt(source, 'from', i)) { + const spec = readStaticSpecifierString(source, i + 4); + if (spec && spec.end <= end + 1) { + return { specifier: spec.value }; + } + } + i++; + } + } + + if (startsWithKeywordAt(source, 'export', pos)) { + const end = statementEndForStaticImport(source, pos + 6); + let i = pos + 6; + while (i < end) { + const code = source.charCodeAt(i); + if (code === 0x27 || code === 0x22 || code === 0x60) { + i = skipQuotedOrTemplate(source, i); + continue; + } + if (startsWithKeywordAt(source, 'from', i)) { + const spec = readStaticSpecifierString(source, i + 4); + if (spec && spec.end <= end + 1) { + return { specifier: spec.value }; + } + } + i++; + } + } + + return null; +} + +function collectStaticEsmEdges(source) { + const edges = []; + scanSourceCodePositions(source, { skipRegex: true }, (i) => { + const edge = staticImportEdgeAt(source, i); + if (edge !== null) edges.push(edge); + return undefined; + }); + return edges; +} + +function collectStaticEsmSpecifiers(source) { + return collectStaticEsmEdges(source).map((edge) => edge.specifier); +} + +function collectLiteralRequireSpecifiers(source, names) { + names = names || ['require']; + const specifiers = []; + scanSourceCodePositions(source, { skipRegex: true }, (i, _, previousCode) => { + for (let n = 0; n < names.length; n++) { + const name = names[n]; + if (startsWithKeywordAt(source, name, i) && previousCode !== 0x2e) { + const open = skipWhitespaceAndComments(source, i + name.length); + if (source.charCodeAt(open) === 0x28) { + const spec = readStaticSpecifierString(source, open + 1); + if (spec) specifiers.push(spec.value); + } + } + } + return undefined; + }); + return specifiers; +} + +function collectCreateRequireNamesFromImport(source, pos, end) { + let i = skipWhitespaceAndComments(source, pos + 6); + if (source.charCodeAt(i) !== 0x7b) return null; + const namedEnd = loaderFindMatchingBrace(source, i); + if (namedEnd < 0 || namedEnd > end) return null; + + let afterNamed = skipWhitespaceAndComments(source, namedEnd + 1); + const fromEnd = readLoaderNamedIdentifier(source, afterNamed, 'from'); + if (fromEnd === null) return null; + afterNamed = skipWhitespaceAndComments(source, fromEnd); + const spec = readStaticSpecifierString(source, afterNamed); + if (spec === null || spec.end > end || (spec.value !== 'module' && spec.value !== 'node:module')) return null; + + const names = []; + let cursor = skipWhitespaceAndComments(source, i + 1); + while (cursor < namedEnd) { + if (source.charCodeAt(cursor) === 0x2c) { + cursor = skipWhitespaceAndComments(source, cursor + 1); + continue; + } + let importedName; + const quote = source.charCodeAt(cursor); + if (quote === 0x27 || quote === 0x22) { + const decoded = decodeStringLiteral(source, cursor + 1, quote); + if (decoded === null) return names; + importedName = decoded.value; + cursor = skipWhitespaceAndComments(source, decoded.end + 1); + } else { + const imported = readLoaderIdentifier(source, cursor); + if (imported === null) return names; + importedName = imported.name; + cursor = skipWhitespaceAndComments(source, imported.end); + } + + let local = importedName; + const asEnd = readLoaderNamedIdentifier(source, cursor, 'as'); + if (asEnd !== null) { + cursor = skipWhitespaceAndComments(source, asEnd); + const alias = readLoaderIdentifier(source, cursor); + if (alias === null) return names; + local = alias.name; + cursor = skipWhitespaceAndComments(source, alias.end); + } else if (quote === 0x27 || quote === 0x22) { + return names; + } + + if (importedName === 'createRequire') names.push(local); + if (cursor < namedEnd && source.charCodeAt(cursor) !== 0x2c) return names; + } + return names; +} + +function collectCreateRequireFactoryNames(source) { + const names = []; + scanSourceCodePositions(source, { skipRegex: false }, (i) => { + if (startsWithKeywordAt(source, 'import', i)) { + const end = statementEndForStaticImport(source, i + 6); + const parsed = collectCreateRequireNamesFromImport(source, i, end); + if (parsed !== null) { + for (let p = 0; p < parsed.length; p++) names.push(parsed[p]); + } + return end; + } + return undefined; + }); + return names; +} + +function collectCreateRequireAliases(source, factoryNames) { + factoryNames = factoryNames || collectCreateRequireFactoryNames(source); + const aliases = []; + if (factoryNames.length === 0) return aliases; + scanSourceCodePositions(source, { skipRegex: false }, (i) => { + const declarationEnd = readVariableDeclarationKeyword(source, i); + if (declarationEnd !== null) { + let p = skipWhitespaceAndComments(source, declarationEnd); + const ident = readLoaderIdentifier(source, p); + if (ident !== null) { + const name = ident.name; + p = skipWhitespaceAndComments(source, ident.end); + if (source.charCodeAt(p) === 0x3d) { + p = skipWhitespaceAndComments(source, p + 1); + for (let f = 0; f < factoryNames.length; f++) { + const factory = factoryNames[f]; + if (startsWithKeywordAt(source, factory, p)) { + const open = skipWhitespaceAndComments(source, p + factory.length); + if (source.charCodeAt(open) === 0x28) { + aliases.push(name); + } + } + } + } + } + } + return undefined; + }); + return aliases; +} + +function collectCreateRequireCallSpecifiers(source, factoryNames) { + factoryNames = factoryNames || collectCreateRequireFactoryNames(source); + const specifiers = []; + if (factoryNames.length === 0) return specifiers; + scanSourceCodePositions(source, { skipRegex: true }, (i, _, previousCode) => { + for (let f = 0; f < factoryNames.length; f++) { + const factory = factoryNames[f]; + if (startsWithKeywordAt(source, factory, i) && previousCode !== 0x2e) { + const firstOpen = skipWhitespaceAndComments(source, i + factory.length); + if (source.charCodeAt(firstOpen) === 0x28) { + const firstClose = loaderFindMatchingParen(source, firstOpen); + if (firstClose >= 0) { + const secondOpen = skipWhitespaceAndComments(source, firstClose + 1); + if (source.charCodeAt(secondOpen) === 0x28) { + const spec = readStaticSpecifierString(source, secondOpen + 1); + if (spec) specifiers.push(spec.value); + } + } + } + } + } + return undefined; + }); + return specifiers; +} + +function isEsmGraphFile(filename, source) { + const packageScope = filename.endsWith('.js') ? getPackageScopeInfo(filename) : null; + const explicitPackageType = packageScope ? packageScope.packageType : null; + const isCommonJsPackage = explicitPackageType === 'commonjs' || + (explicitPackageType === null && packageScope !== null && packageScope.isNodeModulesPackage); + return filename.endsWith('.mjs') || + (filename.endsWith('.js') && explicitPackageType === 'module') || + (!filename.endsWith('.cjs') && !isCommonJsPackage && + (looksLikeEsmSource(source) || hasCjsWrapperLexicalRedeclaration(source))); +} + +function readEsmGraphFileInfo(filename, cache) { + if (Object.prototype.hasOwnProperty.call(cache, filename)) { + return cache[filename]; + } + const source = tryReadFile(filename); + if (source === null) { + return { source: null, isEsm: false }; + } + const info = { + source, + isEsm: isEsmGraphFile(filename, source), + }; + cache[filename] = info; + return info; +} + +function fileUrlForPath(filename) { + return 'file://' + filename; +} + +function resolveEsmGraphSpecifier(specifier, parentFilename, conditions) { + conditions = conditions || esmPackageConditions(); + if (specifier.startsWith('node:') || specifier.startsWith('data:')) return null; + const parentDir = pathModule.dirname(parentFilename); + if (specifier === '.' || specifier === '..' || specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('/')) { + try { + return resolveFilename(specifier, parentDir); + } catch (_) { + return null; + } + } + if (specifier.startsWith('#')) { + try { + const resolved = resolvePackageImports(specifier, parentDir, conditions); + if (resolved && !resolved.builtin) return resolved; + } catch (_) { + return null; + } + return null; + } + try { + return resolveFromNodeModules(specifier, parentDir, parentFilename, conditions); + } catch (_) { + return null; + } +} + +function addRequireEsmGraphMark(filename, marked) { + const graph = globalThis.__wasm_rquickjs_require_esm_graph_in_progress || Object.create(null); + const counts = globalThis.__wasm_rquickjs_require_esm_graph_counts || Object.create(null); + globalThis.__wasm_rquickjs_require_esm_graph_in_progress = graph; + globalThis.__wasm_rquickjs_require_esm_graph_counts = counts; + + counts[filename] = (counts[filename] || 0) + 1; + graph[filename] = true; + marked.push(filename); + + const fileUrl = fileUrlForPath(filename); + counts[fileUrl] = (counts[fileUrl] || 0) + 1; + graph[fileUrl] = true; + marked.push(fileUrl); +} + +function stackContains(stack, filename) { + for (let i = 0; i < stack.length; i++) { + if (stack[i] === filename) return true; + } + return false; +} + +function esmGraphReachesAny(filename, stack, seen, fileInfoCache) { + if (stackContains(stack, filename)) return true; + seen = seen || Object.create(null); + if (seen[filename]) return false; + seen[filename] = true; + + const fileInfo = readEsmGraphFileInfo(filename, fileInfoCache); + if (fileInfo.source === null) return false; + + const source = fileInfo.source; + const isEsm = fileInfo.isEsm; + const specifiers = isEsm + ? collectStaticEsmSpecifiers(source) + : collectLiteralRequireSpecifiers(source); + const conditions = isEsm ? esmPackageConditions() : cjsPackageConditions(); + for (let i = 0; i < specifiers.length; i++) { + const resolved = resolveEsmGraphSpecifier(specifiers[i], filename, conditions); + if (resolved && resolved.filename && esmGraphReachesAny(resolved.filename, stack, seen, fileInfoCache)) return true; + } + + if (isEsm) { + const factoryNames = collectCreateRequireFactoryNames(source); + const aliases = collectCreateRequireAliases(source, factoryNames); + const bridgeSpecifiers = collectCreateRequireCallSpecifiers(source, factoryNames).concat( + aliases.length === 0 ? [] : collectLiteralRequireSpecifiers(source, aliases), + ); + for (let i = 0; i < bridgeSpecifiers.length; i++) { + const resolved = resolveEsmGraphSpecifier(bridgeSpecifiers[i], filename, cjsPackageConditions()); + if (resolved && resolved.filename && esmGraphReachesAny(resolved.filename, stack, seen, fileInfoCache)) return true; + } + } + + return false; +} + +function scanRequireEsmGraph(filename, marked, seen, stack, fileInfoCache) { + if (seen[filename]) return; + seen[filename] = true; + + const fileInfo = readEsmGraphFileInfo(filename, fileInfoCache); + if (fileInfo.source === null) return; + + const source = fileInfo.source; + const isEsm = fileInfo.isEsm; + if (!isEsm) { + const requireSpecifiers = collectLiteralRequireSpecifiers(source); + for (let i = 0; i < requireSpecifiers.length; i++) { + const resolved = resolveEsmGraphSpecifier(requireSpecifiers[i], filename, cjsPackageConditions()); + if (resolved && resolved.filename) { + const targetInfo = readEsmGraphFileInfo(resolved.filename, fileInfoCache); + if (targetInfo.source !== null && targetInfo.isEsm && esmGraphReachesAny(resolved.filename, stack, undefined, fileInfoCache)) { + addRequireEsmGraphMark(resolved.filename, marked); + } else { + scanRequireEsmGraph(resolved.filename, marked, seen, stack, fileInfoCache); + } + } + } + return; + } + + stack.push(filename); + + const specifiers = collectStaticEsmSpecifiers(source); + for (let i = 0; i < specifiers.length; i++) { + const resolved = resolveEsmGraphSpecifier(specifiers[i], filename, esmPackageConditions()); + if (resolved && resolved.filename) { + scanRequireEsmGraph(resolved.filename, marked, seen, stack, fileInfoCache); + } + } + const factoryNames = collectCreateRequireFactoryNames(source); + const aliases = collectCreateRequireAliases(source, factoryNames); + const createRequireSpecifiers = collectCreateRequireCallSpecifiers(source, factoryNames).concat( + aliases.length === 0 ? [] : collectLiteralRequireSpecifiers(source, aliases), + ); + for (let i = 0; i < createRequireSpecifiers.length; i++) { + const resolved = resolveEsmGraphSpecifier(createRequireSpecifiers[i], filename, cjsPackageConditions()); + if (resolved && resolved.filename) { + const targetInfo = readEsmGraphFileInfo(resolved.filename, fileInfoCache); + if (targetInfo.source !== null && targetInfo.isEsm && esmGraphReachesAny(resolved.filename, stack, undefined, fileInfoCache)) { + addRequireEsmGraphMark(resolved.filename, marked); + } else { + scanRequireEsmGraph(resolved.filename, marked, seen, stack, fileInfoCache); + } + } + } + stack.pop(); +} + +function markRequireEsmGraph(filename) { + const marked = []; + withSuppressedPackageDeprecationWarnings(() => { + scanRequireEsmGraph(filename, marked, Object.create(null), [], Object.create(null)); + }); + return marked; +} + +function unmarkRequireEsmGraph(marked) { + const graph = globalThis.__wasm_rquickjs_require_esm_graph_in_progress; + const counts = globalThis.__wasm_rquickjs_require_esm_graph_counts; + if (!graph || !counts) return; + for (let i = 0; i < marked.length; i++) { + const key = marked[i]; + counts[key] = (counts[key] || 1) - 1; + if (counts[key] <= 0) { + delete counts[key]; + delete graph[key]; + } + } +} + +function throwIfRequireEsmGraphCycle(resolvedFilename) { + const graph = globalThis.__wasm_rquickjs_require_esm_graph_in_progress; + if (graph && (graph[resolvedFilename] || graph[fileUrlForPath(resolvedFilename)])) { + const err = new Error('Cannot require() ES Module ' + resolvedFilename + ' in a cycle.'); + err.code = 'ERR_REQUIRE_CYCLE_MODULE'; + throw err; + } +} + +const wrapper = [ + '(function (exports, require, module, __filename, __dirname) { ', + '\n});' +]; + +function wrap(script) { + const activeWrapper = (typeof moduleExports !== 'undefined' && moduleExports.wrapper) || wrapper; + return activeWrapper[0] + script + activeWrapper[1]; +} + +function compileCjs(filename, source) { + if (source.length > 0 && source.charCodeAt(0) === 0xFEFF) { + source = source.slice(1); + } + // Strip shebang + if (source.length > 1 && source.charCodeAt(0) === 0x23 && source.charCodeAt(1) === 0x21) { + source = '//' + source; + } + + source = transpileTypeScriptModule(filename, source); + source = stripV8OptimizationIntrinsics(source); + source = stripImportAttributes(source, filename); + + const cjsLineOffsets = getCjsLineOffsetRegistry(); + cjsLineOffsets[filename] = cjsLineOffset; + + const wrappedSource = wrap(source + '\n//# sourceURL=' + filename + '\n'); + return _evalWithFilename(wrappedSource, filename); +} + +function compileModuleInto(mod, source, filename, requireOverride) { + filename = filename === undefined || filename === null ? mod.filename : filename; + const requireParentFilename = filename === '' && mod && typeof mod.filename === 'string' + ? mod.filename + : filename; + const dirname = pathModule.dirname(filename); + const requireDirname = pathModule.dirname(requireParentFilename); + const childRequire = requireOverride || makeRequire(requireDirname, mod, requireParentFilename); + const compiledFn = compileCjs(filename, String(source)); + const previousModuleContext = globalThis.__wasm_rquickjs_current_module; + globalThis.__wasm_rquickjs_current_module = { + filename: filename, + source: String(source) + }; + const previousCjsImportDir = globalThis.__wasm_rquickjs_cjs_import_dir; + globalThis.__wasm_rquickjs_cjs_import_dir = dirname; + try { + return compiledFn.call(mod.exports, mod.exports, childRequire, mod, filename, dirname); + } finally { + globalThis.__wasm_rquickjs_current_module = previousModuleContext; + if (previousCjsImportDir !== undefined) { + globalThis.__wasm_rquickjs_cjs_import_dir = previousCjsImportDir; + } else { + delete globalThis.__wasm_rquickjs_cjs_import_dir; + } + } +} + +function makeModuleCompile(mod) { + return function _compile(content, filename) { + if (this !== mod) { + throw new ERR_INVALID_ARG_TYPE('mod', 'Module', this); + } + return compileModuleInto(mod, content, arguments.length > 1 ? filename : mod.filename); + }; +} + +function loaderValueTypeName(value) { + if (value === null) return 'null'; + const type = typeof value; + if (type !== 'object') return type; + if (Array.isArray(value)) return 'Array'; + if (value && value.constructor && typeof value.constructor.name === 'string') return value.constructor.name; + return 'Object'; +} + +function makeLoaderInvalidReturnValueError(hookName, value) { + const err = new TypeError(`Expected an object to be returned from the '${hookName}' hook but got ${loaderValueTypeName(value)}.`); + err.code = 'ERR_INVALID_RETURN_VALUE'; + return err; +} + +function makeLoaderInvalidReturnPropertyValueError(propertyName, hookName, expected, value) { + const err = new TypeError(`Expected ${expected} for "${propertyName}" from the '${hookName}' hook but got type ${loaderValueTypeName(value)}.`); + err.code = 'ERR_INVALID_RETURN_PROPERTY_VALUE'; + return err; +} + +function makeLoaderUnknownModuleFormatError(format) { + const err = new RangeError(`Unknown module format: ${String(format)}`); + err.code = 'ERR_UNKNOWN_MODULE_FORMAT'; + return err; +} + +function makeLoaderInvalidUrlError(hookName, loaderUrl, value) { + const err = new TypeError(`Expected a URL string to be returned for "url" from the '${hookName}' hook in ${String(loaderUrl)} but got ${JSON.stringify(String(value))}.`); + err.code = 'ERR_INVALID_RETURN_PROPERTY_VALUE'; + return err; +} + +function makeLoaderMissingUrlError(hookName, loaderUrl, value) { + const err = new TypeError(`Expected a URL string to be returned for "url" from the '${hookName}' hook in ${String(loaderUrl)} but got type ${loaderValueTypeName(value)}.`); + err.code = 'ERR_INVALID_RETURN_PROPERTY_VALUE'; + return err; +} + +function makeLoaderChainError(hook) { + const err = new Error(`${hook} hook did not call the next hook and did not explicitly short circuit`); + err.code = 'ERR_LOADER_CHAIN_INCOMPLETE'; + return err; +} + +function makeEsmModuleNotFoundError(specifier) { + const err = new Error("Cannot find module '" + specifier + "'"); + err.code = 'ERR_MODULE_NOT_FOUND'; + return err; +} + +function makeEsmUnsupportedDirImportError(filename) { + const err = new Error('Directory import ' + JSON.stringify(filename) + ' is not supported resolving ES modules'); + err.code = 'ERR_UNSUPPORTED_DIR_IMPORT'; + return err; +} + +function isRelativeOrAbsoluteSpecifier(specifier) { + return specifier === '.' || specifier === '..' || + specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('/'); +} + +function defaultLoaderFormatForFilename(filename) { + if (filename.endsWith('.json')) return 'json'; + if (filename.endsWith('.mjs')) return 'module'; + if (filename.endsWith('.cjs')) return 'commonjs'; + return undefined; +} + +function resultForEsmFileUrl(url) { + const filename = nodeUrl.fileURLToPath(url); + const stat = _stat(filename); + if (stat === 1) throw makeEsmUnsupportedDirImportError(filename); + if (stat !== 0) throw makeEsmModuleNotFoundError(url.href); + return { url: url.href, format: defaultLoaderFormatForFilename(filename) }; +} + +function resultForPackageFile(filename) { + const stat = _stat(filename); + if (stat === 1) throw makeEsmUnsupportedDirImportError(filename); + if (stat !== 0) throw makeEsmModuleNotFoundError(filename); + return { url: nodeUrl.pathToFileURL(filename).href, format: defaultLoaderFormatForFilename(filename) }; +} + +function parentFilenameForLoaderResolve(parentURL, baseUrl) { + parentURL = String(parentURL || baseUrl); + if (parentURL.startsWith('file://')) { + return nodeUrl.fileURLToPath(parentURL); + } + if (parentURL.startsWith('/')) { + return parentURL; + } + return null; +} + +function conditionsForLoaderResolve(context) { + if (context && Array.isArray(context.conditions)) { + const conditions = setFromArray(context.conditions); + conditions.add('default'); + return conditions; + } + return esmPackageConditions(); +} + +function resultForRelativeOrAbsoluteSpecifier(specifier, parentURL) { + return resultForEsmFileUrl(new URL(specifier, parentURL)); +} + +function decodeEsmPackageSubpath(subpath) { + if (hasEncodedSlashOrBackslash(subpath)) { + throw makeInvalidModuleSpecifierError(subpath, 'must not include encoded "/" or "\\" characters'); + } + try { + return decodeURIComponent(subpath); + } catch (_) { + return subpath; + } +} + +function resolveEsmPackageForLoader(id, parentDir, parentFilename, conditions) { + const parts = splitPackageName(id); + const hasSubpath = parts.subpath.length > 0; + + const selfResolved = resolvePackageSelfReference(parts, parentDir, conditions); + if (selfResolved !== undefined) { + if (selfResolved.builtin) return { url: selfResolved.builtin }; + return resultForPackageFile(selfResolved.filename); + } + + const dirs = _nodeModulePaths(parentDir); + for (let i = 0; i < dirs.length; i++) { + const pkgDir = pathModule.join(dirs[i], parts.name); + const pkgJsonPath = pathModule.join(pkgDir, 'package.json'); + const packageEntry = readPackageDirectoryForExports(parts, pkgDir, pkgJsonPath, conditions); + if (packageEntry === null) continue; + + if (packageEntry.exportsResolved !== undefined) { + if (packageEntry.exportsResolved.builtin) return { url: packageEntry.exportsResolved.builtin }; + return resultForPackageFile(packageEntry.exportsResolved.filename); + } + + if (hasSubpath) { + return resultForPackageFile(pathModule.join(pkgDir, decodeEsmPackageSubpath(parts.subpath))); + } + + return resolveFromNodeModules(id, parentDir, parentFilename, conditions); + } + + return null; +} + +function isLoaderSourceValue(value) { + return typeof value === 'string' || + value instanceof ArrayBuffer || + (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer) || + ArrayBuffer.isView(value); +} + +function validateRegisteredLoaderResult(result, hookName, context) { + if (!result || typeof result !== 'object') { + throw makeLoaderInvalidReturnValueError(hookName, result); + } + if (Object.prototype.hasOwnProperty.call(result, 'format')) { + const format = result.format; + if (format !== undefined && format !== null && typeof format !== 'string') { + throw makeLoaderInvalidReturnPropertyValueError('format', hookName, 'a string or nullish value', format); + } + } + if (hookName === 'load' && Object.prototype.hasOwnProperty.call(result, 'source')) { + const source = result.source; + if (source === null || source === undefined) { + if (result.format === 'commonjs' || (result.format === undefined && context && context.format === 'commonjs')) return result; + throw makeLoaderInvalidReturnPropertyValueError('source', hookName, 'a string, ArrayBuffer, or ArrayBufferView', source); + } + if (!isLoaderSourceValue(source)) { + throw makeLoaderInvalidReturnPropertyValueError('source', hookName, 'a string, ArrayBuffer, or ArrayBufferView', source); + } } + return result; +} - if (!Number.isFinite(column) || column < 1) { - column = 1; +function validateRegisteredLoaderLoadFormat(format) { + if (format === undefined || format === null) return undefined; + if (format === 'module' || format === 'commonjs' || format === 'json' || format === 'builtin' || format === 'wasm') { + return format; } + throw makeLoaderUnknownModuleFormatError(format); +} - let arrowMessage = filename + ':' + line; - if (sourceLine.length > 0) { - arrowMessage += '\n' + sourceLine + '\n' + ' '.repeat(column - 1) + '^'; +function validateRegisteredLoaderResolveUrl(url, loaderUrl) { + if (typeof url !== 'string') { + throw makeLoaderMissingUrlError('resolve', loaderUrl, url); + } + try { + new URL(url); + } catch (_) { + throw makeLoaderInvalidUrlError('resolve', loaderUrl, url); } +} - err[arrowMessageSymbol] = arrowMessage; +function loaderSourceToString(source) { + if (typeof source === 'string') { + return source; + } + if (source instanceof ArrayBuffer) { + return new TextDecoder().decode(new Uint8Array(source)); + } + if (typeof SharedArrayBuffer !== 'undefined' && source instanceof SharedArrayBuffer) { + return new TextDecoder().decode(new Uint8Array(source)); + } + if (ArrayBuffer.isView(source) && source.buffer instanceof ArrayBuffer) { + return new TextDecoder().decode(new Uint8Array(source.buffer, source.byteOffset, source.byteLength)); + } + if ( + typeof SharedArrayBuffer !== 'undefined' && + ArrayBuffer.isView(source) && + source.buffer instanceof SharedArrayBuffer + ) { + return new TextDecoder().decode(new Uint8Array(source.buffer, source.byteOffset, source.byteLength)); + } + throw makeLoaderInvalidReturnPropertyValueError('source', 'load', 'a string, ArrayBuffer, or ArrayBufferView', source); } -// Create a wrapper around an ESM namespace that adds __esModule: true -// while still passing isModuleNamespaceObject checks. -function wrapEsmNamespace(ns) { - if (!ns || typeof ns !== 'object') return ns; - if (!Object.hasOwn(ns, 'default') || Object.hasOwn(ns, '__esModule')) return ns; - // Try to add __esModule directly to the namespace - try { - Object.defineProperty(ns, '__esModule', { - value: true, - writable: false, - configurable: false, - enumerable: false, - }); - return ns; - } catch (_) {} - // Namespace is sealed — create a plain wrapper that looks like a module namespace - const wrapped = Object.create(null); - Object.defineProperty(wrapped, Symbol.toStringTag, { value: 'Module' }); - const keys = Object.keys(ns); - for (let i = 0; i < keys.length; i++) { - const k = keys[i]; - Object.defineProperty(wrapped, k, { - get: (function(key) { return function() { return ns[key]; }; })(k), - enumerable: true, - configurable: false, - }); +function loaderCommonJsSourceModule(source, url) { + source = loaderSourceToString(source); + const filename = loaderCommonJsFilename(url); + const names = loaderCjsNamedExports(source, pathModule.isAbsolute(filename) ? filename : undefined); + const cacheKey = loaderCommonJsCacheKey(url, filename); + const lines = [ + 'const __cjs_default = globalThis.__wasm_rquickjs_load_commonjs_loader_source(' + JSON.stringify(filename) + ',' + JSON.stringify(source) + ',' + JSON.stringify(String(url || '')) + ',' + JSON.stringify(cacheKey) + ');', + 'export default __cjs_default;', + ]; + for (let i = 0; i < names.length; i++) { + const local = '__wasm_rquickjs_loader_export_' + i; + const nameLiteral = JSON.stringify(names[i]); + lines.push('const ' + local + ' = Object.prototype.hasOwnProperty.call(__cjs_default, ' + nameLiteral + ') ? __cjs_default[' + nameLiteral + '] : undefined;'); + lines.push('export { ' + local + ' as ' + nameLiteral + ' };'); } - Object.defineProperty(wrapped, '__esModule', { - value: true, - writable: false, - configurable: false, - enumerable: false, - }); - return wrapped; + return 'data:text/javascript,' + encodeURIComponent(lines.join('\n')); } -// Normalize QuickJS SyntaxError messages for ESM keywords to match Node.js/V8 format. -// QuickJS: "unsupported keyword: export" → Node.js: "Unexpected token 'export'" -function normalizeEsmSyntaxError(err) { - if (!err || typeof err.message !== 'string') return; - const m = err.message.match(/^unsupported keyword: (\w+)$/); - if (m) { - err.message = "Unexpected token '" + m[1] + "'"; +function loaderFileUrlSource(url) { + if (!String(url).startsWith('file://')) return null; + try { + return tryReadFile(nodeUrl.fileURLToPath(url)); + } catch (_) { + return null; } } -function markAsSyntaxError(err) { - if (!err || err.name === 'SyntaxError') return; - err.name = 'SyntaxError'; - if (typeof err.stack === 'string') { - err.stack = err.stack.replace(/^Error:/, 'SyntaxError:'); +function loaderCommonJsFilename(url) { + url = String(url || ''); + if (url.startsWith('file://')) { + return nodeUrl.fileURLToPath(url); + } + if (url.startsWith('/')) { + return url; } + return url || 'anonymous'; } -function looksLikeEsmSource(source) { - return /(^|[\r\n])\s*(?:import\s+(?:[\s\S]*?\s+from\s+)?['"]|import\s*[\{\*]|export\s+)/.test(source); +function loaderCommonJsCacheKey(url, filename) { + return filename; } -const wrapper = [ - '(function (exports, require, module, __filename, __dirname) { ', - '\n});' -]; +function makeModuleRequire(mod) { + return function require(id) { + return makeRequire(pathModule.dirname(mod.filename), mod)(id); + }; +} -function wrap(script) { - const activeWrapper = (typeof moduleExports !== 'undefined' && moduleExports.wrapper) || wrapper; - return activeWrapper[0] + script + activeWrapper[1]; +function markRequireEsmForcedModule(resolvedFilename) { + let registry = globalThis.__wasm_rquickjs_require_esm_forced_module; + if (!registry || typeof registry !== 'object') { + registry = Object.create(null); + Object.defineProperty(globalThis, '__wasm_rquickjs_require_esm_forced_module', { + value: registry, + writable: true, + configurable: true, + enumerable: false, + }); + } + registry[resolvedFilename] = true; + registry[nodeUrl.pathToFileURL(resolvedFilename).href] = true; } -function compileCjs(filename, source) { - // Strip shebang - if (source.length > 1 && source.charCodeAt(0) === 0x23 && source.charCodeAt(1) === 0x21) { - source = '//' + source; +function unmarkRequireEsmForcedModule(resolvedFilename) { + const registry = globalThis.__wasm_rquickjs_require_esm_forced_module; + if (!registry || typeof registry !== 'object') return; + delete registry[resolvedFilename]; + delete registry[nodeUrl.pathToFileURL(resolvedFilename).href]; +} + +function requireEsmWithCacheGuard(mod, resolvedFilename, forceModule) { + throwIfRequireEsmGraphCycle(resolvedFilename); + const markedGraph = markRequireEsmGraph(resolvedFilename); + Object.defineProperty(mod, '__wasmRequireEsmInProgress', { + value: true, + writable: true, + configurable: true, + enumerable: false, + }); + try { + if (forceModule) markRequireEsmForcedModule(resolvedFilename); + const namespace = _requireEsm(resolvedFilename); + if (namespace && typeof namespace === 'object' && Object.hasOwn(namespace, 'module.exports')) { + return namespace['module.exports']; + } + return wrapEsmNamespace(namespace); + } finally { + if (forceModule) unmarkRequireEsmForcedModule(resolvedFilename); + unmarkRequireEsmGraph(markedGraph); + delete mod.__wasmRequireEsmInProgress; } +} - source = transpileTypeScriptModule(filename, source); - source = stripV8OptimizationIntrinsics(source); - source = stripImportAttributes(source); +function currentMainScriptFilename() { + if (!globalThis.process || !globalThis.process.argv || typeof globalThis.process.argv[1] !== 'string') { + return null; + } + const mainScript = globalThis.process.argv[1]; + if (!mainScript) return null; + try { + return toCjsCanonicalFilename(mainScript, true); + } catch (_) { + const absolute = pathModule.isAbsolute(mainScript) ? mainScript : pathModule.resolve('/', mainScript); + return absolute; + } +} - const cjsLineOffsets = getCjsLineOffsetRegistry(); - cjsLineOffsets[filename] = 2; +function isMainEntryFilename(resolvedFilename) { + if (typeof mainModule === 'undefined' || mainModule.filename !== '/') return false; + const mainScript = currentMainScriptFilename(); + if (!mainScript) return false; + try { + return toCjsCanonicalFilename(resolvedFilename, true) === mainScript; + } catch (_) { + const absolute = pathModule.isAbsolute(resolvedFilename) ? resolvedFilename : pathModule.resolve('/', resolvedFilename); + return absolute === mainScript; + } +} - const wrappedSource = wrap(source + '\n//# sourceURL=' + filename + '\n'); - return _evalWithFilename(wrappedSource, filename); +function unlinkModuleFromParent(parentModule, mod) { + if (!parentModule || !parentModule.children) return; + const index = parentModule.children.indexOf(mod); + if (index !== -1) parentModule.children.splice(index, 1); } function loadModule(resolvedFilename, source, parentModule) { + const isMainModuleLoad = isMainEntryFilename(resolvedFilename); + const filename = toCjsCanonicalFilename(resolvedFilename, isMainModuleLoad); + // Check cache - if (moduleCache[resolvedFilename]) { - const cached = moduleCache[resolvedFilename]; + if (moduleCache[filename]) { + const cached = moduleCache[filename]; + if (cached.__wasmRequireEsmInProgress) { + const err = new Error('Cannot require() ES Module ' + filename + ' in a cycle.'); + err.code = 'ERR_REQUIRE_CYCLE_MODULE'; + throw err; + } if (parentModule && parentModule.children && !parentModule.children.includes(cached)) { parentModule.children.push(cached); } @@ -1060,69 +4862,87 @@ function loadModule(resolvedFilename, source, parentModule) { } let mod; - if ((!parentModule || parentModule === mainModule || parentModule.filename === '/') && typeof mainModule !== 'undefined' && mainModule.filename === '/') { + if (isMainModuleLoad) { mod = mainModule; mod.id = '.'; - mod.filename = resolvedFilename; - mod.path = pathModule.dirname(resolvedFilename); + mod.filename = filename; + mod.path = pathModule.dirname(filename); mod.exports = {}; mod.loaded = false; mod.parent = null; mod.children = []; - mod.paths = _nodeModulePaths(pathModule.dirname(resolvedFilename)); + mod.paths = _nodeModulePaths(pathModule.dirname(filename)); + mod._compile = makeModuleCompile(mod); + mod.require = makeModuleRequire(mod); if (globalThis.process) { globalThis.process.mainModule = mod; } } else { mod = { - id: resolvedFilename, - filename: resolvedFilename, - path: pathModule.dirname(resolvedFilename), + id: filename, + filename: filename, + path: pathModule.dirname(filename), exports: {}, loaded: false, parent: parentModule || null, children: [], - paths: _nodeModulePaths(pathModule.dirname(resolvedFilename)), + paths: _nodeModulePaths(pathModule.dirname(filename)), }; + mod._compile = makeModuleCompile(mod); + mod.require = makeModuleRequire(mod); } // Cache before executing (handles circular dependencies) - moduleCache[resolvedFilename] = mod; + moduleCache[filename] = mod; + registerSourceMapForCjs(filename, source, mod); if (parentModule && parentModule.children) { parentModule.children.push(mod); } // Check for custom extension handler - const ext = findLongestRegisteredExtension(resolvedFilename); + const ext = findLongestRegisteredExtension(filename); const handler = requireExtensions[ext]; if (handler && !_defaultExtHandlers.has(handler)) { try { - handler(mod, resolvedFilename); + handler(mod, filename); } catch (err) { - delete moduleCache[resolvedFilename]; + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); throw err; } - } else if (resolvedFilename.endsWith('.node')) { - delete moduleCache[resolvedFilename]; - throw new Error("Native .node modules are not supported in WASM: '" + resolvedFilename + "'"); - } else if (resolvedFilename.endsWith('.json')) { + } else if (filename.endsWith('.node')) { + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); + const err = new Error("Native .node modules are not supported in WASM: '" + filename + "'"); + err.code = 'ERR_DLOPEN_FAILED'; + throw err; + } else if (filename.endsWith('.json')) { try { + if (source.length > 0 && source.charCodeAt(0) === 0xFEFF) { + source = source.slice(1); + } mod.exports = JSON.parse(source); } catch (e) { - delete moduleCache[resolvedFilename]; - const err = new SyntaxError(resolvedFilename + ': ' + e.message); + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); + const err = new SyntaxError(filename + ': ' + e.message); err.code = 'ERR_INVALID_JSON'; throw err; } } else { - const isEsm = resolvedFilename.endsWith('.mjs') || - (resolvedFilename.endsWith('.js') && getPackageScopeType(resolvedFilename) === 'module'); + const packageScope = filename.endsWith('.js') ? getPackageScopeInfo(filename) : null; + const explicitPackageType = packageScope ? packageScope.packageType : null; + const isCommonJsPackage = explicitPackageType === 'commonjs' || + (explicitPackageType === null && packageScope !== null && packageScope.isNodeModulesPackage); + const isEsm = filename.endsWith('.mjs') || + (filename.endsWith('.js') && explicitPackageType === 'module'); if (isEsm && hasExecArgvFlag('--no-experimental-require-module')) { - delete moduleCache[resolvedFilename]; + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); const esmErr = new Error( - "require() of ES Module " + resolvedFilename + " not supported. " + - "Instead change the require of " + resolvedFilename + " to a dynamic " + + "require() of ES Module " + filename + " not supported. " + + "Instead change the require of " + filename + " to a dynamic " + "import() which is available in all CommonJS modules." ); esmErr.code = 'ERR_REQUIRE_ESM'; @@ -1130,18 +4950,22 @@ function loadModule(resolvedFilename, source, parentModule) { } if (isEsm) { try { - mod.exports = wrapEsmNamespace(_requireEsm(resolvedFilename)); + mod.exports = requireEsmWithCacheGuard(mod, filename); } catch (err) { - delete moduleCache[resolvedFilename]; + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); throw err; } } else { - const dirname = pathModule.dirname(resolvedFilename); + const dirname = pathModule.dirname(filename); const childRequire = makeRequire(dirname, mod); let compiledFn; let cjsSyntaxError = null; + const canFallbackToEsm = !filename.endsWith('.cjs') && !isCommonJsPackage; + let cjsWrapperLexicalRedeclaration = false; + let cjsSourceLooksEsm = false; try { - compiledFn = compileCjs(resolvedFilename, source); + compiledFn = compileCjs(filename, source); } catch (err) { // Normalize QuickJS SyntaxError messages for ESM keywords in CJS context if (err && err.name === 'SyntaxError') { @@ -1150,41 +4974,54 @@ function loadModule(resolvedFilename, source, parentModule) { markAsSyntaxError(err); } // For .js files (not .cjs), detect ESM syntax and fall back to ESM loading - if (!resolvedFilename.endsWith('.cjs') && err && err.name === 'SyntaxError') { + if (canFallbackToEsm && err && err.name === 'SyntaxError') { + cjsSourceLooksEsm = looksLikeEsmSource(source); + cjsWrapperLexicalRedeclaration = hasCjsWrapperLexicalRedeclaration(source); + } + if (canFallbackToEsm && err && err.name === 'SyntaxError' && (cjsSourceLooksEsm || cjsWrapperLexicalRedeclaration)) { cjsSyntaxError = err; } else { - delete moduleCache[resolvedFilename]; - maybeSetArrowMessageOnSyntaxError(err, resolvedFilename, source); + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); + maybeSetArrowMessageOnSyntaxError(err, filename, source); throw err; } } - if (cjsSyntaxError) { + if (cjsSyntaxError || cjsWrapperLexicalRedeclaration) { + if (hasExecArgvFlag('--no-experimental-require-module') && cjsSyntaxError) { + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); + maybeSetArrowMessageOnSyntaxError(cjsSyntaxError, filename, source); + throw cjsSyntaxError; + } // SyntaxError in a .js file — try loading as ESM (entry point detection) try { - mod.exports = wrapEsmNamespace(_requireEsm(resolvedFilename)); + mod.exports = requireEsmWithCacheGuard(mod, filename, true); } catch (esmErr) { - delete moduleCache[resolvedFilename]; - if (looksLikeEsmSource(source)) { + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); + if (cjsSourceLooksEsm || cjsWrapperLexicalRedeclaration) { normalizeEsmSyntaxError(esmErr); throw esmErr; } // ESM loading also failed — throw the original CJS SyntaxError - maybeSetArrowMessageOnSyntaxError(cjsSyntaxError, resolvedFilename, source); + maybeSetArrowMessageOnSyntaxError(cjsSyntaxError, filename, source); throw cjsSyntaxError; } } else if (compiledFn) { const previousModuleContext = globalThis.__wasm_rquickjs_current_module; globalThis.__wasm_rquickjs_current_module = { - filename: resolvedFilename, + filename: filename, source: source }; const previousCjsImportDir = globalThis.__wasm_rquickjs_cjs_import_dir; globalThis.__wasm_rquickjs_cjs_import_dir = dirname; try { - compiledFn(mod.exports, childRequire, mod, resolvedFilename, dirname); + compiledFn.call(mod.exports, mod.exports, childRequire, mod, filename, dirname); } catch (err) { - delete moduleCache[resolvedFilename]; - maybeSetArrowMessageOnSyntaxError(err, resolvedFilename, source); + delete moduleCache[filename]; + unlinkModuleFromParent(parentModule, mod); + maybeSetArrowMessageOnSyntaxError(err, filename, source); throw err; } finally { globalThis.__wasm_rquickjs_current_module = previousModuleContext; @@ -1200,6 +5037,94 @@ function loadModule(resolvedFilename, source, parentModule) { return mod; } +function makeLoaderCommonJsRequire(parentUrl, parentDir, parentModule, parentFilename) { + const fallbackRequire = makeRequire(parentDir, parentModule, parentFilename); + function loaderRequire(id) { + if (typeof id !== 'string') { + throw new ERR_INVALID_ARG_TYPE('id', 'string', id); + } + if (id === '') { + const argErr = new TypeError("The argument 'id' must be a non-empty string. Received ''"); + argErr.code = 'ERR_INVALID_ARG_VALUE'; + throw argErr; + } + if (typeof globalThis.__wasm_rquickjs_run_registered_loaders_sync === 'function') { + const loaded = globalThis.__wasm_rquickjs_run_registered_loaders_sync(parentUrl, id); + if (loaded) { + if (loaded.format === 'builtin' && loaded.url) { + const id = String(loaded.url).startsWith('node:') ? String(loaded.url) : 'node:' + String(loaded.url); + const builtin = builtinModuleMap[id]; + if (builtin !== undefined) return builtin; + } + if (loaded.format === 'commonjs' && loaded.source !== undefined) { + const filename = loaderCommonJsFilename(loaded.url); + return loadCommonJsSourceModule(filename, loaderSourceToString(loaded.source), loaded.url, loaderCommonJsCacheKey(loaded.url, filename)).exports; + } + if (loaded.format === 'json' && loaded.source !== undefined) { + return JSON.parse(loaderSourceToString(loaded.source)); + } + } + } + return fallbackRequire(id); + } + loaderRequire.resolve = function resolve(id, options) { + if (typeof id !== 'string') { + throw new ERR_INVALID_ARG_TYPE('request', 'string', id); + } + if (typeof globalThis.__wasm_rquickjs_run_registered_loaders_sync === 'function') { + const loaded = globalThis.__wasm_rquickjs_run_registered_loaders_sync(parentUrl, id, true); + if (loaded && loaded.url) { + if (String(loaded.url).startsWith('node:')) return String(loaded.url).slice(5); + return String(loaded.url).startsWith('file://') ? nodeUrl.fileURLToPath(String(loaded.url)) : String(loaded.url); + } + } + return fallbackRequire.resolve(id, options); + }; + loaderRequire.main = fallbackRequire.main; + return loaderRequire; +} + +function loadCommonJsSourceModule(filename, source, sourceUrl, cacheKey) { + cacheKey = cacheKey || filename; + if (moduleCache[cacheKey]) return moduleCache[cacheKey]; + const dirname = pathModule.isAbsolute(filename) ? pathModule.dirname(filename) : '.'; + const mod = { + id: filename, + filename: filename, + path: dirname, + exports: {}, + loaded: false, + parent: null, + children: [], + paths: _nodeModulePaths(pathModule.isAbsolute(filename) ? dirname : '/'), + }; + mod._compile = makeModuleCompile(mod); + mod.require = makeModuleRequire(mod); + moduleCache[cacheKey] = mod; + registerSourceMapForCjs(filename, source, mod); + try { + const loaderRequire = makeLoaderCommonJsRequire(sourceUrl || (pathModule.isAbsolute(filename) ? fileUrlForPath(filename) : filename), pathModule.isAbsolute(filename) ? dirname : '/', mod, filename); + mod.require = loaderRequire; + compileModuleInto(mod, source, filename, loaderRequire); + mod.loaded = true; + return mod; + } catch (err) { + delete moduleCache[cacheKey]; + throw err; + } +} + +if (typeof globalThis.__wasm_rquickjs_load_commonjs_loader_source !== 'function') { + Object.defineProperty(globalThis, '__wasm_rquickjs_load_commonjs_loader_source', { + value(filename, source) { + const sourceUrl = arguments.length > 2 ? String(arguments[2]) : undefined; + return loadCommonJsSourceModule(String(filename), loaderSourceToString(source), sourceUrl, arguments.length > 3 ? String(arguments[3]) : undefined).exports; + }, + writable: true, + configurable: true, + }); +} + // The root "main" module const mainModule = { id: '.', @@ -1210,6 +5135,8 @@ const mainModule = { parent: null, children: [], }; +mainModule._compile = makeModuleCompile(mainModule); +mainModule.require = makeModuleRequire(mainModule); function splitPackageName(id) { // Scoped packages: @scope/pkg or @scope/pkg/subpath @@ -1226,90 +5153,129 @@ function splitPackageName(id) { return { name: id.substring(0, idx), subpath: id.substring(idx + 1) }; } -function resolveFromNodeModules(id, parentDir, parentFilename) { - const dirs = _nodeModulePaths(parentDir); +function resolveFromNodeModules(id, parentDir, parentFilename, conditions, lookupPaths) { + conditions = conditions || cjsPackageConditions(); + const dirs = Array.isArray(lookupPaths) ? lookupPaths : _nodeModulePaths(parentDir); // Split into package name and subpath for packages with subpath specifiers const parts = splitPackageName(id); - const hasSubpath = parts.subpath.length > 0; + + const selfResolved = resolvePackageSelfReference(parts, parentDir, conditions); + if (selfResolved !== undefined) { + return selfResolved; + } for (let i = 0; i < dirs.length; i++) { - // If there's a subpath, try resolving it relative to the package directory - if (hasSubpath) { - const pkgDir = pathModule.join(dirs[i], parts.name); - const subCandidate = pathModule.join(pkgDir, parts.subpath); - // Try exact subpath - let content = tryReadFile(subCandidate); - if (content !== null) return { filename: subCandidate, content: content }; - // Try with extensions - content = tryReadFile(subCandidate + '.js'); - if (content !== null) return { filename: subCandidate + '.js', content: content }; - content = tryReadFile(subCandidate + '.mjs'); - if (content !== null) return { filename: subCandidate + '.mjs', content: content }; - content = tryReadFile(subCandidate + '.json'); - if (content !== null) return { filename: subCandidate + '.json', content: content }; - // Try as directory - content = tryReadFile(pathModule.join(subCandidate, 'index.js')); - if (content !== null) return { filename: pathModule.join(subCandidate, 'index.js'), content: content }; - content = tryReadFile(pathModule.join(subCandidate, 'index.json')); - if (content !== null) return { filename: pathModule.join(subCandidate, 'index.json'), content: content }; - } - - const candidate = pathModule.join(dirs[i], id); - - // Try as directory: check package.json "main" field - const pkgJsonPath = pathModule.join(candidate, 'package.json'); - const pkgJson = tryReadFile(pkgJsonPath); - if (pkgJson !== null) { - try { - const pkg = JSON.parse(pkgJson); - if (Object.prototype.hasOwnProperty.call(pkg, 'main') && typeof pkg.main === 'string') { - const mainPath = pathModule.resolve(candidate, pkg.main); - const mainCandidates = [ - mainPath, - mainPath + '.js', - mainPath + '.json', - pathModule.join(mainPath, 'index.js'), - pathModule.join(mainPath, 'index.json'), - ]; - for (let m = 0; m < mainCandidates.length; m++) { - const content = tryReadFile(mainCandidates[m]); - if (content !== null) return { filename: mainCandidates[m], content: content }; - } + const pkgDir = pathModule.join(dirs[i], parts.name); + const pkgJsonPath = pathModule.join(pkgDir, 'package.json'); + let pkg = null; + + try { + const packageEntry = readPackageDirectoryForExports(parts, pkgDir, pkgJsonPath, conditions); + if (packageEntry !== null) { + pkg = packageEntry.pkg; + if (packageEntry.exportsResolved !== undefined) { + return packageEntry.exportsResolved; } - } catch (e) { - const fromPart = parentFilename || parentDir; - const pkgErr = new Error( - 'Invalid package config ' + pkgJsonPath + - ' while importing "' + id + '" from ' + fromPart + '.' + - (e.message ? ' ' + e.message : '') - ); - pkgErr.code = 'ERR_INVALID_PACKAGE_CONFIG'; - throw pkgErr; } + } catch (e) { + if (e && e.code) { + throw e; + } + throw makeInvalidPackageConfigWhileImporting(pkgJsonPath, id, parentFilename || parentDir, e); } - // Try as directory: index.js / index.json - const indexJs = pathModule.join(candidate, 'index.js'); - let content = tryReadFile(indexJs); - if (content !== null) return { filename: indexJs, content: content }; - - const indexJson = pathModule.join(candidate, 'index.json'); - content = tryReadFile(indexJson); - if (content !== null) return { filename: indexJson, content: content }; + const fallbackResolved = resolveCjsPackageFallbacks(parts, pkgDir, pkg, pkgJsonPath, id, parentFilename || parentDir); + if (fallbackResolved !== null) return fallbackResolved; - // Try as file with extension - content = tryReadFile(candidate + '.js'); - if (content !== null) return { filename: candidate + '.js', content: content }; - - content = tryReadFile(candidate + '.json'); - if (content !== null) return { filename: candidate + '.json', content: content }; } return null; } +function resolveForRequire(id, options, parentDir, parentFilename, parentLookupPaths) { + if (typeof id !== 'string') { + throw new ERR_INVALID_ARG_TYPE('request', 'string', id); + } + if (isBuiltin(id)) { + return id; + } + if (id.startsWith('node:')) { + const err = new Error("Cannot find module '" + id + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + // If paths option is provided, resolve relative to each path + if (options && options.paths !== undefined) { + const searchPaths = options.paths; + if (!Array.isArray(searchPaths)) { + const argErr = new TypeError("The argument 'paths' must be an array of strings. Received " + typeof searchPaths); + argErr.code = 'ERR_INVALID_ARG_VALUE'; + throw argErr; + } + const isRelative = id === '.' || id === '..' || id.startsWith('./') || id.startsWith('../') || id.startsWith('/'); + for (let pi = 0; pi < searchPaths.length; pi++) { + if (typeof searchPaths[pi] !== 'string') { + const argErr = new TypeError("The argument 'paths[" + pi + "]' must be a string. Received " + typeof searchPaths[pi]); + argErr.code = 'ERR_INVALID_ARG_VALUE'; + throw argErr; + } + const searchDir = pathModule.resolve(searchPaths[pi]); + if (isRelative) { + // Relative/absolute: resolve directly against the search path + try { + const resolved = resolveFilename(id, searchDir); + return toCjsCanonicalFilename(resolved.filename, false); + } catch (e) { + addRequireStackToModuleNotFound(e, id, parentFilename); + // Try next path + } + } else { + // Bare specifier: use node_modules resolution from search path + const nmResolved = resolveFromNodeModules(id, searchDir, parentFilename); + if (nmResolved) return toCjsCanonicalFilename(nmResolved.filename, false); + } + } + const err = new Error("Cannot find module '" + id + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw addRequireStackToModuleNotFound(err, id, parentFilename); + } + if (id === '.' || id === '..' || id.startsWith('./') || id.startsWith('../') || id.startsWith('/')) { + try { + const resolved = resolveFilename(id, parentDir); + return toCjsCanonicalFilename(resolved.filename, false); + } catch (err) { + throw addRequireStackToModuleNotFound(err, id, parentFilename); + } + } + if (id.startsWith('#')) { + try { + const importsResolved = resolvePackageImports(id, parentDir, cjsPackageConditions()); + if (importsResolved.builtin) return importsResolved.builtin; + return toCjsCanonicalFilename(importsResolved.filename, false); + } catch (err) { + if (!err || err.code !== 'ERR_PACKAGE_IMPORT_NOT_DEFINED') { + throw err; + } + const nmResolved = resolveFromNodeModules(id, parentDir, parentFilename, undefined, parentLookupPaths); + if (nmResolved) return toCjsCanonicalFilename(nmResolved.filename, false); + throw err; + } + } + // node_modules resolution for bare specifiers + const nmResolved = resolveFromNodeModules(id, parentDir, parentFilename, undefined, parentLookupPaths); + if (nmResolved) { + return toCjsCanonicalFilename(nmResolved.filename, false); + } + const err = new Error("Cannot find module '" + id + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; +} + function makeRequire(parentDir, parentModule, parentFilenameOverride) { const parentFilename = parentFilenameOverride || (parentModule && parentModule.filename) || null; + const parentLookupPaths = parentModule && Array.isArray(parentModule.paths) + ? parentModule.paths.concat(globalPaths) + : null; function localRequire(id) { if (typeof id !== 'string') { throw new ERR_INVALID_ARG_TYPE('id', 'string', id); @@ -1320,6 +5286,7 @@ function makeRequire(parentDir, parentModule, parentFilenameOverride) { throw argErr; } + return traceModuleRequire(id, parentFilename, () => { // Capture buffer.kMaxLength for zlib on first require (matches Node.js CJS capture-at-require semantics) if ((id === 'zlib' || id === 'node:zlib') && zlib._captureKMaxLength) { zlib._captureKMaxLength(); @@ -1365,84 +5332,53 @@ function makeRequire(parentDir, parentModule, parentFilenameOverride) { // Relative or absolute file paths if (id === '.' || id === '..' || id.startsWith('./') || id.startsWith('../') || id.startsWith('/')) { - const resolved = resolveFilename(id, parentDir); + let resolved; + try { + resolved = resolveFilename(id, parentDir); + } catch (err) { + throw addRequireStackToModuleNotFound(err, id, parentFilename); + } const mod = loadModule(resolved.filename, resolved.content, parentModule || null); return mod.exports; } - // node_modules resolution for bare specifiers - const nmResolved = resolveFromNodeModules(id, parentDir, parentFilename); - if (nmResolved) { - const mod = loadModule(nmResolved.filename, nmResolved.content, parentModule || null); - return mod.exports; - } - - const err = new Error("Cannot find module '" + id + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; - } - - localRequire.cache = moduleCache; - localRequire.extensions = requireExtensions; - - localRequire.resolve = function resolve(id, options) { - if (typeof id !== 'string') { - throw new ERR_INVALID_ARG_TYPE('request', 'string', id); - } - if (isBuiltin(id)) { - return id; - } - if (id.startsWith('node:')) { - const err = new Error("Cannot find module '" + id + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; - } - // If paths option is provided, resolve relative to each path - if (options && options.paths !== undefined) { - const searchPaths = options.paths; - if (!Array.isArray(searchPaths)) { - const argErr = new TypeError("The argument 'paths' must be an array of strings. Received " + typeof searchPaths); - argErr.code = 'ERR_INVALID_ARG_VALUE'; - throw argErr; - } - const isRelative = id === '.' || id === '..' || id.startsWith('./') || id.startsWith('../') || id.startsWith('/'); - for (let pi = 0; pi < searchPaths.length; pi++) { - if (typeof searchPaths[pi] !== 'string') { - const argErr = new TypeError("The argument 'paths[" + pi + "]' must be a string. Received " + typeof searchPaths[pi]); - argErr.code = 'ERR_INVALID_ARG_VALUE'; - throw argErr; + if (id.startsWith('#')) { + try { + const importsResolved = resolvePackageImports(id, parentDir, cjsPackageConditions()); + if (importsResolved.builtin) return builtinModuleMap[importsResolved.builtin]; + const mod = loadModule(importsResolved.filename, importsResolved.content, parentModule || null); + return mod.exports; + } catch (err) { + if (!err || err.code !== 'ERR_PACKAGE_IMPORT_NOT_DEFINED') { + throw err; } - const searchDir = pathModule.resolve(searchPaths[pi]); - if (isRelative) { - // Relative/absolute: resolve directly against the search path - try { - const resolved = resolveFilename(id, searchDir); - return resolved.filename; - } catch (e) { - // Try next path - } - } else { - // Bare specifier: use node_modules resolution from search path - const nmResolved = resolveFromNodeModules(id, searchDir, parentFilename); - if (nmResolved) return nmResolved.filename; + const nmResolved = resolveFromNodeModules(id, parentDir, parentFilename, undefined, parentLookupPaths); + if (nmResolved) { + const mod = loadModule(nmResolved.filename, nmResolved.content, parentModule || null); + return mod.exports; } + throw err; } - const err = new Error("Cannot find module '" + id + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; - } - if (id === '.' || id === '..' || id.startsWith('./') || id.startsWith('../') || id.startsWith('/')) { - const resolved = resolveFilename(id, parentDir); - return resolved.filename; } + // node_modules resolution for bare specifiers - const nmResolved = resolveFromNodeModules(id, parentDir, parentFilename); + const nmResolved = resolveFromNodeModules(id, parentDir, parentFilename, undefined, parentLookupPaths); if (nmResolved) { - return nmResolved.filename; + const mod = loadModule(nmResolved.filename, nmResolved.content, parentModule || null); + return mod.exports; } + const err = new Error("Cannot find module '" + id + "'"); err.code = 'MODULE_NOT_FOUND'; throw err; + }); + } + + localRequire.cache = moduleCache; + localRequire.extensions = requireExtensions; + + localRequire.resolve = function resolve(id, options) { + return resolveForRequire(id, options, parentDir, parentFilename, parentLookupPaths); }; localRequire.resolve.paths = function paths(request) { @@ -1468,11 +5404,11 @@ function makeRequire(parentDir, parentModule, parentFilenameOverride) { // The global require, rooted at '/' const globalRequire = makeRequire('/', mainModule); -export function require(id) { +export let require = function require(id) { return globalRequire(id); -} +}; -export function createRequire(filename) { +export let createRequire = function createRequire(filename) { let filepath; const isUrlObj = filename instanceof URL || (filename !== null && typeof filename === 'object' && @@ -1512,12 +5448,746 @@ export function createRequire(filename) { paths: _nodeModulePaths(dir), }; return makeRequire(dir, syntheticParent, filepath); +}; + +Object.defineProperty(globalThis, '__wasm_rquickjs_create_require', { + value: createRequire, + configurable: true, +}); + +function isUrlInstance(value) { + return value instanceof URL || + (value !== null && typeof value === 'object' && + typeof value.href === 'string' && typeof value.protocol === 'string'); +} + +function normalizeFindPackageJsonSpecifier(specifier) { + if (specifier === undefined) { + throw new ERR_MISSING_ARGS('specifier'); + } + + if (isUrlInstance(specifier)) { + const filePath = nodeUrl.fileURLToPath(specifier); + return { + kind: 'absolute', + path: filePath, + source: filePath, + }; + } + + if (typeof specifier !== 'string') { + throw new ERR_INVALID_ARG_TYPE('specifier', ['string', 'URL'], specifier); + } + + if (specifier.startsWith('file://')) { + const filePath = nodeUrl.fileURLToPath(specifier); + return { + kind: 'absolute', + path: filePath, + source: specifier, + }; + } + + if (pathModule.isAbsolute(specifier)) { + return { + kind: 'absolute', + path: pathModule.normalize(specifier), + source: specifier, + }; + } + + if (specifier === '.' || specifier === '..' || specifier.startsWith('./') || specifier.startsWith('../')) { + return { + kind: 'relative', + value: specifier, + }; + } + + return { + kind: 'bare', + value: specifier, + }; +} + +function normalizeFindPackageJsonBase(base, baseRequired) { + if (base === undefined) { + if (baseRequired) { + throw new ERR_INVALID_ARG_TYPE('base', ['string', 'URL'], base); + } + return null; + } + + if (isUrlInstance(base) || (typeof base === 'string' && base.startsWith('file://'))) { + const filename = nodeUrl.fileURLToPath(base); + return { + filename, + dir: pathModule.dirname(pathModule.resolve(filename)), + }; + } + + if (typeof base !== 'string') { + throw new ERR_INVALID_ARG_TYPE('base', ['string', 'URL'], base); + } + + if (!pathModule.isAbsolute(base)) { + throw new ERR_INVALID_ARG_TYPE('base', ['string', 'URL'], base); + } + + const filename = pathModule.resolve(base); + return { + filename, + dir: pathModule.dirname(filename), + }; +} + +function findNearestPackageJsonPath(startDir) { + let dir = pathModule.resolve(startDir || '/'); + while (true) { + if (pathModule.basename(dir) === 'node_modules') return undefined; + const pkgJsonPath = pathModule.join(dir, 'package.json'); + if (tryReadFile(pkgJsonPath) !== null) { + return pathModule.toNamespacedPath(pkgJsonPath); + } + const parent = pathModule.dirname(dir); + if (parent === dir) return undefined; + dir = parent; + } +} + +function packageSearchStartDir(resolvedPath, sourceSpecifier) { + if (typeof sourceSpecifier === 'string' && + (/\/$/.test(sourceSpecifier) || /(?:^|\/)\.\.?$/.test(sourceSpecifier))) { + return pathModule.resolve(resolvedPath); + } + + if (_stat(resolvedPath) === 1) { + return pathModule.resolve(resolvedPath); + } + + return pathModule.dirname(pathModule.resolve(resolvedPath)); } -export { builtinModuleNames as builtinModules }; +function findBarePackageJson(specifier, parentDir, parentFilename) { + const resolved = resolveFromNodeModules(specifier, parentDir, parentFilename, cjsPackageConditions()); + if (resolved === null) return undefined; -export function isBuiltinModule(id) { + if (typeof resolved.packageDir === 'string' && resolved.packageDir.length > 0) { + const pkgJsonPath = pathModule.join(resolved.packageDir, 'package.json'); + if (tryReadFile(pkgJsonPath) !== null) { + return pathModule.toNamespacedPath(pkgJsonPath); + } + } + + return undefined; +} + +export let findPackageJSON = function findPackageJSON(specifier, base) { + const normalizedSpecifier = normalizeFindPackageJsonSpecifier(specifier); + if (normalizedSpecifier.kind === 'absolute') { + const startDir = packageSearchStartDir(normalizedSpecifier.path, normalizedSpecifier.source); + return findNearestPackageJsonPath(startDir); + } + + const normalizedBase = normalizeFindPackageJsonBase(base, true); + if (normalizedSpecifier.kind === 'relative') { + const resolvedPath = pathModule.resolve(normalizedBase.dir, normalizedSpecifier.value); + const startDir = packageSearchStartDir(resolvedPath, normalizedSpecifier.value); + return findNearestPackageJsonPath(startDir); + } + + return findBarePackageJson(normalizedSpecifier.value, normalizedBase.dir, normalizedBase.filename); +}; + +export let builtinModules = builtinModuleNames; + +export let isBuiltinModule = function isBuiltinModule(id) { return isBuiltin(id); +}; + +export let register = function register(specifier, parentURL, options) { + const url = String(specifier); + let parent = parentURL; + let data; + if (parentURL && typeof parentURL === 'object' && !isUrlInstance(parentURL)) { + parent = parentURL.parentURL; + data = parentURL.data; + } else if (options && typeof options === 'object') { + data = options.data; + } + parent = parent === undefined ? undefined : String(parent); + const loaders = globalThis.__wasm_rquickjs_registered_loaders || + (globalThis.__wasm_rquickjs_registered_loaders = []); + const realm = globalThis.__wasm_rquickjs_registered_loader_realm_counter = + (globalThis.__wasm_rquickjs_registered_loader_realm_counter || 0) + 1; + const loader = { url, parent, data, realm, module: undefined, initialized: false, initializing: undefined }; + loaders.push(loader); + if (typeof globalThis.__wasm_rquickjs_start_registered_loader === 'function') { + globalThis.__wasm_rquickjs_start_registered_loader(loader); + } +}; + +if (typeof globalThis.__wasm_rquickjs_run_registered_loaders !== 'function') { + function normalizeLoaderResolvedUrl(url) { + if (url.startsWith('/')) { + const query = url.indexOf('?'); + const hash = url.indexOf('#'); + const end = query < 0 ? hash : (hash < 0 ? query : Math.min(query, hash)); + if (end >= 0) { + url = nodeUrl.pathToFileURL(url.slice(0, end)).href + url.slice(end); + } else { + url = nodeUrl.pathToFileURL(url).href; + } + } + return url; + } + + function resolveRegisteredLoaderUrl(loader) { + const url = loader.parent !== undefined + ? normalizeLoaderResolvedUrl(globalThis.__wasm_rquickjs_import_meta_resolve(loader.parent, loader.url)) + : loader.url; + return loaderRealmUrl(url, loader.realm); + } + + function loaderRealmUrl(url, realm) { + if (!url.startsWith('file://')) return url; + const hashIndex = url.indexOf('#'); + const beforeHash = hashIndex < 0 ? url : url.slice(0, hashIndex); + const hash = hashIndex < 0 ? '' : url.slice(hashIndex); + const separator = beforeHash.includes('?') ? '&' : '?'; + return beforeHash + separator + '__wasm_rquickjs_loader_realm=' + encodeURIComponent(String(realm)) + hash; + } + + globalThis.__wasm_rquickjs_start_registered_loader = function startRegisteredLoader(loader) { + if (loader.initialized || loader.initializing) return loader.initializing; + loader.initializing = (async () => { + const module = await import(resolveRegisteredLoaderUrl(loader)); + loader.module = module; + if (typeof module.initialize === 'function') { + await module.initialize(loader.data); + } + loader.initialized = true; + })(); + loader.initializing.catch(() => {}); + return loader.initializing; + }; + + function resolveEsmDefaultForLoader(specifier, parentURL, context, baseUrl, missingAsUndefined, allowRootedWithoutFileParent) { + if (specifier.startsWith('node:') || specifier.startsWith('data:')) { + return { url: specifier }; + } + if (specifier.startsWith('file://')) { + return resultForEsmFileUrl(new URL(specifier)); + } + const parentFilename = parentFilenameForLoaderResolve(parentURL, baseUrl); + if (specifier.startsWith('/')) { + if (parentFilename === null && !allowRootedWithoutFileParent) { + if (missingAsUndefined) return undefined; + throw makeEsmModuleNotFoundError(specifier); + } + return resultForEsmFileUrl(new URL(normalizeLoaderResolvedUrl(specifier))); + } + + if (publicBuiltinWithoutSchemeSet.has(specifier)) { + return { url: 'node:' + specifier }; + } + if (parentFilename !== null && isRelativeOrAbsoluteSpecifier(specifier)) { + return resultForRelativeOrAbsoluteSpecifier(specifier, parentURL); + } + + if (parentFilename !== null && specifier.startsWith('#')) { + const resolved = resolvePackageImports(specifier, pathModule.dirname(parentFilename), conditionsForLoaderResolve(context)); + if (resolved && resolved.builtin) return { url: resolved.builtin }; + if (resolved && resolved.filename) { + return { url: nodeUrl.pathToFileURL(resolved.filename).href, format: resolved.filename.endsWith('.json') ? 'json' : undefined }; + } + } + + if (parentFilename !== null) { + const resolved = resolveEsmPackageForLoader(specifier, pathModule.dirname(parentFilename), parentFilename, conditionsForLoaderResolve(context)); + if (resolved) return resolved; + if (missingAsUndefined) return undefined; + throw makeEsmModuleNotFoundError(specifier); + } + if (missingAsUndefined) return undefined; + + let url = globalThis.__wasm_rquickjs_import_meta_resolve(parentURL, specifier); + return { url: normalizeLoaderResolvedUrl(url) }; + } + + globalThis.__wasm_rquickjs_run_registered_loaders = async function runRegisteredLoaders(baseUrl, specifier, attrs, mode) { + const loaders = globalThis.__wasm_rquickjs_registered_loaders; + if (!loaders || loaders.length === 0) return undefined; + + const modules = []; + const moduleUrls = []; + for (let i = 0; i < loaders.length; i++) { + const loader = loaders[i]; + try { + await globalThis.__wasm_rquickjs_start_registered_loader(loader); + } catch (e) { + loader.initializing = undefined; + throw e; + } + if (loader.module) { + modules.push(loader.module); + moduleUrls.push(loader.url); + } + } + + const importAttributes = attrs && attrs.typeValue !== undefined + ? { type: attrs.typeValue } + : {}; + + const baseContext = { + conditions: loaderHookConditions(), + importAttributes, + parentURL: String(baseUrl), + }; + + const defaultResolve = async (nextSpecifier, context) => { + const specifierString = String(nextSpecifier); + const parentURL = context && context.parentURL ? String(context.parentURL) : String(baseUrl); + return resolveEsmDefaultForLoader(specifierString, parentURL, context, baseUrl, false, true); + }; + + const runResolve = async (index, nextSpecifier, context) => { + if (index < 0) return defaultResolve(nextSpecifier, context); + const module = modules[index]; + if (typeof module.resolve === 'function') { + let nextCalled = false; + const nextResolve = async (specifierForNext, contextForNext) => { + nextCalled = true; + return runResolve( + index - 1, + specifierForNext === undefined ? nextSpecifier : specifierForNext, + contextForNext === undefined ? context : Object.assign({}, context, contextForNext), + ); + }; + const result = validateRegisteredLoaderResult(await module.resolve(nextSpecifier, context, nextResolve), 'resolve', context); + validateRegisteredLoaderResolveUrl(result.url, moduleUrls[index]); + if (!nextCalled && (!result || result.shortCircuit !== true)) { + throw makeLoaderChainError('resolve'); + } + return result; + } + return runResolve(index - 1, nextSpecifier, context); + }; + + const resolved = await runResolve(modules.length - 1, specifier, baseContext); + if (!resolved || typeof resolved !== 'object' || resolved.url === undefined) return undefined; + resolved.url = normalizeLoaderResolvedUrl(String(resolved.url)); + const resolvedFormat = resolved.format === undefined || resolved.format === null ? undefined : String(resolved.format); + + const defaultLoad = async (_nextUrl, context) => ({ format: context && context.format }); + + const runLoad = async (index, nextUrl, context) => { + if (index < 0) return defaultLoad(nextUrl, context); + const module = modules[index]; + if (typeof module.load === 'function') { + let nextCalled = false; + const nextLoad = async (urlForNext, contextForNext) => { + nextCalled = true; + return runLoad( + index - 1, + urlForNext === undefined ? nextUrl : String(urlForNext), + contextForNext === undefined ? context : Object.assign({}, context, contextForNext), + ); + }; + const result = validateRegisteredLoaderResult(await module.load(nextUrl, context, nextLoad), 'load', context); + if (result.format !== undefined && result.format !== null && result.format !== '') { + validateRegisteredLoaderLoadFormat(result.format); + } + if (!nextCalled && (!result || result.shortCircuit !== true)) { + throw makeLoaderChainError('load'); + } + return result; + } + return runLoad(index - 1, nextUrl, context); + }; + + const loadContext = { + conditions: baseContext.conditions, + importAttributes: resolved.importAttributes && typeof resolved.importAttributes === 'object' + ? resolved.importAttributes + : baseContext.importAttributes, + format: resolvedFormat, + }; + const loaded = await runLoad(modules.length - 1, resolved.url, loadContext); + const loadedHasSource = loaded && Object.prototype.hasOwnProperty.call(loaded, 'source') && loaded.source !== null && loaded.source !== undefined; + const loadedFormat = loaded && loaded.format !== undefined && loaded.format !== null + ? validateRegisteredLoaderLoadFormat(loaded.format) + : validateRegisteredLoaderLoadFormat(resolvedFormat); + if (mode === 'static-raw') { + const raw = { url: resolved.url, format: loadedFormat }; + if (loadedHasSource) raw.source = loaded.source; + return raw; + } + + if (loadedHasSource && loadedFormat === 'module') { + return 'data:text/javascript,' + encodeURIComponent(loaderSourceToString(loaded.source)); + } + if (!loadedHasSource && loadedFormat === 'module') { + if (String(resolved.url).startsWith('file://')) { + try { + if (nodeUrl.fileURLToPath(resolved.url).endsWith('.mjs')) return resolved.url; + } catch (_) {} + } + const fileSource = loaderFileUrlSource(resolved.url); + if (fileSource !== null) { + return 'data:text/javascript,' + encodeURIComponent(fileSource); + } + } + if (loadedHasSource && loadedFormat === 'commonjs') { + return loaderCommonJsSourceModule(loaded.source, resolved.url); + } + if (!loadedHasSource && loadedFormat === 'commonjs') { + const fileSource = loaderFileUrlSource(resolved.url); + if (fileSource !== null) { + return loaderCommonJsSourceModule(fileSource, resolved.url); + } + } + if (loadedHasSource && loadedFormat === 'json') { + return globalThis.__wasm_rquickjs_register_import_attr_rewrite( + 'data:application/json,' + encodeURIComponent(loaderSourceToString(loaded.source)), + 'json', + ); + } + if (loadContext.importAttributes && loadContext.importAttributes.type === 'json') { + return globalThis.__wasm_rquickjs_register_import_attr_rewrite(resolved.url, 'json'); + } + return undefined; + }; + + function isLoaderThenable(value) { + return value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function'; + } + + function assertSyncLoaderResult(value, hookName, operation) { + if (isLoaderThenable(value)) { + const err = new Error('Async registered loader ' + hookName + ' hooks are not supported from ' + (operation || 'CommonJS require()')); + err.code = 'ERR_REQUIRE_ASYNC_MODULE'; + throw err; + } + return value; + } + + globalThis.__wasm_rquickjs_run_registered_loaders_sync = function runRegisteredLoadersSync(baseUrl, specifier, resolveOnly, mode) { + const loaders = globalThis.__wasm_rquickjs_registered_loaders; + if (!loaders || loaders.length === 0) return undefined; + const isImportMode = mode === 'import'; + const modules = []; + const moduleUrls = []; + for (let i = 0; i < loaders.length; i++) { + const loader = loaders[i]; + if (!loader.initialized) { + if (isImportMode) { + continue; + } + if (loader.initializing) { + const err = new Error('Registered loader initialization has not completed'); + err.code = 'ERR_REQUIRE_ASYNC_MODULE'; + throw err; + } + continue; + } + if (loader.module) { + modules.push(loader.module); + moduleUrls.push(loader.url); + } + } + if (modules.length === 0) return undefined; + + const baseContext = { + conditions: isImportMode ? loaderHookConditions() : Array.from(cjsPackageConditions()), + importAttributes: {}, + parentURL: String(baseUrl || fileUrlForPath('/')), + }; + + const defaultResolve = (nextSpecifier, context) => { + const specifierString = String(nextSpecifier); + const parentURL = context && context.parentURL ? String(context.parentURL) : baseContext.parentURL; + if (specifierString.startsWith('node:')) { + return { url: specifierString, format: isImportMode ? undefined : 'builtin' }; + } + if (isBuiltin(specifierString)) { + return { url: 'node:' + specifierString, format: isImportMode ? undefined : 'builtin' }; + } + if (isImportMode) { + return resolveEsmDefaultForLoader(specifierString, parentURL, context, baseContext.parentURL, true, false); + } + let parentFilename = null; + if (parentURL.startsWith('file://')) { + parentFilename = nodeUrl.fileURLToPath(parentURL); + } else if (parentURL.startsWith('/')) { + parentFilename = parentURL; + } + const parentDir = parentFilename ? pathModule.dirname(parentFilename) : '/'; + if (specifierString.startsWith('file://')) { + const filename = nodeUrl.fileURLToPath(specifierString); + const source = tryReadFile(filename); + if (source === null) return undefined; + return { url: nodeUrl.pathToFileURL(filename).href, format: filename.endsWith('.json') ? 'json' : 'commonjs', source }; + } + if (specifierString === '.' || specifierString === '..' || specifierString.startsWith('./') || specifierString.startsWith('../') || specifierString.startsWith('/')) { + const resolved = resolveFilename(specifierString, parentDir); + return { url: nodeUrl.pathToFileURL(resolved.filename).href, format: resolved.filename.endsWith('.json') ? 'json' : 'commonjs', source: resolved.content }; + } + if (specifierString.startsWith('#') && parentFilename) { + const importsResolved = resolvePackageImports(specifierString, parentDir, cjsPackageConditions()); + if (importsResolved.builtin) return { url: importsResolved.builtin, format: 'builtin' }; + return { url: nodeUrl.pathToFileURL(importsResolved.filename).href, format: importsResolved.filename.endsWith('.json') ? 'json' : 'commonjs', source: importsResolved.content }; + } + const nmResolved = resolveFromNodeModules(specifierString, parentDir, parentFilename, cjsPackageConditions()); + if (nmResolved) { + return { url: nodeUrl.pathToFileURL(nmResolved.filename).href, format: nmResolved.filename.endsWith('.json') ? 'json' : 'commonjs', source: nmResolved.content }; + } + return undefined; + }; + + const runResolve = (index, nextSpecifier, context) => { + if (index < 0) return defaultResolve(nextSpecifier, context); + const module = modules[index]; + if (typeof module.resolve === 'function') { + let nextCalled = false; + const nextResolve = (specifierForNext, contextForNext) => { + nextCalled = true; + return runResolve( + index - 1, + specifierForNext === undefined ? nextSpecifier : specifierForNext, + contextForNext === undefined ? context : Object.assign({}, context, contextForNext), + ); + }; + const hookResult = assertSyncLoaderResult(module.resolve(nextSpecifier, context, nextResolve), 'resolve', isImportMode ? 'static ES module resolution' : undefined); + if (hookResult === undefined) { + if (!nextCalled) throw makeLoaderChainError('resolve'); + return undefined; + } + const result = validateRegisteredLoaderResult(hookResult, 'resolve', context); + validateRegisteredLoaderResolveUrl(result.url, moduleUrls[index]); + if (!nextCalled && (!result || result.shortCircuit !== true)) { + throw makeLoaderChainError('resolve'); + } + return result; + } + return runResolve(index - 1, nextSpecifier, context); + }; + + const initialSpecifier = isImportMode && typeof specifier === 'string' + ? normalizeLoaderResolvedUrl(specifier) + : specifier; + const resolved = runResolve(modules.length - 1, initialSpecifier, baseContext); + if (!resolved || typeof resolved !== 'object' || resolved.url === undefined) return undefined; + resolved.url = normalizeLoaderResolvedUrl(String(resolved.url)); + const resolvedFormat = resolved.format === undefined || resolved.format === null ? undefined : String(resolved.format); + if (resolveOnly) return { url: resolved.url, format: resolvedFormat }; + + const defaultLoad = (_nextUrl, context) => ({ format: context && context.format }); + const runLoad = (index, nextUrl, context) => { + if (index < 0) return defaultLoad(nextUrl, context); + const module = modules[index]; + if (typeof module.load === 'function') { + let nextCalled = false; + const nextLoad = (urlForNext, contextForNext) => { + nextCalled = true; + return runLoad( + index - 1, + urlForNext === undefined ? nextUrl : String(urlForNext), + contextForNext === undefined ? context : Object.assign({}, context, contextForNext), + ); + }; + const result = validateRegisteredLoaderResult(assertSyncLoaderResult(module.load(nextUrl, context, nextLoad), 'load', isImportMode ? 'static ES module resolution' : undefined), 'load', context); + if (result.format !== undefined && result.format !== null && result.format !== '') { + validateRegisteredLoaderLoadFormat(result.format); + } + if (!nextCalled && (!result || result.shortCircuit !== true)) { + throw makeLoaderChainError('load'); + } + return result; + } + return runLoad(index - 1, nextUrl, context); + }; + + const loaded = runLoad(modules.length - 1, resolved.url, { + conditions: baseContext.conditions, + importAttributes: resolved.importAttributes && typeof resolved.importAttributes === 'object' + ? resolved.importAttributes + : baseContext.importAttributes, + format: resolvedFormat, + }); + const finalFormat = loaded && loaded.format !== undefined && loaded.format !== null + ? validateRegisteredLoaderLoadFormat(loaded.format) + : validateRegisteredLoaderLoadFormat(resolvedFormat); + if (finalFormat === 'builtin') return { url: resolved.url, format: finalFormat }; + if (!loaded && resolved.source === undefined) return undefined; + let source = loaded && Object.prototype.hasOwnProperty.call(loaded, 'source') && loaded.source !== null && loaded.source !== undefined + ? loaded.source + : resolved.source; + if (source === undefined && isImportMode) { + return { url: resolved.url, format: finalFormat }; + } + if (source === undefined && finalFormat === 'commonjs' && String(resolved.url).startsWith('file://')) { + try { + source = tryReadFile(nodeUrl.fileURLToPath(resolved.url)); + } catch (_) {} + } + if (source === null) source = undefined; + if (source === undefined) return undefined; + return { url: resolved.url, format: finalFormat, source }; + }; + + function staticRegisteredLoaderCacheKey(baseUrl, specifier) { + return String(baseUrl) + '\0' + String(specifier) + '\0'; + } + + function staticRegisteredLoaderReturn(loaded) { + if (!loaded || !loaded.url) return undefined; + const url = String(loaded.url); + const format = loaded.format === undefined || loaded.format === null ? undefined : String(loaded.format); + const hasSource = Object.prototype.hasOwnProperty.call(loaded, 'source') && loaded.source !== undefined && loaded.source !== null; + if (hasSource && (format === undefined || format === 'module')) { + return 'data:text/javascript,' + encodeURIComponent(loaderSourceToString(loaded.source)); + } + if (!hasSource && format === 'module') { + return url.startsWith('file://') ? nodeUrl.fileURLToPath(url) : url; + } + if (hasSource && format === 'commonjs') { + return loaderCommonJsSourceModule(loaded.source, url); + } + if (!hasSource && format === 'commonjs') { + const fileSource = loaderFileUrlSource(url); + if (fileSource !== null) { + return loaderCommonJsSourceModule(fileSource, url); + } + } + if (hasSource && format === 'json') { + return globalThis.__wasm_rquickjs_register_import_attr_rewrite( + 'data:application/json,' + encodeURIComponent(loaderSourceToString(loaded.source)), + 'json', + ); + } + if (url.startsWith('file://')) { + return nodeUrl.fileURLToPath(url); + } + return url; + } + + function staticRegisteredLoaderSourceForUrl(url) { + url = String(url); + if (url.startsWith('file://')) { + return loaderFileUrlSource(url); + } + if (url.startsWith('/')) { + try { + return tryReadFile(url); + } catch (_) { + return null; + } + } + if (url.startsWith('data:')) { + const comma = url.indexOf(','); + if (comma < 0) return null; + const meta = url.slice(5, comma).toLowerCase(); + if (meta.indexOf('text/javascript') < 0 && meta.indexOf('application/javascript') < 0) { + return null; + } + const body = url.slice(comma + 1); + try { + return meta.indexOf(';base64') >= 0 ? atob(body) : decodeURIComponent(body); + } catch (_) { + return null; + } + } + return null; + } + + function staticRegisteredLoaderChildUrl(loaded, fallback) { + fallback = String(fallback); + if (fallback.startsWith('data:')) return fallback; + if (loaded && loaded.url) return String(loaded.url); + return fallback.startsWith('/') ? nodeUrl.pathToFileURL(fallback).href : fallback; + } + + function staticRegisteredLoaderParentAliases(parentUrl) { + const aliases = [parentUrl]; + const virtualPrefix = 'file:///__wasm_rquickjs_virtual__/'; + if (parentUrl.startsWith(virtualPrefix) && parentUrl.endsWith('.mjs')) { + aliases.push('file:///' + parentUrl.slice(virtualPrefix.length, -4)); + } + return aliases; + } + + async function prepareStaticRegisteredLoaderGraph(parentUrl, seen) { + parentUrl = normalizeLoaderResolvedUrl(String(parentUrl)); + seen = seen || Object.create(null); + if (seen[parentUrl]) return; + seen[parentUrl] = true; + + const source = staticRegisteredLoaderSourceForUrl(parentUrl); + if (source === null) return; + const edges = collectStaticEsmEdges(source); + for (let i = 0; i < edges.length; i++) { + const specifier = edges[i].specifier; + const key = staticRegisteredLoaderCacheKey(parentUrl, specifier); + if (!Object.prototype.hasOwnProperty.call(globalThis.__wasm_rquickjs_static_registered_loader_cache, key)) { + try { + const loaded = await globalThis.__wasm_rquickjs_run_registered_loaders(parentUrl, specifier, undefined, 'static-raw'); + const value = staticRegisteredLoaderReturn(loaded); + globalThis.__wasm_rquickjs_static_registered_loader_cache[key] = { value, loaded }; + } catch (error) { + globalThis.__wasm_rquickjs_static_registered_loader_cache[key] = { error }; + continue; + } + } + const cached = globalThis.__wasm_rquickjs_static_registered_loader_cache[key]; + if (cached && !cached.error && cached.value !== undefined) { + await prepareStaticRegisteredLoaderGraph( + staticRegisteredLoaderChildUrl(cached.loaded, cached.value), + seen, + ); + } + } + } + + globalThis.__wasm_rquickjs_prepare_static_registered_loader_graph = async function prepareStaticRegisteredLoaderEntry(entryUrl, entrySpecifier, entryParentUrl) { + if (!globalThis.__wasm_rquickjs_static_registered_loader_cache) { + globalThis.__wasm_rquickjs_static_registered_loader_cache = Object.create(null); + } + if (entrySpecifier !== undefined && entryParentUrl !== undefined) { + const parentUrl = normalizeLoaderResolvedUrl(String(entryParentUrl)); + const specifier = String(entrySpecifier); + const key = staticRegisteredLoaderCacheKey(parentUrl, specifier); + if (!Object.prototype.hasOwnProperty.call(globalThis.__wasm_rquickjs_static_registered_loader_cache, key)) { + try { + const loaded = await globalThis.__wasm_rquickjs_run_registered_loaders(parentUrl, specifier, undefined, 'static-raw'); + const value = staticRegisteredLoaderReturn(loaded); + globalThis.__wasm_rquickjs_static_registered_loader_cache[key] = { value, loaded }; + } catch (error) { + globalThis.__wasm_rquickjs_static_registered_loader_cache[key] = { error }; + return; + } + } + const cached = globalThis.__wasm_rquickjs_static_registered_loader_cache[key]; + const aliases = staticRegisteredLoaderParentAliases(parentUrl); + for (let i = 1; i < aliases.length; i++) { + globalThis.__wasm_rquickjs_static_registered_loader_cache[ + staticRegisteredLoaderCacheKey(aliases[i], specifier) + ] = cached; + } + } + await prepareStaticRegisteredLoaderGraph(entryUrl, Object.create(null)); + }; + + globalThis.__wasm_rquickjs_resolve_static_registered_loader = function resolveStaticRegisteredLoader(baseUrl, specifier) { + const cache = globalThis.__wasm_rquickjs_static_registered_loader_cache; + const key = staticRegisteredLoaderCacheKey(baseUrl, specifier); + if (cache && Object.prototype.hasOwnProperty.call(cache, key)) { + const cached = cache[key]; + if (cached.error) throw cached.error; + return cached.value; + } + const loaded = globalThis.__wasm_rquickjs_run_registered_loaders_sync(baseUrl, specifier, false, 'import'); + return staticRegisteredLoaderReturn(loaded); + }; } // "node_modules" reversed as char codes: s-e-l-u-d-o-m-_-e-d-o-n @@ -1656,26 +6326,102 @@ function runMain() { } } -const moduleExports = { +export let syncBuiltinESMExports = function() { + const registry = globalThis.__wasm_rquickjs_sync_builtin_esm_exports; + if (!registry) return; + if (typeof registry.fs === 'function') registry.fs(); + if (typeof registry.events === 'function') registry.events(); + require = moduleExports.require; + createRequire = moduleExports.createRequire; + findPackageJSON = moduleExports.findPackageJSON; + builtinModules = moduleExports.builtinModules; + isBuiltinModule = moduleExports.isBuiltin; + register = moduleExports.register; + syncBuiltinESMExports = moduleExports.syncBuiltinESMExports; +}; + +function Module(id, parent) { + this.id = id === undefined ? '' : id; + this.path = pathModule.dirname(this.id); + this.exports = {}; + this.filename = null; + this.loaded = false; + this.children = []; + this.parent = parent || null; + if (parent && parent.children) { + Array.prototype.push.call(parent.children, this); + } +} + +Module.prototype.require = function require(id) { + const baseDir = this && typeof this.filename === 'string' + ? pathModule.dirname(this.filename) + : '.'; + return makeRequire(baseDir, this || null)(id); +}; + +Module.prototype._compile = function _compile(content, filename) { + if (!(this instanceof Module)) { + throw new ERR_INVALID_ARG_TYPE('mod', 'Module', this); + } + return compileModuleInto(this, content, arguments.length > 1 ? filename : this.filename); +}; + +function moduleLoad(request, parent, isMain) { + void isMain; + if (parent && typeof parent.filename === 'string') { + return makeRequire(pathModule.dirname(parent.filename), parent)(request); + } + return makeRequire('.', parent || null)(request); +} + +function moduleResolveFilename(request, parent, isMain, options) { + void isMain; + const baseDir = parent && typeof parent.filename === 'string' + ? pathModule.dirname(parent.filename) + : '.'; + const parentFilename = parent && typeof parent.filename === 'string' + ? parent.filename + : null; + const parentLookupPaths = parent && Array.isArray(parent.paths) + ? parent.paths.concat(globalPaths) + : null; + return resolveForRequire(request, options, baseDir, parentFilename, parentLookupPaths); +} + +const moduleExports = Object.assign(Module, { require: globalRequire, createRequire, + findPackageJSON, + findSourceMap, + SourceMap, builtinModules: builtinModuleNames, + syncBuiltinESMExports, isBuiltin: isBuiltinModule, + register, wrap: wrap, wrapper: wrapper, runMain: runMain, _nodeModulePaths: _nodeModulePaths, _resolveLookupPaths: _resolveLookupPaths, + _resolveFilename: moduleResolveFilename, + _load: moduleLoad, _initPaths: _initPaths, _pathCache: _pathCache, _extensions: requireExtensions, _stat: _stat, globalPaths: globalPaths, setSourceMapsSupport, -}; +}); +moduleExports.Module = Module; // Add self-reference so require('module') works builtinModuleMap['module'] = moduleExports; builtinModuleMap['node:module'] = moduleExports; +if (!builtinModuleNames.includes('module')) { + builtinModuleNames.push('module'); +} +publicBuiltinIdSet.add('module'); +publicBuiltinWithoutSchemeSet.add('module'); export default moduleExports; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/node_http.js b/crates/wasm-rquickjs/skeleton/src/builtin/node_http.js index f365525c..a0603a96 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/node_http.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/node_http.js @@ -249,12 +249,12 @@ export class Agent extends EventEmitter { throw err; } + this.options = Object.assign({}, options, { scheduling }); this.keepAlive = options.keepAlive || false; this.keepAliveMsecs = options.keepAliveMsecs || 1000; this.maxSockets = options.maxSockets || Infinity; this.maxTotalSockets = options.maxTotalSockets || Infinity; this.maxFreeSockets = options.maxFreeSockets || 256; - this.timeout = options.timeout; this.scheduling = scheduling; this.freeSockets = {}; this.requests = {}; @@ -263,6 +263,10 @@ export class Agent extends EventEmitter { this._requestQueue = {}; } + get timeout() { + return this.options ? this.options.timeout : undefined; + } + get totalSocketCount() { let n = 0; for (const key of Object.keys(this.sockets)) { diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/process.js b/crates/wasm-rquickjs/skeleton/src/builtin/process.js index aff4667a..22170065 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/process.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/process.js @@ -165,6 +165,13 @@ Object.defineProperty(process, 'title', { }); process.release = { name: 'node' }; process.allowedNodeEnvironmentFlags = new Set(); +process.report = {}; +Object.defineProperty(process, Symbol.toStringTag, { + value: 'process', + writable: true, + enumerable: false, + configurable: true, +}); let _startTime = null; @@ -628,6 +635,18 @@ process.emitWarning = function emitWarning(warning, typeOrOptions, code, ctor) { if (isDeprecationWarning && process.noDeprecation) { return; } + const warningCode = obj.code ? ' [' + String(obj.code) + ']' : ''; + const warningHeader = warningName + ': ' + String(obj.message || obj); + const stderrHeader = warningName + warningCode + ': ' + String(obj.message || obj); + if (typeof obj.stack === 'string') { + const newline = obj.stack.indexOf('\n'); + const rest = newline === -1 ? '' : obj.stack.slice(newline); + if (!obj.stack.startsWith(warningHeader)) { + obj.stack = warningHeader + rest; + } + } else { + obj.stack = warningHeader; + } const suppressDefaultWarning = !!globalThis.__wasm_rquickjs_suppress_warning_stderr; const shouldThrowDeprecation = isDeprecationWarning && !!process.throwDeprecation; @@ -636,12 +655,14 @@ process.emitWarning = function emitWarning(warning, typeOrOptions, code, ctor) { throw obj; } if (!suppressDefaultWarning && process.stderr && typeof process.stderr.write === 'function') { - const header = warningName + ': ' + String(obj.message || obj); - let text = header; + let text = stderrHeader; if (typeof obj.stack === 'string') { text = obj.stack.indexOf(String(obj.message || obj)) >= 0 ? obj.stack - : header + '\n' + obj.stack; + : stderrHeader + '\n' + obj.stack; + if (warningCode && text.startsWith(warningHeader)) { + text = stderrHeader + text.slice(warningHeader.length); + } } process.stderr.write(text.endsWith('\n') ? text : text + '\n'); } @@ -723,14 +744,26 @@ process._runExitHandlers = function _runExitHandlers(code) { // after a microtask turn, so that assert.rejects() and similar patterns // that handle the rejection synchronously don't cause false positives. const _pendingRejections = new Map(); +const _ignoredUnhandledRejections = new WeakSet(); + +function _isIgnoredUnhandledRejection(promise) { + return _ignoredUnhandledRejections.has(promise); +} globalThis.__wasm_rquickjs_rejection_tracker = function(promise, reason, isHandled) { + if (_isIgnoredUnhandledRejection(promise)) { + _pendingRejections.delete(promise); + return; + } if (!isHandled) { _pendingRejections.set(promise, reason); Promise.resolve().then(function() { Promise.resolve().then(function() { if (_pendingRejections.has(promise)) { _pendingRejections.delete(promise); + if (_isIgnoredUnhandledRejection(promise)) { + return; + } process.emit('unhandledRejection', reason, promise); } }); @@ -740,6 +773,28 @@ globalThis.__wasm_rquickjs_rejection_tracker = function(promise, reason, isHandl } }; +globalThis.__wasm_rquickjs_mark_rejection_handled = function(promise) { + _pendingRejections.delete(promise); +}; + +globalThis.__wasm_rquickjs_ignore_unhandled_rejection = function(promise) { + _ignoredUnhandledRejections.add(promise); + _pendingRejections.delete(promise); +}; + +globalThis.__wasm_rquickjs_ignore_last_pending_unhandled_rejection = function(reason) { + let pendingPromise; + for (const [promise, pendingReason] of _pendingRejections) { + if (pendingReason === reason) { + pendingPromise = promise; + } + } + if (pendingPromise !== undefined) { + _ignoredUnhandledRejections.add(pendingPromise); + _pendingRejections.delete(pendingPromise); + } +}; + // Named exports for import { argv } from 'node:process' style export var argv = process.argv; export var argv0 = process.argv0; @@ -762,6 +817,7 @@ export var cpuUsage = process.cpuUsage; export var memoryUsage = process.memoryUsage; export var uptime = process.uptime; export var release = process.release; +export var report = process.report; export var stdin = process.stdin; export var kill = process.kill; export var emitWarning = process.emitWarning; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/test.js b/crates/wasm-rquickjs/skeleton/src/builtin/test.js index dd4732c0..5ce4f409 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/test.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/test.js @@ -14,6 +14,13 @@ let _subtestFilter = (typeof globalThis.__wasm_rquickjs_node_test_filter === 'nu : null; let _subtestRegistrationIndex = 0; +function activeTestEntryFile(moduleContext) { + if (typeof globalThis.__wasm_rquickjs_node_test_entry_file === 'string') { + return globalThis.__wasm_rquickjs_node_test_entry_file; + } + return moduleContext ? moduleContext.filename : undefined; +} + // --- Custom assertions registry (testAssertions.register) --- const _customAssertions = {}; @@ -437,7 +444,7 @@ function runTest(parsed, parentSuite) { // Handle todo const isTodo = options.todo === true || typeof options.todo === 'string'; - const filePath = moduleContext ? moduleContext.filename : undefined; + const filePath = activeTestEntryFile(moduleContext); const ctx = new TestContext(name, parentSuite, filePath); // Collect beforeEach from parent suite chain @@ -567,7 +574,7 @@ function runSuite(name, options, fn, parentSuite, moduleContext) { const isTodo = options.todo === true || typeof options.todo === 'string'; - const filePath = moduleContext ? moduleContext.filename : undefined; + const filePath = activeTestEntryFile(moduleContext); const suite = new SuiteContext(name, parentSuite, filePath); const prevSuite = currentSuite; currentSuite = suite; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/util.rs b/crates/wasm-rquickjs/skeleton/src/builtin/util.rs index ebe5dcb5..7ee9a7ac 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/util.rs +++ b/crates/wasm-rquickjs/skeleton/src/builtin/util.rs @@ -2,4 +2,52 @@ pub const UTIL_JS: &str = include_str!("util.js"); // Re-export for aliases -pub const REEXPORT_JS: &str = r#"export * from 'node:util'; export { default } from 'node:util';"#; +pub const BARE_UTIL_REEXPORT_JS: &str = + r#"export * from 'node:util'; export { default } from 'node:util';"#; +pub const UTIL_TYPES_JS: &str = r#" +import { types as _types } from 'node:util'; + +export default _types; +export const isAnyArrayBuffer = _types.isAnyArrayBuffer; +export const isArgumentsObject = _types.isArgumentsObject; +export const isArrayBuffer = _types.isArrayBuffer; +export const isArrayBufferView = _types.isArrayBufferView; +export const isAsyncFunction = _types.isAsyncFunction; +export const isBigInt64Array = _types.isBigInt64Array; +export const isBigIntObject = _types.isBigIntObject; +export const isBigUint64Array = _types.isBigUint64Array; +export const isBooleanObject = _types.isBooleanObject; +export const isBoxedPrimitive = _types.isBoxedPrimitive; +export const isCryptoKey = _types.isCryptoKey; +export const isDataView = _types.isDataView; +export const isDate = _types.isDate; +export const isExternal = _types.isExternal; +export const isFloat32Array = _types.isFloat32Array; +export const isFloat64Array = _types.isFloat64Array; +export const isGeneratorFunction = _types.isGeneratorFunction; +export const isGeneratorObject = _types.isGeneratorObject; +export const isInt8Array = _types.isInt8Array; +export const isInt16Array = _types.isInt16Array; +export const isInt32Array = _types.isInt32Array; +export const isKeyObject = _types.isKeyObject; +export const isMap = _types.isMap; +export const isMapIterator = _types.isMapIterator; +export const isModuleNamespaceObject = _types.isModuleNamespaceObject; +export const isNativeError = _types.isNativeError; +export const isNumberObject = _types.isNumberObject; +export const isPromise = _types.isPromise; +export const isProxy = _types.isProxy; +export const isRegExp = _types.isRegExp; +export const isSet = _types.isSet; +export const isSetIterator = _types.isSetIterator; +export const isSharedArrayBuffer = _types.isSharedArrayBuffer; +export const isStringObject = _types.isStringObject; +export const isSymbolObject = _types.isSymbolObject; +export const isTypedArray = _types.isTypedArray; +export const isUint8Array = _types.isUint8Array; +export const isUint8ClampedArray = _types.isUint8ClampedArray; +export const isUint16Array = _types.isUint16Array; +export const isUint32Array = _types.isUint32Array; +export const isWeakMap = _types.isWeakMap; +export const isWeakSet = _types.isWeakSet; +"#; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/vm.js b/crates/wasm-rquickjs/skeleton/src/builtin/vm.js index ad287e38..4a4b3ab3 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/vm.js +++ b/crates/wasm-rquickjs/skeleton/src/builtin/vm.js @@ -1,10 +1,69 @@ -import { eval_in_new_context as evalInNewContext } from '__wasm_rquickjs_builtin/vm_native'; +import { + eval_in_new_context as evalInNewContext, + eval_with_filename as evalWithFilename, +} from '__wasm_rquickjs_builtin/vm_native'; +import * as pathModule from 'node:path'; let contextIdCounter = 1; -const contextSymbol = Symbol('vm.context'); +const contextIds = new WeakMap(); +const contextOptions = new WeakMap(); +const contextDeletedGlobals = new WeakMap(); const identifierPattern = /^[$A-Z_a-z][$0-9A-Z_a-z]*$/; const moduleNamespaceExportsSymbol = Symbol.for('wasm-rquickjs.vm.namespaceExports'); const moduleNamespaceBindingsSymbol = Symbol.for('wasm-rquickjs.vm.namespaceBindings'); +const moduleNamespaceBrandSymbol = Symbol('wasm-rquickjs.vm.namespaceBrand'); +const vmDynamicImportReferrerSymbol = Symbol('wasm-rquickjs.vm.dynamicImportReferrer'); +const scriptBrandSet = new WeakSet(); +const vmModuleInstanceBrandSymbol = Symbol('wasm-rquickjs.vm.moduleInstance'); +const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); +const USE_MAIN_CONTEXT_DEFAULT_LOADER = Symbol('vm_dynamic_import_main_context_default'); +const defaultLoaderImportHelper = '__wasm_rquickjs_vm_default_loader_import__'; +const missingDynamicImportHelper = '__wasm_rquickjs_vm_missing_dynamic_import__'; +const missingDynamicImportFlagHelper = '__wasm_rquickjs_vm_missing_dynamic_import_flag__'; +const sandboxDescriptorsHelper = '__wasm_rquickjs_vm_sandbox_descriptors__'; +const sandboxSymbolsHelper = '__wasm_rquickjs_vm_sandbox_symbols__'; +const sourceTextModuleExportCellsPlaceholder = '__wasm_rquickjs_vm_export_cells_placeholder__'; +let defaultLoaderImportHelperCounter = 1; +let sandboxDescriptorsHelperCounter = 1; +let sandboxSymbolsHelperCounter = 1; +function isPrivateBuiltinSpecifier(specifier) { + return specifier.startsWith('__wasm_rquickjs_builtin/'); +} + +function rejectPrivateBuiltinImport(specifier) { + const err = new Error("Cannot find module '" + specifier + "'"); + err.code = 'ERR_MODULE_NOT_FOUND'; + return Promise.reject(err); +} + +function defaultLoaderImportFunction(filename, specifier) { + specifier = String(specifier); + if (isPrivateBuiltinSpecifier(specifier)) { + return rejectPrivateBuiltinImport(specifier); + } + return import(resolveDefaultLoaderSpecifier(specifier, filename)); +} + +function missingDynamicImportFunction() { + const err = new TypeError('A dynamic import callback was not specified.'); + err.code = 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING'; + return Promise.reject(err); +} + +function missingDynamicImportFlagFunction() { + const err = new TypeError('A dynamic import callback was invoked without --experimental-vm-modules'); + err.code = 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG'; + return Promise.reject(err); +} + +function vmModulesEnabled() { + const execArgv = globalThis.process && globalThis.process.execArgv; + return Array.isArray(execArgv) && execArgv.indexOf('--experimental-vm-modules') !== -1; +} + +export const constants = { + USE_MAIN_CONTEXT_DEFAULT_LOADER, +}; function splitDeclarators(declarationList) { const result = []; @@ -60,256 +119,2996 @@ function splitDeclarators(declarationList) { return result; } -function parseSourceTextModuleBindings(source) { - const bindings = []; - const exportDeclarationPattern = /export\s+(const|let|var)\s+([^;]+)/g; - let match; - - while ((match = exportDeclarationPattern.exec(source)) !== null) { - const kind = match[1]; - const declarators = splitDeclarators(match[2]); - - for (let i = 0; i < declarators.length; i++) { - const declarator = declarators[i]; - const eq = declarator.indexOf('='); - const bindingName = (eq === -1 ? declarator : declarator.slice(0, eq)).trim(); - - if (!identifierPattern.test(bindingName)) { - throw new SyntaxError('Unsupported export declaration in vm.SourceTextModule'); - } - - bindings.push({ - name: bindingName, - kind, - }); +function findSourceTextModuleStatementEnd(source, start) { + let i = start; + let depth = 0; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + return i; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x2f && (regexCanFollow(source, i) || (regexCanFollowParen(source, i) && isLikelyRegexLiteral(source, i)))) { + i = skipRegexLiteral(source, i); + continue; + } + if (ch === 0x28 || ch === 0x5b || ch === 0x7b) { + depth++; + } else if ((ch === 0x29 || ch === 0x5d || ch === 0x7d) && depth > 0) { + depth--; + } + if (depth === 0 && ch === 0x3b) return i + 1; + if (depth === 0 && (ch === 0x0a || ch === 0x0d) && previousSignificantChar(source, i) !== 0x2c) return i; + i++; } + return source.length; +} - if (source.indexOf('export ') !== -1 && bindings.length === 0) { - throw new SyntaxError('Unsupported export declaration in vm.SourceTextModule'); +function skipTemplateLiteralWithExpressions(source, start) { + let i = start + 1; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x5c) { + i += 2; + continue; + } + if (ch === 0x60) return i + 1; + if (ch === 0x24 && source.charCodeAt(i + 1) === 0x7b) { + const expressionEnd = findTemplateExpressionEnd(source, i + 2); + if (expressionEnd === -1) return source.length; + i = expressionEnd + 1; + continue; + } + i++; } + return source.length; +} - return bindings; +function sourceTextModuleReadStringSpecifier(source, pos) { + const quote = source.charCodeAt(pos); + if (quote !== 0x27 && quote !== 0x22) return null; + const end = skipStringLiteral(source, pos, quote); + return { value: source.slice(pos + 1, end - 1), end }; } -function compileSourceTextModuleEvaluator(source, names) { - const executableSource = source.replace(/\bexport\s+(?=(?:const|let|var)\b)/g, ''); - const exportObjectEntries = names.map(function(name) { - return JSON.stringify(name) + ': ' + name; - }).join(', '); +function sourceTextModuleParseNamedImports(source, start, end) { + return source.slice(start, end).split(',').map((part) => { + const pieces = part.trim().split(/\s+as\s+/); + const imported = pieces[0] && pieces[0].trim(); + const local = (pieces[1] || pieces[0] || '').trim(); + return imported ? { imported, local } : null; + }).filter(Boolean); +} - return new Function('"use strict";\n' + executableSource + '\nreturn { ' + exportObjectEntries + ' };'); +function sourceTextModuleFindFrom(source, start, end) { + let i = start; + let depth = 0; + while (i < end) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < end && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x3b && depth === 0) { + return -1; + } + if (ch === 0x28 || ch === 0x5b || ch === 0x7b) { + depth++; + i++; + continue; + } + if ((ch === 0x29 || ch === 0x5d || ch === 0x7d) && depth > 0) { + depth--; + i++; + continue; + } + if (depth === 0 && source.startsWith('from', i) && hasIdentifierBoundary(source, i, i + 4)) { + return i; + } + i++; + } + return -1; } -function createModuleNamespace(module) { - const namespaceTarget = Object.create(null); - const names = module._names.slice().sort(); +function sourceTextModuleParseImportAttributes(source, start, end) { + const attributes = Object.create(null); + let pos = skipWhitespaceAndComments(source, start); + if (!source.startsWith('with', pos) || !hasIdentifierBoundary(source, pos, pos + 4)) { + return attributes; + } + pos = skipWhitespaceAndComments(source, pos + 4); + if (source.charCodeAt(pos) !== 0x7b) return attributes; + pos++; + while (pos < end) { + pos = skipWhitespaceAndComments(source, pos); + if (source.charCodeAt(pos) === 0x7d) break; + let key; + const keyQuote = source.charCodeAt(pos); + if (keyQuote === 0x27 || keyQuote === 0x22) { + const keyLiteral = sourceTextModuleReadStringSpecifier(source, pos); + if (!keyLiteral) break; + key = keyLiteral.value; + pos = keyLiteral.end; + } else { + const keyStart = pos; + while (pos < end && isIdentifierChar(source.charCodeAt(pos))) pos++; + key = source.slice(keyStart, pos); + } + pos = skipWhitespaceAndComments(source, pos); + if (!key || source.charCodeAt(pos) !== 0x3a) break; + pos = skipWhitespaceAndComments(source, pos + 1); + const value = sourceTextModuleReadStringSpecifier(source, pos); + if (!value) break; + attributes[key] = value.value; + pos = skipWhitespaceAndComments(source, value.end); + if (source.charCodeAt(pos) === 0x2c) { + pos++; + continue; + } + if (source.charCodeAt(pos) === 0x7d) break; + } + return attributes; +} - // QuickJS does not expose virtual export keys from this proxy via - // Object.getOwnPropertyNames() while bindings are uninitialized. - // Store names out-of-band so util.inspect can still enumerate exports. - Object.defineProperty(namespaceTarget, moduleNamespaceExportsSymbol, { - value: names.slice(), - enumerable: false, - writable: false, - configurable: false, - }); - Object.defineProperty(namespaceTarget, moduleNamespaceBindingsSymbol, { - value: module._bindings, - enumerable: false, - writable: false, - configurable: false, - }); +function sourceTextModuleParseImportClause(source, clauseStart, clauseEnd) { + const names = []; + let pos = skipWhitespaceAndComments(source, clauseStart); + if (pos >= clauseEnd) return names; - Object.defineProperty(namespaceTarget, Symbol.toStringTag, { - value: 'Module', - enumerable: false, - writable: false, - configurable: true, - }); + if (source.charCodeAt(pos) !== 0x7b && source.charCodeAt(pos) !== 0x2a) { + const localStart = pos; + while (pos < clauseEnd && isIdentifierChar(source.charCodeAt(pos))) pos++; + const local = source.slice(localStart, pos); + if (local) { + sourceTextModuleAddImportName({ names }, 'default', local); + } + pos = skipWhitespaceAndComments(source, pos); + if (source.charCodeAt(pos) === 0x2c) { + pos = skipWhitespaceAndComments(source, pos + 1); + } + } - return new Proxy(namespaceTarget, { - ownKeys: function() { - return names.concat([Symbol.toStringTag]); - }, - has: function(_target, prop) { - if (typeof prop === 'string' && module._bindings[prop] !== undefined) { - return true; - } - return prop in namespaceTarget; - }, - get: function(_target, prop, receiver) { - if (typeof prop === 'string' && module._bindings[prop] !== undefined) { - const binding = module._bindings[prop]; - if (!binding.initialized) { - throw new ReferenceError(prop + ' is not initialized'); - } - return binding.value; + if (source.charCodeAt(pos) === 0x7b) { + const close = source.indexOf('}', pos + 1); + if (close >= 0 && close <= clauseEnd) { + const named = sourceTextModuleParseNamedImports(source, pos + 1, close); + for (let i = 0; i < named.length; i++) { + sourceTextModuleAddImportName({ names }, named[i].imported, named[i].local); } - return Reflect.get(namespaceTarget, prop, receiver); - }, - getOwnPropertyDescriptor: function(_target, prop) { - if (typeof prop === 'string' && module._bindings[prop] !== undefined) { - const binding = module._bindings[prop]; - if (!binding.initialized) { - throw new ReferenceError(prop + ' is not initialized'); - } + } + return names; + } - return { - value: binding.value, - writable: true, - enumerable: true, - configurable: true, - }; + if (source.charCodeAt(pos) === 0x2a) { + pos = skipWhitespaceAndComments(source, pos + 1); + if (source.startsWith('as', pos) && hasIdentifierBoundary(source, pos, pos + 2)) { + pos = skipWhitespaceAndComments(source, pos + 2); + const localStart = pos; + while (pos < clauseEnd && isIdentifierChar(source.charCodeAt(pos))) pos++; + const local = source.slice(localStart, pos); + if (local) { + sourceTextModuleAddImportName({ names }, '*', local); } + } + } - return Object.getOwnPropertyDescriptor(namespaceTarget, prop); - }, - }); + return names; } -function createIndirectEvalSource(code) { - return '(0, eval)(' + JSON.stringify(code) + ')'; +function sourceTextModuleGetDependency(dependencies, bySpecifier, specifier) { + let dependency = bySpecifier[specifier]; + if (!dependency) { + dependency = { specifier, names: [], attributes: Object.create(null), exportAll: false }; + bySpecifier[specifier] = dependency; + dependencies.push(dependency); + } + return dependency; } -export function runInNewContext(code, sandbox, options) { - if (code === undefined || code === null) code = ''; - code = String(code); - - const keys = []; - const values = []; +function sourceTextModuleAddImportName(dependency, imported, local) { + if (!dependency.names.some((entry) => entry.local === local && entry.imported === imported)) { + dependency.names.push({ imported, local }); + } +} - if (sandbox && typeof sandbox === 'object') { - const sandboxKeys = Object.keys(sandbox); - for (let i = 0; i < sandboxKeys.length; i++) { - keys.push(sandboxKeys[i]); - values.push(sandbox[sandboxKeys[i]]); - } +function sourceTextModuleSetAttributes(dependency, attributes) { + const keys = Object.keys(attributes); + for (let i = 0; i < keys.length; i++) { + dependency.attributes[keys[i]] = attributes[keys[i]]; } +} - return evalInNewContext(createIndirectEvalSource(code), keys, values); +function sourceTextModuleExportSyncSource(name, localName) { + return '\n' + sourceTextModuleExportCellsPlaceholder + '[' + JSON.stringify(name) + '].initialized = true;' + + sourceTextModuleExportCellsPlaceholder + '[' + JSON.stringify(name) + '].value = ' + localName + ';\n'; } -export function createContext(sandbox) { - if (sandbox === undefined || sandbox === null) { - sandbox = {}; - } - if (typeof sandbox !== 'object') { - throw new TypeError('sandbox must be an object'); +function applySourceTextModuleEdits(source, edits) { + if (edits.length === 0) return source; + edits.sort((a, b) => a.start - b.start); + let out = ''; + let last = 0; + for (let i = 0; i < edits.length; i++) { + const edit = edits[i]; + if (edit.start < last) continue; + out += source.slice(last, edit.start) + edit.replacement; + last = edit.end; } - sandbox[contextSymbol] = contextIdCounter++; - return sandbox; + return out + source.slice(last); } -export function isContext(obj) { - return obj != null && typeof obj === 'object' && contextSymbol in obj; +function sourceTextModuleExportNamesByLocal(exportBindings) { + const byLocal = Object.create(null); + for (let i = 0; i < exportBindings.length; i++) { + const binding = exportBindings[i]; + if (!byLocal[binding.localName]) byLocal[binding.localName] = []; + byLocal[binding.localName].push(binding.name); + } + return byLocal; } -export function runInContext(code, context, options) { - if (!isContext(context)) { - throw new TypeError('argument must be a vm.Context'); +function sourceTextModuleAssignmentSyncSource(exportNames, localName) { + let out = ''; + for (let i = 0; i < exportNames.length; i++) { + out += sourceTextModuleExportSyncSource(exportNames[i], localName); } - if (code === undefined || code === null) code = ''; - code = String(code); + return out; +} - const keys = []; - const values = []; - for (const k of Object.keys(context)) { - if (typeof context[contextSymbol] !== 'undefined' && k === String(contextSymbol)) { +function injectSourceTextModuleAssignmentSync(source, exportBindings) { + const byLocal = sourceTextModuleExportNamesByLocal(exportBindings); + const locals = Object.keys(byLocal).filter((name) => identifierPattern.test(name)); + if (locals.length === 0) return source; + const edits = []; + sourceTextModuleScanAssignmentSyncRange(source, 0, source.length, byLocal, Object.create(null), edits); + return applySourceTextModuleEdits(source, edits); +} + +function sourceTextModuleScanAssignmentSyncRange(source, start, end, byLocal, shadowed, edits) { + let i = start; + while (i < end) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x2f && (regexCanFollow(source, i) || (regexCanFollowParen(source, i) && isLikelyRegexLiteral(source, i)))) { + i = skipRegexLiteral(source, i); + continue; + } + if ((source.startsWith('function', i) && hasIdentifierBoundary(source, i, i + 8)) + || (source.startsWith('class', i) && hasIdentifierBoundary(source, i, i + 5))) { + const declaration = sourceTextModuleDeclarationBody(source, i); + if (declaration) { + const bodyShadowed = sourceTextModuleMergeShadowed(shadowed, declaration.shadowed); + sourceTextModuleScanAssignmentSyncRange(source, declaration.bodyStart + 1, declaration.bodyEnd, byLocal, bodyShadowed, edits); + i = declaration.bodyEnd + 1; + continue; + } + i = end; + continue; + } + if (isIdentifierStart(ch) && !isIdentifierChar(source.charCodeAt(i - 1))) { + let end = i + 1; + while (end < source.length && isIdentifierChar(source.charCodeAt(end))) end++; + const localName = source.slice(i, end); + if (byLocal[localName] && !shadowed[localName] && sourceTextModuleCanRewriteLocalWrite(source, i)) { + const assignment = skipWhitespaceAndComments(source, end); + const assignmentEnd = sourceTextModuleAssignmentOperatorEnd(source, assignment); + if (assignmentEnd >= 0) { + const statementEnd = sourceTextModuleAssignmentStatementEnd(source, assignmentEnd); + edits.push({ + start: statementEnd, + end: statementEnd, + replacement: sourceTextModuleAssignmentSyncSource(byLocal[localName], localName), + }); + i = statementEnd; + continue; + } + const updateEnd = sourceTextModuleUpdateOperatorEnd(source, i, end); + if (updateEnd >= 0) { + const statementEnd = sourceTextModuleAssignmentStatementEnd(source, updateEnd); + edits.push({ + start: statementEnd, + end: statementEnd, + replacement: sourceTextModuleAssignmentSyncSource(byLocal[localName], localName), + }); + i = statementEnd; + continue; + } + } + i = end; continue; } - keys.push(k); - values.push(context[k]); + i++; } +} - return evalInNewContext(createIndirectEvalSource(code), keys, values); +function sourceTextModuleCanRewriteLocalWrite(source, start) { + const previous = previousSignificantChar(source, start); + if (previous === 0x2e || previous === 0x23) return false; + const word = previousSignificantWord(source, start); + return word !== 'let' && word !== 'const' && word !== 'var' && word !== 'function' && word !== 'class'; } -export function runInThisContext(code, options) { - if (code === undefined || code === null) return undefined; - code = String(code); - return (0, eval)(code); +function sourceTextModuleDeclarationBody(source, start) { + let i = start; + let parenDepth = 0; + let bracketDepth = 0; + const paramsStart = source.indexOf('(', start); + const paramsEnd = paramsStart >= 0 ? findMatchingParen(source, paramsStart) : -1; + const shadowed = Object.create(null); + if (paramsStart >= 0 && paramsEnd >= 0) { + sourceTextModuleCollectBindingNames(source, paramsStart + 1, paramsEnd, shadowed); + } + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x28) parenDepth++; + else if (ch === 0x29 && parenDepth > 0) parenDepth--; + else if (ch === 0x5b) bracketDepth++; + else if (ch === 0x5d && bracketDepth > 0) bracketDepth--; + else if (ch === 0x7b && parenDepth === 0 && bracketDepth === 0) { + const bodyEnd = sourceTextModuleFindMatchingBrace(source, i); + sourceTextModuleCollectDeclaredNames(source, i + 1, bodyEnd, shadowed); + return { bodyStart: i, bodyEnd, shadowed }; + } + i++; + } + return null; } -export function compileFunction(code, params, options) { - params = params || []; - return new Function(...params, code); +function sourceTextModuleMergeShadowed(parent, child) { + const merged = Object.create(null); + for (const name of Object.keys(parent)) merged[name] = true; + for (const name of Object.keys(child)) merged[name] = true; + return merged; } -export class Script { - constructor(code, options) { - this._code = String(code); +function sourceTextModuleCollectBindingNames(source, start, end, out) { + let i = start; + while (i < end) { + if (isIdentifierStart(source.charCodeAt(i))) { + const nameStart = i; + i++; + while (i < end && isIdentifierChar(source.charCodeAt(i))) i++; + out[source.slice(nameStart, i)] = true; + continue; + } + i++; } +} - runInNewContext(sandbox, options) { - return runInNewContext(this._code, sandbox, options); +function sourceTextModuleCollectDeclaredNames(source, start, end, out) { + let i = start; + while (i < end) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < end && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if ((source.startsWith('let', i) && hasIdentifierBoundary(source, i, i + 3)) + || (source.startsWith('var', i) && hasIdentifierBoundary(source, i, i + 3)) + || (source.startsWith('const', i) && hasIdentifierBoundary(source, i, i + 5))) { + const keywordEnd = source.startsWith('const', i) ? i + 5 : i + 3; + const statementEnd = findSourceTextModuleStatementEnd(source, keywordEnd); + sourceTextModuleCollectDeclarationListNames(source, keywordEnd, statementEnd, out); + i = statementEnd; + continue; + } + if ((source.startsWith('function', i) && hasIdentifierBoundary(source, i, i + 8)) + || (source.startsWith('class', i) && hasIdentifierBoundary(source, i, i + 5))) { + const keywordEnd = source.startsWith('function', i) ? i + 8 : i + 5; + const nameStart = skipWhitespaceAndComments(source, keywordEnd); + let nameEnd = nameStart; + while (nameEnd < end && isIdentifierChar(source.charCodeAt(nameEnd))) nameEnd++; + if (nameEnd > nameStart) out[source.slice(nameStart, nameEnd)] = true; + const declaration = sourceTextModuleDeclarationBody(source, i); + i = declaration ? declaration.bodyEnd + 1 : keywordEnd; + continue; + } + i++; } +} - runInContext(context, options) { - return runInContext(this._code, context, options); +function sourceTextModuleCollectDeclarationListNames(source, start, end, out) { + const declarations = source.slice(skipWhitespaceAndComments(source, start), end).replace(/;\s*$/, ''); + const declarators = splitDeclarators(declarations); + for (let i = 0; i < declarators.length; i++) { + const eq = declarators[i].indexOf('='); + const name = (eq === -1 ? declarators[i] : declarators[i].slice(0, eq)).trim(); + if (identifierPattern.test(name)) out[name] = true; } +} - runInThisContext(options) { - return runInThisContext(this._code, options); +function sourceTextModuleFindMatchingBrace(source, open) { + let depth = 1; + let i = open + 1; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x7b) depth++; + else if (ch === 0x7d) { + depth--; + if (depth === 0) return i; + } + i++; } + return source.length - 1; +} - createCachedData() { - return new Uint8Array(0); +function sourceTextModuleAssignmentStatementEnd(source, start) { + let i = start; + let depth = 0; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x2f && (regexCanFollow(source, i) || (regexCanFollowParen(source, i) && isLikelyRegexLiteral(source, i)))) { + i = skipRegexLiteral(source, i); + continue; + } + if (ch === 0x28 || ch === 0x5b || ch === 0x7b) { + depth++; + } else if ((ch === 0x29 || ch === 0x5d || ch === 0x7d) && depth > 0) { + depth--; + } + if (depth === 0 && ch === 0x3b) return i + 1; + if (depth === 0 && (ch === 0x0a || ch === 0x0d)) { + const previous = previousSignificantChar(source, i); + const next = nextSignificantChar(source, i + 1); + if (!sourceTextModuleExpressionContinues(previous, next)) return i; + } + i++; } + return source.length; } -export class SourceTextModule { - constructor(code, options) { - this._source = String(code); - this._status = 'unlinked'; +function sourceTextModuleExpressionContinues(previous, next) { + if (previous === 0x2b || previous === 0x2d || previous === 0x2a || previous === 0x2f || + previous === 0x25 || previous === 0x26 || previous === 0x7c || previous === 0x5e || + previous === 0x3f || previous === 0x3a || previous === 0x2c || previous === 0x3d || + previous === 0x3c || previous === 0x3e || previous === 0x21 || previous === 0x7e) { + return true; + } + return next === 0x2b || next === 0x2d || next === 0x2a || next === 0x2f || + next === 0x25 || next === 0x26 || next === 0x7c || next === 0x5e || + next === 0x3f || next === 0x3a || next === 0x2c || next === 0x2e || + next === 0x28 || next === 0x5b; +} - const declaredBindings = parseSourceTextModuleBindings(this._source); - this._bindings = Object.create(null); - this._names = []; +function sourceTextModuleAssignmentOperatorEnd(source, pos) { + const ch = source.charCodeAt(pos); + const next = source.charCodeAt(pos + 1); + if (ch === 0x3d && next !== 0x3d && next !== 0x3e) return pos + 1; + if ((ch === 0x2b || ch === 0x2d || ch === 0x2a || ch === 0x2f || ch === 0x25 || + ch === 0x26 || ch === 0x7c || ch === 0x5e) && next === 0x3d) { + return pos + 2; + } + if ((ch === 0x3c || ch === 0x3e) && next === ch && source.charCodeAt(pos + 2) === 0x3d) { + return pos + 3; + } + if (ch === 0x2a && next === 0x2a && source.charCodeAt(pos + 2) === 0x3d) { + return pos + 3; + } + if (ch === 0x3f && next === 0x3f && source.charCodeAt(pos + 2) === 0x3d) { + return pos + 3; + } + if (ch === 0x26 && next === 0x26 && source.charCodeAt(pos + 2) === 0x3d) { + return pos + 3; + } + if (ch === 0x7c && next === 0x7c && source.charCodeAt(pos + 2) === 0x3d) { + return pos + 3; + } + return -1; +} - for (let i = 0; i < declaredBindings.length; i++) { - const binding = declaredBindings[i]; - this._names.push(binding.name); - this._bindings[binding.name] = { - kind: binding.kind, - initialized: binding.kind === 'var', - value: undefined, - }; - } +function sourceTextModuleUpdateOperatorEnd(source, start, end) { + const before = previousSignificantIndex(source, start); + if (before >= 1 && source.charCodeAt(before) === 0x2b && source.charCodeAt(before - 1) === 0x2b) return end; + if (before >= 1 && source.charCodeAt(before) === 0x2d && source.charCodeAt(before - 1) === 0x2d) return end; + const after = skipWhitespaceAndComments(source, end); + if (source.charCodeAt(after) === 0x2b && source.charCodeAt(after + 1) === 0x2b) return after + 2; + if (source.charCodeAt(after) === 0x2d && source.charCodeAt(after + 1) === 0x2d) return after + 2; + return -1; +} - this._evaluateSource = compileSourceTextModuleEvaluator(this._source, this._names); - this._namespace = createModuleNamespace(this); +function analyzeSourceTextModule(source) { + const dependencies = []; + const bySpecifier = Object.create(null); + const bindings = []; + const edits = []; + let sawExportKeyword = false; + let sawSupportedExport = false; + let i = 0; + + function getDependency(specifier) { + return sourceTextModuleGetDependency(dependencies, bySpecifier, specifier); } - get status() { - return this._status; + function isModuleItemBoundary(pos) { + const previous = previousSignificantChar(source, pos); + return previous === 0 || previous === 0x3b || previous === 0x7b || previous === 0x7d; } - get namespace() { - if (this._status === 'unlinked') { - throw new Error('Module status must be linked'); + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteralWithExpressions(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x2f && (regexCanFollow(source, i) || (regexCanFollowParen(source, i) && isLikelyRegexLiteral(source, i)))) { + i = skipRegexLiteral(source, i); + continue; + } + + if (source.startsWith('import', i) && hasIdentifierBoundary(source, i, i + 6) && isModuleItemBoundary(i)) { + const afterImport = skipWhitespaceAndComments(source, i + 6); + if (source.charCodeAt(afterImport) === 0x28) { + i = afterImport + 1; + continue; + } + + const sideEffect = sourceTextModuleReadStringSpecifier(source, afterImport); + if (sideEffect) { + const end = findSourceTextModuleStatementEnd(source, sideEffect.end); + const dependency = getDependency(sideEffect.value); + sourceTextModuleSetAttributes(dependency, sourceTextModuleParseImportAttributes(source, sideEffect.end, end)); + edits.push({ start: i, end, replacement: '' }); + i = end; + continue; + } + + const fromIndex = sourceTextModuleFindFrom(source, afterImport, source.length); + if (fromIndex >= 0) { + const specifierStart = skipWhitespaceAndComments(source, fromIndex + 4); + const specifier = sourceTextModuleReadStringSpecifier(source, specifierStart); + if (specifier) { + const importStatementEnd = findSourceTextModuleStatementEnd(source, specifier.end); + const dependency = getDependency(specifier.value); + const names = sourceTextModuleParseImportClause(source, afterImport, fromIndex); + for (let j = 0; j < names.length; j++) { + sourceTextModuleAddImportName(dependency, names[j].imported, names[j].local); + } + sourceTextModuleSetAttributes(dependency, sourceTextModuleParseImportAttributes(source, specifier.end, importStatementEnd)); + edits.push({ start: i, end: importStatementEnd, replacement: '' }); + i = importStatementEnd; + continue; + } + } + } + + if (source.startsWith('export', i) && hasIdentifierBoundary(source, i, i + 6) && isModuleItemBoundary(i)) { + const afterExport = skipWhitespaceAndComments(source, i + 6); + if (source.startsWith('const', afterExport) && hasIdentifierBoundary(source, afterExport, afterExport + 5) + || source.startsWith('let', afterExport) && hasIdentifierBoundary(source, afterExport, afterExport + 3) + || source.startsWith('var', afterExport) && hasIdentifierBoundary(source, afterExport, afterExport + 3)) { + sawExportKeyword = true; + sawSupportedExport = true; + const kindEnd = source.startsWith('const', afterExport) ? afterExport + 5 : afterExport + 3; + const kind = source.slice(afterExport, kindEnd); + const statementEnd = findSourceTextModuleStatementEnd(source, kindEnd); + const declarations = source.slice(skipWhitespaceAndComments(source, kindEnd), statementEnd).replace(/;\s*$/, ''); + const declarators = splitDeclarators(declarations); + for (let j = 0; j < declarators.length; j++) { + const declarator = declarators[j]; + const eq = declarator.indexOf('='); + const bindingName = (eq === -1 ? declarator : declarator.slice(0, eq)).trim(); + if (!identifierPattern.test(bindingName)) { + throw new SyntaxError('Unsupported export declaration in vm.SourceTextModule'); + } + bindings.push({ name: bindingName, localName: bindingName, kind }); + } + edits.push({ start: i, end: afterExport, replacement: '' }); + for (let j = 0; j < declarators.length; j++) { + const bindingName = bindings[bindings.length - declarators.length + j].name; + edits.push({ start: statementEnd, end: statementEnd, replacement: sourceTextModuleExportSyncSource(bindingName, bindingName) }); + } + i = statementEnd; + continue; + } + if (source.startsWith('default', afterExport) && hasIdentifierBoundary(source, afterExport, afterExport + 7)) { + sawExportKeyword = true; + sawSupportedExport = true; + const valueStart = skipWhitespaceAndComments(source, afterExport + 7); + const defaultLocal = chooseInternalBindingName(source, '__wasm_rquickjs_vm_default_export'); + if (source.startsWith('function', valueStart) && hasIdentifierBoundary(source, valueStart, valueStart + 8) + || source.startsWith('class', valueStart) && hasIdentifierBoundary(source, valueStart, valueStart + 5)) { + const keywordEnd = source.startsWith('function', valueStart) ? valueStart + 8 : valueStart + 5; + const nameStart = skipWhitespaceAndComments(source, keywordEnd); + let nameEnd = nameStart; + while (nameEnd < source.length && isIdentifierChar(source.charCodeAt(nameEnd))) nameEnd++; + const declarationName = source.slice(nameStart, nameEnd); + const statementEnd = findSourceTextModuleStatementEnd(source, valueStart); + if (declarationName) { + bindings.push({ name: 'default', localName: declarationName, kind: 'const' }); + edits.push({ start: i, end: valueStart, replacement: '' }); + edits.push({ start: statementEnd, end: statementEnd, replacement: sourceTextModuleExportSyncSource('default', declarationName) }); + } else { + bindings.push({ name: 'default', localName: defaultLocal, kind: 'const' }); + edits.push({ start: i, end: valueStart, replacement: 'const ' + defaultLocal + ' = ' }); + edits.push({ start: statementEnd, end: statementEnd, replacement: sourceTextModuleExportSyncSource('default', defaultLocal) }); + } + i = statementEnd; + continue; + } + const statementEnd = findSourceTextModuleStatementEnd(source, valueStart); + bindings.push({ name: 'default', localName: defaultLocal, kind: 'const' }); + edits.push({ start: i, end: valueStart, replacement: 'const ' + defaultLocal + ' = ' }); + edits.push({ start: statementEnd, end: statementEnd, replacement: sourceTextModuleExportSyncSource('default', defaultLocal) }); + i = statementEnd; + continue; + } + if (source.charCodeAt(afterExport) === 0x2a) { + const fromIndex = sourceTextModuleFindFrom(source, afterExport + 1, source.length); + if (fromIndex >= 0) { + const specifierStart = skipWhitespaceAndComments(source, fromIndex + 4); + const specifier = sourceTextModuleReadStringSpecifier(source, specifierStart); + if (specifier) { + const statementEnd = findSourceTextModuleStatementEnd(source, specifier.end); + sawExportKeyword = true; + sawSupportedExport = true; + const dependency = getDependency(specifier.value); + dependency.exportAll = true; + sourceTextModuleSetAttributes(dependency, sourceTextModuleParseImportAttributes(source, specifier.end, statementEnd)); + edits.push({ start: i, end: statementEnd, replacement: '' }); + i = statementEnd; + continue; + } + } + sawExportKeyword = true; + } + if (source.charCodeAt(afterExport) === 0x7b + || source.startsWith('class', afterExport) && hasIdentifierBoundary(source, afterExport, afterExport + 5) + || source.startsWith('function', afterExport) && hasIdentifierBoundary(source, afterExport, afterExport + 8)) { + sawExportKeyword = true; + } + } + + i++; + } + + if (sawExportKeyword && !sawSupportedExport) { + throw new SyntaxError('Unsupported export declaration in vm.SourceTextModule'); + } + + return { + bindings, + dependencies, + executableSource: applySourceTextModuleEdits(source, edits), + }; +} + +function sourceTextModuleHasImportedNames(dependencies) { + for (let i = 0; i < dependencies.length; i++) { + for (let j = 0; j < dependencies[i].names.length; j++) { + return true; + } + } + return false; +} + +function compileSourceTextModuleEvaluator(source, exportBindings, dependencies, importMetaName, exportCellsName, usesImportMeta) { + const hasImportedNames = sourceTextModuleHasImportedNames(dependencies); + const executableSource = source; + const exportObjectEntries = exportBindings.map(function(binding) { + return JSON.stringify(binding.name) + ': ' + binding.localName; + }).join(', '); + + if (hasImportedNames) { + const importsParameterName = chooseInternalBindingName(source, '__wasm_rquickjs_vm_imports'); + if (usesImportMeta) { + return new Function(importsParameterName, importMetaName, exportCellsName, 'with (' + importsParameterName + ') {\n' + executableSource + '\nreturn { ' + exportObjectEntries + ' };\n}'); + } + return new Function(importsParameterName, exportCellsName, 'with (' + importsParameterName + ') {\n' + executableSource + '\nreturn { ' + exportObjectEntries + ' };\n}'); + } + if (usesImportMeta) { + return new Function(importMetaName, exportCellsName, '"use strict";\n' + executableSource + '\nreturn { ' + exportObjectEntries + ' };'); + } + return new Function(exportCellsName, '"use strict";\n' + executableSource + '\nreturn { ' + exportObjectEntries + ' };'); +} + +function rewriteImportMetaForEvaluation(code, replacementSource) { + code = String(code); + let changed = false; + let out = ''; + let last = 0; + let i = 0; + while (i < code.length) { + const ch = code.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(code, i, ch); + continue; + } + if (ch === 0x60) { + const template = rewriteImportMetaTemplateLiteral(code, i, replacementSource); + if (template.changed) { + changed = true; + out += code.slice(last, i) + template.text; + last = template.end; + } + i = template.end; + continue; + } + if (ch === 0x2f && code.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < code.length && code.charCodeAt(i) !== 0x0a && code.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && code.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(code, i); + continue; + } + if (ch === 0x2f && (regexCanFollow(code, i) || (regexCanFollowParen(code, i) && isLikelyRegexLiteral(code, i)))) { + i = skipRegexLiteral(code, i); + continue; + } + if (code.startsWith('import', i) + && hasIdentifierBoundary(code, i, i + 6) + && previousSignificantChar(code, i) !== 0x2e + && previousSignificantChar(code, i) !== 0x23) { + const dot = skipWhitespaceAndComments(code, i + 6); + if (code.charCodeAt(dot) === 0x2e) { + const meta = skipWhitespaceAndComments(code, dot + 1); + if (code.startsWith('meta', meta) && hasIdentifierBoundary(code, meta, meta + 4)) { + changed = true; + out += code.slice(last, i) + replacementSource; + last = meta + 4; + i = meta + 4; + continue; + } + } + } + i++; + } + if (last === 0) return { code, changed: false }; + return { code: out + code.slice(last), changed }; +} + +function rewriteImportMetaTemplateLiteral(source, start, replacementSource) { + let i = start + 1; + let out = '`'; + let chunkStart = i; + let changed = false; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x5c) { + i += 2; + continue; + } + if (ch === 0x60) { + out += source.slice(chunkStart, i + 1); + return { end: i + 1, text: out, changed }; + } + if (ch === 0x24 && source.charCodeAt(i + 1) === 0x7b) { + out += source.slice(chunkStart, i + 2); + const expressionStart = i + 2; + const expressionEnd = findTemplateExpressionEnd(source, expressionStart); + if (expressionEnd === -1) { + out += source.slice(expressionStart); + return { end: source.length, text: out, changed }; + } + const expression = source.slice(expressionStart, expressionEnd); + const rewritten = rewriteImportMetaForEvaluation(expression, replacementSource); + if (rewritten.changed) changed = true; + out += rewritten.code + '}'; + i = expressionEnd + 1; + chunkStart = i; + continue; + } + i++; + } + out += source.slice(chunkStart); + return { end: source.length, text: out, changed }; +} + +function chooseInternalBindingName(source, baseName) { + let name = baseName; + let suffix = 0; + while (source.indexOf(name) !== -1) { + suffix++; + name = baseName + '_' + suffix; + } + return name; +} + +function createModuleNamespace(module) { + const namespaceTarget = Object.create(null); + const names = module._names.slice().sort(); + + for (let i = 0; i < names.length; i++) { + const exportName = names[i]; + Object.defineProperty(namespaceTarget, exportName, { + get: function() { + return sourceTextModuleBindingValue(module._bindings[exportName], exportName); + }, + enumerable: true, + configurable: false, + }); + } + + // QuickJS does not expose virtual export keys from this proxy via + // Object.getOwnPropertyNames() while bindings are uninitialized. + // Store names out-of-band so util.inspect can still enumerate exports. + Object.defineProperty(namespaceTarget, moduleNamespaceExportsSymbol, { + value: names.slice(), + enumerable: false, + writable: false, + configurable: false, + }); + Object.defineProperty(namespaceTarget, moduleNamespaceBindingsSymbol, { + value: module._bindings, + enumerable: false, + writable: false, + configurable: false, + }); + Object.defineProperty(namespaceTarget, moduleNamespaceBrandSymbol, { + value: true, + enumerable: false, + writable: false, + configurable: false, + }); + + Object.defineProperty(namespaceTarget, Symbol.toStringTag, { + value: 'Module', + enumerable: false, + writable: false, + configurable: true, + }); + + return new Proxy(namespaceTarget, { + ownKeys: function() { + return names.concat([Symbol.toStringTag]); + }, + has: function(_target, prop) { + if (typeof prop === 'string' && module._bindings[prop] !== undefined) { + return true; + } + return prop in namespaceTarget; + }, + get: function(_target, prop, receiver) { + if (typeof prop === 'string' && module._bindings[prop] !== undefined) { + return sourceTextModuleBindingValue(module._bindings[prop], prop); + } + return Reflect.get(namespaceTarget, prop, receiver); + }, + getOwnPropertyDescriptor: function(_target, prop) { + return Object.getOwnPropertyDescriptor(namespaceTarget, prop); + }, + }); +} + +function sourceTextModuleBindingValue(binding, name) { + if (!binding.initialized) { + throw new ReferenceError(name + ' is not initialized'); + } + if (binding.kind === 'reexport') { + return binding.module.namespace[binding.importName]; + } + return binding.value; +} + +function createIndirectEvalSource(code) { + return '(0, eval)(' + JSON.stringify(code) + ')'; +} + +function createSandboxEvalSource(code, helperName, sandboxKeys, descriptorHelperName, symbolHelperName, deletedGlobalKeys) { + const sandboxKeyLiterals = []; + for (let i = 0; i < sandboxKeys.length; i++) { + sandboxKeyLiterals.push(JSON.stringify(sandboxKeys[i])); + } + const deletedGlobalKeyLiterals = []; + for (let i = 0; i < deletedGlobalKeys.length; i++) { + deletedGlobalKeyLiterals.push(JSON.stringify(deletedGlobalKeys[i])); + } + return '(() => {' + + 'const __wasm_rquickjs_vm_Object = ({}).constructor;' + + 'const __wasm_rquickjs_vm_create = __wasm_rquickjs_vm_Object.create;' + + 'const __wasm_rquickjs_vm_names_of = __wasm_rquickjs_vm_Object.getOwnPropertyNames;' + + 'const __wasm_rquickjs_vm_symbols_of = __wasm_rquickjs_vm_Object.getOwnPropertySymbols;' + + 'const __wasm_rquickjs_vm_get_desc = __wasm_rquickjs_vm_Object.getOwnPropertyDescriptor;' + + 'const __wasm_rquickjs_vm_define = __wasm_rquickjs_vm_Object.defineProperty;' + + 'const __wasm_rquickjs_vm_is = __wasm_rquickjs_vm_Object.is;' + + 'const __wasm_rquickjs_vm_same_desc = (__wasm_rquickjs_vm_a, __wasm_rquickjs_vm_b) => __wasm_rquickjs_vm_a === __wasm_rquickjs_vm_b || (__wasm_rquickjs_vm_a !== undefined && __wasm_rquickjs_vm_b !== undefined && __wasm_rquickjs_vm_is(__wasm_rquickjs_vm_a.value, __wasm_rquickjs_vm_b.value) && __wasm_rquickjs_vm_is(__wasm_rquickjs_vm_a.get, __wasm_rquickjs_vm_b.get) && __wasm_rquickjs_vm_is(__wasm_rquickjs_vm_a.set, __wasm_rquickjs_vm_b.set) && __wasm_rquickjs_vm_a.writable === __wasm_rquickjs_vm_b.writable && __wasm_rquickjs_vm_a.enumerable === __wasm_rquickjs_vm_b.enumerable && __wasm_rquickjs_vm_a.configurable === __wasm_rquickjs_vm_b.configurable);' + + 'const __wasm_rquickjs_vm_descriptors = ' + (descriptorHelperName ? 'globalThis[' + JSON.stringify(descriptorHelperName) + ']' : 'undefined') + ';' + + (descriptorHelperName ? 'delete globalThis[' + JSON.stringify(descriptorHelperName) + '];' : '') + + 'const __wasm_rquickjs_vm_symbol_bindings = ' + (symbolHelperName ? 'globalThis[' + JSON.stringify(symbolHelperName) + ']' : 'undefined') + ';' + + (symbolHelperName ? 'delete globalThis[' + JSON.stringify(symbolHelperName) + '];' : '') + + 'const __wasm_rquickjs_vm_sandbox_key_list = [' + sandboxKeyLiterals.join(',') + '];' + + 'const __wasm_rquickjs_vm_deleted_global_list = [' + deletedGlobalKeyLiterals.join(',') + '];' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_deleted_global_list.length; __wasm_rquickjs_vm_i++) {' + + 'delete globalThis[__wasm_rquickjs_vm_deleted_global_list[__wasm_rquickjs_vm_i]];' + + '}' + + 'if (__wasm_rquickjs_vm_descriptors !== undefined) {' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_sandbox_key_list.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_key = __wasm_rquickjs_vm_sandbox_key_list[__wasm_rquickjs_vm_i];' + + 'const __wasm_rquickjs_vm_desc = __wasm_rquickjs_vm_descriptors[__wasm_rquickjs_vm_key];' + + 'if (__wasm_rquickjs_vm_desc !== undefined) {' + + '__wasm_rquickjs_vm_define(globalThis, __wasm_rquickjs_vm_key, __wasm_rquickjs_vm_desc);' + + '}' + + '}' + + '}' + + 'const __wasm_rquickjs_vm_sandbox_symbol_list = __wasm_rquickjs_vm_symbol_bindings === undefined ? [] : __wasm_rquickjs_vm_symbol_bindings.keys;' + + 'const __wasm_rquickjs_vm_sandbox_symbol_descriptors = __wasm_rquickjs_vm_symbol_bindings === undefined ? [] : __wasm_rquickjs_vm_symbol_bindings.descriptors;' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_sandbox_symbol_list.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_symbol = __wasm_rquickjs_vm_sandbox_symbol_list[__wasm_rquickjs_vm_i];' + + 'const __wasm_rquickjs_vm_desc = __wasm_rquickjs_vm_sandbox_symbol_descriptors[__wasm_rquickjs_vm_i];' + + 'if (__wasm_rquickjs_vm_desc !== undefined) {' + + '__wasm_rquickjs_vm_define(globalThis, __wasm_rquickjs_vm_symbol, __wasm_rquickjs_vm_desc);' + + '}' + + '}' + + 'const __wasm_rquickjs_vm_baseline = __wasm_rquickjs_vm_create(null);' + + 'const __wasm_rquickjs_vm_baseline_keys = __wasm_rquickjs_vm_names_of(globalThis);' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_baseline_keys.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_key = __wasm_rquickjs_vm_baseline_keys[__wasm_rquickjs_vm_i];' + + '__wasm_rquickjs_vm_baseline[__wasm_rquickjs_vm_key] = __wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_key);' + + '}' + + 'const __wasm_rquickjs_vm_sandbox_keys = __wasm_rquickjs_vm_create(null);' + + 'const __wasm_rquickjs_vm_represented_sandbox_keys = __wasm_rquickjs_vm_create(null);' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_sandbox_key_list.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_key = __wasm_rquickjs_vm_sandbox_key_list[__wasm_rquickjs_vm_i];' + + '__wasm_rquickjs_vm_sandbox_keys[__wasm_rquickjs_vm_key] = true;' + + 'if (__wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_key) !== undefined) {' + + '__wasm_rquickjs_vm_represented_sandbox_keys[__wasm_rquickjs_vm_key] = true;' + + '}' + + '}' + + 'const __wasm_rquickjs_vm_represented_symbol_indexes = [];' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_sandbox_symbol_list.length; __wasm_rquickjs_vm_i++) {' + + 'if (__wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_sandbox_symbol_list[__wasm_rquickjs_vm_i]) !== undefined) {' + + '__wasm_rquickjs_vm_represented_symbol_indexes[__wasm_rquickjs_vm_i] = true;' + + '}' + + '}' + + 'let __wasm_rquickjs_vm_result;' + + 'let __wasm_rquickjs_vm_thrown;' + + 'let __wasm_rquickjs_vm_ok = true;' + + 'try {' + + '__wasm_rquickjs_vm_result = ' + createIndirectEvalSource(code) + ';' + + '} catch (__wasm_rquickjs_vm_error) {' + + '__wasm_rquickjs_vm_ok = false;' + + '__wasm_rquickjs_vm_thrown = __wasm_rquickjs_vm_error;' + + '}' + + 'const __wasm_rquickjs_vm_updates = [];' + + 'const __wasm_rquickjs_vm_keys = __wasm_rquickjs_vm_names_of(globalThis);' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_keys.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_key = __wasm_rquickjs_vm_keys[__wasm_rquickjs_vm_i];' + + (helperName ? 'if (__wasm_rquickjs_vm_key === ' + JSON.stringify(helperName) + ') continue;' : '') + + 'const __wasm_rquickjs_vm_desc = __wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_key);' + + 'if (!__wasm_rquickjs_vm_represented_sandbox_keys[__wasm_rquickjs_vm_key] && __wasm_rquickjs_vm_same_desc(__wasm_rquickjs_vm_baseline[__wasm_rquickjs_vm_key], __wasm_rquickjs_vm_desc)) continue;' + + '__wasm_rquickjs_vm_updates[__wasm_rquickjs_vm_updates.length] = [__wasm_rquickjs_vm_key, true, __wasm_rquickjs_vm_desc];' + + '}' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_baseline_keys.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_key = __wasm_rquickjs_vm_baseline_keys[__wasm_rquickjs_vm_i];' + + 'if (__wasm_rquickjs_vm_represented_sandbox_keys[__wasm_rquickjs_vm_key]) continue;' + + 'if (__wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_key) === undefined) {' + + '__wasm_rquickjs_vm_updates[__wasm_rquickjs_vm_updates.length] = [__wasm_rquickjs_vm_key, false, undefined, true];' + + '}' + + '}' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_sandbox_key_list.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_key = __wasm_rquickjs_vm_sandbox_key_list[__wasm_rquickjs_vm_i];' + + 'if (!__wasm_rquickjs_vm_represented_sandbox_keys[__wasm_rquickjs_vm_key]) continue;' + + 'if (__wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_key) === undefined) {' + + '__wasm_rquickjs_vm_updates[__wasm_rquickjs_vm_updates.length] = [__wasm_rquickjs_vm_key, false, undefined, false];' + + '}' + + '}' + + 'for (let __wasm_rquickjs_vm_i = 0; __wasm_rquickjs_vm_i < __wasm_rquickjs_vm_sandbox_symbol_list.length; __wasm_rquickjs_vm_i++) {' + + 'const __wasm_rquickjs_vm_symbol = __wasm_rquickjs_vm_sandbox_symbol_list[__wasm_rquickjs_vm_i];' + + 'if (!__wasm_rquickjs_vm_represented_symbol_indexes[__wasm_rquickjs_vm_i]) continue;' + + 'const __wasm_rquickjs_vm_symbol_desc = __wasm_rquickjs_vm_get_desc(globalThis, __wasm_rquickjs_vm_symbol);' + + 'if (__wasm_rquickjs_vm_symbol_desc === undefined) {' + + '__wasm_rquickjs_vm_updates[__wasm_rquickjs_vm_updates.length] = [__wasm_rquickjs_vm_symbol, false, undefined];' + + '} else {' + + '__wasm_rquickjs_vm_updates[__wasm_rquickjs_vm_updates.length] = [__wasm_rquickjs_vm_symbol, true, __wasm_rquickjs_vm_symbol_desc];' + + '}' + + '}' + + 'return [__wasm_rquickjs_vm_ok, __wasm_rquickjs_vm_ok ? __wasm_rquickjs_vm_result : __wasm_rquickjs_vm_thrown, __wasm_rquickjs_vm_updates];' + + '})()'; +} + +function applySandboxUpdates(sandbox, evaluation) { + const defineProperty = ({}).constructor.defineProperty; + const ok = evaluation[0]; + const result = evaluation[1]; + if (!sandbox || typeof sandbox !== 'object') { + if (!ok) throw result; + return result; + } + const updates = evaluation[2]; + let deletedGlobals = contextDeletedGlobals.get(sandbox); + for (let i = 0; i < updates.length; i++) { + const update = updates[i]; + try { + if (update[1]) { + defineProperty(sandbox, update[0], update[2]); + if (deletedGlobals) { + delete deletedGlobals[update[0]]; + } + } else { + delete sandbox[update[0]]; + if (update[3]) { + if (!deletedGlobals) { + deletedGlobals = Object.create(null); + contextDeletedGlobals.set(sandbox, deletedGlobals); + } + deletedGlobals[update[0]] = true; + } else if (deletedGlobals) { + delete deletedGlobals[update[0]]; + } + } + } catch (_writeBackError) { + if (!ok) throw result; + } + } + if (!ok) throw result; + return result; +} + +export function runInNewContext(code, sandbox, options) { + if (code === undefined || code === null) code = ''; + code = String(code); + if (sandbox !== undefined && (sandbox === null || typeof sandbox !== 'object')) { + throwInvalidArgType('object', 'object', sandbox); + } + options = normalizeRunOptions(options); + validateVmRunOptions(options); + validateImportModuleDynamicallyOption(options.importModuleDynamically); + let helperName; + if (options.importModuleDynamically === USE_MAIN_CONTEXT_DEFAULT_LOADER) { + const rewritten = rewriteDefaultLoaderDynamicImportsForEvaluation(code, referrerFilenameFromOptions(options)); + code = rewritten.code; + helperName = rewritten.helperName; + } else if (options.importModuleDynamically === undefined) { + const rewritten = rewriteMissingDynamicImportsForEvaluation(code); + code = rewritten.code; + helperName = rewritten.helperName; + } else if (typeof options.importModuleDynamically === 'function') { + const rewritten = vmModulesEnabled() + ? rewriteVmDynamicImportCallbackForEvaluation(code, options.importModuleDynamically, undefined) + : rewriteMissingDynamicImportFlagForEvaluation(code); + code = rewritten.code; + helperName = rewritten.helperName; + } + return evalVmRunWithFilename(() => evalCodeInNewContext(code, sandbox, helperName), options.filename); +} + +function evalCodeInNewContext(code, sandbox, helperName) { + if (isEmptyVmSourceText(code)) return undefined; + + const keys = []; + const values = []; + let sandboxKeys = []; + let descriptorHelperName; + let symbolHelperName; + + let deletedGlobalKeys = []; + + if (sandbox && typeof sandbox === 'object') { + const bindings = collectSandboxBindings(sandbox); + sandboxKeys = bindings.keys; + deletedGlobalKeys = bindings.deletedGlobalKeys; + for (let i = 0; i < bindings.keys.length; i++) { + keys.push(bindings.keys[i]); + values.push(bindings.values[i]); + } + if (bindings.keys.length > 0) { + descriptorHelperName = chooseSandboxDescriptorsHelperName(code, bindings.keys); + keys.push(descriptorHelperName); + values.push(bindings.descriptors); + } + if (bindings.symbolKeys.length > 0) { + symbolHelperName = chooseSandboxSymbolsHelperName(code, bindings.keys); + keys.push(symbolHelperName); + values.push({ keys: bindings.symbolKeys, descriptors: bindings.symbolDescriptors }); + } + } + if (helperName) { + keys.push(helperName); + values.push(globalThis[helperName]); + } + + return applySandboxUpdates(sandbox, evalInNewContext(createSandboxEvalSource(code, helperName, sandboxKeys, descriptorHelperName, symbolHelperName, deletedGlobalKeys), keys, values)); +} + +function createContextForRunInNewContext(sandbox) { + if (sandbox === undefined) { + return createContext({}); + } + if (sandbox === null || typeof sandbox !== 'object') { + throwInvalidArgType('object', 'object', sandbox); + } + return createContext(sandbox); +} + +function validateRunInNewContextPreSandboxOptions(options) { + if (options === null || typeof options !== 'object') { + return; + } + if (options.contextName !== undefined && typeof options.contextName !== 'string') { + throwInvalidPropertyType('options.contextName', 'string', options.contextName); + } + if (options.contextOrigin !== undefined && typeof options.contextOrigin !== 'string') { + throwInvalidPropertyType('options.contextOrigin', 'string', options.contextOrigin); + } + if (options.contextCodeGeneration !== undefined) { + if (options.contextCodeGeneration === null || typeof options.contextCodeGeneration !== 'object') { + throwInvalidPropertyType('options.contextCodeGeneration', 'object', options.contextCodeGeneration); + } + if (options.contextCodeGeneration.strings !== undefined && typeof options.contextCodeGeneration.strings !== 'boolean') { + throwInvalidPropertyType('options.contextCodeGeneration.strings', 'boolean', options.contextCodeGeneration.strings); + } + if (options.contextCodeGeneration.wasm !== undefined && typeof options.contextCodeGeneration.wasm !== 'boolean') { + throwInvalidPropertyType('options.contextCodeGeneration.wasm', 'boolean', options.contextCodeGeneration.wasm); + } + } +} + +function validateRunInNewContextPostSandboxOptions(options) { + if (options === null || typeof options !== 'object') { + return; + } + if (options.microtaskMode !== undefined && options.microtaskMode !== 'afterEvaluate') { + throw invalidArgValue("The property 'options.microtaskMode' must be one of: 'afterEvaluate', undefined. Received " + formatReceived(options.microtaskMode)); + } +} + +export function createContext(sandbox, options) { + if (sandbox === undefined || sandbox === null) { + sandbox = {}; + } + if (typeof sandbox !== 'object') { + throwInvalidArgType('object', 'object', sandbox); + } + options = validateOptionsObject(options); + contextIds.set(sandbox, contextIdCounter++); + contextOptions.set(sandbox, snapshotVmOptions(options)); + return sandbox; +} + +export function isContext(obj) { + if (obj === null || obj === undefined || typeof obj !== 'object') { + throwInvalidArgType('object', 'object', obj); + } + return isContextObject(obj); +} + +function isContextObject(obj) { + return obj != null && typeof obj === 'object' && contextIds.has(obj); +} + +export function runInContext(code, context, options) { + if (!isContext(context)) { + throwInvalidContextifiedObject(); + } + if (code === undefined || code === null) code = ''; + code = String(code); + options = normalizeRunOptions(options); + validateVmRunOptions(options); + validateImportModuleDynamicallyOption(options.importModuleDynamically); + let helperName; + if (options.importModuleDynamically === USE_MAIN_CONTEXT_DEFAULT_LOADER) { + const rewritten = rewriteDefaultLoaderDynamicImportsForEvaluation(code, referrerFilenameFromOptions(options)); + code = rewritten.code; + helperName = rewritten.helperName; + } else if (options.importModuleDynamically === undefined) { + const rewritten = rewriteMissingDynamicImportsForEvaluation(code); + code = rewritten.code; + helperName = rewritten.helperName; + } else if (typeof options.importModuleDynamically === 'function') { + const rewritten = vmModulesEnabled() + ? rewriteVmDynamicImportCallbackForEvaluation(code, options.importModuleDynamically, undefined) + : rewriteMissingDynamicImportFlagForEvaluation(code); + code = rewritten.code; + helperName = rewritten.helperName; + } + return evalVmRunWithFilename(() => evalCodeInContext(code, context, helperName), options.filename); +} + +function evalCodeInContext(code, context, helperName) { + if (isEmptyVmSourceText(code)) return undefined; + + const keys = []; + const values = []; + const bindings = collectSandboxBindings(context); + for (let i = 0; i < bindings.keys.length; i++) { + keys.push(bindings.keys[i]); + values.push(bindings.values[i]); + } + let descriptorHelperName; + let symbolHelperName; + if (bindings.keys.length > 0) { + descriptorHelperName = chooseSandboxDescriptorsHelperName(code, bindings.keys); + keys.push(descriptorHelperName); + values.push(bindings.descriptors); + } + if (bindings.symbolKeys.length > 0) { + symbolHelperName = chooseSandboxSymbolsHelperName(code, bindings.keys); + keys.push(symbolHelperName); + values.push({ keys: bindings.symbolKeys, descriptors: bindings.symbolDescriptors }); + } + if (helperName) { + keys.push(helperName); + values.push(globalThis[helperName]); + } + + return applySandboxUpdates(context, evalInNewContext(createSandboxEvalSource(code, helperName, bindings.keys, descriptorHelperName, symbolHelperName, bindings.deletedGlobalKeys), keys, values)); +} + +export function runInThisContext(code, options) { + if (code === undefined || code === null) return undefined; + code = String(code); + options = normalizeRunOptions(options); + validateVmRunOptions(options); + validateImportModuleDynamicallyOption(options.importModuleDynamically); + if (options.importModuleDynamically === USE_MAIN_CONTEXT_DEFAULT_LOADER) { + const filename = referrerFilenameFromOptions(options); + return evalVmRunWithFilename(() => evalWithFilename(rewriteDefaultLoaderDynamicImportsForEvaluation(code, filename).code, filename), filename); + } + if (options.importModuleDynamically === undefined) { + const rewritten = rewriteMissingDynamicImportsForEvaluation(code).code; + return evalVmRunWithFilename(() => evalWithFilename(rewritten, filenameForMainContextEval(options)), options.filename); + } + if (typeof options.importModuleDynamically === 'function') { + const rewritten = vmModulesEnabled() + ? rewriteVmDynamicImportCallbackForEvaluation(code, options.importModuleDynamically, undefined) + : rewriteMissingDynamicImportFlagForEvaluation(code); + return evalVmRunWithFilename(() => evalWithFilename(rewritten.code, filenameForMainContextEval(options)), options.filename); + } + return evalVmRunWithFilename(() => evalWithFilename(code, filenameForMainContextEval(options)), options.filename); +} + +function filenameForMainContextEval(options) { + return options.filename === undefined ? 'evalmachine.' : options.filename; +} + +function normalizeRunOptions(options) { + if (typeof options === 'string') { + return { filename: options }; + } + return validateOptionsObject(options); +} + +function validateVmRunOptions(options) { + if (options.filename !== undefined && typeof options.filename !== 'string') { + throwInvalidPropertyType('options.filename', 'string', options.filename); + } + validateInt32PropertyOption(options.lineOffset, 'options.lineOffset'); + validateInt32PropertyOption(options.columnOffset, 'options.columnOffset'); +} + +function evalVmRunWithFilename(fn, filename) { + try { + return fn(); + } catch (err) { + normalizeVmRunFilenameStack(err, filename); + throw err; + } +} + +function normalizeVmRunFilenameStack(err, filename) { + if (filename === undefined || !err || typeof err !== 'object') return; + err.stack = String(filename) + ':1\n' + (err.name || 'Error') + ': ' + (err.message || ''); +} + +export function compileFunction(code, params, options) { + if (typeof code !== 'string') { + throwInvalidArgType('code', 'string', code); + } + if (params === undefined) { + params = []; + } else if (!Array.isArray(params)) { + throwInvalidArgInstance('params', 'Array', params); + } + options = validateOptionsObject(options); + validateCompileFunctionOptions(options); + validateImportModuleDynamicallyOption(options.importModuleDynamically); + validateInt32PropertyOption(options.lineOffset, 'options.lineOffset'); + validateInt32PropertyOption(options.columnOffset, 'options.columnOffset'); + const sourceCode = code; + if (options.importModuleDynamically === USE_MAIN_CONTEXT_DEFAULT_LOADER) { + const filename = referrerFilenameFromOptions(options); + const paramList = params.map(String).join(','); + const source = '[function(' + paramList + '){' + rewriteDefaultLoaderDynamicImportsForEvaluation(code, filename).code + '\n}][0]'; + return finalizeCompileFunction(evaluateCompileFunctionSource(source, sourceCode, filename), sourceCode, params, options); + } + if (options.importModuleDynamically === undefined) { + code = rewriteMissingDynamicImportsForEvaluation(code).code; + } else if (typeof options.importModuleDynamically === 'function') { + if (vmModulesEnabled()) { + const referrer = { [vmDynamicImportReferrerSymbol]: undefined }; + code = rewriteVmDynamicImportCallbackForEvaluation(code, options.importModuleDynamically, referrer).code; + const fn = compileFunctionInContext(code, params, options); + const publicFunction = finalizeCompileFunction(fn, sourceCode, params, options); + referrer[vmDynamicImportReferrerSymbol] = publicFunction; + return publicFunction; + } + code = rewriteMissingDynamicImportFlagForEvaluation(code).code; + } + return finalizeCompileFunction(compileFunctionInContext(code, params, options), sourceCode, params, options); +} + +function validateCompileFunctionOptions(options) { + if (options.filename !== undefined && typeof options.filename !== 'string') { + throwInvalidPropertyType('options.filename', 'string', options.filename); + } + if (options.cachedData !== undefined && !ArrayBuffer.isView(options.cachedData)) { + throwInvalidPropertyInstance('options.cachedData', 'Buffer, TypedArray, or DataView', options.cachedData); + } + if (options.produceCachedData !== undefined && typeof options.produceCachedData !== 'boolean') { + throwInvalidPropertyType('options.produceCachedData', 'boolean', options.produceCachedData); + } + if (options.parsingContext !== undefined && !isContextObject(options.parsingContext)) { + throwInvalidPropertyInstance('options.parsingContext', 'Context', options.parsingContext); + } + if (options.contextExtensions !== undefined && !Array.isArray(options.contextExtensions)) { + throwInvalidPropertyInstance('options.contextExtensions', 'Array', options.contextExtensions); + } + if (Array.isArray(options.contextExtensions)) { + for (let i = 0; i < options.contextExtensions.length; i++) { + const value = options.contextExtensions[i]; + if (value === null || typeof value !== 'object') { + throwInvalidPropertyType('options.contextExtensions[' + i + ']', 'object', value); + } + } + } +} + +function validateScriptConstructorOptions(options) { + if (typeof options === 'string') { + return { filename: options }; + } + options = validateOptionsObject(options); + validateImportModuleDynamicallyOption(options.importModuleDynamically); + validateInt32PropertyOption(options.lineOffset, 'options.lineOffset'); + validateInt32PropertyOption(options.columnOffset, 'options.columnOffset'); + if (options.filename !== undefined && typeof options.filename !== 'string') { + throwInvalidPropertyType('options.filename', 'string', options.filename); + } + if (options.produceCachedData !== undefined && typeof options.produceCachedData !== 'boolean') { + throwInvalidPropertyType('options.produceCachedData', 'boolean', options.produceCachedData); + } + if (options.cachedData !== undefined && !ArrayBuffer.isView(options.cachedData)) { + throwInvalidPropertyInstance('options.cachedData', 'Buffer, TypedArray, or DataView', options.cachedData); + } + return options; +} + +function validateScriptRunOptions(options) { + options = validateOptionsObject(options); + if (options.timeout !== undefined) { + if (typeof options.timeout !== 'number') { + throwInvalidArgType('options.timeout', 'number', options.timeout); + } + if (!Number.isInteger(options.timeout)) { + throwOutOfRange('options.timeout', 'an integer', options.timeout); + } + if (options.timeout < 1 || options.timeout > 4294967295) { + throwOutOfRange('options.timeout', '>= 1 && <= 4294967295', options.timeout); + } + } + if (options.displayErrors !== undefined && typeof options.displayErrors !== 'boolean') { + throwInvalidPropertyType('options.displayErrors', 'boolean', options.displayErrors); + } + if (options.breakOnSigint !== undefined && typeof options.breakOnSigint !== 'boolean') { + throwInvalidPropertyType('options.breakOnSigint', 'boolean', options.breakOnSigint); + } + return options; +} + +function compileFunctionInContext(code, params, options) { + const source = '[function(' + params.map(String).join(',') + '){' + code + '\n}][0]'; + const keys = []; + const values = []; + + if (options.parsingContext !== undefined) { + collectContextBindings(options.parsingContext, keys, values); + } + if (Array.isArray(options.contextExtensions)) { + for (let i = 0; i < options.contextExtensions.length; i++) { + collectContextBindings(options.contextExtensions[i], keys, values); + } + } + if (keys.length > 0) { + try { + return evalInNewContext(createIndirectEvalSource(source), keys, values); + } catch (err) { + throw normalizeCompileFunctionSyntaxError(err, code); + } + } + return evaluateCompileFunctionSource(source, code); +} + +function evaluateCompileFunctionSource(source, code, filename) { + try { + if (filename !== undefined) { + return evalWithFilename(source, filename); + } + return (0, eval)(source); + } catch (err) { + throw normalizeCompileFunctionSyntaxError(err, code); + } +} + +function normalizeCompileFunctionSyntaxError(err, code) { + if (err && err.name === 'SyntaxError') { + const first = firstNonWhitespaceChar(code); + if (first === '}') { + return new SyntaxError("Unexpected token '}'"); + } + } + return err; +} + +function firstNonWhitespaceChar(value) { + for (let i = 0; i < value.length; i++) { + const ch = value.charCodeAt(i); + if (ch !== 0x20 && ch !== 0x09 && ch !== 0x0a && ch !== 0x0d) { + return value[i]; + } + } + return ''; +} + +function isEmptyVmSourceText(value) { + let i = 0; + let lineStart = true; + while (i < value.length) { + const ch = value.charCodeAt(i); + if (i === 0 && ch === 0x23 && value.charCodeAt(i + 1) === 0x21) { + i += 2; + while (i < value.length) { + const lineCh = value.charCodeAt(i); + if (isVmSourceLineTerminator(lineCh)) break; + i++; + } + continue; + } + if (ch === 0x2f && value.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < value.length) { + const lineCh = value.charCodeAt(i); + if (isVmSourceLineTerminator(lineCh)) break; + i++; + } + continue; + } + if (ch === 0x2f && value.charCodeAt(i + 1) === 0x2a) { + const end = value.indexOf('*/', i + 2); + if (end === -1) return false; + for (let j = i + 2; j < end; j++) { + if (isVmSourceLineTerminator(value.charCodeAt(j))) { + lineStart = true; + } + } + i = end + 2; + continue; + } + if (ch === 0x3c && value.charCodeAt(i + 1) === 0x21 && value.charCodeAt(i + 2) === 0x2d && value.charCodeAt(i + 3) === 0x2d) { + i += 4; + while (i < value.length) { + const lineCh = value.charCodeAt(i); + if (isVmSourceLineTerminator(lineCh)) break; + i++; + } + continue; + } + if (lineStart && ch === 0x2d && value.charCodeAt(i + 1) === 0x2d && value.charCodeAt(i + 2) === 0x3e) { + i += 3; + while (i < value.length) { + const lineCh = value.charCodeAt(i); + if (isVmSourceLineTerminator(lineCh)) break; + i++; + } + continue; + } + if (!isVmSourceWhitespace(ch)) return false; + if (isVmSourceLineTerminator(ch)) { + lineStart = true; + } + i++; + } + return true; +} + +function isVmSourceWhitespace(ch) { + return isVmSourceLineTerminator(ch) || + ch === 0x09 || + ch === 0x0b || + ch === 0x0c || + ch === 0x20 || + ch === 0xa0 || + ch === 0x1680 || + ch === 0x202f || + ch === 0x205f || + ch === 0x3000 || + ch === 0xfeff || + (ch >= 0x2000 && ch <= 0x200a); +} + +function isVmSourceLineTerminator(ch) { + return ch === 0x0a || ch === 0x0d || ch === 0x2028 || ch === 0x2029; +} + +function collectContextBindings(context, keys, values) { + const contextKeys = Object.keys(context); + for (let i = 0; i < contextKeys.length; i++) { + const key = contextKeys[i]; + keys.push(key); + values.push(context[key]); + } +} + +function collectSandboxBindings(sandbox) { + const descriptors = Object.create(null); + const keys = Object.getOwnPropertyNames(sandbox); + const values = []; + const deletedGlobals = contextDeletedGlobals.get(sandbox); + const deletedGlobalKeys = deletedGlobals ? Object.getOwnPropertyNames(deletedGlobals).filter((key) => keys.indexOf(key) === -1) : []; + for (let i = 0; i < keys.length; i++) { + const descriptor = Object.getOwnPropertyDescriptor(sandbox, keys[i]); + descriptors[keys[i]] = descriptor; + values.push(descriptor && 'value' in descriptor ? descriptor.value : undefined); + } + const symbolKeys = Object.getOwnPropertySymbols(sandbox); + const symbolDescriptors = []; + for (let i = 0; i < symbolKeys.length; i++) { + symbolDescriptors.push(Object.getOwnPropertyDescriptor(sandbox, symbolKeys[i])); + } + return { keys, values, descriptors, symbolKeys, symbolDescriptors, deletedGlobalKeys }; +} + +function finalizeCompileFunction(fn, code, params, options) { + fn = new Proxy(fn, { + apply(target, thisArg, args) { + try { + return Reflect.apply(target, thisArg, args); + } catch (err) { + normalizeCompileFunctionRuntimeStack(err, code, options); + throw err; + } + }, + }); + const renderedParams = params.map(String).join(', '); + Object.defineProperty(fn, 'toString', { + configurable: true, + value() { + return 'function (' + renderedParams + ') {\n' + code + '\n}'; + }, + }); + if (options.produceCachedData === true) { + const cachedData = new Uint8Array([0x77, 0x72, 0x71, 0x6a, code.length & 0xff]); + cachedData.__wasm_rquickjs_compile_function_source = code; + fn.cachedData = cachedData; + fn.cachedDataProduced = true; + } + if (options.cachedData !== undefined) { + fn.cachedDataRejected = options.cachedData.__wasm_rquickjs_compile_function_source !== code; + } + return fn; +} + +function normalizeCompileFunctionRuntimeStack(err, code, options) { + if (!err || typeof err.stack !== 'string' || err.stack.indexOf('') === -1) return; + const lineOffset = typeof options.lineOffset === 'number' ? options.lineOffset : 0; + const columnOffset = typeof options.columnOffset === 'number' ? options.columnOffset : 0; + const location = (typeof options.filename === 'string' && options.filename.length > 0) + ? options.filename + : ''; + const line = 1 + lineOffset; + const column = compileFunctionRuntimeColumn(code) + columnOffset; + const message = (err.name || 'Error') + ': ' + (err.message || ''); + err.stack = message + '\n at ' + location + ':' + line + ':' + column; +} + +function compileFunctionRuntimeColumn(code) { + let i = 0; + while (i < code.length) { + const ch = code.charCodeAt(i); + if (ch !== 0x20 && ch !== 0x09) break; + i++; + } + if (code.slice(i, i + 5) === 'throw') return i + 7; + return i + 1; +} + +function snapshotVmOptions(options) { + options = validateOptionsObject(options); + validateImportModuleDynamicallyOption(options.importModuleDynamically); + if (options.identifier !== undefined && typeof options.identifier !== 'string') { + throwInvalidPropertyType('options.identifier', 'string', options.identifier); + } + if (options.context !== undefined && !isContext(options.context)) { + throwInvalidPropertyType('options.context', 'vm.Context', options.context); + } + if (options.cachedData !== undefined && !ArrayBuffer.isView(options.cachedData)) { + const err = new TypeError('The "options.cachedData" property must be an instance of Buffer, TypedArray, or DataView.' + invalidArgTypeHelper(options.cachedData)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; + } + return Object.assign({}, options); +} + +function validateOptionsObject(options) { + if (options === undefined) return {}; + if (options === null || typeof options !== 'object' || Array.isArray(options)) { + throwInvalidArgType('options', 'object', options); + } + return options; +} + +function validateImportModuleDynamicallyOption(value) { + if (value === undefined || value === USE_MAIN_CONTEXT_DEFAULT_LOADER || typeof value === 'function') { + return; + } + throwInvalidPropertyType('options.importModuleDynamically', 'function', value); +} + +function validateInitializeImportMetaOption(value) { + if (value === undefined || typeof value === 'function') { + return; + } + throwInvalidPropertyType('options.initializeImportMeta', 'function', value); +} + +function validateInt32Option(value, name) { + if (value === undefined) return; + if (typeof value !== 'number') { + throwInvalidArgType(name, 'number', value); + } + if (!Number.isInteger(value)) { + throwOutOfRange(name, 'an integer', value); + } + if (value < -2147483648 || value > 2147483647) { + throwOutOfRange(name, '>= -2147483648 && <= 2147483647', value); + } +} + +function validateInt32PropertyOption(value, name) { + if (value === undefined) return; + if (typeof value !== 'number') { + throwInvalidPropertyType(name, 'number', value); + } + if (!Number.isInteger(value)) { + throwOutOfRange(name, 'an integer', value); + } + if (value < -2147483648 || value > 2147483647) { + throwOutOfRange(name, '>= -2147483648 && <= 2147483647', value); + } +} + +function hasIdentifierBoundary(source, start, end) { + const before = start > 0 ? source.charCodeAt(start - 1) : 0; + const after = end < source.length ? source.charCodeAt(end) : 0; + return !isIdentifierChar(before) && !isIdentifierChar(after); +} + +function isIdentifierChar(ch) { + return ch === 0x5f || ch === 0x24 || + (ch >= 0x30 && ch <= 0x39) || + (ch >= 0x41 && ch <= 0x5a) || + (ch >= 0x61 && ch <= 0x7a) || + ch >= 0x80; +} + +function isIdentifierStart(ch) { + return ch === 0x5f || ch === 0x24 || + (ch >= 0x41 && ch <= 0x5a) || + (ch >= 0x61 && ch <= 0x7a) || + ch >= 0x80; +} + +function skipWhitespaceAndComments(source, i) { + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d || ch === 0x0b || ch === 0x0c) { + i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i += 2; + while (i + 1 < source.length && !(source.charCodeAt(i) === 0x2a && source.charCodeAt(i + 1) === 0x2f)) i++; + i = Math.min(i + 2, source.length); + continue; + } + break; + } + return i; +} + +function skipStringLiteral(source, i, quote) { + i++; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x5c) { + i += 2; + continue; + } + i++; + if (ch === quote) break; + } + return i; +} + +function skipTemplateLiteral(source, i) { + i++; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x5c) { + i += 2; + continue; + } + i++; + if (ch === 0x60) break; + } + return i; +} + +function rewriteTemplateLiteral(source, start, replacementOpenSource) { + let i = start + 1; + let out = '`'; + let chunkStart = i; + let changed = false; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x5c) { + i += 2; + continue; + } + if (ch === 0x60) { + out += source.slice(chunkStart, i + 1); + return { end: i + 1, text: out, changed }; + } + if (ch === 0x24 && source.charCodeAt(i + 1) === 0x7b) { + out += source.slice(chunkStart, i + 2); + const expressionStart = i + 2; + const expressionEnd = findTemplateExpressionEnd(source, expressionStart); + if (expressionEnd === -1) { + out += source.slice(expressionStart); + return { end: source.length, text: out, changed }; + } + const expression = source.slice(expressionStart, expressionEnd); + const rewritten = rewriteDynamicImports(expression, replacementOpenSource); + if (rewritten.changed) changed = true; + out += rewritten.code + '}'; + i = expressionEnd + 1; + chunkStart = i; + continue; + } + i++; + } + out += source.slice(chunkStart); + return { end: source.length, text: out, changed }; +} + +function findTemplateExpressionEnd(source, start) { + let i = start; + let depth = 0; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteral(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x2f && regexCanFollow(source, i)) { + i = skipRegexLiteral(source, i); + continue; + } + if (ch === 0x7b) { + depth++; + } else if (ch === 0x7d) { + if (depth === 0) return i; + depth--; + } + i++; + } + return -1; +} + +function findMatchingParen(source, open) { + let depth = 1; + let i = open + 1; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(source, i, ch); + continue; + } + if (ch === 0x60) { + i = skipTemplateLiteral(source, i); + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + if (ch === 0x2f && regexCanFollow(source, i)) { + i = skipRegexLiteral(source, i); + continue; + } + if (ch === 0x28) { + depth++; + } else if (ch === 0x29) { + depth--; + if (depth === 0) return i; + } + i++; + } + return -1; +} + +function skipRegexLiteral(source, i) { + i++; + let inClass = false; + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x5c) { + i += 2; + continue; + } + if (ch === 0x5b) inClass = true; + else if (ch === 0x5d) inClass = false; + else if (ch === 0x2f && !inClass) { + i++; + while (i < source.length && isIdentifierChar(source.charCodeAt(i))) i++; + break; + } + i++; + } + return i; +} + +function isLikelyRegexLiteral(source, i) { + const end = skipRegexLiteral(source, i); + if (end >= source.length) return true; + if (end === i + 1) return false; + const next = source.charCodeAt(end); + return next === 0x20 || next === 0x09 || next === 0x0a || next === 0x0d || + next === 0x2e || next === 0x3b || next === 0x2c || next === 0x29 || + next === 0x5d || next === 0x7d; +} + +function previousWordBeforeMatchingParen(source, closeIndex) { + let depth = 1; + let i = closeIndex - 1; + while (i >= 0) { + const ch = source.charCodeAt(i); + if (ch === 0x29) { + depth++; + } else if (ch === 0x28) { + depth--; + if (depth === 0) return previousSignificantWord(source, i); + } + i--; + } + return ''; +} + +function regexCanFollowParen(source, i) { + if (previousSignificantChar(source, i) !== 0x29) return false; + const word = previousWordBeforeMatchingParen(source, i - 1); + return word === 'if' || word === 'while' || word === 'for' || word === 'with'; +} + +function previousSignificantChar(source, i) { + i--; + while (i >= 0) { + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d || ch === 0x0b || ch === 0x0c) { + i--; + continue; + } + if (ch === 0x2f && source.charCodeAt(i - 1) === 0x2a) { + const start = source.lastIndexOf('/*', i - 2); + if (start >= 0) { + i = start - 1; + continue; + } + } + return ch; + } + return 0; +} + +function nextSignificantChar(source, i) { + while (i < source.length) { + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d || ch === 0x0b || ch === 0x0c) { + i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < source.length && source.charCodeAt(i) !== 0x0a && source.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(source, i); + continue; + } + return ch; + } + return 0; +} + +function previousSignificantWord(source, i) { + i--; + while (i >= 0) { + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d || ch === 0x0b || ch === 0x0c) { + i--; + continue; + } + if (ch === 0x2f && source.charCodeAt(i - 1) === 0x2a) { + const start = source.lastIndexOf('/*', i - 2); + if (start >= 0) { + i = start - 1; + continue; + } + } + break; + } + const end = i + 1; + while (i >= 0 && isIdentifierChar(source.charCodeAt(i))) i--; + if (end === i + 1) return ''; + return source.slice(i + 1, end); +} + +function previousSignificantIndex(source, i) { + i--; + while (i >= 0) { + const ch = source.charCodeAt(i); + if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d || ch === 0x0b || ch === 0x0c) { + i--; + continue; + } + return i; + } + return -1; +} + +function regexCanFollow(source, i) { + const prev = previousSignificantChar(source, i); + if (prev === 0 || prev === 0x28 || prev === 0x5b || prev === 0x7b || prev === 0x2c || + prev === 0x3b || prev === 0x3a || prev === 0x3d || prev === 0x21 || prev === 0x3f || + prev === 0x26 || prev === 0x7c || prev === 0x2b || prev === 0x2d || prev === 0x2a || + prev === 0x2f || prev === 0x25 || prev === 0x7e || prev === 0x5e || prev === 0x3c || + prev === 0x3e) { + return true; + } + const word = previousSignificantWord(source, i); + return word === 'return' || word === 'throw' || word === 'case' || word === 'delete' || + word === 'void' || word === 'typeof' || word === 'yield' || word === 'await' || + word === 'else' || word === 'do' || word === 'in' || word === 'instanceof' || + word === 'of'; +} + +function isImportMethodDefinition(source, importStart, open) { + const isMethodDelimiter = (ch) => ch === 0x7b || ch === 0x2c; + const before = previousSignificantChar(source, importStart); + let hasMethodPrefix = isMethodDelimiter(before); + if (!hasMethodPrefix) { + const prefixIndex = previousSignificantIndex(source, importStart); + if (before === 0x2a && prefixIndex >= 0) { + const beforeStar = previousSignificantChar(source, prefixIndex); + if (isMethodDelimiter(beforeStar)) { + hasMethodPrefix = true; + } else if (previousSignificantWord(source, prefixIndex) === 'async') { + const asyncEnd = previousSignificantIndex(source, prefixIndex) + 1; + const asyncStart = source.lastIndexOf('async', asyncEnd); + const beforeAsync = previousSignificantChar(source, asyncStart); + if (isMethodDelimiter(beforeAsync)) { + hasMethodPrefix = true; + } else if (previousSignificantWord(source, asyncStart) === 'static') { + const staticStart = source.lastIndexOf('static', asyncStart); + hasMethodPrefix = isMethodDelimiter(previousSignificantChar(source, staticStart)); + } + } else if (previousSignificantWord(source, prefixIndex) === 'static') { + const staticEnd = previousSignificantIndex(source, prefixIndex) + 1; + const staticStart = source.lastIndexOf('static', staticEnd); + hasMethodPrefix = isMethodDelimiter(previousSignificantChar(source, staticStart)); + } + } else { + const word = previousSignificantWord(source, importStart); + if (word === 'async' || word === 'get' || word === 'set' || word === 'static') { + const wordStart = source.lastIndexOf(word, importStart); + const beforeWord = previousSignificantChar(source, wordStart); + if (isMethodDelimiter(beforeWord)) { + hasMethodPrefix = true; + } else if (word !== 'static' && previousSignificantWord(source, wordStart) === 'static') { + const staticStart = source.lastIndexOf('static', wordStart); + hasMethodPrefix = isMethodDelimiter(previousSignificantChar(source, staticStart)); + } + } + } + } + if (!hasMethodPrefix) return false; + const close = findMatchingParen(source, open); + if (close < 0) return false; + return source.charCodeAt(skipWhitespaceAndComments(source, close + 1)) === 0x7b; +} + +function throwInvalidArgType(name, expected, value) { + const err = new TypeError('The "' + name + '" argument must be of type ' + expected + '.' + formatReceivedType(value)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; +} + +function throwInvalidContextifiedObject() { + const err = new TypeError('The "contextifiedObject" argument must be an vm.Context.'); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; +} + +function throwInvalidPropertyType(name, expected, value) { + const err = new TypeError('The "' + name + '" property must be of type ' + expected + '.' + formatReceivedType(value)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; +} + +function throwInvalidArgInstance(name, expected, value) { + const err = new TypeError('The "' + name + '" argument must be an instance of ' + expected + '.' + invalidArgTypeHelper(value)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; +} + +function throwInvalidPropertyInstance(name, expected, value) { + const err = new TypeError('The "' + name + '" property must be an instance of ' + expected + '.' + invalidArgTypeHelper(value)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; +} + +function throwOutOfRange(name, range, value) { + const err = new RangeError('The value of "' + name + '" is out of range. It must be ' + range + '. Received ' + formatReceived(value)); + err.code = 'ERR_OUT_OF_RANGE'; + throw err; +} + +function formatReceived(value) { + if (value === null) return 'null'; + if (typeof value === 'string') return "'" + value + "'"; + if (typeof value === 'symbol') return value.toString(); + return String(value); +} + +function formatReceivedType(value) { + if (value === null) return ' Received null'; + if (value === undefined) return ' Received undefined'; + if (typeof value === 'string') return " Received type string ('" + value + "')"; + return ' Received type ' + typeof value + ' (' + formatReceived(value) + ')'; +} + +function invalidArgTypeHelper(value) { + if (value === null) return ' Received null'; + if (value === undefined) return ' Received undefined'; + if (typeof value === 'function') return ' Received function ' + (value.name || ''); + if (value && typeof value === 'object' && value.constructor && value.constructor.name) { + return ' Received an instance of ' + value.constructor.name; + } + if (value && typeof value === 'object') return ' Received an object'; + return formatReceivedType(value); +} + +function referrerFilenameFromOptions(options) { + if (typeof options.filename === 'string' && options.filename.length > 0) { + return options.filename; + } + if (globalThis.process && typeof globalThis.process.cwd === 'function') { + return globalThis.process.cwd() + '/'; + } + return '/'; +} + +function referrerDirectory(filename) { + if (filename.startsWith('file://')) { + try { + filename = decodeURIComponent(new URL(filename).pathname); + } catch (_) { + return globalThis.process && typeof globalThis.process.cwd === 'function' + ? globalThis.process.cwd() + : '/'; + } + } + if (filename.endsWith('/')) return filename.slice(0, -1) || '/'; + if (!filename.startsWith('/')) { + return globalThis.process && typeof globalThis.process.cwd === 'function' + ? globalThis.process.cwd() + : '/'; + } + return pathModule.dirname(filename); +} + +function resolveDefaultLoaderSpecifier(specifier, filename) { + if (specifier.startsWith('./') || specifier.startsWith('../')) { + return pathModule.resolve(referrerDirectory(filename), specifier); + } + return specifier; +} + +function ensureDefaultLoaderImportBinding(helperName) { + if (globalThis[helperName] !== defaultLoaderImportFunction) { + Object.defineProperty(globalThis, helperName, { + value: defaultLoaderImportFunction, + writable: false, + configurable: true, + }); + } +} + +function ensureMissingDynamicImportBinding(helperName) { + if (globalThis[helperName] !== missingDynamicImportFunction) { + Object.defineProperty(globalThis, helperName, { + value: missingDynamicImportFunction, + writable: false, + configurable: true, + }); + } +} + +function ensureMissingDynamicImportFlagBinding(helperName) { + if (globalThis[helperName] !== missingDynamicImportFlagFunction) { + Object.defineProperty(globalThis, helperName, { + value: missingDynamicImportFlagFunction, + writable: false, + configurable: true, + }); + } +} + +function chooseDefaultLoaderImportHelperName(code) { + let helperName; + do { + helperName = defaultLoaderImportHelper + '_' + defaultLoaderImportHelperCounter++; + } while (code.indexOf(helperName) !== -1); + return helperName; +} + +function chooseMissingDynamicImportHelperName(code) { + let helperName; + do { + helperName = missingDynamicImportHelper + '_' + defaultLoaderImportHelperCounter++; + } while (code.indexOf(helperName) !== -1); + return helperName; +} + +function chooseMissingDynamicImportFlagHelperName(code) { + let helperName; + do { + helperName = missingDynamicImportFlagHelper + '_' + defaultLoaderImportHelperCounter++; + } while (code.indexOf(helperName) !== -1); + return helperName; +} + +function chooseSandboxDescriptorsHelperName(code, sandboxKeys) { + let helperName; + do { + helperName = sandboxDescriptorsHelper + '_' + sandboxDescriptorsHelperCounter++; + } while (code.indexOf(helperName) !== -1 || sandboxKeys.indexOf(helperName) !== -1); + return helperName; +} + +function chooseSandboxSymbolsHelperName(code, sandboxKeys) { + let helperName; + do { + helperName = sandboxSymbolsHelper + '_' + sandboxSymbolsHelperCounter++; + } while (code.indexOf(helperName) !== -1 || sandboxKeys.indexOf(helperName) !== -1); + return helperName; +} + +function defaultLoaderImportSource(filename, helperName) { + return helperName + '(' + JSON.stringify(filename) + ','; +} + +function missingDynamicImportSource(helperName) { + return helperName + '('; +} + +function missingDynamicImportFlagSource(helperName) { + return helperName + '('; +} + +function vmDynamicImportCallbackSource(helperName) { + return helperName + '('; +} + +function dynamicImportAttributes(options) { + const attributes = Object.create(null); + if (options && typeof options === 'object' && options.with && typeof options.with === 'object') { + const keys = Object.keys(options.with); + for (let i = 0; i < keys.length; i++) { + attributes[keys[i]] = options.with[keys[i]]; + } + } + return attributes; +} + +function vmModuleNotModuleError() { + const err = new TypeError('Provided module is not an instance of Module'); + err.code = 'ERR_VM_MODULE_NOT_MODULE'; + return err; +} + +function invalidArgValue(message) { + const err = new TypeError(message); + err.code = 'ERR_INVALID_ARG_VALUE'; + return err; +} + +function vmModuleStatusError(message) { + const err = new Error(message); + err.code = 'ERR_VM_MODULE_STATUS'; + return err; +} + +function throwInvalidModuleThis(value) { + const err = new TypeError('The "this" argument must be an instance of Module.' + invalidArgTypeHelper(value)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; +} + +function requireVmModuleThis(value) { + if (!value || value[vmModuleInstanceBrandSymbol] !== true) { + throwInvalidModuleThis(value); + } + return value; +} + +function vmModulePublicContext(context) { + if (!context || typeof context !== 'object') return context; + const result = {}; + const keys = Object.keys(context); + for (let i = 0; i < keys.length; i++) { + result[keys[i]] = context[keys[i]]; + } + return result; +} + +function inspectVmModule(depth, options, inspect) { + 'use strict'; + const module = requireVmModuleThis(this); + const name = module instanceof SourceTextModule ? 'SourceTextModule' : 'SyntheticModule'; + if (depth !== null && depth < 0) return '[' + name + ']'; + const context = inspect(vmModulePublicContext(module._context), options); + return name + ' {\n' + + ' status: ' + inspect(module._status, options) + ',\n' + + ' identifier: ' + inspect(module._identifier, options) + ',\n' + + ' context: ' + context.replace(/\n/g, '\n ') + '\n' + + '}'; +} + +function requireSyntheticModuleThis(value) { + if (!value || value[vmModuleInstanceBrandSymbol] !== true || !(value instanceof SyntheticModule)) { + const err = new TypeError('The "this" argument must be an instance of SyntheticModule.' + invalidArgTypeHelper(value)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; + } + return value; +} + +function vmModuleDifferentContextError() { + const err = new Error('Linked modules must use the same context'); + err.code = 'ERR_VM_MODULE_DIFFERENT_CONTEXT'; + return err; +} + +function vmModuleLinkFailureError(cause) { + const err = new Error('Module link failed'); + err.code = 'ERR_VM_MODULE_LINK_FAILURE'; + err.cause = cause; + return err; +} + +function namespaceFromVmModule(module) { + if (!(module instanceof SourceTextModule) && !(module instanceof SyntheticModule)) { + if (module && typeof module === 'object' && module[moduleNamespaceBrandSymbol] === true) { + return module; + } + throw vmModuleNotModuleError(); + } + return module.namespace; +} + +function defineVmDynamicImportCallbackBinding(helperName, callback, wrap) { + Object.defineProperty(globalThis, helperName, { + value: function(specifier, options) { + let result; + const referrer = wrap && typeof wrap === 'object' && Object.prototype.hasOwnProperty.call(wrap, vmDynamicImportReferrerSymbol) + ? wrap[vmDynamicImportReferrerSymbol] + : wrap; + try { + result = callback(String(specifier), referrer, dynamicImportAttributes(options)); + } catch (err) { + return Promise.reject(err); + } + return Promise.resolve(result).then(namespaceFromVmModule); + }, + writable: false, + configurable: true, + }); +} + +function rewriteDefaultLoaderDynamicImportsForEvaluation(code, filename) { + code = String(code); + const helperName = chooseDefaultLoaderImportHelperName(code); + const rewritten = rewriteDynamicImports(code, defaultLoaderImportSource(filename, helperName)); + if (rewritten.changed) ensureDefaultLoaderImportBinding(helperName); + return { + code: rewritten.code, + helperName: rewritten.changed ? helperName : undefined, + }; +} + +function rewriteMissingDynamicImportsForEvaluation(code) { + code = String(code); + const helperName = chooseMissingDynamicImportHelperName(code); + const rewritten = rewriteDynamicImports(code, missingDynamicImportSource(helperName)); + if (rewritten.changed) ensureMissingDynamicImportBinding(helperName); + return { + code: rewritten.code, + helperName: rewritten.changed ? helperName : undefined, + }; +} + +function rewriteMissingDynamicImportFlagForEvaluation(code) { + code = String(code); + const helperName = chooseMissingDynamicImportFlagHelperName(code); + const rewritten = rewriteDynamicImports(code, missingDynamicImportFlagSource(helperName)); + if (rewritten.changed) ensureMissingDynamicImportFlagBinding(helperName); + return { + code: rewritten.code, + helperName: rewritten.changed ? helperName : undefined, + }; +} + +function rewriteVmDynamicImportCallbackForEvaluation(code, callback, wrap) { + code = String(code); + const helperName = chooseDefaultLoaderImportHelperName(code); + const rewritten = rewriteDynamicImports(code, vmDynamicImportCallbackSource(helperName)); + if (rewritten.changed) defineVmDynamicImportCallbackBinding(helperName, callback, wrap); + return { + code: rewritten.code, + helperName: rewritten.changed ? helperName : undefined, + }; +} + +function rewriteDynamicImports(code, replacementOpenSource) { + code = String(code); + let changed = false; + let out = ''; + let last = 0; + let i = 0; + while (i < code.length) { + const ch = code.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(code, i, ch); + continue; + } + if (ch === 0x60) { + const template = rewriteTemplateLiteral(code, i, replacementOpenSource); + if (template.changed) { + changed = true; + out += code.slice(last, i) + template.text; + last = template.end; + } + i = template.end; + continue; + } + if (ch === 0x2f && code.charCodeAt(i + 1) === 0x2f) { + i += 2; + while (i < code.length && code.charCodeAt(i) !== 0x0a && code.charCodeAt(i) !== 0x0d) i++; + continue; + } + if (ch === 0x2f && code.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(code, i); + continue; + } + if (ch === 0x2f && (regexCanFollow(code, i) || (regexCanFollowParen(code, i) && isLikelyRegexLiteral(code, i)))) { + i = skipRegexLiteral(code, i); + continue; + } + if (code.startsWith('import', i) + && hasIdentifierBoundary(code, i, i + 6) + && previousSignificantChar(code, i) !== 0x2e + && previousSignificantChar(code, i) !== 0x23) { + const open = skipWhitespaceAndComments(code, i + 6); + if (code.charCodeAt(open) === 0x28) { + if (isImportMethodDefinition(code, i, open)) { + i = open + 1; + continue; + } + changed = true; + out += code.slice(last, i) + replacementOpenSource; + last = open + 1; + i = open + 1; + continue; + } + } + i++; + } + if (last === 0) return { code, changed: false }; + return { code: out + code.slice(last), changed }; +} + +function extractSourceMapURL(code) { + const text = String(code); + let url; + + if (text.indexOf('sourceMappingURL=') === -1) { + return undefined; + } + + function isSourceMapURLSeparator(ch) { + return ch === 0x09 || + ch === 0x0b || + ch === 0x0c || + ch === 0x20 || + ch === 0xa0; + } + + function scan(start, end) { + for (let i = start; i < end; i++) { + const ch = text.charCodeAt(i); + if (ch === 0x27 || ch === 0x22) { + i = skipStringLiteral(text, i, ch) - 1; + continue; + } + if (ch === 0x60) { + i++; + while (i < end) { + const templateCh = text.charCodeAt(i); + if (templateCh === 0x5c) { + i += 2; + continue; + } + if (templateCh === 0x60) break; + if (templateCh === 0x24 && text.charCodeAt(i + 1) === 0x7b) { + const expressionStart = i + 2; + const expressionEnd = findTemplateExpressionEnd(text, expressionStart); + if (expressionEnd === -1) return; + scan(expressionStart, expressionEnd); + i = expressionEnd + 1; + continue; + } + i++; + } + continue; + } + if (ch === 0x2f && text.charCodeAt(i + 1) === 0x2a) { + i = skipWhitespaceAndComments(text, i) - 1; + continue; + } + if (ch !== 0x2f || text.charCodeAt(i + 1) !== 0x2f) { + if (ch === 0x2f && (regexCanFollow(text, i) || (regexCanFollowParen(text, i) && isLikelyRegexLiteral(text, i)))) { + i = skipRegexLiteral(text, i) - 1; + } + continue; + } + + const marker = text.charCodeAt(i + 2); + if (marker === 0x23 || marker === 0x40) { + const separator = text.charCodeAt(i + 3); + if (isSourceMapURLSeparator(separator) && + text.startsWith('sourceMappingURL=', i + 4)) { + let lineEnd = i + 21; + while (lineEnd < end) { + const endChar = text.charCodeAt(lineEnd); + if (endChar === 0x0a || endChar === 0x0d) break; + lineEnd++; + } + const value = text.slice(i + 21, lineEnd).trim(); + if (value.length > 0) { + url = value; + } + } + } + + i += 2; + while (i < end && text.charCodeAt(i) !== 0x0a && text.charCodeAt(i) !== 0x0d) i++; + } + } + + scan(0, text.length); + return url; +} + +export class Script { + constructor(code, options) { + scriptBrandSet.add(this); + this._code = String(code); + options = validateScriptConstructorOptions(options); + this.sourceMapURL = extractSourceMapURL(this._code); + this._options = snapshotVmOptions(options); + this._usesDefaultLoader = this._options.importModuleDynamically === USE_MAIN_CONTEXT_DEFAULT_LOADER; + this._usesMissingDynamicImportCallback = this._options.importModuleDynamically === undefined; + this._usesDynamicImportCallback = typeof this._options.importModuleDynamically === 'function' && vmModulesEnabled(); + this._usesMissingDynamicImportFlag = typeof this._options.importModuleDynamically === 'function' && !this._usesDynamicImportCallback; + this._defaultLoaderFilename = this._usesDefaultLoader + ? referrerFilenameFromOptions(this._options) + : undefined; + const defaultLoaderRewrite = this._usesDefaultLoader + ? rewriteDefaultLoaderDynamicImportsForEvaluation(this._code, this._defaultLoaderFilename) + : undefined; + const missingCallbackRewrite = this._usesMissingDynamicImportCallback + ? rewriteMissingDynamicImportsForEvaluation(this._code) + : undefined; + const missingFlagRewrite = this._usesMissingDynamicImportFlag + ? rewriteMissingDynamicImportFlagForEvaluation(this._code) + : undefined; + const dynamicImportCallbackRewrite = this._usesDynamicImportCallback + ? rewriteVmDynamicImportCallbackForEvaluation(this._code, this._options.importModuleDynamically, this) + : undefined; + this._defaultLoaderCode = defaultLoaderRewrite && defaultLoaderRewrite.code; + this._defaultLoaderHelperName = defaultLoaderRewrite && defaultLoaderRewrite.helperName; + this._missingCallbackCode = missingCallbackRewrite && missingCallbackRewrite.code; + this._missingCallbackHelperName = missingCallbackRewrite && missingCallbackRewrite.helperName; + this._missingFlagCode = missingFlagRewrite && missingFlagRewrite.code; + this._missingFlagHelperName = missingFlagRewrite && missingFlagRewrite.helperName; + this._dynamicImportCallbackCode = dynamicImportCallbackRewrite && dynamicImportCallbackRewrite.code; + this._dynamicImportCallbackHelperName = dynamicImportCallbackRewrite && dynamicImportCallbackRewrite.helperName; + } + + runInNewContext(sandbox, options) { + if (this === null || this === undefined) { + throw new TypeError("Cannot read properties of " + this + " (reading 'runInContext')"); + } + const runInContext = this.runInContext; + if (typeof runInContext !== 'function') { + throw new TypeError('this.runInContext is not a function'); + } + validateRunInNewContextPreSandboxOptions(options); + const context = createContextForRunInNewContext(sandbox); + validateRunInNewContextPostSandboxOptions(options); + return runInContext.call(this, context, options); + } + + runInContext(context, options) { + if (!this || !scriptBrandSet.has(this)) { + throw new TypeError('Illegal invocation'); + } + if (!isContext(context)) { + throwInvalidContextifiedObject(); + } + options = validateScriptRunOptions(options); + if (this._usesDefaultLoader) { + return evalCodeInContext(this._defaultLoaderCode, context, this._defaultLoaderHelperName); + } + if (this._usesMissingDynamicImportCallback) { + return evalCodeInContext(this._missingCallbackCode, context, this._missingCallbackHelperName); + } + if (this._usesMissingDynamicImportFlag) { + return evalCodeInContext(this._missingFlagCode, context, this._missingFlagHelperName); + } + if (this._usesDynamicImportCallback) { + return evalCodeInContext(this._dynamicImportCallbackCode, context, this._dynamicImportCallbackHelperName); + } + return runInContext(this._code, context, options); + } + + runInThisContext(options) { + if (!this || !scriptBrandSet.has(this)) { + throw new TypeError('Illegal invocation'); + } + options = validateScriptRunOptions(options); + const runOptions = scriptRunInThisContextOptions(this, options); + if (this._usesDefaultLoader) { + return evalWithFilename(this._defaultLoaderCode, this._defaultLoaderFilename); + } + if (this._usesMissingDynamicImportCallback) { + return runInThisContext(this._missingCallbackCode, runOptions); + } + if (this._usesMissingDynamicImportFlag) { + return runInThisContext(this._missingFlagCode, runOptions); + } + if (this._usesDynamicImportCallback) { + return runInThisContext(this._dynamicImportCallbackCode, runOptions); + } + return runInThisContext(this._code, runOptions); + } + + createCachedData() { + if (!this || !scriptBrandSet.has(this)) { + throw new TypeError('Illegal invocation'); + } + return new Uint8Array(0); + } +} + +function scriptRunInThisContextOptions(script, options) { + const result = Object.assign({}, options); + delete result.filename; + if (script._options.filename !== undefined) { + result.filename = script._options.filename; + } + return result; +} + +export function Module() { + throw new TypeError('Module is not a constructor'); +} + +Object.defineProperties(Module.prototype, { + status: { + get: function() { + return requireVmModuleThis(this)._status; + }, + configurable: true, + }, + error: { + get: function() { + const module = requireVmModuleThis(this); + if (module._status !== 'errored') { + throw vmModuleStatusError('Module status must be errored'); + } + return module._error; + }, + configurable: true, + }, + namespace: { + get: function() { + const module = requireVmModuleThis(this); + if (module._status === 'unlinked' || module._status === 'linking') { + throw vmModuleStatusError('Module status must not be unlinked or linking'); + } + return module._namespace; + }, + configurable: true, + }, + identifier: { + get: function() { + return requireVmModuleThis(this)._identifier; + }, + configurable: true, + }, + context: { + get: function() { + return requireVmModuleThis(this)._context; + }, + configurable: true, + }, + [customInspectSymbol]: { + value: inspectVmModule, + configurable: true, + }, +}); + +Module.prototype.link = async function link() { + requireVmModuleThis(this); +}; + +Module.prototype.evaluate = async function evaluate() { + requireVmModuleThis(this); +}; + +export class SourceTextModule { + constructor(code, options) { + if (typeof code !== 'string') { + throwInvalidArgType('code', 'string', code); + } + this._source = code; + this[vmModuleInstanceBrandSymbol] = true; + this._status = 'unlinked'; + this._error = undefined; + this._options = snapshotVmOptions(options); + validateInitializeImportMetaOption(this._options.initializeImportMeta); + this._context = this._options.context; + this._identifier = this._options.identifier || 'vm:module(0)'; + this._importMetaName = undefined; + this._usesDynamicImportCallback = typeof this._options.importModuleDynamically === 'function' && vmModulesEnabled(); + this._usesMissingDynamicImportFlag = typeof this._options.importModuleDynamically === 'function' && !this._usesDynamicImportCallback; + + const analysis = analyzeSourceTextModule(this._source); + const declaredBindings = analysis.bindings; + this._dependencies = analysis.dependencies; + this._usesImportedNames = sourceTextModuleHasImportedNames(this._dependencies); + this._dependencySpecifiers = Object.freeze(this._dependencies.map((dependency) => dependency.specifier)); + this._bindings = Object.create(null); + this._names = []; + this._exportBindings = []; + + for (let i = 0; i < declaredBindings.length; i++) { + const binding = declaredBindings[i]; + this._names.push(binding.name); + this._exportBindings.push(binding); + this._bindings[binding.name] = { + kind: binding.kind, + initialized: binding.kind === 'var', + value: undefined, + }; + } + + let executableSource = analysis.executableSource; + if (this._usesDynamicImportCallback) { + executableSource = rewriteVmDynamicImportCallbackForEvaluation(executableSource, this._options.importModuleDynamically, this).code; + } else if (this._usesMissingDynamicImportFlag) { + executableSource = rewriteMissingDynamicImportFlagForEvaluation(executableSource).code; + } else { + executableSource = rewriteMissingDynamicImportsForEvaluation(executableSource).code; + } + this._importMetaName = chooseInternalBindingName(executableSource, '__wasm_rquickjs_vm_import_meta'); + const importMetaRewrite = rewriteImportMetaForEvaluation(executableSource, this._importMetaName); + executableSource = importMetaRewrite.code; + this._usesImportMeta = importMetaRewrite.changed; + executableSource = injectSourceTextModuleAssignmentSync(executableSource, this._exportBindings); + this._exportCellsName = chooseInternalBindingName(executableSource, '__wasm_rquickjs_vm_export_cells'); + executableSource = executableSource.split(sourceTextModuleExportCellsPlaceholder).join(this._exportCellsName); + + this._evaluateSource = compileSourceTextModuleEvaluator(executableSource, this._exportBindings, this._dependencies, this._importMetaName, this._exportCellsName, this._usesImportMeta); + this._namespace = createModuleNamespace(this); + } + + get status() { + return requireVmModuleThis(this)._status; + } + + get namespace() { + requireVmModuleThis(this); + if (this._status === 'unlinked' || this._status === 'linking') { + throw vmModuleStatusError('Module status must not be unlinked or linking'); } return this._namespace; } + get error() { + requireVmModuleThis(this); + if (this._status !== 'errored') { + throw vmModuleStatusError('Module status must be errored'); + } + return this._error; + } + + get dependencySpecifiers() { + requireVmModuleThis(this); + return this._dependencySpecifiers; + } + async link(linker) { - this._status = 'linked'; + if (typeof linker !== 'function') { + throwInvalidArgType('linker', 'function', linker); + } + if (this._status === 'linked' || this._status === 'evaluated' || this._status === 'errored') { + const err = new Error('Module has already been linked'); + err.code = 'ERR_VM_MODULE_ALREADY_LINKED'; + throw err; + } + if (this._status !== 'unlinked') { + throw vmModuleStatusError('Module status must be unlinked'); + } + this._status = 'linking'; + try { + for (let i = 0; i < this._dependencies.length; i++) { + const dependency = this._dependencies[i]; + const attributes = dependency.attributes || Object.create(null); + const module = await linker(dependency.specifier, this, { attributes, assert: attributes }); + if (!(module instanceof SourceTextModule) && !(module instanceof SyntheticModule)) { + throw vmModuleNotModuleError(); + } + if (this._context !== module._context) { + throw vmModuleDifferentContextError(); + } + if (module.status === 'unlinked') { + await module.link(linker); + } + if (module.status === 'errored') { + throw vmModuleLinkFailureError(module.error); + } + for (let j = 0; j < dependency.names.length; j++) { + if (dependency.names[j].imported === '*') { + continue; + } + if (!Object.prototype.hasOwnProperty.call(module._bindings, dependency.names[j].imported)) { + throw new SyntaxError("The requested module '" + dependency.specifier + "' does not provide an export named '" + dependency.names[j].imported + "'"); + } + } + dependency.module = module; + } + this._resolveStarExports(); + await Promise.resolve(); + this._status = 'linked'; + } catch (err) { + this._error = err; + this._status = 'errored'; + throw err; + } } async evaluate(options) { - if (this._status === 'unlinked') { - throw new Error('Module status must be linked before evaluate()'); + options = validateOptionsObject(options); + if (options.breakOnSigint !== undefined && typeof options.breakOnSigint !== 'boolean') { + throwInvalidPropertyType('options.breakOnSigint', 'boolean', options.breakOnSigint); + } + if (options.timeout !== undefined) { + validateInt32Option(options.timeout, 'options.timeout'); + } + if (this._status === 'unlinked' || this._status === 'linking') { + throw vmModuleStatusError('Module status must be one of linked, evaluated, or errored'); } if (this._status === 'evaluated') { return undefined; } + if (this._status === 'errored') { + throw this._error; + } this._status = 'evaluating'; try { - const evaluatedExports = this._evaluateSource(); - for (let i = 0; i < this._names.length; i++) { - const name = this._names[i]; + for (let i = 0; i < this._dependencies.length; i++) { + const dependency = this._dependencies[i]; + if (dependency.module.status === 'linked') { + await dependency.module.evaluate(); + } + } + const importedValues = Object.create(null); + for (let i = 0; i < this._dependencies.length; i++) { + const dependency = this._dependencies[i]; + for (let j = 0; j < dependency.names.length; j++) { + const binding = dependency.names[j]; + if (binding.imported === '*') { + Object.defineProperty(importedValues, binding.local, { + get: function() { + return dependency.module.namespace; + }, + enumerable: true, + configurable: true, + }); + } else { + Object.defineProperty(importedValues, binding.local, { + get: function() { + return dependency.module.namespace[binding.imported]; + }, + enumerable: true, + configurable: true, + }); + } + } + } + let evaluatedExports; + if (this._usesImportMeta) { + const importMeta = Object.create(null); + const initializeImportMeta = this._options.initializeImportMeta; + if (typeof initializeImportMeta === 'function') { + initializeImportMeta(importMeta, this); + } + evaluatedExports = this._usesImportedNames + ? this._evaluateSource(importedValues, importMeta, this._bindings) + : this._evaluateSource(importMeta, this._bindings); + } else { + evaluatedExports = this._usesImportedNames + ? this._evaluateSource(importedValues, this._bindings) + : this._evaluateSource(this._bindings); + } + for (let i = 0; i < this._exportBindings.length; i++) { + const name = this._exportBindings[i].name; const binding = this._bindings[name]; binding.initialized = true; binding.value = evaluatedExports[name]; @@ -317,12 +3116,201 @@ export class SourceTextModule { this._status = 'evaluated'; return undefined; } catch (err) { + this._error = err; + this._status = 'errored'; + throw err; + } + } + + _resolveStarExports() { + const ambiguous = Object.create(null); + const resolved = Object.create(null); + for (let i = 0; i < this._dependencies.length; i++) { + const dependency = this._dependencies[i]; + if (!dependency.exportAll || !dependency.module) continue; + const names = dependency.module._names; + for (let j = 0; j < names.length; j++) { + const name = names[j]; + if (name === 'default' || Object.prototype.hasOwnProperty.call(this._bindings, name)) { + continue; + } + if (ambiguous[name]) { + continue; + } + const resolution = sourceTextModuleResolveExport(dependency.module, name); + if (!resolution) { + continue; + } + if (resolved[name] && !sourceTextModuleSameExportResolution(resolved[name], resolution)) { + ambiguous[name] = true; + delete resolved[name]; + continue; + } + resolved[name] = resolution; + } + } + const resolvedNames = Object.keys(resolved); + for (let i = 0; i < resolvedNames.length; i++) { + const name = resolvedNames[i]; + if (ambiguous[name] || Object.prototype.hasOwnProperty.call(this._bindings, name)) continue; + const resolution = resolved[name]; + this._names.push(name); + this._bindings[name] = { + kind: 'reexport', + initialized: true, + module: resolution.module, + importName: resolution.importName, + }; + } + this._names = this._names.filter((name, index, names) => names.indexOf(name) === index); + this._namespace = createModuleNamespace(this); + } +} + +function sourceTextModuleResolveExport(module, name, seen) { + seen = seen || []; + for (let i = 0; i < seen.length; i++) { + if (seen[i].module === module && seen[i].name === name) return null; + } + seen.push({ module, name }); + const binding = module._bindings[name]; + if (!binding) return null; + if (binding.kind === 'reexport') { + return sourceTextModuleResolveExport(binding.module, binding.importName, seen); + } + return { module, importName: name }; +} + +function sourceTextModuleSameExportResolution(left, right) { + return left.module === right.module && left.importName === right.importName; +} + +Object.setPrototypeOf(SourceTextModule.prototype, Module.prototype); +Object.setPrototypeOf(SourceTextModule, Module); + +export class SyntheticModule { + constructor(exportNames, evaluateCallback, options) { + if (!Array.isArray(exportNames) || exportNames.some((name) => typeof name !== 'string')) { + const err = new TypeError('The "exportNames" argument must be an Array of unique strings. Received ' + formatReceived(exportNames)); + err.code = 'ERR_INVALID_ARG_TYPE'; + throw err; + } + const seen = new Set(); + for (let i = 0; i < exportNames.length; i++) { + const name = exportNames[i]; + if (seen.has(name)) { + throw invalidArgValue("The property 'exportNames." + name + "' is duplicated. Received '" + name + "'"); + } + seen.add(name); + } + if (typeof evaluateCallback !== 'function') { + throwInvalidArgType('evaluateCallback', 'function', evaluateCallback); + } + this._options = snapshotVmOptions(options); + this[vmModuleInstanceBrandSymbol] = true; + this._context = this._options.context; + this._identifier = this._options.identifier || 'vm:module(0)'; + this._status = 'unlinked'; + this._error = undefined; + this._dependencySpecifiers = Object.freeze([]); + this._names = exportNames.slice(); + this._bindings = Object.create(null); + for (let i = 0; i < this._names.length; i++) { + this._bindings[this._names[i]] = { + kind: 'const', + initialized: true, + value: undefined, + }; + } + this._evaluateCallback = evaluateCallback; + this._namespace = createModuleNamespace(this); + } + + get status() { + return requireVmModuleThis(this)._status; + } + + get namespace() { + requireVmModuleThis(this); + if (this._status === 'unlinked' || this._status === 'linking') { + throw vmModuleStatusError('Module status must not be unlinked or linking'); + } + return this._namespace; + } + + get error() { + requireVmModuleThis(this); + if (this._status !== 'errored') { + throw vmModuleStatusError('Module status must be errored'); + } + return this._error; + } + + async link(linker) { + if (typeof linker !== 'function') { + throwInvalidArgType('linker', 'function', linker); + } + if (this._status === 'linked' || this._status === 'evaluated' || this._status === 'errored') { + const err = new Error('Module has already been linked'); + err.code = 'ERR_VM_MODULE_ALREADY_LINKED'; + throw err; + } + if (this._status !== 'unlinked') { + throw vmModuleStatusError('Module status must be unlinked'); + } + this._status = 'linking'; + await Promise.resolve(); + this._status = 'linked'; + } + + setExport(name, value) { + requireSyntheticModuleThis(this); + if (typeof name !== 'string') { + throw new TypeError('Export name must be a string'); + } + if (this._status === 'unlinked' || this._status === 'linking') { + throw vmModuleStatusError('Module status must not be unlinked or linking'); + } + const binding = this._bindings[name]; + if (binding === undefined) { + throw new ReferenceError('Export ' + name + ' is not defined in module'); + } + binding.value = value; + } + + async evaluate(options) { + options = validateOptionsObject(options); + if (options.breakOnSigint !== undefined && typeof options.breakOnSigint !== 'boolean') { + throwInvalidPropertyType('options.breakOnSigint', 'boolean', options.breakOnSigint); + } + if (options.timeout !== undefined) { + validateInt32Option(options.timeout, 'options.timeout'); + } + if (this._status === 'unlinked' || this._status === 'linking') { + throw vmModuleStatusError('Module status must be one of linked, evaluated, or errored'); + } + if (this._status === 'evaluated') { + return undefined; + } + if (this._status === 'errored') { + throw this._error; + } + this._status = 'evaluating'; + try { + this._evaluateCallback.call(this); + this._status = 'evaluated'; + return undefined; + } catch (err) { + this._error = err; this._status = 'errored'; throw err; } } } +Object.setPrototypeOf(SyntheticModule.prototype, Module.prototype); +Object.setPrototypeOf(SyntheticModule, Module); + export function createScript(code, options) { return new Script(code, options); } @@ -334,9 +3322,12 @@ const vmExports = { createContext, isContext, compileFunction, + Module, Script, SourceTextModule, + SyntheticModule, createScript, + constants, }; export default vmExports; diff --git a/crates/wasm-rquickjs/skeleton/src/builtin/vm.rs b/crates/wasm-rquickjs/skeleton/src/builtin/vm.rs index 497a6c2f..e3bdf979 100644 --- a/crates/wasm-rquickjs/skeleton/src/builtin/vm.rs +++ b/crates/wasm-rquickjs/skeleton/src/builtin/vm.rs @@ -1,5 +1,6 @@ use rquickjs::qjs; -use rquickjs::{CaughtError, Persistent, Value}; +use rquickjs::promise::PromiseState; +use rquickjs::{CaughtError, FromJs, Persistent, Promise, Value}; use std::ptr::NonNull; #[rquickjs::module(rename = "camelCase")] @@ -70,6 +71,17 @@ fn eval_in_new_context_impl<'js>( // Restore sandbox values into the new context's global object let new_global = new_ctx.globals(); + // Match the Node 22 vm global surface by hiding QuickJS globals that are + // not present on a fresh contextified global unless the sandbox provides them. + for key in [ + "DOMException", + "Float16Array", + "InternalError", + "performance", + "queueMicrotask", + ] { + let _ = new_global.remove(key); + } for (key, pval) in sandbox_keys.iter().zip(persistent_values) { let restored: Value<'js> = pval .restore(&new_ctx) @@ -168,57 +180,49 @@ fn require_esm_impl<'js>( return throw_require_async_module(ctx, &globals, filename); } - unsafe { - let val = qjs::JS_Eval( + enter_require_esm(&ctx, &globals, filename, &file_url)?; + + let eval_result = unsafe { + qjs::JS_Eval( ctx.as_raw().as_ptr(), src.as_ptr(), code.len() as _, fname.as_ptr(), qjs::JS_EVAL_TYPE_MODULE as i32, - ); - if qjs::JS_IsException(val) { - return Err(rquickjs::Error::Exception); - } + ) + }; - // If the module evaluation returned a Promise (TLA), attach a no-op - // .catch() handler so any rejection is marked as handled and doesn't - // trigger an unhandledRejection event. We'll report TLA as - // ERR_REQUIRE_ASYNC_MODULE below instead. - let tag = qjs::JS_VALUE_GET_TAG(val); - if tag == qjs::JS_TAG_OBJECT { - let catch_str = CString::new("catch").unwrap(); - let catch_fn = qjs::JS_GetPropertyStr(ctx.as_raw().as_ptr(), val, catch_str.as_ptr()); - if !qjs::JS_IsUndefined(catch_fn) && !qjs::JS_IsException(catch_fn) { - // Create a no-op function: function() {} - let noop_code = CString::new("(function(){})").unwrap(); - let noop_fname = CString::new("").unwrap(); - let noop_fn = qjs::JS_Eval( - ctx.as_raw().as_ptr(), - noop_code.as_ptr(), - 14, - noop_fname.as_ptr(), - qjs::JS_EVAL_TYPE_GLOBAL as i32, - ); - if !qjs::JS_IsException(noop_fn) { - // Call promise.catch(noop) - let result = qjs::JS_Call( - ctx.as_raw().as_ptr(), - catch_fn, - val, - 1, - &noop_fn as *const _ as *mut _, - ); - if !qjs::JS_IsException(result) { - qjs::JS_FreeValue(ctx.as_raw().as_ptr(), result); - } - qjs::JS_FreeValue(ctx.as_raw().as_ptr(), noop_fn); - } - qjs::JS_FreeValue(ctx.as_raw().as_ptr(), catch_fn); + if unsafe { qjs::JS_IsException(eval_result) } { + leave_require_esm(&globals, filename, &file_url)?; + return Err(rquickjs::Error::Exception); + } + + let eval_value = unsafe { Value::from_raw(ctx.clone(), eval_result) }; + let pending_tla = if let Ok(promise) = Promise::from_js(&ctx, eval_value.clone()) { + match promise.state() { + PromiseState::Pending => { + ignore_unhandled_rejection(&ctx, eval_value); + true + } + PromiseState::Rejected => { + ignore_unhandled_rejection(&ctx, eval_value); + let _ = promise.result::>(); + let rejected = ctx.catch(); + ignore_last_pending_unhandled_rejection(&ctx, rejected.clone()); + leave_require_esm(&globals, filename, &file_url)?; + return Err(ctx.throw(rejected)); } + PromiseState::Resolved => false, } + } else { + false + }; - // Free the return value (Promise from module evaluation) - qjs::JS_FreeValue(ctx.as_raw().as_ptr(), val); + leave_require_esm(&globals, filename, &file_url)?; + + if pending_tla { + mark_async_esm_module(&ctx, &globals, filename, &file_url)?; + return throw_require_async_module(ctx, &globals, filename); } // Read the namespace from globalThis and clean up @@ -238,6 +242,66 @@ fn require_esm_impl<'js>( } } +fn ignore_unhandled_rejection<'js>(ctx: &rquickjs::Ctx<'js>, promise: Value<'js>) { + if let Ok(handler) = ctx + .globals() + .get::<_, rquickjs::Function>("__wasm_rquickjs_ignore_unhandled_rejection") + { + let _ = handler.call::<_, ()>((promise,)); + } +} + +fn ignore_last_pending_unhandled_rejection<'js>(ctx: &rquickjs::Ctx<'js>, reason: Value<'js>) { + if let Ok(handler) = ctx + .globals() + .get::<_, rquickjs::Function>("__wasm_rquickjs_ignore_last_pending_unhandled_rejection") + { + let _ = handler.call::<_, ()>((reason,)); + } +} + +fn enter_require_esm<'js>( + ctx: &rquickjs::Ctx<'js>, + globals: &rquickjs::Object<'js>, + filename: &str, + file_url: &str, +) -> rquickjs::Result<()> { + let registry = match globals.get::<_, rquickjs::Value>("__wasm_rquickjs_require_esm_in_progress") { + Ok(value) if value.is_object() => value.into_object().unwrap(), + _ => { + let object = rquickjs::Object::new(ctx.clone())?; + globals.set("__wasm_rquickjs_require_esm_in_progress", object.clone())?; + object + } + }; + + if registry.get::<_, bool>(filename).unwrap_or(false) + || registry.get::<_, bool>(file_url).unwrap_or(false) + { + let error_ctor: rquickjs::Function = globals.get("Error")?; + let msg = format!("Cannot require() ES Module {filename} in a cycle."); + let error_obj: rquickjs::Object = error_ctor.call((&msg,))?; + error_obj.set("code", "ERR_REQUIRE_CYCLE_MODULE")?; + return Err(ctx.throw(error_obj.into_value())); + } + + registry.set(filename, true)?; + registry.set(file_url, true)?; + Ok(()) +} + +fn leave_require_esm<'js>( + globals: &rquickjs::Object<'js>, + filename: &str, + file_url: &str, +) -> rquickjs::Result<()> { + if let Ok(registry) = globals.get::<_, rquickjs::Object>("__wasm_rquickjs_require_esm_in_progress") { + let _ = registry.remove(filename); + let _ = registry.remove(file_url); + } + Ok(()) +} + fn cached_async_esm_module<'js>(globals: &rquickjs::Object<'js>, filename: &str, file_url: &str) -> bool { let Ok(registry) = globals.get::<_, rquickjs::Object>("__wasm_rquickjs_async_esm_modules") else { return false; diff --git a/crates/wasm-rquickjs/skeleton/src/internal.rs b/crates/wasm-rquickjs/skeleton/src/internal.rs index d4561064..9d9f5343 100644 --- a/crates/wasm-rquickjs/skeleton/src/internal.rs +++ b/crates/wasm-rquickjs/skeleton/src/internal.rs @@ -1,18 +1,49 @@ use futures::future::AbortHandle; use futures_concurrency::future::Join; -use rquickjs::function::{Args, Constructor}; +use indexmap::IndexMap; +use rquickjs::convert::Coerced; +use rquickjs::function::{Args, Constructor, This}; use rquickjs::loader::{BuiltinLoader, BuiltinResolver, FileResolver, Loader, Resolver}; +use rquickjs::object::Property; use rquickjs::{ AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Error, Filter, FromJs, Function, Module, - Object, Promise, Value, async_with, + Object, Promise, Value, async_with, Exception, }; use rquickjs::{CaughtError, prelude::*}; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer}; +use std::borrow::Cow; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::future::Future; -use std::sync::atomic::AtomicUsize; +use std::ops::ControlFlow; +use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; use wstd::runtime::block_on; +fn throw_native_coded_error<'js, T>( + ctx: &Ctx<'js>, + message: &str, + code: &str, + type_error: bool, +) -> rquickjs::Result { + let error_value = if type_error { + let _ = Exception::throw_type(ctx, message); + ctx.catch() + } else { + Exception::from_message(ctx.clone(), message)?.into_value() + }; + let Some(error_obj) = error_value.clone().into_object() else { + return Err(ctx.throw(error_value)); + }; + error_obj.prop( + "code", + Property::from(code).writable().enumerable().configurable(), + )?; + Err(ctx.throw(error_obj.into_value())) +} + /// Resolver that passes `data:` URLs through as-is. struct DataUrlResolver; @@ -31,10 +62,45 @@ impl Resolver for DataUrlResolver { } } +struct PrivateBuiltinResolverGuard; + +impl PrivateBuiltinResolverGuard { + fn is_private_builtin(name: &str) -> bool { + name.starts_with("__wasm_rquickjs_builtin/") + } + + fn is_user_referrer(base: &str) -> bool { + base == crate::JS_EXPORT_MODULE_NAME + || base == "" + || crate::JS_ADDITIONAL_MODULES + .iter() + .any(|(name, _)| base == *name) + || base.starts_with("data:") + || base.starts_with("file:") + || base.starts_with('/') + || base.starts_with("virtual:") + } +} + +impl Resolver for PrivateBuiltinResolverGuard { + fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { + if !Self::is_private_builtin(name) || !Self::is_user_referrer(base) { + return Err(Error::new_resolving(base, name)); + } + + let message = format!("Cannot find module '{}'", name); + throw_native_coded_error(ctx, &message, "ERR_MODULE_NOT_FOUND", false) + } +} + /// Loader for `data:` URL modules (e.g. `data:text/javascript,export default 42`). struct DataUrlLoader; impl DataUrlLoader { + fn content_separator_pos(rest: &str) -> Option { + rest.find(',') + } + fn percent_decode(encoded: &str) -> Option { let bytes = encoded.as_bytes(); let mut decoded = Vec::with_capacity(bytes.len()); @@ -280,7 +346,7 @@ impl DataUrlLoader { } }; let escaped_msg = Self::js_string_escape(&msg); - format!("await Promise.reject(new SyntaxError('{escaped_msg}'));\n") + format!("export default undefined;\nawait Promise.reject(new SyntaxError('{escaped_msg}'));\n") } } @@ -290,17 +356,33 @@ impl Loader for DataUrlLoader { ctx: &Ctx<'js>, path: &str, ) -> rquickjs::Result> { - let rest = path + let path_without_suffix = module_filesystem_path(path); + let rest = path_without_suffix .strip_prefix("data:") .ok_or_else(|| Error::new_loading(path))?; // Find the comma separating metadata from content - let comma_pos = rest.find(',').ok_or_else(|| Error::new_loading(path))?; + let comma_pos = Self::content_separator_pos(rest).ok_or_else(|| Error::new_loading(path))?; let metadata = &rest[..comma_pos]; - let raw_content = &rest[comma_pos + 1..]; + let raw_content = rest[comma_pos + 1..] + .split_once('#') + .map(|(content, _)| content) + .unwrap_or(&rest[comma_pos + 1..]); + + // Parse metadata: e.g. "text/javascript" or "text/javascript;base64". + let is_base64 = metadata + .split(';') + .skip(1) + .any(|part| part.eq_ignore_ascii_case("base64")); + + // Extract base MIME type (before any parameters) + let base_mime = metadata.split(';').next().unwrap_or(metadata).trim(); - // Parse metadata: e.g. "text/javascript" or "text/javascript;base64" - let is_base64 = metadata.ends_with(";base64"); + let json_import_attr = if base_mime == "application/json" { + import_attr_type_from_path(path) + } else { + None + }; let source = if is_base64 { // Simple base64 decoder for ASCII content @@ -310,10 +392,14 @@ impl Loader for DataUrlLoader { Self::percent_decode(raw_content).ok_or_else(|| Error::new_loading(path))? }; - // Extract base MIME type (before any parameters) - let base_mime = metadata.split(';').next().unwrap_or(metadata).trim(); - if base_mime == "application/json" { + if json_import_attr.as_deref() != Some("json") { + let escaped = DataUrlLoader::js_string_escape(path); + let module_source = format!( + "await Promise.reject(Object.assign(new TypeError('Module \"{escaped}\" needs an import attribute of type: json'), {{code: 'ERR_IMPORT_ATTRIBUTE_MISSING'}}));\n" + ); + return Module::declare(ctx.clone(), path, module_source.as_bytes().to_vec()); + } // Validate JSON by attempting a simple parse check. // For valid JSON: embed directly as a JS literal. // For invalid JSON: throw a SyntaxError with V8-compatible message. @@ -332,6 +418,12 @@ impl Loader for DataUrlLoader { // - If valid, strip the `with { ... }` clause // - `assert { ... }` is left as-is (QuickJS will throw SyntaxError, as expected) let source = process_static_import_attrs(&source, path); + if let Some(error_source) = esm_preflight_error_module_source(&source, false, false) { + return Module::declare(ctx.clone(), path, error_source.as_bytes().to_vec()); + } + if let Some(error_source) = data_url_simple_identifier_error_module_source(&source) { + return Module::declare(ctx.clone(), path, error_source.as_bytes().to_vec()); + } let init = ImportMetaInit { url: path.to_string(), @@ -377,6 +469,164 @@ fn base64_decode(input: &str) -> Option> { Some(buf) } +const IMPORT_TYPE_QUERY_PREFIX: &str = "__wasm_rquickjs_import_type="; + +thread_local! { + static IMPORT_ATTR_REWRITE_TOKENS: RefCell> = RefCell::new(HashMap::new()); +} + +static IMPORT_ATTR_REWRITE_SEQ: AtomicUsize = AtomicUsize::new(1); + +fn next_import_attr_rewrite_token(import_type: &str) -> String { + let seq = IMPORT_ATTR_REWRITE_SEQ.fetch_add(1, Ordering::Relaxed); + let nonce = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or(seq as u128); + format!("{import_type}-{seq:x}-{nonce:x}") +} + +fn register_import_attr_rewrite(token: &str, rewritten_specifier: &str) { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + tokens + .borrow_mut() + .insert(token.to_string(), rewritten_specifier.to_string()); + }); +} + +fn existing_import_attr_rewrite(specifier: &str, import_type: &str) -> Option { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + tokens.borrow().iter().find_map(|(token, rewritten)| { + let (token_import_type, _) = token.split_once('-')?; + if token_import_type == import_type && strip_import_type_rewrite_token(rewritten) == specifier { + Some(rewritten.clone()) + } else { + None + } + }) + }) +} + +fn consume_import_type_rewrite_token(token: &str, path: &str) -> Option { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + let mut tokens = tokens.borrow_mut(); + if tokens.get(token).is_some_and(|specifier| specifier == path) { + tokens.remove(token); + token.split_once('-').map(|(import_type, _)| import_type.to_string()) + } else { + None + } + }) +} + +fn transfer_import_type_rewrite_token(unresolved: &str, resolved: &str) { + let token = import_type_rewrite_token(unresolved); + if let Some(token) = token { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + let mut tokens = tokens.borrow_mut(); + if tokens + .get(token) + .is_some_and(|specifier| specifier == unresolved) + { + tokens.insert(token.to_string(), resolved.to_string()); + } + }); + } +} + +fn discard_import_type_rewrite_token(path: &str) { + let token = import_type_rewrite_token(path); + if let Some(token) = token { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + let mut tokens = tokens.borrow_mut(); + if tokens.get(token).is_some_and(|specifier| specifier == path) { + tokens.remove(token); + } + }); + } +} + +fn discard_generated_import_type_rewrite_token(path: &str) { + let token = import_type_rewrite_token(path); + if let Some(token) = token { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + tokens.borrow_mut().remove(token); + }); + } +} + +fn import_type_rewrite_token(path: &str) -> Option<&str> { + if let Some(rest) = path.strip_prefix("data:") + && let Some(comma_pos) = DataUrlLoader::content_separator_pos(rest) + { + let metadata = &rest[..comma_pos]; + return metadata + .split(';') + .find_map(|part| part.strip_prefix(IMPORT_TYPE_QUERY_PREFIX)); + } + + let suffix = split_module_path_suffix(path).1; + if suffix.is_empty() { + return None; + } + let query = suffix + .strip_prefix('?') + .or_else(|| suffix.strip_prefix('#')) + .unwrap_or(suffix); + query + .split(['&', '#']) + .find_map(|part| part.strip_prefix(IMPORT_TYPE_QUERY_PREFIX)) +} + +fn has_import_type_rewrite_token(path: &str) -> bool { + import_type_rewrite_token(path).is_some_and(|token| { + IMPORT_ATTR_REWRITE_TOKENS.with(|tokens| { + tokens + .borrow() + .get(token) + .is_some_and(|specifier| specifier == path) + }) + }) +} + +fn read_import_specifier_literal( + source: &str, + pos: usize, +) -> Option<(usize, usize, usize, usize)> { + let bytes = source.as_bytes(); + if !matches!(bytes.get(pos), Some(b'"' | b'\'')) { + return None; + } + + let literal_start = pos; + let quote = bytes[pos]; + let mut i = pos + 1; + let specifier_start = i; + while i < bytes.len() && bytes[i] != quote { + if bytes[i] == b'\\' { + i += 1; + } + i += 1; + } + let specifier_end = i; + if i < bytes.len() { + i += 1; + } + Some((literal_start, i, specifier_start, specifier_end)) +} + +fn read_closed_import_specifier_literal( + source: &str, + pos: usize, +) -> Option<(usize, usize, usize, usize)> { + let literal = read_import_specifier_literal(source, pos)?; + let bytes = source.as_bytes(); + if literal.1 <= pos + 1 || literal.1 > bytes.len() || bytes.get(literal.1 - 1) != bytes.get(pos) { + return None; + } + Some(literal) +} + /// Process static import attributes in JavaScript module source code. /// /// Handles patterns like `import "specifier" with { type: "json" }`. @@ -390,11 +640,18 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { let mut i = 0; while i < len { + if let Some(next) = skip_non_code(source, i, true) { + result.push_str(&source[i..next]); + i = next; + continue; + } + // Look for 'import' keyword if bytes[i] == b'i' && i + 6 <= len && &source[i..i + 6] == "import" && (i == 0 || !is_id_char(bytes[i - 1])) + && (i == 0 || (bytes[i - 1] != b'.' && bytes[i - 1] != b'#')) && (i + 6 >= len || !is_id_char(bytes[i + 6]) || bytes[i + 6] == b'"' @@ -403,32 +660,63 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { let import_start = i; i += 6; - // Skip whitespace - while i < len && bytes[i].is_ascii_whitespace() { - i += 1; + let mut specifier_literal = None; + + i = skip_ws_comments(source, i); + + if i < len && bytes[i] == b'(' { + if is_object_method_shorthand_import(source, import_start, i) { + result.push_str(&source[import_start..i]); + continue; + } + if let Some((rewritten, next)) = rewrite_dynamic_import_call(source, import_start, i) { + result.push_str(&rewritten); + i = next; + continue; + } + result.push_str(&source[import_start..i]); + continue; } - // Check for string literal (bare import: import "spec") - if i < len && (bytes[i] == b'"' || bytes[i] == b'\'') { - let quote = bytes[i]; - i += 1; - let spec_start = i; - while i < len && bytes[i] != quote { - if bytes[i] == b'\\' { - i += 1; + if let Some(literal) = read_import_specifier_literal(source, i) { + specifier_literal = Some(literal); + i = literal.1; + } else { + while i < len { + if bytes[i] == b'f' + && i + 4 <= len + && &source[i..i + 4] == "from" + && (i == 0 || !is_id_char(bytes[i - 1])) + && (i + 4 >= len || !is_id_char(bytes[i + 4])) + { + let mut j = i + 4; + j = skip_ws_comments(source, j); + if let Some(literal) = read_import_specifier_literal(source, j) { + specifier_literal = Some(literal); + i = literal.1; + break; + } + } + if matches!(bytes[i], b';' | b'\n' | b'\r') { + break; } i += 1; } - let spec_end = i; - if i < len { - i += 1; // skip closing quote - } + } + + if let Some((spec_lit_start, spec_lit_end, spec_start, spec_end)) = specifier_literal { let specifier = &source[spec_start..spec_end]; // Skip whitespace let after_spec = i; - while i < len && bytes[i].is_ascii_whitespace() { - i += 1; + i = skip_ws_comments(source, i); + + if i + 6 <= len + && &source[i..i + 6] == "assert" + && (i + 6 >= len || !is_id_char(bytes[i + 6])) + { + return "await Promise.reject(new SyntaxError('Unexpected identifier'));\n" + .to_string(); } // Check for 'with' keyword (not 'with(' which is a with-statement) @@ -438,9 +726,7 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { { let with_start = i; i += 4; - while i < len && bytes[i].is_ascii_whitespace() { - i += 1; - } + i = skip_ws_comments(source, i); if i < len && bytes[i] == b'{' { i += 1; let attrs_start = i; @@ -450,14 +736,13 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { b'{' => depth += 1, b'}' => depth -= 1, b'"' | b'\'' => { - let q = bytes[i]; - i += 1; - while i < len && bytes[i] != q { - if bytes[i] == b'\\' { - i += 1; - } - i += 1; - } + let next = skip_string_or_template(source, i); + i = if next == len && bytes.get(len - 1) != Some(&bytes[i]) { + len + 1 + } else { + next + }; + continue; } _ => {} } @@ -465,13 +750,16 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { } let attrs_content = &source[attrs_start..if i > 0 { i - 1 } else { i }]; - // Parse the type value from attributes - let type_value = extract_attr_type_value(attrs_content); + let attr_info = extract_import_attr_info(attrs_content); + if attr_info.type_non_string { + return syntax_error_module_source("Import attribute value must be a string"); + } let format = determine_data_url_format(specifier); // Validate if let Some(error_module) = validate_static_import_attrs( - type_value.as_deref(), + attr_info.type_value.as_deref(), + attr_info.unsupported_key.as_deref(), format, specifier, module_path, @@ -480,14 +768,14 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { } // Valid: strip the with clause, keep everything else - result.push_str(&source[import_start..after_spec]); - // Skip any remaining content after the with block - // and append the rest of the source - while i < len && bytes[i].is_ascii_whitespace() { - i += 1; - } - result.push_str(&source[i..]); - return result; + result.push_str(&source[import_start..spec_lit_start]); + result.push_str(&rewrite_import_specifier_literal( + &source[spec_lit_start..spec_lit_end], + specifier, + attr_info.type_value.as_deref(), + )); + result.push_str(&source[spec_lit_end..after_spec]); + continue; } else { // 'with' not followed by '{', not import attrs i = with_start; @@ -495,7 +783,14 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { continue; } } - // No 'with' keyword, output as-is + + let format = determine_data_url_format(specifier); + if let Some(error_module) = + validate_static_import_attrs(None, None, format, specifier, module_path) + { + return error_module; + } + result.push_str(&source[import_start..i]); continue; } @@ -507,608 +802,5583 @@ fn process_static_import_attrs(source: &str, module_path: &str) -> String { continue; } - result.push(bytes[i] as char); - i += 1; + if let Some(ch) = source[i..].chars().next() { + result.push(ch); + i += ch.len_utf8(); + } else { + break; + } } result } -fn is_id_char(b: u8) -> bool { - b.is_ascii_alphanumeric() || b == b'_' || b == b'$' +fn rewrite_import_specifier_literal(literal: &str, specifier: &str, type_value: Option<&str>) -> String { + if type_value != Some("json") { + return literal.to_string(); + } + let rewritten = append_import_type_query(specifier, "json"); + format!("\"{}\"", escape_js_string(&rewritten)) } -/// Extract the value of the `type` key from a simple attributes string like `type:"json"`. -fn extract_attr_type_value(attrs: &str) -> Option { - // Look for `type` key followed by `:` and a string value - let bytes = attrs.as_bytes(); +fn rewrite_dynamic_import_call( + source: &str, + import_start: usize, + open_paren: usize, +) -> Option<(String, usize)> { + let bytes = source.as_bytes(); let len = bytes.len(); - let mut i = 0; + let mut i = open_paren + 1; + i = skip_ws_comments(source, i); + if i >= len || (bytes[i] != b'"' && bytes[i] != b'\'') { + return rewrite_dynamic_import_expression_call(source, open_paren); + } - while i < len { - // Skip whitespace - while i < len && (bytes[i].is_ascii_whitespace() || bytes[i] == b',') { - i += 1; - } - if i >= len { - break; - } + let (spec_literal_start, spec_literal_end, _, _) = + read_closed_import_specifier_literal(source, i)?; + i = spec_literal_end; - // Read key (identifier or quoted string) - let key_start = i; - if bytes[i] == b'"' || bytes[i] == b'\'' { - let q = bytes[i]; - i += 1; - while i < len && bytes[i] != q { - if bytes[i] == b'\\' { - i += 1; - } - i += 1; - } - if i < len { - i += 1; + i = skip_ws_comments(source, i); + + if i < len && bytes[i] == b')' { + return Some(( + format!( + "globalThis.__wasm_rquickjs_import_attr_dynamic_import(import.meta.url,{},undefined,true,(__wasm_rquickjs_prepared)=>import(__wasm_rquickjs_prepared))", + &source[spec_literal_start..spec_literal_end] + ), + i + 1, + )); + } + if i >= len || bytes[i] != b',' { + return None; + } + i += 1; + let options_start = i; + let mut paren_depth = 1usize; + let mut brace_depth = 0usize; + while i < len { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; } - } else { - while i < len && is_id_char(bytes[i]) { - i += 1; + b'(' => paren_depth += 1, + b')' => { + paren_depth = paren_depth.saturating_sub(1); + if paren_depth == 0 { + let options = &source[options_start..i]; + return Some(( + format!( + "globalThis.__wasm_rquickjs_import_attr_dynamic_import(import.meta.url,{},{},true,(__wasm_rquickjs_prepared)=>import(__wasm_rquickjs_prepared))", + &source[spec_literal_start..spec_literal_end], + options + ), + i + 1, + )); + } } + b'{' => brace_depth += 1, + b'}' => brace_depth = brace_depth.saturating_sub(1), + _ => {} } - let key = attrs[key_start..i].trim_matches(|c: char| c == '"' || c == '\''); - - // Skip whitespace and colon - while i < len && bytes[i].is_ascii_whitespace() { - i += 1; - } - if i < len && bytes[i] == b':' { - i += 1; - } - while i < len && bytes[i].is_ascii_whitespace() { - i += 1; - } + i += 1; + } - // Read value (string) - if i < len && (bytes[i] == b'"' || bytes[i] == b'\'') { - let q = bytes[i]; - i += 1; - let val_start = i; - while i < len && bytes[i] != q { - if bytes[i] == b'\\' { - i += 1; - } - i += 1; - } - let val = &attrs[val_start..i]; - if i < len { - i += 1; - } + let _ = import_start; + let _ = brace_depth; + None +} - if key == "type" { - return Some(val.to_string()); - } - } else { - // Skip non-string values - while i < len && bytes[i] != b',' && bytes[i] != b'}' { - i += 1; - } +fn previous_non_whitespace_pos(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = pos; + while i > 0 { + i -= 1; + if !bytes[i].is_ascii_whitespace() { + return Some(i); } } None } -/// Determine module format from a data URL specifier. -fn determine_data_url_format(specifier: &str) -> Option<&'static str> { - if let Some(rest) = specifier.strip_prefix("data:") { - if let Some(comma_pos) = rest.find(',') { - let metadata = &rest[..comma_pos]; - let base_mime = metadata.split(';').next().unwrap_or(metadata).trim(); - return match base_mime { - "application/json" => Some("json"), - "text/javascript" | "application/javascript" => Some("module"), - "text/css" => Some("css"), - _ => None, - }; +fn previous_word(source: &str, pos: usize) -> Option<(&str, usize)> { + let bytes = source.as_bytes(); + let mut end = pos; + while end > 0 && bytes[end - 1].is_ascii_whitespace() { + end -= 1; + } + let mut start = end; + while start > 0 && is_id_char(bytes[start - 1]) { + start -= 1; + } + (start < end).then_some((&source[start..end], start)) +} + +fn next_non_whitespace_byte(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = pos; + while i < bytes.len() { + if !bytes[i].is_ascii_whitespace() { + return Some(bytes[i]); } - } else if specifier.ends_with(".json") { - return Some("json"); + i += 1; } None } -/// Validate static import attributes. Returns Some(error_module_source) if invalid, None if valid. -fn validate_static_import_attrs( - type_value: Option<&str>, - format: Option<&str>, - specifier: &str, - _module_path: &str, -) -> Option { - if let Some(tv) = type_value { - match tv { - "json" => { - if format == Some("module") { - return Some( - "await Promise.reject(Object.assign(new TypeError('Cannot use import attributes to change the type of a JavaScript module'), {code: 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE'}));\n".to_string() - ); +fn matching_paren_end(source: &str, open_paren: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = open_paren + 1; + let mut depth = 1usize; + while i < bytes.len() { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'(' => depth += 1, + b')' => { + depth = depth.saturating_sub(1); + if depth == 0 { + return Some(i + 1); } } - "css" => { - // CSS is a recognized type, let loader handle it + _ => {} + } + i += 1; + } + None +} + +fn is_object_method_shorthand_import(source: &str, import_start: usize, open_paren: usize) -> bool { + if matching_paren_end(source, open_paren) + .and_then(|close| next_non_whitespace_byte(source, close)) + != Some(b'{') + { + return false; + } + if previous_word(source, import_start).is_some_and(|(word, _)| word == "static") + { + return true; + } + + let bytes = source.as_bytes(); + let mut pos = import_start; + loop { + let Some(prev) = previous_non_whitespace_pos(source, pos) else { + return false; + }; + match bytes[prev] { + b'{' | b',' | b';' => return true, + b'*' => { + pos = prev; + continue; + } + _ => {} + } + + let Some((word, start)) = previous_word(source, pos) else { + return false; + }; + if matches!(word, "async" | "get" | "set" | "static") { + pos = start; + continue; + } + return false; + } +} + +fn rewrite_dynamic_import_expression_call(source: &str, open_paren: usize) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + let len = bytes.len(); + let mut i = open_paren + 1; + let expr_start = i; + let mut paren_depth = 0usize; + let mut bracket_depth = 0usize; + let mut brace_depth = 0usize; + while i < len { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'(' => paren_depth += 1, + b')' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => { + let expr = source[expr_start..i].trim(); + return Some(( + format!( + "globalThis.__wasm_rquickjs_import_attr_dynamic_import(import.meta.url,{},undefined,true,(__wasm_rquickjs_prepared)=>import(__wasm_rquickjs_prepared))", + expr + ), + i + 1, + )); + } + b')' => paren_depth = paren_depth.saturating_sub(1), + b'[' => bracket_depth += 1, + b']' => bracket_depth = bracket_depth.saturating_sub(1), + b'{' => brace_depth += 1, + b'}' => brace_depth = brace_depth.saturating_sub(1), + b',' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => break, + _ => {} + } + i += 1; + } + if i >= len || bytes[i] != b',' { + return None; + } + let expr = source[expr_start..i].trim(); + i += 1; + let options_start = i; + let mut call_paren_depth = 1usize; + while i < len { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'(' => call_paren_depth += 1, + b')' => { + call_paren_depth = call_paren_depth.saturating_sub(1); + if call_paren_depth == 0 { + let options = &source[options_start..i]; + return Some(( + format!( + "globalThis.__wasm_rquickjs_import_attr_dynamic_import(import.meta.url,{},{},true,(__wasm_rquickjs_prepared)=>import(__wasm_rquickjs_prepared))", + expr, options + ), + i + 1, + )); + } + } + _ => {} + } + i += 1; + } + None +} + +#[derive(Default)] +struct ImportAttrInfo { + type_value: Option, + unsupported_key: Option, + type_non_string: bool, +} + +fn append_import_type_query(specifier: &str, import_type: &str) -> String { + if let Some(rewritten) = existing_import_attr_rewrite(specifier, import_type) { + return rewritten; + } + + let token = next_import_attr_rewrite_token(import_type); + if specifier.starts_with("data:") { + if let Some(comma_pos) = specifier.strip_prefix("data:").and_then(DataUrlLoader::content_separator_pos) { + let insert_pos = "data:".len() + comma_pos; + let rewritten = format!( + "{};{IMPORT_TYPE_QUERY_PREFIX}{token}{}", + &specifier[..insert_pos], + &specifier[insert_pos..] + ); + register_import_attr_rewrite(&token, &rewritten); + return rewritten; + } + return specifier.to_string(); + } + let (base, suffix) = split_module_path_suffix(specifier); + let separator = if suffix.is_empty() { "?" } else { "&" }; + let rewritten = format!("{base}{suffix}{separator}{IMPORT_TYPE_QUERY_PREFIX}{token}"); + register_import_attr_rewrite(&token, &rewritten); + rewritten +} + +fn import_attr_type_from_path(path: &str) -> Option { + import_type_rewrite_token(path) + .and_then(|token| consume_import_type_rewrite_token(token, path)) +} + +fn strip_import_type_rewrite_token(path: &str) -> String { + let Some(token) = import_type_rewrite_token(path) else { + return path.to_string(); + }; + let marker = format!("{IMPORT_TYPE_QUERY_PREFIX}{token}"); + + if let Some(rest) = path.strip_prefix("data:") + && let Some(comma_pos) = DataUrlLoader::content_separator_pos(rest) + { + let metadata_end = "data:".len() + comma_pos; + let metadata = &path[..metadata_end]; + if let Some(marker_start) = metadata.find(&marker) { + let remove_start = marker_start + .checked_sub(1) + .filter(|idx| path.as_bytes().get(*idx) == Some(&b';')) + .unwrap_or(marker_start); + return format!("{}{}", &path[..remove_start], &path[marker_start + marker.len()..]); + } + } + + let (base, suffix) = split_module_path_suffix(path); + if suffix.is_empty() { + return path.to_string(); + } + let Some(marker_start) = suffix.find(&marker) else { + return path.to_string(); + }; + let remove_start = marker_start + .checked_sub(1) + .filter(|idx| matches!(suffix.as_bytes().get(*idx), Some(b'?') | Some(b'&'))) + .unwrap_or(marker_start); + let mut stripped_suffix = String::with_capacity(suffix.len().saturating_sub(marker.len())); + stripped_suffix.push_str(&suffix[..remove_start]); + stripped_suffix.push_str(&suffix[marker_start + marker.len()..]); + if stripped_suffix == "?" || stripped_suffix == "#" { + base.to_string() + } else { + format!("{base}{stripped_suffix}") + } +} + +fn is_id_char(b: u8) -> bool { + b.is_ascii_alphanumeric() || b == b'_' || b == b'$' +} + +fn extract_import_attr_info(attrs: &str) -> ImportAttrInfo { + let bytes = attrs.as_bytes(); + let len = bytes.len(); + let mut i = 0; + let mut info = ImportAttrInfo::default(); + + while i < len { + // Skip whitespace + while i < len && (bytes[i].is_ascii_whitespace() || bytes[i] == b',') { + i += 1; + } + if i >= len { + break; + } + + // Read key (identifier or quoted string) + let key_start = i; + if bytes[i] == b'"' || bytes[i] == b'\'' { + let q = bytes[i]; + i += 1; + while i < len && bytes[i] != q { + if bytes[i] == b'\\' { + i += 1; + } + i += 1; + } + if i < len { + i += 1; + } + } else { + while i < len && is_id_char(bytes[i]) { + i += 1; + } + } + let key = attrs[key_start..i].trim_matches(|c: char| c == '"' || c == '\''); + if key != "type" && info.unsupported_key.is_none() { + info.unsupported_key = Some(key.to_string()); + } + + // Skip whitespace and colon + while i < len && bytes[i].is_ascii_whitespace() { + i += 1; + } + if i < len && bytes[i] == b':' { + i += 1; + } + while i < len && bytes[i].is_ascii_whitespace() { + i += 1; + } + + // Read value (string) + if i < len && (bytes[i] == b'"' || bytes[i] == b'\'') { + let q = bytes[i]; + i += 1; + let val_start = i; + while i < len && bytes[i] != q { + if bytes[i] == b'\\' { + i += 1; + } + i += 1; + } + let val = &attrs[val_start..i]; + if i < len { + i += 1; + } + + if key == "type" { + info.type_value = Some(val.to_string()); + } + } else { + if key == "type" { + info.type_non_string = true; + } + // Skip non-string values + while i < len && bytes[i] != b',' && bytes[i] != b'}' { + i += 1; + } + } + } + info +} + +/// Determine module format from a data URL specifier. +fn determine_data_url_format(specifier: &str) -> Option<&'static str> { + if let Some(rest) = specifier.strip_prefix("data:") { + if let Some(comma_pos) = DataUrlLoader::content_separator_pos(rest) { + let metadata = &rest[..comma_pos]; + let base_mime = metadata.split(';').next().unwrap_or(metadata).trim(); + return match base_mime { + "application/json" => Some("json"), + "text/javascript" | "application/javascript" => Some("module"), + "text/css" => Some("css"), + _ => None, + }; + } + } else if specifier.starts_with("node:") { + return Some("module"); + } else if module_filesystem_path(specifier).ends_with(".json") { + return Some("json"); + } else if module_filesystem_path(specifier).ends_with(".js") + || module_filesystem_path(specifier).ends_with(".mjs") + || module_filesystem_path(specifier).ends_with(".cjs") + { + return Some("module"); + } + None +} + +/// Validate static import attributes. Returns Some(error_module_source) if invalid, None if valid. +fn validate_static_import_attrs( + type_value: Option<&str>, + unsupported_key: Option<&str>, + format: Option<&str>, + specifier: &str, + _module_path: &str, +) -> Option { + let (code, message) = validate_import_attrs_error(type_value, unsupported_key, format, specifier)?; + Some(import_attr_error_module_source(&code, &message)) +} + +fn validate_import_attrs_error( + type_value: Option<&str>, + unsupported_key: Option<&str>, + format: Option<&str>, + specifier: &str, +) -> Option<(String, String)> { + if let Some(tv) = type_value { + match tv { + "json" => { + if format == Some("module") { + return Some(( + "ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE".to_string(), + "Cannot use import attributes to change the type of a JavaScript module" + .to_string(), + )); + } + } + "css" => { + if format != Some("css") { + return Some(( + "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED".to_string(), + "Import attribute type \"css\" is not supported".to_string(), + )); + } } other => { - let escaped_type = DataUrlLoader::js_string_escape(other); - return Some(format!( - "await Promise.reject(Object.assign(new TypeError('Import attribute type \"{escaped_type}\" is not supported'), {{code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED'}}));\n" + return Some(( + "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED".to_string(), + format!("Import attribute type \"{other}\" is not supported"), )); } } } - // Check for missing required attributes (JSON without type: "json") - if format == Some("json") && type_value != Some("json") { - let escaped = DataUrlLoader::js_string_escape(specifier); - return Some(format!( - "await Promise.reject(Object.assign(new TypeError('Module \"{escaped}\" needs an import attribute of type: json'), {{code: 'ERR_IMPORT_ATTRIBUTE_MISSING'}}));\n" - )); + // Check for missing required attributes (JSON without type: "json") + if format == Some("json") && type_value != Some("json") { + return Some(( + "ERR_IMPORT_ATTRIBUTE_MISSING".to_string(), + format!("Module \"{specifier}\" needs an import attribute of type: json"), + )); + } + + if let Some(key) = unsupported_key { + return Some(( + "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED".to_string(), + format!("Import attribute \"{key}\" is not supported"), + )); + } + + None +} + +fn import_attr_error_module_source(code: &str, message: &str) -> String { + format!("await {};\n", import_attr_error_expression(code, message)) +} + +fn syntax_error_module_source(message: &str) -> String { + let escaped = DataUrlLoader::js_string_escape(message); + format!("await Promise.reject(new SyntaxError('{escaped}'));\n") +} + +fn import_attr_error_expression(code: &str, message: &str) -> String { + let escaped_message = DataUrlLoader::js_string_escape(message); + let escaped_code = DataUrlLoader::js_string_escape(code); + format!( + "Promise.reject(Object.assign(new TypeError('{escaped_message}'), {{code: '{escaped_code}'}}))" + ) +} + +fn throw_import_attr_type_incompatible<'js, T>(ctx: &Ctx<'js>) -> rquickjs::Result { + let globals = ctx.globals(); + let type_error_ctor: Function = globals.get("TypeError")?; + let error_obj: Object = + type_error_ctor.call(("Cannot use import attributes to change the type of a JavaScript module",))?; + error_obj.set("code", "ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE")?; + Err(ctx.throw(error_obj.into_value())) +} + +fn esm_preflight_error_module_source( + source: &str, + package_type_module_js: bool, + raw_cjs_global_messages: bool, +) -> Option { + if package_type_module_js { + let cjs_global = find_bare_cjs_global_in_esm(source); + if cjs_global.is_none() { + return None; + } + let name = cjs_global.unwrap_or("module"); + let message = format!( + "{name} is not defined in ES module scope. This file is being treated as an ES module because it has a .js file extension and package.json contains \"type\": \"module\". To treat it as a CommonJS script, rename it to use the '.cjs' file extension." + ); + let escaped = DataUrlLoader::js_string_escape(&message); + return Some(format!( + "await Promise.reject(new ReferenceError('{escaped}'));\n" + )); + } + + let Some(name) = find_bare_cjs_global_in_esm(source) else { + return None; + }; + let message = if raw_cjs_global_messages { + match name { + "require" => "require is not defined", + "exports" => "exports is not defined", + "module" => "module is not defined", + "__filename" => "__filename is not defined", + "__dirname" => "__dirname is not defined", + _ => return None, + } + } else { + match name { + "require" => "require is not defined in ES module scope, you can use import instead", + "exports" => "exports is not defined in ES module scope", + "module" => "module is not defined in ES module scope", + "__filename" => "__filename is not defined in ES module scope", + "__dirname" => "__dirname is not defined in ES module scope", + _ => return None, + } + }; + let escaped = DataUrlLoader::js_string_escape(message); + Some(format!( + "await Promise.reject(new ReferenceError('{escaped}'));\n" + )) +} + +fn esm_require_global_preflight_error_module_source( + source: &str, + raw_cjs_global_messages: bool, +) -> Option { + find_bare_cjs_global_in_esm_among(source, &["require"])?; + let message = if raw_cjs_global_messages { + "require is not defined" + } else { + "require is not defined in ES module scope, you can use import instead" + }; + let escaped = DataUrlLoader::js_string_escape(message); + Some(format!( + "await Promise.reject(new ReferenceError('{escaped}'));\n" + )) +} + +#[derive(Debug, PartialEq, Eq)] +struct StaticNamedImport { + imported: String, + local: String, +} + +fn cjs_named_import_error_module_source(ctx: &Ctx<'_>, filename: &str, source: &str) -> Option { + let conditions = + NodeModulesResolver::conditions_from_global(ctx, NodePackageResolveMode::CjsAnalysis.default_conditions()); + find_cjs_named_import_error(filename, source, &conditions).map(|message| { + let escaped = DataUrlLoader::js_string_escape(&message); + format!("await Promise.reject(new SyntaxError('{escaped}'));\n") + }) +} + +fn find_cjs_named_import_error(filename: &str, source: &str, conditions: &[String]) -> Option { + let mut result = None; + let _ = scan_code_positions(source, true, |i, _| { + if let Some((specifier, named_imports, next)) = parse_static_named_import(source, i) { + if let Some(message) = cjs_named_import_error_message(filename, &specifier, &named_imports, conditions) { + result = Some(message); + return ControlFlow::Break(()); + } + return ControlFlow::Continue(Some(next)); + } + ControlFlow::Continue(None) + }); + result +} + +fn cjs_named_import_error_message( + filename: &str, + specifier: &str, + named_imports: &[StaticNamedImport], + conditions: &[String], +) -> Option { + if named_imports.is_empty() || !could_resolve_to_cjs_for_named_import_error(specifier) { + return None; + } + let resolved = resolve_cjs_reexport_path(filename, specifier, conditions)?; + if !resolved.ends_with(".cjs") && !is_cjs_js_file_for_named_import_error(&resolved) { + return None; + } + let source = std::fs::read_to_string(&resolved).ok()?; + let analysis = analyze_cjs_exports_for_file(&resolved, &source, &mut HashSet::new(), conditions); + if !analysis.is_cjs && analysis.exports.is_empty() && analysis.reexports.is_empty() { + return None; + } + + for named_import in named_imports { + if named_import.imported == "default" { + continue; + } + if !analysis.exports.iter().any(|name| name == &named_import.imported) { + let mut message = format!( + "Named export '{}' not found. The requested module '{}' is a CommonJS module, which may not support all module.exports as named exports.\nCommonJS modules can always be imported via the default export, for example using:\n\nimport pkg from '{}';\n", + named_import.imported, specifier, specifier + ); + if named_imports.len() == 1 { + message.push_str(&format!( + "const {{ {} }} = pkg;\n", + format_cjs_named_import_binding(named_import) + )); + } + return Some(message); + } + } + None +} + +fn could_resolve_to_cjs_for_named_import_error(specifier: &str) -> bool { + if specifier.starts_with("node:") || specifier.starts_with("data:") || specifier.contains("://") { + return false; + } + if specifier.starts_with("./") || specifier.starts_with("../") || specifier.starts_with('/') { + let (path, _) = split_module_path_suffix(specifier); + return match std::path::Path::new(path).extension().and_then(|ext| ext.to_str()) { + Some("cjs" | "js") | None => true, + Some(_) => false, + }; + } + true +} + +fn format_cjs_named_import_binding(named_import: &StaticNamedImport) -> String { + let imported = if is_valid_js_identifier_name(&named_import.imported) { + named_import.imported.clone() + } else { + format!("\"{}\"", escape_js_string(&named_import.imported)) + }; + if named_import.imported == named_import.local { + imported + } else { + format!("{}: {}", imported, named_import.local) + } +} + +fn is_valid_js_identifier_name(value: &str) -> bool { + let bytes = value.as_bytes(); + let Some((&first, rest)) = bytes.split_first() else { + return false; + }; + is_ident_start(first) && rest.iter().copied().all(is_ident_continue) +} + +fn is_cjs_js_file_for_named_import_error(filename: &str) -> bool { + filename.ends_with(".js") && package_scope_type(filename).as_deref() != Some("module") +} + +const CJS_GLOBAL_NAMES: [&str; 5] = ["require", "exports", "module", "__filename", "__dirname"]; + +fn skip_esm_cjs_global_scanner_span(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + if let Some(next) = parse_object_method_span(source, pos) { + return Some(next); + } + match bytes[pos] { + b'\'' | b'"' | b'`' => Some(skip_string_or_template(source, pos)), + b'/' if pos + 1 < bytes.len() && bytes[pos + 1] == b'/' => { + let mut i = pos + 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + Some(i) + } + b'/' if pos + 1 < bytes.len() && bytes[pos + 1] == b'*' => { + let mut i = pos + 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + Some((i + 2).min(bytes.len())) + } + b'/' if is_regex_literal_start(source, pos) => Some(skip_regex_literal(source, pos)), + _ => None, + } +} + +fn add_declared_cjs_global_bindings(bindings: Vec, names: &[&str], declared: &mut Vec) { + for name in bindings { + if names.contains(&name.as_str()) && !declared.iter().any(|existing| existing == &name) { + declared.push(name); + } + } +} + +fn collect_declared_cjs_globals_in_esm(source: &str) -> Vec { + let bytes = source.as_bytes(); + let mut i = 0usize; + let mut declared = Vec::::new(); + while i < bytes.len() { + if let Some(next) = skip_esm_cjs_global_scanner_span(source, i) { + i = next; + continue; + } + + if let Some((bindings, next)) = parse_import_declaration_bindings(source, i) { + add_declared_cjs_global_bindings(bindings, &CJS_GLOBAL_NAMES, &mut declared); + i = next; + continue; + } + + if let Some(next) = parse_arrow_function_span(source, i) { + i = next; + continue; + } + + if let Some((bindings, next)) = parse_declaration_span(source, i) { + add_declared_cjs_global_bindings(bindings, &CJS_GLOBAL_NAMES, &mut declared); + i = next; + continue; + } + + i = next_char_boundary(source, i); + } + declared +} + +fn find_bare_cjs_global_in_esm(source: &str) -> Option<&'static str> { + find_bare_cjs_global_in_esm_among(source, &CJS_GLOBAL_NAMES) +} + +fn find_bare_cjs_global_in_esm_among(source: &str, names: &'static [&'static str]) -> Option<&'static str> { + let bytes = source.as_bytes(); + let mut i = 0usize; + let mut declared = Vec::::new(); + while i < bytes.len() { + if let Some(next) = skip_esm_cjs_global_scanner_span(source, i) { + i = next; + continue; + } + + if let Some((bindings, next)) = parse_import_declaration_bindings(source, i) { + add_declared_cjs_global_bindings(bindings, names, &mut declared); + i = next; + continue; + } + + if let Some(next) = parse_arrow_function_span(source, i) { + i = next; + continue; + } + + if let Some((bindings, _)) = parse_variable_declaration_span(source, i) { + add_declared_cjs_global_bindings(bindings, names, &mut declared); + i = next_char_boundary(source, i); + continue; + } + + if let Some((bindings, next)) = parse_function_declaration_span(source, i) + .or_else(|| parse_class_declaration_span(source, i)) + { + add_declared_cjs_global_bindings(bindings, names, &mut declared); + i = next; + continue; + } + + for name in names { + if let Some(name_end) = parse_free_ident_name(source, i, name) + && previous_significant_byte(source, i) != Some(b'.') + && !is_typeof_operand(source, i) + && !declared.iter().any(|declared| declared == name) + { + let next = skip_ws_comments(source, name_end); + if next < bytes.len() && bytes[next] == b':' { + break; + } + return Some(name); + } + } + i = next_char_boundary(source, i); + } + None +} + +fn is_typeof_operand(source: &str, pos: usize) -> bool { + let bytes = source.as_bytes(); + let mut end = pos; + while end > 0 && bytes[end - 1].is_ascii_whitespace() { + end -= 1; + } + if end > 0 && bytes[end - 1] == b'(' { + end -= 1; + while end > 0 && bytes[end - 1].is_ascii_whitespace() { + end -= 1; + } + } + let mut start = end; + while start > 0 && is_ident_continue(bytes[start - 1]) { + start -= 1; + } + start < end && &source[start..end] == "typeof" && is_ident_start_boundary(bytes, start) +} + +fn find_statement_end(source: &str, pos: usize) -> usize { + let bytes = source.as_bytes(); + let mut i = pos; + while i < bytes.len() { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + i += 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + continue; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(bytes.len()); + continue; + } + b';' | b'\n' | b'\r' => return i + 1, + _ => i = next_char_boundary(source, i), + } + } + i +} + +fn parse_import_declaration_bindings(source: &str, pos: usize) -> Option<(Vec, usize)> { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, parse_ident_name(source, pos, "import")?); + if i < bytes.len() && (bytes[i] == b'(' || bytes[i] == b'\'' || bytes[i] == b'"') { + return Some((Vec::new(), find_statement_end(source, i))); + } + + let mut bindings = Vec::new(); + if i < bytes.len() && bytes[i] == b'*' { + i = skip_ws_comments(source, i + 1); + if let Some(as_end) = parse_ident_name(source, i, "as") { + i = skip_ws_comments(source, as_end); + let (name, _) = read_ident(source, i)?; + bindings.push(name); + } + return Some((bindings, find_statement_end(source, i))); + } + + if i < bytes.len() && bytes[i] == b'{' { + collect_named_import_bindings(source, i, &mut bindings)?; + return Some((bindings, find_statement_end(source, i))); + } + + if let Some((name, next)) = read_ident(source, i) { + bindings.push(name); + i = skip_ws_comments(source, next); + if i < bytes.len() && bytes[i] == b',' { + i = skip_ws_comments(source, i + 1); + if i < bytes.len() && bytes[i] == b'*' { + i = skip_ws_comments(source, i + 1); + if let Some(as_end) = parse_ident_name(source, i, "as") { + i = skip_ws_comments(source, as_end); + let (name, _) = read_ident(source, i)?; + bindings.push(name); + } + } else if i < bytes.len() && bytes[i] == b'{' { + collect_named_import_bindings(source, i, &mut bindings)?; + } + } + return Some((bindings, find_statement_end(source, i))); + } + + Some((bindings, find_statement_end(source, i))) +} + +fn parse_static_named_import(source: &str, pos: usize) -> Option<(String, Vec, usize)> { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, parse_ident_name(source, pos, "import")?); + if i < bytes.len() && matches!(bytes[i], b'(' | b'\'' | b'"') { + return None; + } + + let mut named_imports = Vec::new(); + if i < bytes.len() && bytes[i] == b'{' { + collect_named_import_specifiers(source, i, &mut named_imports)?; + i = skip_ws_comments(source, find_matching_brace(source, i)? + 1); + } else { + if i < bytes.len() && bytes[i] == b'*' { + return None; + } + let (_, next) = read_ident(source, i)?; + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b',' { + return None; + } + i = skip_ws_comments(source, i + 1); + if i >= bytes.len() || bytes[i] != b'{' { + return None; + } + collect_named_import_specifiers(source, i, &mut named_imports)?; + i = skip_ws_comments(source, find_matching_brace(source, i)? + 1); + } + + i = skip_ws_comments(source, parse_ident_name(source, i, "from")?); + let (specifier, next) = read_js_string(source, i)?; + Some((specifier, named_imports, find_statement_end(source, next))) +} + +fn collect_named_import_specifiers( + source: &str, + start: usize, + imports: &mut Vec, +) -> Option<()> { + let bytes = source.as_bytes(); + let end = find_matching_brace(source, start)?; + let mut i = start + 1; + while i < end { + i = skip_ws_comments(source, i); + if i >= end { + break; + } + let (imported, next, needs_alias) = if matches!(bytes[i], b'\'' | b'"') { + let (name, next) = read_js_string(source, i)?; + (name, next, true) + } else { + let (name, next) = read_ident(source, i)?; + (name, next, false) + }; + let mut local = imported.clone(); + i = skip_ws_comments(source, next); + if let Some(as_end) = parse_ident_name(source, i, "as") { + i = skip_ws_comments(source, as_end); + let (alias, next) = read_ident(source, i)?; + local = alias; + i = next; + } else if needs_alias { + return None; + } + imports.push(StaticNamedImport { imported, local }); + while i < end && bytes[i] != b',' { + i = next_char_boundary(source, i); + } + if i < end && bytes[i] == b',' { + i += 1; + } + } + Some(()) +} + +fn collect_named_import_bindings(source: &str, start: usize, bindings: &mut Vec) -> Option<()> { + let bytes = source.as_bytes(); + let end = find_matching_brace(source, start)?; + let mut i = start + 1; + while i < end { + i = skip_ws_comments(source, i); + if i >= end { + break; + } + let (mut name, next) = read_ident(source, i)?; + i = skip_ws_comments(source, next); + if let Some(as_end) = parse_ident_name(source, i, "as") { + i = skip_ws_comments(source, as_end); + let (alias, next) = read_ident(source, i)?; + name = alias; + i = next; + } + bindings.push(name); + while i < end && bytes[i] != b',' { + i = next_char_boundary(source, i); + } + if i < end && bytes[i] == b',' { + i += 1; + } + } + Some(()) +} + +fn parse_declaration_span(source: &str, pos: usize) -> Option<(Vec, usize)> { + if let Some((bindings, next)) = parse_variable_declaration_span(source, pos) { + return Some((bindings, next)); + } + if let Some((bindings, next)) = parse_function_declaration_span(source, pos) { + return Some((bindings, next)); + } + if let Some((bindings, next)) = parse_class_declaration_span(source, pos) { + return Some((bindings, next)); + } + None +} + +fn parse_variable_declaration_span(source: &str, pos: usize) -> Option<(Vec, usize)> { + let start = skip_ws_comments(source, parse_variable_declaration_keyword(source, pos)?); + let end = find_variable_declaration_end(source, start); + Some((collect_cjs_global_binding_names_in_variable_declaration(source, start, end), end)) +} + +fn parse_lexical_variable_declaration_span(source: &str, pos: usize) -> Option<(Vec, usize)> { + let start = skip_ws_comments(source, parse_lexical_variable_declaration_keyword(source, pos)?); + let end = find_variable_declaration_end(source, start); + Some((collect_cjs_global_binding_names_in_variable_declaration(source, start, end), end)) +} + +fn parse_lexical_variable_declaration_keyword(source: &str, pos: usize) -> Option { + parse_free_ident_name(source, pos, "const").or_else(|| parse_free_ident_name(source, pos, "let")) +} + +fn parse_variable_declaration_keyword(source: &str, pos: usize) -> Option { + parse_free_ident_name(source, pos, "const") + .or_else(|| parse_free_ident_name(source, pos, "let")) + .or_else(|| parse_free_ident_name(source, pos, "var")) +} + +fn find_variable_declaration_end(source: &str, pos: usize) -> usize { + let bytes = source.as_bytes(); + let mut i = pos; + let mut paren = 0usize; + let mut brace = 0usize; + let mut bracket = 0usize; + while i < bytes.len() { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + i += 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + continue; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(bytes.len()); + continue; + } + b'/' if is_regex_literal_start(source, i) => { + i = skip_regex_literal(source, i); + continue; + } + b'(' => paren += 1, + b')' => paren = paren.saturating_sub(1), + b'{' => brace += 1, + b'}' => { + if paren == 0 && brace == 0 && bracket == 0 { + return i; + } + brace = brace.saturating_sub(1); + } + b'[' => bracket += 1, + b']' => bracket = bracket.saturating_sub(1), + b';' if paren == 0 && brace == 0 && bracket == 0 => return i + 1, + _ => {} + } + i = next_char_boundary(source, i); + } + i +} + +fn parse_function_declaration_span(source: &str, pos: usize) -> Option<(Vec, usize)> { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, parse_ident_name(source, pos, "function")?); + if i < bytes.len() && bytes[i] == b'*' { + i = skip_ws_comments(source, i + 1); + } + let mut bindings = Vec::new(); + if let Some((name, next)) = read_ident(source, i) { + bindings.push(name); + i = skip_ws_comments(source, next); + } + if i < bytes.len() && bytes[i] == b'(' { + let params_end = find_matching_paren(source, i)?; + i = skip_ws_comments(source, params_end + 1); + if i < bytes.len() && bytes[i] == b'{' { + return Some((bindings, find_matching_brace(source, i)? + 1)); + } + } + Some((bindings, i)) +} + +fn parse_arrow_function_span(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let mut i; + if pos < bytes.len() && bytes[pos] == b'(' { + let params_end = find_matching_paren(source, pos)?; + i = skip_ws_comments(source, params_end + 1); + } else { + let (_, next) = read_ident(source, pos)?; + i = skip_ws_comments(source, next); + } + if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' { + return None; + } + i = skip_ws_comments(source, i + 2); + if i < bytes.len() && bytes[i] == b'{' { + Some(find_matching_brace(source, i)? + 1) + } else { + Some(find_statement_end(source, i)) + } +} + +fn parse_object_method_span(source: &str, pos: usize) -> Option { + if !matches!(previous_significant_byte_before_method(source, pos), Some(b'{') | Some(b',')) { + return None; + } + let bytes = source.as_bytes(); + let mut i = pos; + if let Some(async_end) = parse_ident_name(source, i, "async") { + let next = skip_ws_comments(source, async_end); + if next < bytes.len() && bytes[next] != b':' { + i = next; + } + } + if i < bytes.len() && bytes[i] == b'*' { + i = skip_ws_comments(source, i + 1); + } + if let Some(accessor_end) = parse_ident_name(source, i, "get").or_else(|| parse_ident_name(source, i, "set")) { + let next = skip_ws_comments(source, accessor_end); + if next < bytes.len() && bytes[next] != b':' { + i = next; + } + } + if i >= bytes.len() { + return None; + } + if matches!(bytes[i], b'\'' | b'"') { + let (_, next) = read_js_string(source, i)?; + i = next; + } else if bytes[i].is_ascii_digit() { + while i < bytes.len() && bytes[i].is_ascii_digit() { + i += 1; + } + } else { + let (_, next) = read_ident(source, i)?; + i = next; + } + i = skip_ws_comments(source, i); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + let params_end = find_matching_paren(source, i)?; + i = skip_ws_comments(source, params_end + 1); + if i < bytes.len() && bytes[i] == b'{' { + Some(find_matching_brace(source, i)? + 1) + } else { + None + } +} + +fn previous_significant_byte_before_method(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let mut end = pos; + loop { + while end > 0 && bytes[end - 1].is_ascii_whitespace() { + end -= 1; + } + if end >= 2 && bytes[end - 2] == b'*' && bytes[end - 1] == b'/' { + if let Some(start) = source[..end - 2].rfind("/*") { + end = start; + continue; + } + } + return if end == 0 { None } else { Some(bytes[end - 1]) }; + } +} + +fn parse_class_declaration_span(source: &str, pos: usize) -> Option<(Vec, usize)> { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, parse_ident_name(source, pos, "class")?); + let mut bindings = Vec::new(); + if let Some((name, next)) = read_ident(source, i) { + bindings.push(name); + i = skip_ws_comments(source, next); + } + while i < bytes.len() && bytes[i] != b'{' { + i = next_char_boundary(source, i); + } + if i < bytes.len() && bytes[i] == b'{' { + return Some((bindings, find_matching_brace(source, i)? + 1)); + } + Some((bindings, i)) +} + +fn collect_cjs_global_binding_names_in_variable_declaration(source: &str, start: usize, end: usize) -> Vec { + let bytes = source.as_bytes(); + let mut names = Vec::new(); + let mut i = start; + let mut in_binding = true; + let mut paren = 0usize; + let mut brace = 0usize; + let mut bracket = 0usize; + while i < end && i < bytes.len() { + if in_binding + && bytes[i] == b'[' + && let Some(close) = find_matching_bracket(source, i) + && close < end + && bytes.get(skip_ws_comments(source, close + 1)) == Some(&b':') + { + i = close + 1; + continue; + } + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + i += 2; + while i < end && i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + continue; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < end && i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(end).min(bytes.len()); + continue; + } + b'/' if is_regex_literal_start(source, i) => { + i = skip_regex_literal(source, i); + continue; + } + b'(' => paren += 1, + b')' => paren = paren.saturating_sub(1), + b'{' => brace += 1, + b'}' => brace = brace.saturating_sub(1), + b'[' => bracket += 1, + b']' => bracket = bracket.saturating_sub(1), + b'=' if paren == 0 && brace == 0 && bracket == 0 => in_binding = false, + b',' if paren == 0 && brace == 0 && bracket == 0 => in_binding = true, + _ => {} + } + + if in_binding { + for name in CJS_GLOBAL_NAMES { + if let Some(name_end) = parse_ident_name(source, i, name) + && cjs_global_identifier_is_binding_name(source, i, name_end) + && !names.iter().any(|existing| existing == name) + { + names.push(name.to_string()); + break; + } + } + } + i = next_char_boundary(source, i); + } + names +} + +fn cjs_global_identifier_is_binding_name(source: &str, pos: usize, name_end: usize) -> bool { + if object_pattern_property_key_without_binding(source, name_end) { + return false; + } + let bytes = source.as_bytes(); + let next = skip_ws_comments(source, name_end); + if bytes.get(next) == Some(&b'(') { + return false; + } + if previous_significant_byte(source, pos) == Some(b'=') { + return false; + } + if previous_significant_byte(source, pos) == Some(b'[') + && bytes.get(next) == Some(&b']') + && bytes.get(skip_ws_comments(source, next + 1)) == Some(&b':') + { + return false; + } + true +} + +fn find_matching_bracket(source: &str, start: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = start; + let mut depth = 0usize; + while i < bytes.len() { + match bytes[i] { + b'\'' | b'"' | b'`' => i = skip_string_or_template(source, i), + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + i += 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(bytes.len()); + } + b'/' if is_regex_literal_start(source, i) => { + i = skip_regex_literal(source, i); + } + b'[' => { + depth += 1; + i += 1; + } + b']' => { + depth = depth.saturating_sub(1); + if depth == 0 { + return Some(i); + } + i += 1; + } + _ => i = next_char_boundary(source, i), + } + } + None +} + +fn object_pattern_property_key_without_binding(source: &str, pos: usize) -> bool { + let i = skip_ws_comments(source, pos); + i < source.len() && source.as_bytes()[i] == b':' +} + +fn data_url_simple_identifier_error_module_source(source: &str) -> Option { + let ident = source.trim().strip_suffix(';').unwrap_or(source.trim()).trim(); + if ident.is_empty() + || ["require", "exports", "module", "__filename", "__dirname"].contains(&ident) + || !is_ascii_js_identifier(ident) + { + return None; + } + let escaped = DataUrlLoader::js_string_escape(&format!("{ident} is not defined")); + Some(format!( + "await Promise.reject(new ReferenceError('{escaped}'));\n" + )) +} + +fn has_cjs_wrapper_lexical_redeclaration(source: &str) -> bool { + let mut found = false; + let mut brace_depth = 0usize; + let _ = scan_code_positions(source, true, |i, byte| { + match byte { + b'{' => { + brace_depth += 1; + return ControlFlow::Continue(None); + } + b'}' => { + brace_depth = brace_depth.saturating_sub(1); + return ControlFlow::Continue(None); + } + _ => {} + } + + if brace_depth == 0 { + if let Some((bindings, _)) = parse_lexical_variable_declaration_span(source, i) { + for binding in bindings { + if binding == "require" { + let keyword_end = parse_lexical_variable_declaration_keyword(source, i).unwrap_or(i); + let next = skip_ws_comments(source, keyword_end); + if is_create_require_import_meta_url_declaration(source, next) { + continue; + } + } + if CJS_GLOBAL_NAMES.contains(&binding.as_str()) { + found = true; + return ControlFlow::Break(()); + } + } + } else if let Some((bindings, _)) = parse_class_declaration_span(source, i) { + if bindings + .iter() + .any(|binding| CJS_GLOBAL_NAMES.contains(&binding.as_str())) + { + found = true; + return ControlFlow::Break(()); + } + } + } + ControlFlow::Continue(None) + }); + found +} + +fn is_create_require_import_meta_url_declaration(source: &str, require_pos: usize) -> bool { + let mut next = skip_ws_comments(source, require_pos + "require".len()); + if source.as_bytes().get(next) != Some(&b'=') { + return false; + } + next = skip_ws_comments(source, next + 1); + let Some(create_require_end) = parse_ident_name(source, next, "createRequire") else { + return false; + }; + next = skip_ws_comments(source, create_require_end); + if source.as_bytes().get(next) != Some(&b'(') { + return false; + } + next = skip_ws_comments(source, next + 1); + parse_import_meta_url(source, next).is_some() +} + +fn parse_import_meta_url(source: &str, pos: usize) -> Option { + let i = parse_import_meta(source, pos)?; + parse_dot_member_name(source, i, "url") +} + +fn parse_import_meta(source: &str, pos: usize) -> Option { + let i = parse_ident_name(source, pos, "import")?; + if source.as_bytes().get(skip_ws_comments(source, i)) == Some(&b'(') { + return None; + } + parse_dot_member_name(source, i, "meta") +} + +fn is_ascii_js_identifier(value: &str) -> bool { + let bytes = value.as_bytes(); + if bytes.is_empty() || !(bytes[0] == b'_' || bytes[0] == b'$' || bytes[0].is_ascii_alphabetic()) { + return false; + } + bytes[1..] + .iter() + .all(|byte| *byte == b'_' || *byte == b'$' || byte.is_ascii_alphanumeric()) +} + +/// Resolver that strips `file://` URL prefixes so that `import('file:///path/to/mod.mjs')` +/// resolves to the filesystem path `/path/to/mod.mjs`. +struct FileUrlResolver; + +impl FileUrlResolver { + /// Decode a `file://` URL into a filesystem path, handling percent-encoding. + fn file_url_to_path(url: &str) -> Option { + let (mut path, suffix) = Self::file_url_to_path_parts(url)?; + path.push_str(suffix); + Some(path) + } + + fn file_url_to_path_parts(url: &str) -> Option<(String, &str)> { + let (encoded_path, suffix) = Self::file_url_path_and_suffix(url)?; + let bytes = encoded_path.as_bytes(); + let mut decoded = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'%' + && i + 2 < bytes.len() + && let (Some(hi), Some(lo)) = + (Self::hex_val(bytes[i + 1]), Self::hex_val(bytes[i + 2])) + { + decoded.push(hi << 4 | lo); + i += 3; + continue; + } + decoded.push(bytes[i]); + i += 1; + } + Some((String::from_utf8(decoded).ok()?, suffix)) + } + + fn file_url_path_and_suffix(url: &str) -> Option<(&str, &str)> { + let encoded = url.strip_prefix("file://")?; + let end = encoded + .find(|ch| ch == '?' || ch == '#') + .unwrap_or(encoded.len()); + let encoded_path = &encoded[..end]; + let (host, path) = if encoded_path.starts_with('/') { + ("", encoded_path) + } else if let Some(slash) = encoded_path.find('/') { + (&encoded_path[..slash], &encoded_path[slash..]) + } else { + (encoded_path, "/") + }; + + if host.is_empty() || host.eq_ignore_ascii_case("localhost") { + Some((path, &encoded[end..])) + } else { + None + } + } + + fn has_invalid_file_url_host(url: &str) -> bool { + url.starts_with("file://") && Self::file_url_path_and_suffix(url).is_none() + } + + fn with_loader_realm_suffix(base: &str, suffix: &str) -> String { + append_loader_realm_param(suffix, loader_realm_param(base).as_deref()) + } + + fn is_same_directory_file_import(normalized: &str, base: &str) -> bool { + let base_path = if let Some(path) = Self::file_url_to_path(base) { + path + } else if base.starts_with('/') { + module_filesystem_path(base).to_string() + } else { + return false; + }; + let Some(base_parent) = std::path::Path::new(&base_path).parent() else { + return false; + }; + let Some(target_parent) = std::path::Path::new(normalized).parent() else { + return false; + }; + CjsEvalResolver::normalize_path(base_parent) == CjsEvalResolver::normalize_path(target_parent) + } + + fn hex_val(b: u8) -> Option { + match b { + b'0'..=b'9' => Some(b - b'0'), + b'A'..=b'F' => Some(b - b'A' + 10), + b'a'..=b'f' => Some(b - b'a' + 10), + _ => None, + } + } +} + +impl Resolver for FileUrlResolver { + fn resolve<'js>( + &mut self, + ctx: &Ctx<'js>, + base: &str, + name: &str, + ) -> rquickjs::Result { + if let Some(encoded) = name.strip_prefix("file://") { + let end = encoded + .find(|ch| ch == '?' || ch == '#') + .unwrap_or(encoded.len()); + if NodeFileResolver::has_encoded_path_separator(&encoded[..end]) { + return NodeFileResolver::throw_invalid_encoded_separator(ctx, base, name); + } + if Self::has_invalid_file_url_host(name) { + return NodeFileResolver::throw_invalid_file_url_host( + ctx, + format!("File URL host must be \"localhost\" or empty: {}", name), + ); + } + } + + if let Some((path, suffix)) = Self::file_url_to_path_parts(name) { + let normalized = CjsEvalResolver::normalize_path(std::path::Path::new(&path)); + let url = NodeFileResolver::module_url_for_file_specifier(name); + if std::path::Path::new(&normalized).is_dir() { + discard_import_type_rewrite_token(name); + return NodeFileResolver::throw_module_resolution_error( + ctx, + "ERR_UNSUPPORTED_DIR_IMPORT", + NodeFileResolver::directory_import_message( + &normalized, + base, + !Self::is_same_directory_file_import(&normalized, base), + ), + url, + ); + } + if !std::path::Path::new(&normalized).is_file() { + discard_import_type_rewrite_token(name); + return NodeFileResolver::throw_module_resolution_error( + ctx, + "ERR_MODULE_NOT_FOUND", + format!("Cannot find module '{}'", name), + url, + ); + } + let resolved = format!( + "{}{}", + normalized, + Self::with_loader_realm_suffix(base, suffix) + ); + transfer_import_type_rewrite_token(name, &resolved); + Ok(resolved) + } else { + Err(Error::new_resolving(base, name)) + } + } +} + +struct RegisteredLoaderResolver; + +impl Resolver for RegisteredLoaderResolver { + fn resolve<'js>( + &mut self, + ctx: &Ctx<'js>, + base: &str, + name: &str, + ) -> rquickjs::Result { + let globals = ctx.globals(); + let Ok(resolve_fn) = + globals.get::<_, Function>("__wasm_rquickjs_resolve_static_registered_loader") + else { + return Err(Error::new_resolving(base, name)); + }; + let base_url = + if base.starts_with("data:") || base.starts_with("file://") || base.starts_with("node:") + { + base.to_string() + } else { + path_to_file_url(base) + }; + let resolved: Option = resolve_fn.call((base_url, name.to_string()))?; + match resolved { + Some(resolved) if !resolved.is_empty() => Ok(resolved), + _ => Err(Error::new_resolving(base, name)), + } + } +} + +/// Resolver that handles bare specifier imports by walking up the directory tree +/// looking for `node_modules//` directories, reading their `package.json` +/// to find the entry point. +/// Resolver that guards against dynamic import from contexts without a module referrer. +/// +/// QuickJS currently reports `` for both direct and indirect eval, so we +/// conservatively enforce Node's missing-callback error for `node:` specifiers. +/// This is enough for Node's `Promise.resolve(...).then(eval)` realm test case +/// while preserving successful direct-eval imports in CommonJS modules. +struct RealmGuardResolver; + +impl Resolver for RealmGuardResolver { + fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { + if base != "" { + return Err(Error::new_resolving(base, name)); + } + + if !name.starts_with("node:") { + return Err(Error::new_resolving(base, name)); + } + + let globals = ctx.globals(); + let current_module: Value = globals + .get("__wasm_rquickjs_current_module") + .unwrap_or_else(|_| Value::new_undefined(ctx.clone())); + + if !current_module.is_undefined() && !current_module.is_null() { + return Err(Error::new_resolving(base, name)); + } + + let eval_script: Value = globals + .get("__wasm_rquickjs_current_eval_script_name") + .unwrap_or_else(|_| Value::new_undefined(ctx.clone())); + if !eval_script.is_undefined() && !eval_script.is_null() { + return Err(Error::new_resolving(base, name)); + } + + let type_error_ctor: Function = globals.get("TypeError")?; + let error_obj: Object = + type_error_ctor.call(("A dynamic import callback was not specified.",))?; + error_obj.set("code", "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING")?; + Err(ctx.throw(error_obj.into_value())) + } +} + +/// Resolver that intercepts module resolution for mocked modules. +/// Checks `globalThis.__wasm_rquickjs_module_mocks` registry via JS helpers. +struct MockModuleResolver; + +impl Resolver for MockModuleResolver { + fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { + let globals = ctx.globals(); + + let canonical_key_fn: Function = globals + .get::<_, Function>("__wasm_rquickjs_mock_canonical_key") + .map_err(|_| Error::new_resolving(base, name))?; + + let key: Value = canonical_key_fn + .call((name, base)) + .map_err(|_| Error::new_resolving(base, name))?; + + if key.is_null() || key.is_undefined() { + return Err(Error::new_resolving(base, name)); + } + + let key_str: String = key + .get::() + .map_err(|_| Error::new_resolving(base, name))?; + + let registry: Object = globals + .get::<_, Object>("__wasm_rquickjs_module_mocks") + .map_err(|_| Error::new_resolving(base, name))?; + + let entry: Value = registry + .get::<_, Value>(&key_str as &str) + .map_err(|_| Error::new_resolving(base, name))?; + + if entry.is_undefined() || entry.is_null() { + return Err(Error::new_resolving(base, name)); + } + + let entry_obj: Object = entry + .into_object() + .ok_or_else(|| Error::new_resolving(base, name))?; + + let mock_id: i64 = entry_obj + .get::<_, i64>("id") + .map_err(|_| Error::new_resolving(base, name))?; + + let cache: bool = entry_obj.get::<_, bool>("cache").unwrap_or(false); + + if cache { + Ok(format!("__wasm_rquickjs_mock__:{}", mock_id)) + } else { + let seq_key = "__wasm_rquickjs_mock_seq"; + let seq: i64 = globals.get::<_, i64>(seq_key).unwrap_or(0); + let next_seq = seq + 1; + let _ = globals.set(seq_key, next_seq); + Ok(format!("__wasm_rquickjs_mock__:{}:{}", mock_id, next_seq)) + } + } +} + +/// Loader that handles synthetic mock module IDs produced by MockModuleResolver. +/// Generates ESM source from the JS-side mock registry. +struct MockModuleLoader; + +impl Loader for MockModuleLoader { + fn load<'js>( + &mut self, + ctx: &Ctx<'js>, + path: &str, + ) -> rquickjs::Result> { + if !path.starts_with("__wasm_rquickjs_mock__:") { + return Err(Error::new_loading(path)); + } + + let rest = &path["__wasm_rquickjs_mock__:".len()..]; + let mock_id_str = rest.split(':').next().unwrap_or(rest); + let mock_id: i64 = mock_id_str.parse().map_err(|_| Error::new_loading(path))?; + + let globals = ctx.globals(); + let gen_fn: Function = globals + .get::<_, Function>("__wasm_rquickjs_get_mock_module_source") + .map_err(|_| Error::new_loading(path))?; + + let source: String = gen_fn + .call::<_, String>((mock_id,)) + .map_err(|_| Error::new_loading(path))?; + + Module::declare(ctx.clone(), path, source.as_bytes().to_vec()) + } +} + +/// Resolver that handles relative path imports from eval'd CJS code. +/// When base is `` (from eval) and there's a CJS module context, +/// resolves relative paths against the module's directory. +struct CjsEvalResolver; + +impl CjsEvalResolver { + fn normalize_path(path: &std::path::Path) -> String { + use std::path::Component; + let mut parts: Vec = Vec::new(); + let is_absolute = path.has_root(); + + for component in path.components() { + match component { + Component::RootDir | Component::Prefix(_) => {} + Component::CurDir => {} + Component::ParentDir => { + parts.pop(); + } + Component::Normal(part) => { + parts.push(part.to_string_lossy().into_owned()); + } + } + } + + if is_absolute { + format!("/{}", parts.join("/")) + } else { + parts.join("/") + } + } +} + +impl Resolver for CjsEvalResolver { + fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { + if base != "" { + return Err(Error::new_resolving(base, name)); + } + + if !name.starts_with("./") && !name.starts_with("../") { + return Err(Error::new_resolving(base, name)); + } + + let globals = ctx.globals(); + let import_dir: Value = globals + .get("__wasm_rquickjs_cjs_import_dir") + .unwrap_or_else(|_| Value::new_undefined(ctx.clone())); + + if import_dir.is_undefined() || import_dir.is_null() { + return Err(Error::new_resolving(base, name)); + } + + let dir_str: String = import_dir + .get::() + .map_err(|_| Error::new_resolving(base, name))?; + + let module_dir = std::path::Path::new(&dir_str); + let resolved = module_dir.join(name); + let normalized = Self::normalize_path(&resolved); + + let candidates = [ + normalized.clone(), + format!("{}.js", normalized), + format!("{}.mjs", normalized), + ]; + + for candidate in &candidates { + if std::path::Path::new(candidate).is_file() { + return Ok(candidate.clone()); + } + } + + Err(Error::new_resolving(base, name)) + } +} + +/// Resolver for filesystem-backed ES modules. +/// +/// QuickJS gives dynamic imports from CommonJS `eval()` a synthetic `` +/// base (handled by `CjsEvalResolver` above), but normal ESM resolution still +/// needs Node-style filesystem handling for absolute paths and paths relative +/// to the referrer module. `rquickjs::FileResolver` is kept as a fallback, but +/// it does not reliably accept already-absolute guest paths in this WASI setup. +struct NodeFileResolver; + +impl NodeFileResolver { + fn decode_module_path<'js, 'path>( + ctx: &Ctx<'js>, + base: &str, + name: &str, + path: &'path str, + ) -> rquickjs::Result> { + if path.as_bytes().contains(&b'%') { + if Self::has_encoded_path_separator(path) { + return Self::throw_invalid_encoded_separator(ctx, base, name); + } + percent_decode(path) + .map(Cow::Owned) + .ok_or_else(|| Error::new_resolving(base, name)) + } else { + Ok(Cow::Borrowed(path)) + } + } + + fn has_encoded_path_separator(path: &str) -> bool { + let bytes = path.as_bytes(); + let mut i = 0; + while i + 2 < bytes.len() { + if bytes[i] == b'%' && bytes[i + 1] == b'2' && matches!(bytes[i + 2], b'f' | b'F') { + return true; + } + if bytes[i] == b'%' && bytes[i + 1] == b'5' && matches!(bytes[i + 2], b'c' | b'C') { + return true; + } + i += 1; + } + false + } + + fn throw_invalid_encoded_separator<'js, T>( + ctx: &Ctx<'js>, + base: &str, + name: &str, + ) -> rquickjs::Result { + let msg = format!( + "Invalid module \"{}\" must not include encoded \"/\" or \"\\\" characters imported from {}", + name, base + ); + let type_error_ctor: Function = ctx.globals().get("TypeError")?; + let error_obj: Object = type_error_ctor.call((&msg,))?; + error_obj.set("code", "ERR_INVALID_MODULE_SPECIFIER")?; + Err(ctx.throw(error_obj.into_value())) + } + + fn throw_invalid_file_url_host<'js, T>( + ctx: &Ctx<'js>, + message: String, + ) -> rquickjs::Result { + let _ = Exception::throw_type(ctx, &message); + let error_value = ctx.catch(); + let Some(error_obj) = error_value.clone().into_object() else { + return Err(ctx.throw(error_value)); + }; + Self::define_error_property(&error_obj, "code", "ERR_INVALID_FILE_URL_HOST")?; + Err(ctx.throw(error_obj.into_value())) + } + + fn resolve_candidate(candidate: std::path::PathBuf, suffix: &str) -> Option { + let normalized = CjsEvalResolver::normalize_path(&candidate); + if std::path::Path::new(&normalized).is_file() { + return Some(format!("{normalized}{suffix}")); + } + + if std::path::Path::new(&normalized).extension().is_none() { + for ext in ["js", "mjs", "json"] { + let with_ext = format!("{}.{}", normalized, ext); + if std::path::Path::new(&with_ext).is_file() { + return Some(format!("{with_ext}{suffix}")); + } + } + } + + None + } + + fn module_url_for_encoded_path(path: &str, suffix: &str) -> String { + let path = normalize_encoded_module_path(path); + format!( + "{}{}", + path_with_preserved_escapes_to_file_url(&path), + serialize_url_preserving_escapes(suffix) + ) + } + + fn module_url_for_file_specifier(specifier: &str) -> String { + if !specifier.starts_with("file://") { + return serialize_url_preserving_escapes(specifier); + } + let Some((encoded_path, suffix)) = FileUrlResolver::file_url_path_and_suffix(specifier) + else { + return serialize_url_preserving_escapes(specifier); + }; + let encoded_path = normalize_encoded_module_path(encoded_path); + format!( + "{}{}", + path_with_preserved_escapes_to_file_url(&encoded_path), + serialize_url_preserving_escapes(suffix) + ) + } + + fn throw_module_resolution_error<'js, T>( + ctx: &Ctx<'js>, + code: &str, + message: String, + url: String, + ) -> rquickjs::Result { + let error_obj = Exception::from_message(ctx.clone(), &message)?.into_object(); + let error_proto = error_obj.get_prototype(); + let coded_proto = Object::new(ctx.clone())?; + coded_proto.set_prototype(error_proto.as_ref())?; + coded_proto.prop( + "name", + Property::from(format!("Error [{code}]")) + .writable() + .configurable(), + )?; + error_obj.set_prototype(Some(&coded_proto))?; + Self::define_error_property(&error_obj, "code", code)?; + Self::define_error_property(&error_obj, "url", &url)?; + Err(ctx.throw(error_obj.into_value())) + } + + fn directory_import_message(normalized_dir: &str, importer: &str, include_suggestion: bool) -> String { + let mut message = format!( + "Directory import '{}' is not supported resolving ES modules imported from {}", + normalized_dir, + Self::format_importer(importer) + ); + if include_suggestion { + let package_json_path = std::path::Path::new(normalized_dir).join("package.json"); + if let Ok(Some(package)) = NodeModulesResolver::read_package_json_optional(&package_json_path) + && let Some(main) = package.main.as_deref() + && let Some((suggestion, _)) = + NodeModulesResolver::resolve_package_legacy_main(std::path::Path::new(normalized_dir), main) + { + message.push_str(&format!("\nDid you mean to import \"{suggestion}\"?")); + } + } + message + } + + fn format_importer(importer: &str) -> String { + FileUrlResolver::file_url_to_path(importer).unwrap_or_else(|| importer.to_string()) + } + + fn define_error_property<'js>( + error_obj: &Object<'js>, + name: &str, + value: &str, + ) -> rquickjs::Result<()> { + error_obj.prop( + name, + Property::from(value) + .writable() + .enumerable() + .configurable(), + ) + } +} + +impl Resolver for NodeFileResolver { + fn resolve<'js>( + &mut self, + ctx: &Ctx<'js>, + base: &str, + name: &str, + ) -> rquickjs::Result { + if name.contains("://") || name.starts_with("node:") { + return Err(Error::new_resolving(base, name)); + } + + let (name_path, suffix) = split_module_path_suffix(name); + let (candidate, url, include_directory_suggestion) = if name_path.starts_with('/') { + let encoded_path = CjsEvalResolver::normalize_path(std::path::Path::new(name_path)); + let url = Self::module_url_for_encoded_path(&encoded_path, suffix); + let name_path = match Self::decode_module_path(ctx, base, name, name_path) { + Ok(path) => path, + Err(err) => { + discard_import_type_rewrite_token(name); + return Err(err); + } + }; + (std::path::PathBuf::from(name_path.as_ref()), url, true) + } else if name_path.starts_with("./") || name_path.starts_with("../") { + let base_path = if let Some(path) = FileUrlResolver::file_url_to_path(base) { + path + } else { + base.to_string() + }; + let base_path = module_filesystem_path(&base_path); + + if base_path == "" { + discard_import_type_rewrite_token(name); + return Err(Error::new_resolving(base, name)); + } + + let Some(base_dir) = std::path::Path::new(&base_path).parent() else { + discard_import_type_rewrite_token(name); + return Err(Error::new_resolving(base, name)); + }; + let encoded_candidate = base_dir.join(name_path); + let encoded_path = CjsEvalResolver::normalize_path(&encoded_candidate); + let url = Self::module_url_for_encoded_path(&encoded_path, suffix); + let name_path = match Self::decode_module_path(ctx, base, name, name_path) { + Ok(path) => path, + Err(err) => { + discard_import_type_rewrite_token(name); + return Err(err); + } + }; + (base_dir.join(name_path.as_ref()), url, false) + } else { + discard_import_type_rewrite_token(name); + return Err(Error::new_resolving(base, name)); + }; + + let normalized = CjsEvalResolver::normalize_path(&candidate); + if std::path::Path::new(&normalized).is_dir() { + discard_import_type_rewrite_token(name); + return Self::throw_module_resolution_error( + ctx, + "ERR_UNSUPPORTED_DIR_IMPORT", + Self::directory_import_message( + &normalized, + base, + include_directory_suggestion + && !FileUrlResolver::is_same_directory_file_import(&normalized, base), + ), + url, + ); + } + + let suffix = append_loader_realm_param(suffix, loader_realm_param(base).as_deref()); + if let Some(resolved) = Self::resolve_candidate(candidate, &suffix) { + transfer_import_type_rewrite_token(name, &resolved); + return Ok(resolved); + } + + discard_import_type_rewrite_token(name); + Self::throw_module_resolution_error( + ctx, + "ERR_MODULE_NOT_FOUND", + format!("Cannot find module '{}'", name), + url, + ) + } +} + +/// Resolver that provides Node.js-style error codes for failed module resolution. +/// This should be the LAST resolver in the chain, catching everything that +/// preceding resolvers couldn't handle. +struct NodeModuleErrorResolver; + +impl Resolver for NodeModuleErrorResolver { + fn resolve<'js>( + &mut self, + ctx: &Ctx<'js>, + _base: &str, + name: &str, + ) -> rquickjs::Result { + if name.starts_with("node:") { + let msg = format!("No such built-in module: {}", name); + return throw_native_coded_error(ctx, &msg, "ERR_UNKNOWN_BUILTIN_MODULE", true); + } + + if let Some(scheme_end) = name.find("://") { + let scheme = &name[..scheme_end]; + if scheme != "file" && scheme != "data" { + let msg = format!( + "Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol '{}:'", + scheme + ); + return throw_native_coded_error(ctx, &msg, "ERR_UNSUPPORTED_ESM_URL_SCHEME", false); + } + } + + let msg = format!("Cannot find module '{}'", name); + throw_native_coded_error(ctx, &msg, "ERR_MODULE_NOT_FOUND", false) + } +} + +enum NodePackageResolveError { + InvalidModuleSpecifier { specifier: String, base: String }, + InvalidPackagePatternMatch { specifier: String, message: String }, + PackagePathNotExported { + package_name: String, + subpath: String, + no_exports_main: bool, + }, + PackageImportNotDefined { specifier: String }, + InvalidPackageTarget { kind: &'static str, target: String }, + InvalidPackageConfig { + path: String, + reason: Option, + }, + UnsupportedDirectoryImport { request: String }, + ModuleNotFound { request: String }, +} + +enum PackageTargetResolution { + Resolved(String), + NoMatch, + Blocked, +} + +struct PackageTargetResolveContext<'a> { + package_dir: &'a std::path::Path, + allow_bare_target: bool, + kind: &'static str, + conditions: &'a [String], + pattern_substitution: Option<&'a str>, + warning_specifier: &'a str, + warning_pattern_key: Option<&'a str>, + warning_importer: Option<&'a str>, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +enum PackageTarget { + String(String), + Array(Vec), + Object(IndexMap), + Bool(bool), + Null, + Invalid(serde_json::Value), +} + +fn deserialize_optional_package_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let value = Option::::deserialize(deserializer)?; + Ok(value.and_then(|value| value.as_str().map(|value| value.to_string()))) +} + +fn deserialize_strict_package_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + match serde_json::Value::deserialize(deserializer)? { + serde_json::Value::String(value) => Ok(Some(value)), + value => Err(D::Error::custom(format!( + "invalid package field type {}, expected string", + value + ))), + } +} + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] +struct PackageJson { + #[serde(default, deserialize_with = "deserialize_strict_package_string")] + name: Option, + #[serde(deserialize_with = "deserialize_optional_package_string")] + main: Option, + exports: Option, + imports: Option, + #[serde(rename = "type")] + #[serde(default, deserialize_with = "deserialize_strict_package_string")] + package_type: Option, +} + +thread_local! { + static PACKAGE_JSON_CACHE: RefCell>> = RefCell::new(HashMap::new()); +} + +struct NodePackageWarning { + message: String, + code: &'static str, + dedupe_key: Option, +} + +pub(crate) fn node_package_deprecation_warning_seen(key: &str) -> bool { + get_js_state() + .node_package_deprecation_warnings + .borrow() + .contains(key) +} + +pub(crate) fn mark_node_package_deprecation_warning_seen(key: String) { + get_js_state() + .node_package_deprecation_warnings + .borrow_mut() + .insert(key); +} + +struct NodeModulesResolver; + +enum NodePackageResolveMode { + EsmImport, + CjsAnalysis, +} + +impl NodePackageResolveMode { + const ESM_CONDITIONS: [&'static str; 5] = ["golem", "node", "module-sync", "import", "default"]; + const CJS_ANALYSIS_CONDITIONS: [&'static str; 5] = + ["golem", "node", "require", "module-sync", "default"]; + + fn default_conditions(&self) -> &'static [&'static str] { + match self { + NodePackageResolveMode::EsmImport => &Self::ESM_CONDITIONS, + NodePackageResolveMode::CjsAnalysis => &Self::CJS_ANALYSIS_CONDITIONS, + } + } + + fn package_exports_importer<'a>(&self, base: &'a str) -> Option<&'a str> { + match self { + NodePackageResolveMode::EsmImport => Some(base), + NodePackageResolveMode::CjsAnalysis => None, + } + } + + fn probes_missing_package_root_file(&self) -> bool { + matches!(self, NodePackageResolveMode::CjsAnalysis) + } +} + +enum CjsAnalysisPackageFallbackStep { + RootFile, + PackageMain, + Subpath, + RootDirectory, +} + +enum CjsAnalysisDirectoryFallbackStep { + RootFile, + PackageMain, + RootDirectory, +} + +impl NodeModulesResolver { + fn try_resolve( + &self, + base: &str, + name: &str, + conditions: &[String], + warnings: &mut Vec, + ) -> Result, NodePackageResolveError> { + self.try_resolve_package(base, name, conditions, warnings, NodePackageResolveMode::EsmImport) + } + + fn try_resolve_for_cjs_analysis( + &self, + base: &str, + name: &str, + conditions: &[String], + ) -> Result, NodePackageResolveError> { + let mut ignored_warnings = Vec::new(); + self.try_resolve_package( + base, + name, + conditions, + &mut ignored_warnings, + NodePackageResolveMode::CjsAnalysis, + ) + } + + fn try_resolve_package( + &self, + base: &str, + name: &str, + conditions: &[String], + warnings: &mut Vec, + mode: NodePackageResolveMode, + ) -> Result, NodePackageResolveError> { + use std::path::Path; + + if name.starts_with('#') { + return self.try_resolve_package_import_with_conditions(base, name, conditions, warnings); + } + + if name.starts_with('.') || name.starts_with('/') || name.contains("://") { + return Ok(None); + } + + let Some((package_name, subpath)) = Self::split_package_name(name) else { + return Ok(None); + }; + Self::validate_package_name(base, name, package_name)?; + + let Some(base_dir) = Path::new(base).parent() else { + return Ok(None); + }; + if let Some(resolved) = Self::try_resolve_package_self( + base_dir, + base, + package_name, + subpath, + conditions, + warnings, + )? + { + return Ok(Some(resolved)); + } + + let mut dir = base_dir.to_path_buf(); + loop { + let package_path = dir.join("node_modules").join(package_name); + if package_path.is_dir() { + if let Some(resolved) = Self::try_resolve_package_directory( + base, + name, + package_name, + subpath, + &package_path, + conditions, + warnings, + &mode, + )? { + return Ok(Some(resolved)); + } + } + + if mode.probes_missing_package_root_file() + && subpath.is_empty() + && let Some(resolved) = Self::resolve_cjs_analysis_package_root_file(&package_path) + { + return Ok(Some(resolved)); + } + + if !dir.pop() { + break; + } + } + + Ok(None) + } + + fn try_resolve_package_directory( + base: &str, + specifier: &str, + package_name: &str, + subpath: &str, + package_path: &std::path::Path, + conditions: &[String], + warnings: &mut Vec, + mode: &NodePackageResolveMode, + ) -> Result, NodePackageResolveError> { + let pkg_path = package_path.join("package.json"); + let package = Self::read_package_json_optional(&pkg_path)?; + + if let Some(package) = package.as_ref() { + if let Some(exports_field) = package.exports.as_ref() { + Self::validate_package_exports_map(&pkg_path, exports_field)?; + return Self::resolve_package_exports( + package_name, + package_path, + exports_field, + subpath, + conditions, + warnings, + mode.package_exports_importer(base), + ) + .map(Some); + } + } + + match mode { + NodePackageResolveMode::EsmImport => { + Self::try_resolve_package_directory_esm( + base, + specifier, + subpath, + package_path, + package.as_deref(), + warnings, + ) + } + NodePackageResolveMode::CjsAnalysis => { + Ok(Self::try_resolve_package_directory_for_cjs_analysis( + subpath, + package_path, + package.as_deref(), + )) + } + } + } + + fn read_package_json_optional(pkg_path: &std::path::Path) -> Result>, NodePackageResolveError> { + let cache_key = CjsEvalResolver::normalize_path(pkg_path); + if let Some(cached) = PACKAGE_JSON_CACHE.with_borrow(|cache| cache.get(&cache_key).cloned()) { + return Ok(Some(cached)); + } + match std::fs::read_to_string(pkg_path) { + Ok(pkg_content) => { + let package = Rc::new(serde_json::from_str::(&pkg_content).map_err(|_| { + NodePackageResolveError::InvalidPackageConfig { + path: pkg_path.to_string_lossy().into_owned(), + reason: None, + } + })?); + PACKAGE_JSON_CACHE.with_borrow_mut(|cache| { + cache.insert(cache_key, package.clone()); + }); + Ok(Some(package)) + } + Err(_) => Ok(None), + } + } + + fn try_resolve_package_directory_esm( + base: &str, + specifier: &str, + subpath: &str, + package_path: &std::path::Path, + package: Option<&PackageJson>, + warnings: &mut Vec, + ) -> Result, NodePackageResolveError> { + let package_type = package.and_then(|package| package.package_type.as_ref()); + if subpath.is_empty() + && let Some(package) = package + && let Some(main) = package.main.as_ref() + { + let is_module_package = package.package_type.as_deref() == Some("module"); + let resolved = Self::resolve_package_legacy_main(package_path, main); + if let Some((resolved, used_extension_lookup)) = resolved { + if is_module_package && used_extension_lookup { + warnings.push(NodePackageWarning { + message: format!( + "Package {}/ has a \"main\" field set to {:?}, excluding the full filename and extension to the resolved file at {:?}, imported from {}.\nAutomatic extension resolution of the \"main\" field is deprecated for ES modules.", + package_path.to_string_lossy().trim_end_matches('/'), + main, + std::path::Path::new(&resolved) + .strip_prefix(package_path) + .ok() + .map(|path| path.to_string_lossy().into_owned()) + .unwrap_or_else(|| resolved.clone()), + base + ), + code: "DEP0151", + dedupe_key: None, + }); + } + return Ok(Some(resolved)); + } + } + + if !subpath.is_empty() + && let Some(resolved) = Self::resolve_package_subpath(package_path, subpath, base, specifier)? + { + return Ok(Some(resolved)); + } + + if subpath.is_empty() { + let is_module_package = package_type.is_some_and(|package_type| package_type == "module"); + let fallbacks = [ + package_path.join("index.js"), + package_path.join("index.json"), + package_path.join("index.node"), + ]; + for fallback in &fallbacks { + if fallback.is_file() { + if is_module_package + && fallback.extension().and_then(|ext| ext.to_str()) == Some("js") + { + warnings.push(NodePackageWarning { + message: format!( + "No \"main\" or \"exports\" field defined in the package.json for {}/ resolving the main entry point \"index.js\", imported from {}.\nDefault \"index\" lookups for the main are deprecated for ES modules.", + package_path.to_string_lossy().trim_end_matches('/'), + base + ), + code: "DEP0151", + dedupe_key: None, + }); + } + return Ok(Some(fallback.to_string_lossy().into_owned())); + } + } + } + + Ok(None) + } + + fn try_resolve_package_directory_for_cjs_analysis( + subpath: &str, + package_path: &std::path::Path, + package: Option<&PackageJson>, + ) -> Option { + let steps = [ + CjsAnalysisPackageFallbackStep::RootFile, + CjsAnalysisPackageFallbackStep::PackageMain, + CjsAnalysisPackageFallbackStep::Subpath, + CjsAnalysisPackageFallbackStep::RootDirectory, + ]; + for step in steps { + if let Some(resolved) = Self::resolve_cjs_analysis_package_fallback_step( + step, + subpath, + package_path, + package, + ) { + return Some(resolved); + } + } + + None + } + + fn resolve_cjs_analysis_package_fallback_step( + step: CjsAnalysisPackageFallbackStep, + subpath: &str, + package_path: &std::path::Path, + package: Option<&PackageJson>, + ) -> Option { + match step { + CjsAnalysisPackageFallbackStep::RootFile => { + subpath.is_empty().then(|| { + Self::resolve_cjs_analysis_directory_fallback_step( + CjsAnalysisDirectoryFallbackStep::RootFile, + package_path, + package, + ) + }).flatten() + } + CjsAnalysisPackageFallbackStep::PackageMain => { + if !subpath.is_empty() { + return None; + } + Self::resolve_cjs_analysis_directory_fallback_step( + CjsAnalysisDirectoryFallbackStep::PackageMain, + package_path, + package, + ) + } + CjsAnalysisPackageFallbackStep::Subpath => { + if subpath.is_empty() { + None + } else { + Self::resolve_cjs_analysis_file_or_directory(package_path, subpath) + } + } + CjsAnalysisPackageFallbackStep::RootDirectory => { + subpath.is_empty().then(|| { + Self::resolve_cjs_analysis_directory_fallback_step( + CjsAnalysisDirectoryFallbackStep::RootDirectory, + package_path, + package, + ) + }).flatten() + } + } + } + + fn try_resolve_package_import_with_conditions( + &self, + base: &str, + name: &str, + conditions: &[String], + warnings: &mut Vec, + ) -> Result, NodePackageResolveError> { + use std::path::Path; + + let Some(parent) = Path::new(base).parent() else { + return Ok(None); + }; + let mut dir = parent.to_path_buf(); + loop { + if dir.file_name().is_some_and(|name| name == "node_modules") { + return Err(NodePackageResolveError::PackageImportNotDefined { + specifier: name.to_string(), + }); + } + + let pkg_path = dir.join("package.json"); + if let Some(package) = Self::read_package_json_optional(&pkg_path)? { + let Some(imports) = package.imports.as_ref() else { + return Err(NodePackageResolveError::PackageImportNotDefined { + specifier: name.to_string(), + }); + }; + Self::validate_package_import_specifier(name)?; + return Self::resolve_package_import(&dir, imports, name, conditions, warnings, Some(base)).map(Some); + } + + if !dir.pop() { + break; + } + } + + Err(NodePackageResolveError::PackageImportNotDefined { + specifier: name.to_string(), + }) + } + + fn try_resolve_package_self( + base_dir: &std::path::Path, + importer: &str, + package_name: &str, + subpath: &str, + conditions: &[String], + warnings: &mut Vec, + ) -> Result, NodePackageResolveError> { + let mut dir = base_dir.to_path_buf(); + loop { + if dir.file_name().is_some_and(|name| name == "node_modules") { + return Ok(None); + } + + let pkg_path = dir.join("package.json"); + if let Some(package) = Self::read_package_json_optional(&pkg_path)? { + if package.name.as_deref() == Some(package_name) + && let Some(exports_field) = package.exports.as_ref() + { + Self::validate_package_exports_map(&pkg_path, exports_field)?; + return Self::resolve_package_exports( + package_name, + &dir, + exports_field, + subpath, + conditions, + warnings, + Some(importer), + ) + .map(Some); + } + return Ok(None); + } + + if !dir.pop() { + break; + } + } + + Ok(None) + } + + fn split_package_name(name: &str) -> Option<(&str, &str)> { + if name.starts_with('@') { + let Some(first) = name.find('/') else { + return Some((name, "")); + }; + let rest = &name[first + 1..]; + if rest.is_empty() { + return Some((name, "")); + } + if let Some(second_rel) = rest.find('/') { + let second = first + 1 + second_rel; + Some((&name[..second], &name[second + 1..])) + } else { + Some((name, "")) + } + } else if let Some(idx) = name.find('/') { + Some((&name[..idx], &name[idx + 1..])) + } else { + Some((name, "")) + } + } + + fn validate_package_name( + base: &str, + specifier: &str, + package_name: &str, + ) -> Result<(), NodePackageResolveError> { + let invalid_scoped_name = package_name.starts_with('@') && !package_name.contains('/'); + if invalid_scoped_name || package_name.contains('%') || package_name.contains('\\') { + return Err(NodePackageResolveError::InvalidModuleSpecifier { + specifier: specifier.to_string(), + base: base.to_string(), + }); + } + Ok(()) + } + + fn validate_package_import_specifier(specifier: &str) -> Result<(), NodePackageResolveError> { + if specifier == "#" || specifier.starts_with("#/") { + return Err(NodePackageResolveError::InvalidPackagePatternMatch { + specifier: specifier.to_string(), + message: "is not a valid internal imports specifier name".to_string(), + }); + } + Ok(()) + } + + fn resolve_package_subpath( + package_dir: &std::path::Path, + subpath: &str, + base: &str, + specifier: &str, + ) -> Result, NodePackageResolveError> { + if Self::has_encoded_slash_or_backslash(subpath) { + return Err(NodePackageResolveError::InvalidModuleSpecifier { + specifier: specifier.to_string(), + base: base.to_string(), + }); + } + let decoded_subpath = percent_decode(subpath).unwrap_or_else(|| subpath.to_string()); + let target_path = package_dir.join(decoded_subpath); + if target_path.is_file() { + return Ok(Some(target_path.to_string_lossy().into_owned())); + } + if target_path.is_dir() { + return Err(NodePackageResolveError::UnsupportedDirectoryImport { + request: target_path.to_string_lossy().into_owned(), + }); + } + Ok(None) + } + + fn resolve_package_legacy_main( + package_dir: &std::path::Path, + target: &str, + ) -> Option<(String, bool)> { + let target_path = package_dir.join(target.strip_prefix("./").unwrap_or(target)); + if target_path.is_file() { + return Some((target_path.to_string_lossy().into_owned(), false)); + } + if target_path.extension().is_none() { + let js_target = Self::with_appended_extension(&target_path, ".js"); + if js_target.is_file() { + return Some((js_target.to_string_lossy().into_owned(), true)); + } + let json_target = Self::with_appended_extension(&target_path, ".json"); + if json_target.is_file() { + return Some((json_target.to_string_lossy().into_owned(), false)); + } + let node_target = Self::with_appended_extension(&target_path, ".node"); + if node_target.is_file() { + return Some((node_target.to_string_lossy().into_owned(), false)); + } + } + let index_js = target_path.join("index.js"); + if index_js.is_file() { + return Some((index_js.to_string_lossy().into_owned(), true)); + } + let index_json = target_path.join("index.json"); + if index_json.is_file() { + return Some((index_json.to_string_lossy().into_owned(), false)); + } + let index_node = target_path.join("index.node"); + if index_node.is_file() { + return Some((index_node.to_string_lossy().into_owned(), false)); + } + None + } + + fn first_existing_normalized(candidates: [std::path::PathBuf; N]) -> Option { + for candidate in candidates { + let normalized = CjsEvalResolver::normalize_path(&candidate); + if std::path::Path::new(&normalized).is_file() { + return Some(normalized); + } + } + + None + } + + fn with_appended_extension(path: &std::path::Path, extension: &str) -> std::path::PathBuf { + std::path::PathBuf::from(format!("{}{}", path.to_string_lossy(), extension)) + } + + fn resolve_cjs_analysis_file_or_directory(package_dir: &std::path::Path, target: &str) -> Option { + let target_path = package_dir.join(target.strip_prefix("./").unwrap_or(target)); + Self::first_existing_normalized([ + target_path.to_path_buf(), + Self::with_appended_extension(&target_path, ".js"), + Self::with_appended_extension(&target_path, ".json"), + Self::with_appended_extension(&target_path, ".node"), + target_path.join("index.js"), + target_path.join("index.json"), + target_path.join("index.node"), + ]) + } + + fn resolve_cjs_analysis_package_root_file(package_dir: &std::path::Path) -> Option { + Self::first_existing_normalized([ + package_dir.to_path_buf(), + Self::with_appended_extension(package_dir, ".js"), + Self::with_appended_extension(package_dir, ".json"), + Self::with_appended_extension(package_dir, ".node"), + ]) + } + + fn resolve_cjs_analysis_package_root_directory(package_dir: &std::path::Path) -> Option { + Self::first_existing_normalized([ + package_dir.join("index.js"), + package_dir.join("index.json"), + package_dir.join("index.node"), + ]) + } + + fn resolve_cjs_analysis_directory_fallback_step( + step: CjsAnalysisDirectoryFallbackStep, + directory_path: &std::path::Path, + package: Option<&PackageJson>, + ) -> Option { + match step { + CjsAnalysisDirectoryFallbackStep::RootFile => { + Self::resolve_cjs_analysis_package_root_file(directory_path) + } + CjsAnalysisDirectoryFallbackStep::PackageMain => { + let main = package.and_then(|package| package.main.as_ref())?; + Self::resolve_cjs_analysis_file_or_directory(directory_path, main) + } + CjsAnalysisDirectoryFallbackStep::RootDirectory => { + Self::resolve_cjs_analysis_package_root_directory(directory_path) + } + } + } + + fn resolve_cjs_analysis_relative(target_path: &std::path::Path) -> Option { + if let Some(resolved) = Self::resolve_cjs_analysis_directory_fallback_step( + CjsAnalysisDirectoryFallbackStep::RootFile, + target_path, + None, + ) { + return Some(resolved); + } + + if !target_path.is_dir() { + return None; + } + + let pkg_path = target_path.join("package.json"); + let package = match Self::read_package_json_optional(&pkg_path) { + Ok(package) => package, + Err(_) => return None, + }; + let steps = [ + CjsAnalysisDirectoryFallbackStep::PackageMain, + CjsAnalysisDirectoryFallbackStep::RootDirectory, + ]; + for step in steps { + if let Some(resolved) = + Self::resolve_cjs_analysis_directory_fallback_step(step, target_path, package.as_deref()) + { + return Some(resolved); + } + } + + None + } + + fn resolve_package_exports( + package_name: &str, + package_dir: &std::path::Path, + exports: &PackageTarget, + subpath: &str, + conditions: &[String], + warnings: &mut Vec, + importer: Option<&str>, + ) -> Result { + let key = if subpath.is_empty() { + ".".to_string() + } else { + format!("./{}", subpath) + }; + + if matches!(exports, PackageTarget::String(_) | PackageTarget::Array(_)) + || Self::is_conditions_object(exports) + { + if key != "." { + return Err(NodePackageResolveError::PackagePathNotExported { + package_name: package_name.to_string(), + subpath: subpath.to_string(), + no_exports_main: false, + }); + } + return Self::resolve_package_target_with_context( + package_dir, + exports, + false, + "exports", + conditions, + None, + &key, + None, + importer, + warnings, + ) + .and_then(|resolution| { + Self::target_resolution_to_export_result( + resolution, + package_name, + subpath, + key == "." && Self::is_conditions_object(exports), + ) + }); + } + + if let PackageTarget::Object(map) = exports { + if let Some((target, pattern_substitution, pattern_key)) = + Self::find_package_map_target(map, &key, "is not a valid match in pattern")? + { + return Self::resolve_package_target_with_context( + package_dir, + target, + false, + "exports", + conditions, + pattern_substitution.as_deref(), + &key, + pattern_key, + importer, + warnings, + ) + .and_then(|resolution| { + Self::target_resolution_to_export_result(resolution, package_name, subpath, false) + }); + } + } + + Err(NodePackageResolveError::PackagePathNotExported { + package_name: package_name.to_string(), + subpath: subpath.to_string(), + no_exports_main: false, + }) + } + + fn resolve_package_import( + package_dir: &std::path::Path, + imports: &PackageTarget, + specifier: &str, + conditions: &[String], + warnings: &mut Vec, + importer: Option<&str>, + ) -> Result { + if let PackageTarget::Object(map) = imports + { + if let Some((target, pattern_substitution, pattern_key)) = + Self::find_package_map_target( + map, + specifier, + "request is not a valid match in pattern", + )? + { + return Self::resolve_package_target_with_context( + package_dir, + target, + true, + "imports", + conditions, + pattern_substitution.as_deref(), + specifier, + pattern_key, + importer, + warnings, + ) + .and_then( + |resolution| Self::target_resolution_to_import_result(resolution, specifier), + ); + } + } + Err(NodePackageResolveError::PackageImportNotDefined { + specifier: specifier.to_string(), + }) + } + + fn is_conditions_object(value: &PackageTarget) -> bool { + matches!( + value, + PackageTarget::Object(map) if !map.is_empty() && !map.iter().any(|(key, _)| key.starts_with('.')) + ) + } + + fn validate_package_exports_map( + pkg_path: &std::path::Path, + exports: &PackageTarget, + ) -> Result<(), NodePackageResolveError> { + let PackageTarget::Object(map) = exports else { + return Ok(()); + }; + if map.keys().any(|key| { + !key.is_empty() + && key + .chars() + .enumerate() + .all(|(idx, ch)| ch.is_ascii_digit() && (idx > 0 || ch != '0' || key.len() == 1)) + }) { + return Err(NodePackageResolveError::InvalidPackageConfig { + path: pkg_path.to_string_lossy().into_owned(), + reason: Some("\"exports\" cannot contain numeric property keys".to_string()), + }); + } + let has_subpath_key = map.keys().any(|key| key.starts_with('.')); + let has_condition_key = map.keys().any(|key| !key.starts_with('.')); + if has_subpath_key && has_condition_key { + return Err(NodePackageResolveError::InvalidPackageConfig { + path: pkg_path.to_string_lossy().into_owned(), + reason: Some( + "\"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." + .to_string(), + ), + }); + } + Ok(()) + } + + fn decode_package_target_path(target: &str) -> String { + percent_decode(target).unwrap_or_else(|| target.to_string()) + } + + fn resolve_package_target_with_context( + package_dir: &std::path::Path, + target: &PackageTarget, + allow_bare_target: bool, + kind: &'static str, + conditions: &[String], + pattern_substitution: Option<&str>, + warning_specifier: &str, + warning_pattern_key: Option<&str>, + warning_importer: Option<&str>, + warnings: &mut Vec, + ) -> Result { + let ctx = PackageTargetResolveContext { + package_dir, + allow_bare_target, + kind, + conditions, + pattern_substitution, + warning_specifier, + warning_pattern_key, + warning_importer, + }; + Self::add_invalid_package_target_context( + Self::resolve_package_target_value(target, &ctx, warnings), + warning_specifier, + ) + } + + fn resolve_package_target_value( + target: &PackageTarget, + ctx: &PackageTargetResolveContext<'_>, + warnings: &mut Vec, + ) -> Result { + match target { + PackageTarget::Null => { + return Ok(PackageTargetResolution::Blocked); + } + PackageTarget::Bool(false) => { + return Err(NodePackageResolveError::InvalidPackageTarget { + kind: ctx.kind, + target: "false".to_string(), + }); + } + PackageTarget::Bool(true) => { + return Err(NodePackageResolveError::InvalidPackageTarget { + kind: ctx.kind, + target: "true".to_string(), + }); + } + PackageTarget::Invalid(value) => { + return Err(NodePackageResolveError::InvalidPackageTarget { + kind: ctx.kind, + target: value.to_string(), + }); + } + PackageTarget::String(target_str) => { + let target_str = if let Some(pattern_substitution) = ctx.pattern_substitution { + target_str.replace('*', pattern_substitution) + } else { + target_str.clone() + }; + Self::push_package_deprecation_warning( + warnings, + ctx.package_dir, + ctx.kind, + ctx.warning_specifier, + &target_str, + ctx.pattern_substitution, + ctx.warning_pattern_key, + ctx.warning_importer, + ); + if ctx.allow_bare_target && Self::is_bare_package_specifier(&target_str) { + let base = ctx.package_dir.join("package.json"); + let base_str = base.to_string_lossy(); + let resolver = NodeModulesResolver; + if let Some(resolved) = + resolver.try_resolve(&base_str, &target_str, ctx.conditions, warnings)? + { + return Ok(PackageTargetResolution::Resolved(resolved)); + } + return Err(NodePackageResolveError::ModuleNotFound { + request: target_str, + }); + } + if Self::has_encoded_slash_or_backslash(&target_str) { + return Err(NodePackageResolveError::InvalidPackagePatternMatch { + specifier: target_str, + message: "must not include encoded \"/\" or \"\\\" characters".to_string(), + }); + } + if !target_str.starts_with("./") { + return Err(NodePackageResolveError::InvalidPackageTarget { + kind: ctx.kind, + target: target_str, + }); + } + let decoded_target = Self::decode_package_target_path(&target_str); + let Some(candidate) = + Self::resolve_valid_package_target_path(ctx.package_dir, &decoded_target) + else { + return Err(NodePackageResolveError::InvalidPackageTarget { + kind: ctx.kind, + target: target_str, + }); + }; + if candidate.is_file() { + return Ok(PackageTargetResolution::Resolved( + candidate.to_string_lossy().into_owned(), + )); + } + if candidate.is_dir() { + return Err(NodePackageResolveError::UnsupportedDirectoryImport { + request: candidate.to_string_lossy().into_owned(), + }); + } + return Err(NodePackageResolveError::ModuleNotFound { + request: candidate.to_string_lossy().into_owned(), + }); + } + PackageTarget::Array(array) => { + let mut last_fallback_error = None; + for item in array { + match Self::resolve_package_target_value(item, ctx, warnings) { + Ok(PackageTargetResolution::Resolved(path)) => { + return Ok(PackageTargetResolution::Resolved(path)); + } + Ok(PackageTargetResolution::Blocked) => continue, + Ok(PackageTargetResolution::NoMatch) => continue, + Err(err @ NodePackageResolveError::InvalidPackageTarget { .. }) => { + last_fallback_error = Some(err); + continue; + } + Err(err) => return Err(err), + } + } + if let Some(err) = last_fallback_error { + return Err(err); + } + return Ok(PackageTargetResolution::NoMatch); + } + PackageTarget::Object(map) => { + for (condition, value) in map { + if ctx + .conditions + .iter() + .any(|candidate| candidate == condition) + { + match Self::resolve_package_target_value(value, ctx, warnings)? { + PackageTargetResolution::NoMatch => continue, + resolution => return Ok(resolution), + } + } + } + Ok(PackageTargetResolution::NoMatch) + } + } + } + + fn package_pattern_key_match(pattern_key: &str, key: &str) -> Option { + let star = pattern_key.find('*')?; + let prefix = &pattern_key[..star]; + let suffix = &pattern_key[star + 1..]; + if !key.starts_with(prefix) || !key.ends_with(suffix) { + return None; + } + if key.len() <= prefix.len() + suffix.len() { + return None; + } + Some(key[prefix.len()..key.len() - suffix.len()].to_string()) + } + + fn has_encoded_slash_or_backslash(value: &str) -> bool { + let lower = value.to_ascii_lowercase(); + lower.contains("%2f") || lower.contains("%5c") + } + + fn is_invalid_package_pattern_substitution(substitution: &str) -> bool { + if Self::has_encoded_slash_or_backslash(substitution) { + return true; + } + substitution + .split('/') + .any(|segment| !segment.is_empty() && Self::is_invalid_package_target_segment(segment)) + } + + fn invalid_package_pattern_substitution_message(substitution: &str, fallback: &str) -> String { + if Self::has_encoded_slash_or_backslash(substitution) { + "must not include encoded \"/\" or \"\\\" characters".to_string() + } else { + fallback.to_string() + } + } + + fn add_invalid_package_target_context( + result: Result, + specifier: &str, + ) -> Result { + result.map_err(|err| match err { + NodePackageResolveError::InvalidPackageTarget { kind, target } => { + NodePackageResolveError::InvalidPackageTarget { + kind, + target: if target.contains(specifier) { + target + } else { + format!("{} for {}", target, specifier) + }, + } + } + other => other, + }) + } + + fn find_best_package_pattern<'a>( + map: &'a IndexMap, + key: &str, + ) -> Option<(&'a str, String)> { + let mut best: Option<(&str, String)> = None; + for pattern_key in map.keys() { + if !pattern_key.contains('*') { + continue; + } + let Some(substitution) = Self::package_pattern_key_match(pattern_key, key) else { + continue; + }; + if best + .as_ref() + .is_none_or(|(best_key, _)| Self::package_pattern_compare(pattern_key, best_key).is_lt()) + { + best = Some((pattern_key.as_str(), substitution)); + } + } + best + } + + fn find_package_map_target<'a>( + map: &'a IndexMap, + specifier: &str, + invalid_pattern_message: &str, + ) -> Result, Option<&'a str>)>, NodePackageResolveError> { + if let Some(target) = map.get(specifier) { + return Ok(Some((target, None, None))); + } + + let Some((pattern_key, pattern_substitution)) = Self::find_best_package_pattern(map, specifier) else { + return Ok(None); + }; + if Self::is_invalid_package_pattern_substitution(&pattern_substitution) { + return Err(NodePackageResolveError::InvalidPackagePatternMatch { + specifier: specifier.to_string(), + message: Self::invalid_package_pattern_substitution_message( + &pattern_substitution, + invalid_pattern_message, + ), + }); + } + Ok(map + .get(pattern_key) + .map(|target| (target, Some(pattern_substitution), Some(pattern_key)))) + } + + fn package_pattern_compare(a: &str, b: &str) -> std::cmp::Ordering { + let a_star = a.find('*').unwrap_or(a.len()); + let b_star = b.find('*').unwrap_or(b.len()); + match b_star.cmp(&a_star) { + std::cmp::Ordering::Equal => {} + ordering => return ordering, + } + let a_trailer = a.len().saturating_sub(a_star + 1); + let b_trailer = b.len().saturating_sub(b_star + 1); + match b_trailer.cmp(&a_trailer) { + std::cmp::Ordering::Equal => {} + ordering => return ordering, + } + match b.len().cmp(&a.len()) { + std::cmp::Ordering::Equal => a.cmp(b), + ordering => ordering, + } + } + + fn target_resolution_to_export_result( + resolution: PackageTargetResolution, + package_name: &str, + subpath: &str, + no_exports_main: bool, + ) -> Result { + match resolution { + PackageTargetResolution::Resolved(path) => Ok(path), + PackageTargetResolution::NoMatch | PackageTargetResolution::Blocked => { + Err(NodePackageResolveError::PackagePathNotExported { + package_name: package_name.to_string(), + subpath: subpath.to_string(), + no_exports_main, + }) + } + } + } + + fn target_resolution_to_import_result( + resolution: PackageTargetResolution, + specifier: &str, + ) -> Result { + match resolution { + PackageTargetResolution::Resolved(path) => Ok(path), + PackageTargetResolution::NoMatch | PackageTargetResolution::Blocked => { + Err(NodePackageResolveError::PackageImportNotDefined { + specifier: specifier.to_string(), + }) + } + } + } + + fn is_bare_package_specifier(target: &str) -> bool { + !target.is_empty() + && !target.starts_with('.') + && !target.starts_with('/') + && !target.starts_with('#') + && !target.contains(':') + } + + fn resolve_valid_package_target_path( + package_dir: &std::path::Path, + target: &str, + ) -> Option { + let mut relative_parts = Vec::<&str>::new(); + for part in target.strip_prefix("./")?.split('/') { + match part { + "" => {} + part if Self::is_invalid_package_target_segment(part) => return None, + part => relative_parts.push(part), + } + } + let mut candidate = package_dir.to_path_buf(); + for part in relative_parts { + candidate.push(part); + } + Some(candidate) + } + + fn is_invalid_package_target_segment(segment: &str) -> bool { + if matches!(segment, "." | ".." | "node_modules") { + return true; + } + let decoded = percent_decode(segment).unwrap_or_else(|| segment.to_string()); + matches!(decoded.to_ascii_lowercase().as_str(), "." | ".." | "node_modules") + } + + fn has_deprecated_double_slash(value: &str) -> bool { + value.contains("//") + } + + fn has_deprecated_leading_or_trailing_slash(value: Option<&str>) -> bool { + value.is_some_and(|value| value.starts_with('/') || value.ends_with('/')) + } + + fn package_warning_location( + package_dir: &std::path::Path, + kind: &str, + importer: Option<&str>, + ) -> String { + let package_json = package_dir.join("package.json"); + let mut location = format!( + " in the \"{}\" field module resolution of the package at {}", + kind, + package_json.to_string_lossy() + ); + if let Some(importer) = importer { + location.push_str(" imported from "); + location.push_str(importer); + } + location.push('.'); + location + } + + fn push_package_deprecation_warning( + warnings: &mut Vec, + package_dir: &std::path::Path, + kind: &str, + specifier: &str, + target: &str, + pattern_substitution: Option<&str>, + pattern_key: Option<&str>, + importer: Option<&str>, + ) { + if kind == "exports" + && pattern_substitution.is_some_and(|substitution| substitution.ends_with('/')) + { + let location = Self::package_warning_location(package_dir, kind, importer); + warnings.push(NodePackageWarning { + message: format!( + "Use of deprecated trailing slash pattern mapping {:?}{} Mapping specifiers ending in \"/\" is no longer supported.", + specifier, location + ), + code: "DEP0155", + dedupe_key: Some(format!( + "{}:{}", + package_dir.to_string_lossy(), + specifier + )), + }); + return; + } + if Self::has_deprecated_double_slash(target) { + let location = Self::package_warning_location(package_dir, kind, importer); + let matched_pattern = pattern_key + .map(|pattern_key| format!(" matched to {:?}", pattern_key)) + .unwrap_or_default(); + warnings.push(NodePackageWarning { + message: format!( + "Use of deprecated double slash resolving {:?} for module request {:?}{}{}", + target, specifier, matched_pattern, location + ), + code: "DEP0166", + dedupe_key: None, + }); + } else if Self::has_deprecated_leading_or_trailing_slash(pattern_substitution) { + let location = Self::package_warning_location(package_dir, kind, importer); + let matched_pattern = pattern_key + .map(|pattern_key| format!(" matched to {:?}", pattern_key)) + .unwrap_or_default(); + warnings.push(NodePackageWarning { + message: format!( + "Use of deprecated leading or trailing slash matching resolving {:?} for module request {:?}{}{}", + target, specifier, matched_pattern, location + ), + code: "DEP0166", + dedupe_key: None, + }); + } else if Self::has_deprecated_double_slash(specifier) { + let location = Self::package_warning_location(package_dir, kind, importer); + let matched_pattern = pattern_key + .map(|pattern_key| format!(" matched to {:?}", pattern_key)) + .unwrap_or_default(); + warnings.push(NodePackageWarning { + message: format!( + "Use of deprecated double slash resolving {:?} for module request {:?}{}{}", + target, specifier, matched_pattern, location + ), + code: "DEP0166", + dedupe_key: None, + }); + } + } + + fn default_conditions(defaults: &[&str]) -> Vec { + defaults.iter().map(|condition| (*condition).to_string()).collect() + } + + fn conditions_from_global(ctx: &Ctx<'_>, defaults: &[&str]) -> Vec { + let mut conditions = Self::default_conditions(defaults); + let Ok(user_conditions) = ctx.globals().get::<_, rquickjs::Array>("__wasm_rquickjs_package_conditions") else { + return conditions; + }; + + for i in 0..user_conditions.len() { + if let Ok(condition) = user_conditions.get::(i) { + Self::add_condition(&mut conditions, &condition); + } + } + + conditions + } + + fn add_condition(conditions: &mut Vec, condition: &str) { + if condition.is_empty() || conditions.iter().any(|existing| existing == condition) { + return; + } + conditions.push(condition.to_string()); + } +} + +fn percent_decode(input: &str) -> Option { + let bytes = input.as_bytes(); + let mut decoded = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'%' + && i + 2 < bytes.len() + && let (Some(hi), Some(lo)) = ( + FileUrlResolver::hex_val(bytes[i + 1]), + FileUrlResolver::hex_val(bytes[i + 2]), + ) + { + decoded.push(hi << 4 | lo); + i += 3; + continue; + } + decoded.push(bytes[i]); + i += 1; + } + String::from_utf8(decoded).ok() +} + +fn emit_node_package_deprecation_warnings<'js>( + ctx: &Ctx<'js>, + warnings: &[NodePackageWarning], +) -> rquickjs::Result<()> { + if warnings.is_empty() { + return Ok(()); + } + let process_object = match ctx.globals().get::<_, Object>("process") { + Ok(process_object) => process_object, + Err(_) => { + let error_ctor: Function = ctx.globals().get("Error")?; + let error_obj: Object = error_ctor.call(("Internal process object is not initialized",))?; + return Err(ctx.throw(error_obj.into_value())); + } + }; + for warning in warnings { + let key = warning.dedupe_key.as_deref().unwrap_or(warning.message.as_str()); + let warning_key = if warning.code == "DEP0155" { + Some(format!("{}:{}", warning.code, key)) + } else { + None + }; + let no_deprecation = process_object.get::<_, Coerced>("noDeprecation")?.0; + if no_deprecation { + continue; + } + if let Some(warning_key) = warning_key.as_deref() + && node_package_deprecation_warning_seen(warning_key) + { + continue; + } + if let Some(warning_key) = warning_key.as_ref() { + mark_node_package_deprecation_warning_seen(warning_key.clone()); + } + let emit_warning: Function = process_object.get("emitWarning")?; + let _: Value = emit_warning.call(( + This(process_object.clone()), + warning.message.as_str(), + "DeprecationWarning", + warning.code, + ))?; + } + Ok(()) +} + +fn throw_node_package_resolve_error<'js>( + ctx: &Ctx<'js>, + err: NodePackageResolveError, +) -> rquickjs::Result { + let (code, message, type_error) = match err { + NodePackageResolveError::InvalidModuleSpecifier { specifier, base } => ( + "ERR_INVALID_MODULE_SPECIFIER", + format!( + "Invalid module \"{}\" is not a valid package name imported from {}", + specifier, base + ), + true, + ), + NodePackageResolveError::InvalidPackagePatternMatch { specifier, message } => ( + "ERR_INVALID_MODULE_SPECIFIER", + format!("Invalid module \"{}\" {}", specifier, message), + true, + ), + NodePackageResolveError::PackagePathNotExported { + package_name, + subpath, + no_exports_main, + } => { + if no_exports_main { + ( + "ERR_PACKAGE_PATH_NOT_EXPORTED", + format!("No \"exports\" main defined in package {}", package_name), + false, + ) + } else { + let subpath = if subpath.is_empty() { + ".".to_string() + } else { + format!("./{}", subpath) + }; + ( + "ERR_PACKAGE_PATH_NOT_EXPORTED", + format!("Package subpath '{}' is not defined by \"exports\" in package {}", subpath, package_name), + false, + ) + } + } + NodePackageResolveError::PackageImportNotDefined { specifier } => ( + "ERR_PACKAGE_IMPORT_NOT_DEFINED", + format!("Package import specifier '{}' is not defined", specifier), + false, + ), + NodePackageResolveError::InvalidPackageTarget { kind, target } => { + let mut message = format!("Invalid \"{}\" target '{}'", kind, target); + if kind == "exports" && !target.starts_with("./") { + message.push_str("; targets must start with \"./\""); + } + ("ERR_INVALID_PACKAGE_TARGET", message, false) + } + NodePackageResolveError::InvalidPackageConfig { path, reason } => ( + "ERR_INVALID_PACKAGE_CONFIG", + match reason { + Some(reason) => format!("Invalid package config {}. {}", path, reason), + None => format!("Invalid package config {}", path), + }, + false, + ), + NodePackageResolveError::UnsupportedDirectoryImport { request } => ( + "ERR_UNSUPPORTED_DIR_IMPORT", + format!( + "Directory import '{}' is not supported resolving ES modules", + request + ), + false, + ), + NodePackageResolveError::ModuleNotFound { request } => ( + "ERR_MODULE_NOT_FOUND", + format!("Cannot find module '{}'", request), + false, + ), + }; + + throw_native_coded_error(ctx, &message, code, type_error) +} + +impl Resolver for NodeModulesResolver { + fn resolve<'js>( + &mut self, + ctx: &Ctx<'js>, + base: &str, + name: &str, + ) -> rquickjs::Result { + let conditions = + Self::conditions_from_global(ctx, NodePackageResolveMode::EsmImport.default_conditions()); + let mut warnings = Vec::new(); + let (resolution_name, suffix) = if has_import_type_rewrite_token(name) { + split_module_path_suffix(name) + } else { + (name, "") + }; + let package_like = resolution_name.starts_with('#') + || !(resolution_name.starts_with('.') + || resolution_name.starts_with('/') + || resolution_name.contains("://")); + let result = self.try_resolve(base, resolution_name, &conditions, &mut warnings); + emit_node_package_deprecation_warnings(ctx, &warnings)?; + match result { + Ok(Some(resolved)) => { + let suffix = append_loader_realm_param(suffix, loader_realm_param(base).as_deref()); + let resolved = if suffix.is_empty() { + resolved + } else { + format!("{resolved}{suffix}") + }; + transfer_import_type_rewrite_token(name, &resolved); + Ok(resolved) + } + Ok(None) => { + if package_like { + discard_import_type_rewrite_token(name); + } + Err(Error::new_resolving(base, name)) + } + Err(err) => { + discard_import_type_rewrite_token(name); + throw_node_package_resolve_error(ctx, err) + } + } + } +} + +/// Loader that wraps CommonJS sources in ESM-compatible wrappers when loaded via `import()`. +/// This enables ESM modules to import CJS packages from `node_modules`. +struct CjsCompatLoader; + +#[derive(Default)] +struct CjsExportAnalysis { + exports: Vec, + reexports: Vec, + is_cjs: bool, +} + +fn add_unique(items: &mut Vec, item: String) { + if !items.iter().any(|existing| existing == &item) { + items.push(item); + } +} + +fn is_ident_start(byte: u8) -> bool { + byte == b'_' || byte == b'$' || byte.is_ascii_alphabetic() || byte >= 0x80 +} + +fn is_ident_continue(byte: u8) -> bool { + is_ident_start(byte) || byte.is_ascii_digit() +} + +fn is_ident_boundary(source: &[u8], pos: usize) -> bool { + pos >= source.len() || !is_ident_continue(source[pos]) +} + +fn is_ident_start_boundary(source: &[u8], pos: usize) -> bool { + pos == 0 || !is_ident_continue(source[pos - 1]) +} + +fn is_free_ident_start(source: &[u8], pos: usize) -> bool { + is_ident_start_boundary(source, pos) && (pos == 0 || !matches!(source[pos - 1], b'.' | b'#')) +} + +fn skip_ws_comments(source: &str, pos: usize) -> usize { + skip_ws_comments_impl::(source, pos).0 +} + +fn skip_ws_comments_with_line_terminator(source: &str, pos: usize) -> (usize, bool) { + skip_ws_comments_impl::(source, pos) +} + +fn skip_ws_comments_impl( + source: &str, + mut pos: usize, +) -> (usize, bool) { + let bytes = source.as_bytes(); + let mut has_line_terminator = false; + loop { + while pos < bytes.len() && bytes[pos].is_ascii_whitespace() { + if TRACK_LINE_TERMINATOR && matches!(bytes[pos], b'\n' | b'\r') { + has_line_terminator = true; + } + pos += 1; + } + if pos + 1 < bytes.len() && bytes[pos] == b'/' && bytes[pos + 1] == b'/' { + pos += 2; + while pos < bytes.len() && !matches!(bytes[pos], b'\n' | b'\r') { + pos += 1; + } + continue; + } + if pos + 1 < bytes.len() && bytes[pos] == b'/' && bytes[pos + 1] == b'*' { + pos += 2; + while pos + 1 < bytes.len() && !(bytes[pos] == b'*' && bytes[pos + 1] == b'/') { + if TRACK_LINE_TERMINATOR && matches!(bytes[pos], b'\n' | b'\r') { + has_line_terminator = true; + } + pos += 1; + } + pos = (pos + 2).min(bytes.len()); + continue; + } + return (pos, has_line_terminator); + } +} + +fn read_ident_span(source: &str, mut pos: usize) -> Option<(usize, usize)> { + let bytes = source.as_bytes(); + if pos >= bytes.len() || !is_ident_start(bytes[pos]) { + return None; + } + let start = pos; + pos += 1; + while pos < bytes.len() && is_ident_continue(bytes[pos]) { + pos += 1; + } + Some((start, pos)) +} + +fn read_ident(source: &str, pos: usize) -> Option<(String, usize)> { + let (start, end) = read_ident_span(source, pos)?; + Some((source[start..end].to_string(), end)) +} + +fn parse_ident_name(source: &str, pos: usize, name: &str) -> Option { + let bytes = source.as_bytes(); + if !is_ident_start_boundary(bytes, pos) + || !source[pos..].starts_with(name) + || !is_ident_boundary(bytes, pos + name.len()) + { + return None; + } + Some(pos + name.len()) +} + +fn parse_free_ident_name(source: &str, pos: usize, name: &str) -> Option { + let bytes = source.as_bytes(); + if !is_free_ident_start(bytes, pos) { + return None; + } + parse_ident_name(source, pos, name) +} + +fn read_js_string(source: &str, pos: usize) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + if pos >= bytes.len() || !matches!(bytes[pos], b'\'' | b'"') { + return None; + } + let quote = bytes[pos]; + let mut units = Vec::::new(); + let mut i = pos + 1; + while i < bytes.len() { + let byte = bytes[i]; + if byte == quote { + return String::from_utf16(&units).ok().map(|s| (s, i + 1)); + } + if byte == b'\\' { + i += 1; + if i >= bytes.len() { + return None; + } + match bytes[i] { + b'n' => units.push(b'\n' as u16), + b'r' => units.push(b'\r' as u16), + b't' => units.push(b'\t' as u16), + b'b' => units.push(8), + b'f' => units.push(12), + b'v' => units.push(11), + b'x' if i + 2 < bytes.len() + && bytes[i + 1].is_ascii_hexdigit() + && bytes[i + 2].is_ascii_hexdigit() => + { + let value = hex_byte(bytes[i + 1])? * 16 + hex_byte(bytes[i + 2])?; + units.push(value as u16); + i += 2; + } + b'x' => return None, + b'u' if i + 1 < bytes.len() && bytes[i + 1] == b'{' => { + let start = i + 2; + let end = source[start..].find('}')? + start; + let code = u32::from_str_radix(&source[start..end], 16).ok()?; + if code <= 0xFFFF { + units.push(code as u16); + } else { + let code = code - 0x1_0000; + units.push(0xD800 | ((code >> 10) as u16)); + units.push(0xDC00 | ((code & 0x3FF) as u16)); + } + i = end; + } + b'u' if i + 4 < bytes.len() + && bytes[i + 1].is_ascii_hexdigit() + && bytes[i + 2].is_ascii_hexdigit() + && bytes[i + 3].is_ascii_hexdigit() + && bytes[i + 4].is_ascii_hexdigit() => + { + let value = u16::from(hex_byte(bytes[i + 1])?) << 12 + | u16::from(hex_byte(bytes[i + 2])?) << 8 + | u16::from(hex_byte(bytes[i + 3])?) << 4 + | u16::from(hex_byte(bytes[i + 4])?); + units.push(value); + i += 4; + } + b'u' => return None, + other => units.push(other as u16), + } + i += 1; + continue; + } + if byte == b'\n' || byte == b'\r' { + return None; + } + let ch = source[i..].chars().next()?; + let mut buf = [0u16; 2]; + units.extend_from_slice(ch.encode_utf16(&mut buf)); + i += ch.len_utf8(); + } + None +} + +fn hex_byte(byte: u8) -> Option { + match byte { + b'0'..=b'9' => Some(byte - b'0'), + b'a'..=b'f' => Some(byte - b'a' + 10), + b'A'..=b'F' => Some(byte - b'A' + 10), + _ => None, + } +} + +fn skip_string_or_template(source: &str, pos: usize) -> usize { + let bytes = source.as_bytes(); + if pos >= bytes.len() { + return pos; + } + let quote = bytes[pos]; + let mut i = pos + 1; + while i < bytes.len() { + if bytes[i] == b'\\' { + i += 2; + } else if bytes[i] == quote { + return i + 1; + } else { + i += 1; + } + } + i +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum CjsExportTarget { + Exports, + ModuleExports, +} + +fn parse_exports_target(source: &str, pos: usize) -> Option<(CjsExportTarget, usize)> { + let bytes = source.as_bytes(); + if let Some(exports_end) = parse_free_ident_name(source, pos, "exports") { + return Some((CjsExportTarget::Exports, exports_end)); + } + if let Some(module_end) = parse_free_ident_name(source, pos, "module") { + let mut i = skip_ws_comments(source, module_end); + if i < bytes.len() && bytes[i] == b'.' { + i = skip_ws_comments(source, i + 1); + if let Some(exports_end) = parse_ident_name(source, i, "exports") { + return Some((CjsExportTarget::ModuleExports, exports_end)); + } + } + } + None +} + +fn parse_export_member(source: &str, pos: usize) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + let (_, mut i) = parse_exports_target(source, pos)?; + i = skip_ws_comments(source, i); + let name; + if i < bytes.len() && bytes[i] == b'.' { + i = skip_ws_comments(source, i + 1); + let (ident, next) = read_ident(source, i)?; + name = ident; + i = next; + } else if i < bytes.len() && bytes[i] == b'[' { + i = skip_ws_comments(source, i + 1); + let (string_name, next) = read_js_string(source, i)?; + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b']' { + return None; + } + name = string_name; + i += 1; + } else { + return None; + } + i = skip_ws_comments(source, i); + parse_assignment_operator(source, i).map(|next| (name, next)) +} + +fn parse_require_string(source: &str, pos: usize) -> Option<(String, usize)> { + parse_require_call_string(source, pos, true) +} + +fn parse_require_string_loose(source: &str, pos: usize) -> Option<(String, usize)> { + parse_require_call_string(source, pos, false) +} + +fn parse_require_call_string(source: &str, pos: usize, require_free_start: bool) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + let require_end = if require_free_start { + parse_free_ident_name(source, pos, "require")? + } else { + parse_ident_name(source, pos, "require")? + }; + let mut i = skip_ws_comments(source, require_end); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + i = skip_ws_comments(source, i + 1); + let (specifier, next) = read_js_string(source, i)?; + i = skip_ws_comments(source, next); + if i < bytes.len() && bytes[i] == b')' { + Some((specifier, i + 1)) + } else { + None + } +} + +fn parse_object_define_property_call(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, parse_free_ident_name(source, pos, "Object")?); + if i >= bytes.len() || bytes[i] != b'.' { + return None; + } + i = skip_ws_comments(source, i + 1); + i = skip_ws_comments(source, parse_ident_name(source, i, "defineProperty")?); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + Some(skip_ws_comments(source, i + 1)) +} + +fn parse_define_property_export(source: &str, pos: usize) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + let mut i = parse_object_define_property_call(source, pos)?; + let (_, next) = parse_exports_target(source, i)?; + i = next; + i = skip_ws_comments(source, i); + if i >= bytes.len() || bytes[i] != b',' { + return None; + } + i = skip_ws_comments(source, i + 1); + let (name, next) = read_js_string(source, i)?; + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b',' { + return None; + } + let descriptor_start = i + 1; + let end = find_matching_paren(source, pos)?; + let descriptor = &source[descriptor_start..end]; + if descriptor_has_named_property(descriptor) { + Some((name, end + 1)) + } else { + None + } +} + +enum DescriptorNamedProperty { + Value, + Getter, +} + +fn descriptor_function_getter_body( + source: &str, + pos: usize, + descriptor_end: usize, +) -> Option<(usize, usize, usize)> { + let bytes = source.as_bytes(); + let mut next = skip_ws_comments(source, parse_ident_name(source, pos, "function")?); + if let Some((_, ident_end)) = read_ident(source, next) { + next = skip_ws_comments(source, ident_end); + } + if next >= descriptor_end || bytes[next] != b'(' { + return None; + } + let body = getter_body_after_empty_params(source, next, descriptor_end)?; + Some((body.0, body.1, body.1 + 1)) +} + +fn descriptor_function_getter_end(source: &str, pos: usize, descriptor_end: usize) -> Option { + let body = descriptor_function_getter_body(source, pos, descriptor_end)?; + if !is_simple_getter_body(&source[body.0..body.1]) { + return None; + } + Some(body.2) +} + +fn getter_body_after_empty_params(source: &str, params_open: usize, limit: usize) -> Option<(usize, usize)> { + let params_end = find_matching_paren(source, params_open)?; + if params_end > limit || skip_ws_comments(source, params_open + 1) != params_end { + return None; + } + let body_open = skip_ws_comments(source, params_end + 1); + if body_open >= limit || source.as_bytes()[body_open] != b'{' { + return None; + } + let body_end = find_matching_brace(source, body_open)?; + if body_end > limit { + return None; + } + Some((body_open + 1, body_end)) +} + +fn descriptor_object_span(descriptor: &str) -> Option<(usize, usize)> { + let bytes = descriptor.as_bytes(); + let descriptor_start = skip_ws_comments(descriptor, 0); + if descriptor_start >= bytes.len() || bytes[descriptor_start] != b'{' { + return None; + } + let descriptor_end = find_matching_brace(descriptor, descriptor_start)?; + Some((skip_ws_comments(descriptor, descriptor_start + 1), descriptor_end)) +} + +fn next_object_literal_entry(source: &str, cursor: usize, object_end: usize) -> Option { + if cursor >= object_end { + return Some(object_end); + } + if source.as_bytes()[cursor] != b',' { + return None; + } + Some(skip_ws_comments(source, cursor + 1)) +} + +fn is_spread_token_at(source: &str, pos: usize) -> bool { + let bytes = source.as_bytes(); + bytes.get(pos).copied() == Some(b'.') + && bytes.get(pos + 1).copied() == Some(b'.') + && bytes.get(pos + 2).copied() == Some(b'.') +} + +fn descriptor_has_named_property(descriptor: &str) -> bool { + let bytes = descriptor.as_bytes(); + let Some((mut cursor, descriptor_end)) = descriptor_object_span(descriptor) else { + return false; + }; + let mut found: Option = None; + while cursor < descriptor_end { + if bytes[cursor] == b',' { + cursor = skip_ws_comments(descriptor, cursor + 1); + continue; + } + if is_spread_token_at(descriptor, cursor) { + return false; + } + if bytes[cursor] == b'[' { + if matches!(found, Some(DescriptorNamedProperty::Value)) { + cursor = skip_ws_comments(descriptor, skip_object_literal_value(descriptor, cursor, descriptor_end)); + let Some(next_cursor) = next_object_literal_entry(descriptor, cursor, descriptor_end) else { + return false; + }; + cursor = next_cursor; + continue; + } + return false; + } + + let Some((name, key_is_ident, key_end)) = parse_exports_literal_key(descriptor, cursor) else { + return false; + }; + let next = skip_ws_comments(descriptor, key_end); + if !key_is_ident { + if !matches!(found, Some(DescriptorNamedProperty::Value)) { + return false; + } + cursor = skip_ws_comments(descriptor, skip_object_literal_value(descriptor, next, descriptor_end)); + } else if name == "value" { + if matches!(found, Some(DescriptorNamedProperty::Getter)) { + return false; + } + if matches!(found, Some(DescriptorNamedProperty::Value)) { + let value_start = if next < descriptor_end && bytes[next] == b':' { + next + 1 + } else { + next + }; + cursor = skip_ws_comments(descriptor, skip_object_literal_value(descriptor, value_start, descriptor_end)); + } else { + if next >= descriptor_end || bytes[next] != b':' { + return false; + } + found = Some(DescriptorNamedProperty::Value); + cursor = skip_ws_comments(descriptor, skip_object_literal_value(descriptor, next + 1, descriptor_end)); + } + } else if name == "get" { + if found.is_some() { + return false; + } + if next < descriptor_end && bytes[next] == b'(' { + let Some((body_start, body_end)) = getter_body_after_empty_params(descriptor, next, descriptor_end) else { + return false; + }; + if !is_simple_getter_body(&descriptor[body_start..body_end]) { + return false; + } + found = Some(DescriptorNamedProperty::Getter); + cursor = skip_ws_comments(descriptor, body_end + 1); + } else if next < descriptor_end && bytes[next] == b':' { + let function_end = descriptor_function_getter_end(descriptor, skip_ws_comments(descriptor, next + 1), descriptor_end); + let Some(function_end) = function_end else { + return false; + }; + found = Some(DescriptorNamedProperty::Getter); + cursor = skip_ws_comments(descriptor, function_end); + } else { + return false; + } + } else if name == "enumerable" { + if next >= descriptor_end || bytes[next] != b':' { + return false; + } + if matches!(found, Some(DescriptorNamedProperty::Value)) { + cursor = skip_ws_comments(descriptor, skip_object_literal_value(descriptor, next + 1, descriptor_end)); + let Some(next_cursor) = next_object_literal_entry(descriptor, cursor, descriptor_end) else { + return false; + }; + cursor = next_cursor; + continue; + } + if matches!(found, Some(DescriptorNamedProperty::Getter)) { + return false; + } + let value_start = skip_ws_comments(descriptor, next + 1); + let Some(true_end) = parse_ident_name(descriptor, value_start, "true") else { + return false; + }; + cursor = skip_ws_comments(descriptor, true_end); + } else { + if matches!(found, Some(DescriptorNamedProperty::Value)) { + cursor = skip_ws_comments(descriptor, skip_object_literal_value(descriptor, next, descriptor_end)); + } else { + return false; + } + } + + let Some(next_cursor) = next_object_literal_entry(descriptor, cursor, descriptor_end) else { + return false; + }; + cursor = next_cursor; + } + + found.is_some() +} + +fn find_matching_paren(source: &str, start: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = source[start..].find('(')? + start; + let mut depth = 0usize; + while i < bytes.len() { + match bytes[i] { + b'\'' | b'"' | b'`' => i = skip_string_or_template(source, i), + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + i += 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(bytes.len()); + } + b'/' if is_regex_literal_start(source, i) => { + i = skip_regex_literal(source, i); + } + b'(' => { + depth += 1; + i += 1; + } + b')' => { + depth = depth.saturating_sub(1); + if depth == 0 { + return Some(i); + } + i += 1; + } + _ => i += 1, + } + } + None +} + +fn find_matching_brace(source: &str, start: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = start; + let mut depth = 0usize; + while i < bytes.len() { + match bytes[i] { + b'\'' | b'"' | b'`' => i = skip_string_or_template(source, i), + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + i += 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(bytes.len()); + } + b'/' if is_regex_literal_start(source, i) => { + i = skip_regex_literal(source, i); + } + b'{' => { + depth += 1; + i += 1; + } + b'}' => { + depth = depth.saturating_sub(1); + if depth == 0 { + return Some(i); + } + i += 1; + } + _ => i += 1, + } + } + None +} + + +enum GetterReturnMember<'a> { + Bare, + Dot, + BracketString, + BracketIdentifier { receiver: &'a str, member: &'a str }, +} + +fn parse_getter_return_member_body(body: &str) -> Option> { + let bytes = body.as_bytes(); + let mut i = skip_ws_comments(body, 0); + let return_end = parse_free_ident_name(body, i, "return")?; + i = skip_ws_comments(body, return_end); + let (receiver_start, next) = read_ident_span(body, i)?; + let receiver = &body[receiver_start..next]; + i = skip_ws_comments(body, next); + let member = if i < body.len() && bytes[i] == b'.' { + i = skip_ws_comments(body, i + 1); + let (_, next) = read_ident_span(body, i)?; + i = next; + GetterReturnMember::Dot + } else if i < body.len() && bytes[i] == b'[' { + i = skip_ws_comments(body, i + 1); + let quote = bytes.get(i).copied(); + let member = if matches!(quote, Some(b'\'') | Some(b'"')) { + let (_, next) = read_js_string(body, i)?; + i = skip_ws_comments(body, next); + GetterReturnMember::BracketString + } else { + let (member_start, next) = read_ident_span(body, i)?; + let member = &body[member_start..next]; + i = skip_ws_comments(body, next); + GetterReturnMember::BracketIdentifier { receiver, member } + }; + if i >= body.len() || bytes[i] != b']' { + return None; + } + i += 1; + member + } else { + GetterReturnMember::Bare + }; + i = skip_ws_comments(body, i); + if i < body.len() && bytes[i] == b';' { + i = skip_ws_comments(body, i + 1); + } + if i >= body.len() { + Some(member) + } else { + None + } +} + +fn is_simple_getter_body(body: &str) -> bool { + matches!( + parse_getter_return_member_body(body), + Some(GetterReturnMember::Bare | GetterReturnMember::Dot | GetterReturnMember::BracketString) + ) +} + + +fn parse_exports_assign_require_value(source: &str, pos: usize) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + if let Some((specifier, next)) = parse_require_string(source, pos) { + return Some((specifier, next)); + } + + let mut i = skip_ws_comments(source, parse_free_ident_name(source, pos, "_interopRequireWildcard")?); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + i = skip_ws_comments(source, i + 1); + let (specifier, next) = parse_require_string(source, i)?; + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b')' { + return None; + } + + Some((specifier, i + 1)) +} + +fn parse_require_binding(source: &str, pos: usize) -> Option<(String, String, usize)> { + let mut i = skip_ws_comments(source, parse_variable_declaration_keyword(source, pos)?); + let (name, next) = read_ident(source, i)?; + i = skip_ws_comments(source, next); + if i >= source.len() || source.as_bytes()[i] != b'=' { + return None; + } + i = skip_ws_comments(source, i + 1); + let (specifier, next) = parse_exports_assign_require_value(source, i)?; + if !is_statement_boundary(source, next) { + return None; + } + Some((name, specifier, next)) +} + +fn is_statement_boundary(source: &str, pos: usize) -> bool { + let (next, has_line_terminator) = skip_ws_comments_with_line_terminator(source, pos); + if next >= source.len() { + return true; + } + if matches!(source.as_bytes()[next], b';' | b'}') { + return true; + } + has_line_terminator && !is_asi_continuation_next(source, next) +} + +fn is_asi_continuation_previous(byte: u8) -> bool { + is_asi_continuation_operator(byte) || matches!(byte, b'!' | b'~') +} + +fn is_asi_continuation_next(source: &str, pos: usize) -> bool { + let bytes = source.as_bytes(); + if pos + 1 < bytes.len() { + let current = bytes[pos]; + let next = bytes[pos + 1]; + if (current == b'+' && next == b'+') || (current == b'-' && next == b'-') { + return false; + } + } + is_asi_continuation_operator(bytes[pos]) +} + +fn is_asi_continuation_operator(byte: u8) -> bool { + matches!( + byte, + b'(' | b'[' | b'.' | b',' | b':' | b'?' | b'+' | b'-' | b'*' | b'/' | b'%' | b'&' | b'|' | b'^' | b'<' | b'>' + | b'=' + ) +} + +fn parse_module_exports_reexport(source: &str, pos: usize) -> Option<(String, usize)> { + let (target, mut i) = parse_exports_target(source, pos)?; + if target != CjsExportTarget::ModuleExports { + return None; + } + i = skip_ws_comments(source, i); + if i >= source.len() || source.as_bytes()[i] != b'=' { + return None; + } + let (specifier, next) = parse_require_string(source, skip_ws_comments(source, i + 1))?; + let after_require = skip_ws_comments(source, next); + Some((specifier, after_require.min(source.len()))) +} + +fn parse_export_star_reexport(source: &str, pos: usize) -> Option<(String, usize)> { + fn parse_export_star_callee(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let member_access = previous_significant_byte(source, pos) == Some(b'.'); + if !member_access { + if let Some(export_star_end) = parse_free_ident_name(source, pos, "__exportStar") { + return Some(export_star_end); + } + if let Some(export_end) = parse_free_ident_name(source, pos, "__export") { + return Some(export_end); + } + } + if let Some(tslib_end) = parse_free_ident_name(source, pos, "tslib") { + let mut i = skip_ws_comments(source, tslib_end); + if i >= bytes.len() || bytes[i] != b'.' { + return None; + } + i = skip_ws_comments(source, i + 1); + if let Some(export_star_end) = parse_ident_name(source, i, "__exportStar") { + return Some(export_star_end); + } + if let Some(export_end) = parse_ident_name(source, i, "__export") { + return Some(export_end); + } + } + None + } + + let bytes = source.as_bytes(); + let mut i = parse_export_star_callee(source, pos)?; + i = skip_ws_comments(source, i); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + + i = skip_ws_comments(source, i + 1); + let (specifier, next) = parse_require_string(source, i)?; + i = skip_ws_comments(source, next); + + if i < bytes.len() && bytes[i] == b',' { + i = skip_ws_comments(source, i + 1); + let (_, next_target) = parse_exports_target(source, i)?; + i = skip_ws_comments(source, next_target); + } + + if i >= bytes.len() || bytes[i] != b')' { + return None; + } + + let after_call = skip_ws_comments(source, i + 1); + if is_statement_boundary(source, after_call) { + Some((specifier, after_call.min(source.len()))) + } else { + None + } +} + +fn parse_module_exports_assignment(source: &str, pos: usize) -> Option { + let (target, mut i) = parse_exports_target(source, pos)?; + if target != CjsExportTarget::ModuleExports { + return None; + } + i = skip_ws_comments(source, i); + parse_assignment_operator(source, i) +} + +fn parse_exports_literal_key(source: &str, pos: usize) -> Option<(String, bool, usize)> { + if let Some((ident, next)) = read_ident(source, pos) { + return Some((ident, true, next)); + } + let (name, next) = read_js_string(source, pos)?; + Some((name, false, next)) +} + +fn skip_object_literal_value(source: &str, pos: usize, object_end: usize) -> usize { + let bytes = source.as_bytes(); + let mut i = pos; + let mut brace_depth = 0usize; + let mut paren_depth = 0usize; + let mut bracket_depth = 0usize; + while i < object_end { + match bytes[i] { + b'\'' | b'"' | b'`' => { + i = skip_string_or_template(source, i); + continue; + } + b'/' if i + 1 < object_end && bytes[i + 1] == b'/' => { + i += 2; + while i < object_end && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + continue; + } + b'/' if i + 1 < object_end && bytes[i + 1] == b'*' => { + i += 2; + while i + 1 < object_end && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i = (i + 2).min(object_end); + continue; + } + b'/' if is_regex_literal_start(source, i) => { + i = skip_regex_literal(source, i).min(object_end); + continue; + } + b'{' => brace_depth += 1, + b'}' => brace_depth = brace_depth.saturating_sub(1), + b'(' => paren_depth += 1, + b')' => paren_depth = paren_depth.saturating_sub(1), + b'[' => bracket_depth += 1, + b']' => bracket_depth = bracket_depth.saturating_sub(1), + b',' if brace_depth == 0 && paren_depth == 0 && bracket_depth == 0 => return i, + _ => {} + } + i = next_char_boundary(source, i); + } + object_end +} + +enum ObjectLiteralValueExport { + NamedContinue, + NamedStop, +} + +fn named_export_object_literal_value(source: &str, pos: usize, object_end: usize) -> Option { + let Some((ident, mut next)) = read_ident(source, pos) else { + return None; + }; + next = skip_ws_comments(source, next); + if next >= object_end || source.as_bytes()[next] == b',' { + Some(ObjectLiteralValueExport::NamedContinue) + } else if matches!(ident.as_str(), "true" | "false" | "null" | "undefined") { + Some(ObjectLiteralValueExport::NamedContinue) + } else { + Some(ObjectLiteralValueExport::NamedStop) + } +} + +fn parse_module_exports_object_literal(source: &str, pos: usize) -> Option<(Vec, Vec, usize)> { + let bytes = source.as_bytes(); + let (target, mut i) = parse_exports_target(source, pos)?; + if target != CjsExportTarget::ModuleExports { + return None; } - None -} + i = skip_ws_comments(source, i); + i = skip_ws_comments(source, parse_assignment_operator(source, i)?); + if i >= bytes.len() || bytes[i] != b'{' { + return None; + } + let object_end = find_matching_brace(source, i)?; -/// Resolver that strips `file://` URL prefixes so that `import('file:///path/to/mod.mjs')` -/// resolves to the filesystem path `/path/to/mod.mjs`. -struct FileUrlResolver; + let mut exports = Vec::new(); + let mut reexports = Vec::new(); + let mut cursor = skip_ws_comments(source, i + 1); -impl FileUrlResolver { - /// Decode a `file://` URL into a filesystem path, handling percent-encoding. - fn file_url_to_path(url: &str) -> Option { - let encoded = url.strip_prefix("file://")?; - let bytes = encoded.as_bytes(); - let mut decoded = Vec::with_capacity(bytes.len()); - let mut i = 0; - while i < bytes.len() { - if bytes[i] == b'%' - && i + 2 < bytes.len() - && let (Some(hi), Some(lo)) = - (Self::hex_val(bytes[i + 1]), Self::hex_val(bytes[i + 2])) - { - decoded.push(hi << 4 | lo); - i += 3; - continue; + while cursor < object_end { + if bytes[cursor] == b',' { + cursor = skip_ws_comments(source, cursor + 1); + continue; + } + + if is_spread_token_at(source, cursor) { + let spread_start = skip_ws_comments(source, cursor + 3); + if let Some((specifier, next)) = parse_require_string_loose(source, spread_start) { + add_unique(&mut reexports, specifier); + cursor = skip_ws_comments(source, next); + if cursor < object_end && bytes[cursor] != b',' { + break; + } + } else if let Some((_, next)) = read_ident(source, spread_start) { + let after_ident = skip_ws_comments(source, next); + if after_ident < object_end && bytes[after_ident] != b',' { + break; + } + cursor = after_ident; + } else { + break; + }; + cursor = next_object_literal_entry(source, cursor, object_end)?; + continue; + } + + let Some((name, key_is_ident, key_end)) = parse_exports_literal_key(source, cursor) else { + break; + }; + let mut next = skip_ws_comments(source, key_end); + if next < object_end && bytes[next] == b':' { + next = skip_ws_comments(source, next + 1); + if parse_require_string_loose(source, next).is_some() { + add_unique(&mut exports, name); + break; } - decoded.push(bytes[i]); - i += 1; + match named_export_object_literal_value(source, next, object_end) { + Some(ObjectLiteralValueExport::NamedContinue) => { + add_unique(&mut exports, name); + cursor = skip_ws_comments(source, skip_object_literal_value(source, next, object_end)); + } + Some(ObjectLiteralValueExport::NamedStop) => { + add_unique(&mut exports, name); + break; + } + None => break, + } + } else if key_is_ident { + add_unique(&mut exports, name); + cursor = next; + if cursor < object_end && bytes[cursor] != b',' { + break; + } + } else { + break; } - String::from_utf8(decoded).ok() + + cursor = next_object_literal_entry(source, cursor, object_end)?; } - fn hex_val(b: u8) -> Option { - match b { - b'0'..=b'9' => Some(b - b'0'), - b'A'..=b'F' => Some(b - b'A' + 10), - b'a'..=b'f' => Some(b - b'a' + 10), - _ => None, - } + let after_object = skip_ws_comments(source, object_end + 1); + if is_statement_boundary(source, after_object) { + Some((exports, reexports, after_object.min(source.len()))) + } else { + None } } -impl Resolver for FileUrlResolver { - fn resolve<'js>( - &mut self, - _ctx: &Ctx<'js>, - _base: &str, - name: &str, - ) -> rquickjs::Result { - if let Some(path) = Self::file_url_to_path(name) { - Ok(path) - } else { - Err(Error::new_resolving(_base, name)) - } +fn parse_object_keys_reexport(source: &str, pos: usize, bindings: &HashMap) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, parse_free_ident_name(source, pos, "Object")?); + if i >= bytes.len() || bytes[i] != b'.' { + return None; + } + i = skip_ws_comments(source, i + 1); + i = skip_ws_comments(source, parse_ident_name(source, i, "keys")?); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + i = skip_ws_comments(source, i + 1); + let (binding, next) = read_ident(source, i)?; + let specifier = bindings.get(&binding)?.clone(); + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b')' { + return None; + } + let after_keys = skip_ws_comments(source, i + 1); + if after_keys >= bytes.len() || bytes[after_keys] != b'.' { + return None; + } + i = skip_ws_comments(source, after_keys + 1); + i = skip_ws_comments(source, parse_ident_name(source, i, "forEach")?); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + let end = find_matching_paren(source, i)?; + let (callback_key, callback_body) = extract_for_each_callback_body(source, i, end)?; + if callback_has_transpiler_reexport(callback_body, &binding, &callback_key) { + Some((specifier, end + 1)) + } else { + None } } -/// Resolver that handles bare specifier imports by walking up the directory tree -/// looking for `node_modules//` directories, reading their `package.json` -/// to find the entry point. -/// Resolver that guards against dynamic import from contexts without a module referrer. -/// -/// QuickJS currently reports `` for both direct and indirect eval, so we -/// conservatively enforce Node's missing-callback error for `node:` specifiers. -/// This is enough for Node's `Promise.resolve(...).then(eval)` realm test case -/// while preserving successful direct-eval imports in CommonJS modules. -struct RealmGuardResolver; +fn extract_for_each_callback_body(source: &str, call_open: usize, end: usize) -> Option<(String, &str)> { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, call_open + 1); + i = skip_ws_comments(source, parse_free_ident_name(source, i, "function")?); + if i < end && is_ident_start(bytes[i]) { + let (_, next) = read_ident(source, i)?; + i = skip_ws_comments(source, next); + } + if i >= end || bytes[i] != b'(' { + return None; + } + let params_end = find_matching_paren(source, i)?; + if params_end > end { + return None; + } + let mut param_pos = skip_ws_comments(source, i + 1); + let (key, next) = read_ident(source, param_pos)?; + param_pos = skip_ws_comments(source, next); + if param_pos != params_end || bytes[param_pos] != b')' { + return None; + } + i = skip_ws_comments(source, params_end + 1); + if i >= end || bytes[i] != b'{' { + return None; + } + let body_end = find_matching_brace(source, i)?; + if body_end > end || skip_ws_comments(source, body_end + 1) != end { + return None; + } + Some((key, &source[i + 1..body_end])) +} -impl Resolver for RealmGuardResolver { - fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { - if base != "" { - return Err(Error::new_resolving(base, name)); +fn callback_has_transpiler_reexport(callback: &str, binding: &str, key: &str) -> bool { + let mut found = false; + let statement_starts = statement_starts(callback); + let _ = scan_code_positions_with_brace_depth(callback, true, |i, _, brace_depth| { + if brace_depth != 0 { + return ControlFlow::Continue(None); } - - if !name.starts_with("node:") { - return Err(Error::new_resolving(base, name)); + if !statement_starts.get(i).copied().unwrap_or(false) { + return ControlFlow::Continue(None); + } + if parse_export_star_conditional_reexport(callback, i, binding, key).is_some() { + found = true; + return ControlFlow::Break(()); + } + if let Some(next) = parse_export_star_return_guard(callback, i, key) { + let mut write_pos = skip_statement_separator(callback, next); + while let Some(next_guard) = parse_duplicate_export_return_guard(callback, write_pos, binding, key) { + write_pos = skip_statement_separator(callback, next_guard); + } + if statement_starts.get(write_pos).copied().unwrap_or(false) + && (parse_define_property_reexport(callback, write_pos, binding, key).is_some() + || parse_direct_exports_reexport_assignment(callback, write_pos, binding, key).is_some()) + { + found = true; + return ControlFlow::Break(()); + } + return ControlFlow::Continue(Some(next)); } + ControlFlow::Continue(None) + }); + found +} - let globals = ctx.globals(); - let current_module: Value = globals - .get("__wasm_rquickjs_current_module") - .unwrap_or_else(|_| Value::new_undefined(ctx.clone())); +fn skip_statement_separator(source: &str, pos: usize) -> usize { + let mut i = skip_ws_comments(source, pos); + if i < source.len() && source.as_bytes()[i] == b';' { + i = skip_ws_comments(source, i + 1); + } + i +} - if !current_module.is_undefined() && !current_module.is_null() { - return Err(Error::new_resolving(base, name)); - } +fn parse_if_condition(source: &str, pos: usize) -> Option<(&str, usize)> { + let bytes = source.as_bytes(); + let i = skip_ws_comments(source, parse_free_ident_name(source, pos, "if")?); + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + let condition_end = find_matching_paren(source, i)?; + Some(( + &source[i + 1..condition_end], + skip_ws_comments(source, condition_end + 1), + )) +} - let eval_script: Value = globals - .get("__wasm_rquickjs_current_eval_script_name") - .unwrap_or_else(|_| Value::new_undefined(ctx.clone())); - if !eval_script.is_undefined() && !eval_script.is_null() { - return Err(Error::new_resolving(base, name)); - } +fn parse_export_star_conditional_reexport(source: &str, pos: usize, binding: &str, key: &str) -> Option { + let (condition, i) = parse_if_condition(source, pos)?; + if !is_export_star_has_own_guard_condition(condition, key) { + return None; + } + parse_direct_exports_reexport_assignment(source, i, binding, key) +} - let type_error_ctor: Function = globals.get("TypeError")?; - let error_obj: Object = - type_error_ctor.call(("A dynamic import callback was not specified.",))?; - error_obj.set("code", "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING")?; - Err(ctx.throw(error_obj.into_value())) +fn parse_export_star_return_guard(source: &str, pos: usize, key: &str) -> Option { + let (condition, i) = parse_if_condition(source, pos)?; + if !is_export_star_guard_condition(condition, key) { + return None; } + parse_free_ident_name(source, i, "return") } -/// Resolver that intercepts module resolution for mocked modules. -/// Checks `globalThis.__wasm_rquickjs_module_mocks` registry via JS helpers. -struct MockModuleResolver; +fn parse_duplicate_export_return_guard(source: &str, pos: usize, binding: &str, key: &str) -> Option { + let (condition, i) = parse_if_condition(source, pos)?; + if !is_duplicate_export_guard_condition(condition, binding, key) { + return None; + } + parse_free_ident_name(source, i, "return") +} -impl Resolver for MockModuleResolver { - fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { - let globals = ctx.globals(); +fn is_duplicate_export_guard_condition(condition: &str, binding: &str, key: &str) -> bool { + let i = skip_ws_comments(condition, 0); + if let Some(next) = parse_exports_has_own_key(condition, i, key) + && skip_ws_comments(condition, next) >= condition.len() + { + return true; + } - let canonical_key_fn: Function = globals - .get::<_, Function>("__wasm_rquickjs_mock_canonical_key") - .map_err(|_| Error::new_resolving(base, name))?; + let Some(next) = parse_key_in_export_target_condition(condition, i, key) else { + return false; + }; + let Some(next) = parse_operator(condition, skip_ws_comments(condition, next), "&&") else { + return false; + }; + let mut i = skip_ws_comments(condition, next); + let Some(next) = parse_export_target_bracket_key(condition, i, key) else { + return false; + }; + i = skip_ws_comments(condition, next); + let Some(next) = parse_operator(condition, i, "===") else { + return false; + }; + i = skip_ws_comments(condition, next); + let Some(next) = parse_binding_bracket_key(condition, i, binding, key) else { + return false; + }; + skip_ws_comments(condition, next) >= condition.len() +} - let key: Value = canonical_key_fn - .call((name, base)) - .map_err(|_| Error::new_resolving(base, name))?; +fn is_export_star_guard_condition(condition: &str, key: &str) -> bool { + let mut i = skip_ws_comments(condition, 0); + let (first, next) = match parse_key_equals_string(condition, i, key) { + Some(result) => result, + None => return false, + }; + if first != "default" { + return false; + } + let Some(next) = parse_operator(condition, skip_ws_comments(condition, next), "||") else { + return false; + }; + i = skip_ws_comments(condition, next); + let (second, next) = match parse_key_equals_string(condition, i, key) { + Some(result) => result, + None => return false, + }; + if second != "__esModule" { + return false; + } + skip_ws_comments(condition, next) >= condition.len() +} - if key.is_null() || key.is_undefined() { - return Err(Error::new_resolving(base, name)); - } +fn is_export_star_has_own_guard_condition(condition: &str, key: &str) -> bool { + let mut i = skip_ws_comments(condition, 0); + let (first, next) = match parse_key_not_equals_string(condition, i, key) { + Some(result) => result, + None => return false, + }; + if first != "default" { + return false; + } + let Some(next) = parse_operator(condition, skip_ws_comments(condition, next), "&&") else { + return false; + }; + i = skip_ws_comments(condition, next); + let Some(next) = parse_negated_exports_has_own_key(condition, i, key) else { + return false; + }; + skip_ws_comments(condition, next) >= condition.len() +} - let key_str: String = key - .get::() - .map_err(|_| Error::new_resolving(base, name))?; +fn parse_key_equals_string(source: &str, pos: usize, key: &str) -> Option<(String, usize)> { + parse_key_string_comparison(source, pos, key, "===") +} - let registry: Object = globals - .get::<_, Object>("__wasm_rquickjs_module_mocks") - .map_err(|_| Error::new_resolving(base, name))?; +fn parse_key_not_equals_string(source: &str, pos: usize, key: &str) -> Option<(String, usize)> { + parse_key_string_comparison(source, pos, key, "!==") +} - let entry: Value = registry - .get::<_, Value>(&key_str as &str) - .map_err(|_| Error::new_resolving(base, name))?; +fn parse_key_string_comparison(source: &str, pos: usize, key: &str, operator: &str) -> Option<(String, usize)> { + let mut i = skip_ws_comments(source, parse_free_ident_name(source, pos, key)?); + i = skip_ws_comments(source, parse_operator(source, i, operator)?); + let (value, next) = read_js_string(source, i)?; + Some((value, next)) +} - if entry.is_undefined() || entry.is_null() { - return Err(Error::new_resolving(base, name)); +fn parse_operator(source: &str, pos: usize, operator: &str) -> Option { + let bytes = source.as_bytes(); + let operator_bytes = operator.as_bytes(); + for (offset, expected) in operator_bytes.iter().enumerate() { + if bytes.get(pos + offset).copied() != Some(*expected) { + return None; } + } + Some(pos + operator_bytes.len()) +} - let entry_obj: Object = entry - .into_object() - .ok_or_else(|| Error::new_resolving(base, name))?; +fn parse_assignment_operator(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + if bytes.get(pos).copied() != Some(b'=') + || matches!(bytes.get(pos + 1).copied(), Some(b'=' | b'>')) + { + return None; + } + Some(pos + 1) +} - let mock_id: i64 = entry_obj - .get::<_, i64>("id") - .map_err(|_| Error::new_resolving(base, name))?; +fn parse_exports_has_own_key(source: &str, pos: usize, key: &str) -> Option { + let (target, next) = parse_object_has_own_property_call(source, pos, key, true)?; + if target != "exports" { + return None; + } + Some(next) +} - let cache: bool = entry_obj.get::<_, bool>("cache").unwrap_or(false); +fn parse_negated_exports_has_own_key(source: &str, pos: usize, key: &str) -> Option { + let bytes = source.as_bytes(); + if pos >= bytes.len() || bytes[pos] != b'!' { + return None; + } + let mut i = skip_ws_comments(source, pos + 1); - if cache { - Ok(format!("__wasm_rquickjs_mock__:{}", mock_id)) - } else { - let seq_key = "__wasm_rquickjs_mock_seq"; - let seq: i64 = globals.get::<_, i64>(seq_key).unwrap_or(0); - let next_seq = seq + 1; - let _ = globals.set(seq_key, next_seq); - Ok(format!("__wasm_rquickjs_mock__:{}:{}", mock_id, next_seq)) + let (receiver, next) = read_ident(source, i)?; + if receiver == "Object" { + if let Some((_, next)) = parse_object_has_own_property_call(source, i, key, false) { + return Some(next); } } -} - -/// Loader that handles synthetic mock module IDs produced by MockModuleResolver. -/// Generates ESM source from the JS-side mock registry. -struct MockModuleLoader; -impl Loader for MockModuleLoader { - fn load<'js>( - &mut self, - ctx: &Ctx<'js>, - path: &str, - ) -> rquickjs::Result> { - if !path.starts_with("__wasm_rquickjs_mock__:") { - return Err(Error::new_loading(path)); + { + i = parse_dot_member_name(source, next, "hasOwnProperty")?; + if i >= bytes.len() || bytes[i] != b'(' { + return None; } + i = skip_ws_comments(source, i + 1); + i = skip_ws_comments(source, parse_free_ident_name(source, i, key)?); + if i >= bytes.len() || bytes[i] != b')' { + return None; + } + return Some(i + 1); + } +} + +fn parse_object_has_own_property_call( + source: &str, + pos: usize, + key: &str, + require_prototype: bool, +) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + let (receiver, next) = read_ident(source, pos)?; + if receiver != "Object" { + return None; + } + let mut i = next; + if let Some(next) = parse_dot_member_name(source, i, "prototype") { + i = next; + } else if require_prototype { + return None; + } + i = parse_dot_member_name(source, i, "hasOwnProperty")?; + i = parse_dot_member_name(source, i, "call")?; + if i >= bytes.len() || bytes[i] != b'(' { + return None; + } + i = skip_ws_comments(source, i + 1); + let (target, next) = read_ident(source, i)?; + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b',' { + return None; + } + i = skip_ws_comments(source, i + 1); + i = skip_ws_comments(source, parse_free_ident_name(source, i, key)?); + if i >= bytes.len() || bytes[i] != b')' { + return None; + } + Some((target, i + 1)) +} - let rest = &path["__wasm_rquickjs_mock__:".len()..]; - let mock_id_str = rest.split(':').next().unwrap_or(rest); - let mock_id: i64 = mock_id_str.parse().map_err(|_| Error::new_loading(path))?; +fn parse_key_in_export_target_condition(source: &str, pos: usize, key: &str) -> Option { + let mut i = skip_ws_comments(source, parse_free_ident_name(source, pos, key)?); + i = skip_ws_comments(source, parse_free_ident_name(source, i, "in")?); + let (_, next) = parse_exports_target(source, i)?; + Some(next) +} - let globals = ctx.globals(); - let gen_fn: Function = globals - .get::<_, Function>("__wasm_rquickjs_get_mock_module_source") - .map_err(|_| Error::new_loading(path))?; +fn parse_export_target_bracket_key(source: &str, pos: usize, key: &str) -> Option { + let (_, next) = parse_exports_target(source, pos)?; + parse_bracket_key(source, next, key) +} - let source: String = gen_fn - .call::<_, String>((mock_id,)) - .map_err(|_| Error::new_loading(path))?; +fn parse_binding_bracket_key(source: &str, pos: usize, binding: &str, key: &str) -> Option { + parse_bracket_key(source, parse_free_ident_name(source, pos, binding)?, key) +} - Module::declare(ctx.clone(), path, source.as_bytes().to_vec()) +fn parse_bracket_key(source: &str, pos: usize, key: &str) -> Option { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, pos); + if i >= bytes.len() || bytes[i] != b'[' { + return None; + } + i = skip_ws_comments(source, i + 1); + i = skip_ws_comments(source, parse_free_ident_name(source, i, key)?); + if i >= bytes.len() || bytes[i] != b']' { + return None; } + Some(i + 1) } -/// Resolver that handles relative path imports from eval'd CJS code. -/// When base is `` (from eval) and there's a CJS module context, -/// resolves relative paths against the module's directory. -struct CjsEvalResolver; +fn parse_dot_member_name(source: &str, pos: usize, name: &str) -> Option { + let bytes = source.as_bytes(); + let mut i = skip_ws_comments(source, pos); + if i >= bytes.len() || bytes[i] != b'.' { + return None; + } + i = skip_ws_comments(source, i + 1); + Some(skip_ws_comments(source, parse_ident_name(source, i, name)?)) +} -impl CjsEvalResolver { - fn normalize_path(path: &std::path::Path) -> String { - use std::path::Component; - let mut parts: Vec = Vec::new(); - let is_absolute = path.has_root(); +fn parse_direct_exports_reexport_assignment(source: &str, pos: usize, binding: &str, key: &str) -> Option { + let mut i = skip_ws_comments(source, parse_export_target_bracket_key(source, pos, key)?); + i = skip_ws_comments(source, parse_assignment_operator(source, i)?); + let after_rhs = skip_ws_comments(source, parse_binding_bracket_key(source, i, binding, key)?); + if is_statement_boundary(source, after_rhs) { + Some(after_rhs.min(source.len())) + } else { + None + } +} - for component in path.components() { - match component { - Component::RootDir | Component::Prefix(_) => {} - Component::CurDir => {} - Component::ParentDir => { - parts.pop(); +fn parse_define_property_reexport(source: &str, pos: usize, binding: &str, key: &str) -> Option { + let bytes = source.as_bytes(); + let mut i = parse_object_define_property_call(source, pos)?; + let (target, next) = parse_exports_target(source, i)?; + if target != CjsExportTarget::Exports { + return None; + } + i = skip_ws_comments(source, next); + if i >= bytes.len() || bytes[i] != b',' { + return None; + } + i = skip_ws_comments(source, i + 1); + let Some(key_end) = parse_free_ident_name(source, i, key) else { + return None; + }; + i = skip_ws_comments(source, key_end); + if i >= bytes.len() || bytes[i] != b',' { + return None; + } + let descriptor_start = i + 1; + let end = find_matching_paren(source, pos)?; + let descriptor = &source[descriptor_start..end]; + if descriptor_getter_returns_binding_key(descriptor, binding, &key) { + Some(end + 1) + } else { + None + } +} + +fn descriptor_getter_returns_binding_key(descriptor: &str, binding: &str, key: &str) -> bool { + let bytes = descriptor.as_bytes(); + let Some((mut cursor, descriptor_end)) = descriptor_object_span(descriptor) else { + return false; + }; + let mut seen_enumerable = false; + let mut found = false; + while cursor < descriptor_end { + if bytes[cursor] == b',' { + cursor = skip_ws_comments(descriptor, cursor + 1); + continue; + } + if is_spread_token_at(descriptor, cursor) || bytes[cursor] == b'[' { + return false; + } + let Some((name, key_is_ident, key_end)) = parse_exports_literal_key(descriptor, cursor) + else { + return false; + }; + if !key_is_ident { + return false; + } + let mut next = skip_ws_comments(descriptor, key_end); + if name == "enumerable" { + if seen_enumerable || found || next >= descriptor_end || bytes[next] != b':' { + return false; + } + let value_start = skip_ws_comments(descriptor, next + 1); + let Some(true_end) = parse_ident_name(descriptor, value_start, "true") else { + return false; + }; + seen_enumerable = true; + cursor = skip_ws_comments(descriptor, true_end); + } else if name == "get" { + if found { + return false; + } + if next < descriptor_end && bytes[next] == b'(' { + let Some((body_start, body_end)) = + getter_body_after_empty_params(descriptor, next, descriptor_end) + else { + return false; + }; + if !getter_body_returns_binding_key(&descriptor[body_start..body_end], binding, key) + { + return false; } - Component::Normal(part) => { - parts.push(part.to_string_lossy().into_owned()); + found = true; + cursor = skip_ws_comments(descriptor, body_end + 1); + } else if next < descriptor_end && bytes[next] == b':' { + next = skip_ws_comments(descriptor, next + 1); + let Some((body_start, body_end, function_end)) = + descriptor_function_getter_body(descriptor, next, descriptor_end) + else { + return false; + }; + if !getter_body_returns_binding_key(&descriptor[body_start..body_end], binding, key) + { + return false; } + found = true; + cursor = skip_ws_comments(descriptor, function_end); + } else { + return false; } - } - - if is_absolute { - format!("/{}", parts.join("/")) } else { - parts.join("/") + return false; } + + let Some(next_cursor) = next_object_literal_entry(descriptor, cursor, descriptor_end) else { + return false; + }; + cursor = next_cursor; } -} -impl Resolver for CjsEvalResolver { - fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> rquickjs::Result { - if base != "" { - return Err(Error::new_resolving(base, name)); - } + found && seen_enumerable +} - if !name.starts_with("./") && !name.starts_with("../") { - return Err(Error::new_resolving(base, name)); - } +fn getter_body_returns_binding_key(body: &str, binding: &str, key: &str) -> bool { + matches!( + parse_getter_return_member_body(body), + Some(GetterReturnMember::BracketIdentifier { receiver, member }) + if receiver == binding && member == key + ) +} - let globals = ctx.globals(); - let import_dir: Value = globals - .get("__wasm_rquickjs_cjs_import_dir") - .unwrap_or_else(|_| Value::new_undefined(ctx.clone())); +fn next_char_boundary(source: &str, pos: usize) -> usize { + if pos >= source.len() { + return source.len(); + } + pos + source[pos..].chars().next().map_or(1, char::len_utf8) +} - if import_dir.is_undefined() || import_dir.is_null() { - return Err(Error::new_resolving(base, name)); +fn previous_significant_byte(source: &str, pos: usize) -> Option { + let bytes = source.as_bytes(); + let mut i = pos; + while i > 0 { + i -= 1; + if !bytes[i].is_ascii_whitespace() { + return Some(bytes[i]); } + } + None +} - let dir_str: String = import_dir - .get::() - .map_err(|_| Error::new_resolving(base, name))?; +fn previous_significant_byte_before_import_meta(source: &str, pos: usize) -> Option { + let mut previous = None; + let _ = scan_code_positions(&source[..pos], true, |_, byte| { + if !byte.is_ascii_whitespace() { + previous = Some(byte); + } + ControlFlow::Continue(None) + }); + previous +} - let module_dir = std::path::Path::new(&dir_str); - let resolved = module_dir.join(name); - let normalized = Self::normalize_path(&resolved); +fn is_regex_literal_start(source: &str, pos: usize) -> bool { + if matches!( + previous_significant_byte(source, pos), + None | Some(b'(' | b'{' | b'[' | b'=' | b':' | b',' | b';' | b'!' | b'?' | b'&' | b'|' | b'+' | b'-' | b'*' | b'~' | b'^' | b'%' | b'>') + ) { + return true; + } - let candidates = [ - normalized.clone(), - format!("{}.js", normalized), - format!("{}.mjs", normalized), - ]; + let bytes = source.as_bytes(); + let mut end = pos; + while end > 0 && bytes[end - 1].is_ascii_whitespace() { + end -= 1; + } + let mut start = end; + while start > 0 && is_ident_continue(bytes[start - 1]) { + start -= 1; + } + matches!(&source[start..end], "return" | "throw" | "case" | "yield") +} - for candidate in &candidates { - if std::path::Path::new(candidate).is_file() { - return Ok(candidate.clone()); +fn skip_regex_literal(source: &str, pos: usize) -> usize { + let bytes = source.as_bytes(); + let mut i = pos + 1; + let mut in_class = false; + while i < bytes.len() { + match bytes[i] { + b'\\' => i += 2, + b'[' => { + in_class = true; + i += 1; + } + b']' => { + in_class = false; + i += 1; + } + b'/' if !in_class => { + i += 1; + while i < bytes.len() && bytes[i].is_ascii_alphabetic() { + i += 1; + } + return i; } + b'\n' | b'\r' => return pos + 1, + _ => i += 1, } - - Err(Error::new_resolving(base, name)) } + pos + 1 } -/// Resolver for filesystem-backed ES modules. -/// -/// QuickJS gives dynamic imports from CommonJS `eval()` a synthetic `` -/// base (handled by `CjsEvalResolver` above), but normal ESM resolution still -/// needs Node-style filesystem handling for absolute paths and paths relative -/// to the referrer module. `rquickjs::FileResolver` is kept as a fallback, but -/// it does not reliably accept already-absolute guest paths in this WASI setup. -struct NodeFileResolver; - -impl NodeFileResolver { - fn resolve_candidate(candidate: std::path::PathBuf) -> Option { - let normalized = CjsEvalResolver::normalize_path(&candidate); - if std::path::Path::new(&normalized).is_file() { - return Some(normalized); +fn skip_non_code(source: &str, pos: usize, skip_regex: bool) -> Option { + let bytes = source.as_bytes(); + match bytes.get(pos).copied()? { + b'\'' | b'"' | b'`' => Some(skip_string_or_template(source, pos)), + b'/' if pos + 1 < bytes.len() && bytes[pos + 1] == b'/' => { + let mut i = pos + 2; + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + Some(i) } - - if std::path::Path::new(&normalized).extension().is_none() { - for ext in ["js", "mjs", "json"] { - let with_ext = format!("{}.{}", normalized, ext); - if std::path::Path::new(&with_ext).is_file() { - return Some(with_ext); - } + b'/' if pos + 1 < bytes.len() && bytes[pos + 1] == b'*' => { + let mut i = pos + 2; + while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; } + Some((i + 2).min(bytes.len())) } - - None + b'/' if skip_regex && is_regex_literal_start(source, pos) => { + Some(skip_regex_literal(source, pos)) + } + _ => None, } } -impl Resolver for NodeFileResolver { - fn resolve<'js>( - &mut self, - _ctx: &Ctx<'js>, - base: &str, - name: &str, - ) -> rquickjs::Result { - if name.contains("://") || name.starts_with("node:") { - return Err(Error::new_resolving(base, name)); +fn scan_code_positions(source: &str, skip_regex: bool, mut visitor: F) -> ControlFlow<()> +where + F: FnMut(usize, u8) -> ControlFlow<(), Option>, +{ + let bytes = source.as_bytes(); + let mut i = 0usize; + while i < bytes.len() { + if let Some(next) = skip_non_code(source, i, skip_regex) { + i = next; + continue; } - let candidate = if name.starts_with('/') { - std::path::PathBuf::from(name) - } else if name.starts_with("./") || name.starts_with("../") { - let base_path = if let Some(path) = FileUrlResolver::file_url_to_path(base) { - path - } else { - base.to_string() - }; + match visitor(i, bytes[i]) { + ControlFlow::Break(()) => return ControlFlow::Break(()), + ControlFlow::Continue(Some(next)) => i = next, + ControlFlow::Continue(None) => i = next_char_boundary(source, i), + } + } + ControlFlow::Continue(()) +} - if base_path == "" { - return Err(Error::new_resolving(base, name)); - } +fn scan_code_positions_with_brace_depth( + source: &str, + skip_regex: bool, + mut visitor: F, +) -> ControlFlow<()> +where + F: FnMut(usize, u8, usize) -> ControlFlow<(), Option>, +{ + let bytes = source.as_bytes(); + let mut i = 0usize; + let mut brace_depth = 0usize; + while i < bytes.len() { + if let Some(next) = skip_non_code(source, i, skip_regex) { + i = next; + continue; + } - let base_dir = std::path::Path::new(&base_path) - .parent() - .ok_or_else(|| Error::new_resolving(base, name))?; - base_dir.join(name) - } else { - return Err(Error::new_resolving(base, name)); - }; + let current = bytes[i]; + match visitor(i, current, brace_depth) { + ControlFlow::Break(()) => return ControlFlow::Break(()), + ControlFlow::Continue(Some(next)) => i = next, + ControlFlow::Continue(None) => i = next_char_boundary(source, i), + } - Self::resolve_candidate(candidate).ok_or_else(|| Error::new_resolving(base, name)) + match current { + b'{' => brace_depth += 1, + b'}' => brace_depth = brace_depth.saturating_sub(1), + _ => {} + } } + ControlFlow::Continue(()) } -/// Resolver that provides Node.js-style error codes for failed module resolution. -/// This should be the LAST resolver in the chain, catching everything that -/// preceding resolvers couldn't handle. -struct NodeModuleErrorResolver; +fn statement_starts(source: &str) -> Vec { + let bytes = source.as_bytes(); + let mut starts = vec![false; bytes.len() + 1]; + let mut i = 0usize; + let mut brace_depth = 0usize; + let mut previous_code = None; + let mut line_terminator_since_code = false; + while i < bytes.len() { + if bytes[i].is_ascii_whitespace() { + if matches!(bytes[i], b'\n' | b'\r') { + line_terminator_since_code = true; + } + i += 1; + continue; + } + if let Some(next) = skip_non_code(source, i, true) { + if source[i..next].bytes().any(|byte| matches!(byte, b'\n' | b'\r')) { + line_terminator_since_code = true; + } + i = next; + continue; + } -impl Resolver for NodeModuleErrorResolver { - fn resolve<'js>( - &mut self, - ctx: &Ctx<'js>, - _base: &str, - name: &str, - ) -> rquickjs::Result { - let globals = ctx.globals(); + let current = bytes[i]; + if brace_depth == 0 + && (matches!(previous_code, None | Some(b';' | b'}')) + || (line_terminator_since_code + && !previous_code.is_some_and(is_asi_continuation_previous) + && !is_asi_continuation_next(source, i))) + { + starts[i] = true; + } - if name.starts_with("node:") { - let msg = format!("No such built-in module: {}", name); - let type_error_ctor: Function = globals.get("TypeError")?; - let error_obj: Object = type_error_ctor.call((&msg,))?; - error_obj.set("code", "ERR_UNKNOWN_BUILTIN_MODULE")?; - return Err(ctx.throw(error_obj.into_value())); + match current { + b'{' => brace_depth += 1, + b'}' => brace_depth = brace_depth.saturating_sub(1), + _ => {} } + previous_code = Some(current); + line_terminator_since_code = false; + i = next_char_boundary(source, i); + } + starts +} - if let Some(scheme_end) = name.find("://") { - let scheme = &name[..scheme_end]; - if scheme != "file" && scheme != "data" { - let msg = format!( - "Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol '{}:'", - scheme - ); - let error_ctor: Function = globals.get("Error")?; - let error_obj: Object = error_ctor.call((&msg,))?; - error_obj.set("code", "ERR_UNSUPPORTED_ESM_URL_SCHEME")?; - return Err(ctx.throw(error_obj.into_value())); +fn analyze_cjs_exports(source: &str) -> CjsExportAnalysis { + let mut analysis = CjsExportAnalysis::default(); + let mut require_bindings = HashMap::::new(); + let statement_starts = statement_starts(source); + let _ = scan_code_positions_with_brace_depth(source, true, |i, _, brace_depth| { + if let Some((name, next)) = parse_export_member(source, i) { + analysis.is_cjs = true; + add_unique(&mut analysis.exports, name); + return ControlFlow::Continue(Some(next)); + } + if let Some((name, next)) = parse_define_property_export(source, i) { + analysis.is_cjs = true; + add_unique(&mut analysis.exports, name); + return ControlFlow::Continue(Some(next)); + } + if brace_depth == 0 + && statement_starts.get(i).copied().unwrap_or(false) + && let Some((binding, specifier, next)) = parse_require_binding(source, i) + { + require_bindings.insert(binding, specifier); + return ControlFlow::Continue(Some(next)); + } + if brace_depth == 0 + && let Some((specifier, next)) = parse_export_star_reexport(source, i) + { + analysis.is_cjs = true; + add_unique(&mut analysis.reexports, specifier); + return ControlFlow::Continue(Some(next)); + } + if let Some((specifier, next)) = parse_module_exports_reexport(source, i) { + analysis.is_cjs = true; + analysis.reexports.clear(); + add_unique(&mut analysis.reexports, specifier); + return ControlFlow::Continue(Some(next)); + } + if let Some((exports, reexports, next)) = parse_module_exports_object_literal(source, i) { + analysis.is_cjs = true; + analysis.reexports.clear(); + for name in exports { + add_unique(&mut analysis.exports, name); } + for specifier in reexports { + add_unique(&mut analysis.reexports, specifier); + } + return ControlFlow::Continue(Some(next)); + } + if let Some(next) = parse_module_exports_assignment(source, i) { + analysis.is_cjs = true; + return ControlFlow::Continue(Some(next)); + } + if brace_depth == 0 + && statement_starts.get(i).copied().unwrap_or(false) + && let Some((specifier, next)) = parse_object_keys_reexport(source, i, &require_bindings) + { + analysis.is_cjs = true; + add_unique(&mut analysis.reexports, specifier); + return ControlFlow::Continue(Some(next)); } + ControlFlow::Continue(None) + }); + analysis +} - let msg = format!("Cannot find module '{}'", name); - let error_ctor: Function = globals.get("Error")?; - let error_obj: Object = error_ctor.call((&msg,))?; - error_obj.set("code", "ERR_MODULE_NOT_FOUND")?; - Err(ctx.throw(error_obj.into_value())) +fn resolve_cjs_reexport_path(filename: &str, specifier: &str, conditions: &[String]) -> Option { + if !specifier.starts_with("./") && !specifier.starts_with("../") && !specifier.starts_with('/') { + let resolver = NodeModulesResolver; + return resolver + .try_resolve_for_cjs_analysis(filename, specifier, conditions) + .ok() + .flatten(); } + let base = if specifier.starts_with('/') { + std::path::PathBuf::from(specifier) + } else { + std::path::Path::new(filename).parent()?.join(specifier) + }; + NodeModulesResolver::resolve_cjs_analysis_relative(&base) } -struct NodeModulesResolver; - -impl NodeModulesResolver { - fn try_resolve(&self, base: &str, name: &str) -> Option { - use std::path::{Path, PathBuf}; +fn is_cjs_analysis_source_path(path: &str) -> bool { + let extension = std::path::Path::new(path) + .extension() + .and_then(|extension| extension.to_str()); + !matches!(extension, Some("json" | "node")) +} - // Only handle bare specifiers (not relative, absolute, or URL) - if name.starts_with('.') || name.starts_with('/') || name.contains("://") { - return None; +fn analyze_cjs_exports_for_file( + filename: &str, + source: &str, + seen: &mut HashSet, + conditions: &[String], +) -> CjsExportAnalysis { + let mut analysis = analyze_cjs_exports(source); + if !seen.insert(filename.to_string()) { + return analysis; + } + let reexports = analysis.reexports.clone(); + for reexport in reexports { + if let Some(path) = resolve_cjs_reexport_path(filename, &reexport, conditions) + && !seen.contains(&path) + && is_cjs_analysis_source_path(&path) + && let Ok(source) = std::fs::read_to_string(&path) + { + let child = analyze_cjs_exports_for_file(&path, &source, seen, conditions); + for name in child.exports { + add_unique(&mut analysis.exports, name); + } } + } + analysis +} - // Extract directory from base module path - let base_dir = Path::new(base).parent()?; - - // Walk up directory tree looking for node_modules - let mut dir = base_dir.to_path_buf(); - loop { - let nm_dir = dir.join("node_modules").join(name); - if nm_dir.is_dir() { - // Try package.json main field - let pkg_path = nm_dir.join("package.json"); - if let Ok(pkg_content) = std::fs::read_to_string(&pkg_path) - && let Some(main) = Self::extract_json_string_field(&pkg_content, "main") - { - // Try the main entry with various extensions - let main_path = nm_dir.join(&main); - let candidates = [ - main_path.clone(), - main_path.with_extension("mjs"), - main_path.with_extension("js"), - main_path.join("index.mjs"), - main_path.join("index.js"), - ]; - for candidate in &candidates { - if candidate.is_file() { - return Some(candidate.to_string_lossy().into_owned()); - } - } - } +struct PackageScopeInfo { + package_type: Option, + is_node_modules_package: bool, +} - // Fallback: index.mjs, index.js - let fallbacks: [PathBuf; 2] = [nm_dir.join("index.mjs"), nm_dir.join("index.js")]; - for fallback in &fallbacks { - if fallback.is_file() { - return Some(fallback.to_string_lossy().into_owned()); - } - } - } +fn package_scope_type(filename: &str) -> Option { + package_scope_info(filename).and_then(|scope| scope.package_type) +} - if !dir.pop() { - break; - } +fn package_scope_info(filename: &str) -> Option { + let mut dir = std::path::Path::new(filename).parent()?.to_path_buf(); + loop { + if dir.file_name().is_some_and(|name| name == "node_modules") { + return None; + } + let pkg_path = dir.join("package.json"); + if let Ok(Some(package)) = NodeModulesResolver::read_package_json_optional(&pkg_path) + { + return Some(PackageScopeInfo { + package_type: package.package_type.clone(), + is_node_modules_package: is_node_modules_package_scope(&dir), + }); + } + if !dir.pop() { + break; } - - None } + None +} - /// Extract a simple string field value from a JSON object string. - fn extract_json_string_field(json: &str, field: &str) -> Option { - let pattern = format!("\"{}\"", field); - let idx = json.find(&pattern)?; - let after_key = &json[idx + pattern.len()..]; - let after_colon = after_key.trim_start(); - let after_colon = after_colon.strip_prefix(':')?; - let after_colon = after_colon.trim_start(); - let after_colon = after_colon.strip_prefix('"')?; - let end = after_colon.find('"')?; - Some(after_colon[..end].to_string()) +fn is_node_modules_package_scope(dir: &std::path::Path) -> bool { + let Some(parent) = dir.parent() else { + return false; + }; + if parent.file_name().is_some_and(|name| name == "node_modules") { + return true; } + parent + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.starts_with('@')) + && parent + .parent() + .and_then(|grandparent| grandparent.file_name()) + .is_some_and(|name| name == "node_modules") } -impl Resolver for NodeModulesResolver { - fn resolve<'js>( - &mut self, - _ctx: &Ctx<'js>, - base: &str, - name: &str, - ) -> rquickjs::Result { - self.try_resolve(base, name) - .ok_or_else(|| Error::new_resolving(base, name)) - } +fn is_js_in_module_package_scope(filename: &str) -> bool { + filename.ends_with(".js") && package_scope_type(filename).as_deref() == Some("module") } -/// Loader that wraps CJS `.js` and `.cjs` files in ESM-compatible wrappers when loaded via `import()`. -/// This enables ESM modules to import CJS packages from `node_modules`. -struct CjsCompatLoader; +fn cjs_named_export_source(names: &[String]) -> String { + let mut out = String::new(); + for (index, name) in names.iter().enumerate() { + if name == "default" { + continue; + } + let local = format!("__cjs_export_{}", index); + let escaped = escape_js_string(name); + out.push_str(&format!( + "var {local} = __cjs_default[\"{escaped}\"];\nexport {{ {local} as \"{escaped}\" }};\n" + )); + } + out +} impl Loader for CjsCompatLoader { fn load<'js>( @@ -1116,12 +6386,17 @@ impl Loader for CjsCompatLoader { ctx: &Ctx<'js>, path: &str, ) -> rquickjs::Result> { - let is_cjs_ext = path.ends_with(".cjs"); - if !path.ends_with(".js") && !is_cjs_ext { + let fs_path = module_filesystem_path(path); + let is_extensionless = std::path::Path::new(fs_path).extension().is_none(); + let is_cjs_ext = fs_path.ends_with(".cjs"); + if !fs_path.ends_with(".js") && !is_cjs_ext && !is_extensionless { return Err(Error::new_loading(path)); } + if import_attr_type_from_path(path).as_deref() == Some("json") { + return throw_import_attr_type_incompatible(ctx); + } - let source = match std::fs::read_to_string(path) { + let source = match std::fs::read_to_string(fs_path) { Ok(s) => s, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { let globals = ctx.globals(); @@ -1134,62 +6409,85 @@ impl Loader for CjsCompatLoader { Err(_) => return Err(Error::new_loading(path)), }; - let abs_path = ensure_absolute_path(path); - let std_path = std::path::Path::new(&abs_path); - let filename = Some(abs_path.clone()); - let dirname = std_path.parent().map(|p| p.to_string_lossy().into_owned()); + let fs_abs_path = ensure_absolute_path(fs_path); + let module_source = process_static_import_attrs(&source, path); + let filename = Some(fs_abs_path.clone()); let url = path_to_file_url(path); + let raw_cjs_global_messages = require_esm_in_progress(ctx, &fs_abs_path, &url); + let force_module = require_esm_forced_module(ctx, &fs_abs_path, &url); let init = ImportMetaInit { url, filename, - dirname, + dirname: std::path::Path::new(&fs_abs_path) + .parent() + .map(|p| p.to_string_lossy().into_owned()), include_resolve: true, }; - // .cjs files are always CommonJS; for .js files, detect CJS patterns + let package_scope = if fs_abs_path.ends_with(".js") || is_extensionless { + package_scope_info(&fs_abs_path) + } else { + None + }; + let package_type = package_scope + .as_ref() + .and_then(|scope| scope.package_type.clone()); + let is_module_package_js = package_type.as_deref() == Some("module"); + let is_commonjs_package_js = package_type.as_deref() == Some("commonjs") + || (package_type.is_none() + && package_scope + .as_ref() + .is_some_and(|scope| scope.is_node_modules_package)); + let has_esm_syntax = force_module + || source_has_static_import_or_export(&source) + || source_has_import_meta(&source) + || source_has_top_level_await(&source) + || has_cjs_wrapper_lexical_redeclaration(&source); + // .cjs files are always CommonJS; JS-like files outside a module package + // remain CommonJS unless syntax detection finds ESM. let is_cjs = is_cjs_ext - || source.contains("module.exports") - || source.contains("exports.") - || (source.contains("require(") && !source.contains("import ")); - + || is_commonjs_package_js + || (!is_module_package_js && !has_esm_syntax); if !is_cjs { + let package_type_module_js = fs_path.ends_with(".js") && is_module_package_js; + let preflight_error_source = if package_type_module_js { + esm_preflight_error_module_source(&source, true, raw_cjs_global_messages) + } else { + esm_require_global_preflight_error_module_source(&source, raw_cjs_global_messages) + }; + if let Some(error_source) = preflight_error_source { + return Module::declare(ctx.clone(), path, error_source.as_bytes().to_vec()); + } + if let Some(error_source) = cjs_named_import_error_module_source(ctx, &fs_abs_path, &source) { + return Module::declare(ctx.clone(), path, error_source.as_bytes().to_vec()); + } // Treat as ESM — inject import.meta prologue (handles shebangs) - let injected = inject_import_meta_prologue(&init, &source); + let injected = inject_import_meta_prologue(&init, &module_source); return Module::declare(ctx.clone(), path, injected.as_bytes().to_vec()); } - // Strip shebang before wrapping in IIFE (it would be invalid inside the wrapper) - let cjs_source = if let Some(rest) = source.strip_prefix("#!") { - if let Some(newline_pos) = rest.find('\n') { - // Replace shebang with a comment to preserve line numbers - format!( - "//{}{}", - &source[2..2 + newline_pos + 1], - &source[2 + newline_pos + 1..] - ) - } else { - String::new() - } - } else { - source - }; + let cjs_conditions = + NodeModulesResolver::conditions_from_global(ctx, NodePackageResolveMode::CjsAnalysis.default_conditions()); + let detected_analysis = + analyze_cjs_exports_for_file(&fs_abs_path, &source, &mut HashSet::new(), &cjs_conditions); + let named_exports = cjs_named_export_source(&detected_analysis.exports); - // Wrap CJS source in ESM-compatible wrapper, with import.meta prologue before the wrapper + // Let the existing CommonJS loader execute and cache the module. The + // facade only exposes the shared module.exports object to ESM. let prologue = inject_import_meta_prologue(&init, ""); let wrapped = format!( - r#"{} -var module = {{ exports: {{}} }}; -var exports = module.exports; -(function(module, exports) {{ + r#"import {{ createRequire as __wasm_rquickjs_createRequire }} from 'node:module'; {} -}})(module, exports); -var __cjs_default = module.exports; +var __wasm_rquickjs_require = __wasm_rquickjs_createRequire("{}"); +var __cjs_default = __wasm_rquickjs_require("{}"); export default __cjs_default; -export var __esModule = __cjs_default && __cjs_default.__esModule; +{} "#, prologue.trim(), - cjs_source + escape_js_string(&fs_abs_path), + escape_js_string(&fs_abs_path), + named_exports ); Module::declare(ctx.clone(), path, wrapped.as_bytes().to_vec()) @@ -1205,15 +6503,31 @@ struct ImportMetaInit { /// Ensure a path is absolute. If relative, prepend `/` (WASI cwd is `/`). fn ensure_absolute_path(path: &str) -> String { - if path.starts_with('/') { + let (path, suffix) = split_module_path_suffix(path); + let mut absolute = if path.starts_with('/') { path.to_string() } else { format!("/{}", path) - } + }; + absolute.push_str(suffix); + absolute } fn path_to_file_url(path: &str) -> String { - let abs_path = ensure_absolute_path(path); + let stripped_path = strip_loader_realm_param(path); + let abs_path = ensure_absolute_path(&stripped_path); + let (abs_path, suffix) = split_module_path_suffix(&abs_path); + let mut url = path_without_suffix_to_file_url(abs_path); + url.push_str(suffix); + url +} + +fn path_without_suffix_to_file_url(path: &str) -> String { + let abs_path = if path.starts_with('/') { + Cow::Borrowed(path) + } else { + Cow::Owned(format!("/{path}")) + }; let mut url = String::from("file://"); for byte in abs_path.as_bytes() { match byte { @@ -1238,6 +6552,206 @@ fn path_to_file_url(path: &str) -> String { url } +fn path_with_preserved_escapes_to_file_url(path: &str) -> String { + let abs_path = if path.starts_with('/') { + Cow::Borrowed(path) + } else { + Cow::Owned(format!("/{path}")) + }; + let mut url = String::from("file://"); + let bytes = abs_path.as_bytes(); + let mut i = 0; + while i < bytes.len() { + match bytes[i] { + b'%' if i + 2 < bytes.len() + && FileUrlResolver::hex_val(bytes[i + 1]).is_some() + && FileUrlResolver::hex_val(bytes[i + 2]).is_some() => + { + url.push('%'); + url.push(bytes[i + 1] as char); + url.push(bytes[i + 2] as char); + i += 3; + continue; + } + b'%' => url.push_str("%25"), + b' ' => url.push_str("%20"), + b'#' => url.push_str("%23"), + b'?' => url.push_str("%3F"), + b'A'..=b'Z' + | b'a'..=b'z' + | b'0'..=b'9' + | b'-' + | b'_' + | b'.' + | b'~' + | b'/' + | b':' => url.push(bytes[i] as char), + _ => { + url.push_str(&format!("%{:02X}", bytes[i])); + } + } + i += 1; + } + url +} + +fn normalize_encoded_module_path(path: &str) -> String { + let is_absolute = path.starts_with('/'); + let mut parts = Vec::new(); + + for segment in path.split('/') { + if segment.is_empty() || is_encoded_dot_segment(segment, ".") { + continue; + } + if is_encoded_dot_segment(segment, "..") { + parts.pop(); + } else { + parts.push(segment); + } + } + + if is_absolute { + format!("/{}", parts.join("/")) + } else { + parts.join("/") + } +} + +fn is_encoded_dot_segment(segment: &str, expected: &str) -> bool { + if segment == expected { + return true; + } + percent_decode(segment).is_some_and(|decoded| decoded == expected) +} + +fn serialize_url_preserving_escapes(input: &str) -> String { + let bytes = input.as_bytes(); + let mut encoded = String::with_capacity(input.len()); + let mut i = 0; + while i < bytes.len() { + match bytes[i] { + b'%' if i + 2 < bytes.len() + && FileUrlResolver::hex_val(bytes[i + 1]).is_some() + && FileUrlResolver::hex_val(bytes[i + 2]).is_some() => + { + encoded.push('%'); + encoded.push(bytes[i + 1] as char); + encoded.push(bytes[i + 2] as char); + i += 3; + continue; + } + b' ' => encoded.push_str("%20"), + 0x00..=0x20 | b'"' | b'<' | b'>' | b'`' => { + encoded.push_str(&format!("%{:02X}", bytes[i])); + } + _ if bytes[i] > 0x7F => { + encoded.push_str(&format!("%{:02X}", bytes[i])); + } + _ => encoded.push(bytes[i] as char), + } + i += 1; + } + encoded +} + +fn split_module_path_suffix(path: &str) -> (&str, &str) { + if path.starts_with("data:") { + return (path, ""); + } + let suffix_start = path.find(|ch| ch == '?' || ch == '#').unwrap_or(path.len()); + (&path[..suffix_start], &path[suffix_start..]) +} + +fn module_filesystem_path(path: &str) -> &str { + split_module_path_suffix(path).0 +} + +fn require_esm_in_progress(ctx: &Ctx<'_>, filename: &str, file_url: &str) -> bool { + let globals = ctx.globals(); + let Ok(registry) = globals.get::<_, Object>("__wasm_rquickjs_require_esm_in_progress") else { + return false; + }; + registry.get::<_, bool>(filename).unwrap_or(false) || registry.get::<_, bool>(file_url).unwrap_or(false) +} + +fn require_esm_forced_module(ctx: &Ctx<'_>, filename: &str, file_url: &str) -> bool { + let globals = ctx.globals(); + let Ok(registry) = globals.get::<_, Object>("__wasm_rquickjs_require_esm_forced_module") else { + return false; + }; + registry.get::<_, bool>(filename).unwrap_or(false) || registry.get::<_, bool>(file_url).unwrap_or(false) +} + +const LOADER_REALM_QUERY_PARAM: &str = "__wasm_rquickjs_loader_realm"; + +fn loader_realm_param(path_or_suffix: &str) -> Option { + let suffix = if path_or_suffix.starts_with('?') || path_or_suffix.starts_with('#') { + path_or_suffix + } else { + split_module_path_suffix(path_or_suffix).1 + }; + let query = suffix.strip_prefix('?')?; + let query = query.split_once('#').map_or(query, |(query, _)| query); + for part in query.split('&') { + if part + .split_once('=') + .is_some_and(|(key, _)| key == LOADER_REALM_QUERY_PARAM) + { + return Some(part.to_string()); + } + } + None +} + +fn append_loader_realm_param(suffix: &str, param: Option<&str>) -> String { + let Some(param) = param else { + return suffix.to_string(); + }; + if loader_realm_param(suffix).is_some() { + return suffix.to_string(); + } + let hash_start = suffix.find('#').unwrap_or(suffix.len()); + let (before_hash, hash) = suffix.split_at(hash_start); + let separator = if before_hash.contains('?') { '&' } else { '?' }; + format!("{before_hash}{separator}{param}{hash}") +} + +fn strip_loader_realm_param_from_suffix(suffix: &str) -> String { + let Some(query) = suffix.strip_prefix('?') else { + return suffix.to_string(); + }; + let (query, hash) = query + .split_once('#') + .map_or((query, ""), |(query, hash)| (query, hash)); + let kept: Vec<&str> = query + .split('&') + .filter(|part| { + !part + .split_once('=') + .is_some_and(|(key, _)| key == LOADER_REALM_QUERY_PARAM) + }) + .collect(); + let mut stripped = String::new(); + if !kept.is_empty() { + stripped.push('?'); + stripped.push_str(&kept.join("&")); + } + if !hash.is_empty() { + stripped.push('#'); + stripped.push_str(hash); + } else if suffix.contains('#') && suffix.ends_with('#') { + stripped.push('#'); + } + stripped +} + +fn strip_loader_realm_param(path: &str) -> String { + let (path, suffix) = split_module_path_suffix(path); + let mut stripped = path.to_string(); + stripped.push_str(&strip_loader_realm_param_from_suffix(suffix)); + stripped +} + fn escape_js_string(s: &str) -> String { let mut out = String::with_capacity(s.len()); for ch in s.chars() { @@ -1304,6 +6818,10 @@ fn source_has_top_level_await(source: &str) -> bool { i = (i + 2).min(bytes.len()); continue; } + if is_regex_literal_start(source, i) { + i = skip_regex_literal(source, i); + continue; + } } if b == b'\'' || b == b'"' || b == b'`' { @@ -1399,6 +6917,198 @@ fn source_has_top_level_await(source: &str) -> bool { false } +fn source_looks_like_esm(source: &str) -> bool { + source_has_top_level_await(source) || source_has_static_import_or_export(source) || source_has_import_meta(source) +} + +fn source_has_static_import_or_export(source: &str) -> bool { + scan_code_positions(source, true, |i, _| { + if parse_ident_name(source, i, "export").is_some() && is_static_export_syntax(source, i) { + return ControlFlow::Break(()); + } + if parse_ident_name(source, i, "import").is_some() && is_static_import_syntax(source, i) { + return ControlFlow::Break(()); + } + ControlFlow::Continue(None) + }) + .is_break() + || source_has_line_start_static_import_or_export(source) +} + +fn source_has_line_start_static_import_or_export(source: &str) -> bool { + let bytes = source.as_bytes(); + let mut i = 0usize; + let mut at_line_start = true; + let mut in_block_comment = false; + let mut in_string: Option = None; + + while i < bytes.len() { + if in_block_comment { + if i + 1 < bytes.len() && bytes[i] == b'*' && bytes[i + 1] == b'/' { + in_block_comment = false; + i += 2; + } else { + at_line_start = matches!(bytes[i], b'\n' | b'\r'); + i += 1; + } + continue; + } + + if let Some(quote) = in_string { + match bytes[i] { + b'\\' => i = (i + 2).min(bytes.len()), + b if b == quote => { + in_string = None; + at_line_start = false; + i += 1; + } + b'\n' | b'\r' => { + at_line_start = true; + i += 1; + } + _ => i += 1, + } + continue; + } + + if at_line_start { + while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | 0x0b | 0x0c) { + i += 1; + } + if i >= bytes.len() { + break; + } + if matches!(bytes[i], b'\n' | b'\r') { + i += 1; + at_line_start = true; + continue; + } + if i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'/' { + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + at_line_start = true; + continue; + } + if i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'*' { + in_block_comment = true; + i += 2; + continue; + } + let line = &source[i..]; + if line_starts_with_static_export(line) || line_starts_with_static_import(line) { + return true; + } + at_line_start = false; + } + + match bytes[i] { + b'\'' | b'"' | b'`' => { + in_string = Some(bytes[i]); + i += 1; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'/' => { + while i < bytes.len() && !matches!(bytes[i], b'\n' | b'\r') { + i += 1; + } + at_line_start = true; + } + b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => { + in_block_comment = true; + i += 2; + } + b'\n' | b'\r' => { + at_line_start = true; + i += 1; + } + _ => i += 1, + } + } + false +} + +fn line_starts_with_static_export(line: &str) -> bool { + let Some(rest) = line.strip_prefix("export") else { + return false; + }; + if rest.chars().next().is_some_and(|ch| is_ident_continue(ch as u8)) { + return false; + } + let rest = rest.trim_start(); + rest.starts_with('{') + || rest.starts_with('*') + || ["default", "const", "let", "var", "function", "class"] + .iter() + .any(|keyword| { + rest.starts_with(keyword) + && rest[keyword.len()..] + .chars() + .next() + .is_none_or(|ch| !is_ident_continue(ch as u8)) + }) +} + +fn line_starts_with_static_import(line: &str) -> bool { + let Some(rest) = line.strip_prefix("import") else { + return false; + }; + if rest.chars().next().is_some_and(|ch| is_ident_continue(ch as u8)) { + return false; + } + let rest = rest.trim_start(); + if rest.starts_with('(') || rest.starts_with(':') { + return false; + } + rest.starts_with('"') || rest.starts_with('\'') || rest.starts_with('{') || rest.starts_with('*') || rest.chars().next().is_some_and(|ch| is_js_identifier_start(ch as u8)) +} + +fn source_has_import_meta(source: &str) -> bool { + scan_code_positions(source, true, |i, _| { + if parse_import_meta(source, i).is_some() + && !is_static_import_syntax(source, i) + && previous_significant_byte(source, i) != Some(b'.') + { + return ControlFlow::Break(()); + } + ControlFlow::Continue(None) + }) + .is_break() +} + +fn is_static_export_syntax(source: &str, pos: usize) -> bool { + if previous_significant_byte(source, pos) == Some(b'.') { + return false; + } + let next = skip_ws_comments(source, pos + "export".len()); + if source.as_bytes().get(next) == Some(&b':') { + return false; + } + match source.as_bytes().get(next).copied() { + Some(b'{' | b'*') => true, + _ => ["default", "const", "let", "var", "function", "class"] + .iter() + .any(|keyword| parse_ident_name(source, next, keyword).is_some()), + } +} + +fn is_static_import_syntax(source: &str, pos: usize) -> bool { + if previous_significant_byte(source, pos) == Some(b'.') { + return false; + } + let next = skip_ws_comments(source, pos + "import".len()); + if matches!(source.as_bytes().get(next), Some(b'(' | b':')) { + return false; + } + matches!( + source.as_bytes().get(next).copied(), + Some(b'\'' | b'"' | b'{' | b'*') + ) || source + .as_bytes() + .get(next) + .copied() + .is_some_and(is_js_identifier_start) +} + fn is_js_identifier_start(byte: u8) -> bool { byte == b'_' || byte == b'$' || byte.is_ascii_alphabetic() } @@ -1426,7 +7136,7 @@ fn inject_import_meta_prologue(init: &ImportMetaInit, source: &str) -> String { if init.include_resolve { props.push(format!( - "resolve:{{value:(s)=>globalThis.__wasm_rquickjs_import_meta_resolve(\"{}\",s),writable:true,enumerable:true,configurable:true}}", + "resolve:{{value:(s,p)=>{{if(p!==undefined){{if(typeof p==='string'){{return globalThis.__wasm_rquickjs_import_meta_resolve(p,s);}}if(p instanceof URL){{return globalThis.__wasm_rquickjs_import_meta_resolve(p.href,s);}}const e=new TypeError('The \"parentURL\" argument must be of type string or an instance of URL.');e.code='ERR_INVALID_ARG_TYPE';throw e;}}return globalThis.__wasm_rquickjs_import_meta_resolve(\"{}\",s);}},writable:true,enumerable:true,configurable:true}}", escape_js_string(&init.url) )); } @@ -1436,22 +7146,35 @@ fn inject_import_meta_prologue(init: &ImportMetaInit, source: &str) -> String { escape_js_string(&init.url) )); - // Define import.meta properties and also shim __filename/__dirname as - // top-level variables. Many libraries (especially Rollup-bundled CJS→ESM) - // reference bare __dirname/__filename which don't exist in ESM scope. let mut prologue = format!( "Object.defineProperties(import.meta,{{{}}});", props.join(",") ); - if let Some(ref filename) = init.filename { - prologue.push_str(&format!( - "var __filename=\"{}\";", - escape_js_string(filename) - )); - } - if let Some(ref dirname) = init.dirname { - prologue.push_str(&format!("var __dirname=\"{}\";", escape_js_string(dirname))); + prologue.push_str( + r##"if(!globalThis.__wasm_rquickjs_import_attr_specifier){globalThis.__wasm_rquickjs_import_attr_specifier=(s,t)=>{let v=String(s);let f=null;if(v.startsWith("data:")){const r=v.slice(5);const c=r.indexOf(",");const m=(c<0?r:r.slice(0,c)).split(";")[0].trim();if(m==="application/json")f="json";else if(m==="text/javascript"||m==="application/javascript")f="module";else if(m==="text/css")f="css";}else if(v.startsWith("node:"))f="module";else{const b=v.split(/[?#]/,1)[0];if(b.endsWith(".json"))f="json";else if(b.endsWith(".js")||b.endsWith(".mjs")||b.endsWith(".cjs"))f="module";}function er(c,m){return"data:text/javascript,"+encodeURIComponent(`await Promise.reject(Object.assign(new TypeError(${JSON.stringify(m)}),{code:${JSON.stringify(c)}}));`)}if(t&&t!=="json"&&t!=="css")return er("ERR_IMPORT_ATTRIBUTE_UNSUPPORTED",`Import attribute type "${t}" is not supported`);if(t==="json"&&f==="module")return er("ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE","Cannot use import attributes to change the type of a JavaScript module");if(f==="json"&&t!=="json")return er("ERR_IMPORT_ATTRIBUTE_MISSING",`Module "${v}" needs an import attribute of type: json`);if(t==="json"){if(v.startsWith("data:"))v=v.replace(/\"/g,"%22");return"data:text/javascript,"+encodeURIComponent("import value from "+JSON.stringify(v)+" with { type: \"json\" }; export default value;");}return v;};}"##, + ); + let declared_cjs_globals = collect_declared_cjs_globals_in_esm(source); + let shadowed_cjs_globals: Vec<&str> = ["require"] + .iter() + .copied() + .filter(|name| !declared_cjs_globals.iter().any(|declared| declared == name)) + .collect(); + if !shadowed_cjs_globals.is_empty() { + prologue.push_str("var "); + prologue.push_str(&shadowed_cjs_globals.join(",")); + prologue.push(';'); } + let main_expr = init + .filename + .as_ref() + .map(|filename| { + format!( + "!!(globalThis.process&&Array.isArray(globalThis.process.argv)&&globalThis.process.argv[1]===\"{}\")", + escape_js_string(filename) + ) + }) + .unwrap_or_else(|| "false".to_string()); + let source = rewrite_import_meta_main(source, &main_expr); if let Some(rest) = source.strip_prefix("#!") { if let Some(newline_pos) = rest.find('\n') { @@ -1459,12 +7182,56 @@ fn inject_import_meta_prologue(init: &ImportMetaInit, source: &str) -> String { let remaining = &source[2 + newline_pos + 1..]; format!("{}{}\n{}", shebang_line, prologue, remaining) } else { - // Shebang with no newline — entire file is the shebang - format!("{}\n{}", source, prologue) + // Shebang with no newline — entire file is the shebang + format!("{}\n{}", source, prologue) + } + } else { + format!("{}\n{}", prologue, source) + } +} + +fn rewrite_import_meta_main(source: &str, replacement: &str) -> String { + let mut spans = Vec::new(); + let _ = scan_code_positions(source, true, |i, _| { + if let Some(end) = parse_import_meta_main_span(source, i) { + spans.push((i, end)); + ControlFlow::Continue(Some(end)) + } else { + ControlFlow::Continue(None) } - } else { - format!("{}\n{}", prologue, source) + }); + + if spans.is_empty() { + return source.to_string(); + } + + let mut rewritten = source.to_string(); + for (start, end) in spans.into_iter().rev() { + rewritten.replace_range(start..end, replacement); + } + rewritten +} + +fn parse_import_meta_main_span(source: &str, pos: usize) -> Option { + let mut i = parse_ident_name(source, pos, "import")?; + if matches!( + previous_significant_byte_before_import_meta(source, pos), + Some(b'.' | b'#') + ) { + return None; + } + i = skip_ws_comments(source, i); + if source.as_bytes().get(i) != Some(&b'.') { + return None; + } + i = skip_ws_comments(source, i + 1); + i = parse_ident_name(source, i, "meta")?; + i = skip_ws_comments(source, i); + if source.as_bytes().get(i) != Some(&b'.') { + return None; } + i = skip_ws_comments(source, i + 1); + parse_ident_name(source, i, "main") } struct ImportMetaLoader; @@ -1475,11 +7242,28 @@ impl Loader for ImportMetaLoader { ctx: &Ctx<'js>, path: &str, ) -> rquickjs::Result> { - if !path.ends_with(".mjs") { - return Err(Error::new_loading(path)); + let fs_path = module_filesystem_path(path); + let is_extensionless = std::path::Path::new(fs_path).extension().is_none(); + if !fs_path.ends_with(".mjs") && !is_extensionless { + let ext = std::path::Path::new(fs_path) + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| format!(".{}", ext)) + .unwrap_or_default(); + let globals = ctx.globals(); + let type_error_ctor: Function = globals.get("TypeError")?; + let error_obj: Object = type_error_ctor.call((format!( + "Unknown file extension {:?} for {}", + ext, fs_path + ),))?; + error_obj.set("code", "ERR_UNKNOWN_FILE_EXTENSION")?; + return Err(ctx.throw(error_obj.into_value())); + } + if import_attr_type_from_path(path).as_deref() == Some("json") { + return throw_import_attr_type_incompatible(ctx); } - let source = match std::fs::read_to_string(path) { + let mut source = match std::fs::read_to_string(fs_path) { Ok(s) => s, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { let globals = ctx.globals(); @@ -1492,11 +7276,14 @@ impl Loader for ImportMetaLoader { Err(_) => return Err(Error::new_loading(path)), }; - let abs_path = ensure_absolute_path(path); - let std_path = std::path::Path::new(&abs_path); - let filename = Some(abs_path.clone()); + let fs_abs_path = ensure_absolute_path(fs_path); + let module_abs_path = ensure_absolute_path(path); + source = process_static_import_attrs(&source, path); + let std_path = std::path::Path::new(&fs_abs_path); + let filename = Some(fs_abs_path.clone()); let dirname = std_path.parent().map(|p| p.to_string_lossy().into_owned()); let url = path_to_file_url(path); + let raw_cjs_global_messages = require_esm_in_progress(ctx, &fs_abs_path, &url); let init = ImportMetaInit { url, @@ -1517,9 +7304,18 @@ impl Loader for ImportMetaLoader { return Err(ctx.throw(cached_error)); } + if let Some(error_source) = + esm_require_global_preflight_error_module_source(&source, raw_cjs_global_messages) + { + return Module::declare(ctx.clone(), path, error_source.as_bytes().to_vec()); + } + if let Some(error_source) = cjs_named_import_error_module_source(ctx, &fs_abs_path, &source) { + return Module::declare(ctx.clone(), path, error_source.as_bytes().to_vec()); + } + let mut injected = inject_import_meta_prologue(&init, &source); if source_has_top_level_await(&source) { - let escaped_path = escape_js_string(&abs_path); + let escaped_path = escape_js_string(&module_abs_path); let escaped_url = escape_js_string(&init.url); let marker = format!( "globalThis.__wasm_rquickjs_async_esm_modules=globalThis.__wasm_rquickjs_async_esm_modules||Object.create(null);globalThis.__wasm_rquickjs_async_esm_modules[\"{}\"]=true;globalThis.__wasm_rquickjs_async_esm_modules[\"{}\"]=true;\n", @@ -1563,14 +7359,36 @@ impl Loader for JsonFileLoader { ctx: &Ctx<'js>, path: &str, ) -> rquickjs::Result> { - if !path.ends_with(".json") { + let fs_path = module_filesystem_path(path); + if !fs_path.ends_with(".json") { return Err(Error::new_loading(path)); } - let source = std::fs::read_to_string(path).map_err(|_| Error::new_loading(path))?; - let module_source = if DataUrlLoader::is_valid_json(&source) { + let import_attr_type = import_attr_type_from_path(path); + let source = std::fs::read_to_string(fs_path).map_err(|_| Error::new_loading(path))?; + let module_source = if import_attr_type.as_deref() != Some("json") { + let escaped = DataUrlLoader::js_string_escape(path); + format!( + "await Promise.reject(Object.assign(new TypeError('Module \"{escaped}\" needs an import attribute of type: json'), {{code: 'ERR_IMPORT_ATTRIBUTE_MISSING'}}));\n" + ) + } else if DataUrlLoader::is_valid_json(&source) { let escaped = DataUrlLoader::js_string_escape(&source); - format!("export default JSON.parse('{escaped}');\n") + let original_path = strip_import_type_rewrite_token(path); + if split_module_path_suffix(&original_path).1.is_empty() { + format!( + "const __wasm_rquickjs_require = globalThis.__wasm_rquickjs_create_require(\"{}\");\nconst __wasm_rquickjs_filename = \"{}\";\nconst __wasm_rquickjs_cached = __wasm_rquickjs_require.cache[__wasm_rquickjs_filename];\nconst __wasm_rquickjs_value = __wasm_rquickjs_cached ? __wasm_rquickjs_cached.exports : JSON.parse('{escaped}');\nif (!__wasm_rquickjs_cached) __wasm_rquickjs_require.cache[__wasm_rquickjs_filename] = {{ id: __wasm_rquickjs_filename, filename: __wasm_rquickjs_filename, path: \"{}\", exports: __wasm_rquickjs_value, loaded: true, parent: null, children: [], paths: [] }};\nexport default __wasm_rquickjs_value;\n", + escape_js_string(fs_path), + escape_js_string(fs_path), + escape_js_string( + std::path::Path::new(fs_path) + .parent() + .and_then(|path| path.to_str()) + .unwrap_or("/") + ) + ) + } else { + format!("export default JSON.parse('{escaped}');\n") + } } else { DataUrlLoader::make_json_error_module(&source) }; @@ -1591,6 +7409,7 @@ pub struct JsState { pub abort_handles: RefCell>, pub last_abort_id: AtomicUsize, pub unrefed_timers: RefCell>, + pub node_package_deprecation_warnings: RefCell>, pub gc_pending: std::sync::atomic::AtomicBool, } @@ -1649,6 +7468,10 @@ impl JsState { MockModuleResolver, DataUrlResolver, FileUrlResolver, + PrivateBuiltinResolverGuard, + RegisteredLoaderResolver, + ), + ( builtin_resolver, NodeModulesResolver, NodeFileResolver, @@ -1706,6 +7529,28 @@ impl JsState { global.set("__wasm_rquickjs_mock_seq", 0i64) .expect("Failed to initialize mock sequence counter"); + + global.set( + "__wasm_rquickjs_register_import_attr_rewrite", + Function::new(ctx.clone(), |specifier: String, import_type: String| { + if import_type == "json" { + append_import_type_query(&specifier, &import_type) + } else { + specifier + } + }) + .expect("Failed to create import attribute rewrite registrar"), + ) + .expect("Failed to initialize import attribute rewrite registrar"); + + global.set( + "__wasm_rquickjs_discard_import_attr_rewrite", + Function::new(ctx.clone(), |specifier: String| { + discard_generated_import_type_rewrite_token(&specifier); + }) + .expect("Failed to create import attribute rewrite discard"), + ) + .expect("Failed to initialize import attribute rewrite discard"); }) .await; @@ -1733,6 +7578,7 @@ impl JsState { abort_handles: RefCell::new(HashMap::new()), last_abort_id: AtomicUsize::new(0), unrefed_timers: RefCell::new(HashSet::new()), + node_package_deprecation_warnings: RefCell::new(HashSet::new()), gc_pending: std::sync::atomic::AtomicBool::new(false), } } @@ -2560,6 +8406,1358 @@ pub fn format_caught_error(caught: CaughtError) -> String { } } +#[cfg(test)] +mod cjs_export_analyzer_tests { + use super::*; + + #[test] + fn data_url_separator_uses_first_comma() { + assert_eq!( + DataUrlLoader::content_separator_pos(r#"application/json;foo="test,""this""#), + Some(r#"application/json;foo="test"#.len()) + ); + assert_eq!( + DataUrlLoader::content_separator_pos(r#"application/json;foo="test\,",0"#), + Some(r#"application/json;foo="test\"#.len()) + ); + assert_eq!( + DataUrlLoader::content_separator_pos("application/json;foo=test%2C,0"), + Some("application/json;foo=test%2C".len()) + ); + let rewritten = append_import_type_query(r#"data:application/json;foo="test,""this""#, "json"); + assert!(rewritten.starts_with(r#"data:application/json;foo="test;__wasm_rquickjs_import_type=json-"#)); + assert!(rewritten.ends_with(r#",""this""#)); + assert_eq!( + import_attr_type_from_path(r#"data:application/json;__wasm_rquickjs_import_type=json,0"#), + None + ); + assert_eq!(import_attr_type_from_path(&rewritten), Some("json".to_string())); + assert_eq!( + split_module_path_suffix(r#"data:application/json,"?__wasm_rquickjs_import_type=json""#), + ( + r#"data:application/json,"?__wasm_rquickjs_import_type=json""#, + "" + ) + ); + assert_eq!( + split_module_path_suffix(r#"data:text/javascript,var x = "hello world?""#), + (r#"data:text/javascript,var x = "hello world?""#, "") + ); + + let relative_rewritten = append_import_type_query("./test.json", "json"); + let (_, suffix) = split_module_path_suffix(&relative_rewritten); + let resolved_rewritten = format!("/app/test.json{suffix}"); + assert_eq!(import_attr_type_from_path(&resolved_rewritten), None); + + let relative_rewritten = append_import_type_query("./test.json", "json"); + let (_, suffix) = split_module_path_suffix(&relative_rewritten); + let resolved_rewritten = format!("/app/test.json{suffix}"); + transfer_import_type_rewrite_token(&relative_rewritten, &resolved_rewritten); + assert_eq!( + import_attr_type_from_path(&resolved_rewritten), + Some("json".to_string()) + ); + } + + #[test] + fn dynamic_import_rewrite_handles_array_commas() { + let source = r#" + await Promise.all([ + import("./plain.json"), + import("./typed.json", { with: { type: "json" } }), + ]); + "#; + let rewritten = process_static_import_attrs(source, "/app/main.mjs"); + + assert!(rewritten.contains("__wasm_rquickjs_import_attr_dynamic_import")); + assert!(rewritten.contains(r#"./typed.json", { with: { type: "json" } }"#)); + assert!(!rewritten.contains(r#"import("./typed.json","#)); + } + + #[test] + fn dynamic_import_rewrite_preserves_object_method_shorthand() { + let source = r#" + const obj = { + import(value) { return ["method", value]; }, + async importAsync(value) { return value; }, + *importGenerator(value) { yield value; }, + get importGetter() { return "getter"; }, + set importSetter(value) { this.value = value; }, + }; + const asyncObj = { async import(value) { return value; } }; + const generatorObj = { *import(value) { yield value; } }; + const asyncGeneratorObj = { async * import(value) { yield value; } }; + const getterObj = { get import() { return "getter"; } }; + const setterObj = { set import(value) { this.value = value; } }; + class ImportMethods { + import(value) { return value; } + static import(value) { return value; } + static get importGetterStatic() { return "getter"; } + async importAsync(value) { return value; } + *importGenerator(value) { yield value; } + async * importAsyncGenerator(value) { yield value; } + get importGetter() { return "getter"; } + set importSetter(value) { this.value = value; } + } + class AsyncImportMethod { async import(value) { return value; } } + class GeneratorImportMethod { *import(value) { yield value; } } + class StaticImportMethod { static import(value) { return value; } } + class StaticGetterImportMethod { static get import() { return "getter"; } } + class AsyncGeneratorImportMethod { async * import(value) { yield value; } } + class GetterImportMethod { get import() { return "getter"; } } + class SetterImportMethod { set import(value) { this.value = value; } } + obj.import("value"); + "#; + + assert_eq!(process_static_import_attrs(source, "/app/main.mjs"), source); + } + + fn assert_analysis( + source: &str, + is_cjs: bool, + exports: &[&str], + reexports: &[&str], + ) { + let analysis = analyze_cjs_exports(source); + assert_eq!(analysis.is_cjs, is_cjs, "is_cjs mismatch for {source}"); + assert_eq!(analysis.exports, exports, "exports mismatch for {source}"); + assert_eq!( + analysis.reexports, reexports, + "reexports mismatch for {source}" + ); + } + + fn assert_cjs_global(source: &str, expected: Option<&str>) { + assert_eq!( + find_bare_cjs_global_in_esm(source), + expected, + "CJS global detection mismatch for {source}" + ); + } + + #[test] + fn detects_supported_cjs_export_patterns() { + assert_analysis( + r#" + exports.foo = 1; + module.exports.bar = 2; + exports["baz"] = 3; + Object.defineProperty(exports, "valueExport", { value: 4 }); + Object.defineProperty(module.exports, "getterExport", { get() { return dep.value; } }); + Object.defineProperty(exports, "functionGetter", { get: function () { return dep["other"]; } }); + Object.defineProperty(exports, "valueThenValue", { value: "first", value: "second" }); + Object.defineProperty(exports, "valueThenString", { value: "good", "value": "string-wins" }); + Object.defineProperty(exports, "valueThenComputed", { value: "good", ["value"]: "computed-wins" }); + Object.defineProperty(exports, "valueThenShorthand", { value: "first", value }); + Object.defineProperty(exports, "valueThenMethod", { value: "first", value() { return "method-value"; } }); + Object.defineProperty(exports, "valueThenFalseEnumerable", { value: dep.value, enumerable: false }); + if (false) Object.defineProperty(exports, "objectMemberDescriptor", { value: "bad" }.descriptor); + if (false) Object.defineProperty(exports, "objectPlusDescriptor", { value: "bad" } + suffix); + "#, + true, + &[ + "foo", + "bar", + "baz", + "valueExport", + "getterExport", + "functionGetter", + "valueThenValue", + "valueThenString", + "valueThenComputed", + "valueThenShorthand", + "valueThenMethod", + "valueThenFalseEnumerable", + "objectMemberDescriptor", + "objectPlusDescriptor", + ], + &[], + ); + } + + #[test] + fn rejects_unsupported_cjs_define_property_descriptors() { + assert_analysis( + r#" + const dep = { value: "getter-value" }; + const value = "shorthand-value"; + Object.defineProperty(exports, "arrowGetter", { get: () => dep.value }); + Object.defineProperty(exports, "stringKeyGetter", { "get": function () { return dep.value; } }); + Object.defineProperty(exports, "stringKeyValue", { "value": "string-key-value" }); + Object.defineProperty(exports, "shorthandValue", { value }); + Object.defineProperty(exports, "computedValue", { ["value"]: "computed-value" }); + Object.defineProperty(exports, "multiStatementGetter", { get() { const v = dep.value; return v; } }); + Object.defineProperty(exports, "helperValueDescriptor", makeDescriptor({ value: dep.value })); + Object.defineProperty(exports, "parameterGetter", { get(a) { return dep.value; } }); + Object.defineProperty(exports, "parameterFunctionGetter", { get: function (a) { return dep.value; } }); + Object.defineProperty(exports, "helperDescriptor", makeDescriptor({ get() { return dep.value; } })); + Object.defineProperty(exports, "nestedMemberGetter", { get() { return dep.value.nested; } }); + Object.defineProperty(exports, "nestedBracketGetter", { get() { return dep["value"]["nested"]; } }); + Object.defineProperty(exports, "duplicateGet", { get() { return dep.value; }, get: function (a) { return dep.value; } }); + Object.defineProperty(exports, "stringThenValue", { "value": "bad", value: dep.value }); + Object.defineProperty(exports, "computedThenValue", { ["value"]: "bad", value: dep.value }); + Object.defineProperty(exports, "writableThenValue", { writable: true, value: dep.value }); + Object.defineProperty(exports, "configurableThenValue", { configurable: true, value: dep.value }); + Object.defineProperty(exports, "quotedEnumerableThenValue", { "enumerable": true, value: dep.value }); + "#, + false, + &[], + &[], + ); + } + + #[test] + fn malformed_non_ascii_escapes_do_not_panic() { + assert_analysis(r#"exports["\xaé"] = 1;"#, false, &[], &[]); + assert_analysis(r#"exports["\uabcé"] = 1;"#, false, &[], &[]); + } + + #[test] + fn detects_module_exports_assignments_with_comments() { + assert_analysis(r#"module /*x*/ . /*y*/ exports = {};"#, true, &[], &[]); + assert_analysis( + r#"module /*x*/ . /*y*/ exports = require("./dep.cjs");"#, + true, + &[], + &["./dep.cjs"], + ); + assert_analysis( + r#"module.exports = require("./dep.cjs").nested;"#, + true, + &[], + &["./dep.cjs"], + ); + assert_analysis( + r#"module.exports = require("./dep.cjs")();"#, + true, + &[], + &["./dep.cjs"], + ); + assert_analysis( + r#" + var dep = require("./dep.cjs").nested; + Object.keys(dep).forEach(function (key) { + Object.defineProperty(exports, key, { get: function () { return dep[key]; } }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + } + + #[test] + fn cjs_statement_start_asi_continuation_edges() { + assert!(is_asi_continuation_previous(b'!')); + assert!(is_asi_continuation_previous(b'~')); + assert!(!is_asi_continuation_next("!", 0)); + assert!(!is_asi_continuation_next("~", 0)); + assert!(!is_asi_continuation_next("++x", 0)); + assert!(is_asi_continuation_next("+x", 0)); + + let continued = "const dep = require('./dep')\n(exports.x = dep.x);"; + let continued_starts = statement_starts(continued); + let continued_export = continued.find("exports").unwrap(); + assert!(!continued_starts[continued_export]); + + let separated = "const dep = require('./dep')\nexports.x = dep.x;"; + let separated_starts = statement_starts(separated); + let separated_export = separated.find("exports").unwrap(); + assert!(separated_starts[separated_export]); + } + + #[test] + fn detects_module_exports_object_literal_names_and_spread_reexports() { + assert_analysis( + r#" + const a = 1; + const c = 2; + const e = 4; + module.exports = { a, b: c, "d": e, ...require("./dep.cjs") }; + "#, + true, + &["a", "b", "d"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + const a = 1; + module.exports = { a, dynamic: factory() }; + "#, + true, + &["a", "dynamic"], + &[], + ); + + assert_analysis( + r#" + const a = 1; + module.exports = { a, b: require("./dep.cjs"), c: "not-detected" }; + "#, + true, + &["a", "b"], + &[], + ); + + assert_analysis( + r#" + module.exports = { booleanLiteral: true, nullLiteral: null, undefinedLiteral: undefined }; + "#, + true, + &["booleanLiteral", "nullLiteral", "undefinedLiteral"], + &[], + ); + + assert_analysis( + r#" + module.exports = { identifierValue: value, memberExpression: ns.x, callExpression: factory() }; + "#, + true, + &["identifierValue", "memberExpression"], + &[], + ); + + assert_analysis( + r#" + module.exports = { nestedMemberExpression: ns.x.y, after: value }; + "#, + true, + &["nestedMemberExpression"], + &[], + ); + + assert_analysis( + r#" + module.exports = { bracketMemberExpression: ns["x"], after: value }; + "#, + true, + &["bracketMemberExpression"], + &[], + ); + + assert_analysis( + r#" + module.exports = { binaryExpression: value + 1, after: value }; + "#, + true, + &["binaryExpression"], + &[], + ); + + assert_analysis( + r#" + module.exports = { + stringLiteral: "not-detected", + numberLiteral: 1, + objectLiteral: {}, + callExpression: factory(), + identifierValue: value, + }; + "#, + true, + &[], + &[], + ); + + assert_analysis( + r#" + const a = 1; + const c = 3; + module.exports = { a, ...require("./dep.cjs"), c }; + "#, + true, + &["a", "c"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + const a = 1; + const c = 3; + module.exports = { a, ...require("./dep.cjs").nested, c }; + "#, + true, + &["a"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + const a = 1; + const b = 2; + const other = {}; + module.exports = { a, ...other, b }; + "#, + true, + &["a", "b"], + &[], + ); + + assert_analysis( + r#" + module.exports = { a, ...other(), b }; + module.exports = { c, ...(other), d }; + module.exports = { e, ...ns.other, f }; + "#, + true, + &["a", "c", "e"], + &[], + ); + + assert_analysis( + r#" + const a = 1; + module.exports = { a, [dynamic]: value, c: "not-detected" }; + "#, + true, + &["a"], + &[], + ); + } + + #[test] + fn detects_only_documented_export_star_helper_reexports() { + assert_analysis( + r#" + __export(require("./dep-a.cjs")); + __exportStar(require("./dep-b.cjs"), exports); + tslib.__export(require("./dep-c.cjs"), exports); + tslib.__exportStar(require("./dep-d.cjs"), exports); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep-a.cjs", "./dep-b.cjs", "./dep-c.cjs", "./dep-d.cjs"], + ); + + assert_analysis( + r#" + function nested() { + __export(require("./dep-a.cjs")); + } + nested(); + helper.__export(require("./dep-b.cjs"), exports); + __export(require(depName)); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + } + + #[test] + fn require_binding_alone_does_not_classify_esm_as_cjs() { + assert_analysis( + r#" + import { createRequire } from "node:module"; + const require = createRequire(import.meta.url); + const dep = require("./dep.cjs"); + export const value = dep.value; + "#, + false, + &[], + &[], + ); + } + + #[test] + fn detects_free_cjs_globals_for_esm_diagnostics() { + assert_cjs_global("require;", Some("require")); + assert_cjs_global("require('x');", Some("require")); + assert_cjs_global("const x = require; export default x;", Some("require")); + assert_cjs_global("exports = {};", Some("exports")); + assert_cjs_global("module;", Some("module")); + assert_cjs_global("__filename;", Some("__filename")); + assert_cjs_global("__dirname;", Some("__dirname")); + } + + #[test] + fn ignores_bound_or_non_free_cjs_global_names() { + assert_cjs_global("export default { require: 1 };", None); + assert_cjs_global("export default import.meta.require;", None); + assert_cjs_global("const require = 1; export default require;", None); + assert_cjs_global("let exports = 1; export default exports;", None); + assert_cjs_global("var module = 1; export default module;", None); + assert_cjs_global("class __dirname {} export default __dirname;", None); + assert_cjs_global( + "import require from 'data:text/javascript,export default 1'; export default require;", + None, + ); + assert_cjs_global( + "import * as module from 'data:text/javascript,export default {}'; export default module;", + None, + ); + assert_cjs_global( + "import { value as exports } from 'data:text/javascript,export const value = 1'; export default exports;", + None, + ); + assert_cjs_global( + "function f(require) { return require; } export default f(1);", + None, + ); + assert_cjs_global( + "function f(require) { return require; } export default require;", + Some("require"), + ); + assert_cjs_global("const f = (require) => require; export default f(1);", None); + assert_cjs_global("export default ((require) => require)(1);", None); + assert_cjs_global( + "const {\n module\n} = { module: 1 };\nexport default module;", + None, + ); + assert_cjs_global( + "const { require: localRequire } = { require: 1 };\nexport default localRequire;", + None, + ); + assert_cjs_global("const x = 0,\n require = 1;\nexport default require;", None); + assert_cjs_global("class C { #require = 1; get() { return this.#require; } } export default C;", None); + assert_cjs_global("class C { #exports = 1; get() { return this.#exports; } } export default C;", None); + assert_cjs_global("class C { #module = 1; get() { return this.#module; } } export default C;", None); + assert_cjs_global("export default import.meta . require;", None); + assert_cjs_global("export default globalThis . require;", None); + assert_cjs_global("export default obj\n.\nmodule;", None); + assert_cjs_global( + "export default { require() { return 1; }, f(module) { return module; } }.f(2);", + None, + ); + assert_cjs_global("export default { async require() { return 1; } };", None); + assert_cjs_global("export default { *module() { yield 1; } }.module().next().value;", None); + assert_cjs_global("export default { get exports() { return 1; } }.exports;", None); + assert_cjs_global("export default { \"x\"(require) { return require; } }.x(1);", None); + assert_cjs_global("export default { /* comment */ require() { return 1; } }.require();", None); + assert_cjs_global("function* module() { yield 1; } export default module;", None); + } + + #[test] + fn package_type_diagnostics_ignore_local_exports_binding() { + assert!(esm_preflight_error_module_source( + r#" + const exports = {}; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = "value"; + export default exports; + export { exports as "module.exports" }; + "#, + true, + false, + ) + .is_none()); + } + + #[test] + fn parses_static_named_import_specifiers_for_cjs_diagnostics() { + assert_eq!( + parse_static_named_import(r#"import { comeOn } from './fail.cjs';"#, 0), + Some(( + "./fail.cjs".to_string(), + vec![StaticNamedImport { + imported: "comeOn".to_string(), + local: "comeOn".to_string(), + }], + r#"import { comeOn } from './fail.cjs';"#.len() + )) + ); + assert_eq!( + parse_static_named_import(r#"import { comeOn as renamed } from "deep-fail""#, 0) + .map(|(specifier, imports, _)| (specifier, imports)), + Some(( + "deep-fail".to_string(), + vec![StaticNamedImport { + imported: "comeOn".to_string(), + local: "renamed".to_string(), + }], + )) + ); + assert_eq!( + parse_static_named_import( + r#"import defaultValue, { comeOn, everybody } from './fail.cjs';"#, + 0, + ) + .map(|(specifier, imports, _)| (specifier, imports)), + Some(( + "./fail.cjs".to_string(), + vec![ + StaticNamedImport { + imported: "comeOn".to_string(), + local: "comeOn".to_string(), + }, + StaticNamedImport { + imported: "everybody".to_string(), + local: "everybody".to_string(), + }, + ], + )) + ); + assert_eq!( + parse_static_named_import(r#"import { default as cjsDefault } from './dep.cjs';"#, 0) + .map(|(specifier, imports, _)| (specifier, imports)), + Some(( + "./dep.cjs".to_string(), + vec![StaticNamedImport { + imported: "default".to_string(), + local: "cjsDefault".to_string(), + }], + )) + ); + assert_eq!( + parse_static_named_import( + r#"import { "missing-name" as missingName } from './dep.cjs';"#, + 0, + ) + .map(|(specifier, imports, _)| (specifier, imports)), + Some(( + "./dep.cjs".to_string(), + vec![StaticNamedImport { + imported: "missing-name".to_string(), + local: "missingName".to_string(), + }], + )) + ); + assert_eq!( + format_cjs_named_import_binding(&StaticNamedImport { + imported: "missing-name".to_string(), + local: "missingName".to_string(), + }), + r#""missing-name": missingName"# + ); + } + + #[test] + fn package_type_diagnostics_use_first_cjs_global() { + let require_diag = esm_preflight_error_module_source("require('x');", true, false).unwrap(); + assert!(require_diag.contains("require is not defined")); + assert!(require_diag.contains(".cjs")); + + let filename_diag = esm_preflight_error_module_source("console.log(__filename);", true, false).unwrap(); + assert!(filename_diag.contains("__filename is not defined")); + assert!(filename_diag.contains(".cjs")); + + assert!(esm_preflight_error_module_source("const require = 1; export default require;", true, false).is_none()); + assert!(esm_preflight_error_module_source("export default typeof require;", false, false).is_none()); + assert!(esm_preflight_error_module_source("export default typeof (exports);", false, false).is_none()); + let raw_exports_diag = esm_preflight_error_module_source("Object.keys(exports);", false, true).unwrap(); + assert!(raw_exports_diag.contains("exports is not defined")); + assert!(!raw_exports_diag.contains("ES module scope")); + } + + #[test] + fn cjs_wrapper_lexical_redeclaration_scanner_skips_non_code() { + assert!(has_cjs_wrapper_lexical_redeclaration("const require = 1;")); + assert!(has_cjs_wrapper_lexical_redeclaration("let /*x*/ require = 1;")); + assert!(has_cjs_wrapper_lexical_redeclaration("const exports = 1;")); + assert!(has_cjs_wrapper_lexical_redeclaration("let module = 1;")); + assert!(has_cjs_wrapper_lexical_redeclaration("const __filename = 1;")); + assert!(has_cjs_wrapper_lexical_redeclaration("class __dirname {}")); + assert!(has_cjs_wrapper_lexical_redeclaration("const { exports } = ns;")); + assert!(has_cjs_wrapper_lexical_redeclaration( + "const x = 1, module = 2;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration("var require = 1;")); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const { x = require('node:path') } = {};" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const { [require('x')]: value } = obj;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const { [module.id]: value } = obj;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const { [exports.name]: value } = obj;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const { [__dirname + '/x']: value } = obj;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const require = createRequire(import.meta.url);" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const require = createRequire(import . meta . url);" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const require = createRequire(import/*x*/.meta.url);" + )); + assert!(has_cjs_wrapper_lexical_redeclaration( + "const require = createRequire(import.meta.urls);" + )); + assert!(has_cjs_wrapper_lexical_redeclaration( + "const require = createRequire(import.meta.urlx);" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const text = `const require = 1`; export default text;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "// const require = 1\nexport default 1;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "const re = /const require = 1/; export default re;" + )); + assert!(!has_cjs_wrapper_lexical_redeclaration( + "function f() { const require = 1; return require; }" + )); + } + + #[test] + fn esm_syntax_detection_includes_import_meta() { + assert!(source_looks_like_esm( + "// comment\n// another comment\nexport default 'module';\nconsole.log('executed');" + )); + assert!(source_looks_like_esm("globalThis.url = import.meta.url;")); + assert!(source_looks_like_esm("globalThis.meta = import . meta;")); + assert!(!source_looks_like_esm("const obj = { import: { meta: 1 } };")); + assert!(!source_looks_like_esm("globalThis.meta = obj.import.meta;")); + assert!(!source_looks_like_esm("const text = 'import.meta.url';")); + } + + #[test] + fn ignores_false_positive_assignments_and_define_property_descriptors() { + assert_analysis( + r#" + if (module.exports === undefined) {} + if (exports.fake == "no") {} + exports.arrow => value; + module.exports => {}; + module.exports => require("./dep.cjs"); + const template = `exports.templateOnly = "no";`; + Object.defineProperty(exports, "setterOnly", { set(v) { return dep.value; } }); + Object.defineProperty(exports, "unrelated", { other: function () { return dep.value; } }); + Object.defineProperty(exports, "regexDescriptor", { enumerable: /value:/ }); + Object.defineProperty(exports, "hiddenGetter", { enumerable: false, get() { return dep.value; } }); + Object.defineProperty(exports, "truthyEnumerableGetter", { enumerable: 1, get() { return dep.value; } }); + Object.defineProperty(exports, "multipleReturn", { get() { return dep.value; return dynamic(); } }); + Object.defineProperty(exports, "conditionalReturn", { get() { if (dep) return dep.value; return dynamic(); } }); + class PrivateNames { + #exports = {}; + #module = { exports: {} }; + write() { + this.#exports.privateExport = 1; + this.#module.exports.privateModuleExport = 1; + } + } + "#, + false, + &[], + &[], + ); + } + + #[test] + fn detects_only_real_transpiler_reexport_callbacks() { + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + const π = 1; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { return _dep[key]; } + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { return _dep[key]; } + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach /* comment */ (function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach; + (function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + const msg = "Object.defineProperty(exports, key, { get: function () { return _dep[key]; } })"; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + Object.defineProperty(other, key, { value: 1 }); + exports; + function unrelated() { return _dep[key]; } + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(exports, key, { + enumerable: false, + get: function () { return _dep[key]; } + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { return _dep[key]; }, + configurable: true + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(exports, key, { + enumerable: true, + enumerable: true, + get: function () { return _dep[key]; } + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(exports, key, { + get: function () { return _dep[key]; } + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = require("./dep.cjs"); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(exports, key, { + get: function () { return _dep[key]; }, + enumerable: true + }); + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + function copy() { + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + } + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = {}; + function init() { + var dep = require("./dep.cjs"); + } + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key !== "default" && !Object.prototype.hasOwnProperty.call(exports, key)) exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key !== "default" && !exports.hasOwnProperty(key)) exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key !== "default" && !Object.hasOwnProperty.call(exports, key)) exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + var ignored = {}; + Object.keys(dep).forEach(function (key) { + if (key !== "default" && !ignored.hasOwnProperty(key)) exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + var ignored = {}; + Object.keys(dep).forEach(function (key) { + if (key !== "default" && !Object.hasOwnProperty.call(ignored, key)) exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + var ignored = {}; + Object.keys(dep).forEach(function (key) { + if (key !== "default" && !Object.prototype.hasOwnProperty.call(ignored, key)) exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs") + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key] + }) + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if ("default" === key || "__esModule" === key) return; + exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + exports[key] = dep[key]; + if (key === "default" || key === "__esModule") return; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + function guard() { + if (key === "default" || key === "__esModule") return; + } + exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + for (var dep = require("./dep.cjs"); false;) {} + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + /* header */ var dep = require("./dep.cjs"); + exports.own = "own"; + /* separator */ Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + exports.own = "own"; // trailing comment + // separator + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach((key) => { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key]; + }, null); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] => dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + exports[key] = other[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var dep = require("./dep.cjs"); + Object.keys(dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + exports[key] = dep[key].nested; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(exports, key)) return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _dep[key]) return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in module.exports && module.exports[key] === _dep[key]) return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in module.exports && module.exports[key] === _dep[key]) return; + module.exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &["./dep.cjs"], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + var skip = {}; + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (skip.hasOwnProperty(key)) return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + var skip = {}; + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in skip && skip[key] === _dep[key]) return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = _interopRequireWildcard(require("./dep.cjs")); + var other = {}; + Object.keys(_dep).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === other[key]) return; + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var _dep = _interopWildcard(require("./dep.cjs")); + Object.keys(_dep).forEach(function (key) { + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + + assert_analysis( + r#" + var name = "./dep.cjs"; + var _dep = _interopRequireWildcard(require(name)); + Object.keys(_dep).forEach(function (key) { + exports[key] = _dep[key]; + }); + exports.own = "own"; + "#, + true, + &["own"], + &[], + ); + } +} + /// Wizer pre-initialization entry point: full initialization including user module. /// After Wizer snapshots this state, the runtime is ready to handle exports immediately. #[allow(static_mut_refs)] @@ -2604,6 +9802,7 @@ pub fn wizer_initialize() { ); }); + PACKAGE_JSON_CACHE.with_borrow_mut(|cache| cache.clear()); INIT_PHASE = InitPhase::WizerPreInitialized; } diff --git a/examples/runtime/cjs-require/src/cjs-require.js b/examples/runtime/cjs-require/src/cjs-require.js index b0e4700e..c21f637c 100644 --- a/examples/runtime/cjs-require/src/cjs-require.js +++ b/examples/runtime/cjs-require/src/cjs-require.js @@ -1,5 +1,9 @@ // Tests for CJS require() loader // This is an ESM file (as all user modules are), but it tests globalThis.require +const require = globalThis.require; +if (typeof require !== 'function') { + throw new Error('globalThis.require is not installed'); +} export const testRequireBuiltin = () => { try { @@ -188,3 +192,302 @@ export const testRequireModuleNotFound = () => { return false; } }; + +export const testRequirePackageExports = () => { + try { + const assert = require('assert'); + const fs = require('fs'); + + fs.mkdirSync('/exports-app/node_modules/conditional-pkg', { recursive: true }); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/package.json', JSON.stringify({ + exports: { + '.': { + import: './esm.mjs', + require: './cjs.cjs', + default: './default.js', + }, + './feature': { + require: './feature.cjs', + default: './feature-default.js', + }, + './encoded-target': './sp%20ce.js', + './import-only': { + import: './import-only.mjs', + }, + }, + })); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/esm.mjs', 'export default { mode: "esm" };'); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/cjs.cjs', 'module.exports = { mode: "cjs" };'); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/default.js', 'module.exports = { mode: "default" };'); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/feature.cjs', 'module.exports = { feature: "cjs" };'); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/feature-default.js', 'module.exports = { feature: "default" };'); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/sp ce.js', 'module.exports = { encoded: true };'); + fs.writeFileSync('/exports-app/node_modules/conditional-pkg/import-only.mjs', 'export default { mode: "import" };'); + + const appRequire = require('module').createRequire('/exports-app/app.js'); + assert.deepStrictEqual(appRequire('conditional-pkg'), { mode: 'cjs' }); + assert.deepStrictEqual(appRequire('conditional-pkg/feature'), { feature: 'cjs' }); + assert.deepStrictEqual(appRequire('conditional-pkg/encoded-target'), { encoded: true }); + + assert.throws(() => appRequire('conditional-pkg/import-only'), { + code: 'ERR_PACKAGE_PATH_NOT_EXPORTED', + }); + assert.throws(() => appRequire('conditional-pkg/private'), { + code: 'ERR_PACKAGE_PATH_NOT_EXPORTED', + }); + + assert.strictEqual(appRequire.resolve('conditional-pkg'), '/exports-app/node_modules/conditional-pkg/cjs.cjs'); + assert.strictEqual(appRequire.resolve('conditional-pkg/feature'), '/exports-app/node_modules/conditional-pkg/feature.cjs'); + + return true; + } catch (e) { + console.error(e); + return false; + } +}; + +export const testRequirePackageImports = () => { + try { + const assert = require('assert'); + const fs = require('fs'); + + fs.mkdirSync('/imports-app', { recursive: true }); + fs.writeFileSync('/imports-app/package.json', JSON.stringify({ + imports: { + '#dep': { + require: './dep.cjs', + default: './dep-default.js', + }, + '#default-only': { + default: './default-only.js', + }, + '#import-only': { + import: './import-only.mjs', + }, + '#false-target': false, + '#array-false-fallback': [ + false, + './dep.cjs', + ], + }, + })); + fs.writeFileSync('/imports-app/dep.cjs', 'module.exports = { mode: "require" };'); + fs.writeFileSync('/imports-app/dep-default.js', 'module.exports = { mode: "default" };'); + fs.writeFileSync('/imports-app/default-only.js', 'module.exports = { mode: "default-only" };'); + fs.writeFileSync('/imports-app/import-only.mjs', 'export default { mode: "import" };'); + fs.writeFileSync('/imports-app/main.cjs', [ + 'exports.dep = require("#dep");', + 'exports.defaultOnly = require("#default-only");', + 'exports.missing = function() { return require("#missing"); };', + 'exports.invalidBare = function() { return require("#"); };', + 'exports.initialSlash = function() { return require("#/initialslash"); };', + 'exports.importOnly = function() { return require("#import-only"); };', + 'exports.falseTarget = function() { return require("#false-target"); };', + 'exports.arrayFalseFallback = require("#array-false-fallback");', + ].join('\n')); + + const appRequire = require('module').createRequire('/imports-app/main.cjs'); + const mod = appRequire('./main.cjs'); + assert.deepStrictEqual(mod.dep, { mode: 'require' }); + assert.deepStrictEqual(mod.defaultOnly, { mode: 'default-only' }); + assert.throws(() => mod.missing(), { code: 'ERR_PACKAGE_IMPORT_NOT_DEFINED' }); + assert.throws(() => mod.invalidBare(), { code: 'ERR_INVALID_MODULE_SPECIFIER' }); + assert.throws(() => mod.initialSlash(), { code: 'ERR_INVALID_MODULE_SPECIFIER' }); + assert.throws(() => mod.importOnly(), { code: 'ERR_PACKAGE_IMPORT_NOT_DEFINED' }); + assert.throws(() => mod.falseTarget(), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.deepStrictEqual(mod.arrayFalseFallback, { mode: 'require' }); + assert.strictEqual(appRequire.resolve('#dep'), '/imports-app/dep.cjs'); + + return true; + } catch (e) { + console.error(e); + return false; + } +}; + +export const testRequirePackageMapEdgeCases = () => { + try { + const assert = require('assert'); + const fs = require('fs'); + const { createRequire } = require('module'); + + fs.mkdirSync('/package-map-edge-app/node_modules/exported-pkg', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/outside.js', 'module.exports = { escaped: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/package.json', JSON.stringify({ + main: './main.js', + exports: { + './public': './public.js', + './encoded-target': './sp%20ce.js', + './trailing-pattern-slash*': './trailing-pattern-slash*index.js', + './missing-selected': { + require: './missing.cjs', + default: './default.js', + }, + './escape': './../outside.js', + './nested-escape': './sub/../../outside.js', + './node-modules-target': './sub/../node_modules/other/index.js', + './dot-segment-target': './sub/../public.js', + './encoded-dot-target': './%2e%2e/outside.js', + './blocked-null': null, + './blocked-false': false, + './array-fallback': [ + { browser: './browser.js' }, + './public.js', + ], + './array-blocked': [ + null, + './public.js', + ], + './array-false-fallback': [ + false, + './public.js', + ], + './array-missing-first': [ + './missing.js', + './public.js', + ], + './array-invalid-fallback': [ + '../outside.js', + './public.js', + ], + './condition-no-match-fallback': { + node: { browser: './browser.js' }, + default: './public.js', + }, + './directory': './subdir', + './no-ext': './real', + }, + })); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/main.js', 'module.exports = { main: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/private.js', 'module.exports = { private: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/public.js', 'module.exports = { public: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/sp ce.js', 'module.exports = { encoded: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/default.js', 'module.exports = { defaulted: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/real.js', 'module.exports = { extensionFallback: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/exported-pkg/trailing-pattern-slash', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/trailing-pattern-slash/index.js', 'module.exports = { trailingPattern: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/exported-pkg/subdir', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/exported-pkg/subdir/index.js', 'module.exports = { directory: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/exports-vs-file.js', 'module.exports = { wrong: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/exports-vs-file', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/exports-vs-file/package.json', JSON.stringify({ exports: './main.js' })); + fs.writeFileSync('/package-map-edge-app/node_modules/exports-vs-file/main.js', 'module.exports = { exported: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/native-main', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/native-main/package.json', JSON.stringify({ main: 'addon' })); + fs.writeFileSync('/package-map-edge-app/node_modules/native-main/addon.node', 'not a native addon'); + fs.mkdirSync('/package-map-edge-app/node_modules/native-main/addon', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/native-main/addon/index.js', 'module.exports = { wrong: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/native-subpath', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/native-subpath/feature.node', 'not a native addon'); + fs.mkdirSync('/package-map-edge-app/node_modules/native-index/feature', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/native-index/feature/index.node', 'not a native addon'); + fs.writeFileSync('/package-map-edge-app/node_modules/native-root.node', 'not a native addon'); + fs.mkdirSync('/package-map-edge-app/node_modules/native-root', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/native-root/index.js', 'module.exports = { wrong: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/dotted-pkg.js', 'module.exports = { dotted: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/dotted-subpath', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/dotted-subpath/foo.bar.js', 'module.exports = { dottedSubpath: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/dotted-main', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/dotted-main/package.json', JSON.stringify({ main: 'foo.bar' })); + fs.writeFileSync('/package-map-edge-app/node_modules/dotted-main/foo.bar.js', 'module.exports = { dottedMain: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/mjs-not-implicit', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/mjs-not-implicit/feature.mjs', 'export default { wrong: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/mjs-not-implicit/feature.json', '{"json":true}'); + + fs.mkdirSync('/package-map-edge-app/node_modules/#cjs', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/#cjs/index.js', 'module.exports = { hashPackage: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/self-invalid', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/self-invalid/package.json', JSON.stringify({ + name: 'self-invalid', + exports: { + '.': './index.js', + './feature': './feature.js', + bad: './bad.js', + }, + })); + fs.writeFileSync('/package-map-edge-app/node_modules/self-invalid/index.js', [ + 'exports.loadFeature = function() { return require("self-invalid/feature"); };', + ].join('\n')); + fs.writeFileSync('/package-map-edge-app/node_modules/self-invalid/feature.js', 'module.exports = { feature: true };'); + + const appRequire = createRequire('/package-map-edge-app/app.js'); + assert.deepStrictEqual(appRequire('exported-pkg/public'), { public: true }); + assert.deepStrictEqual(appRequire('exported-pkg/encoded-target'), { encoded: true }); + const cjsPackageWarnings = []; + const onCjsPackageWarning = (warning) => cjsPackageWarnings.push(warning); + process.on('warning', onCjsPackageWarning); + try { + assert.deepStrictEqual(appRequire('exported-pkg/trailing-pattern-slash/'), { trailingPattern: true }); + assert.deepStrictEqual(appRequire('exported-pkg/trailing-pattern-slash/'), { trailingPattern: true }); + globalThis.__wasm_rquickjs_drainNextTick(); + } finally { + process.removeListener('warning', onCjsPackageWarning); + } + assert.deepStrictEqual(cjsPackageWarnings.map((warning) => warning.code), ['DEP0155']); + assert.match(cjsPackageWarnings[0].message, /package at \/package-map-edge-app\/node_modules\/exported-pkg\/package\.json\./); + assert.doesNotMatch(cjsPackageWarnings[0].message, / imported from /); + assert.deepStrictEqual(appRequire('exported-pkg/array-blocked'), { public: true }); + assert.deepStrictEqual(appRequire('#cjs'), { hashPackage: true }); + assert.strictEqual(appRequire.resolve('#cjs'), '/package-map-edge-app/node_modules/#cjs/index.js'); + assert.throws(() => appRequire('exported-pkg'), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }); + assert.throws(() => appRequire('exported-pkg/private.js'), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }); + assert.throws(() => appRequire('exported-pkg/missing-selected'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => appRequire('exported-pkg/escape'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.throws(() => appRequire('exported-pkg/nested-escape'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.throws(() => appRequire('exported-pkg/node-modules-target'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.throws(() => appRequire('exported-pkg/dot-segment-target'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.throws(() => appRequire('exported-pkg/encoded-dot-target'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.throws(() => appRequire('exported-pkg/blocked-null'), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }); + assert.throws(() => appRequire('exported-pkg/blocked-false'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.deepStrictEqual(appRequire('exported-pkg/array-fallback'), { public: true }); + assert.deepStrictEqual(appRequire('exported-pkg/array-false-fallback'), { public: true }); + assert.deepStrictEqual(appRequire('exported-pkg/array-invalid-fallback'), { public: true }); + assert.deepStrictEqual(appRequire('exported-pkg/condition-no-match-fallback'), { public: true }); + assert.deepStrictEqual(appRequire('exports-vs-file'), { exported: true }); + assert.deepStrictEqual(appRequire('dotted-pkg.js'), { dotted: true }); + assert.deepStrictEqual(appRequire('dotted-subpath/foo.bar'), { dottedSubpath: true }); + assert.deepStrictEqual(appRequire('dotted-main'), { dottedMain: true }); + assert.deepStrictEqual(appRequire('mjs-not-implicit/feature'), { json: true }); + assert.throws(() => appRequire('exported-pkg/array-missing-first'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => appRequire('exported-pkg/directory'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => appRequire('exported-pkg/no-ext'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => appRequire('native-main'), { code: 'ERR_DLOPEN_FAILED' }); + assert.throws(() => appRequire('native-subpath/feature'), { code: 'ERR_DLOPEN_FAILED' }); + assert.throws(() => appRequire('native-index/feature'), { code: 'ERR_DLOPEN_FAILED' }); + assert.throws(() => appRequire('native-root'), { code: 'ERR_DLOPEN_FAILED' }); + assert.throws(() => appRequire('self-invalid').loadFeature(), { code: 'ERR_INVALID_PACKAGE_CONFIG' }); + + fs.mkdirSync('/package-map-edge-app/node_modules/external-pkg', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/node_modules/external-pkg/index.js', 'module.exports = { external: true };'); + fs.mkdirSync('/package-map-edge-app/node_modules/dep', { recursive: true }); + fs.writeFileSync('/package-map-edge-app/package.json', JSON.stringify({ + imports: { + '#app-alias': './app-alias.js', + '#external': 'external-pkg', + '#external-encoded-slash': 'missing-external/a%2Fb', + '#external-encoded-backslash': 'missing-external/a%5Cb', + '#relative-encoded-slash': './a%2Fb.js', + '#relative-encoded-backslash': './a%5Cb.js', + '#builtin': 'node:fs', + }, + })); + fs.writeFileSync('/package-map-edge-app/app-alias.js', 'module.exports = { appAlias: true };'); + fs.writeFileSync('/package-map-edge-app/node_modules/dep/index.js', [ + 'exports.loadAppAlias = function() { return require("#app-alias"); };', + ].join('\n')); + + assert.deepStrictEqual(appRequire('#external'), { external: true }); + assert.throws(() => appRequire('#builtin'), { code: 'ERR_INVALID_PACKAGE_TARGET' }); + assert.throws(() => appRequire('#external-encoded-slash'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => appRequire('#external-encoded-backslash'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => appRequire('#relative-encoded-slash'), { code: 'ERR_INVALID_MODULE_SPECIFIER' }); + assert.throws(() => appRequire('#relative-encoded-backslash'), { code: 'ERR_INVALID_MODULE_SPECIFIER' }); + const dep = appRequire('dep'); + assert.throws(() => dep.loadAppAlias(), { code: 'ERR_PACKAGE_IMPORT_NOT_DEFINED' }); + + return true; + } catch (e) { + console.error(e); + return false; + } +}; diff --git a/examples/runtime/cjs-require/wit/cjs-require.wit b/examples/runtime/cjs-require/wit/cjs-require.wit index 8b847291..7315f107 100644 --- a/examples/runtime/cjs-require/wit/cjs-require.wit +++ b/examples/runtime/cjs-require/wit/cjs-require.wit @@ -10,4 +10,7 @@ world cjs-require { export test-require-json: func() -> bool; export test-require-module-exports-function: func() -> bool; export test-require-module-not-found: func() -> bool; + export test-require-package-exports: func() -> bool; + export test-require-package-imports: func() -> bool; + export test-require-package-map-edge-cases: func() -> bool; } diff --git a/examples/runtime/diagnostics-channel/src/diagnostics-channel.js b/examples/runtime/diagnostics-channel/src/diagnostics-channel.js index 7840993c..3a462919 100644 --- a/examples/runtime/diagnostics-channel/src/diagnostics-channel.js +++ b/examples/runtime/diagnostics-channel/src/diagnostics-channel.js @@ -1,7 +1,9 @@ import dc from 'node:diagnostics_channel'; import { AsyncLocalStorage } from 'node:async_hooks'; +import fs from 'node:fs'; +import { pathToFileURL } from 'node:url'; -export function test() { +export async function test() { const results = {}; const errors = []; @@ -257,6 +259,266 @@ export function test() { results.tracingChannelCtorError = e.code === 'ERR_INVALID_ARG_TYPE'; } + // === module.require tracing === + try { + const fixture = '/diagnostics-channel-module-require.cjs'; + fs.writeFileSync(fixture, 'module.exports = function () { return require("http"); };'); + const trace = dc.tracingChannel('module.require'); + const events = []; + let lastEvent; + trace.subscribe({ + start: (event) => { + if (event.id !== 'http') return; + lastEvent = event; + events.push({ name: 'start', id: event.id, parentFilename: event.parentFilename }); + }, + end: (event) => { + if (event.id !== 'http') return; + results.moduleRequireSameObject = event === lastEvent; + events.push({ name: 'end', id: event.id, parentFilename: event.parentFilename, hasResult: !!event.result }); + }, + }); + const result = require(fixture)(); + results.moduleRequireResult = result && typeof result.request === 'function'; + results.moduleRequireTrace = events.length === 2 && + events[0].name === 'start' && + events[1].name === 'end' && + events[0].id === 'http' && + events[1].id === 'http' && + events[0].parentFilename === fixture && + events[1].hasResult; + } catch (e) { + errors.push('moduleRequireTrace: ' + e.message); + } + + // === module.import tracing from CJS === + try { + const fixture = '/diagnostics-channel-module-import.cjs'; + fs.writeFileSync(fixture, 'module.exports = async function () { return import("http"); };'); + const trace = dc.tracingChannel('module.import'); + const events = []; + let lastEvent; + trace.subscribe({ + start: (event) => { + lastEvent = event; + events.push({ name: 'start', url: event.url, parentURL: event.parentURL }); + }, + end: (event) => { + results.moduleImportEndSameObject = event === lastEvent; + events.push({ name: 'end', url: event.url, parentURL: event.parentURL }); + }, + asyncStart: (event) => { + results.moduleImportAsyncStartSameObject = event === lastEvent; + events.push({ name: 'asyncStart', url: event.url, parentURL: event.parentURL, hasResult: !!event.result }); + }, + asyncEnd: (event) => { + results.moduleImportAsyncEndSameObject = event === lastEvent; + events.push({ name: 'asyncEnd', url: event.url, parentURL: event.parentURL, hasResult: !!event.result }); + }, + }); + const result = await require(fixture)(); + const expectedParentURL = pathToFileURL(fixture).href; + results.moduleImportResult = result && result.default && typeof result.default.request === 'function'; + results.moduleImportTrace = events.map((event) => event.name).join(',') === 'start,end,asyncStart,asyncEnd' && + events.every((event) => event.url === 'http' && event.parentURL === expectedParentURL) && + events[2].hasResult && + events[3].hasResult; + } catch (e) { + errors.push('moduleImportTrace: ' + e.message); + } + + // === nested module.require tracing === + try { + const parentFixture = '/diagnostics-channel-module-require-parent.cjs'; + const childFixture = '/diagnostics-channel-module-require-child.cjs'; + fs.writeFileSync(childFixture, 'module.exports = require("http");'); + fs.writeFileSync(parentFixture, 'module.exports = require(' + JSON.stringify(childFixture) + ');'); + const trace = dc.tracingChannel('module.require'); + const ids = new Set([parentFixture, childFixture, 'http']); + const events = []; + const starts = new Map(); + trace.subscribe({ + start: (event) => { + if (!ids.has(event.id)) return; + starts.set(event.id, event); + events.push({ name: 'start', id: event.id, parentFilename: event.parentFilename }); + }, + end: (event) => { + if (!ids.has(event.id)) return; + events.push({ name: 'end', id: event.id, sameObject: event === starts.get(event.id), hasResult: !!event.result }); + }, + }); + const result = require(parentFixture); + results.moduleRequireNestedResult = result && typeof result.request === 'function'; + results.moduleRequireNestedTrace = events.map((event) => event.name + ':' + event.id).join(',') === + 'start:' + parentFixture + ',start:' + childFixture + ',start:http,end:http,end:' + childFixture + ',end:' + parentFixture && + events[0].parentFilename === '/' && + events[1].parentFilename === parentFixture && + events[2].parentFilename === childFixture && + events.filter((event) => event.name === 'end').every((event) => event.sameObject && event.hasResult); + } catch (e) { + errors.push('moduleRequireNestedTrace: ' + e.message); + } + + // === module.import specifier coercion happens once === + try { + const fixture = '/diagnostics-channel-module-import-coerce.cjs'; + fs.writeFileSync(fixture, [ + 'module.exports = async function () {', + ' let calls = 0;', + ' const spec = { toString() { calls++; return calls === 1 ? "http" : "fs"; } };', + ' const result = await import(spec);', + ' return { calls, result };', + '};', + ].join('\n')); + const expectedParentURL = pathToFileURL(fixture).href; + const trace = dc.tracingChannel('module.import'); + const events = []; + trace.subscribe({ + start: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'start', url: event.url }); }, + end: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'end', url: event.url }); }, + asyncStart: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'asyncStart', url: event.url, hasResult: !!event.result }); }, + asyncEnd: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'asyncEnd', url: event.url, hasResult: !!event.result }); }, + }); + const result = await require(fixture)(); + results.moduleImportCoerceOnce = result.calls === 1 && + result.result && result.result.default && typeof result.result.default.request === 'function' && + events.map((event) => event.name).join(',') === 'start,end,asyncStart,asyncEnd' && + events.every((event) => event.url === 'http') && + events[2].hasResult && + events[3].hasResult; + } catch (e) { + errors.push('moduleImportCoerceOnce: ' + e.message); + } + + // === module.import parentURL ignores local __filename shadowing === + try { + const fixture = '/diagnostics-channel-module-import-shadow.cjs'; + fs.writeFileSync(fixture, 'module.exports = async function (__filename) { return import("http"); };'); + const expectedParentURL = pathToFileURL(fixture).href; + const trace = dc.tracingChannel('module.import'); + const events = []; + trace.subscribe({ + start: (event) => { if (event.url === 'http') events.push({ name: 'start', parentURL: event.parentURL }); }, + end: (event) => { if (event.url === 'http') events.push({ name: 'end', parentURL: event.parentURL }); }, + asyncStart: (event) => { if (event.url === 'http') events.push({ name: 'asyncStart', parentURL: event.parentURL, hasResult: !!event.result }); }, + asyncEnd: (event) => { if (event.url === 'http') events.push({ name: 'asyncEnd', parentURL: event.parentURL, hasResult: !!event.result }); }, + }); + const result = await require(fixture)('shadowed-filename'); + const ownEvents = events.filter((event) => event.parentURL === expectedParentURL); + results.moduleImportShadowParentResult = result && result.default && typeof result.default.request === 'function'; + results.moduleImportShadowParent = ownEvents.map((event) => event.name).join(',') === 'start,end,asyncStart,asyncEnd' && + ownEvents.every((event) => event.parentURL === expectedParentURL) && + ownEvents[2].hasResult && + ownEvents[3].hasResult; + } catch (e) { + errors.push('moduleImportShadowParent: ' + e.message); + } + + // === invalid import() options are rejected before module.import tracing === + try { + const fixture = '/diagnostics-channel-module-import-invalid-options.cjs'; + fs.writeFileSync(fixture, 'module.exports = async function () { return import("http", null); };'); + const expectedParentURL = pathToFileURL(fixture).href; + const trace = dc.tracingChannel('module.import'); + const events = []; + trace.subscribe({ + start: (event) => { if (event.parentURL === expectedParentURL) events.push('start'); }, + end: (event) => { if (event.parentURL === expectedParentURL) events.push('end'); }, + error: (event) => { if (event.parentURL === expectedParentURL) events.push('error'); }, + asyncStart: (event) => { if (event.parentURL === expectedParentURL) events.push('asyncStart'); }, + asyncEnd: (event) => { if (event.parentURL === expectedParentURL) events.push('asyncEnd'); }, + }); + let rejected = false; + try { + await require(fixture)(); + } catch (e) { + rejected = e instanceof TypeError; + } + results.moduleImportInvalidOptionsTrace = rejected && events.length === 0; + } catch (e) { + errors.push('moduleImportInvalidOptionsTrace: ' + e.message); + } + + // === semantic import attribute errors reject through async module.import tracing === + try { + const fixture = '/diagnostics-channel-module-import-unsupported-attr.cjs'; + fs.writeFileSync(fixture, 'module.exports = async function () { return import("http", { with: { unsupported: "x" } }); };'); + const expectedParentURL = pathToFileURL(fixture).href; + const trace = dc.tracingChannel('module.import'); + const events = []; + let lastEvent; + trace.subscribe({ + start: (event) => { + if (event.parentURL !== expectedParentURL) return; + lastEvent = event; + events.push({ name: 'start', url: event.url }); + }, + end: (event) => { + if (event.parentURL !== expectedParentURL) return; + events.push({ name: 'end', url: event.url, sameObject: event === lastEvent, hasError: !!event.error }); + }, + error: (event) => { + if (event.parentURL !== expectedParentURL) return; + events.push({ name: 'error', url: event.url, sameObject: event === lastEvent, code: event.error && event.error.code }); + }, + asyncStart: (event) => { + if (event.parentURL !== expectedParentURL) return; + events.push({ name: 'asyncStart', url: event.url, sameObject: event === lastEvent, code: event.error && event.error.code }); + }, + asyncEnd: (event) => { + if (event.parentURL !== expectedParentURL) return; + events.push({ name: 'asyncEnd', url: event.url, sameObject: event === lastEvent, code: event.error && event.error.code }); + }, + }); + let rejected = false; + try { + await require(fixture)(); + } catch (e) { + rejected = e && e.code === 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED'; + } + results.moduleImportUnsupportedAttrTrace = rejected && + events.map((event) => event.name).join(',') === 'start,end,error,asyncStart,asyncEnd' && + events.every((event) => event.url === 'http') && + events[1].sameObject && + !events[1].hasError && + events.slice(2).every((event) => event.sameObject && event.code === 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED'); + } catch (e) { + errors.push('moduleImportUnsupportedAttrTrace: ' + e.message); + } + + // === unsupported import attributes take priority over type semantic errors === + try { + const fixture = '/diagnostics-channel-module-import-mixed-attrs.cjs'; + fs.writeFileSync(fixture, 'module.exports = async function () { return import("/mixed-attrs-target.mjs", { with: { unsupported: "x", type: "json" } }); };'); + fs.writeFileSync('/mixed-attrs-target.mjs', 'export default 1;'); + const expectedParentURL = pathToFileURL(fixture).href; + const trace = dc.tracingChannel('module.import'); + const events = []; + trace.subscribe({ + start: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'start', code: event.error && event.error.code }); }, + end: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'end', code: event.error && event.error.code }); }, + error: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'error', code: event.error && event.error.code, message: event.error && event.error.message }); }, + asyncStart: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'asyncStart', code: event.error && event.error.code }); }, + asyncEnd: (event) => { if (event.parentURL === expectedParentURL) events.push({ name: 'asyncEnd', code: event.error && event.error.code }); }, + }); + let rejected = false; + try { + await require(fixture)(); + } catch (e) { + rejected = e && e.code === 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' && + e.message.indexOf('unsupported') !== -1; + } + results.moduleImportMixedAttrPriority = rejected && + events.map((event) => event.name).join(',') === 'start,end,error,asyncStart,asyncEnd' && + events[0].code === undefined && + events[1].code === undefined && + events.slice(2).every((event) => event.code === 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED') && + events[2].message.indexOf('unsupported') !== -1; + } catch (e) { + errors.push('moduleImportMixedAttrPriority: ' + e.message); + } + results.errors = errors; return JSON.stringify(results); } diff --git a/examples/runtime/module-resolution/src/module-resolution.js b/examples/runtime/module-resolution/src/module-resolution.js new file mode 100644 index 00000000..43436963 --- /dev/null +++ b/examples/runtime/module-resolution/src/module-resolution.js @@ -0,0 +1,7206 @@ +import assert from 'node:assert'; +import fs from 'node:fs'; +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +async function expectImportError(specifier, code) { + let thrown = false; + try { + await import(specifier); + } catch (error) { + thrown = true; + assert.strictEqual(error && error.code, code, error && error.stack ? error.stack : String(error)); + } + if (!thrown) { + throw new Error(`Expected import(${specifier}) to throw ${code}`); + } +} + +async function expectImportRejectsMessage(specifier, pattern) { + let thrown = false; + try { + await import(specifier); + } catch (error) { + thrown = true; + assert.match(String(error && error.message), pattern, error && error.stack ? `${error.message}\n${error.stack}` : String(error)); + } + if (!thrown) { + throw new Error(`Expected import(${specifier}) to reject`); + } +} + +async function expectImportRejectsCode(specifier, code) { + let thrown = false; + try { + await import(specifier); + } catch (error) { + thrown = true; + assert.strictEqual(error && error.code, code, error && error.stack ? error.stack : String(error)); + } + if (!thrown) { + throw new Error(`Expected import(${specifier}) to reject with ${code}`); + } +} + +function writeImportEntry(path, specifier) { + fs.writeFileSync(path, `export default await import(${JSON.stringify(specifier)});`); +} + +export const testImportMetaResolve = async () => { + const appDir = '/import-meta-resolve-app'; + const entryUrl = `${pathToFileURL(`${appDir}/entry.mjs`).href}`; + fs.mkdirSync(`${appDir}/node_modules/pkg-dir`, { recursive: true }); + + assert.strictEqual(import.meta.resolve('./local.mjs', entryUrl), `${pathToFileURL(`${appDir}/local.mjs`).href}`); + assert.strictEqual(import.meta.resolve('node:fs', entryUrl), 'node:fs'); + assert.strictEqual(import.meta.resolve('fs', entryUrl), 'node:fs'); + assert.strictEqual(import.meta.resolve('pkg-dir/', entryUrl), `${pathToFileURL(`${appDir}/node_modules/pkg-dir/`).href}`); + assert.throws(() => import.meta.resolve('does-not-exist', entryUrl), { code: 'ERR_MODULE_NOT_FOUND' }); + assert.throws(() => import.meta.resolve('./relative.mjs', 'data:text/javascript,'), { code: 'ERR_UNSUPPORTED_RESOLVE_REQUEST' }); + assert.throws(() => import.meta.resolve('../relative.mjs', 'data:text/javascript,'), { code: 'ERR_UNSUPPORTED_RESOLVE_REQUEST' }); + assert.throws(() => import.meta.resolve('does-not-exist', 'data:text/javascript,'), { code: 'ERR_UNSUPPORTED_RESOLVE_REQUEST' }); + + const resolvedFromData = await import('data:text/javascript,export default import.meta.resolve("http://example.com/value")'); + assert.strictEqual(resolvedFromData.default, 'http://example.com/value'); + const fileResolvedFromData = await import('data:text/javascript,export default import.meta.resolve("file:///tmp/value.mjs")'); + assert.strictEqual(fileResolvedFromData.default, 'file:///tmp/value.mjs'); + await expectImportRejectsCode( + 'data:text/javascript,export default import.meta.resolve("does-not-exist")', + 'ERR_UNSUPPORTED_RESOLVE_REQUEST', + ); + return true; +}; + +export const testEsmPackageMapEdgeCases = async () => { + try { + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/outside.mjs', 'export default { escaped: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/package.json', JSON.stringify({ + type: 'module', + main: './main.mjs', + exports: { + './public': './public.mjs', + './encoded-target': './sp%20ce.mjs', + './root-directory': './', + './deprecated-double': './/public.mjs', + './pattern-slash*': './subpath*.mjs', + './trailing-pattern-slash*': './trailing-pattern-slash*index.mjs', + './folder-pattern*': './folder-pattern*index.mjs', + './tamper-pattern*': './tamper-pattern*index.mjs', + './tamper-require*': './tamper-require*index.cjs', + './shared-warning*': { + import: './shared-warning*index.mjs', + require: './shared-warning*index.cjs', + }, + './suppressed-pattern*': './suppressed-pattern*index.mjs', + './suppressed-require*': './suppressed-require*index.cjs', + './throwing-pattern*': './throwing-pattern*index.mjs', + './condition-order': { + default: './default.mjs', + import: './import.mjs', + }, + './escape': './../outside.mjs', + './nested-escape': './sub/../../outside.mjs', + './node-modules-target': './sub/../node_modules/other/index.mjs', + './dot-segment-target': './sub/../public.mjs', + './encoded-dot-target': './%2e%2e/outside.mjs', + './blocked-null': null, + './blocked-false': false, + './array-fallback': [ + { browser: './browser.mjs' }, + './public.mjs', + ], + './array-blocked': [ + null, + './public.mjs', + ], + './array-false-fallback': [ + false, + './public.mjs', + ], + './array-missing-first': [ + './missing.mjs', + './public.mjs', + ], + './array-root-first': [ + './', + './public.mjs', + ], + './array-invalid-fallback': [ + '../outside.mjs', + './public.mjs', + ], + './condition-no-match-fallback': { + node: { browser: './browser.mjs' }, + default: './public.mjs', + }, + './directory': './subdir', + './no-ext': './real', + }, + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/main.mjs', 'export default { main: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/private.mjs', 'export default { private: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/public.mjs', 'export default { public: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/sp ce.mjs', 'export default { encoded: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/default.mjs', 'export default { condition: "default" };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/import.mjs', 'export default { condition: "import" };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/trailing-pattern-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/trailing-pattern-slash/index.mjs', 'export default { trailingPattern: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/tamper-pattern-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/tamper-pattern-slash/index.mjs', 'export default { tamperPattern: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/tamper-require-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/tamper-require-slash/index.cjs', 'module.exports = { tamperRequire: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/shared-warning-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/shared-warning-slash/index.mjs', 'export default { sharedWarning: "esm" };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/shared-warning-slash/index.cjs', 'module.exports = { sharedWarning: "cjs" };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/suppressed-pattern-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/suppressed-pattern-slash/index.mjs', 'export default { suppressedPattern: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/suppressed-require-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/suppressed-require-slash/index.cjs', 'module.exports = { suppressedRequire: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/throwing-pattern-slash', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/throwing-pattern-slash/index.mjs', 'export default { throwingPattern: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/folder-pattern/foo', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/folder-pattern/foo/index.mjs', 'export default { folderPattern: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/subpath/dir1', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/subpath/dir1/dir1.mjs', 'export default { patternSlash: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/exported-pkg/subdir', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/subdir/index.mjs', 'export default { directory: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/exported-pkg/real.mjs', 'export default { extensionFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/root-export-pkg', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/root-export-pkg/package.json', JSON.stringify({ + type: 'module', + exports: './', + })); + + fs.writeFileSync('/esm-package-map-edge-app/entry.mjs', [ + 'export const publicValue = (await import("exported-pkg/public")).default;', + 'export const encodedTarget = (await import("exported-pkg/encoded-target")).default;', + 'export const conditionOrder = (await import("exported-pkg/condition-order")).default;', + 'export const arrayFallback = (await import("exported-pkg/array-fallback")).default;', + 'export const arrayBlocked = (await import("exported-pkg/array-blocked")).default;', + 'export const arrayFalseFallback = (await import("exported-pkg/array-false-fallback")).default;', + 'export const arrayInvalidFallback = (await import("exported-pkg/array-invalid-fallback")).default;', + 'export const conditionNoMatchFallback = (await import("exported-pkg/condition-no-match-fallback")).default;', + ].join('\n')); + + const entry = await import('/esm-package-map-edge-app/entry.mjs'); + assert.deepStrictEqual(entry.publicValue, { public: true }); + assert.deepStrictEqual(entry.encodedTarget, { encoded: true }); + assert.deepStrictEqual(entry.conditionOrder, { condition: 'default' }); + assert.deepStrictEqual(entry.arrayFallback, { public: true }); + assert.deepStrictEqual(entry.arrayBlocked, { public: true }); + assert.deepStrictEqual(entry.arrayFalseFallback, { public: true }); + assert.deepStrictEqual(entry.arrayInvalidFallback, { public: true }); + assert.deepStrictEqual(entry.conditionNoMatchFallback, { public: true }); + + assert.strictEqual(Object.prototype.hasOwnProperty.call(globalThis, '__wasm_rquickjs_emit_package_deprecation_warning'), false); + + const genericDeprecationWarnings = []; + const onGenericDeprecationWarning = (warning) => { + if (warning.code === 'DEP0155' && warning.message === 'generic dep0155') { + genericDeprecationWarnings.push(warning); + } + }; + process.on('warning', onGenericDeprecationWarning); + try { + process.emitWarning('generic dep0155', 'DeprecationWarning', 'DEP0155'); + process.emitWarning('generic dep0155', 'DeprecationWarning', 'DEP0155'); + await new Promise((resolve) => process.nextTick(resolve)); + } finally { + process.removeListener('warning', onGenericDeprecationWarning); + } + assert.strictEqual(genericDeprecationWarnings.length, 2); + + const packageWarnings = []; + const onPackageWarning = (warning) => packageWarnings.push(warning); + process.on('warning', onPackageWarning); + const originalNoDeprecation = process.noDeprecation; + const originalNoDeprecationDescriptor = Object.getOwnPropertyDescriptor(process, 'noDeprecation'); + const originalEmitWarning = process.emitWarning; + const originalBoolean = globalThis.Boolean; + try { + writeImportEntry('/esm-package-map-edge-app/deprecated-double-subpath.mjs', 'exported-pkg/deprecated-double'); + writeImportEntry('/esm-package-map-edge-app/pattern-slash-subpath.mjs', 'exported-pkg/pattern-slash/dir1/dir1'); + writeImportEntry('/esm-package-map-edge-app/trailing-pattern-slash-subpath.mjs', 'exported-pkg/trailing-pattern-slash/'); + writeImportEntry('/esm-package-map-edge-app/trailing-pattern-slash-subpath-duplicate.mjs', 'exported-pkg/trailing-pattern-slash/'); + writeImportEntry('/esm-package-map-edge-app/folder-pattern-trailing-subpath.mjs', 'exported-pkg/folder-pattern/foo/'); + writeImportEntry('/esm-package-map-edge-app/tamper-pattern-slash-subpath.mjs', 'exported-pkg/tamper-pattern-slash/'); + writeImportEntry('/esm-package-map-edge-app/suppressed-pattern-slash-subpath.mjs', 'exported-pkg/suppressed-pattern-slash/'); + writeImportEntry('/esm-package-map-edge-app/suppressed-pattern-slash-subpath-after.mjs', 'exported-pkg/suppressed-pattern-slash/'); + writeImportEntry('/esm-package-map-edge-app/throwing-pattern-slash-subpath.mjs', 'exported-pkg/throwing-pattern-slash/'); + writeImportEntry('/esm-package-map-edge-app/shared-warning-subpath.mjs', 'exported-pkg/shared-warning-slash/'); + globalThis.__wasm_rquickjs_suppress_package_deprecation_warnings = 100; + globalThis.__wasm_rquickjs_package_deprecation_warnings = { + 'DEP0155:/esm-package-map-edge-app/node_modules/exported-pkg:./tamper-pattern-slash/': true, + }; + globalThis.__wasm_rquickjs_emit_package_deprecation_warning = () => { + throw new Error('userland package warning helper must not be called'); + }; + try { + process.noDeprecation = 'yes'; + process.emitWarning = undefined; + globalThis.Boolean = () => { + throw new Error('userland Boolean must not be used for noDeprecation coercion'); + }; + assert.strictEqual( + import.meta.resolve( + 'exported-pkg/suppressed-pattern-slash/', + pathToFileURL('/esm-package-map-edge-app/suppressed-resolve-parent.mjs').href, + ), + 'file:///esm-package-map-edge-app/node_modules/exported-pkg/suppressed-pattern-slash/', + ); + const requireSuppressed = createRequire('/esm-package-map-edge-app/suppressed-require-entry.cjs'); + assert.deepStrictEqual(requireSuppressed('exported-pkg/suppressed-require-slash/'), { suppressedRequire: true }); + } finally { + process.noDeprecation = originalNoDeprecation; + process.emitWarning = originalEmitWarning; + globalThis.Boolean = originalBoolean; + } + Object.defineProperty(process, 'noDeprecation', { + configurable: true, + get() { + throw new Error('noDeprecation getter failed'); + }, + }); + await assert.rejects( + () => import('/esm-package-map-edge-app/throwing-pattern-slash-subpath.mjs'), + /noDeprecation getter failed/, + ); + Object.defineProperty(process, 'noDeprecation', originalNoDeprecationDescriptor || { + configurable: true, + enumerable: true, + writable: true, + value: originalNoDeprecation, + }); + process.emitWarning = () => { + throw new Error('package warning emit failed'); + }; + const requireThrowingWarning = createRequire('/esm-package-map-edge-app/shared-warning-require-entry.cjs'); + assert.throws( + () => requireThrowingWarning('exported-pkg/shared-warning-slash/'), + /package warning emit failed/, + ); + assert.deepStrictEqual( + (await import('/esm-package-map-edge-app/shared-warning-subpath.mjs')).default.default, + { sharedWarning: 'esm' }, + ); + process.emitWarning = function emitWarningWithProcessThis(...args) { + assert.strictEqual(this, process); + return originalEmitWarning.apply(this, args); + }; + assert.deepStrictEqual((await import('/esm-package-map-edge-app/deprecated-double-subpath.mjs')).default.default, { public: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/pattern-slash-subpath.mjs')).default.default, { patternSlash: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/trailing-pattern-slash-subpath.mjs')).default.default, { trailingPattern: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/trailing-pattern-slash-subpath-duplicate.mjs')).default.default, { trailingPattern: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/folder-pattern-trailing-subpath.mjs')).default.default, { folderPattern: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/tamper-pattern-slash-subpath.mjs')).default.default, { tamperPattern: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/suppressed-pattern-slash-subpath-after.mjs')).default.default, { suppressedPattern: true }); + const require = createRequire('/esm-package-map-edge-app/require-entry.cjs'); + assert.deepStrictEqual(require('exported-pkg/tamper-require-slash/'), { tamperRequire: true }); + await new Promise((resolve) => process.nextTick(resolve)); + } finally { + delete globalThis.__wasm_rquickjs_suppress_package_deprecation_warnings; + delete globalThis.__wasm_rquickjs_package_deprecation_warnings; + delete globalThis.__wasm_rquickjs_emit_package_deprecation_warning; + Object.defineProperty(process, 'noDeprecation', originalNoDeprecationDescriptor || { + configurable: true, + enumerable: true, + writable: true, + value: originalNoDeprecation, + }); + process.emitWarning = originalEmitWarning; + globalThis.Boolean = originalBoolean; + process.removeListener('warning', onPackageWarning); + } + assert.deepStrictEqual(packageWarnings.map((warning) => warning.code), ['DEP0166', 'DEP0166', 'DEP0155', 'DEP0155', 'DEP0155', 'DEP0155', 'DEP0155']); + assert.match(packageWarnings[0].stack, /DeprecationWarning: Use of deprecated double slash/); + assert.match(packageWarnings[0].message, /package\.json imported from \/esm-package-map-edge-app\/deprecated-double-subpath\.mjs\./); + assert.match(packageWarnings[1].message, /matched to "\.\/pattern-slash\*"/); + assert.match(packageWarnings[1].message, /package\.json imported from \/esm-package-map-edge-app\/pattern-slash-subpath\.mjs\./); + assert.match(packageWarnings[2].stack, /DeprecationWarning: Use of deprecated trailing slash pattern mapping/); + assert.match(packageWarnings[2].message, /package\.json imported from \/esm-package-map-edge-app\/trailing-pattern-slash-subpath\.mjs\./); + assert.match(packageWarnings[3].message, /folder-pattern\/foo\//); + assert.match(packageWarnings[3].message, /package\.json imported from \/esm-package-map-edge-app\/folder-pattern-trailing-subpath\.mjs\./); + assert.match(packageWarnings[4].message, /tamper-pattern-slash\//); + assert.match(packageWarnings[4].message, /package\.json imported from \/esm-package-map-edge-app\/tamper-pattern-slash-subpath\.mjs\./); + assert.match(packageWarnings[5].message, /suppressed-pattern-slash\//); + assert.match(packageWarnings[5].message, /package\.json imported from \/esm-package-map-edge-app\/suppressed-pattern-slash-subpath-after\.mjs\./); + assert.match(packageWarnings[6].message, /tamper-require-slash\//); + assert.match(packageWarnings[6].message, /package\.json\./); + + fs.mkdirSync('/esm-package-map-edge-app/node_modules/no-exports-warn', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-warn/package.json', JSON.stringify({ + type: 'module', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-warn/index.js', 'export default { indexFallback: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-warn/foo.js', 'export default { exactSubpath: true };'); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-warn/sp ce.js', 'export default { encodedSpace: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/no-exports-warn/dir', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-warn/dir/index.js', 'export default { directorySubpath: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/main-extension-warn', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/main-extension-warn/package.json', JSON.stringify({ + type: 'module', + main: 'index', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/main-extension-warn/index.js', 'export default { mainExtensionFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/main-directory-warn/dir', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/main-directory-warn/package.json', JSON.stringify({ + type: 'module', + main: 'dir', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/main-directory-warn/dir/index.js', 'export default { mainDirectoryFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/no-exports-mjs-only', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-mjs-only/package.json', JSON.stringify({ + type: 'module', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-mjs-only/index.mjs', 'export default { indexFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/main-extension-mjs-only', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/main-extension-mjs-only/package.json', JSON.stringify({ + type: 'module', + main: 'index', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/main-extension-mjs-only/index.mjs', 'export default { mainExtensionFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/no-package-mjs-only', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-package-mjs-only/index.mjs', 'export default { indexFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/default-main-mjs-only', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/default-main-mjs-only/package.json', JSON.stringify({ + main: 'index', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/default-main-mjs-only/index.mjs', 'export default { mainExtensionFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/commonjs-main-mjs-only', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/commonjs-main-mjs-only/package.json', JSON.stringify({ + type: 'commonjs', + main: 'index', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/commonjs-main-mjs-only/index.mjs', 'export default { mainExtensionFallback: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/no-exports-native-only', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-native-only/package.json', JSON.stringify({ + type: 'module', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/no-exports-native-only/index.node', 'not a native addon'); + + const fallbackWarnings = []; + const onFallbackWarning = (warning) => fallbackWarnings.push(warning); + process.on('warning', onFallbackWarning); + try { + writeImportEntry('/esm-package-map-edge-app/no-exports-warn-entry.mjs', 'no-exports-warn'); + writeImportEntry('/esm-package-map-edge-app/no-exports-exact-subpath.mjs', 'no-exports-warn/foo.js'); + writeImportEntry('/esm-package-map-edge-app/no-exports-encoded-space-subpath.mjs', 'no-exports-warn/sp%20ce.js'); + writeImportEntry('/esm-package-map-edge-app/main-extension-warn-entry.mjs', 'main-extension-warn'); + writeImportEntry('/esm-package-map-edge-app/main-directory-warn-entry.mjs', 'main-directory-warn'); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/no-exports-warn-entry.mjs')).default.default, { indexFallback: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/no-exports-exact-subpath.mjs')).default.default, { exactSubpath: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/no-exports-encoded-space-subpath.mjs')).default.default, { encodedSpace: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/main-extension-warn-entry.mjs')).default.default, { mainExtensionFallback: true }); + assert.deepStrictEqual((await import('/esm-package-map-edge-app/main-directory-warn-entry.mjs')).default.default, { mainDirectoryFallback: true }); + await new Promise((resolve) => process.nextTick(resolve)); + } finally { + process.removeListener('warning', onFallbackWarning); + } + assert.deepStrictEqual(fallbackWarnings.map((warning) => warning.code), ['DEP0151', 'DEP0151', 'DEP0151']); + assert.match(fallbackWarnings[0].message, /no-exports-warn\/ resolving the main entry point "index\.js", imported from \/esm-package-map-edge-app\/no-exports-warn-entry\.mjs\.\nDefault "index" lookups/); + assert.match(fallbackWarnings[1].message, /main-extension-warn\/ has a "main" field set to "index".*resolved file at "index\.js", imported from \/esm-package-map-edge-app\/main-extension-warn-entry\.mjs\.\nAutomatic extension resolution/); + assert.match(fallbackWarnings[2].message, /main-directory-warn\/ has a "main" field set to "dir".*resolved file at "dir\/index\.js", imported from \/esm-package-map-edge-app\/main-directory-warn-entry\.mjs\.\nAutomatic extension resolution/); + + writeImportEntry('/esm-package-map-edge-app/no-exports-mjs-only-entry.mjs', 'no-exports-mjs-only'); + writeImportEntry('/esm-package-map-edge-app/main-extension-mjs-only-entry.mjs', 'main-extension-mjs-only'); + writeImportEntry('/esm-package-map-edge-app/no-package-mjs-only-entry.mjs', 'no-package-mjs-only'); + writeImportEntry('/esm-package-map-edge-app/default-main-mjs-only-entry.mjs', 'default-main-mjs-only'); + writeImportEntry('/esm-package-map-edge-app/commonjs-main-mjs-only-entry.mjs', 'commonjs-main-mjs-only'); + writeImportEntry('/esm-package-map-edge-app/no-exports-native-only-entry.mjs', 'no-exports-native-only'); + writeImportEntry('/esm-package-map-edge-app/no-exports-missing-subpath.mjs', 'no-exports-warn/missing'); + writeImportEntry('/esm-package-map-edge-app/no-exports-no-ext-subpath.mjs', 'no-exports-warn/foo'); + writeImportEntry('/esm-package-map-edge-app/no-exports-dir-subpath.mjs', 'no-exports-warn/dir'); + writeImportEntry('/esm-package-map-edge-app/no-exports-encoded-slash-subpath.mjs', 'no-exports-warn/a%2Fb.js'); + writeImportEntry('/esm-package-map-edge-app/no-exports-encoded-backslash-subpath.mjs', 'no-exports-warn/a%5Cb.js'); + writeImportEntry('/esm-package-map-edge-app/missing-root.mjs', 'exported-pkg'); + writeImportEntry('/esm-package-map-edge-app/private-subpath.mjs', 'exported-pkg/private.mjs'); + writeImportEntry('/esm-package-map-edge-app/escape-subpath.mjs', 'exported-pkg/escape'); + writeImportEntry('/esm-package-map-edge-app/nested-escape-subpath.mjs', 'exported-pkg/nested-escape'); + writeImportEntry('/esm-package-map-edge-app/node-modules-target-subpath.mjs', 'exported-pkg/node-modules-target'); + writeImportEntry('/esm-package-map-edge-app/dot-segment-target-subpath.mjs', 'exported-pkg/dot-segment-target'); + writeImportEntry('/esm-package-map-edge-app/encoded-dot-target-subpath.mjs', 'exported-pkg/encoded-dot-target'); + writeImportEntry('/esm-package-map-edge-app/blocked-null-subpath.mjs', 'exported-pkg/blocked-null'); + writeImportEntry('/esm-package-map-edge-app/blocked-false-subpath.mjs', 'exported-pkg/blocked-false'); + writeImportEntry('/esm-package-map-edge-app/array-missing-first-subpath.mjs', 'exported-pkg/array-missing-first'); + writeImportEntry('/esm-package-map-edge-app/root-directory-subpath.mjs', 'exported-pkg/root-directory'); + writeImportEntry('/esm-package-map-edge-app/array-root-first-subpath.mjs', 'exported-pkg/array-root-first'); + writeImportEntry('/esm-package-map-edge-app/directory-subpath.mjs', 'exported-pkg/directory'); + writeImportEntry('/esm-package-map-edge-app/no-ext-subpath.mjs', 'exported-pkg/no-ext'); + writeImportEntry('/esm-package-map-edge-app/root-export-pkg-entry.mjs', 'root-export-pkg'); + + await expectImportError('/esm-package-map-edge-app/no-exports-mjs-only-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/main-extension-mjs-only-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/no-package-mjs-only-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/default-main-mjs-only-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/commonjs-main-mjs-only-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/no-exports-native-only-entry.mjs', 'ERR_UNKNOWN_FILE_EXTENSION'); + await expectImportError('/esm-package-map-edge-app/no-exports-missing-subpath.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/no-exports-no-ext-subpath.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/no-exports-dir-subpath.mjs', 'ERR_UNSUPPORTED_DIR_IMPORT'); + await expectImportError('/esm-package-map-edge-app/no-exports-encoded-slash-subpath.mjs', 'ERR_INVALID_MODULE_SPECIFIER'); + await expectImportError('/esm-package-map-edge-app/no-exports-encoded-backslash-subpath.mjs', 'ERR_INVALID_MODULE_SPECIFIER'); + await expectImportError('/esm-package-map-edge-app/missing-root.mjs', 'ERR_PACKAGE_PATH_NOT_EXPORTED'); + await expectImportError('/esm-package-map-edge-app/private-subpath.mjs', 'ERR_PACKAGE_PATH_NOT_EXPORTED'); + await expectImportError('/esm-package-map-edge-app/escape-subpath.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + await expectImportError('/esm-package-map-edge-app/nested-escape-subpath.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + await expectImportError('/esm-package-map-edge-app/node-modules-target-subpath.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + await expectImportError('/esm-package-map-edge-app/dot-segment-target-subpath.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + await expectImportError('/esm-package-map-edge-app/encoded-dot-target-subpath.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + await expectImportError('/esm-package-map-edge-app/blocked-null-subpath.mjs', 'ERR_PACKAGE_PATH_NOT_EXPORTED'); + await expectImportError('/esm-package-map-edge-app/blocked-false-subpath.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + await expectImportError('/esm-package-map-edge-app/array-missing-first-subpath.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/root-directory-subpath.mjs', 'ERR_UNSUPPORTED_DIR_IMPORT'); + await expectImportError('/esm-package-map-edge-app/array-root-first-subpath.mjs', 'ERR_UNSUPPORTED_DIR_IMPORT'); + await expectImportError('/esm-package-map-edge-app/directory-subpath.mjs', 'ERR_UNSUPPORTED_DIR_IMPORT'); + await expectImportError('/esm-package-map-edge-app/no-ext-subpath.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/root-export-pkg-entry.mjs', 'ERR_UNSUPPORTED_DIR_IMPORT'); + const requireRootDirectory = createRequire('/esm-package-map-edge-app/root-directory-require.cjs'); + assert.throws(() => requireRootDirectory('exported-pkg/root-directory'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => requireRootDirectory('exported-pkg/array-root-first'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => requireRootDirectory('root-export-pkg'), { code: 'MODULE_NOT_FOUND' }); + + fs.mkdirSync('/esm-package-map-edge-app/node_modules/external-pkg', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/external-pkg/package.json', JSON.stringify({ + type: 'module', + exports: './index.mjs', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/external-pkg/index.mjs', 'export default { external: true };'); + fs.mkdirSync('/esm-package-map-edge-app/node_modules/dep', { recursive: true }); + fs.writeFileSync('/esm-package-map-edge-app/package.json', JSON.stringify({ + imports: { + '#app-alias': './app-alias.mjs', + '#external': 'external-pkg', + '#external-encoded-slash': 'missing-external/a%2Fb', + '#external-encoded-backslash': 'missing-external/a%5Cb', + '#relative-encoded-slash': './a%2Fb.js', + '#relative-encoded-backslash': './a%5Cb.js', + '#root-directory': './', + '#builtin': 'node:fs', + '#false-target': false, + '#array-false-fallback': [ + false, + './app-alias.mjs', + ], + }, + })); + fs.writeFileSync('/esm-package-map-edge-app/app-alias.mjs', 'export default { appAlias: true };'); + fs.writeFileSync('/esm-package-map-edge-app/imports-entry.mjs', [ + 'import external from "#external";', + 'import arrayFalseFallback from "#array-false-fallback";', + 'export default external;', + 'export const arrayFalseFallbackValue = arrayFalseFallback;', + ].join('\n')); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/dep/package.json', JSON.stringify({ + type: 'module', + exports: './index.js', + })); + fs.writeFileSync('/esm-package-map-edge-app/node_modules/dep/index.js', [ + 'import appAlias from "#app-alias";', + 'export default appAlias;', + ].join('\n')); + fs.writeFileSync('/esm-package-map-edge-app/imports-boundary-entry.mjs', 'export default await import("dep");'); + + const importsEntry = await import('/esm-package-map-edge-app/imports-entry.mjs'); + assert.deepStrictEqual(importsEntry.default, { external: true }); + assert.deepStrictEqual(importsEntry.arrayFalseFallbackValue, { appAlias: true }); + fs.writeFileSync('/esm-package-map-edge-app/imports-builtin-entry.mjs', 'export default await import("#builtin");'); + await expectImportError('/esm-package-map-edge-app/imports-builtin-entry.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + fs.writeFileSync('/esm-package-map-edge-app/imports-external-encoded-slash-entry.mjs', 'export default await import("#external-encoded-slash");'); + fs.writeFileSync('/esm-package-map-edge-app/imports-external-encoded-backslash-entry.mjs', 'export default await import("#external-encoded-backslash");'); + await expectImportError('/esm-package-map-edge-app/imports-external-encoded-slash-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + await expectImportError('/esm-package-map-edge-app/imports-external-encoded-backslash-entry.mjs', 'ERR_MODULE_NOT_FOUND'); + fs.writeFileSync('/esm-package-map-edge-app/imports-relative-encoded-slash-entry.mjs', 'export default await import("#relative-encoded-slash");'); + fs.writeFileSync('/esm-package-map-edge-app/imports-relative-encoded-backslash-entry.mjs', 'export default await import("#relative-encoded-backslash");'); + await expectImportError('/esm-package-map-edge-app/imports-relative-encoded-slash-entry.mjs', 'ERR_INVALID_MODULE_SPECIFIER'); + await expectImportError('/esm-package-map-edge-app/imports-relative-encoded-backslash-entry.mjs', 'ERR_INVALID_MODULE_SPECIFIER'); + fs.writeFileSync('/esm-package-map-edge-app/imports-false-entry.mjs', 'export default await import("#false-target");'); + await expectImportError('/esm-package-map-edge-app/imports-false-entry.mjs', 'ERR_INVALID_PACKAGE_TARGET'); + fs.writeFileSync('/esm-package-map-edge-app/imports-root-directory-entry.mjs', 'export default await import("#root-directory");'); + await expectImportError('/esm-package-map-edge-app/imports-root-directory-entry.mjs', 'ERR_UNSUPPORTED_DIR_IMPORT'); + assert.throws(() => requireRootDirectory('#root-directory'), { code: 'MODULE_NOT_FOUND' }); + await expectImportError('/esm-package-map-edge-app/imports-boundary-entry.mjs', 'ERR_PACKAGE_IMPORT_NOT_DEFINED'); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testEsmEncodedRelativePaths = async () => { + try { + fs.mkdirSync('/esm-encoded-relative-app/sub', { recursive: true }); + fs.writeFileSync('/esm-encoded-relative-app/sub/test-esm-ok.mjs', 'export default "ok";'); + fs.writeFileSync('/esm-encoded-relative-app/sub/test-esm-comma,.mjs', 'export default "comma";'); + fs.writeFileSync('/esm-encoded-relative-app/sub/test-esm-double-encoding-native%20.mjs', 'export default "percent";'); + fs.writeFileSync('/esm-encoded-relative-app/sub/blocked.mjs', 'export default "blocked";'); + fs.writeFileSync('/esm-encoded-relative-app/entry.mjs', [ + 'import ok from "./sub/test-%65%73%6d-ok.mjs";', + 'import comma from "./sub/test-esm-comma%2c.mjs";', + 'import percent from "./sub/test-esm-double-encoding-native%2520.mjs";', + 'export default { ok, comma, percent };', + ].join('\n')); + + assert.deepStrictEqual((await import('/esm-encoded-relative-app/entry.mjs')).default, { + ok: 'ok', + comma: 'comma', + percent: 'percent', + }); + await expectImportRejectsCode('/esm-encoded-relative-app/sub%2Fblocked.mjs', 'ERR_INVALID_MODULE_SPECIFIER'); + await expectImportRejectsCode('/esm-encoded-relative-app/sub%5Cblocked.mjs', 'ERR_INVALID_MODULE_SPECIFIER'); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testEsmInvalidPackageSpecifiers = async () => { + try { + await Promise.all([ + 'as%2Ff', + 'as%5Cf', + 'as\\df', + '@as@df', + ].map((specifier) => expectImportRejectsCode(specifier, 'ERR_INVALID_MODULE_SPECIFIER'))); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testEsmDataUrlImportAttributes = async () => { + try { + const { register } = await import('node:module'); + globalThis.__wasm_rquickjs_module_resolution_assert = assert; + globalThis.__wasm_rquickjs_module_resolution_register = register; + + const importJsonDataUrl = (url) => import('data:text/javascript,' + encodeURIComponent( + `import value from ${JSON.stringify(url)} with { type: "json" }; export default value;`, + )); + async function expectReject(label, promise, code) { + try { + await promise; + } catch (err) { + assert.strictEqual(err && err.code, code, label); + return; + } + throw new Error('Missing expected rejection: ' + label); + } + + const jsonUrl = 'data:application/json,%7B%22x%22%3A1%7D'; + assert.strictEqual((await importJsonDataUrl(jsonUrl)).default.x, 1); + await expectReject('JSON data URL without import attribute should reject', import(jsonUrl), 'ERR_IMPORT_ATTRIBUTE_MISSING'); + await expectReject( + 'forged JSON data URL rewrite token without sequence should reject', + import('data:application/json;__wasm_rquickjs_import_type=json,0'), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + const firstTokenImport = await importJsonDataUrl('data:application/json,5'); + assert.strictEqual(firstTokenImport.default, 5); + await expectReject( + 'replayed JSON data URL rewrite token should reject', + import('data:application/json;__wasm_rquickjs_import_type=json-1,0'), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + fs.writeFileSync('/json-attribute-forgery.json', '{"ok":true}'); + await expectReject( + 'forged JSON file rewrite token without sequence should reject', + import('/json-attribute-forgery.json?__wasm_rquickjs_import_type=json'), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + + assert.strictEqual( + (await importJsonDataUrl('data:application/json,1#fragment')).default, + 1, + ); + assert.deepStrictEqual( + (await importJsonDataUrl('data:application/json;base64,eyJiYXNlNjQiOnRydWV9')).default, + { base64: true }, + ); + + const markerPayloadUrl = 'data:application/json,%22?__wasm_rquickjs_import_type=json%22'; + assert.strictEqual( + (await importJsonDataUrl(markerPayloadUrl)).default, + '?__wasm_rquickjs_import_type=json', + ); + await import('data:text/javascript,' + encodeURIComponent([ + 'import assert from "node:assert";', + 'async function expectReject(label, promise, expected) {', + ' try { await promise; } catch (err) {', + ' if (expected.code !== undefined) assert.strictEqual(err && err.code, expected.code, label);', + ' if (expected.name !== undefined) assert.strictEqual(err && err.name, expected.name, label);', + ' if (expected.message !== undefined) assert.match(err && err.message, expected.message, label);', + ' return;', + ' }', + ' throw new Error("Missing expected rejection: " + label);', + '}', + 'await expectReject("null import options should reject", import("data:text/javascript,export default 1", null), { name: "TypeError", message: /second argument to import\\(\\) must be an object/ });', + 'await expectReject("null import with option should reject", import("data:text/javascript,export default 1", { with: null }), { name: "TypeError", message: /\\x27with\\x27 option must be an object/ });', + 'await expectReject("non-object import with option should reject", import("data:text/javascript,export default 1", { with: 1 }), { name: "TypeError", message: /\\x27with\\x27 option must be an object/ });', + 'await expectReject("non-string import attribute type should reject", import("data:text/javascript,export default 1", { with: { type: 1 } }), { name: "TypeError", message: /Import attribute value must be a string/ });', + 'await expectReject("CSS import attribute for JS should reject", import("data:text/javascript,export default 1", { with: { type: "css" } }), { code: "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED" });', + 'await expectReject("CSS import attribute for JSON should reject", import("data:application/json,1", { with: { type: "css" } }), { code: "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED" });', + 'const obj = { import(value) { return ["method", value]; } };', + 'assert.deepStrictEqual(obj.import("not-a-module", { with: { type: "json" } }), ["method", "not-a-module"]);', + 'const ignored = "import value from \\"/comment-json-token.json\\" with { type: \\"json\\" };";', + '// import value from "/comment-json-token.json" with { type: "json" };', + 'let withGets = 0;', + 'let ownKeys = 0;', + 'let typeGets = 0;', + 'const attrs = new Proxy({ type: "json" }, {', + ' ownKeys(target) { ownKeys++; return Reflect.ownKeys(target); },', + ' getOwnPropertyDescriptor(target, prop) { return Reflect.getOwnPropertyDescriptor(target, prop); },', + ' get(target, prop, receiver) {', + ' if (prop === "type") typeGets++;', + ' return Reflect.get(target, prop, receiver);', + ' },', + '});', + 'const optionsProxy = new Proxy({}, {', + ' get(_target, prop) {', + ' if (prop === "with") withGets++;', + ' return prop === "with" ? attrs : undefined;', + ' },', + '});', + 'assert.strictEqual((await import("data:application/json,9", optionsProxy)).default, 9);', + 'assert.deepStrictEqual({ withGets, ownKeys, typeGets }, { withGets: 1, ownKeys: 1, typeGets: 1 });', + ].join('\n'))); + await expectReject( + 'late static JSON rewrite token should not authorize future file import', + import('data:text/javascript,' + encodeURIComponent( + 'import value from "/late-json-token.json" with { type: "json" }; export default value;', + )), + 'ERR_MODULE_NOT_FOUND', + ); + fs.writeFileSync('/late-json-token.json', '{"late":true}'); + for (let token = 1; token <= 100; token++) { + await expectReject( + 'late JSON rewrite token replay should reject', + import(`/late-json-token.json?__wasm_rquickjs_import_type=json-${token}`), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + } + fs.writeFileSync('/comment-json-token.json', '{"comment":true}'); + for (let token = 1; token <= 100; token++) { + await expectReject( + 'commented JSON rewrite token should not authorize import', + import(`/comment-json-token.json?__wasm_rquickjs_import_type=json-${token}`), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + } + fs.writeFileSync('/static-non-string-attr.js', 'export default 1;'); + let staticNonStringRejected = false; + try { + await import('data:text/javascript,' + encodeURIComponent( + 'import value from "/static-non-string-attr.js" with { type: 1 }; export default value;', + )); + } catch (err) { + assert.strictEqual(err && err.name, 'SyntaxError', 'static non-string import attribute should reject'); + staticNonStringRejected = true; + } + if (!staticNonStringRejected) { + throw new Error('Missing expected rejection: static non-string import attribute should reject'); + } + fs.mkdirSync('/json-pkg-attrs-app/node_modules/json-pkg', { recursive: true }); + fs.writeFileSync( + '/json-pkg-attrs-app/node_modules/json-pkg/package.json', + JSON.stringify({ exports: './data.json' }), + ); + fs.writeFileSync('/json-pkg-attrs-app/node_modules/json-pkg/data.json', '{"pkg":true}'); + fs.mkdirSync('/json-pkg-attrs-app/node_modules/js-pkg', { recursive: true }); + fs.writeFileSync( + '/json-pkg-attrs-app/node_modules/js-pkg/package.json', + JSON.stringify({ exports: './index.mjs' }), + ); + fs.writeFileSync('/json-pkg-attrs-app/node_modules/js-pkg/index.mjs', 'export default { js: true };'); + fs.writeFileSync( + '/json-pkg-attrs-app/main.mjs', + 'import value from "json-pkg" with { type: "json" }; export default value;', + ); + assert.deepStrictEqual((await import('/json-pkg-attrs-app/main.mjs')).default, { pkg: true }); + fs.writeFileSync( + '/json-pkg-attrs-app/commented-static.mjs', + [ + 'import /* after import */ value from /* before specifier */ "json-pkg" /* before with */ with /* before attrs */ { type: "json" };', + 'export default value;', + ].join('\n'), + ); + assert.deepStrictEqual((await import('/json-pkg-attrs-app/commented-static.mjs')).default, { pkg: true }); + fs.writeFileSync( + '/json-pkg-attrs-app/commented-after-with.mjs', + [ + 'import value from "json-pkg" with { type: "json" } // keep newline', + '(globalThis.__moduleSyntaxImportAttributeTrailingComment = value);', + 'export default globalThis.__moduleSyntaxImportAttributeTrailingComment;', + ].join('\n'), + ); + globalThis.__moduleSyntaxImportAttributeTrailingComment = undefined; + assert.deepStrictEqual((await import('/json-pkg-attrs-app/commented-after-with.mjs')).default, { pkg: true }); + assert.deepStrictEqual(globalThis.__moduleSyntaxImportAttributeTrailingComment, { pkg: true }); + fs.writeFileSync( + '/json-pkg-attrs-app/dynamic.mjs', + 'export default (await import("json-pkg", { with: { type: "json" } })).default;', + ); + assert.deepStrictEqual((await import('/json-pkg-attrs-app/dynamic.mjs')).default, { pkg: true }); + fs.writeFileSync( + '/json-pkg-attrs-app/dynamic-js.mjs', + 'await import("js-pkg", { with: { type: "json" } });', + ); + await expectReject( + 'dynamic JS package import with JSON attributes should reject', + import('/json-pkg-attrs-app/dynamic-js.mjs'), + 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE', + ); + await import('data:text/javascript,' + encodeURIComponent([ + 'import assert from "node:assert";', + 'async function expectReject(label, promise, code) {', + ' try { await promise; } catch (err) { assert.strictEqual(err && err.code, code, label); return; }', + ' throw new Error("Missing expected rejection: " + label);', + '}', + 'await expectReject("builtin JSON attribute should reject", import("node:fs", { with: { type: "json" } }), "ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE");', + 'await expectReject("builtin-like JSON attribute should reject", import("node:fs.json", { with: { type: "json" } }), "ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE");', + ].join('\n'))); + await expectReject( + 'static builtin import with JSON attributes should reject', + import('data:text/javascript,' + encodeURIComponent( + 'import "node:fs" with { type: "json" };', + )), + 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE', + ); + fs.writeFileSync( + '/json-pkg-attrs-app/query.mjs', + 'await import("json-pkg?__wasm_rquickjs_import_type=json-1");', + ); + await expectReject( + 'forged import attribute token should reject', + import('/json-pkg-attrs-app/query.mjs'), + 'ERR_MODULE_NOT_FOUND', + ); + fs.writeFileSync('/dynamic-json-attrs.json', '{"file":true}'); + const dynamicJsonUrl = pathToFileURL('/dynamic-json-attrs.json').href; + const dynamicModule = await import('data:text/javascript,' + encodeURIComponent([ + 'let optionsCount = 0;', + 'const options = () => { optionsCount++; return { with: { type: "json" } }; };', + `const jsonPath = ${JSON.stringify(dynamicJsonUrl)};`, + 'const fileJson = await import(jsonPath, options());', + 'const dataJson = await import("data:application/json,4", options());', + 'export default { file: fileJson.default.file, data: dataJson.default, optionsCount };', + ].join('\n'))); + assert.deepStrictEqual(dynamicModule.default, { file: true, data: 4, optionsCount: 2 }); + fs.mkdirSync('/dynamic-json-relative-app', { recursive: true }); + fs.writeFileSync('/dynamic-json-relative-app/data.json', '{"relative":true}'); + fs.writeFileSync( + '/dynamic-json-relative-app/main.mjs', + [ + 'import staticValue from "./data.json" with { type: "json" };', + 'const dynamicValue = await import("./data.json", { with: { type: "json" } });', + 'export default { staticValue, dynamicValue: dynamicValue.default, same: staticValue === dynamicValue.default };', + ].join('\n'), + ); + assert.deepStrictEqual( + (await import('/dynamic-json-relative-app/main.mjs')).default, + { + staticValue: { relative: true }, + dynamicValue: { relative: true }, + same: true, + }, + ); + fs.writeFileSync( + '/dynamic-json-relative-app/object-specifier.mjs', + [ + 'const specifier = { toString() { return "./data.json"; } };', + 'export default (await import(specifier, { with: { type: "json" } })).default;', + ].join('\n'), + ); + assert.deepStrictEqual( + (await import('/dynamic-json-relative-app/object-specifier.mjs')).default, + { relative: true }, + ); + let malformedJsonRejected = false; + try { + await import('data:text/javascript,' + encodeURIComponent( + 'import value from "data:application/json;foo=%22test,%22,0" with { type: "json" }; export default value;', + )); + } catch (err) { + assert.strictEqual(err && err.name, 'SyntaxError', 'malformed JSON data URL should reject'); + assert.match(err && err.message, /Unterminated string in JSON at position 3/, 'malformed JSON data URL should reject'); + malformedJsonRejected = true; + } + if (!malformedJsonRejected) { + throw new Error('Missing expected rejection: malformed JSON data URL should reject'); + } + fs.writeFileSync('/dynamic-json-relative-app/assertionless.json', '{"ofLife":42}'); + const assertionlessJsonUrl = pathToFileURL('/dynamic-json-relative-app/assertionless.json').href; + const assertionlessJsonQueryUrl = `${assertionlessJsonUrl}?cache#frag`; + globalThis.__assertionlessJsonEnabled = true; + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'globalThis.__assertionlessJsonSeen = [];', + 'function resolve(specifier, context, next) {', + ' const noType = context.importAttributes.type == null;', + ' const result = next(specifier, context);', + ' const finish = (result) => {', + ' const resultUrl = String(result.url);', + ' let pathname = "";', + ' try { pathname = new URL(resultUrl, resultUrl.startsWith("/") ? "file:///" : context.parentURL).pathname; } catch (_) {}', + ' if (globalThis.__assertionlessJsonEnabled !== false && noType && (pathname.endsWith("/assertionless.json") || (resultUrl.startsWith("data:application/json") && resultUrl.includes("ofLife")))) {', + ' result.importAttributes = Object.assign({}, result.importAttributes || context.importAttributes, { type: "json" });', + ' }', + ' globalThis.__assertionlessJsonSeen.push({ specifier, url: result.url, contextImportAttributes: context.importAttributes, resultImportAttributes: result.importAttributes });', + ' return result;', + ' };', + ' return result && typeof result.then === "function" ? result.then(finish) : finish(result);', + '}', + 'register("data:text/javascript," + encodeURIComponent("export " + resolve));', + 'const [filePlain, fileTyped] = await Promise.all([', + ` import(${JSON.stringify(assertionlessJsonUrl)}),`, + ` import(${JSON.stringify(assertionlessJsonUrl)}, { with: { type: "json" } }),`, + ']);', + 'assert.strictEqual(filePlain, fileTyped, JSON.stringify(globalThis.__assertionlessJsonSeen));', + 'assert.strictEqual(filePlain.default, fileTyped.default, JSON.stringify(globalThis.__assertionlessJsonSeen));', + 'assert.deepStrictEqual(filePlain.default, { ofLife: 42 });', + `const filePlainAgain = await import(${JSON.stringify(assertionlessJsonUrl)});`, + `const fileTypedAgain = await import(${JSON.stringify(assertionlessJsonUrl)}, { with: { type: "json" } });`, + 'assert.strictEqual(filePlainAgain, filePlain);', + 'assert.strictEqual(fileTypedAgain, filePlain);', + 'const [queryPlain, queryTyped] = await Promise.all([', + ` import(${JSON.stringify(assertionlessJsonQueryUrl)}),`, + ` import(${JSON.stringify(assertionlessJsonQueryUrl)}, { with: { type: "json" } }),`, + ']);', + 'assert.strictEqual(queryPlain, queryTyped);', + 'assert.deepStrictEqual(queryPlain.default, { ofLife: 42 });', + 'const dataPlain = await import("data:application/json,{%22ofLife%22:42}");', + 'const dataTyped = await import("data:application/json,{%22ofLife%22:42}", { with: { type: "json" } });', + 'assert.deepStrictEqual(dataPlain.default, { ofLife: 42 });', + 'assert.deepStrictEqual(dataTyped.default, { ofLife: 42 });', + 'globalThis.__assertionlessJsonEnabled = false;', + ].join('\n'))); + for (let token = 1; token <= 100; token++) { + await expectReject( + `assertionless JSON superseded rewrite token should not authorize import: ${token}`, + import(`/dynamic-json-relative-app/assertionless.json?__wasm_rquickjs_import_type=json-${token}`), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + } + await expectReject( + 'JSON file imported with attributes should still reject without attributes after assertionless hook is disabled', + import('/dynamic-json-relative-app/assertionless.json'), + 'ERR_IMPORT_ATTRIBUTE_MISSING', + ); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'let seed = 0;', + 'function resolve(specifier, context, next) {', + ' const result = next(specifier, context);', + ' const finish = (result) => {', + ' const resultUrl = String(result.url);', + ' const url = new URL(resultUrl, resultUrl.startsWith("/") ? "file:///" : context.parentURL);', + ' if (url.pathname.endsWith("/dynamic-json-relative-app/data.json")) {', + ' url.searchParams.set("seed", String(++seed));', + ' return Object.assign({}, result, { url: url.href });', + ' }', + ' return result;', + ' };', + ' return result && typeof result.then === "function" ? result.then(finish) : finish(result);', + '}', + 'function load(url, context, next) {', + ' if (context.importAttributes.type === "json" && url.includes("/dynamic-json-relative-app/data.json")) {', + ' const value = new URL(url).searchParams.get("seed");', + ' return { shortCircuit: true, format: "json", source: JSON.stringify({ value }) };', + ' }', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent("let seed = 0; export " + resolve + ";export " + load));', + `const first = await import(${JSON.stringify(pathToFileURL('/dynamic-json-relative-app/data.json').href)}, { with: { type: "json" } });`, + `const second = await import(${JSON.stringify(pathToFileURL('/dynamic-json-relative-app/data.json').href)}, { with: { type: "json" } });`, + 'assert.notDeepStrictEqual(first.default, second.default);', + 'assert.deepStrictEqual(first.default, { value: "1" });', + 'assert.deepStrictEqual(second.default, { value: "2" });', + ].join('\n'))); + fs.mkdirSync('/loader-relative-app', { recursive: true }); + fs.writeFileSync('/loader-relative-app/data.json', '{"hookRelative":true}'); + fs.writeFileSync( + '/loader-relative-app/main.mjs', + 'export default (await import("./data.json", { with: { type: "json" } })).default;', + ); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'function resolve(specifier, context, next) {', + ' if (specifier === "data:text/javascript,export default 5") {', + ' if (JSON.stringify(context.importAttributes) !== "{}") throw new Error("plain import should pass empty import attributes");', + ' }', + ' if (String(context.parentURL).endsWith("/loader-relative-app/main.mjs")) {', + ' if (specifier !== "./data.json") throw new Error("resolve hook did not receive original relative specifier");', + ' globalThis.__loader_relative_seen = true;', + ' }', + ' return next(specifier, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent("export " + resolve));', + `assert.deepStrictEqual((await import(${JSON.stringify(pathToFileURL('/loader-relative-app/main.mjs').href)})).default, { hookRelative: true });`, + 'assert.strictEqual(globalThis.__loader_relative_seen, true);', + 'assert.strictEqual((await import("data:text/javascript,export default 5", {})).default, 5);', + ].join('\n'))); + fs.writeFileSync( + '/loader-relative-app/relative-loader.mjs', + [ + 'export function resolve(specifier, context, next) {', + ' if (specifier === "virtual:relative-loader") {', + ' return { shortCircuit: true, url: "virtual:relative-loader-json", format: "json" };', + ' }', + ' return next(specifier, context);', + '}', + 'export function load(url, context, next) {', + ' if (url === "virtual:relative-loader-json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"relativeLoader\\":true}" };', + ' }', + ' return next(url, context);', + '}', + ].join('\n'), + ); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'register("./relative-loader.mjs", { parentURL: "file:///loader-relative-app/main.mjs" });', + 'assert.deepStrictEqual((await import("virtual:relative-loader", { with: { type: "json" } })).default, { relativeLoader: true });', + ].join('\n'))); + fs.writeFileSync('/loader-relative-app/bytes.json', '{}'); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'function resolve(specifier, context, next) {', + ' if (specifier === "virtual:resolve-attrs") {', + ' return { shortCircuit: true, url: "data:application/json,{%22resolveAttrs%22:true}", format: "json", importAttributes: { type: "json" } };', + ' }', + ' return next(specifier, context);', + '}', + 'function load(url, context, next) {', + ' if (url.includes("%22resolveAttrs%22")) {', + ' if (JSON.stringify(context.importAttributes) !== "{\\"type\\":\\"json\\"}") throw new Error("resolve import attributes were not passed to load");', + ' return { shortCircuit: true, format: "json", source: "{\\"resolveAttrs\\":true}" };', + ' }', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent("export " + resolve + "; export " + load));', + 'assert.deepStrictEqual((await import("virtual:resolve-attrs", {})).default, { resolveAttrs: true });', + ].join('\n'))); + fs.mkdirSync('/loader-next-app/node_modules/loader-next-pkg', { recursive: true }); + fs.writeFileSync( + '/loader-next-app/node_modules/loader-next-pkg/package.json', + JSON.stringify({ + name: 'loader-next-pkg', + exports: { + '.': { + golem: './data.json', + default: './fallback.json', + }, + }, + }), + ); + fs.writeFileSync('/loader-next-app/node_modules/loader-next-pkg/data.json', '{"fromPackage":true}'); + fs.writeFileSync('/loader-next-app/node_modules/loader-next-pkg/fallback.json', '{"nextResolvePackage":true}'); + fs.mkdirSync('/loader-next-app/node_modules/loader-sparse-pkg', { recursive: true }); + fs.writeFileSync( + '/loader-next-app/node_modules/loader-sparse-pkg/package.json', + JSON.stringify({ + name: 'loader-sparse-pkg', + exports: { + '.': { + undefined: './bad.json', + default: './sparse-default.json', + }, + }, + }), + ); + fs.writeFileSync('/loader-next-app/node_modules/loader-sparse-pkg/bad.json', '{"sparseConditions":"bad"}'); + fs.writeFileSync('/loader-next-app/node_modules/loader-sparse-pkg/sparse-default.json', '{"sparseConditions":"default"}'); + fs.mkdirSync('/loader-next-app/node_modules/fs', { recursive: true }); + fs.writeFileSync( + '/loader-next-app/node_modules/fs/package.json', + JSON.stringify({ name: 'fs', main: './shadow.js' }), + ); + fs.writeFileSync('/loader-next-app/node_modules/fs/shadow.js', 'export default "shadow";'); + fs.mkdirSync('/loader-next-app/node_modules/loader-subpath-pkg', { recursive: true }); + fs.writeFileSync( + '/loader-next-app/node_modules/loader-subpath-pkg/package.json', + JSON.stringify({ name: 'loader-subpath-pkg' }), + ); + fs.writeFileSync('/loader-next-app/node_modules/loader-subpath-pkg/foo.js', 'export default "should-not-resolve";'); + fs.writeFileSync('/loader-next-app/node_modules/loader-subpath-pkg/sp ce.json', '{"encodedSpaceSubpath":true}'); + fs.writeFileSync('/loader-next-app/query.json', '{"query":true}'); + fs.writeFileSync('/loader-next-app/sp ce.json', '{"encodedRelative":true}'); + fs.writeFileSync('/loader-next-app/extensionless.js', 'export default "should-not-resolve";'); + fs.writeFileSync( + '/loader-next-app/main.mjs', + [ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'async function resolve(specifier, context, next) {', + ' globalThis.__wasm_rquickjs_module_resolution_assert.deepStrictEqual([...context.conditions].sort(), ["import", "module-sync", "node", "node-addons"]);', + ' if (specifier === "loader-next-pkg") {', + ' const result = await next(specifier, context);', + ' if (new URL(result.url).pathname !== "/loader-next-app/node_modules/loader-next-pkg/fallback.json") throw new Error("nextResolve exposed runtime-only package conditions to loader context: " + result.url);', + ' return result;', + ' }', + ' if (specifier === "virtual:sparse-conditions") {', + ' const result = await next("loader-sparse-pkg", { ...context, conditions: Array(1) });', + ' if (new URL(result.url).pathname !== "/loader-next-app/node_modules/loader-sparse-pkg/sparse-default.json") throw new Error("nextResolve treated sparse condition holes as string undefined: " + result.url);', + ' return result;', + ' }', + ' if (specifier === "virtual:undefined-condition") {', + ' const result = await next("loader-sparse-pkg", { ...context, conditions: [undefined] });', + ' if (new URL(result.url).pathname !== "/loader-next-app/node_modules/loader-sparse-pkg/sparse-default.json") throw new Error("nextResolve treated explicit undefined as string undefined: " + result.url);', + ' return result;', + ' }', + ' if (specifier === "virtual:builtin-shadow") {', + ' const result = await next("fs", context);', + ' if (result.url !== "node:fs") throw new Error("nextResolve allowed node_modules to shadow builtin: " + result.url);', + ' return { shortCircuit: true, url: "virtual:builtin-shadow-json", format: "json" };', + ' }', + ' if (specifier === "virtual:package-subpath-no-extension") {', + ' try {', + ' await next("loader-subpath-pkg/foo", context);', + ' throw new Error("nextResolve unexpectedly resolved package subpath without extension");', + ' } catch (error) {', + ' if (!error || error.code !== "ERR_MODULE_NOT_FOUND") throw error;', + ' }', + ' return { shortCircuit: true, url: "virtual:package-subpath-json", format: "json" };', + ' }', + ' if (specifier === "virtual:encoded-space-subpath") {', + ' const result = await next("loader-subpath-pkg/sp%20ce.json", context);', + ' if (new URL(result.url).pathname !== "/loader-next-app/node_modules/loader-subpath-pkg/sp%20ce.json") throw new Error("nextResolve did not decode package subpath space: " + result.url);', + ' return result;', + ' }', + ' if (specifier === "virtual:encoded-separator-subpath") {', + ' for (const bad of ["loader-subpath-pkg/a%2Fb.js", "loader-subpath-pkg/a%5Cb.js"]) {', + ' try {', + ' await next(bad, context);', + ' throw new Error("nextResolve unexpectedly accepted encoded separator subpath: " + bad);', + ' } catch (error) {', + ' if (!error || error.code !== "ERR_INVALID_MODULE_SPECIFIER") throw error;', + ' }', + ' }', + ' return { shortCircuit: true, url: "virtual:encoded-separator-json", format: "json" };', + ' }', + ' if (specifier === "./query.json?one#two") {', + ' const result = await next(specifier, context);', + ' const url = new URL(result.url);', + ' if (url.pathname !== "/loader-next-app/query.json" || url.search !== "?one" || url.hash !== "#two") throw new Error("nextResolve did not preserve file URL search/hash: " + result.url);', + ' return result;', + ' }', + ' if (specifier === "./sp%20ce.json?encoded#space") {', + ' const result = await next(specifier, context);', + ' const url = new URL(result.url);', + ' if (url.pathname !== "/loader-next-app/sp%20ce.json" || url.search !== "?encoded" || url.hash !== "#space") throw new Error("nextResolve did not preserve encoded relative path/search/hash: " + result.url);', + ' return result;', + ' }', + ' return next(specifier, context);', + '}', + 'function load(url, context, next) {', + ' if (new URL(url).pathname === "/loader-next-app/node_modules/loader-next-pkg/data.json") {', + ' if (context.format !== "json") throw new Error("nextResolve did not pass json format to load");', + ' return { shortCircuit: true, format: "json", source: "{\\"nextResolvePackage\\":true}" };', + ' }', + ' if (new URL(url).pathname === "/loader-next-app/node_modules/loader-sparse-pkg/sparse-default.json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"sparseConditions\\":\\"default\\"}" };', + ' }', + ' if (new URL(url).pathname === "/loader-next-app/node_modules/loader-sparse-pkg/bad.json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"sparseConditions\\":\\"bad\\"}" };', + ' }', + ' if (url === "virtual:builtin-shadow-json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"builtinShadow\\":true}" };', + ' }', + ' if (url === "virtual:package-subpath-json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"packageSubpath\\":true}" };', + ' }', + ' if (new URL(url).pathname === "/loader-next-app/node_modules/loader-subpath-pkg/sp%20ce.json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"encodedSpaceSubpath\\":true}" };', + ' }', + ' if (url === "virtual:encoded-separator-json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"encodedSeparator\\":true}" };', + ' }', + ' if (new URL(url).pathname === "/loader-next-app/query.json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"queryHash\\":true}" };', + ' }', + ' if (new URL(url).pathname === "/loader-next-app/sp%20ce.json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"encodedRelative\\":true}" };', + ' }', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent("export " + resolve + "; export " + load));', + 'let extensionlessRejected = false;', + 'try {', + ' await import("./extensionless", {});', + '} catch (err) {', + ' if (err && err.code !== undefined) assert.strictEqual(err.code, "ERR_MODULE_NOT_FOUND", "extensionless loader nextResolve import should reject");', + ' extensionlessRejected = true;', + '}', + 'if (!extensionlessRejected) throw new Error("Missing expected rejection: extensionless loader nextResolve import should reject");', + 'assert.deepStrictEqual((await import("virtual:sparse-conditions", { with: { type: "json" } })).default, { sparseConditions: "default" });', + 'assert.deepStrictEqual((await import("virtual:undefined-condition", { with: { type: "json" } })).default, { sparseConditions: "default" });', + 'assert.deepStrictEqual((await import("virtual:builtin-shadow", { with: { type: "json" } })).default, { builtinShadow: true });', + 'assert.deepStrictEqual((await import("virtual:package-subpath-no-extension", { with: { type: "json" } })).default, { packageSubpath: true });', + 'assert.deepStrictEqual((await import("virtual:encoded-space-subpath", { with: { type: "json" } })).default, { encodedSpaceSubpath: true });', + 'assert.deepStrictEqual((await import("virtual:encoded-separator-subpath", { with: { type: "json" } })).default, { encodedSeparator: true });', + 'assert.deepStrictEqual((await import("./query.json?one#two", { with: { type: "json" } })).default, { queryHash: true });', + 'assert.deepStrictEqual((await import("./sp%20ce.json?encoded#space", { with: { type: "json" } })).default, { encodedRelative: true });', + 'export default (await import("loader-next-pkg", { with: { type: "json" } })).default;', + ].join('\n'), + ); + assert.deepStrictEqual( + (await import('/loader-next-app/main.mjs')).default, + { nextResolvePackage: true }, + ); + globalThis.__wasm_rquickjs_registered_loaders = []; + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'function load(url, context, next) {', + ' if (url.includes("/loader-relative-app/bytes.json")) {', + ' return { shortCircuit: true, format: "json", source: new TextEncoder().encode("{\\"bytes\\":true}") };', + ' }', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent("export " + load));', + `assert.deepStrictEqual((await import(${JSON.stringify(pathToFileURL('/loader-relative-app/bytes.json').href)}, { with: { type: "json" } })).default, { bytes: true });`, + ].join('\n'))); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'function initialize(data) {', + ' globalThis.__loader_initialize_calls = (globalThis.__loader_initialize_calls || 0) + 1;', + ' globalThis.__loader_initialize_value = data.value;', + '}', + 'function resolve(specifier, context, next) {', + ' if (specifier === "virtual:initialize-data") {', + ' if (globalThis.__loader_initialize_value !== 42) throw new Error("loader initialize data was not available");', + ' return { shortCircuit: true, url: "virtual:initialize-data-json", format: "json" };', + ' }', + ' return next(specifier, context);', + '}', + 'function load(url, context, next) {', + ' if (url === "virtual:initialize-data-json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"initialized\\":true}" };', + ' }', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent("export " + initialize + "; export " + resolve + "; export " + load), { data: { value: 42 } });', + 'assert.deepStrictEqual((await import("virtual:initialize-data", { with: { type: "json" } })).default, { initialized: true });', + 'assert.deepStrictEqual((await import("virtual:initialize-data", { with: { type: "json" } })).default, { initialized: true });', + 'assert.strictEqual(globalThis.__loader_initialize_calls, 1);', + ].join('\n'))); + fs.writeFileSync( + '/loader-relative-app/url-parent-loader.mjs', + [ + 'let initializedValue;', + 'export function initialize(data) { initializedValue = data.value; }', + 'export function resolve(specifier, context, next) {', + ' if (specifier === "virtual:url-parent-initialize") {', + ' if (initializedValue !== 7) throw new Error("three-argument initialize data was not available");', + ' return { shortCircuit: true, url: "virtual:url-parent-initialize-json", format: "json" };', + ' }', + ' return next(specifier, context);', + '}', + 'export function load(url, context, next) {', + ' if (url === "virtual:url-parent-initialize-json") {', + ' return { shortCircuit: true, format: "json", source: "{\\"urlParent\\":true}" };', + ' }', + ' return next(url, context);', + '}', + ].join('\n'), + ); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'register("./url-parent-loader.mjs", new URL("file:///loader-relative-app/main.mjs"), { data: { value: 7 } });', + 'assert.deepStrictEqual((await import("virtual:url-parent-initialize", { with: { type: "json" } })).default, { urlParent: true });', + ].join('\n'))); + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + 'register("./url-parent-loader.mjs", { parentURL: "file:///loader-relative-app/main.mjs", data: { value: 7 } });', + 'assert.deepStrictEqual((await import("virtual:url-parent-initialize", { with: { type: "json" } })).default, { urlParent: true });', + ].join('\n'))); + fs.writeFileSync('/cjs-data-url-import-attributes.cjs', [ + 'const assert = require("node:assert");', + 'module.exports = async function () {', + ' const literal = await import("data:application/json;foo=\\"test,\\"this\\"", { with: { type: "json" } });', + ' if (literal.default !== "this") throw new Error("literal data URL import failed");', + ' const unicodeLiteral = await import("data:application/json,%7B%22snowman%22%3A%22\\u2603%22%7D", { with: { type: "json" } });', + ' if (unicodeLiteral.default.snowman !== "☃") throw new Error("unicode literal data URL import failed");', + ' const escaped = await import("data:application/json,%7B%22snowman%22%3A%22%E2%98%83%22%7D", { with: { type: "json" } });', + ' if (escaped.default.snowman !== "☃") throw new Error("escaped literal data URL import failed");', + ' const variableSpecifier = `data:application/json;foo=${encodeURIComponent("test,")},0`;', + ' const variable = await import(variableSpecifier, { with: { type: "json" } });', + ' if (variable.default !== 0) throw new Error("variable data URL import failed");', + ' let missingAttrRejected = false;', + ' try {', + ' await import(variableSpecifier);', + ' } catch (err) {', + ' assert.strictEqual(err && err.code, "ERR_IMPORT_ATTRIBUTE_MISSING", "CJS dynamic JSON import without attributes should reject");', + ' missingAttrRejected = true;', + ' }', + ' if (!missingAttrRejected) throw new Error("Missing expected rejection: CJS dynamic JSON import without attributes should reject");', + ' const urls = [variableSpecifier, "data:application/json,1"];', + ' let index = 0;', + ' let optionsCount = 0;', + ' const options = () => { optionsCount++; return { with: { type: "json" } }; };', + ' const sideEffected = await import(urls[index++], { with: { type: "json" } });', + ' if (index !== 1 || sideEffected.default !== 0) throw new Error("specifier evaluated more than once");', + ' const sideEffectedOptions = await import(urls[index++], options());', + ' if (index !== 2 || optionsCount !== 1 || sideEffectedOptions.default !== 1) throw new Error("options evaluated more than once");', + ' const plainUrls = ["data:text/javascript,export default 6", "data:text/javascript,export default 7"];', + ' let plainIndex = 0;', + ' const plain = await import(plainUrls[plainIndex++]);', + ' if (plainIndex !== 1 || plain.default !== 6) throw new Error("plain import specifier evaluated more than once");', + ' const obj = { "import": function(value) { return ["method", value]; } };', + ' const methodResult = obj.import("not-a-module", { with: { type: "json" } });', + ' if (methodResult[0] !== "method" || methodResult[1] !== "not-a-module") throw new Error("property import call was rewritten");', + ' const plainMethod = { import(value) { return ["plain", value]; } };', + ' if (plainMethod.import("value")[0] !== "plain") throw new Error("plain import method was rewritten");', + ' const asyncMethod = { async import(value) { return ["async", value]; } };', + ' if ((await asyncMethod.import("value"))[0] !== "async") throw new Error("async import method was rewritten");', + ' const generatorMethod = { *import(value) { yield value; } };', + ' if (generatorMethod.import("value").next().value !== "value") throw new Error("generator import method was rewritten");', + ' const asyncGeneratorMethod = { async * import(value) { yield value; } };', + ' if ((await asyncGeneratorMethod.import("value").next()).value !== "value") throw new Error("async generator import method was rewritten");', + ' const accessorMethod = { get import() { return "getter"; }, set import(value) { this.setter = value; } };', + ' if (accessorMethod.import !== "getter") throw new Error("getter import method was rewritten");', + ' accessorMethod.import = "setter";', + ' if (accessorMethod.setter !== "setter") throw new Error("setter import method was rewritten");', + ' class ImportMethods { import(value) { return ["class", value]; } }', + ' if (new ImportMethods().import("value")[0] !== "class") throw new Error("class import method was rewritten");', + ' class StaticImportMethod { static import(value) { return ["static", value]; } }', + ' if (StaticImportMethod.import("value")[0] !== "static") throw new Error("static import method was rewritten");', + ' class StaticGetterImportMethod { static get import() { return "staticGetter"; } }', + ' if (StaticGetterImportMethod.import !== "staticGetter") throw new Error("static getter import method was rewritten");', + ' class AsyncGeneratorImportMethod { async * import(value) { yield value; } }', + ' if ((await new AsyncGeneratorImportMethod().import("value").next()).value !== "value") throw new Error("class async generator import method was rewritten");', + ' const regex = /import\\("not-a-module", \\{ with: \\{ type: "json" \\} \\}\\)/;', + ' if (!regex.test(\'import("not-a-module", { with: { type: "json" } })\')) throw new Error("regex literal changed");', + '};', + ].join('\n')); + await (await import('/cjs-data-url-import-attributes.cjs')).default(); + + delete globalThis.__wasm_rquickjs_module_resolution_assert; + delete globalThis.__wasm_rquickjs_module_resolution_register; + return true; + } catch (error) { + delete globalThis.__wasm_rquickjs_module_resolution_assert; + delete globalThis.__wasm_rquickjs_module_resolution_register; + console.error(error); + throw error; + } +}; + +export const testEsmJsonUrlCacheKeys = async () => { + try { + const root = '/esm-json-url-cache-keys-app'; + fs.mkdirSync(root, { recursive: true }); + fs.writeFileSync(`${root}/cache-key.json`, JSON.stringify({ id: 0 })); + const jsonUrl = pathToFileURL(`${root}/cache-key.json`).href; + + globalThis.__wasm_rquickjs_json_cache_key_write = (value) => { + fs.writeFileSync(`${root}/cache-key.json`, JSON.stringify({ id: value })); + }; + try { + const result = (await import('data:text/javascript,' + encodeURIComponent([ + 'import assert from "node:assert";', + `const jsonUrl = ${JSON.stringify(jsonUrl)};`, + 'const plain = await import(jsonUrl, { with: { type: "json" } });', + 'globalThis.__wasm_rquickjs_json_cache_key_write(1);', + 'const query = await import(`${jsonUrl}?a=1`, { with: { type: "json" } });', + 'globalThis.__wasm_rquickjs_json_cache_key_write(2);', + 'const hash = await import(`${jsonUrl}#a=1`, { with: { type: "json" } });', + 'globalThis.__wasm_rquickjs_json_cache_key_write(3);', + 'const queryHash = await import(`${jsonUrl}?a=1#a=1`, { with: { type: "json" } });', + 'assert.notStrictEqual(plain, query);', + 'assert.notStrictEqual(plain, hash);', + 'assert.notStrictEqual(query, hash);', + 'export default [plain.default, query.default, hash.default, queryHash.default];', + ].join('\n')))).default; + assert.deepStrictEqual(result, [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]); + } finally { + delete globalThis.__wasm_rquickjs_json_cache_key_write; + } + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testStaticLoaderAbsoluteEntrySpecifier = async () => { + try { + const root = '/static-loader-absolute-entry-app'; + fs.mkdirSync(root, { recursive: true }); + fs.mkdirSync(`${root}/node_modules/static-condition-pkg`, { recursive: true }); + fs.writeFileSync(`${root}/entry.mjs`, 'export default true;'); + fs.writeFileSync( + `${root}/node_modules/static-condition-pkg/package.json`, + JSON.stringify({ + name: 'static-condition-pkg', + exports: { + customStatic: './custom.mjs', + default: './default.mjs', + }, + }), + ); + fs.writeFileSync(`${root}/node_modules/static-condition-pkg/custom.mjs`, 'export default "custom";'); + fs.writeFileSync(`${root}/node_modules/static-condition-pkg/default.mjs`, 'export default "default";'); + fs.writeFileSync(`${root}/from-data-parent.mjs`, 'export default "should-not-resolve";'); + const loaderUrl = 'data:text/javascript,' + encodeURIComponent([ + 'export function resolve(specifier, context, next) {', + ' if (specifier.startsWith("/")) throw new Error("static loader received absolute path: " + specifier);', + ' if (specifier.startsWith("file://") && specifier.includes("/static-loader-absolute-entry-app/entry.mjs?cache#frag")) {', + ' globalThis.__static_loader_absolute_entry_seen = specifier;', + ' }', + ' if (specifier === "virtual:static-condition") {', + ' return next("static-condition-pkg", { ...context, conditions: ["customStatic"] });', + ' }', + ' if (specifier === "virtual:rooted-data-parent") {', + ` const resolved = next(${JSON.stringify(`${root}/from-data-parent.mjs`)}, { ...context, parentURL: "data:text/javascript,export%20default%200" });`, + ' if (resolved !== undefined) throw new Error("nextResolve resolved rooted specifier under data: parent: " + resolved.url);', + ' const relative = next("./relative-from-data.mjs", { ...context, parentURL: "data:text/javascript,export%20default%200" });', + ' if (relative !== undefined) throw new Error("nextResolve resolved relative specifier under data: parent: " + relative.url);', + ' const bare = next("definitely-not-installed-static-loader-pkg", { ...context, parentURL: "data:text/javascript,export%20default%200" });', + ' if (bare !== undefined) throw new Error("nextResolve resolved bare specifier under data: parent: " + bare.url);', + ' return { shortCircuit: true, url: "data:text/javascript,export default true", format: "module" };', + ' }', + ' return next(specifier, context);', + '}', + ].join('\n')); + await import(loaderUrl); + const { register } = await import('node:module'); + register(loaderUrl); + await import('data:text/javascript,export default 0'); + assert.strictEqual( + (await import('data:text/javascript,' + encodeURIComponent( + `import value from ${JSON.stringify(`${root}/entry.mjs?cache#frag`)}; export default value;`, + ))).default, + true, + ); + assert.strictEqual( + globalThis.__static_loader_absolute_entry_seen, + `${pathToFileURL(`${root}/entry.mjs`).href}?cache#frag`, + ); + assert.strictEqual( + globalThis.__wasm_rquickjs_resolve_static_registered_loader( + pathToFileURL(`${root}/entry.mjs`).href, + 'virtual:static-condition', + ), + `${root}/node_modules/static-condition-pkg/custom.mjs`, + ); + assert.strictEqual( + globalThis.__wasm_rquickjs_resolve_static_registered_loader( + pathToFileURL(`${root}/entry.mjs`).href, + 'virtual:rooted-data-parent', + ), + 'data:text/javascript,export default true', + ); + delete globalThis.__static_loader_absolute_entry_seen; + return true; + } catch (error) { + delete globalThis.__static_loader_absolute_entry_seen; + console.error(error); + throw error; + } +}; + +export const testRegisteredLoaderModuleRealmIsolation = async () => { + try { + const root = '/registered-loader-realm-app'; + fs.mkdirSync(root, { recursive: true }); + fs.writeFileSync( + `${root}/stateful.mjs`, + [ + 'let value = 0;', + 'export function count() { return ++value; }', + ].join('\n'), + ); + fs.mkdirSync(`${root}/node_modules/loader-realm-pkg`, { recursive: true }); + fs.writeFileSync( + `${root}/node_modules/loader-realm-pkg/package.json`, + JSON.stringify({ name: 'loader-realm-pkg', exports: './index.mjs' }), + ); + fs.writeFileSync( + `${root}/node_modules/loader-realm-pkg/index.mjs`, + [ + 'let value = 0;', + 'export function count() { return ++value; }', + ].join('\n'), + ); + fs.writeFileSync( + `${root}/app.mjs`, + [ + 'import { count as stateCount } from "./stateful.mjs";', + 'import { count as packageCount } from "loader-realm-pkg";', + 'export default [stateCount(), packageCount()];', + ].join('\n'), + ); + fs.writeFileSync( + `${root}/loader.mjs`, + [ + 'import { count } from "./stateful.mjs";', + 'import { count as packageCount } from "loader-realm-pkg";', + 'if (import.meta.url.includes("__wasm_rquickjs_loader_realm")) {', + ' throw new Error("loader realm marker leaked through import.meta.url");', + '}', + 'const loaderCount = count();', + 'const loaderPackageCount = packageCount();', + 'export function resolve(specifier, context, next) {', + ' if (specifier === "virtual:loader-realm-count") {', + ' return { shortCircuit: true, url: "virtual:loader-realm-count-json", format: "json" };', + ' }', + ' return next(specifier, context);', + '}', + 'export function load(url, context, next) {', + ' if (url === "virtual:loader-realm-count-json") {', + ' return { shortCircuit: true, format: "json", source: JSON.stringify({ loaderCount, loaderPackageCount }) };', + ' }', + ' return next(url, context);', + '}', + ].join('\n'), + ); + globalThis.__wasm_rquickjs_registered_loaders = []; + globalThis.__wasm_rquickjs_module_resolution_assert = assert; + globalThis.__wasm_rquickjs_module_resolution_register = (await import('node:module')).register; + await import('data:text/javascript,' + encodeURIComponent([ + 'const assert = globalThis.__wasm_rquickjs_module_resolution_assert;', + 'const register = globalThis.__wasm_rquickjs_module_resolution_register;', + `register(${JSON.stringify(pathToFileURL(`${root}/loader.mjs`).href)});`, + 'assert.deepStrictEqual((await import("virtual:loader-realm-count", { with: { type: "json" } })).default, { loaderCount: 1, loaderPackageCount: 1 });', + `const userState = await import(${JSON.stringify(pathToFileURL(`${root}/stateful.mjs`).href)});`, + 'assert.strictEqual(userState.count(), 1);', + `assert.deepStrictEqual((await import(${JSON.stringify(pathToFileURL(`${root}/app.mjs`).href)})).default, [2, 1]);`, + ].join('\n'))); + delete globalThis.__wasm_rquickjs_module_resolution_assert; + delete globalThis.__wasm_rquickjs_module_resolution_register; + return true; + } catch (error) { + delete globalThis.__wasm_rquickjs_module_resolution_assert; + delete globalThis.__wasm_rquickjs_module_resolution_register; + console.error(error); + throw error; + } +}; + +export const testEsmForbiddenCjsGlobals = async () => { + try { + const root = '/esm-forbidden-cjs-globals-app'; + fs.mkdirSync(root, { recursive: true }); + fs.writeFileSync( + `${root}/main.mjs`, + [ + 'export default [', + ' typeof arguments,', + ' typeof this,', + ' typeof exports,', + ' typeof require,', + ' typeof module,', + ' typeof __filename,', + ' typeof __dirname,', + '];', + 'export const meta = [typeof import.meta.url, typeof import.meta.filename, typeof import.meta.dirname];', + ].join('\n'), + ); + fs.writeFileSync( + `${root}/declared.mjs`, + [ + 'const require = () => "local-require";', + 'const exports = "local-exports";', + 'const module = "local-module";', + 'const __filename = "local-filename";', + 'const __dirname = "local-dirname";', + 'export default [require(), exports, module, __filename, __dirname];', + ].join('\n'), + ); + fs.writeFileSync( + `${root}/bindings.mjs`, + [ + 'export const req = "imported-require";', + 'export const exp = "imported-exports";', + 'export const mod = "imported-module";', + 'export const file = "imported-filename";', + 'export const dir = "imported-dirname";', + ].join('\n'), + ); + fs.writeFileSync( + `${root}/imported.mjs`, + [ + 'import { req as require, exp as exports, mod as module, file as __filename, dir as __dirname } from "./bindings.mjs";', + 'export default [require, exports, module, __filename, __dirname];', + ].join('\n'), + ); + fs.writeFileSync(`${root}/rhs-require.mjs`, 'const value = require; export default value;'); + fs.writeFileSync( + `${root}/param-require.mjs`, + [ + 'function local(require) { return require; }', + 'export default [local("local-require"), typeof require];', + ].join('\n'), + ); + fs.writeFileSync(`${root}/direct.mjs`, 'Object.defineProperty(exports, "__esModule", { value: true });'); + const imported = await import(pathToFileURL(`${root}/main.mjs`).href); + assert.deepStrictEqual(imported.default, [ + 'undefined', + 'undefined', + 'undefined', + 'undefined', + 'undefined', + 'undefined', + 'undefined', + ]); + assert.deepStrictEqual(imported.meta, ['string', 'string', 'string']); + assert.deepStrictEqual((await import(pathToFileURL(`${root}/declared.mjs`).href)).default, [ + 'local-require', + 'local-exports', + 'local-module', + 'local-filename', + 'local-dirname', + ]); + assert.deepStrictEqual((await import(pathToFileURL(`${root}/imported.mjs`).href)).default, [ + 'imported-require', + 'imported-exports', + 'imported-module', + 'imported-filename', + 'imported-dirname', + ]); + assert.deepStrictEqual((await import(pathToFileURL(`${root}/param-require.mjs`).href)).default, [ + 'local-require', + 'undefined', + ]); + await assert.rejects(import(pathToFileURL(`${root}/rhs-require.mjs`).href), { + name: 'ReferenceError', + message: /require is not defined/, + }); + await assert.rejects(import(pathToFileURL(`${root}/direct.mjs`).href), { + name: 'ReferenceError', + message: /exports is not defined/, + }); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsDynamicImportAttributeScanner = async () => { + try { + fs.mkdirSync('/cjs-dynamic-import-attr-scanner', { recursive: true }); + fs.writeFileSync('/cjs-dynamic-import-attr-scanner/data.json', '{"fromCjs":true}'); + fs.writeFileSync('/cjs-dynamic-import-attr-scanner/module.cjs', [ + 'const assert = require("node:assert");', + 'const stringLiteral = "import(\\"./missing-string.json\\", { with: { type: \\"json\\" } })";', + 'const templateLiteral = `before ${"import(\\"./missing-template.json\\", { with: { type: \\"json\\" } })"} after`;', + 'const regexLiteral = /import\\(\\"\\.\\/missing-regex\\.json\\", \\{ with: \\{ type: \\"json\\" \\} \\}\\)/;', + 'const commentedAssignmentRegexLiteral = /* scanner comment */ /import(".+")/.source;', + 'function returnedRegexLiteral() { return /* scanner comment */ /import(".+")/.source; }', + '// import("./missing-comment.json", { with: { type: "json" } });', + 'const objectMethod = { import(value, options) { return [value, options.with.type]; } };', + 'class ImportMethods {', + ' static import(value, options) { return [value, options.with.type]; }', + '}', + 'exports.run = async function run() {', + ' assert.deepStrictEqual(objectMethod.import("object", { with: { type: "json" } }), ["object", "json"]);', + ' assert.deepStrictEqual(ImportMethods.import("static", { with: { type: "json" } }), ["static", "json"]);', + ' const imported = await import("./data.json", { with: { type: "json" } });', + ' const spaced = await import ("./data.json", { with: { type: "json" } });', + ' const commented = await import /* scanner comment */ ("./data.json", { with: { type: "json" } });', + ' const commentedInside = await import( /* inside call */ "./data.json" /* before options */, { with: { type: "json" } });', + ' const templateImported = await `${(await import("./data.json", { with: { type: "json" } })).default.fromCjs}`;', + ' const nested = await import((await import("./name.json", { with: { type: "json" } })).default.name, { with: { type: "json" } });', + ' return { stringLiteral, templateLiteral, regexLiteral: regexLiteral.source, commentedAssignmentRegexLiteral, returnedRegexLiteral: returnedRegexLiteral(), json: imported.default, spaced: spaced.default, commented: commented.default, commentedInside: commentedInside.default, templateImported, nested: nested.default };', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-dynamic-import-attr-scanner/name.json', '{"name":"./data.json"}'); + + const result = (await import('/cjs-dynamic-import-attr-scanner/module.cjs')).default; + const value = await result.run(); + assert.strictEqual( + value.stringLiteral, + 'import("./missing-string.json", { with: { type: "json" } })', + ); + assert.strictEqual( + value.templateLiteral, + 'before import("./missing-template.json", { with: { type: "json" } }) after', + ); + assert.match(value.regexLiteral, /missing-regex/); + assert.strictEqual(value.commentedAssignmentRegexLiteral, 'import(".+")'); + assert.strictEqual(value.returnedRegexLiteral, 'import(".+")'); + assert.deepStrictEqual(value.json, { fromCjs: true }); + assert.deepStrictEqual(value.spaced, { fromCjs: true }); + assert.deepStrictEqual(value.commented, { fromCjs: true }); + assert.deepStrictEqual(value.commentedInside, { fromCjs: true }); + assert.strictEqual(value.templateImported, 'true'); + assert.deepStrictEqual(value.nested, { fromCjs: true }); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testLoaderCommonjsSourceNamedExports = async () => { + try { + fs.mkdirSync('/loader-cjs-source-app', { recursive: true }); + fs.writeFileSync('/loader-cjs-source-app/package.json', JSON.stringify({ + imports: { + '#loader-import': './imports-target.cjs', + }, + })); + fs.writeFileSync('/loader-cjs-source-app/dep.cjs', 'module.exports = { depValue: 17 };'); + fs.writeFileSync('/loader-cjs-source-app/imports-target.cjs', 'exports.importsReexported = 102;'); + fs.writeFileSync('/loader-cjs-source-app/reexport-dep.cjs', 'exports.reexported = 91;'); + fs.mkdirSync('/loader-cjs-source-app/dot-reexport-dir', { recursive: true }); + fs.writeFileSync('/loader-cjs-source-app/dot-reexport-dir/index.js', 'exports.dotReexported = 103;'); + fs.writeFileSync('/loader-cjs-source-app/guard-dep.cjs', 'exports.foo = "foo"; exports.bar = "bar";'); + fs.writeFileSync('/loader-cjs-source-app/direct-guard-dep.cjs', 'exports.directGuarded = 93;'); + fs.writeFileSync('/loader-cjs-source-app/object-guard-dep.cjs', 'exports.objectGuarded = 94;'); + fs.writeFileSync('/loader-cjs-source-app/prototype-guard-dep.cjs', 'exports.prototypeGuarded = 95;'); + fs.writeFileSync('/loader-cjs-source-app/nested-dep.cjs', 'exports.nested = { nestedValue: 92 };'); + fs.writeFileSync('/loader-cjs-source-app/tag-dep.cjs', 'module.exports = function tag() { return { reexported: 1 }; }; module.exports.reexported = 91;'); + fs.writeFileSync('/loader-cjs-source-app/aliased-dep.cjs', 'exports.aliasValue = 77;'); + fs.mkdirSync('/loader-cjs-source-app/node_modules/loader-bare-dep', { recursive: true }); + fs.writeFileSync('/loader-cjs-source-app/node_modules/loader-bare-dep/package.json', JSON.stringify({ main: 'index.cjs' })); + fs.writeFileSync('/loader-cjs-source-app/node_modules/loader-bare-dep/index.cjs', 'exports.bareReexported = 101;'); + await import('data:text/javascript,' + encodeURIComponent([ + 'import assert from "node:assert";', + 'import { register } from "node:module";', + 'function resolve(specifier, context, next) {', + ' if (specifier === "virtual:loader-cjs") {', + ' return { shortCircuit: true, url: "virtual:loader-cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:sync-chain-incomplete") {', + ' return { url: "virtual:sync-chain-incomplete", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-file") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/source.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-bare-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/bare-reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-imports-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/imports-reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-dot-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/dot-reexport-dir/reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-reexport-continuation") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/reexport-continuation.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-member-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/member-reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-member-define-property") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/member-define-property.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-member-exports") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/member-exports.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-member-module") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/member-module.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-bare-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-bare-reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-imports-reexport") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-imports-reexport.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-member-require") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-member-require.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-string-first-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-string-first-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-asi") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-asi.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-asi-unary") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-asi-unary.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-asi-before-binding") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-asi-before-binding.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-commented") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-commented.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-line-comment-boundary") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-line-comment-boundary.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-block-comment-boundary") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-block-comment-boundary.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-hasown-return-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-hasown-return-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-hasown-return-negative") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-hasown-return-negative.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-duplicate-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-duplicate-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-duplicate-enumerable") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-duplicate-enumerable.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-getter-only") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-getter-only.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-getter-before-enumerable") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-getter-before-enumerable.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-direct-hasown-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-direct-hasown-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-object-hasown-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-object-hasown-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-prototype-hasown-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-prototype-hasown-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-semantic-guard") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-semantic-guard.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-negative") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-negative.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-rhs-member") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-rhs-member.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-nested") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-nested.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-scoped-binding") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-scoped-binding.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-continuation") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-continuation.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-keys-tagged-template") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/keys-tagged-template.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-exports-reassign") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/exports-reassign.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-view") {', + ' return { shortCircuit: true, url: "virtual:loader-cjs-view", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-proto-assignment") {', + ' return { shortCircuit: true, url: "virtual:loader-cjs-proto-assignment", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-object-require-spread-relative") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/object-require-spread-relative.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-object-require-spread-member") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/object-require-spread-member.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-object-require-spread-call") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/object-require-spread-call.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-object-require-spread-optional") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/object-require-spread-optional.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-object-require-spread-bracket") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/object-require-spread-bracket.cjs", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-object-require-spread-tagged") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/object-require-spread-tagged.cjs", format: "commonjs" };', + ' }', + ' if (specifier.startsWith("virtual:loader-cjs-object-")) {', + ' return { shortCircuit: true, url: specifier, format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-define-getters") {', + ' return { shortCircuit: true, url: specifier, format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-collision-a") {', + ' return { shortCircuit: true, url: "virtual:a:b", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:loader-cjs-collision-b") {', + ' return { shortCircuit: true, url: "virtual:a_3Ab", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:child") {', + ' return { shortCircuit: true, url: "virtual:child", format: "commonjs" };', + ' }', + ' if (specifier === "alias-from-next") {', + ' return next("./aliased-dep.cjs", { parentURL: "file:///loader-cjs-source-app/entry.cjs" });', + ' }', + ' if (specifier === "alias-fs") {', + ' return { shortCircuit: true, url: "node:fs", format: "builtin" };', + ' }', + ' if (specifier === "virtual:file-query-a") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/query.cjs?one", format: "commonjs" };', + ' }', + ' if (specifier === "virtual:file-query-b") {', + ' return { shortCircuit: true, url: "file:///loader-cjs-source-app/query.cjs?two", format: "commonjs" };', + ' }', + ' return next(specifier, context);', + '}', + 'function sourceView(text) {', + ' const bytes = new Uint8Array(text.length + 4);', + ' bytes[0] = 33;', + ' bytes[1] = 33;', + ' for (let i = 0; i < text.length; i++) bytes[i + 2] = text.charCodeAt(i);', + ' bytes[text.length + 2] = 33;', + ' bytes[text.length + 3] = 33;', + ' return bytes.subarray(2, text.length + 2);', + '}', + 'function load(url, context, next) {', + ' if (url === "virtual:loader-cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "const fs = require(\\"node:fs\\");",', + ' "module.exports = fs;",', + ' "module.exports.__fromLoader = true;",', + ' "module.exports.virtualFilename = __filename;",', + ' "module.exports.virtualDirname = __dirname;",', + ' "module.exports.virtualModuleId = module.id;",', + ' "module.exports.virtualModuleFilename = module.filename;",', + ' "module.exports[\\"escaped\\\\u004eame\\"] = 42;",', + ' "module.exports[\\"brace\\\\u{4e}ame\\"] = 84;",', + ' "module.exports.constructor = \\"own-constructor\\";",', + ' "module.exports.toString = \\"own-toString\\";",', + ' "module.exports.__proto__ = \\"assigned-proto\\";",', + ' "Object.defineProperty(module.exports, \\"definedValue\\", { value: 64 });",', + ' "Object.defineProperty(module.exports, \\"definedProto\\", { value: module.exports.__proto__ });",', + ' "Object.defineProperty(module.exports, \\"__proto__\\", { value: \\"own-proto\\" });",', + ' "const child = require(\\"virtual:child\\");",', + ' "module.exports.childValue = child.value;",', + ' "module.exports.aliasValue = require(\\"alias-from-next\\").aliasValue;",', + ' "module.exports.aliasResolved = require.resolve(\\"alias-from-next\\");",', + ' "module.exports.aliasFsReadFile = require(\\"alias-fs\\").readFile;",', + ' "module.exports.aliasFsResolved = require.resolve(\\"alias-fs\\");",', + ' "module.exports.childConditions = child.conditions;",', + ' "module.exports.childFromView = child.fromView;",', + ' "module.exports.moduleRequireValue = module.require(\\"virtual:child\\").value;",', + ' "module.exports.childResolved = require.resolve(\\"virtual:child\\");",', + ' "module.exports.childResolvedWithOptions = require.resolve(\\"virtual:child\\", {});",', + ' "try { require(\\"virtual:sync-chain-incomplete\\"); } catch (e) { module.exports.syncChainErrorCode = e.code; }",', + ' "exports.readFile = fs.readFile;",', + ' "exports.__fromLoader = true;",', + ' "const obj = { exports: {}, module: { exports: {} } };",', + ' "obj.exports.falsePositive = true;",', + ' "obj.module.exports.falsePositive = true;"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "virtual:loader-cjs-view") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: (() => {', + ' return sourceView("exports.fromView = true;");', + ' })()', + ' };', + ' }', + ' if (url === "virtual:loader-cjs-proto-assignment") {', + ' return { shortCircuit: true, format: "commonjs", source: "module.exports.__proto__ = \\"assigned-proto\\";" };', + ' }', + ' if (url === "virtual:loader-cjs-object-values") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; const ns = { member: \\"member-value\\" }; module.exports = { shorthand: v, member: ns.member, call: factory(), after: v }; function factory() { return \\"call-value\\"; }" };', + ' }', + ' if (url === "virtual:loader-cjs-object-computed") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; const key = \\"computed\\"; module.exports = { before: v, [key]: 2, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-spread") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; const other = { spread: 2 }; module.exports = { before: v, ...other, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-require-spread") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'virtual:child\'), after: v };" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/object-require-spread-relative.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'./reexport-dep.cjs\'), after: v };" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/object-require-spread-member.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'./reexport-dep.cjs\').nested, after: v };" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/object-require-spread-call.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'./tag-dep.cjs\')(), after: v };" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/object-require-spread-optional.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'./reexport-dep.cjs\')?.nested, after: v };" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/object-require-spread-bracket.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'./reexport-dep.cjs\')[\'nested\'], after: v };" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/object-require-spread-tagged.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, ...require(\'./tag-dep.cjs\')`x`, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-call-spread") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; const other = () => ({ spread: 2 }); module.exports = { before: v, ...other(), after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-paren-spread") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; const other = { spread: 2 }; module.exports = { before: v, ...(other), after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-member-spread") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; const ns = { other: { spread: 2 } }; module.exports = { before: v, ...ns.other, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-literals") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = \\"after\\"; module.exports = { stringLiteral: \\"no\\", numberLiteral: 1, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-primitives") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = \\"after\\"; module.exports = { trueValue: true, falseValue: false, nullValue: null, undefinedValue: undefined, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-object-accessor") {', + ' return { shortCircuit: true, format: "commonjs", source: "const v = 1; module.exports = { before: v, get getter() { return 2; }, after: v };" };', + ' }', + ' if (url === "virtual:loader-cjs-define-getters") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "const dep = { value: \\"getter-value\\" };",', + ' "const value = \\"shorthand-value\\";",', + ' "Object.defineProperty(exports, \\"getterExport\\", { get() { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"functionGetterExport\\", { get: function () { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"namedFunctionGetterExport\\", { get: function getValue() { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"bracketGetterExport\\", { get() { return dep[\\"value\\"]; } });",', + ' "Object.defineProperty(exports, \\"valueThenValue\\", { value: \\"first\\", value: \\"second\\" });",', + ' "Object.defineProperty(exports, \\"valueThenString\\", { value: \\"good\\", \\"value\\": \\"string-wins\\" });",', + ' "Object.defineProperty(exports, \\"valueThenComputed\\", { value: \\"good\\", [\\"value\\"]: \\"computed-wins\\" });",', + ' "Object.defineProperty(exports, \\"valueThenShorthand\\", { value: \\"first\\", value });",', + ' "Object.defineProperty(exports, \\"valueThenMethod\\", { value: \\"first\\", value() { return \\"method-value\\"; } });",', + ' "Object.defineProperty(exports, \\"arrowGetter\\", { get: () => dep.value });",', + ' "Object.defineProperty(exports, \\"stringKeyGetter\\", { \\"get\\": function () { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"shorthandValue\\", { value });",', + ' "Object.defineProperty(exports, \\"computedValue\\", { [\\"value\\"]: 1 });",', + ' "Object.defineProperty(exports, \\"multiStatementGetter\\", { get() { const v = dep.value; return v; } });",', + ' "Object.defineProperty(exports, \\"helperValueDescriptor\\", makeDescriptor({ value: dep.value }));",', + ' "Object.defineProperty(exports, \\"parameterGetter\\", { get(a) { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"parameterFunctionGetter\\", { get: function (a) { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"helperDescriptor\\", makeDescriptor({ get() { return dep.value; } }));",', + ' "Object.defineProperty(exports, \\"nestedMemberGetter\\", { get() { return dep.value.nested; } });",', + ' "Object.defineProperty(exports, \\"nestedBracketGetter\\", { get() { return dep[\\"value\\"][\\"nested\\"]; } });",', + ' "Object.defineProperty(exports, \\"duplicateGet\\", { get() { return dep.value; }, get: function (a) { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"stringThenValue\\", { \\"value\\": \\"bad\\", value: dep.value });",', + ' "Object.defineProperty(exports, \\"computedThenValue\\", { [\\"value\\"]: \\"bad\\", value: dep.value });",', + ' "Object.defineProperty(exports, \\"writableThenValue\\", { writable: true, value: dep.value });",', + ' "Object.defineProperty(exports, \\"configurableThenValue\\", { configurable: true, value: dep.value });",', + ' "Object.defineProperty(exports, \\"quotedEnumerableThenValue\\", { \\"enumerable\\": true, value: dep.value });",', + ' "Object.defineProperty(exports, \\"valueThenFalseEnumerable\\", { value: dep.value, enumerable: false });",', + ' "Object.defineProperty(exports, \\"hiddenGetter\\", { enumerable: false, get() { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"truthyEnumerableGetter\\", { enumerable: 1, get() { return dep.value; } });",', + ' "Object.defineProperty(exports, \\"getterThenEnumerable\\", { get() { return dep.value; }, enumerable: true });",', + ' "function makeDescriptor(descriptor) { return descriptor; }"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "virtual:a:b") {', + ' return { shortCircuit: true, format: "commonjs", source: "exports.marker = \\"colon\\";" };', + ' }', + ' if (url === "virtual:a_3Ab") {', + ' return { shortCircuit: true, format: "commonjs", source: "exports.marker = \\"underscore\\";" };', + ' }', + ' if (url === "virtual:child") {', + ' return { shortCircuit: true, format: "commonjs", source: sourceView("exports.value = 123; exports.fromView = true; exports.conditions = " + JSON.stringify(context.conditions) + ";") };', + ' }', + ' if (url === "file:///loader-cjs-source-app/query.cjs?one") {', + ' return { shortCircuit: true, format: "commonjs", source: "exports.query = \\"one\\"; exports.filename = __filename; exports.moduleId = module.id; exports.moduleFilename = module.filename;" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/query.cjs?two") {', + ' return { shortCircuit: true, format: "commonjs", source: "exports.query = \\"two\\"; exports.filename = __filename; exports.moduleId = module.id; exports.moduleFilename = module.filename;" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/source.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "this.fromThis = true;",', + ' "exports.filename = __filename;",', + ' "exports.dirname = __dirname;",', + ' "exports.dep = require(\\"./dep.cjs\\").depValue;",', + ' "exports.beforeReturn = true;",', + ' "return;",', + ' "exports.afterReturn = true;"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/reexport.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "module.exports = require(\\"./reexport-dep.cjs\\");" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/bare-reexport.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "module.exports = require(\\"loader-bare-dep\\");" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/imports-reexport.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "module.exports = require(\\"#loader-import\\");" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/dot-reexport-dir/reexport.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "module.exports = require(\\".\\");" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/reexport-continuation.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "module.exports = require(\\"./nested-dep.cjs\\").nested;" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/member-reexport.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const holder = { require() { return { local: \\"local\\" }; } }; module.exports = holder. /* member require */ require(\\"./reexport-dep.cjs\\");" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/member-define-property.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const holder = { Object: { defineProperty() {} } }; holder. /* member Object */ Object.defineProperty(exports, \\"ghost\\", { value: \\"ghost\\" }); exports.real = \\"real\\";" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/member-exports.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const holder = { exports: {} }; holder. /* member exports */ exports.foo = 1;" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/member-module.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "const holder = { module: { exports: {} } }; holder. /* member module */ module.exports.foo = 1;" };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-reexport.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-bare-reexport.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"loader-bare-dep\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-imports-reexport.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"#loader-import\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-member-require.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "const holder = { require() { return {}; } };",', + ' "var dep = holder. /* member require binding */ require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-string-first-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (\\"default\\" === key || \\"__esModule\\" === key) return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-asi.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\")",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key]",', + ' "})",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-asi-unary.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\")",', + ' "!function () {}();",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key]",', + ' "})",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-asi-before-binding.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "init()",', + ' "var dep = require(\\"./reexport-dep.cjs\\")",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key]",', + ' "})",', + ' "exports.own = \\"own-value\\";",', + ' "function init() {}"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-commented.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "/* header */ var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-line-comment-boundary.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\")// comment before statement boundary",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-block-comment-boundary.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\")/* block comment before statement boundary */",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-hasown-return-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " if (Object.prototype.hasOwnProperty.call(exports, key)) return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-hasown-return-negative.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./guard-dep.cjs\\");",', + ' "var skip = { foo: true };",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " if (skip.hasOwnProperty(key)) return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-duplicate-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " if (key in exports && exports[key] === dep[key]) return;",', + ' " Object.defineProperty(exports, key, { enumerable: true, get: function () { return dep[key]; } });",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-duplicate-enumerable.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " Object.defineProperty(exports, key, { enumerable: true, enumerable: true, get: function () { return dep[key]; } });",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-getter-only.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " Object.defineProperty(exports, key, { get: function () { return dep[key]; } });",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-getter-before-enumerable.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " Object.defineProperty(exports, key, { get: function () { return dep[key]; }, enumerable: true });",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-direct-hasown-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./direct-guard-dep.cjs\\");",', + ' "var directExportNames = {};",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key !== \\"default\\" && !directExportNames.hasOwnProperty(key)) exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-object-hasown-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./object-guard-dep.cjs\\");",', + ' "var objectExportNames = {};",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key !== \\"default\\" && !Object.hasOwnProperty.call(objectExportNames, key)) exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-prototype-hasown-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./prototype-guard-dep.cjs\\");",', + ' "var prototypeExportNames = {};",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key !== \\"default\\" && !Object.prototype.hasOwnProperty.call(prototypeExportNames, key)) exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-semantic-guard.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./guard-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " if (key === \\"foo\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-negative.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-rhs-member.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./nested-dep.cjs\\").nested;",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key].nested;",', + ' "});",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key] /* comment */ .nested;",', + ' "});",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key][\\"nested\\"];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-nested.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./reexport-dep.cjs\\");",', + ' "function copy() {",', + ' " Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' " });",', + ' "}",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-scoped-binding.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = {};",', + ' "function init() {; var dep = require(\\"./reexport-dep.cjs\\"); }",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-continuation.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./nested-dep.cjs\\").nested;",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/keys-tagged-template.cjs") {', + ' return {', + ' shortCircuit: true,', + ' format: "commonjs",', + ' source: [', + ' "var dep = require(\\"./tag-dep.cjs\\")",', + ' "`ignored`",', + ' "Object.keys(dep).forEach(function (key) {",', + ' " if (key === \\"default\\" || key === \\"__esModule\\") return;",', + ' " exports[key] = dep[key];",', + ' "});",', + ' "exports.own = \\"own-value\\";"', + ' ].join("\\n")', + ' };', + ' }', + ' if (url === "file:///loader-cjs-source-app/exports-reassign.cjs") {', + ' return { shortCircuit: true, format: "commonjs", source: "exports = require(\\"./reexport-dep.cjs\\");" };', + ' }', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent(sourceView + "; export " + resolve + "; export " + load));', + 'const ns = await import("virtual:loader-cjs");', + 'assert.strictEqual(typeof ns.default.readFile, "function");', + 'assert.strictEqual(ns.readFile, ns.default.readFile);', + 'assert.strictEqual(ns.__fromLoader, true);', + 'assert.strictEqual(ns.default.__fromLoader, true);', + 'assert.strictEqual(ns.virtualFilename, "virtual:loader-cjs");', + 'assert.strictEqual(ns.virtualDirname, ".");', + 'assert.strictEqual(ns.virtualModuleId, "virtual:loader-cjs");', + 'assert.strictEqual(ns.virtualModuleFilename, "virtual:loader-cjs");', + 'assert.strictEqual(ns.escapedName, 42);', + 'assert.strictEqual(ns.braceName, 84);', + 'assert.strictEqual(ns.constructor, "own-constructor");', + 'assert.strictEqual(ns.toString, "own-toString");', + 'assert.strictEqual(ns.__proto__, "own-proto");', + 'assert.strictEqual(ns.definedValue, 64);', + 'assert.notStrictEqual(ns.definedProto, "assigned-proto");', + 'assert.strictEqual(ns.childValue, 123);', + 'assert.strictEqual(ns.aliasValue, 77);', + 'assert.strictEqual(ns.aliasResolved, "/loader-cjs-source-app/aliased-dep.cjs");', + 'assert.strictEqual(typeof ns.aliasFsReadFile, "function");', + 'assert.strictEqual(ns.aliasFsResolved, "fs");', + 'assert.strictEqual(ns.childFromView, true);', + 'assert(ns.childConditions.includes("require"));', + 'assert(!ns.childConditions.includes("import"));', + 'assert.strictEqual(ns.moduleRequireValue, 123);', + 'assert.strictEqual(ns.childResolved, "virtual:child");', + 'assert.strictEqual(ns.childResolvedWithOptions, "virtual:child");', + 'assert.strictEqual(ns.syncChainErrorCode, "ERR_LOADER_CHAIN_INCOMPLETE");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(ns, "falsePositive"), false);', + 'assert.strictEqual((await import("virtual:loader-cjs-view")).fromView, true);', + 'assert.strictEqual((await import("virtual:loader-cjs-proto-assignment"))["__proto__"], undefined);', + 'const objectValues = await import("virtual:loader-cjs-object-values");', + 'assert.strictEqual(objectValues.shorthand, 1);', + 'assert.strictEqual(objectValues.member, "member-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(objectValues, "call"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(objectValues, "after"), false);', + 'const computedObject = await import("virtual:loader-cjs-object-computed");', + 'assert.strictEqual(computedObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(computedObject, "computed"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(computedObject, "after"), false);', + 'const spreadObject = await import("virtual:loader-cjs-object-spread");', + 'assert.strictEqual(spreadObject.before, 1);', + 'assert.strictEqual(spreadObject.after, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(spreadObject, "spread"), false);', + 'const requireSpreadObject = await import("virtual:loader-cjs-object-require-spread");', + 'assert.strictEqual(requireSpreadObject.before, 1);', + 'assert.strictEqual(requireSpreadObject.after, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(requireSpreadObject, "value"), false);', + 'const relativeRequireSpreadObject = await import("virtual:loader-cjs-object-require-spread-relative");', + 'assert.strictEqual(relativeRequireSpreadObject.before, 1);', + 'assert.strictEqual(relativeRequireSpreadObject.after, 1);', + 'assert.strictEqual(relativeRequireSpreadObject.reexported, 91);', + 'const memberRequireSpreadObject = await import("virtual:loader-cjs-object-require-spread-member");', + 'assert.strictEqual(memberRequireSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberRequireSpreadObject, "after"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberRequireSpreadObject, "reexported"), true);', + 'assert.strictEqual(memberRequireSpreadObject.reexported, undefined);', + 'const callRequireSpreadObject = await import("virtual:loader-cjs-object-require-spread-call");', + 'assert.strictEqual(callRequireSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(callRequireSpreadObject, "after"), false);', + 'assert.strictEqual(callRequireSpreadObject.reexported, 1);', + 'const optionalRequireSpreadObject = await import("virtual:loader-cjs-object-require-spread-optional");', + 'assert.strictEqual(optionalRequireSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(optionalRequireSpreadObject, "after"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(optionalRequireSpreadObject, "reexported"), true);', + 'assert.strictEqual(optionalRequireSpreadObject.reexported, undefined);', + 'const bracketRequireSpreadObject = await import("virtual:loader-cjs-object-require-spread-bracket");', + 'assert.strictEqual(bracketRequireSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(bracketRequireSpreadObject, "after"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(bracketRequireSpreadObject, "reexported"), true);', + 'assert.strictEqual(bracketRequireSpreadObject.reexported, undefined);', + 'const taggedRequireSpreadObject = await import("virtual:loader-cjs-object-require-spread-tagged");', + 'assert.strictEqual(taggedRequireSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(taggedRequireSpreadObject, "after"), false);', + 'assert.strictEqual(taggedRequireSpreadObject.reexported, 1);', + 'const callSpreadObject = await import("virtual:loader-cjs-object-call-spread");', + 'assert.strictEqual(callSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(callSpreadObject, "after"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(callSpreadObject, "spread"), false);', + 'const parenSpreadObject = await import("virtual:loader-cjs-object-paren-spread");', + 'assert.strictEqual(parenSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(parenSpreadObject, "after"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(parenSpreadObject, "spread"), false);', + 'const memberSpreadObject = await import("virtual:loader-cjs-object-member-spread");', + 'assert.strictEqual(memberSpreadObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberSpreadObject, "after"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberSpreadObject, "spread"), false);', + 'const literalObject = await import("virtual:loader-cjs-object-literals");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(literalObject, "stringLiteral"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(literalObject, "numberLiteral"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(literalObject, "after"), false);', + 'const primitiveObject = await import("virtual:loader-cjs-object-primitives");', + 'assert.strictEqual(primitiveObject.trueValue, true);', + 'assert.strictEqual(primitiveObject.falseValue, false);', + 'assert.strictEqual(primitiveObject.nullValue, null);', + 'assert.strictEqual(primitiveObject.undefinedValue, undefined);', + 'assert.strictEqual(primitiveObject.after, "after");', + 'const accessorObject = await import("virtual:loader-cjs-object-accessor");', + 'assert.strictEqual(accessorObject.before, 1);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(accessorObject, "get"), true);', + 'assert.strictEqual(accessorObject.get, undefined);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(accessorObject, "getter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(accessorObject, "after"), false);', + 'const defineGetters = await import("virtual:loader-cjs-define-getters");', + 'assert.strictEqual(defineGetters.getterExport, "getter-value");', + 'assert.strictEqual(defineGetters.functionGetterExport, "getter-value");', + 'assert.strictEqual(defineGetters.namedFunctionGetterExport, "getter-value");', + 'assert.strictEqual(defineGetters.bracketGetterExport, "getter-value");', + 'assert.strictEqual(defineGetters.valueThenValue, "second");', + 'assert.strictEqual(defineGetters.valueThenString, "string-wins");', + 'assert.strictEqual(defineGetters.valueThenComputed, "computed-wins");', + 'assert.strictEqual(defineGetters.valueThenShorthand, "shorthand-value");', + 'assert.strictEqual(typeof defineGetters.valueThenMethod, "function");', + 'assert.strictEqual(defineGetters.valueThenMethod(), "method-value");', + 'assert.strictEqual(defineGetters.valueThenFalseEnumerable, "getter-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "arrowGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "stringKeyGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "shorthandValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "computedValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "multiStatementGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "helperValueDescriptor"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "parameterGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "parameterFunctionGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "helperDescriptor"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "nestedMemberGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "nestedBracketGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "duplicateGet"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "stringThenValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "computedThenValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "writableThenValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "configurableThenValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "quotedEnumerableThenValue"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "hiddenGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "truthyEnumerableGetter"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(defineGetters, "getterThenEnumerable"), false);', + 'assert.strictEqual((await import("virtual:loader-cjs-collision-a")).marker, "colon");', + 'assert.strictEqual((await import("virtual:loader-cjs-collision-b")).marker, "underscore");', + 'const fileQueryA = await import("virtual:file-query-a");', + 'const fileQueryB = await import("virtual:file-query-b");', + 'assert.strictEqual(fileQueryA.query, "one");', + 'assert.strictEqual(fileQueryB.query, "one");', + 'assert.strictEqual(fileQueryA.default, fileQueryB.default);', + 'assert.strictEqual(fileQueryA.filename, "/loader-cjs-source-app/query.cjs");', + 'assert.strictEqual(fileQueryB.filename, "/loader-cjs-source-app/query.cjs");', + 'assert.strictEqual(fileQueryA.moduleId, "/loader-cjs-source-app/query.cjs");', + 'assert.strictEqual(fileQueryB.moduleId, "/loader-cjs-source-app/query.cjs");', + 'assert.strictEqual(fileQueryA.moduleFilename, "/loader-cjs-source-app/query.cjs");', + 'assert.strictEqual(fileQueryB.moduleFilename, "/loader-cjs-source-app/query.cjs");', + 'assert.strictEqual((await import("virtual:loader-cjs-reexport")).reexported, 91);', + 'assert.strictEqual((await import("virtual:loader-cjs-bare-reexport")).bareReexported, 101);', + 'assert.strictEqual((await import("virtual:loader-cjs-imports-reexport")).importsReexported, 102);', + 'assert.strictEqual((await import("virtual:loader-cjs-dot-reexport")).dotReexported, 103);', + 'const continuationReexport = await import("virtual:loader-cjs-reexport-continuation");', + 'assert.strictEqual(continuationReexport.default.nestedValue, 92);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(continuationReexport, "nested"), true);', + 'assert.strictEqual(continuationReexport.nested, undefined);', + 'const memberReexport = await import("virtual:loader-cjs-member-reexport");', + 'assert.strictEqual(memberReexport.default.local, "local");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberReexport, "reexported"), false);', + 'const memberDefineProperty = await import("virtual:loader-cjs-member-define-property");', + 'assert.strictEqual(memberDefineProperty.real, "real");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberDefineProperty, "ghost"), true);', + 'assert.strictEqual(memberDefineProperty.ghost, undefined);', + 'const memberExports = await import("virtual:loader-cjs-member-exports");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberExports, "foo"), true);', + 'assert.strictEqual(memberExports.foo, undefined);', + 'const memberModule = await import("virtual:loader-cjs-member-module");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(memberModule, "foo"), true);', + 'assert.strictEqual(memberModule.foo, undefined);', + 'const keysReexport = await import("virtual:loader-cjs-keys-reexport");', + 'assert.strictEqual(keysReexport.reexported, 91);', + 'assert.strictEqual(keysReexport.own, "own-value");', + 'const keysBareReexport = await import("virtual:loader-cjs-keys-bare-reexport");', + 'assert.strictEqual(keysBareReexport.bareReexported, 101);', + 'assert.strictEqual(keysBareReexport.own, "own-value");', + 'const keysImportsReexport = await import("virtual:loader-cjs-keys-imports-reexport");', + 'assert.strictEqual(keysImportsReexport.importsReexported, 102);', + 'assert.strictEqual(keysImportsReexport.own, "own-value");', + 'const keysMemberRequire = await import("virtual:loader-cjs-keys-member-require");', + 'assert.strictEqual(keysMemberRequire.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysMemberRequire, "reexported"), false);', + 'const keysStringFirstGuard = await import("virtual:loader-cjs-keys-string-first-guard");', + 'assert.strictEqual(keysStringFirstGuard.default.reexported, 91);', + 'assert.strictEqual(keysStringFirstGuard.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysStringFirstGuard, "reexported"), false);', + 'const keysAsi = await import("virtual:loader-cjs-keys-asi");', + 'assert.strictEqual(keysAsi.reexported, 91);', + 'assert.strictEqual(keysAsi.own, "own-value");', + 'const keysAsiUnary = await import("virtual:loader-cjs-keys-asi-unary");', + 'assert.strictEqual(keysAsiUnary.reexported, 91);', + 'assert.strictEqual(keysAsiUnary.own, "own-value");', + 'const keysAsiBeforeBinding = await import("virtual:loader-cjs-keys-asi-before-binding");', + 'assert.strictEqual(keysAsiBeforeBinding.reexported, 91);', + 'assert.strictEqual(keysAsiBeforeBinding.own, "own-value");', + 'const keysCommented = await import("virtual:loader-cjs-keys-commented");', + 'assert.strictEqual(keysCommented.reexported, 91);', + 'assert.strictEqual(keysCommented.own, "own-value");', + 'const keysLineCommentBoundary = await import("virtual:loader-cjs-keys-line-comment-boundary");', + 'assert.strictEqual(keysLineCommentBoundary.reexported, 91);', + 'assert.strictEqual(keysLineCommentBoundary.own, "own-value");', + 'const keysBlockCommentBoundary = await import("virtual:loader-cjs-keys-block-comment-boundary");', + 'assert.strictEqual(keysBlockCommentBoundary.reexported, 91);', + 'assert.strictEqual(keysBlockCommentBoundary.own, "own-value");', + 'const keysHasOwnReturnGuard = await import("virtual:loader-cjs-keys-hasown-return-guard");', + 'assert.strictEqual(keysHasOwnReturnGuard.reexported, 91);', + 'assert.strictEqual(keysHasOwnReturnGuard.own, "own-value");', + 'const keysHasOwnReturnNegative = await import("virtual:loader-cjs-keys-hasown-return-negative");', + 'assert.strictEqual(keysHasOwnReturnNegative.default.bar, "bar");', + 'assert.strictEqual(keysHasOwnReturnNegative.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysHasOwnReturnNegative, "foo"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysHasOwnReturnNegative, "bar"), false);', + 'const keysDuplicateGuard = await import("virtual:loader-cjs-keys-duplicate-guard");', + 'assert.strictEqual(keysDuplicateGuard.reexported, 91);', + 'assert.strictEqual(keysDuplicateGuard.own, "own-value");', + 'const keysDuplicateEnumerable = await import("virtual:loader-cjs-keys-duplicate-enumerable");', + 'assert.strictEqual(keysDuplicateEnumerable.default.reexported, 91);', + 'assert.strictEqual(keysDuplicateEnumerable.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysDuplicateEnumerable, "reexported"), false);', + 'const keysGetterOnly = await import("virtual:loader-cjs-keys-getter-only");', + 'assert.strictEqual(keysGetterOnly.default.reexported, 91);', + 'assert.strictEqual(keysGetterOnly.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysGetterOnly, "reexported"), false);', + 'const keysGetterBeforeEnumerable = await import("virtual:loader-cjs-keys-getter-before-enumerable");', + 'assert.strictEqual(keysGetterBeforeEnumerable.default.reexported, 91);', + 'assert.strictEqual(keysGetterBeforeEnumerable.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysGetterBeforeEnumerable, "reexported"), false);', + 'const keysDirectHasOwnGuard = await import("virtual:loader-cjs-keys-direct-hasown-guard");', + 'assert.strictEqual(keysDirectHasOwnGuard.directGuarded, 93);', + 'assert.strictEqual(keysDirectHasOwnGuard.own, "own-value");', + 'const keysObjectHasOwnGuard = await import("virtual:loader-cjs-keys-object-hasown-guard");', + 'assert.strictEqual(keysObjectHasOwnGuard.objectGuarded, 94);', + 'assert.strictEqual(keysObjectHasOwnGuard.own, "own-value");', + 'const keysPrototypeHasOwnGuard = await import("virtual:loader-cjs-keys-prototype-hasown-guard");', + 'assert.strictEqual(keysPrototypeHasOwnGuard.prototypeGuarded, 95);', + 'assert.strictEqual(keysPrototypeHasOwnGuard.own, "own-value");', + 'const keysSemanticGuard = await import("virtual:loader-cjs-keys-semantic-guard");', + 'assert.strictEqual(keysSemanticGuard.default.bar, "bar");', + 'assert.strictEqual(keysSemanticGuard.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysSemanticGuard, "foo"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysSemanticGuard, "bar"), false);', + 'const keysNegative = await import("virtual:loader-cjs-keys-negative");', + 'assert.strictEqual(keysNegative.default.reexported, 91);', + 'assert.strictEqual(keysNegative.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysNegative, "reexported"), false);', + 'const keysRhsMember = await import("virtual:loader-cjs-keys-rhs-member");', + 'assert.strictEqual(keysRhsMember.default.nestedValue, undefined);', + 'assert.strictEqual(keysRhsMember.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysRhsMember, "nestedValue"), false);', + 'const keysNested = await import("virtual:loader-cjs-keys-nested");', + 'assert.strictEqual(keysNested.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysNested, "reexported"), false);', + 'const keysScopedBinding = await import("virtual:loader-cjs-keys-scoped-binding");', + 'assert.strictEqual(keysScopedBinding.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysScopedBinding, "reexported"), false);', + 'const keysContinuation = await import("virtual:loader-cjs-keys-continuation");', + 'assert.strictEqual(keysContinuation.default.nestedValue, 92);', + 'assert.strictEqual(keysContinuation.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysContinuation, "nestedValue"), false);', + 'const keysTaggedTemplate = await import("virtual:loader-cjs-keys-tagged-template");', + 'assert.strictEqual(keysTaggedTemplate.default.reexported, 1);', + 'assert.strictEqual(keysTaggedTemplate.own, "own-value");', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(keysTaggedTemplate, "reexported"), false);', + 'assert.strictEqual(Object.prototype.hasOwnProperty.call(await import("virtual:loader-cjs-exports-reassign"), "reexported"), false);', + 'const fileNs = await import("virtual:loader-cjs-file");', + 'assert.strictEqual(fileNs.default.fromThis, true);', + 'assert.strictEqual(fileNs.filename, "/loader-cjs-source-app/source.cjs");', + 'assert.strictEqual(fileNs.dirname, "/loader-cjs-source-app");', + 'assert.strictEqual(fileNs.dep, 17);', + 'assert.strictEqual(fileNs.beforeReturn, true);', + 'assert.strictEqual(fileNs.afterReturn, undefined);', + ].join('\n'))); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testLoaderModuleSourceValidation = async () => { + try { + fs.mkdirSync('/loader-module-source-app', { recursive: true }); + fs.writeFileSync('/loader-module-source-app/as-module.ext', 'export default "from-ext"; export const named = 11;'); + fs.writeFileSync('/loader-module-source-app/null-source.cjs', 'exports.marker = "null-source";'); + fs.writeFileSync('/loader-module-source-app/inherited-null-source.cjs', 'exports.marker = "inherited-null-source";'); + fs.writeFileSync('/loader-module-source-app/undefined-source.cjs', 'exports.marker = "undefined-source";'); + + await import('data:text/javascript,' + encodeURIComponent([ + 'import assert from "node:assert";', + 'import { register } from "node:module";', + 'function sourceView(text) {', + ' const bytes = new TextEncoder().encode(text);', + ' return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);', + '}', + 'async function expectReject(label, promise, code, message) {', + ' let rejected = false;', + ' try {', + ' await promise;', + ' } catch (error) {', + ' rejected = true;', + ' assert.strictEqual(error && error.code, code, label);', + ' if (message) assert.match(error && error.message, message, label);', + ' }', + ' if (!rejected) throw new Error("Missing expected rejection: " + label);', + '}', + 'function resolve(specifier, context, next) {', + ' if (specifier === "virtual:module-source") return { shortCircuit: true, url: "virtual:module-source", format: "module" };', + ' if (specifier === "virtual:module-view") return { shortCircuit: true, url: "virtual:module-view", format: "module" };', + ' if (specifier === "virtual:static-url") return { shortCircuit: true, url: "data:text/javascript,export default 23;", format: "module" };', + ' if (specifier === "virtual:invalid-result") return { shortCircuit: true, url: "virtual:invalid-result", format: "module" };', + ' if (specifier === "virtual:invalid-source") return { shortCircuit: true, url: "virtual:invalid-source", format: "module" };', + ' if (specifier === "virtual:bad-format") return { shortCircuit: true, url: "virtual:bad-format", format: "module" };', + ' if (specifier === "virtual:empty-format") return { shortCircuit: true, url: "virtual:empty-format", format: "module" };', + ' if (specifier === "virtual:bad-url") return { shortCircuit: true, url: "not-a-url" };', + ' if (specifier === "virtual:missing-url") return { shortCircuit: true };', + ' if (specifier === "virtual:undefined-url") return { shortCircuit: true, url: undefined };', + ' if (specifier === "virtual:resolve-esm-hint") return { shortCircuit: true, url: "virtual:resolve-esm-hint", format: "esm" };', + ' if (specifier === "virtual:bad-resolve-format") return { shortCircuit: true, url: "virtual:bad-resolve-format", format: false };', + ' if (specifier === "virtual:cjs-null-source") return { shortCircuit: true, url: "file:///loader-module-source-app/null-source.cjs", format: "commonjs" };', + ' if (specifier === "virtual:cjs-inherited-null-source") return { shortCircuit: true, url: "file:///loader-module-source-app/inherited-null-source.cjs", format: "commonjs" };', + ' if (specifier === "virtual:cjs-undefined-source") return { shortCircuit: true, url: "file:///loader-module-source-app/undefined-source.cjs", format: "commonjs" };', + ' if (specifier === "virtual:bad-cjs-source") return { shortCircuit: true, url: "virtual:bad-cjs-source", format: "commonjs" };', + ' return next(specifier, context);', + '}', + 'function load(url, context, next) {', + ' if (url === "virtual:module-source") return { shortCircuit: true, format: "module", source: "export const named = 42; export default named;" };', + ' if (url === "virtual:module-view") return { shortCircuit: true, format: "module", source: sourceView("export default 7;") };', + ' if (url === "virtual:invalid-result") return "export default 0;";', + ' if (url === "virtual:invalid-source") return { shortCircuit: true, format: "module", source: [] };', + ' if (url === "virtual:bad-format") return { shortCircuit: true, format: "foo", source: "" };', + ' if (url === "virtual:empty-format") return { shortCircuit: true, format: "", source: "" };', + ' if (url === "virtual:resolve-esm-hint") return { shortCircuit: true, format: "module", source: "export default 19;" };', + ' if (url.endsWith("/null-source.cjs")) return { shortCircuit: true, format: "commonjs", source: null };', + ' if (url.endsWith("/inherited-null-source.cjs")) return { shortCircuit: true, source: null };', + ' if (url.endsWith("/undefined-source.cjs")) return { shortCircuit: true, format: "commonjs", source: undefined };', + ' if (url === "virtual:bad-cjs-source") return { shortCircuit: true, format: "commonjs", source: 1n };', + ' if (url.endsWith("/as-module.ext")) return next(url, { ...context, format: "module" });', + ' return next(url, context);', + '}', + 'register("data:text/javascript," + encodeURIComponent(sourceView + "; export " + resolve + "; export " + load));', + 'const sourced = await import("virtual:module-source");', + 'assert.strictEqual(sourced.default, 42);', + 'assert.strictEqual(sourced.named, 42);', + 'const staticConsumer = await import("data:text/javascript," + encodeURIComponent(', + ' "import value, { named } from \\"virtual:module-source\\"; import urlValue from \\"virtual:static-url\\"; export default { value, named, urlValue };"', + '));', + 'assert.deepStrictEqual(staticConsumer.default, { value: 42, named: 42, urlValue: 23 });', + 'assert.strictEqual((await import("virtual:module-view")).default, 7);', + 'const ext = await import("file:///loader-module-source-app/as-module.ext");', + 'assert.strictEqual(ext.default, "from-ext");', + 'assert.strictEqual(ext.named, 11);', + 'assert.strictEqual((await import("virtual:resolve-esm-hint")).default, 19);', + 'assert.strictEqual((await import("virtual:cjs-null-source")).marker, "null-source");', + 'assert.strictEqual((await import("virtual:cjs-inherited-null-source")).marker, "inherited-null-source");', + 'assert.strictEqual((await import("virtual:cjs-undefined-source")).marker, "undefined-source");', + 'await expectReject("load hook must return object", import("virtual:invalid-result"), "ERR_INVALID_RETURN_VALUE");', + 'await expectReject("resolve format type", import("virtual:bad-resolve-format"), "ERR_INVALID_RETURN_PROPERTY_VALUE");', + 'await expectReject("resolve url must be absolute", import("virtual:bad-url"), "ERR_INVALID_RETURN_PROPERTY_VALUE", /url.*resolve/);', + 'await expectReject("resolve url is required", import("virtual:missing-url"), "ERR_INVALID_RETURN_PROPERTY_VALUE", /url.*resolve/);', + 'await expectReject("resolve url cannot be undefined", import("virtual:undefined-url"), "ERR_INVALID_RETURN_PROPERTY_VALUE", /url.*resolve/);', + 'await expectReject("unknown module format", import("virtual:bad-format"), "ERR_UNKNOWN_MODULE_FORMAT");', + 'await expectReject("empty module format", import("virtual:empty-format"), "ERR_UNKNOWN_MODULE_FORMAT");', + 'await expectReject("invalid module source", import("virtual:invalid-source"), "ERR_INVALID_RETURN_PROPERTY_VALUE");', + 'await expectReject("invalid commonjs source", import("virtual:bad-cjs-source"), "ERR_INVALID_RETURN_PROPERTY_VALUE", /"source".*\'load\'.*got type bigint/);', + ].join('\n'))); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testSyncBuiltinEsmExports = async () => { + try { + const module = await import('node:module'); + const fsModule = await import('node:fs'); + const eventsModule = await import('node:events'); + const vmModule = await import('node:vm'); + + const fs = fsModule.default; + const originalReadFile = fs.readFile; + const originalReadFileSync = fs.readFileSync; + const originalWriteFile = fs.writeFile; + const originalExistsSync = fs.existsSync; + const originalOpenAsBlob = fs.openAsBlob; + const replacementReadFile = function replacementReadFile() {}; + const replacementReadFileSync = function replacementReadFileSync() {}; + const replacementWriteFile = function replacementWriteFile() {}; + const replacementExistsSync = function replacementExistsSync() {}; + const replacementOpenAsBlob = function replacementOpenAsBlob() {}; + + fs.readFile = replacementReadFile; + fs.readFileSync = replacementReadFileSync; + fs.writeFile = replacementWriteFile; + fs.existsSync = replacementExistsSync; + fs.openAsBlob = replacementOpenAsBlob; + module.syncBuiltinESMExports(); + assert.strictEqual(fsModule.readFile, replacementReadFile); + assert.strictEqual(fsModule.readFileSync, replacementReadFileSync); + assert.strictEqual(fsModule.writeFile, replacementWriteFile); + assert.strictEqual(fsModule.existsSync, replacementExistsSync); + assert.strictEqual(fsModule.openAsBlob, replacementOpenAsBlob); + + delete fs.readFile; + module.syncBuiltinESMExports(); + assert.strictEqual(fsModule.readFile, undefined); + + fs.readFile = originalReadFile; + fs.readFileSync = originalReadFileSync; + fs.writeFile = originalWriteFile; + fs.existsSync = originalExistsSync; + fs.openAsBlob = originalOpenAsBlob; + module.syncBuiltinESMExports(); + + const events = eventsModule.default; + const originalDefaultMaxListeners = events.defaultMaxListeners; + const originalOnce = events.once; + const originalGetMaxListeners = events.getMaxListeners; + const replacementOnce = function replacementOnce() {}; + const replacementGetMaxListeners = function replacementGetMaxListeners() {}; + events.defaultMaxListeners = originalDefaultMaxListeners + 1; + events.once = replacementOnce; + events.getMaxListeners = replacementGetMaxListeners; + module.syncBuiltinESMExports(); + assert.strictEqual(eventsModule.defaultMaxListeners, originalDefaultMaxListeners + 1); + assert.strictEqual(eventsModule.once, replacementOnce); + assert.strictEqual(eventsModule.getMaxListeners, replacementGetMaxListeners); + events.defaultMaxListeners = originalDefaultMaxListeners; + events.once = originalOnce; + events.getMaxListeners = originalGetMaxListeners; + module.syncBuiltinESMExports(); + + const moduleDefault = module.default; + const originalSyncBuiltinESMExports = moduleDefault.syncBuiltinESMExports; + const originalCreateRequire = moduleDefault.createRequire; + const replacementSyncBuiltinESMExports = function replacementSyncBuiltinESMExports() {}; + const replacementCreateRequire = function replacementCreateRequire() {}; + moduleDefault.syncBuiltinESMExports = replacementSyncBuiltinESMExports; + moduleDefault.createRequire = replacementCreateRequire; + originalSyncBuiltinESMExports(); + assert.strictEqual(module.syncBuiltinESMExports, replacementSyncBuiltinESMExports); + assert.strictEqual(module.createRequire, replacementCreateRequire); + moduleDefault.syncBuiltinESMExports = originalSyncBuiltinESMExports; + moduleDefault.createRequire = originalCreateRequire; + originalSyncBuiltinESMExports(); + + try { + await import('__wasm_rquickjs_builtin/vm_native'); + throw new Error('private builtin import should not resolve from user modules'); + } catch (error) { + assert.strictEqual(error.code, 'ERR_MODULE_NOT_FOUND'); + } + assert.throws(() => import.meta.resolve('__wasm_rquickjs_builtin/vm_native'), { code: 'ERR_MODULE_NOT_FOUND' }); + assert.throws(() => module.createRequire(import.meta.url)('__wasm_rquickjs_builtin/vm_native'), { code: 'MODULE_NOT_FOUND' }); + + async function expectPrivateBuiltinRejected(label, promise) { + try { + await promise; + } catch (error) { + assert.strictEqual(error.code, 'ERR_MODULE_NOT_FOUND', label); + return; + } + throw new Error('private builtin import should not resolve from ' + label); + } + + await expectPrivateBuiltinRejected( + 'data module', + import('data:text/javascript,' + encodeURIComponent('import "__wasm_rquickjs_builtin/vm_native"; export default true;')), + ); + + await expectPrivateBuiltinRejected( + 'vm default loader', + vmModule.default.runInNewContext('import("__wasm_rquickjs_builtin/vm_native")', {}, { + importModuleDynamically: vmModule.default.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }), + ); + + const vmSpecifierSandbox = {}; + const vmSpecifierResult = await vmModule.default.runInNewContext([ + 'globalThis.toStringCalls = 0;', + 'const specifier = {', + ' toString() {', + ' globalThis.toStringCalls += 1;', + ' return globalThis.toStringCalls === 1 ? "node:fs" : "__wasm_rquickjs_builtin/vm_native";', + ' }', + '};', + 'import(specifier);', + ].join('\n'), vmSpecifierSandbox, { + importModuleDynamically: vmModule.default.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.strictEqual(typeof vmSpecifierResult.existsSync, 'function'); + assert.strictEqual(vmSpecifierSandbox.toStringCalls, 1); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testEsmResolutionErrorUrls = async () => { + try { + fs.mkdirSync('/esm-error-url-app/dir', { recursive: true }); + fs.mkdirSync('/esm-error-url-app/package-dir', { recursive: true }); + fs.mkdirSync('/esm-error-url-app/relative-package-dir', { recursive: true }); + fs.mkdirSync('/esm-error-url-app/sub', { recursive: true }); + fs.writeFileSync('/esm-error-url-app/package-dir/package.json', JSON.stringify({ main: 'main-entry' })); + fs.writeFileSync('/esm-error-url-app/package-dir/main-entry.js', 'export default 1;'); + fs.writeFileSync('/esm-error-url-app/relative-package-dir/package.json', JSON.stringify({ main: 'main-entry' })); + fs.writeFileSync('/esm-error-url-app/relative-package-dir/main-entry.js', 'export default 1;'); + fs.writeFileSync('/esm-error-url-app/entry.mjs', "await import('./miss%2Eing');\n"); + fs.writeFileSync('/esm-error-url-app/relative-package-entry.mjs', "await import('./relative-package-dir');\n"); + fs.writeFileSync('/esm-error-url-app/entry-dot.mjs', "await import('./sub/%2e%2e/missing');\n"); + const originalError = globalThis.Error; + const originalTypeError = globalThis.TypeError; + const poisonUrl = { + configurable: true, + get() { + throw new originalError('prototype url getter should not be read'); + }, + set() { + throw new originalError('prototype url setter should not be called'); + }, + }; + Object.defineProperty(Error.prototype, 'url', poisonUrl); + Object.defineProperty(Object.prototype, 'url', poisonUrl); + const originalDefineProperty = Object.defineProperty; + Object.defineProperty = () => { + throw new originalError('patched Object.defineProperty should not be called'); + }; + globalThis.Error = function PatchedError() { + throw new originalError('patched Error constructor should not be called'); + }; + globalThis.TypeError = function PatchedTypeError() { + throw new originalError('patched TypeError constructor should not be called'); + }; + const cases = [ + ['/esm-error-url-app/dir', 'ERR_UNSUPPORTED_DIR_IMPORT'], + ['/esm-error-url-app/missing', 'ERR_MODULE_NOT_FOUND'], + ['/esm-error-url-app/miss%2Eing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/miss%2Eing'], + ['/esm-error-url-app/missing?x= a#b c', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing?x=%20a#b%20c'], + ['/esm-error-url-app/entry.mjs', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/miss%2Eing'], + ['/esm-error-url-app/sub/%2e%2e/missing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing'], + ['/esm-error-url-app/entry-dot.mjs', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing'], + ['file:///esm-error-url-app/miss%23ing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/miss%23ing'], + ['file:///esm-error-url-app/miss%2Eing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/miss%2Eing'], + ['file:///esm-error-url-app/missing?x= a#b c', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing?x=%20a#b%20c'], + ['file:///esm-error-url-app/sub/%2e%2e/missing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing'], + ['file://localhost/esm-error-url-app/sub/%2e%2e/missing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing'], + ['file://LOCALHOST/esm-error-url-app/sub/%2e%2e/missing', 'ERR_MODULE_NOT_FOUND', 'file:///esm-error-url-app/missing'], + ['file://example.com/esm-error-url-app/missing', 'ERR_INVALID_FILE_URL_HOST', null], + ]; + + try { + for (const [specifier, code, expectedUrl = pathToFileURL(specifier).href] of cases) { + await assert.rejects( + import(specifier), + (error) => { + assert.strictEqual(error.code, code); + assert(!Object.prototype.hasOwnProperty.call(error, 'name')); + if (expectedUrl === null) { + assert(!Object.prototype.hasOwnProperty.call(error, 'url')); + assert(error instanceof originalError || error.name === 'TypeError'); + } else { + assert(Object.prototype.hasOwnProperty.call(error, 'url')); + assert.strictEqual(error.url, expectedUrl); + } + return true; + } + ); + const dataSpecifier = expectedUrl === null ? specifier : expectedUrl; + await assert.rejects( + import(`data:text/javascript,import${encodeURIComponent(JSON.stringify(dataSpecifier))}`), + (error) => { + assert.strictEqual(error.code, code); + assert(!Object.prototype.hasOwnProperty.call(error, 'name')); + if (expectedUrl === null) { + assert(!Object.prototype.hasOwnProperty.call(error, 'url')); + assert(error instanceof originalError || error.name === 'TypeError'); + } else { + assert(Object.prototype.hasOwnProperty.call(error, 'url')); + assert.strictEqual(error.url, expectedUrl); + } + return true; + } + ); + } + } finally { + globalThis.TypeError = originalTypeError; + globalThis.Error = originalError; + Object.defineProperty = originalDefineProperty; + delete Error.prototype.url; + delete Object.prototype.url; + } + await assert.rejects(import('/esm-error-url-app/dir'), /ERR_UNSUPPORTED_DIR_IMPORT/); + await assert.rejects(import('file:///esm-error-url-app/package-dir'), /Did you mean/); + await assert.rejects( + import('/esm-error-url-app/relative-package-entry.mjs'), + (error) => error.code === 'ERR_UNSUPPORTED_DIR_IMPORT' && !String(error).includes('Did you mean'), + ); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsDirectNamedExports = async () => { + try { + fs.mkdirSync('/cjs-named-export-app', { recursive: true }); + fs.writeFileSync('/cjs-named-export-app/direct.cjs', [ + 'exports.foo = "foo";', + 'module.exports.bar = "bar";', + 'exports["baz"] = "baz";', + 'module.exports["π"] = "pi";', + 'exports["invalid identifier"] = "invalid";', + 'module.exports["?invalid"] = "question";', + 'exports.package = "reserved";', + '// exports.commentOnly = "no";', + '/* module.exports.blockCommentOnly = "no"; */', + 'const text = "exports.stringOnly = no";', + ].join('\n')); + fs.writeFileSync('/cjs-named-export-app/bracket-only.js', [ + 'exports["bracketOnly"] = "bracket";', + ].join('\n')); + fs.writeFileSync('/cjs-named-export-app/define-only.js', [ + 'Object.defineProperty(exports, "definedOnly", { value: "defined" });', + ].join('\n')); + fs.writeFileSync('/cjs-named-export-app/object-primitives.cjs', [ + 'const value = "after";', + 'module.exports = {', + ' yes: true,', + ' no: false,', + ' empty: null,', + ' missing: undefined,', + ' after: value,', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-named-export-app/false-positives.cjs', [ + 'const myexports = {};', + 'myexports.fake1 = "no";', + 'const obj = { exports: {} };', + 'obj.exports.fake2 = "no";', + 'const notmodule = {};', + 'notmodule.exports = {};', + 'notmodule.exports.fake3 = "no";', + 'if (exports.fake4 === "no") {}', + 'if (module.exports.fake5 == "no") {}', + 'const re = /exports.fake6 = "no"/;', + 'exports.real = "yes";', + ].join('\n')); + fs.writeFileSync('/cjs-named-export-app/direct-entry.mjs', [ + 'import def, { foo, bar, baz, π, package as packageExport } from "./direct.cjs";', + 'import { bracketOnly } from "./bracket-only.js";', + 'import { definedOnly } from "./define-only.js";', + 'import { yes, no, empty, missing, after } from "./object-primitives.cjs";', + 'import * as ns from "./direct.cjs";', + 'import * as fp from "./false-positives.cjs";', + 'export default {', + ' def, foo, bar, baz, pi: π, packageExport, bracketOnly, definedOnly,', + ' yes, no, empty, missing, after,', + ' invalidIdentifier: ns["invalid identifier"],', + ' questionInvalid: ns["?invalid"],', + ' hasCommentOnly: Object.prototype.hasOwnProperty.call(ns, "commentOnly"),', + ' hasBlockCommentOnly: Object.prototype.hasOwnProperty.call(ns, "blockCommentOnly"),', + ' hasStringOnly: Object.prototype.hasOwnProperty.call(ns, "stringOnly"),', + ' falsePositiveKeys: Object.keys(fp).filter((key) => key !== "default" && key !== "real"),', + ' real: fp.real,', + '};', + ].join('\n')); + + const result = (await import('/cjs-named-export-app/direct-entry.mjs')).default; + assert.strictEqual(result.foo, 'foo'); + assert.strictEqual(result.bar, 'bar'); + assert.strictEqual(result.baz, 'baz'); + assert.strictEqual(result.pi, 'pi'); + assert.strictEqual(result.packageExport, 'reserved'); + assert.strictEqual(result.bracketOnly, 'bracket'); + assert.strictEqual(result.definedOnly, 'defined'); + assert.strictEqual(result.yes, true); + assert.strictEqual(result.no, false); + assert.strictEqual(result.empty, null); + assert.strictEqual(result.missing, undefined); + assert.strictEqual(result.after, 'after'); + assert.strictEqual(result.invalidIdentifier, 'invalid'); + assert.strictEqual(result.questionInvalid, 'question'); + assert.strictEqual(result.def.foo, 'foo'); + assert.strictEqual(result.def['π'], 'pi'); + assert.deepStrictEqual(result.falsePositiveKeys, []); + assert.strictEqual(result.real, 'yes'); + assert.strictEqual(result.hasCommentOnly, false); + assert.strictEqual(result.hasBlockCommentOnly, false); + assert.strictEqual(result.hasStringOnly, false); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testEsmImportsSideEffectCommonJs = async () => { + try { + fs.mkdirSync('/esm-side-effect-cjs-app', { recursive: true }); + fs.writeFileSync('/esm-side-effect-cjs-app/side-effect.js', [ + "'use strict';", + 'const path = require("node:path");', + 'const self = require.resolve("./side-effect.js");', + 'globalThis.esmSideEffectCjs = {', + ' basename: path.basename(self),', + ' cached: require.cache[self] !== undefined,', + '};', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/dynamic-import-method.js', [ + "'use strict';", + 'module.exports = {', + ' async importFromInside(specifier) {', + ' return import(specifier);', + ' },', + '};', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/var-require.js', [ + "'use strict';", + 'var require = "var-require-binding";', + 'module.exports = { require };', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/destructure-default-require.js', [ + "'use strict';", + 'const { x = require("node:path") } = {};', + 'module.exports = { sep: x.sep };', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/computed-key-module.js', [ + "'use strict';", + 'const source = { [module.id]: "computed-key-module" };', + 'const { [module.id]: value } = source;', + 'module.exports = { value };', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/property-import-meta.js', [ + "'use strict';", + 'const obj = { import: { meta: "plain-property" } };', + 'module.exports = { value: obj.import.meta };', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/entry.mjs', [ + 'import value from "./side-effect.js";', + 'import dynamicImportMethod from "./dynamic-import-method.js";', + 'import varRequire from "./var-require.js";', + 'import destructureDefaultRequire from "./destructure-default-require.js";', + 'import computedKeyModule from "./computed-key-module.js";', + 'import propertyImportMeta from "./property-import-meta.js";', + 'export default {', + ' value,', + ' sideEffect: globalThis.esmSideEffectCjs,', + ' dynamicImportMethod,', + ' varRequire,', + ' destructureDefaultRequire,', + ' computedKeyModule,', + ' propertyImportMeta,', + '};', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/import-meta.js', [ + 'globalThis.esmAmbiguousImportMeta = import.meta.url;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/redeclare-exports.js', [ + 'const exports = "exports-binding";', + 'globalThis.esmAmbiguousExports = exports;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/redeclare-module.js', [ + 'let module = "module-binding";', + 'globalThis.esmAmbiguousModule = module;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/redeclare-filename.js', [ + 'const __filename = "filename-binding";', + 'globalThis.esmAmbiguousFilename = __filename;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/redeclare-dirname.js', [ + 'class __dirname {}', + 'globalThis.esmAmbiguousDirname = __dirname.name;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/require-import-meta.js', [ + 'globalThis.requireAmbiguousImportMeta = import.meta.url;', + 'export default "require-import-meta";', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/require-redeclare-exports.js', [ + 'const exports = "require-exports-binding";', + 'export default exports;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/require-redeclare-dirname.js', [ + 'class __dirname {}', + 'export default __dirname.name;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/require-redeclare-destructure.js', [ + 'const { exports } = { exports: "require-destructure-binding" };', + 'export default exports;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/require-redeclare-later.js', [ + 'const first = "ignored", module = "require-later-binding";', + 'export default module;', + ].join('\n')); + fs.writeFileSync('/esm-side-effect-cjs-app/require-entry.cjs', [ + 'const importMeta = require("./require-import-meta.js");', + 'const redeclareExports = require("./require-redeclare-exports.js");', + 'const redeclareDirname = require("./require-redeclare-dirname.js");', + 'const redeclareDestructure = require("./require-redeclare-destructure.js");', + 'const redeclareLater = require("./require-redeclare-later.js");', + 'module.exports = {', + ' importMeta,', + ' redeclareExports,', + ' redeclareDirname,', + ' redeclareDestructure,', + ' redeclareLater,', + '};', + ].join('\n')); + fs.mkdirSync('/esm-side-effect-cjs-app/commonjs-package', { recursive: true }); + fs.writeFileSync('/esm-side-effect-cjs-app/commonjs-package/package.json', JSON.stringify({ type: 'commonjs' })); + fs.writeFileSync('/esm-side-effect-cjs-app/commonjs-package/esm-syntax.js', 'export default "must-not-load-as-esm";'); + fs.mkdirSync('/esm-side-effect-cjs-app/node_modules/no-type-package', { recursive: true }); + fs.writeFileSync('/esm-side-effect-cjs-app/node_modules/no-type-package/package.json', JSON.stringify({ name: 'no-type-package' })); + fs.writeFileSync('/esm-side-effect-cjs-app/node_modules/no-type-package/esm-syntax.js', 'export default "must-not-load-as-esm";'); + + const result = (await import('/esm-side-effect-cjs-app/entry.mjs')).default; + assert.deepStrictEqual(result.value, {}); + assert.deepStrictEqual(result.sideEffect, { + basename: 'side-effect.js', + cached: true, + }); + assert.strictEqual(typeof result.dynamicImportMethod.importFromInside, 'function'); + assert.deepStrictEqual(result.varRequire, { require: 'var-require-binding' }); + assert.deepStrictEqual(result.destructureDefaultRequire, { sep: '/' }); + assert.deepStrictEqual(result.computedKeyModule, { value: 'computed-key-module' }); + assert.deepStrictEqual(result.propertyImportMeta, { value: 'plain-property' }); + await import('/esm-side-effect-cjs-app/import-meta.js'); + await import('/esm-side-effect-cjs-app/redeclare-exports.js'); + await import('/esm-side-effect-cjs-app/redeclare-module.js'); + await import('/esm-side-effect-cjs-app/redeclare-filename.js'); + await import('/esm-side-effect-cjs-app/redeclare-dirname.js'); + assert.strictEqual(globalThis.esmAmbiguousImportMeta, 'file:///esm-side-effect-cjs-app/import-meta.js'); + assert.strictEqual(globalThis.esmAmbiguousExports, 'exports-binding'); + assert.strictEqual(globalThis.esmAmbiguousModule, 'module-binding'); + assert.strictEqual(globalThis.esmAmbiguousFilename, 'filename-binding'); + assert.strictEqual(globalThis.esmAmbiguousDirname, '__dirname'); + const localRequire = createRequire('/esm-side-effect-cjs-app/entry.mjs'); + const requireResult = localRequire('/esm-side-effect-cjs-app/require-entry.cjs'); + assert.strictEqual(requireResult.importMeta.default, 'require-import-meta'); + assert.strictEqual( + globalThis.requireAmbiguousImportMeta, + 'file:///esm-side-effect-cjs-app/require-import-meta.js', + ); + assert.strictEqual(requireResult.redeclareExports.default, 'require-exports-binding'); + assert.strictEqual(requireResult.redeclareDirname.default, '__dirname'); + assert.strictEqual(requireResult.redeclareDestructure.default, 'require-destructure-binding'); + assert.strictEqual(requireResult.redeclareLater.default, 'require-later-binding'); + await assert.rejects( + import('/esm-side-effect-cjs-app/commonjs-package/esm-syntax.js'), + (error) => error && error.name === 'SyntaxError', + ); + await assert.rejects( + import('/esm-side-effect-cjs-app/node_modules/no-type-package/esm-syntax.js'), + (error) => error && error.name === 'SyntaxError', + ); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsDefinePropertyNamedExports = async () => { + try { + fs.mkdirSync('/cjs-define-export-app', { recursive: true }); + fs.writeFileSync('/cjs-define-export-app/define.cjs', [ + 'const dep = { value: "getter-value" };', + 'Object.defineProperty(exports, "valueExport", { value: "value" });', + 'Object.defineProperty(exports, "getterExport", { enumerable: true, get: function () { return dep.value; } });', + 'Object.defineProperty(module.exports, "moduleGetter", { enumerable: true, get() { return dep.value; } });', + 'Object.defineProperty(exports, "unsafe", { enumerable: true, get() { return dynamic(); } });', + 'Object.defineProperty(exports, "unsafeValueWord", { enumerable: true, get() { return value(); } });', + ].join('\n')); + fs.writeFileSync('/cjs-define-export-app/define-entry.mjs', [ + 'import { valueExport, getterExport, moduleGetter } from "./define.cjs";', + 'import * as ns from "./define.cjs";', + 'export default {', + ' valueExport, getterExport, moduleGetter,', + ' hasUnsafe: Object.prototype.hasOwnProperty.call(ns, "unsafe"),', + ' hasUnsafeValueWord: Object.prototype.hasOwnProperty.call(ns, "unsafeValueWord"),', + '};', + ].join('\n')); + + const result = (await import('/cjs-define-export-app/define-entry.mjs')).default; + assert.strictEqual(result.valueExport, 'value'); + assert.strictEqual(result.getterExport, 'getter-value'); + assert.strictEqual(result.moduleGetter, 'getter-value'); + assert.strictEqual(result.hasUnsafe, false); + assert.strictEqual(result.hasUnsafeValueWord, false); + return true; + } catch (error) { + console.error(error); + return false; + } +}; + +export const testCjsReexportNamedExports = async () => { + try { + fs.mkdirSync('/cjs-reexport-app', { recursive: true }); + fs.writeFileSync('/cjs-reexport-app/dep.cjs', [ + 'exports.alpha = "alpha";', + 'exports.beta = "beta";', + 'exports.nested = { nestedValue: "nested" };', + ].join('\n')); + fs.writeFileSync('/cjs-reexport-app/reexport.cjs', 'module.exports = require("./dep.cjs");'); + fs.writeFileSync('/cjs-reexport-app/reexport-continuation.cjs', 'module.exports = require("./dep.cjs").nested;'); + fs.writeFileSync('/cjs-reexport-app/transpiler.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, {', + ' enumerable: true,', + ' get: function () { return _dep[key]; }', + ' });', + '});', + ].join('\n')); + fs.writeFileSync('/cjs-reexport-app/not-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(console.log);', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-reexport-app/reexport-entry.mjs', [ + 'import { alpha, beta } from "./reexport.cjs";', + 'import * as continuation from "./reexport-continuation.cjs";', + 'import { alpha as transAlpha, beta as transBeta } from "./transpiler.cjs";', + 'import * as nonReexport from "./not-reexport.cjs";', + 'export default {', + ' alpha, beta, transAlpha, transBeta,', + ' continuationKeys: Object.keys(continuation).filter((key) => key !== "default").sort(),', + ' continuationAlpha: continuation.alpha,', + ' continuationBeta: continuation.beta,', + ' continuationNested: continuation.nested,', + ' continuationDefault: continuation.default,', + ' nonReexportKeys: Object.keys(nonReexport).filter((key) => key !== "default" && key !== "own"),', + ' nonReexportOwn: nonReexport.own,', + '};', + ].join('\n')); + + const result = (await import('/cjs-reexport-app/reexport-entry.mjs')).default; + assert.deepStrictEqual(result, { + alpha: 'alpha', + beta: 'beta', + transAlpha: 'alpha', + transBeta: 'beta', + continuationKeys: ['alpha', 'beta', 'nested'], + continuationAlpha: undefined, + continuationBeta: undefined, + continuationNested: undefined, + continuationDefault: { nestedValue: 'nested' }, + nonReexportKeys: [], + nonReexportOwn: 'own', + }); + return true; + } catch (error) { + console.error(error); + return false; + } +}; + +export const testCjsAnalyzerFalsePositiveGuards = async () => { + try { + fs.mkdirSync('/cjs-analyzer-guards-app', { recursive: true }); + fs.writeFileSync('/cjs-analyzer-guards-app/esm-with-cjs-text.js', [ + '// exports.commentOnly = "no";', + 'const text = "module.exports = {}; require(";', + 'const re = /exports.regexOnly = "no"/;', + 'const fn = () => /module.exports.arrowRegexOnly = "no"/;', + 'if (typeof module !== "undefined" && module.exports === undefined) {}', + 'const require = () => ({ value: 64 });', + 'const dep = require("./dep.cjs");', + 'export const value = 42;', + 'export const requireValue = dep.value;', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/esm-entry.mjs', [ + 'import { value, requireValue } from "./esm-with-cjs-text.js";', + 'export default { value, requireValue };', + ].join('\n')); + assert.deepStrictEqual((await import('/cjs-analyzer-guards-app/esm-entry.mjs')).default, { value: 42, requireValue: 64 }); + + fs.writeFileSync('/cjs-analyzer-guards-app/whitespace-module.js', 'module /*x*/ . /*y*/ exports = { value: "module" };'); + fs.writeFileSync('/cjs-analyzer-guards-app/whitespace-entry.mjs', [ + 'import mod from "./whitespace-module.js";', + 'export default mod.value;', + ].join('\n')); + assert.strictEqual((await import('/cjs-analyzer-guards-app/whitespace-entry.mjs')).default, 'module'); + + fs.writeFileSync('/cjs-analyzer-guards-app/false-positives.cjs', [ + 'const myexports = {};', + 'myexports.fake1 = "no";', + 'const obj = { exports: {} };', + 'obj.exports.fake2 = "no";', + 'const notmodule = {};', + 'notmodule.exports = {};', + 'notmodule.exports.fake3 = "no";', + 'if (exports.fake4 === "no") {}', + 'if (module.exports.fake5 == "no") {}', + 'function f() { return /exports.fake6 = "no"/; }', + 'const g = () => /module.exports.fake7 = "no"/;', + 'exports.real = "yes";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/unsafe-define.cjs', [ + 'Object.defineProperty(exports, "unsafeStringReturn", { enumerable: true, get() { const s = "return dep.value"; return dynamic(); } });', + 'Object.defineProperty(exports, "unsafeRegexValue", { enumerable: true, get() { return /value:/; } });', + 'Object.defineProperty(exports, "unsafeRegexDescriptor", { enumerable: /value:/ });', + 'Object.defineProperty(exports, "unsafeNestedValue", { enumerable: true, get() { return { value: dynamic() }; } });', + 'Object.defineProperty(exports, "unsafeMultipleReturn", { enumerable: true, get() { return dep.value; return dynamic(); } });', + 'Object.defineProperty(exports, "unsafeConditionalReturn", { enumerable: true, get() { if (dep) return dep.value; return dynamic(); } });', + 'exports.safe = "yes";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/dep.cjs', 'exports.alpha = "alpha";'); + fs.writeFileSync('/cjs-analyzer-guards-app/dep-nested.cjs', 'exports.nested = { beta: "beta" };'); + fs.writeFileSync('/cjs-analyzer-guards-app/dep-spread.cjs', [ + 'exports.alpha = "alpha";', + 'exports.nested = { beta: "beta" };', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/not-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'var other = {};', + 'Object.keys(_dep).forEach(function (key) {', + ' const msg = "Object.defineProperty(exports, key, { get: function () { return _dep[key]; } })";', + '});', + 'Object.keys(_dep).forEach(function (key) {', + ' Object.defineProperty(other, key, { value: 1 });', + ' exports;', + ' function unrelated() { return _dep[key]; }', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/foreach-property-non-call.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach;', + '(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/nested-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'function copy() {', + ' Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + ' });', + '}', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/nested-require-binding.cjs', [ + 'var _dep = {};', + 'function init() {', + ' var _dep = require("./dep.cjs");', + '}', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/unguarded-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' const π = 1;', + ' Object.defineProperty(exports, key, { enumerable: true, get: function () { return _dep[key]; } });', + '});', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/hidden-descriptor-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: false, get: function () { return _dep[key]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/extra-descriptor-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: true, get: function () { return _dep[key]; }, configurable: true });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/duplicate-enumerable-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: true, enumerable: true, get: function () { return _dep[key]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/getter-only-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { get: function () { return _dep[key]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/getter-before-enumerable-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { get: function () { return _dep[key]; }, enumerable: true });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/named-function-getter-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: true, get: function namedGetter() { return _dep[key]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/nested-getter-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: true, get: function () { return _dep[key].nested; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/other-binding-getter-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'var other = _dep;', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: true, get: function () { return other[key]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/other-key-getter-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' var otherKey = key;', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, { enumerable: true, get: function () { return _dep[otherKey]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/reversed-guard-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if ("default" === key || "__esModule" === key) return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/delayed-guard-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' exports[key] = _dep[key];', + ' if (key === "default" || key === "__esModule") return;', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/nested-guard-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' function guard() {', + ' if (key === "default" || key === "__esModule") return;', + ' }', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/for-header-binding.cjs', [ + 'for (var _dep = require("./dep.cjs"); false;) {}', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/commented-reexport.cjs', [ + '/* header */ var _dep = require("./dep.cjs");', + 'exports.own = "own";', + '/* separator */ Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/line-commented-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'exports.own = "own"; // trailing comment', + '// separator', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/arrow-callback-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach((key) => {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/extra-arg-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '}, null);', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/has-own-guard-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key !== "default" && !Object.prototype.hasOwnProperty.call(exports, key)) exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/duplicate-return-guard-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (key in exports && exports[key] === _dep[key]) return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/module-exports-duplicate-return-guard-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (key in module.exports && module.exports[key] === _dep[key]) return;', + ' module.exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/skip-map-return-guard.cjs', [ + 'var _dep = require("./dep.cjs");', + 'var skip = {};', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (skip.hasOwnProperty(key)) return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-hasown-return-guard.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (Object.hasOwnProperty.call(exports, key)) return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/skip-map-duplicate-shape-return-guard.cjs', [ + 'var _dep = require("./dep.cjs");', + 'var skip = {};', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (key in skip && skip[key] === _dep[key]) return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/other-binding-duplicate-shape-return-guard.cjs', [ + 'var _dep = require("./dep.cjs");', + 'var other = {};', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (key in exports && exports[key] === other[key]) return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/asi-reexport.cjs', [ + 'var _dep = require("./dep.cjs")', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key]', + '})', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/renamed-key-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (name) {', + ' if (name === "default" || name === "__esModule") return;', + ' exports[name] = _dep[name];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/require-continuation.cjs', [ + 'var _dep = require("./dep.cjs")', + '+ 0;', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/statement-continuation.cjs', [ + 'var _dep = require("./dep.cjs");', + 'false &&', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/conditional-body-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' if (false) exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/intervening-statement-reexport.cjs', [ + 'var _dep = require("./dep.cjs");', + 'function touch() {}', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' touch();', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/prefix-asi-reexport.cjs', [ + 'var x = 0;', + 'var _dep = require("./dep.cjs")', + '++x;', + 'Object.keys(_dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' exports[key] = _dep[key];', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/continuation.cjs', [ + 'module.exports = require("./dep-spread.cjs").nested;', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/binding-continuation.cjs', [ + 'var dep = require("./dep-nested.cjs").nested;', + 'Object.keys(dep).forEach(function (key) {', + ' Object.defineProperty(exports, key, { enumerable: true, get: function () { return dep[key]; } });', + '});', + 'exports.own = "own";', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-literal-values.cjs', [ + 'const identifierValue = "identifier";', + 'const memberSource = { x: "member" };', + 'module.exports = {', + ' identifierValue,', + ' callExpression: factory(),', + ' memberExpression: memberSource.x,', + ' booleanLiteral: true,', + ' nullLiteral: null,', + ' undefinedLiteral: undefined,', + '};', + 'function factory() { return "call"; }', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-literal-require-value.cjs', [ + 'module.exports = {', + ' requireValue: require("./dep.cjs"),', + ' afterRequire: "not-detected",', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-literal-require-spread-continuation.cjs', [ + 'const before = "before";', + 'const after = "after";', + 'module.exports = {', + ' before,', + ' ...require("./dep-spread.cjs").nested,', + ' after,', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-literal-unsupported.cjs', [ + 'const identifierValue = "identifier";', + 'module.exports = {', + ' stringLiteral: "not-detected",', + ' numberLiteral: 1,', + ' objectLiteral: {},', + ' callExpression: factory(),', + ' identifierValue,', + '};', + 'function factory() { return "not-detected"; }', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-literal-call-terminal.cjs', [ + 'const afterCall = "after";', + 'module.exports = {', + ' callValue: factory(),', + ' afterCall,', + '};', + 'function factory() { return "call"; }', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/object-literal-method-terminal.cjs', [ + 'const beforeMethod = "before";', + 'const afterMethod = "after";', + 'module.exports = {', + ' beforeMethod,', + ' method() { return "method"; },', + ' afterMethod,', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-analyzer-guards-app/guards-entry.mjs', [ + 'import * as fp from "./false-positives.cjs";', + 'import * as unsafe from "./unsafe-define.cjs";', + 'import * as nonReexport from "./not-reexport.cjs";', + 'import * as foreachPropertyNonCall from "./foreach-property-non-call.cjs";', + 'import * as nestedReexport from "./nested-reexport.cjs";', + 'import * as nestedRequireBinding from "./nested-require-binding.cjs";', + 'import * as unguardedReexport from "./unguarded-reexport.cjs";', + 'import * as hiddenDescriptorReexport from "./hidden-descriptor-reexport.cjs";', + 'import * as extraDescriptorReexport from "./extra-descriptor-reexport.cjs";', + 'import * as duplicateEnumerableReexport from "./duplicate-enumerable-reexport.cjs";', + 'import * as getterOnlyReexport from "./getter-only-reexport.cjs";', + 'import * as getterBeforeEnumerableReexport from "./getter-before-enumerable-reexport.cjs";', + 'import * as namedFunctionGetterReexport from "./named-function-getter-reexport.cjs";', + 'import * as nestedGetterReexport from "./nested-getter-reexport.cjs";', + 'import * as otherBindingGetterReexport from "./other-binding-getter-reexport.cjs";', + 'import * as otherKeyGetterReexport from "./other-key-getter-reexport.cjs";', + 'import * as reversedGuardReexport from "./reversed-guard-reexport.cjs";', + 'import * as delayedGuardReexport from "./delayed-guard-reexport.cjs";', + 'import * as nestedGuardReexport from "./nested-guard-reexport.cjs";', + 'import * as forHeaderBinding from "./for-header-binding.cjs";', + 'import * as commentedReexport from "./commented-reexport.cjs";', + 'import * as lineCommentedReexport from "./line-commented-reexport.cjs";', + 'import * as arrowCallbackReexport from "./arrow-callback-reexport.cjs";', + 'import * as extraArgReexport from "./extra-arg-reexport.cjs";', + 'import * as hasOwnGuardReexport from "./has-own-guard-reexport.cjs";', + 'import * as duplicateReturnGuardReexport from "./duplicate-return-guard-reexport.cjs";', + 'import * as moduleExportsDuplicateReturnGuardReexport from "./module-exports-duplicate-return-guard-reexport.cjs";', + 'import * as skipMapReturnGuard from "./skip-map-return-guard.cjs";', + 'import * as objectHasOwnReturnGuard from "./object-hasown-return-guard.cjs";', + 'import * as skipMapDuplicateShapeReturnGuard from "./skip-map-duplicate-shape-return-guard.cjs";', + 'import * as otherBindingDuplicateShapeReturnGuard from "./other-binding-duplicate-shape-return-guard.cjs";', + 'import * as asiReexport from "./asi-reexport.cjs";', + 'import * as renamedKeyReexport from "./renamed-key-reexport.cjs";', + 'import * as requireContinuation from "./require-continuation.cjs";', + 'import * as statementContinuation from "./statement-continuation.cjs";', + 'import * as conditionalBodyReexport from "./conditional-body-reexport.cjs";', + 'import * as interveningStatementReexport from "./intervening-statement-reexport.cjs";', + 'import * as prefixAsiReexport from "./prefix-asi-reexport.cjs";', + 'import * as continuation from "./continuation.cjs";', + 'import * as bindingContinuation from "./binding-continuation.cjs";', + 'import * as objectLiteralValues from "./object-literal-values.cjs";', + 'import * as objectLiteralRequireValue from "./object-literal-require-value.cjs";', + 'import * as objectLiteralRequireSpreadContinuation from "./object-literal-require-spread-continuation.cjs";', + 'import * as objectLiteralUnsupported from "./object-literal-unsupported.cjs";', + 'import * as objectLiteralCallTerminal from "./object-literal-call-terminal.cjs";', + 'import * as objectLiteralMethodTerminal from "./object-literal-method-terminal.cjs";', + 'export default {', + ' fpKeys: Object.keys(fp).filter((key) => key !== "default" && key !== "real"),', + ' real: fp.real,', + ' unsafeKeys: Object.keys(unsafe).filter((key) => key !== "default" && key !== "safe"),', + ' safe: unsafe.safe,', + ' nonReexportKeys: Object.keys(nonReexport).filter((key) => key !== "default" && key !== "own"),', + ' own: nonReexport.own,', + ' foreachPropertyNonCallKeys: Object.keys(foreachPropertyNonCall).filter((key) => key !== "default" && key !== "own"),', + ' foreachPropertyNonCallOwn: foreachPropertyNonCall.own,', + ' nestedReexportKeys: Object.keys(nestedReexport).filter((key) => key !== "default" && key !== "own"),', + ' nestedOwn: nestedReexport.own,', + ' nestedRequireBindingKeys: Object.keys(nestedRequireBinding).filter((key) => key !== "default" && key !== "own"),', + ' nestedRequireBindingOwn: nestedRequireBinding.own,', + ' unguardedReexportKeys: Object.keys(unguardedReexport).filter((key) => key !== "default"),', + ' hiddenDescriptorReexportKeys: Object.keys(hiddenDescriptorReexport).filter((key) => key !== "default" && key !== "own"),', + ' hiddenDescriptorOwn: hiddenDescriptorReexport.own,', + ' extraDescriptorReexportKeys: Object.keys(extraDescriptorReexport).filter((key) => key !== "default" && key !== "own"),', + ' extraDescriptorOwn: extraDescriptorReexport.own,', + ' duplicateEnumerableReexportKeys: Object.keys(duplicateEnumerableReexport).filter((key) => key !== "default" && key !== "own"),', + ' duplicateEnumerableOwn: duplicateEnumerableReexport.own,', + ' getterOnlyReexportKeys: Object.keys(getterOnlyReexport).filter((key) => key !== "default" && key !== "own"),', + ' getterOnlyOwn: getterOnlyReexport.own,', + ' getterBeforeEnumerableReexportKeys: Object.keys(getterBeforeEnumerableReexport).filter((key) => key !== "default" && key !== "own"),', + ' getterBeforeEnumerableOwn: getterBeforeEnumerableReexport.own,', + ' namedFunctionGetterAlpha: namedFunctionGetterReexport.alpha,', + ' nestedGetterReexportKeys: Object.keys(nestedGetterReexport).filter((key) => key !== "default" && key !== "own"),', + ' nestedGetterOwn: nestedGetterReexport.own,', + ' otherBindingGetterReexportKeys: Object.keys(otherBindingGetterReexport).filter((key) => key !== "default" && key !== "own"),', + ' otherBindingGetterOwn: otherBindingGetterReexport.own,', + ' otherKeyGetterReexportKeys: Object.keys(otherKeyGetterReexport).filter((key) => key !== "default" && key !== "own"),', + ' otherKeyGetterOwn: otherKeyGetterReexport.own,', + ' reversedGuardReexportKeys: Object.keys(reversedGuardReexport).filter((key) => key !== "default" && key !== "own"),', + ' reversedGuardOwn: reversedGuardReexport.own,', + ' delayedGuardReexportKeys: Object.keys(delayedGuardReexport).filter((key) => key !== "default" && key !== "own"),', + ' delayedGuardOwn: delayedGuardReexport.own,', + ' nestedGuardReexportKeys: Object.keys(nestedGuardReexport).filter((key) => key !== "default" && key !== "own"),', + ' nestedGuardOwn: nestedGuardReexport.own,', + ' forHeaderBindingKeys: Object.keys(forHeaderBinding).filter((key) => key !== "default" && key !== "own"),', + ' forHeaderBindingOwn: forHeaderBinding.own,', + ' commentedAlpha: commentedReexport.alpha,', + ' lineCommentedAlpha: lineCommentedReexport.alpha,', + ' arrowCallbackReexportKeys: Object.keys(arrowCallbackReexport).filter((key) => key !== "default" && key !== "own"),', + ' arrowCallbackOwn: arrowCallbackReexport.own,', + ' extraArgReexportKeys: Object.keys(extraArgReexport).filter((key) => key !== "default" && key !== "own"),', + ' extraArgOwn: extraArgReexport.own,', + ' hasOwnGuardAlpha: hasOwnGuardReexport.alpha,', + ' duplicateReturnGuardAlpha: duplicateReturnGuardReexport.alpha,', + ' moduleExportsDuplicateReturnGuardAlpha: moduleExportsDuplicateReturnGuardReexport.alpha,', + ' skipMapReturnGuardKeys: Object.keys(skipMapReturnGuard).filter((key) => key !== "default" && key !== "own"),', + ' objectHasOwnReturnGuardKeys: Object.keys(objectHasOwnReturnGuard).filter((key) => key !== "default" && key !== "own"),', + ' skipMapDuplicateShapeReturnGuardKeys: Object.keys(skipMapDuplicateShapeReturnGuard).filter((key) => key !== "default" && key !== "own"),', + ' otherBindingDuplicateShapeReturnGuardKeys: Object.keys(otherBindingDuplicateShapeReturnGuard).filter((key) => key !== "default" && key !== "own"),', + ' asiAlpha: asiReexport.alpha,', + ' renamedKeyAlpha: renamedKeyReexport.alpha,', + ' requireContinuationKeys: Object.keys(requireContinuation).filter((key) => key !== "default" && key !== "own"),', + ' statementContinuationKeys: Object.keys(statementContinuation).filter((key) => key !== "default" && key !== "own"),', + ' conditionalBodyReexportKeys: Object.keys(conditionalBodyReexport).filter((key) => key !== "default" && key !== "own"),', + ' interveningStatementReexportKeys: Object.keys(interveningStatementReexport).filter((key) => key !== "default" && key !== "own"),', + ' prefixAsiAlpha: prefixAsiReexport.alpha,', + ' continuationKeys: Object.keys(continuation).filter((key) => key !== "default"),', + ' continuationDefault: continuation.default,', + ' bindingContinuationKeys: Object.keys(bindingContinuation).filter((key) => key !== "default" && key !== "own"),', + ' bindingContinuationOwn: bindingContinuation.own,', + ' objectLiteralValueKeys: Object.keys(objectLiteralValues).filter((key) => key !== "default").sort(),', + ' identifierValue: objectLiteralValues.identifierValue,', + ' callExpression: objectLiteralValues.callExpression,', + ' objectLiteralRequireValueKeys: Object.keys(objectLiteralRequireValue).filter((key) => key !== "default").sort(),', + ' requireValue: objectLiteralRequireValue.requireValue,', + ' objectLiteralRequireSpreadContinuationKeys: Object.keys(objectLiteralRequireSpreadContinuation).filter((key) => key !== "default" && key !== "before").sort(),', + ' objectLiteralRequireSpreadContinuationBefore: objectLiteralRequireSpreadContinuation.before,', + ' objectLiteralRequireSpreadContinuationAlpha: objectLiteralRequireSpreadContinuation.alpha,', + ' objectLiteralRequireSpreadContinuationNested: objectLiteralRequireSpreadContinuation.nested,', + ' objectLiteralUnsupportedKeys: Object.keys(objectLiteralUnsupported).filter((key) => key !== "default").sort(),', + ' objectLiteralCallTerminalKeys: Object.keys(objectLiteralCallTerminal).filter((key) => key !== "default").sort(),', + ' callValue: objectLiteralCallTerminal.callValue,', + ' objectLiteralMethodTerminalKeys: Object.keys(objectLiteralMethodTerminal).filter((key) => key !== "default").sort(),', + ' methodType: typeof objectLiteralMethodTerminal.method,', + '};', + ].join('\n')); + + const result = (await import('/cjs-analyzer-guards-app/guards-entry.mjs')).default; + assert.deepStrictEqual(result.fpKeys, []); + assert.strictEqual(result.real, 'yes'); + assert.deepStrictEqual(result.unsafeKeys, []); + assert.strictEqual(result.safe, 'yes'); + assert.deepStrictEqual(result.nonReexportKeys, []); + assert.strictEqual(result.own, 'own'); + assert.deepStrictEqual(result.foreachPropertyNonCallKeys, []); + assert.strictEqual(result.foreachPropertyNonCallOwn, 'own'); + assert.deepStrictEqual(result.nestedReexportKeys, []); + assert.strictEqual(result.nestedOwn, 'own'); + assert.deepStrictEqual(result.nestedRequireBindingKeys, []); + assert.strictEqual(result.nestedRequireBindingOwn, 'own'); + assert.deepStrictEqual(result.unguardedReexportKeys, []); + assert.deepStrictEqual(result.hiddenDescriptorReexportKeys, []); + assert.strictEqual(result.hiddenDescriptorOwn, 'own'); + assert.deepStrictEqual(result.extraDescriptorReexportKeys, []); + assert.strictEqual(result.extraDescriptorOwn, 'own'); + assert.deepStrictEqual(result.duplicateEnumerableReexportKeys, []); + assert.strictEqual(result.duplicateEnumerableOwn, 'own'); + assert.deepStrictEqual(result.getterOnlyReexportKeys, []); + assert.strictEqual(result.getterOnlyOwn, 'own'); + assert.deepStrictEqual(result.getterBeforeEnumerableReexportKeys, []); + assert.strictEqual(result.getterBeforeEnumerableOwn, 'own'); + assert.strictEqual(result.namedFunctionGetterAlpha, 'alpha'); + assert.deepStrictEqual(result.nestedGetterReexportKeys, []); + assert.strictEqual(result.nestedGetterOwn, 'own'); + assert.deepStrictEqual(result.otherBindingGetterReexportKeys, []); + assert.strictEqual(result.otherBindingGetterOwn, 'own'); + assert.deepStrictEqual(result.otherKeyGetterReexportKeys, []); + assert.strictEqual(result.otherKeyGetterOwn, 'own'); + assert.deepStrictEqual(result.reversedGuardReexportKeys, []); + assert.strictEqual(result.reversedGuardOwn, 'own'); + assert.deepStrictEqual(result.delayedGuardReexportKeys, []); + assert.strictEqual(result.delayedGuardOwn, 'own'); + assert.deepStrictEqual(result.nestedGuardReexportKeys, []); + assert.strictEqual(result.nestedGuardOwn, 'own'); + assert.deepStrictEqual(result.forHeaderBindingKeys, []); + assert.strictEqual(result.forHeaderBindingOwn, 'own'); + assert.strictEqual(result.commentedAlpha, 'alpha'); + assert.strictEqual(result.lineCommentedAlpha, 'alpha'); + assert.deepStrictEqual(result.arrowCallbackReexportKeys, []); + assert.strictEqual(result.arrowCallbackOwn, 'own'); + assert.deepStrictEqual(result.extraArgReexportKeys, []); + assert.strictEqual(result.extraArgOwn, 'own'); + assert.strictEqual(result.hasOwnGuardAlpha, 'alpha'); + assert.strictEqual(result.duplicateReturnGuardAlpha, 'alpha'); + assert.strictEqual(result.moduleExportsDuplicateReturnGuardAlpha, 'alpha'); + assert.deepStrictEqual(result.skipMapReturnGuardKeys, []); + assert.deepStrictEqual(result.objectHasOwnReturnGuardKeys, []); + assert.deepStrictEqual(result.skipMapDuplicateShapeReturnGuardKeys, []); + assert.deepStrictEqual(result.otherBindingDuplicateShapeReturnGuardKeys, []); + assert.strictEqual(result.asiAlpha, 'alpha'); + assert.strictEqual(result.renamedKeyAlpha, 'alpha'); + assert.deepStrictEqual(result.requireContinuationKeys, []); + assert.deepStrictEqual(result.statementContinuationKeys, []); + assert.deepStrictEqual(result.conditionalBodyReexportKeys, []); + assert.deepStrictEqual(result.interveningStatementReexportKeys, []); + assert.strictEqual(result.prefixAsiAlpha, 'alpha'); + assert.deepStrictEqual(result.continuationKeys, ['alpha', 'nested']); + assert.deepStrictEqual(result.continuationDefault, { beta: 'beta' }); + assert.deepStrictEqual(result.bindingContinuationKeys, []); + assert.strictEqual(result.bindingContinuationOwn, 'own'); + assert.deepStrictEqual(result.objectLiteralValueKeys, [ + 'callExpression', + 'identifierValue', + ]); + assert.strictEqual(result.identifierValue, 'identifier'); + assert.strictEqual(result.callExpression, 'call'); + assert.deepStrictEqual(result.objectLiteralRequireValueKeys, ['requireValue']); + assert.deepStrictEqual(result.requireValue, { alpha: 'alpha' }); + assert.deepStrictEqual(result.objectLiteralRequireSpreadContinuationKeys, ['alpha', 'nested']); + assert.strictEqual(result.objectLiteralRequireSpreadContinuationBefore, 'before'); + assert.strictEqual(result.objectLiteralRequireSpreadContinuationAlpha, undefined); + assert.strictEqual(result.objectLiteralRequireSpreadContinuationNested, undefined); + assert.deepStrictEqual(result.objectLiteralUnsupportedKeys, []); + assert.deepStrictEqual(result.objectLiteralCallTerminalKeys, ['callValue']); + assert.strictEqual(result.callValue, 'call'); + assert.deepStrictEqual(result.objectLiteralMethodTerminalKeys, ['beforeMethod', 'method']); + assert.strictEqual(result.methodType, 'function'); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsSharedLoaderIdentity = async () => { + try { + fs.mkdirSync('/cjs-shared-loader-app', { recursive: true }); + fs.writeFileSync('/cjs-shared-loader-app/shared.cjs', [ + 'globalThis.__sharedLoaderCount = (globalThis.__sharedLoaderCount || 0) + 1;', + 'exports.count = globalThis.__sharedLoaderCount;', + 'exports.marker = "shared";', + ].join('\n')); + fs.writeFileSync('/cjs-shared-loader-app/named.cjs', [ + 'globalThis.__sharedNamedCount = (globalThis.__sharedNamedCount || 0) + 1;', + 'exports.alpha = "alpha";', + 'module.exports.beta = "beta";', + 'exports.count = globalThis.__sharedNamedCount;', + ].join('\n')); + fs.writeFileSync('/cjs-shared-loader-app/esm-first.mjs', [ + 'import { createRequire } from "node:module";', + 'import shared from "./shared.cjs";', + 'const require = createRequire(import.meta.url);', + 'const required = require("./shared.cjs");', + 'required.fromRequire = "mutated";', + 'const resolved = require.resolve("./shared.cjs");', + 'export default {', + ' same: shared === required,', + ' count: globalThis.__sharedLoaderCount,', + ' sharedFromRequire: shared.fromRequire,', + ' cacheExportsSame: require.cache[resolved].exports === shared,', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-shared-loader-app/cjs-first.cjs', [ + 'exports.run = async function () {', + ' const required = require("./shared.cjs");', + ' required.fromCjsFirst = "yes";', + ' const imported = await import("./shared.cjs");', + ' const resolved = require.resolve("./shared.cjs");', + ' return {', + ' same: imported.default === required,', + ' count: globalThis.__sharedLoaderCount,', + ' importedMutation: imported.default.fromCjsFirst,', + ' cacheExportsSame: require.cache[resolved].exports === imported.default,', + ' };', + '};', + ].join('\n')); + fs.writeFileSync('/cjs-shared-loader-app/named-entry.mjs', [ + 'import { createRequire } from "node:module";', + 'import namedDefault, { alpha, beta, count } from "./named.cjs";', + 'const require = createRequire(import.meta.url);', + 'const required = require("./named.cjs");', + 'export default {', + ' same: namedDefault === required,', + ' alpha, beta, count,', + ' loadCount: globalThis.__sharedNamedCount,', + '};', + ].join('\n')); + fs.mkdirSync('/cjs-shared-loader-app/type-module/node_modules/dep-without-package-json', { recursive: true }); + fs.writeFileSync('/cjs-shared-loader-app/type-module/package.json', JSON.stringify({ type: 'module' })); + fs.writeFileSync('/cjs-shared-loader-app/type-module/index.js', [ + 'import dep from "dep-without-package-json/dep.js";', + 'export default { esm: true, dep };', + ].join('\n')); + fs.writeFileSync('/cjs-shared-loader-app/type-module/node_modules/dep-without-package-json/dep.js', [ + 'globalThis.__sharedBoundaryCount = (globalThis.__sharedBoundaryCount || 0) + 1;', + 'module.exports = { cjs: true, count: globalThis.__sharedBoundaryCount };', + ].join('\n')); + fs.writeFileSync('/cjs-shared-loader-app/handled.js', 'exports.source = "source";'); + + globalThis.__sharedLoaderCount = 0; + globalThis.__sharedNamedCount = 0; + globalThis.__sharedBoundaryCount = 0; + + const esmFirst = (await import('/cjs-shared-loader-app/esm-first.mjs')).default; + assert.deepStrictEqual(esmFirst, { + same: true, + count: 1, + sharedFromRequire: 'mutated', + cacheExportsSame: true, + }); + + const cjsFirst = await (await import('/cjs-shared-loader-app/cjs-first.cjs')).default.run(); + assert.deepStrictEqual(cjsFirst, { + same: true, + count: 1, + importedMutation: 'yes', + cacheExportsSame: true, + }); + + const named = (await import('/cjs-shared-loader-app/named-entry.mjs')).default; + assert.deepStrictEqual(named, { + same: true, + alpha: 'alpha', + beta: 'beta', + count: 1, + loadCount: 1, + }); + + const { createRequire } = await import('node:module'); + const require = createRequire('/cjs-shared-loader-app/main.cjs'); + const originalJsHandler = require.extensions['.js']; + try { + require.extensions['.js'] = (module) => { + module.exports = { fromExtension: true }; + }; + const handled = (await import('/cjs-shared-loader-app/handled.js')).default; + assert.deepStrictEqual(handled, { fromExtension: true }); + assert.strictEqual(require('/cjs-shared-loader-app/handled.js'), handled); + } finally { + require.extensions['.js'] = originalJsHandler; + } + + const boundary = (await import('/cjs-shared-loader-app/type-module/index.js')).default; + assert.deepStrictEqual(boundary, { + esm: true, + dep: { cjs: true, count: 1 }, + }); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testModuleSyntaxDetectionAndDiagnostics = async () => { + try { + fs.mkdirSync('/module-syntax-app/package-without-type', { recursive: true }); + fs.writeFileSync('/module-syntax-app/loose.js', [ + 'export default "loose-module";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/bridge-cjs-dep.cjs', [ + 'module.exports = { value: "cjs-dependency" };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/loose-with-comments.js', [ + '// Mention require() before ESM syntax.', + '// Ambiguous .js still has to load through the ESM bridge.', + 'import dep from "./bridge-cjs-dep.cjs";', + 'export default { kind: "module", dep: dep.value };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-source.mjs', [ + 'export const named = "named";', + 'export default "source-default";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-import-side-effect.js', [ + 'import "./static-source.mjs";', + 'export default "side-effect-import";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-import-commented.js', [ + 'globalThis.__moduleSyntaxCommentedImport = "before";', + 'import /* comment */ "./static-source.mjs";', + 'globalThis.__moduleSyntaxCommentedImport = "after";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-import-default.js', [ + 'import value from "./static-source.mjs";', + 'export default value;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-import-named.js', [ + 'import { named } from "./static-source.mjs";', + 'export default named;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-import-namespace.js', [ + 'import * as ns from "./static-source.mjs";', + 'export default ns.named;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-export-list.js', [ + 'const listed = "listed";', + 'export { listed as default };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-export-commented.js', [ + 'export /* comment */ default "commented-export";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/static-export-star.js', [ + 'export * from "./static-source.mjs";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/tla-only.js', [ + 'globalThis.__moduleSyntaxTlaOnly = "before";', + 'await Promise.resolve();', + 'globalThis.__moduleSyntaxTlaOnly = "after";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/tla-require-only.js', [ + 'await Promise.resolve();', + 'globalThis.__moduleSyntaxTlaRequireOnly = true;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/mixed-export-cjs.js', [ + 'export default "esm-wins";', + 'if (false) module.exports = { wrong: true };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/local-cjs-names.js', [ + 'const require = 1;', + 'const module = 2;', + 'const exports = 3;', + 'export default { require, module, exports };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-idiom.js', [ + 'import { createRequire } from "node:module";', + 'const require = createRequire(import.meta.url);', + 'export default { kind: typeof require, resolved: require.resolve("./false-positive.cjs") };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-spaced.js', [ + 'import { createRequire } from "node:module";', + 'const require = createRequire(import . meta . url);', + 'export default { kind: typeof require, resolved: require.resolve("./false-positive.cjs") };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-commented.js', [ + 'import { createRequire } from "node:module";', + 'const require = createRequire(import/*x*/.meta.url);', + 'export default { kind: typeof require, resolved: require.resolve("./false-positive.cjs") };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-ambiguous-spaced.js', [ + 'const require = createRequire(import . meta . url);', + 'globalThis.__moduleSyntaxAmbiguousSpaced = require.resolve("./false-positive.cjs");', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-ambiguous-commented.js', [ + 'const require = createRequire(import/*x*/.meta.url);', + 'globalThis.__moduleSyntaxAmbiguousCommented = require.resolve("./false-positive.cjs");', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-ambiguous-commented-binding.js', [ + 'const require /*b*/ = /*c*/ createRequire /*d*/ (import/*x*/.meta.url);', + 'globalThis.__moduleSyntaxAmbiguousCommentedBinding = require.resolve("./false-positive.cjs");', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/create-require-ambiguous-url-prefix-negative.js', [ + 'const require = createRequire(import.meta.urlx);', + 'globalThis.__moduleSyntaxAmbiguousUrlPrefix = require.resolve("./false-positive.cjs");', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/entry-main-dep.cjs', [ + 'module.exports = {', + ' isMain: require.main === module,', + ' mainFilename: require.main && require.main.filename,', + ' processMainFilename: process.mainModule && process.mainModule.filename,', + '};', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/entry-main.cjs', [ + 'const dep = require("./entry-main-dep.cjs");', + 'module.exports = {', + ' isMain: require.main === module,', + ' processMain: process.mainModule === module,', + ' mainFilename: require.main && require.main.filename,', + ' processMainFilename: process.mainModule && process.mainModule.filename,', + ' dep,', + '};', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/entry-main-dep.mjs', [ + 'export const main = import.meta.main;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/entry-main.mjs', [ + 'import { main as depMain } from "./entry-main-dep.mjs";', + 'export default { main: import.meta.main, depMain };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/entry-main-spaced.mjs', [ + 'export default {', + ' spaced: import . meta . main,', + ' commented: import/*x*/.meta.main,', + ' prefix: typeof import.meta.mainx,', + '};', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/import-meta-main-false-positive.mjs', [ + 'const obj = { "import": { meta: { main: 1 } }, "//": { import: { meta: { main: 2 } } } };', + 'class C { #import = { meta: { main: 1 } }; m() { return this.#import.meta.main; } }', + 'export default [', + ' "import.meta.main",', + ' /import\\.meta\\.main/.source,', + ' obj["import"].meta.main,', + ' obj.import.meta.main,', + ' obj.import . meta . main,', + ' obj["//"].import.meta.main,', + ' obj./*x*/import.meta.main,', + ' obj./* x /* y */import.meta.main,', + ' obj.//x', + ' import.meta.main,', + ' (() => { const s = ".//"; return import.meta.main; })(),', + ' new C().m(),', + ' import.meta.main,', + ' import . meta . main,', + ' import/*x*/.meta.main,', + ' typeof import.meta.mainx,', + '];', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/package-without-type/package.json', JSON.stringify({ main: 'index.js' })); + fs.writeFileSync('/module-syntax-app/package-without-type/noext-esm', [ + 'export default "extensionless-module";', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/false-positive.cjs', [ + 'const a = "export default no";', + 'const b = /import { nope } from "x"/;', + '// export const commentOnly = 1;', + '/* import "comment-only"; */', + 'exports.value = "cjs";', + ].join('\n')); + fs.mkdirSync('/module-syntax-app/type-module', { recursive: true }); + fs.writeFileSync('/module-syntax-app/type-module/package.json', JSON.stringify({ type: 'module' })); + fs.writeFileSync('/module-syntax-app/type-module/cjs.js', 'module.exports = "wrong-extension";'); + fs.writeFileSync('/module-syntax-app/type-module/require.js', 'require("x");'); + fs.writeFileSync('/module-syntax-app/type-module/exports.js', 'exports = {};'); + fs.writeFileSync('/module-syntax-app/type-module/filename.js', 'console.log(__filename);'); + fs.writeFileSync('/module-syntax-app/type-module/dirname.js', 'console.log(__dirname);'); + fs.writeFileSync('/module-syntax-app/type-module/local-require.js', 'const require = 1; export default require;'); + fs.writeFileSync('/module-syntax-app/type-module/dep.mjs', 'export default 2;'); + fs.writeFileSync('/module-syntax-app/type-module/import-module.js', 'import module from "./dep.mjs"; export default module;'); + fs.writeFileSync('/module-syntax-app/type-module/object-exports.js', 'export default { exports: 3 };'); + fs.mkdirSync('/module-syntax-app/type-commonjs', { recursive: true }); + fs.writeFileSync('/module-syntax-app/type-commonjs/package.json', JSON.stringify({ type: 'commonjs' })); + fs.writeFileSync('/module-syntax-app/type-commonjs/export-syntax.js', 'export default "not-forced";'); + fs.writeFileSync('/module-syntax-app/query.mjs', [ + 'globalThis.__queryModuleCount = (globalThis.__queryModuleCount || 0) + 1;', + 'export const count = globalThis.__queryModuleCount;', + 'export const url = import.meta.url;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/relative-query-entry.mjs', [ + 'const one = await import("./query.mjs?relative-one");', + 'const two = await import("./query.mjs?relative-two");', + 'export default {', + ' one: one.count,', + ' two: two.count,', + ' oneUrl: one.url,', + ' twoUrl: two.url,', + '};', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-data.json', JSON.stringify({ one: 1 })); + fs.writeFileSync('/module-syntax-app/attr-cjs.cjs', [ + 'exports.data = require("./attr-data.json");', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-entry.mjs', [ + 'import data from "./attr-data.json" with { type: "json" };', + 'import dataWithQuery from "./attr-data.json?cache" with { type: "json" };', + 'import cjs from "./attr-cjs.cjs";', + 'export default {', + ' data,', + ' dataWithQuery,', + ' sameAsCjs: data === cjs.data,', + ' querySameAsCjs: dataWithQuery === cjs.data,', + '};', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-missing.mjs', [ + 'import data from "./attr-data.json";', + 'export default data;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-type-mismatch.mjs', [ + 'import value from "./static-source.mjs" with { type: "json" };', + 'export default value;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-unsupported.mjs', [ + 'import data from "./attr-data.json" with { type: "unsupported" };', + 'export default data;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-data-url-entry.mjs', [ + 'import data from "data:application/json,{%22two%22:2}" with { type: "json" };', + 'export default data;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/attr-data-url-missing.mjs', [ + 'import data from "data:application/json,{%22two%22:2}";', + 'export default data;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/member-false-positive.js', [ + 'const obj = { import: 1 };', + 'obj.import;', + 'const = ;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/property-false-positive.js', [ + '({ export: 1 });', + 'const = ;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/dynamic-import-false-positive.js', [ + 'import("./static-source.mjs");', + 'const = ;', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/line-start-comment-export.js', [ + '/*', + 'export default "not module";', + '*/', + 'module.exports = { value: "comment-export" };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/line-start-template-export.js', [ + 'const text = `', + 'export default "not module";', + '`;', + 'module.exports = { value: text.includes("not module") };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/import-prefix-identifiers.js', [ + 'const important = 1;', + 'let imported = 2;', + 'module.exports = { value: important + imported };', + ].join('\n')); + fs.writeFileSync('/module-syntax-app/export-prefix-identifier.js', [ + 'const exported = 3;', + 'module.exports = { value: exported };', + ].join('\n')); + + const { createRequire } = await import('node:module'); + const require = createRequire('/module-syntax-app/main.cjs'); + + assert.strictEqual(require('/module-syntax-app/loose.js').default, 'loose-module'); + assert.deepStrictEqual(require('/module-syntax-app/loose-with-comments.js').default, { + kind: 'module', + dep: 'cjs-dependency', + }); + assert.strictEqual(require('/module-syntax-app/static-import-side-effect.js').default, 'side-effect-import'); + globalThis.__moduleSyntaxCommentedImport = undefined; + require('/module-syntax-app/static-import-commented.js'); + assert.strictEqual(globalThis.__moduleSyntaxCommentedImport, 'after'); + assert.strictEqual(require('/module-syntax-app/static-import-default.js').default, 'source-default'); + assert.strictEqual(require('/module-syntax-app/static-import-named.js').default, 'named'); + assert.strictEqual(require('/module-syntax-app/static-import-namespace.js').default, 'named'); + assert.strictEqual(require('/module-syntax-app/static-export-list.js').default, 'listed'); + assert.strictEqual(require('/module-syntax-app/static-export-commented.js').default, 'commented-export'); + assert.strictEqual(require('/module-syntax-app/static-export-star.js').named, 'named'); + assert.strictEqual(require('/module-syntax-app/package-without-type/noext-esm').default, 'extensionless-module'); + assert.deepStrictEqual(require('/module-syntax-app/false-positive.cjs'), { value: 'cjs' }); + assert.deepStrictEqual(require('/module-syntax-app/line-start-comment-export.js'), { value: 'comment-export' }); + assert.deepStrictEqual(require('/module-syntax-app/line-start-template-export.js'), { value: true }); + assert.deepStrictEqual(require('/module-syntax-app/import-prefix-identifiers.js'), { value: 3 }); + assert.deepStrictEqual(require('/module-syntax-app/export-prefix-identifier.js'), { value: 3 }); + globalThis.__moduleSyntaxTlaOnly = undefined; + await import('/module-syntax-app/tla-only.js'); + assert.strictEqual(globalThis.__moduleSyntaxTlaOnly, 'after'); + assert.throws(() => require('/module-syntax-app/tla-require-only.js'), /async|top-level await|ERR_REQUIRE_ASYNC_MODULE/i); + assert.strictEqual(require('/module-syntax-app/mixed-export-cjs.js').default, 'esm-wins'); + assert.strictEqual((await import('/module-syntax-app/mixed-export-cjs.js')).default, 'esm-wins'); + assert.deepStrictEqual(require('/module-syntax-app/local-cjs-names.js').default, { + require: 1, + module: 2, + exports: 3, + }); + const createRequireIdiom = require('/module-syntax-app/create-require-idiom.js').default; + assert.deepStrictEqual(createRequireIdiom, { + kind: 'function', + resolved: '/module-syntax-app/false-positive.cjs', + }); + assert.deepStrictEqual(require('/module-syntax-app/create-require-spaced.js').default, createRequireIdiom); + assert.deepStrictEqual(require('/module-syntax-app/create-require-commented.js').default, createRequireIdiom); + globalThis.createRequire = createRequire; + globalThis.__moduleSyntaxAmbiguousSpaced = undefined; + globalThis.__moduleSyntaxAmbiguousCommented = undefined; + globalThis.__moduleSyntaxAmbiguousCommentedBinding = undefined; + globalThis.__moduleSyntaxAmbiguousUrlPrefix = undefined; + require('/module-syntax-app/create-require-ambiguous-spaced.js'); + require('/module-syntax-app/create-require-ambiguous-commented.js'); + require('/module-syntax-app/create-require-ambiguous-commented-binding.js'); + assert.throws(() => require('/module-syntax-app/create-require-ambiguous-url-prefix-negative.js'), /urlx|undefined/i); + assert.throws(() => require('/module-syntax-app/type-commonjs/export-syntax.js'), /export|Unexpected/i); + assert.strictEqual(globalThis.__moduleSyntaxAmbiguousSpaced, '/module-syntax-app/false-positive.cjs'); + assert.strictEqual(globalThis.__moduleSyntaxAmbiguousCommented, '/module-syntax-app/false-positive.cjs'); + assert.strictEqual(globalThis.__moduleSyntaxAmbiguousCommentedBinding, '/module-syntax-app/false-positive.cjs'); + assert.strictEqual(globalThis.__moduleSyntaxAmbiguousUrlPrefix, undefined); + delete globalThis.createRequire; + + const originalArgv = process.argv.slice(); + const originalMainModule = process.mainModule; + const originalRequireMain = { + id: require.main.id, + filename: require.main.filename, + path: require.main.path, + exports: require.main.exports, + loaded: require.main.loaded, + parent: require.main.parent, + children: require.main.children.slice(), + paths: require.main.paths ? require.main.paths.slice() : require.main.paths, + }; + try { + process.argv[1] = '/module-syntax-app/entry-main.cjs'; + const cjsMain = require('/module-syntax-app/entry-main.cjs'); + assert.deepStrictEqual(cjsMain, { + isMain: true, + processMain: true, + mainFilename: '/module-syntax-app/entry-main.cjs', + processMainFilename: '/module-syntax-app/entry-main.cjs', + dep: { + isMain: false, + mainFilename: '/module-syntax-app/entry-main.cjs', + processMainFilename: '/module-syntax-app/entry-main.cjs', + }, + }); + + process.argv[1] = '/module-syntax-app/entry-main.mjs'; + const esmMain = (await import('/module-syntax-app/entry-main.mjs')).default; + assert.deepStrictEqual(esmMain, { main: true, depMain: false }); + process.argv[1] = '/module-syntax-app/entry-main-spaced.mjs'; + assert.deepStrictEqual((await import('/module-syntax-app/entry-main-spaced.mjs')).default, { + spaced: true, + commented: true, + prefix: 'undefined', + }); + assert.deepStrictEqual(await import('/module-syntax-app/import-meta-main-false-positive.mjs').then((m) => m.default), [ + 'import.meta.main', + 'import\\.meta\\.main', + 1, + 1, + 1, + 2, + 1, + 1, + 1, + false, + 1, + false, + false, + false, + 'undefined', + ]); + } finally { + Object.assign(require.main, originalRequireMain); + process.argv = originalArgv; + process.mainModule = originalMainModule; + } + + await expectImportRejectsMessage('/module-syntax-app/type-module/cjs.js', /use the '\.cjs' file extension/); + await expectImportRejectsMessage('/module-syntax-app/type-module/require.js', /require is not defined.*use the '\.cjs' file extension/); + await expectImportRejectsMessage('/module-syntax-app/type-module/exports.js', /exports is not defined.*use the '\.cjs' file extension/); + await expectImportRejectsMessage('/module-syntax-app/type-module/filename.js', /__filename is not defined.*use the '\.cjs' file extension/); + await expectImportRejectsMessage('/module-syntax-app/type-module/dirname.js', /__dirname is not defined.*use the '\.cjs' file extension/); + assert.strictEqual((await import('/module-syntax-app/type-module/local-require.js')).default, 1); + assert.strictEqual((await import('/module-syntax-app/type-module/import-module.js')).default, 2); + assert.deepStrictEqual((await import('/module-syntax-app/type-module/object-exports.js')).default, { exports: 3 }); + await expectImportRejectsMessage('data:text/javascript,require;', /require.*not defined/i); + await expectImportRejectsMessage('data:text/javascript,exports={};', /exports.*not defined/i); + await expectImportRejectsMessage('data:text/javascript,require_custom;', /^(?!.*in ES module scope)(?!.*use import instead).*$/); + + const propertyKeyModule = await import('data:text/javascript,export default { require: 1 };'); + assert.deepStrictEqual(propertyKeyModule.default, { require: 1 }); + const localBindingModule = await import('data:text/javascript,const module = 1; export default module;'); + assert.strictEqual(localBindingModule.default, 1); + const importBindingModule = await import('data:text/javascript,import require from "data:text/javascript,export default 1"; export default require;'); + assert.strictEqual(importBindingModule.default, 1); + const namespaceImportBindingModule = await import('data:text/javascript,import * as module from "data:text/javascript,export default 1"; export default module.default;'); + assert.strictEqual(namespaceImportBindingModule.default, 1); + const namedImportBindingModule = await import('data:text/javascript,import { value as exports } from "data:text/javascript,export const value = 1"; export default exports;'); + assert.strictEqual(namedImportBindingModule.default, 1); + const functionParamModule = await import('data:text/javascript,function f(require) { return require; } export default f(1);'); + assert.strictEqual(functionParamModule.default, 1); + const arrowParamModule = await import('data:text/javascript,export default ((require) => require)(1);'); + assert.strictEqual(arrowParamModule.default, 1); + const methodNameModule = await import('data:text/javascript,export default { require() { return 1; }, f(module) { return module; } }.f(2);'); + assert.strictEqual(methodNameModule.default, 2); + const asyncMethodModule = await import('data:text/javascript,export default { async require() { return 1; } };'); + assert.strictEqual(await asyncMethodModule.default.require(), 1); + const generatorMethodModule = await import('data:text/javascript,export default { *module() { yield 1; } }.module().next().value;'); + assert.strictEqual(generatorMethodModule.default, 1); + const getterMethodModule = await import('data:text/javascript,export default { get exports() { return 1; } }.exports;'); + assert.strictEqual(getterMethodModule.default, 1); + const stringKeyMethodModule = await import('data:text/javascript,export default { "x"(require) { return require; } }.x(1);'); + assert.strictEqual(stringKeyMethodModule.default, 1); + const commentedMethodModule = await import('data:text/javascript,export default { /* comment */ require() { return 1; } }.require();'); + assert.strictEqual(commentedMethodModule.default, 1); + const generatorModule = await import('data:text/javascript,function* module() { yield 1; } export default module().next().value;'); + assert.strictEqual(generatorModule.default, 1); + const multiDeclarationModule = await import('data:text/javascript,const a = 0,\n require = 1;\nexport default require;'); + assert.strictEqual(multiDeclarationModule.default, 1); + const destructuringModule = await import('data:text/javascript,const {\n module\n} = { module: 1 };\nexport default module;'); + assert.strictEqual(destructuringModule.default, 1); + const memberNameModule = await import('data:text/javascript,export default import.meta.require;'); + assert.strictEqual(memberNameModule.default, undefined); + + globalThis.__queryModuleCount = 0; + const queryBase = pathToFileURL('/module-syntax-app/query.mjs').href; + const queryOne = await import(`${queryBase}?one`); + const queryTwo = await import(`${queryBase}?two`); + assert.strictEqual(queryOne.count, 1); + assert.strictEqual(queryTwo.count, 2); + assert.match(queryOne.url, /\?one$/); + assert.match(queryTwo.url, /\?two$/); + const relativeQuery = (await import('/module-syntax-app/relative-query-entry.mjs')).default; + assert.deepStrictEqual(relativeQuery, { + one: 3, + two: 4, + oneUrl: 'file:///module-syntax-app/query.mjs?relative-one', + twoUrl: 'file:///module-syntax-app/query.mjs?relative-two', + }); + const attrEntry = (await import('/module-syntax-app/attr-entry.mjs')).default; + assert.deepStrictEqual(attrEntry, { + data: { one: 1 }, + dataWithQuery: { one: 1 }, + sameAsCjs: true, + querySameAsCjs: false, + }); + assert.deepStrictEqual((await import('/module-syntax-app/attr-data-url-entry.mjs')).default, { two: 2 }); + await expectImportRejectsCode('/module-syntax-app/attr-missing.mjs', 'ERR_IMPORT_ATTRIBUTE_MISSING'); + await expectImportRejectsCode('/module-syntax-app/attr-type-mismatch.mjs', 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE'); + await expectImportRejectsCode('/module-syntax-app/attr-unsupported.mjs', 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED'); + await expectImportRejectsCode('/module-syntax-app/attr-data-url-missing.mjs', 'ERR_IMPORT_ATTRIBUTE_MISSING'); + + assert.throws(() => require('/module-syntax-app/member-false-positive.js'), /unexpected|expecting|SyntaxError/i); + assert.throws(() => require('/module-syntax-app/property-false-positive.js'), /unexpected|expecting|SyntaxError/i); + assert.throws(() => require('/module-syntax-app/dynamic-import-false-positive.js'), /unexpected|expecting|SyntaxError/i); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testPackageCustomConditions = async () => { + const hadPackageConditions = Object.prototype.hasOwnProperty.call(globalThis, '__wasm_rquickjs_package_conditions'); + const originalPackageConditions = globalThis.__wasm_rquickjs_package_conditions; + try { + globalThis.__wasm_rquickjs_package_conditions = ['custom-condition', 'another']; + + fs.mkdirSync('/package-custom-conditions-app/node_modules/conditional-pkg', { recursive: true }); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/package.json', JSON.stringify({ + exports: { + './condition': { + 'custom-condition': { + import: './custom.mjs', + require: './custom.cjs', + }, + another: './another.mjs', + import: './import.mjs', + require: './require.cjs', + default: './default.mjs', + }, + }, + })); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/custom.mjs', 'export default "custom-import";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/custom.cjs', 'exports.selected = "custom-require";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/another.mjs', 'export default "another";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/import.mjs', 'export default "import";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/require.cjs', 'module.exports = "require";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/conditional-pkg/default.mjs', 'export default "default";'); + fs.writeFileSync('/package-custom-conditions-app/entry.mjs', [ + 'import selected from "conditional-pkg/condition";', + 'export default selected;', + ].join('\n')); + fs.writeFileSync('/package-custom-conditions-app/reexport.cjs', 'module.exports = require("conditional-pkg/condition");'); + fs.writeFileSync('/package-custom-conditions-app/facade-entry.mjs', [ + 'import { selected } from "./reexport.cjs";', + 'export default selected;', + ].join('\n')); + + const imported = (await import('/package-custom-conditions-app/entry.mjs')).default; + assert.strictEqual(imported, 'custom-import'); + + const { createRequire } = await import('node:module'); + const require = createRequire('/package-custom-conditions-app/entry.cjs'); + assert.deepStrictEqual(require('conditional-pkg/condition'), { selected: 'custom-require' }); + + const facadeImported = (await import('/package-custom-conditions-app/facade-entry.mjs')).default; + assert.strictEqual(facadeImported, 'custom-require'); + + fs.mkdirSync('/package-custom-conditions-app/node_modules/sparse-pkg', { recursive: true }); + fs.writeFileSync('/package-custom-conditions-app/node_modules/sparse-pkg/package.json', JSON.stringify({ + exports: { + './condition': { + undefined: './bad.mjs', + null: './bad.mjs', + import: './import.mjs', + require: './require.cjs', + default: './default.mjs', + }, + }, + })); + fs.writeFileSync('/package-custom-conditions-app/node_modules/sparse-pkg/bad.mjs', 'export default "bad";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/sparse-pkg/import.mjs', 'export default "sparse-import";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/sparse-pkg/require.cjs', 'module.exports = "sparse-require";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/sparse-pkg/default.mjs', 'export default "sparse-default";'); + fs.writeFileSync('/package-custom-conditions-app/sparse-entry.mjs', [ + 'import selected from "sparse-pkg/condition";', + 'export default selected;', + ].join('\n')); + + globalThis.__wasm_rquickjs_package_conditions = Array(1); + assert.strictEqual((await import('/package-custom-conditions-app/sparse-entry.mjs')).default, 'sparse-import'); + assert.strictEqual(require('sparse-pkg/condition'), 'sparse-require'); + + fs.mkdirSync('/package-custom-conditions-app/node_modules/undefined-pkg', { recursive: true }); + fs.writeFileSync('/package-custom-conditions-app/node_modules/undefined-pkg/package.json', JSON.stringify({ + exports: { + './condition': { + undefined: './bad.mjs', + null: './bad.mjs', + import: './import.mjs', + require: './require.cjs', + default: './default.mjs', + }, + }, + })); + fs.writeFileSync('/package-custom-conditions-app/node_modules/undefined-pkg/bad.mjs', 'export default "bad";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/undefined-pkg/import.mjs', 'export default "undefined-import";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/undefined-pkg/require.cjs', 'module.exports = "undefined-require";'); + fs.writeFileSync('/package-custom-conditions-app/node_modules/undefined-pkg/default.mjs', 'export default "undefined-default";'); + fs.writeFileSync('/package-custom-conditions-app/undefined-entry.mjs', [ + 'import selected from "undefined-pkg/condition";', + 'export default selected;', + ].join('\n')); + globalThis.__wasm_rquickjs_package_conditions = [undefined, null]; + assert.strictEqual((await import('/package-custom-conditions-app/undefined-entry.mjs')).default, 'undefined-import'); + assert.strictEqual(require('undefined-pkg/condition'), 'undefined-require'); + + return true; + } catch (error) { + console.error(error); + throw error; + } finally { + if (hadPackageConditions) { + globalThis.__wasm_rquickjs_package_conditions = originalPackageConditions; + } else { + delete globalThis.__wasm_rquickjs_package_conditions; + } + } +}; + +export const testCjsPackageJsonParseCache = async () => { + try { + const root = '/package-json-cache-app'; + const require = createRequire(`${root}/entry.cjs`); + fs.mkdirSync(`${root}/node_modules/cached-pkg`, { recursive: true }); + fs.writeFileSync(`${root}/node_modules/cached-pkg/package.json`, JSON.stringify({ + exports: './entry.js', + })); + fs.writeFileSync(`${root}/node_modules/cached-pkg/entry.js`, 'module.exports = { cached: true };'); + fs.writeFileSync(`${root}/node_modules/cached-pkg/changed.js`, 'module.exports = { changed: true };'); + fs.writeFileSync(`${root}/app.cjs`, [ + 'const assert = require("assert");', + 'const path = require("path");', + 'const fs = require("fs");', + 'const pkgJsonPath = "/package-json-cache-app/node_modules/cached-pkg/package.json";', + 'const first = require.resolve("cached-pkg");', + 'fs.writeFileSync(pkgJsonPath, JSON.stringify({ exports: "./changed.js" }));', + 'const second = require.resolve("cached-pkg");', + 'assert.strictEqual(first, second);', + 'assert.strictEqual(path.basename(first), "entry.js");', + 'module.exports = true;', + ].join('\n')); + + assert.strictEqual(require(`${root}/app.cjs`), true); + + fs.mkdirSync(`${root}/node_modules/cached-esm-pkg`, { recursive: true }); + fs.writeFileSync(`${root}/node_modules/cached-esm-pkg/package.json`, JSON.stringify({ + exports: { + './first': './entry.mjs', + './second': './entry.mjs', + }, + })); + fs.writeFileSync(`${root}/node_modules/cached-esm-pkg/entry.mjs`, 'export default { cached: true };'); + fs.writeFileSync(`${root}/node_modules/cached-esm-pkg/changed.mjs`, 'export default { changed: true };'); + fs.writeFileSync(`${root}/esm-entry.mjs`, [ + 'const fs = await import("node:fs");', + 'const first = await import("cached-esm-pkg/first");', + 'fs.writeFileSync("/package-json-cache-app/node_modules/cached-esm-pkg/package.json", JSON.stringify({ exports: { "./second": "./changed.mjs" } }));', + 'const second = await import("cached-esm-pkg/second");', + 'export default { first: first.default, second: second.default };', + ].join('\n')); + + assert.deepStrictEqual((await import(`${root}/esm-entry.mjs`)).default, { + first: { cached: true }, + second: { cached: true }, + }); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsPackageReexportNamedExports = async () => { + try { + fs.mkdirSync('/cjs-package-reexport-app/node_modules/pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/pkg/index.js', [ + 'exports.alpha = "alpha";', + 'exports.beta = "beta";', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/pkg/subpath.js', [ + 'exports.sub = "sub";', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/reexport-package.cjs', 'module.exports = require("pkg");'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-subpath.cjs', 'module.exports = require("pkg/subpath");'); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/file-pkg.js', 'exports.file = "file";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-file-package.cjs', 'module.exports = require("file-pkg");'); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/bare-non-string-main', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-non-string-main/package.json', JSON.stringify({ + main: {}, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-non-string-main/index.js', 'exports.bareNonStringMain = "bare-non-string-main";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-bare-non-string-main.cjs', 'module.exports = require("bare-non-string-main");'); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/bare-null-main', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-null-main/package.json', JSON.stringify({ + main: null, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-null-main/index.js', 'exports.bareNullMain = "bare-null-main";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-bare-null-main.cjs', 'module.exports = require("bare-null-main");'); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/bare-non-string-type', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-non-string-type/package.json', JSON.stringify({ + type: {}, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-non-string-type/index.js', 'exports.bareNonStringType = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-bare-non-string-type.cjs', 'module.exports = require("bare-non-string-type");'); + fs.writeFileSync('/cjs-package-reexport-app/bare-non-string-type-entry.mjs', [ + 'import { bareNonStringType } from "./reexport-bare-non-string-type.cjs";', + 'export default bareNonStringType;', + ].join('\n')); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/bare-null-type', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-null-type/package.json', JSON.stringify({ + type: null, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-null-type/index.js', 'exports.bareNullType = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-bare-null-type.cjs', 'module.exports = require("bare-null-type");'); + fs.writeFileSync('/cjs-package-reexport-app/bare-null-type-entry.mjs', [ + 'import { bareNullType } from "./reexport-bare-null-type.cjs";', + 'export default bareNullType;', + ].join('\n')); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/bare-non-string-name', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-non-string-name/package.json', JSON.stringify({ + name: {}, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-non-string-name/index.js', 'exports.bareNonStringName = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-bare-non-string-name.cjs', 'module.exports = require("bare-non-string-name");'); + fs.writeFileSync('/cjs-package-reexport-app/bare-non-string-name-entry.mjs', [ + 'import { bareNonStringName } from "./reexport-bare-non-string-name.cjs";', + 'export default bareNonStringName;', + ].join('\n')); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/bare-null-name', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-null-name/package.json', JSON.stringify({ + name: null, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/bare-null-name/index.js', 'exports.bareNullName = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-bare-null-name.cjs', 'module.exports = require("bare-null-name");'); + fs.writeFileSync('/cjs-package-reexport-app/bare-null-name-entry.mjs', [ + 'import { bareNullName } from "./reexport-bare-null-name.cjs";', + 'export default bareNullName;', + ].join('\n')); + + fs.mkdirSync('/cjs-package-reexport-app/node_modules/exported-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/package.json', JSON.stringify({ + exports: { + '.': './main.cjs', + './feature': './feature.cjs', + './condition': { + import: './import.mjs', + 'module-sync': './sync.cjs', + require: './require.cjs', + default: './default.cjs', + }, + }, + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/main.cjs', 'exports.main = "main";'); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/feature.cjs', 'exports.feature = "feature";'); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/sync.cjs', 'exports.condition = "module-sync";'); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/require.cjs', 'exports.condition = "require";'); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/default.cjs', 'exports.condition = "default";'); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/exported-pkg/import.mjs', 'export const condition = "import";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-exported-root.cjs', 'module.exports = require("exported-pkg");'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-exported-feature.cjs', 'module.exports = require("exported-pkg/feature");'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-exported-condition.cjs', 'module.exports = require("exported-pkg/condition");'); + + fs.writeFileSync('/cjs-package-reexport-app/package.json', JSON.stringify({ + imports: { + '#dep': './imports-target.cjs', + '#condition': { + import: './imports-import.mjs', + 'module-sync': './imports-sync.cjs', + require: './imports-require.cjs', + default: './imports-default.cjs', + }, + '#pattern/*': './imports-pattern/*.cjs', + }, + })); + fs.writeFileSync('/cjs-package-reexport-app/imports-target.cjs', 'exports.imported = "imported";'); + fs.writeFileSync('/cjs-package-reexport-app/imports-sync.cjs', 'exports.importsCondition = "module-sync";'); + fs.writeFileSync('/cjs-package-reexport-app/imports-require.cjs', 'exports.requireOnly = "require";'); + fs.writeFileSync('/cjs-package-reexport-app/imports-default.cjs', 'exports.defaultOnly = "default";'); + fs.writeFileSync('/cjs-package-reexport-app/imports-import.mjs', 'export const importOnly = "import";'); + fs.mkdirSync('/cjs-package-reexport-app/imports-pattern', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/imports-pattern/target.cjs', 'exports.importsPattern = "pattern";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-imports.cjs', 'module.exports = require("#dep");'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-imports-condition.cjs', 'module.exports = require("#condition");'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-imports-pattern.cjs', 'module.exports = require("#pattern/target");'); + + fs.mkdirSync('/cjs-package-reexport-app/relative-main-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/relative-main-pkg/package.json', JSON.stringify({ + main: 'main.cjs', + })); + fs.writeFileSync('/cjs-package-reexport-app/relative-main-pkg/main.cjs', 'exports.relativeMain = "relative-main";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-relative-main.cjs', 'module.exports = require("./relative-main-pkg");'); + fs.mkdirSync('/cjs-package-reexport-app/relative-index-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/relative-index-pkg/index.js', 'exports.relativeIndex = "relative-index";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-relative-index.cjs', 'module.exports = require("./relative-index-pkg");'); + fs.mkdirSync('/cjs-package-reexport-app/non-string-main-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/non-string-main-pkg/package.json', JSON.stringify({ + main: {}, + })); + fs.writeFileSync('/cjs-package-reexport-app/non-string-main-pkg/index.js', 'exports.nonStringMainIndex = "non-string-main-index";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-non-string-main.cjs', 'module.exports = require("./non-string-main-pkg");'); + fs.mkdirSync('/cjs-package-reexport-app/malformed-relative-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/malformed-relative-pkg/package.json', '{'); + fs.writeFileSync('/cjs-package-reexport-app/malformed-relative-pkg/index.js', 'exports.malformedIndex = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-malformed-relative.cjs', 'module.exports = require("./malformed-relative-pkg");'); + fs.writeFileSync('/cjs-package-reexport-app/malformed-relative-entry.mjs', [ + 'import { malformedIndex } from "./reexport-malformed-relative.cjs";', + 'export default malformedIndex;', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/json-analysis.json', '"exports.jsonFalsePositive = true;"'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-json-analysis.cjs', 'module.exports = require("./json-analysis");'); + fs.writeFileSync('/cjs-package-reexport-app/json-analysis-entry.mjs', [ + 'import { jsonFalsePositive } from "./reexport-json-analysis.cjs";', + 'export default jsonFalsePositive;', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/implicit-cjs.cjs', 'exports.implicitCjs = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-implicit-cjs.cjs', 'module.exports = require("./implicit-cjs");'); + fs.writeFileSync('/cjs-package-reexport-app/implicit-cjs-entry.mjs', [ + 'import { implicitCjs } from "./reexport-implicit-cjs.cjs";', + 'export default implicitCjs;', + ].join('\n')); + + fs.mkdirSync('/cjs-package-reexport-app/node_modules/transitive-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/transitive-pkg/index.js', [ + 'exports.gamma = "gamma";', + 'exports.delta = "delta";', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/reexport-transpiler.cjs', [ + 'var dep = require("transitive-pkg");', + 'Object.keys(dep).forEach(function (key) {', + ' if (key === "default" || key === "__esModule") return;', + ' Object.defineProperty(exports, key, {', + ' enumerable: true,', + ' get: function () { return dep[key]; }', + ' });', + '});', + ].join('\n')); + + fs.mkdirSync('/cjs-package-reexport-app/node_modules/cycle-pkg', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/cycle-a.cjs', [ + 'module.exports = require("cycle-pkg");', + 'exports.a = "a";', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/cycle-pkg/index.js', [ + 'module.exports = require("../../cycle-a.cjs");', + 'exports.b = "b";', + ].join('\n')); + + fs.writeFileSync('/cjs-package-reexport-app/reexport-continuation.cjs', [ + 'var ignored = require("pkg").nested;', + 'exports.own = "own";', + ].join('\n')); + + fs.writeFileSync('/cjs-package-reexport-app/node_modules/analysis-native.node', 'not a native addon'); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/analysis-native', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/analysis-native/index.js', 'exports.wrong = "wrong";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-native.cjs', 'module.exports = require("analysis-native");'); + fs.writeFileSync('/cjs-package-reexport-app/native-named-entry.mjs', [ + 'import { wrong } from "./reexport-native.cjs";', + 'export default wrong;', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/analysis-exports.js', 'exports.wrong = "wrong";'); + fs.mkdirSync('/cjs-package-reexport-app/node_modules/analysis-exports', { recursive: true }); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/analysis-exports/package.json', JSON.stringify({ + exports: './main.cjs', + })); + fs.writeFileSync('/cjs-package-reexport-app/node_modules/analysis-exports/main.cjs', 'exports.right = "right";'); + fs.writeFileSync('/cjs-package-reexport-app/reexport-analysis-exports.cjs', 'module.exports = require("analysis-exports");'); + fs.writeFileSync('/cjs-package-reexport-app/analysis-exports-entry.mjs', [ + 'import { right } from "./reexport-analysis-exports.cjs";', + 'export default right;', + ].join('\n')); + fs.writeFileSync('/cjs-package-reexport-app/analysis-exports-wrong-entry.mjs', [ + 'import { wrong } from "./reexport-analysis-exports.cjs";', + 'export default wrong;', + ].join('\n')); + + fs.writeFileSync('/cjs-package-reexport-app/package-entry.mjs', [ + 'import packageDefault, { alpha, beta } from "./reexport-package.cjs";', + 'import { sub } from "./reexport-subpath.cjs";', + 'import { file } from "./reexport-file-package.cjs";', + 'import { bareNonStringMain } from "./reexport-bare-non-string-main.cjs";', + 'import { bareNullMain } from "./reexport-bare-null-main.cjs";', + 'import { main } from "./reexport-exported-root.cjs";', + 'import { feature } from "./reexport-exported-feature.cjs";', + 'import { condition } from "./reexport-exported-condition.cjs";', + 'import { imported } from "./reexport-imports.cjs";', + 'import { importsCondition } from "./reexport-imports-condition.cjs";', + 'import { importsPattern } from "./reexport-imports-pattern.cjs";', + 'import { relativeMain } from "./reexport-relative-main.cjs";', + 'import { relativeIndex } from "./reexport-relative-index.cjs";', + 'import { nonStringMainIndex } from "./reexport-non-string-main.cjs";', + 'import { gamma, delta } from "./reexport-transpiler.cjs";', + 'import * as continuation from "./reexport-continuation.cjs";', + 'import * as cycle from "./cycle-a.cjs";', + 'export default {', + ' alpha, beta, defaultAlpha: packageDefault.alpha, sub, file, bareNonStringMain, bareNullMain, main, feature, condition, imported, importsCondition, importsPattern, relativeMain, relativeIndex, nonStringMainIndex, gamma, delta,', + ' continuationKeys: Object.keys(continuation).filter((key) => key !== "default" && key !== "own"),', + ' continuationOwn: continuation.own,', + ' cycleKeys: Object.keys(cycle).filter((key) => key !== "default").sort(),', + '};', + ].join('\n')); + + const result = (await import('/cjs-package-reexport-app/package-entry.mjs')).default; + assert.deepStrictEqual(result, { + alpha: 'alpha', + beta: 'beta', + defaultAlpha: 'alpha', + sub: 'sub', + file: 'file', + bareNonStringMain: 'bare-non-string-main', + bareNullMain: 'bare-null-main', + main: 'main', + feature: 'feature', + condition: 'module-sync', + imported: 'imported', + importsCondition: 'module-sync', + importsPattern: 'pattern', + relativeMain: 'relative-main', + relativeIndex: 'relative-index', + nonStringMainIndex: 'non-string-main-index', + gamma: 'gamma', + delta: 'delta', + continuationKeys: [], + continuationOwn: 'own', + cycleKeys: ['a', 'b'], + }); + await assert.rejects(() => import('/cjs-package-reexport-app/native-named-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'wrong' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/implicit-cjs-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'implicitCjs' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/malformed-relative-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'malformedIndex' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/json-analysis-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'jsonFalsePositive' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/bare-non-string-type-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'bareNonStringType' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/bare-null-type-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'bareNullType' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/bare-non-string-name-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'bareNonStringName' not found/, + }); + await assert.rejects(() => import('/cjs-package-reexport-app/bare-null-name-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'bareNullName' not found/, + }); + assert.strictEqual((await import('/cjs-package-reexport-app/analysis-exports-entry.mjs')).default, 'right'); + await assert.rejects(() => import('/cjs-package-reexport-app/analysis-exports-wrong-entry.mjs'), { + name: 'SyntaxError', + message: /Named export 'wrong' not found/, + }); + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testFindPackageJson = async () => { + try { + const { createRequire, findPackageJSON } = await import('node:module'); + const require = createRequire('/find-package-json-app/entry.cjs'); + + fs.mkdirSync('/find-package-json-app/node_modules/pkg/subfolder', { recursive: true }); + fs.mkdirSync('/find-package-json-app/node_modules/pkg/subfolder2', { recursive: true }); + fs.mkdirSync('/find-package-json-app/node_modules/pkg2', { recursive: true }); + fs.mkdirSync('/find-package-json-app/packages/nested/sub-pkg-cjs', { recursive: true }); + fs.mkdirSync('/find-package-json-app/packages/nested/sub-pkg-esm', { recursive: true }); + + fs.writeFileSync('/find-package-json-app/package.json', JSON.stringify({ name: 'root-app' })); + fs.writeFileSync('/find-package-json-app/packages/nested/package.json', JSON.stringify({ name: 'nested-parent' })); + fs.writeFileSync('/find-package-json-app/packages/nested/sub-pkg-cjs/index.cjs', [ + 'const { findPackageJSON } = require("node:module");', + 'module.exports = findPackageJSON("..", __filename);', + ].join('\n')); + fs.writeFileSync('/find-package-json-app/packages/nested/sub-pkg-esm/index.mjs', [ + 'import { findPackageJSON } from "node:module";', + 'export default findPackageJSON("..", import.meta.url);', + ].join('\n')); + + fs.writeFileSync('/find-package-json-app/node_modules/pkg/subfolder/index.js', 'module.exports = { subfolder: true };'); + fs.writeFileSync('/find-package-json-app/node_modules/pkg/subfolder/package.json', JSON.stringify({ + name: 'pkg-subfolder', + secretNumberSubfolder: 11, + })); + fs.writeFileSync('/find-package-json-app/node_modules/pkg/subfolder2/index.js', 'module.exports = { subfolder2: true };'); + fs.writeFileSync('/find-package-json-app/node_modules/pkg/subfolder2/package.json', JSON.stringify({ + name: 'pkg-subfolder2', + secretNumberSubfolder2: 22, + })); + fs.writeFileSync('/find-package-json-app/node_modules/pkg/package.json', JSON.stringify({ + name: 'pkg', + exports: './subfolder/index.js', + secretNumberPkgRoot: 33, + })); + fs.writeFileSync('/find-package-json-app/node_modules/pkg2/package.json', JSON.stringify({ + name: 'pkg2', + main: '/find-package-json-app/node_modules/pkg/subfolder2/index.js', + secretNumberPkg2: 44, + })); + + assert.throws( + () => findPackageJSON(), + { code: 'ERR_MISSING_ARGS' }, + ); + + for (const invalidBase of [null, {}, [], Symbol('invalid'), () => {}, true, false, 1, 0]) { + assert.throws( + () => findPackageJSON('', invalidBase), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); + } + + const basePath = '/find-package-json-app/entry.mjs'; + const baseUrl = pathToFileURL(basePath); + const subfolderPackageJson = '/find-package-json-app/node_modules/pkg/subfolder/package.json'; + const nestedPackageJson = '/find-package-json-app/packages/nested/package.json'; + const pkgRootPackageJson = '/find-package-json-app/node_modules/pkg/package.json'; + const pkg2RootPackageJson = '/find-package-json-app/node_modules/pkg2/package.json'; + + assert.strictEqual( + findPackageJSON('./node_modules/pkg/subfolder/index.js', baseUrl.href), + subfolderPackageJson, + ); + assert.strictEqual( + findPackageJSON(new URL('./node_modules/pkg/subfolder/index.js', baseUrl), baseUrl), + subfolderPackageJson, + ); + assert.strictEqual( + findPackageJSON('./node_modules/pkg/subfolder/index.js', basePath), + subfolderPackageJson, + ); + + const cjsParentPackageJson = require('/find-package-json-app/packages/nested/sub-pkg-cjs/index.cjs'); + assert.strictEqual(cjsParentPackageJson, nestedPackageJson); + + const esmParentPackageJson = (await import('/find-package-json-app/packages/nested/sub-pkg-esm/index.mjs')).default; + assert.strictEqual(esmParentPackageJson, nestedPackageJson); + + assert.strictEqual(findPackageJSON('pkg', baseUrl), pkgRootPackageJson); + assert.strictEqual(findPackageJSON('pkg2', baseUrl), pkg2RootPackageJson); + + const pkgResolved = require.resolve('pkg', { paths: ['/find-package-json-app'] }); + assert.strictEqual(findPackageJSON(pkgResolved), subfolderPackageJson); + assert.strictEqual(findPackageJSON(pathToFileURL(pkgResolved).href), subfolderPackageJson); + assert.strictEqual(findPackageJSON(pathToFileURL(pkgResolved)), subfolderPackageJson); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testVmMainContextDefaultLoader = async () => { + try { + const vm = await import('node:vm'); + const { default: assert } = await import('node:assert'); + const { pathToFileURL } = await import('node:url'); + + fs.mkdirSync('/vm-default-loader-app/subdir', { recursive: true }); + fs.mkdirSync('/vm-default-loader-app/other', { recursive: true }); + fs.mkdirSync('/vm-default-loader-app/space dir', { recursive: true }); + fs.writeFileSync('/vm-default-loader-app/subdir/message.mjs', [ + 'export const value = "from-subdir";', + 'export default { value };', + ].join('\n')); + fs.writeFileSync('/vm-default-loader-app/other/message.mjs', [ + 'export const value = "from-other";', + 'export default { value };', + ].join('\n')); + fs.writeFileSync('/vm-default-loader-app/space dir/message.mjs', [ + 'export const value = "from-space";', + 'export default { value };', + ].join('\n')); + fs.writeFileSync('/vm-default-loader-app/message.mjs', [ + 'export const value = "from-cwd";', + 'export default { value };', + ].join('\n')); + + assert.strictEqual(typeof vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, 'symbol'); + assert.strictEqual(typeof vm.Module, 'function'); + assert.strictEqual(typeof vm.SourceTextModule, 'function'); + assert.strictEqual(typeof vm.SyntheticModule, 'function'); + assert.throws(() => new vm.Module(), { + name: 'TypeError', + message: 'Module is not a constructor', + }); + assert.throws(() => new vm.SourceTextModule(null), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.strictEqual(new vm.SourceTextModule('') instanceof vm.Module, true); + const util = await import('node:util'); + const inspectContext = vm.createContext({ foo: 'bar' }); + const inspectSourceTextModule = new vm.SourceTextModule('1', { context: inspectContext }); + assert.strictEqual(util.inspect(inspectSourceTextModule), [ + 'SourceTextModule {', + " status: 'unlinked',", + " identifier: 'vm:module(0)',", + " context: { foo: 'bar' }", + '}', + ].join('\n')); + assert.strictEqual(util.inspect(inspectSourceTextModule, { depth: -1 }), '[SourceTextModule]'); + const inspectSyntheticModule = new vm.SyntheticModule([], () => {}, { context: inspectContext }); + assert.strictEqual(util.inspect(inspectSyntheticModule), [ + 'SyntheticModule {', + " status: 'unlinked',", + " identifier: 'vm:module(0)',", + " context: { foo: 'bar' }", + '}', + ].join('\n')); + assert.strictEqual(util.inspect(inspectSyntheticModule, { depth: -1 }), '[SyntheticModule]'); + for (const invalidThis of [null, { __proto__: null }, vm.SourceTextModule.prototype]) { + assert.throws(() => inspectSourceTextModule[util.inspect.custom].call(invalidThis), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: /The "this" argument must be an instance of Module/, + }); + } + assert.throws(() => new vm.SyntheticModule(undefined, () => {}, {}), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "exportNames" argument must be an Array of unique strings. Received undefined', + }); + assert.throws(() => new vm.SyntheticModule(['x', 'x'], () => {}, {}), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'exportNames.x' is duplicated. Received 'x'", + }); + assert.throws(() => new vm.SyntheticModule([], undefined, {}), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => new vm.SyntheticModule([], () => {}, null), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + for (const invalidInitializeImportMeta of [null, {}, 0, Symbol.iterator, [], 'string', false]) { + assert.throws(() => new vm.SourceTextModule('', { + initializeImportMeta: invalidInitializeImportMeta, + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + } + + const importMetaModule = new vm.SourceTextModule('globalThis.vmImportMetaResult = import.meta;', { + initializeImportMeta(meta, module) { + assert.strictEqual(module, importMetaModule); + assert.strictEqual(this, undefined); + meta.prop = 42; + }, + }); + await importMetaModule.link(() => {}); + await importMetaModule.evaluate(); + assert.strictEqual(typeof globalThis.vmImportMetaResult, 'object'); + assert.strictEqual(Object.getPrototypeOf(globalThis.vmImportMetaResult), null); + assert.deepStrictEqual(Reflect.ownKeys(globalThis.vmImportMetaResult), ['prop']); + assert.strictEqual(globalThis.vmImportMetaResult.prop, 42); + delete globalThis.vmImportMetaResult; + + const importMetaSloppyThisModule = new vm.SourceTextModule('globalThis.vmImportMetaSloppyThis = import.meta.value;', { + initializeImportMeta: Function('meta', 'module', 'meta.value = this === globalThis;'), + }); + await importMetaSloppyThisModule.link(() => {}); + await importMetaSloppyThisModule.evaluate(); + assert.strictEqual(globalThis.vmImportMetaSloppyThis, true); + delete globalThis.vmImportMetaSloppyThis; + + const importMetaTemplateModule = new vm.SourceTextModule('globalThis.vmImportMetaTemplate = `${import.meta.prop}:${`${import.meta.prop}`}`;', { + initializeImportMeta(meta) { + meta.prop = 'template'; + }, + }); + await importMetaTemplateModule.link(() => {}); + await importMetaTemplateModule.evaluate(); + assert.strictEqual(globalThis.vmImportMetaTemplate, 'template:template'); + delete globalThis.vmImportMetaTemplate; + + const importMetaFalsePositiveModule = new vm.SourceTextModule(` + globalThis.vmImportMetaFalsePositives = [ + "import.meta", + /import.meta/.source, + Array.from(/import.meta/.source).join(""), + ({ import: { meta: 7 } }). /* comment */ import.meta, + typeof importMeta, + ]; + // import.meta + /* import.meta */ + for (const ch of /import.meta/.source) {} + for (const ch of /* comment */ /import.meta/.source) {} + `, { + initializeImportMeta() { + throw new Error('unreachable'); + }, + }); + await importMetaFalsePositiveModule.link(() => {}); + await importMetaFalsePositiveModule.evaluate(); + assert.deepStrictEqual(globalThis.vmImportMetaFalsePositives, ['import.meta', 'import.meta', 'import.meta', 7, 'undefined']); + delete globalThis.vmImportMetaFalsePositives; + + const sourceTextParserFalsePositiveModule = new vm.SourceTextModule(` + const text = "import { x } from 'dep'; export const wrong = 1;"; + const regex = /import "dep"; export const wrong = 1;/; + // import "dep"; export const wrong = 1; + /* import "dep"; export const wrong = 1; */ + globalThis.vmSourceTextParserFalsePositives = [text.includes("export const wrong"), regex.test('import "dep"; export const wrong = 1;')]; + `); + assert.deepStrictEqual(sourceTextParserFalsePositiveModule.dependencySpecifiers, []); + await sourceTextParserFalsePositiveModule.link(() => { + throw new Error('unreachable'); + }); + await sourceTextParserFalsePositiveModule.evaluate(); + assert.deepStrictEqual(globalThis.vmSourceTextParserFalsePositives, [true, true]); + delete globalThis.vmSourceTextParserFalsePositives; + + const sourceTextExportRegexModule = new vm.SourceTextModule('export const r = /import "dep"; export const wrong = 1;/;'); + assert.deepStrictEqual(sourceTextExportRegexModule.dependencySpecifiers, []); + await sourceTextExportRegexModule.link(() => { + throw new Error('unreachable'); + }); + await sourceTextExportRegexModule.evaluate(); + assert.strictEqual(sourceTextExportRegexModule.namespace.r.test('import "dep"; export const wrong = 1;'), true); + assert.strictEqual(Object.prototype.hasOwnProperty.call(sourceTextExportRegexModule.namespace, 'wrong'), false); + + const sourceTextPropertyExportModule = new vm.SourceTextModule(` + const o = { export: 1 }; + class C { export() { return 2; } } + globalThis.vmSourceTextPropertyExport = [o.export, new C().export()]; + `); + await sourceTextPropertyExportModule.link(() => {}); + await sourceTextPropertyExportModule.evaluate(); + assert.deepStrictEqual(globalThis.vmSourceTextPropertyExport, [1, 2]); + delete globalThis.vmSourceTextPropertyExport; + + const sourceTextMultilineExportModule = new vm.SourceTextModule(` + export const a = 1, + b = 2; + `); + await sourceTextMultilineExportModule.link(() => {}); + await sourceTextMultilineExportModule.evaluate(); + assert.strictEqual(sourceTextMultilineExportModule.namespace.a, 1); + assert.strictEqual(sourceTextMultilineExportModule.namespace.b, 2); + + const sourceTextNestedTemplateModule = new vm.SourceTextModule('const s = `${`import "dep"; export const wrong = 1;`}`; globalThis.vmSourceTextNestedTemplate = s;'); + assert.deepStrictEqual(sourceTextNestedTemplateModule.dependencySpecifiers, []); + await sourceTextNestedTemplateModule.link(() => { + throw new Error('unreachable'); + }); + await sourceTextNestedTemplateModule.evaluate(); + assert.strictEqual(globalThis.vmSourceTextNestedTemplate, 'import "dep"; export const wrong = 1;'); + delete globalThis.vmSourceTextNestedTemplate; + + const syntheticModule = new vm.SyntheticModule(['x'], function() { + syntheticEvaluateCalled = true; + this.setExport('x', 1); + }); + var syntheticEvaluateCalled = false; + assert.strictEqual(syntheticModule instanceof vm.Module, true); + assert.strictEqual(syntheticModule instanceof vm.SourceTextModule, false); + assert.strictEqual(typeof syntheticModule.setExport, 'function'); + await syntheticModule.link(() => {}); + assert.strictEqual(syntheticModule.namespace.x, undefined); + await syntheticModule.evaluate(); + assert.strictEqual(syntheticEvaluateCalled, true); + assert.strictEqual(syntheticModule.namespace.x, 1); + assert.strictEqual(Object.getOwnPropertyDescriptor(syntheticModule.namespace, 'x').configurable, false); + assert.throws(() => syntheticModule.setExport(1, 2), TypeError); + assert.throws(() => syntheticModule.setExport('missing', 2), ReferenceError); + + const prelinkedSynthetic = new vm.SyntheticModule(['x'], function() {}); + assert.throws(() => prelinkedSynthetic.setExport('x', 1), { + code: 'ERR_VM_MODULE_STATUS', + }); + + const rejectedSynthetic = new vm.SyntheticModule([], function() { + const promise = Promise.reject(new Error('ignored')); + promise.catch(() => {}); + return promise; + }); + await rejectedSynthetic.link(() => {}); + assert.strictEqual(await rejectedSynthetic.evaluate(), undefined); + + const liveSynthetic = new vm.SyntheticModule(['x'], function() { + liveSynthetic.setExport('x', 1); + }); + const liveSyntheticUser = new vm.SourceTextModule('import { x } from "synthetic"; export const getX = () => x;'); + await liveSyntheticUser.link((specifier) => { + assert.strictEqual(specifier, 'synthetic'); + return liveSynthetic; + }); + await liveSyntheticUser.evaluate(); + assert.strictEqual(liveSyntheticUser.namespace.getX(), 1); + liveSynthetic.setExport('x', 42); + assert.strictEqual(liveSyntheticUser.namespace.getX(), 42); + + const dedupedSyntheticUser = new vm.SourceTextModule('import { x } from "synthetic"; import "synthetic"; export const getX = () => x;'); + assert.deepStrictEqual(dedupedSyntheticUser.dependencySpecifiers, ['synthetic']); + let dedupedSyntheticLinkCalls = 0; + await dedupedSyntheticUser.link((specifier) => { + dedupedSyntheticLinkCalls++; + assert.strictEqual(specifier, 'synthetic'); + return liveSynthetic; + }); + assert.strictEqual(dedupedSyntheticLinkCalls, 1); + await dedupedSyntheticUser.evaluate(); + assert.strictEqual(dedupedSyntheticUser.namespace.getX(), 42); + + const aliasSyntheticUser = new vm.SourceTextModule('import { x as y } from "synthetic"; export const getY = () => y;'); + await aliasSyntheticUser.link(() => liveSynthetic); + await aliasSyntheticUser.evaluate(); + assert.strictEqual(aliasSyntheticUser.namespace.getY(), 42); + liveSynthetic.setExport('x', 7); + assert.strictEqual(aliasSyntheticUser.namespace.getY(), 7); + + const scopedSyntheticUser = new vm.SourceTextModule(` + import { x } from "synthetic"; + export const shadow = (x) => x; + export const property = () => ({ x: 1 }).x; + export const shorthand = () => ({ x }).x; + export const regex = () => /x/.test("x"); + `); + await scopedSyntheticUser.link(() => liveSynthetic); + await scopedSyntheticUser.evaluate(); + assert.strictEqual(scopedSyntheticUser.namespace.shadow(3), 3); + assert.strictEqual(scopedSyntheticUser.namespace.property(), 1); + assert.strictEqual(scopedSyntheticUser.namespace.shorthand(), 7); + assert.strictEqual(scopedSyntheticUser.namespace.regex(), true); + liveSynthetic.setExport('x', 8); + assert.strictEqual(scopedSyntheticUser.namespace.shorthand(), 8); + + const unicodeSyntheticUser = new vm.SourceTextModule('import { x as π } from "synthetic"; export const get = () => π;'); + await unicodeSyntheticUser.link(() => liveSynthetic); + await unicodeSyntheticUser.evaluate(); + assert.strictEqual(unicodeSyntheticUser.namespace.get(), 8); + + const internalNameSyntheticUser = new vm.SourceTextModule('import { x } from "synthetic"; export const leak = __wasm_rquickjs_vm_imports;'); + await internalNameSyntheticUser.link(() => liveSynthetic); + await assert.rejects(internalNameSyntheticUser.evaluate(), ReferenceError); + + const missingImportHelperCount = () => Object.getOwnPropertyNames(globalThis) + .filter((name) => name.indexOf('__wasm_rquickjs_vm_missing_dynamic_import__') !== -1) + .length; + const missingImportFlagHelperCount = () => Object.getOwnPropertyNames(globalThis) + .filter((name) => name.indexOf('__wasm_rquickjs_vm_missing_dynamic_import_flag__') !== -1) + .length; + const missingImportHelperCountBefore = missingImportHelperCount(); + const missingImportFlagHelperCountBefore = missingImportFlagHelperCount(); + assert.strictEqual(new vm.Script('1 + 1').runInThisContext(), 2); + assert.strictEqual(new vm.Script('1 + 1').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('1 + 1\n// sourceMappingURL=wrong.map').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('1 + 1\n//#sourceMappingURL=nospace.map').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('1 + 1\n//# sourceMappingURL=multi-space.map').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('const s = "//# sourceMappingURL=string.map";').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('const s = `//# sourceMappingURL=template.map`;').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('/[//# sourceMappingURL=regex.map]/;').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('/*\n//# sourceMappingURL=inside-block.map\n*/').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('1 + 1\n/*# sourceMappingURL=block.map */').sourceMapURL, undefined); + assert.strictEqual(new vm.Script('1 + 1\n//# sourceMappingURL=script.map').sourceMapURL, 'script.map'); + assert.strictEqual(new vm.Script('1;\n//# sourceMappingURL=semi.map').sourceMapURL, 'semi.map'); + assert.strictEqual(new vm.Script('1 + 1\n//#\tsourceMappingURL=tab.map').sourceMapURL, 'tab.map'); + assert.strictEqual(new vm.Script('1 + 1\n//#\vsourceMappingURL=vertical-tab.map').sourceMapURL, 'vertical-tab.map'); + assert.strictEqual(new vm.Script('1 + 1\n//#\fsourceMappingURL=form-feed.map').sourceMapURL, 'form-feed.map'); + assert.strictEqual(new vm.Script('1 + 1\n//#\u00a0sourceMappingURL=nbsp.map').sourceMapURL, 'nbsp.map'); + assert.strictEqual(new vm.Script('const s = `${1 //# sourceMappingURL=expr.map\n}`;').sourceMapURL, 'expr.map'); + const receiverScript = new vm.Script(''); + assert.throws(() => receiverScript.runInNewContext.call('hello'), { + name: 'TypeError', + message: 'this.runInContext is not a function', + }); + assert.throws(() => receiverScript.runInNewContext.call(null), { + name: 'TypeError', + message: "Cannot read properties of null (reading 'runInContext')", + }); + assert.throws(() => receiverScript.runInNewContext.call(undefined), { + name: 'TypeError', + message: "Cannot read properties of undefined (reading 'runInContext')", + }); + const overriddenReceiverScript = new vm.Script('41'); + overriddenReceiverScript.runInContext = function(context, options) { + assert.strictEqual(vm.isContext(context), true); + assert.deepStrictEqual(options, { displayErrors: false }); + return 99; + }; + assert.strictEqual(overriddenReceiverScript.runInNewContext({}, { displayErrors: false }), 99); + for (const invalidSandbox of [null, 1, 'x']) { + assert.throws(() => receiverScript.runInNewContext(invalidSandbox), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + } + const genericReceiverSandbox = {}; + assert.strictEqual(vm.Script.prototype.runInNewContext.call({ + runInContext(context, options) { + assert.strictEqual(context, genericReceiverSandbox); + assert.strictEqual(vm.isContext(context), true); + assert.deepStrictEqual(options, { displayErrors: false }); + return 42; + }, + }, genericReceiverSandbox, { displayErrors: false }), 42); + let genericReceiverWasCalled = false; + assert.strictEqual(vm.Script.prototype.runInNewContext.call({ + runInContext(context) { + genericReceiverWasCalled = true; + assert.strictEqual(vm.isContext(context), true); + return 43; + }, + }), 43); + assert.strictEqual(genericReceiverWasCalled, true); + for (const invalidSandbox of [null, 1, 'x']) { + genericReceiverWasCalled = false; + assert.throws(() => vm.Script.prototype.runInNewContext.call({ + runInContext() { + genericReceiverWasCalled = true; + }, + }, invalidSandbox), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.strictEqual(genericReceiverWasCalled, false); + } + for (const invalidOptions of [ + { contextName: null }, + { contextOrigin: null }, + { contextCodeGeneration: null }, + { contextCodeGeneration: 1 }, + { contextCodeGeneration: { strings: null } }, + { contextCodeGeneration: { strings: 1 } }, + { contextCodeGeneration: { wasm: null } }, + { contextCodeGeneration: { wasm: 1 } }, + { microtaskMode: 'bad' }, + ]) { + genericReceiverWasCalled = false; + const expectedCode = invalidOptions.microtaskMode === 'bad' ? 'ERR_INVALID_ARG_VALUE' : 'ERR_INVALID_ARG_TYPE'; + assert.throws(() => vm.Script.prototype.runInNewContext.call({ + runInContext() { + genericReceiverWasCalled = true; + }, + }, {}, invalidOptions), { + name: 'TypeError', + code: expectedCode, + }); + assert.strictEqual(genericReceiverWasCalled, false); + assert.throws(() => receiverScript.runInNewContext({}, invalidOptions), { + name: 'TypeError', + code: expectedCode, + }); + } + assert.throws(() => receiverScript.runInNewContext(null, { microtaskMode: 'bad' }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => receiverScript.runInNewContext(null, { contextCodeGeneration: 1 }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => receiverScript.runInContext.call('hello', vm.createContext({})), { + name: 'TypeError', + message: 'Illegal invocation', + }); + assert.throws(() => receiverScript.runInThisContext.call('hello'), { + name: 'TypeError', + message: 'Illegal invocation', + }); + assert.throws(() => receiverScript.createCachedData.call('hello'), { + name: 'TypeError', + message: 'Illegal invocation', + }); + assert.deepStrictEqual(Object.getOwnPropertySymbols(receiverScript), []); + assert.throws(() => vm.Script.prototype.runInThisContext.call({ _code: '40 + 2' }), { + name: 'TypeError', + message: 'Illegal invocation', + }); + const sandboxWriteBack = { foo: 0, baz: 3 }; + globalThis.vmWriteBackOuterFoo = 2; + assert.strictEqual(new vm.Script('foo = 1; bar = 2; if (baz !== 3) throw new Error("bad baz");').runInNewContext(sandboxWriteBack), undefined); + assert.strictEqual(sandboxWriteBack.foo, 1); + assert.strictEqual(sandboxWriteBack.bar, 2); + assert.strictEqual(globalThis.vmWriteBackOuterFoo, 2); + delete globalThis.vmWriteBackOuterFoo; + const directWriteBack = { foo: 0 }; + assert.strictEqual(vm.runInNewContext('foo = 3; bar = 4; "ok"', directWriteBack), 'ok'); + assert.deepStrictEqual(directWriteBack, { foo: 3, bar: 4 }); + const throwWriteBack = { foo: 0 }; + assert.throws(() => vm.runInNewContext('foo = 5; throw new Error("boom")', throwWriteBack), { + name: 'Error', + message: 'boom', + }); + assert.strictEqual(throwWriteBack.foo, 5); + const deleteWriteBack = { foo: 0, bar: 1 }; + assert.strictEqual(vm.runInNewContext('foo = 6; delete globalThis.foo; bar = 2', deleteWriteBack), 2); + assert.deepStrictEqual(deleteWriteBack, { bar: 2 }); + const poisonedWriteBack = { foo: 0 }; + assert.strictEqual(vm.runInNewContext('Object.keys = () => []; foo = 7; bar = 8', poisonedWriteBack), 8); + assert.deepStrictEqual(poisonedWriteBack, { foo: 7, bar: 8 }); + const accessorWriteBack = {}; + assert.strictEqual(vm.runInNewContext('Object.defineProperty(globalThis, "boom", { enumerable: true, configurable: true, get() { throw new Error("getter"); } }); "ok"', accessorWriteBack), 'ok'); + const boomDescriptor = Object.getOwnPropertyDescriptor(accessorWriteBack, 'boom'); + assert.strictEqual(boomDescriptor.enumerable, true); + assert.strictEqual(typeof boomDescriptor.get, 'function'); + assert.throws(() => accessorWriteBack.boom, { + name: 'Error', + message: 'getter', + }); + const nonEnumerableWriteBack = { x: 1 }; + assert.strictEqual(vm.runInNewContext('delete globalThis.x; Object.defineProperty(globalThis, "x", { value: 2, configurable: true }); "ok"', nonEnumerableWriteBack), 'ok'); + assert.deepStrictEqual(Object.getOwnPropertyDescriptor(nonEnumerableWriteBack, 'x'), { + value: 2, + writable: false, + enumerable: false, + configurable: true, + }); + const callPoisonWriteBack = { foo: 0 }; + assert.strictEqual(vm.runInNewContext('Function.prototype.call = () => { throw new Error("poison"); }; foo = 9; bar = 10', callPoisonWriteBack), 10); + assert.strictEqual(callPoisonWriteBack.foo, 9); + assert.strictEqual(callPoisonWriteBack.bar, 10); + const originalErrorWriteBack = {}; + Object.defineProperty(originalErrorWriteBack, 'foo', { + value: 0, + writable: false, + enumerable: true, + configurable: false, + }); + assert.throws(() => vm.runInNewContext('foo = 1; throw new Error("boom")', originalErrorWriteBack), { + name: 'Error', + message: 'boom', + }); + assert.strictEqual(originalErrorWriteBack.foo, 0); + const readonlyWriteBack = {}; + Object.defineProperty(readonlyWriteBack, 'foo', { + value: 0, + writable: false, + enumerable: true, + configurable: false, + }); + assert.strictEqual(vm.runInNewContext('foo = 1; bar = 2', readonlyWriteBack), 2); + assert.strictEqual(readonlyWriteBack.foo, 0); + assert.strictEqual(readonlyWriteBack.bar, 2); + assert.strictEqual(vm.compileFunction('return 1')(), 1); + assert.strictEqual(vm.compileFunction('console.log("Hello, World!")').toString(), 'function () {\nconsole.log("Hello, World!")\n}'); + assert.throws(() => vm.compileFunction('});\n\n(function() {\nthrow new Error("unreachable");\n})();\n\n(function() {'), { + name: 'SyntaxError', + message: "Unexpected token '}'", + }); + assert.throws(() => vm.compileFunction('', undefined, { filename: null }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options.filename" property must be of type string. Received null', + }); + assert.throws(() => vm.compileFunction('', undefined, { columnOffset: null }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options.columnOffset" property must be of type number. Received null', + }); + assert.strictEqual(vm.compileFunction('return a;', undefined, { contextExtensions: [{ a: 5 }] })(), 5); + let compileFunctionSetterValue; + const compileFunctionExtension = {}; + Object.defineProperty(compileFunctionExtension, 'x', { + enumerable: true, + configurable: true, + get() { return 42; }, + set(value) { compileFunctionSetterValue = value; }, + }); + assert.strictEqual(vm.compileFunction('return x', [], { contextExtensions: [compileFunctionExtension] })(), 42); + assert.strictEqual(compileFunctionSetterValue, undefined); + assert.strictEqual(vm.compileFunction('return varInContext', [], { + parsingContext: vm.createContext({ varInContext: 'abc' }), + })(), 'abc'); + const cachedFunction = vm.compileFunction('return 3', [], { produceCachedData: true }); + assert.strictEqual(cachedFunction.cachedDataProduced, true); + assert.ok(cachedFunction.cachedData.length > 0); + assert.strictEqual(vm.compileFunction('return 3', [], { cachedData: cachedFunction.cachedData }).cachedDataRejected, false); + assert.strictEqual(vm.compileFunction('return 4', [], { cachedData: cachedFunction.cachedData }).cachedDataRejected, true); + const oldStackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 1; + try { + assert.throws(() => vm.compileFunction('throw new Error("Sample Error")')(), { + message: 'Sample Error', + stack: 'Error: Sample Error\n at :1:7', + }); + assert.throws(() => vm.compileFunction('throw new Error("Sample Error")', [], { lineOffset: 3 })(), { + message: 'Sample Error', + stack: 'Error: Sample Error\n at :4:7', + }); + assert.throws(() => vm.compileFunction('throw new Error("Sample Error")', [], { columnOffset: 3 })(), { + message: 'Sample Error', + stack: 'Error: Sample Error\n at :1:10', + }); + } finally { + Error.stackTraceLimit = oldStackTraceLimit; + } + const runFilenameContext = vm.createContext({}); + function hasVmFilenameStack(err) { + return typeof err.stack === 'string' && err.stack.startsWith('runtime-boom.js:1'); + } + assert.throws(() => vm.runInThisContext('throw new Error("boom")', 'runtime-boom.js'), hasVmFilenameStack); + assert.throws(() => vm.runInNewContext('throw new Error("boom")', {}, 'runtime-boom.js'), hasVmFilenameStack); + assert.strictEqual(vm.runInNewContext('1 + 1', undefined), 2); + assert.throws(() => vm.runInNewContext('', null, 'runtime-null-sandbox.js'), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + for (const invalidSandbox of [0, '', true, Symbol('sandbox'), function invalidSandbox() {}]) { + assert.throws(() => vm.runInNewContext('1 + 1', invalidSandbox), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + } + assert.throws(() => vm.runInNewContext('', {}, { filename: 1 }), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); + assert.throws(() => vm.runInNewContext('', {}, { filename: 1, get lineOffset() { throw new Error('wrong order'); } }), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); + assert.throws(() => vm.runInContext('', runFilenameContext, { lineOffset: 1.5 }), { code: 'ERR_OUT_OF_RANGE', name: 'RangeError' }); + assert.throws(() => vm.runInThisContext('', { columnOffset: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); + assert.throws(() => vm.runInContext('throw new Error("boom")', runFilenameContext, 'runtime-boom.js'), hasVmFilenameStack); + assert.throws(() => vm.runInContext('', {}), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /contextifiedObject.*vm\.Context/, + }); + assert.throws(() => new vm.Script('').runInContext([]), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /contextifiedObject.*vm\.Context/, + }); + function assertEmptyVmRunDoesNotTouchProxy(run) { + const emptyRunProxyTraps = { ownKeys: 0, getOwnPropertyDescriptor: 0, get: 0 }; + const descriptorThrowingProxy = new Proxy({ foo: 'bar' }, { + ownKeys() { + emptyRunProxyTraps.ownKeys++; + throw new Error('ownKeys trap should not run'); + }, + getOwnPropertyDescriptor() { + emptyRunProxyTraps.getOwnPropertyDescriptor++; + throw new Error('descriptor trap should not run'); + }, + get() { + emptyRunProxyTraps.get++; + throw new Error('get trap should not run'); + }, + }); + assert.strictEqual(run(descriptorThrowingProxy), undefined); + assert.deepStrictEqual(emptyRunProxyTraps, { ownKeys: 0, getOwnPropertyDescriptor: 0, get: 0 }); + } + assertEmptyVmRunDoesNotTouchProxy((proxy) => vm.runInContext('', vm.createContext(proxy))); + assertEmptyVmRunDoesNotTouchProxy((proxy) => vm.runInContext(' \n/* comment */\n// line comment', vm.createContext(proxy))); + assertEmptyVmRunDoesNotTouchProxy((proxy) => vm.runInContext('\uFEFF\u00A0\u2028', vm.createContext(proxy))); + assertEmptyVmRunDoesNotTouchProxy((proxy) => vm.runInContext('#!/usr/bin/env node', vm.createContext(proxy))); + assertEmptyVmRunDoesNotTouchProxy((proxy) => vm.runInContext(' html close comment', vm.createContext(proxy))); + assertEmptyVmRunDoesNotTouchProxy((proxy) => vm.runInNewContext('', proxy)); + assertEmptyVmRunDoesNotTouchProxy((proxy) => new vm.Script('/* script comment */').runInContext(vm.createContext(proxy))); + const vmRegex = vm.runInNewContext('/hello/'); + assert.throws(() => { throw 'hello world'; }, vmRegex); + assert.throws(() => assert.match('hello', { [Symbol.toStringTag]: 'RegExp', test() { return true; } }), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + assert.throws(() => assert.match('hello', Object.create(/hello/)), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + assert.throws(() => assert.match('hello', new Proxy(/hello/, {})), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + const poisonedRegex = /hello/; + poisonedRegex.test = () => false; + assert.match('hello', poisonedRegex); + assert.throws(() => { throw 'hello world'; }, poisonedRegex); + const originalRegExpSource = Object.getOwnPropertyDescriptor(RegExp.prototype, 'source'); + try { + Object.defineProperty(RegExp.prototype, 'source', { + configurable: true, + get() { return ''; }, + }); + assert.throws(() => assert.match('hello', { test() { return true; } }), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + } finally { + Object.defineProperty(RegExp.prototype, 'source', originalRegExpSource); + } + const sloppyMainResult = vm.runInThisContext([ + 'var __wasm_rquickjs_sloppy_main_value = "main";', + '[delete __wasm_rquickjs_sloppy_main_value, __wasm_rquickjs_sloppy_main_value];', + ].join('\n')); + assert.deepStrictEqual(sloppyMainResult, [false, 'main']); + assert.deepStrictEqual(Object.getOwnPropertyDescriptor(globalThis, '__wasm_rquickjs_sloppy_main_value'), { + value: 'main', + writable: true, + enumerable: true, + configurable: false, + }); + const sloppyScriptResult = new vm.Script([ + 'var __wasm_rquickjs_sloppy_script_value = "script";', + '[delete __wasm_rquickjs_sloppy_script_value, __wasm_rquickjs_sloppy_script_value];', + ].join('\n')).runInThisContext(); + assert.deepStrictEqual(sloppyScriptResult, [false, 'script']); + const sloppyConstructorFilenameResult = new vm.Script([ + 'var __wasm_rquickjs_sloppy_constructor_filename_value = "constructor";', + '[delete __wasm_rquickjs_sloppy_constructor_filename_value, __wasm_rquickjs_sloppy_constructor_filename_value];', + ].join('\n'), 'runtime-script-sloppy.js').runInThisContext(); + assert.deepStrictEqual(sloppyConstructorFilenameResult, [false, 'constructor']); + assert.throws(() => new vm.Script('throw new Error("script filename")', { + filename: 'runtime-script-sloppy-stack.js', + }).runInThisContext(), (err) => { + return typeof err.stack === 'string' && err.stack.startsWith('runtime-script-sloppy-stack.js:1'); + }); + assert.throws(() => new vm.Script('throw new Error("empty script filename")', { + filename: '', + }).runInThisContext(), (err) => { + return typeof err.stack === 'string' && err.stack.startsWith(':1'); + }); + globalThis.__wasm_rquickjs_sloppy_main_value = undefined; + globalThis.__wasm_rquickjs_sloppy_script_value = undefined; + globalThis.__wasm_rquickjs_sloppy_constructor_filename_value = undefined; + assert.throws(() => vm.createContext('bad'), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => vm.createContext(function badSandbox() {}), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script('void 0', 42), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script('void 0', { lineOffset: null }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script('void 0', { lineOffset: 0.5 }), { name: 'RangeError', code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => new vm.Script('void 0', { columnOffset: 2 ** 32 }), { name: 'RangeError', code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => new vm.Script('void 0', { filename: 123 }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script('void 0', { produceCachedData: 1 }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script('void 0', { cachedData: {} }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script('void 0', { importModuleDynamically: 123 }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => new vm.Script({ toString() { throw new Error('code toString'); } }, 42), { + name: 'Error', + message: 'code toString', + }); + assert.doesNotThrow(() => new vm.Script('void 0', 'runtime-script.js')); + const runOptionScript = new vm.Script('void 0'); + const runOptionContext = vm.createContext({}); + assert.throws(() => runOptionScript.runInThisContext(null), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => runOptionScript.runInContext(runOptionContext, { timeout: 0 }), { name: 'RangeError', code: 'ERR_OUT_OF_RANGE' }); + assert.doesNotThrow(() => runOptionScript.runInThisContext({ timeout: 4294967295 })); + assert.throws(() => runOptionScript.runInThisContext({ timeout: 4294967296 }), { name: 'RangeError', code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => runOptionScript.runInContext({}, { get timeout() { throw new Error('bad order'); } }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: /contextifiedObject.*vm\.Context/, + }); + assert.throws(() => runOptionScript.runInNewContext({}, { displayErrors: null }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => runOptionScript.runInNewContext({}, { breakOnSigint: 1 }), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + assert.strictEqual(vm.isContext({}), false); + assert.strictEqual(vm.isContext([]), false); + for (const invalidContext of ['string', null, undefined, 8.9, Symbol('sym'), true, function invalidContext() {}]) { + assert.throws(() => vm.isContext(invalidContext), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' }); + } + const contextSandbox = {}; + assert.strictEqual(vm.isContext(vm.createContext(contextSandbox)), true); + assert.strictEqual(vm.isContext(contextSandbox), true); + assert.strictEqual(vm.isContext(Object.create(contextSandbox)), false); + assert.deepStrictEqual(Reflect.ownKeys(contextSandbox), []); + const contextSymbolA = Symbol('context-a'); + const contextSymbolB = Symbol('context-b'); + const keyedContextSandbox = { + visible: true, + [contextSymbolA]: true, + }; + Object.defineProperty(keyedContextSandbox, 'hidden', { value: true }); + Object.defineProperty(keyedContextSandbox, contextSymbolB, { value: true }); + vm.createContext(keyedContextSandbox); + assert.deepStrictEqual(Reflect.ownKeys(keyedContextSandbox), ['visible', 'hidden', contextSymbolA, contextSymbolB]); + assert.deepStrictEqual(Object.getOwnPropertyNames(keyedContextSandbox), ['visible', 'hidden']); + assert.deepStrictEqual(Object.getOwnPropertySymbols(keyedContextSandbox), [contextSymbolA, contextSymbolB]); + const nativeContextNames = vm.runInNewContext('Object.getOwnPropertyNames(this)'); + const keyedContextNames = vm.runInContext('Object.getOwnPropertyNames(this)', keyedContextSandbox); + const keyedContextOwnNames = keyedContextNames.filter((name) => !nativeContextNames.includes(name)); + assert.strictEqual(keyedContextOwnNames.length, 2); + assert.strictEqual(keyedContextOwnNames[0], 'visible'); + assert.strictEqual(keyedContextOwnNames[1], 'hidden'); + const nativeContextSymbols = vm.runInNewContext('Object.getOwnPropertySymbols(this)'); + const keyedContextSymbols = vm.runInContext('Object.getOwnPropertySymbols(this)', keyedContextSandbox); + const keyedContextOwnSymbols = keyedContextSymbols.filter((symbol) => !nativeContextSymbols.includes(symbol)); + assert.strictEqual(keyedContextOwnSymbols.length, 2); + assert.strictEqual(keyedContextOwnSymbols[0], contextSymbolA); + assert.strictEqual(keyedContextOwnSymbols[1], contextSymbolB); + assert.strictEqual(vm.runInContext('const symbols = Object.getOwnPropertySymbols(this); const symbol = symbols.find((value) => String(value) === "Symbol(context-a)"); this[symbol] = "updated"; this[symbol]', keyedContextSandbox), 'updated'); + assert.strictEqual(keyedContextSandbox[contextSymbolA], 'updated'); + assert.strictEqual(keyedContextSandbox[contextSymbolB], true); + const newSymbolSandbox = vm.createContext({}); + const assignedSymbol = vm.runInContext('const symbol = Symbol("assigned"); this[symbol] = 1; symbol', newSymbolSandbox); + assert.deepStrictEqual(Object.getOwnPropertySymbols(newSymbolSandbox), []); + assert.strictEqual(newSymbolSandbox[assignedSymbol], undefined); + const hiddenDescriptor = vm.runInContext('Object.getOwnPropertyDescriptor(this, "hidden")', keyedContextSandbox); + assert.strictEqual(hiddenDescriptor.value, true); + assert.strictEqual(hiddenDescriptor.writable, false); + assert.strictEqual(hiddenDescriptor.enumerable, false); + assert.strictEqual(hiddenDescriptor.configurable, false); + assert.strictEqual(vm.runInNewContext('typeof performance'), 'undefined'); + assert.strictEqual(vm.runInContext('Object.keys(this).join(",")', vm.createContext({ key: 'value', 1: 'one' })), '1,key'); + const hiddenVmGlobalNames = ['DOMException', 'Float16Array', 'InternalError', 'performance', 'queueMicrotask']; + assert.strictEqual(vm.runInNewContext('["DOMException", "Float16Array", "InternalError", "performance", "queueMicrotask"].filter((name) => Object.hasOwn(this, name)).join(",")'), ''); + for (const hiddenName of hiddenVmGlobalNames) { + assert.strictEqual(vm.runInContext(hiddenName + '.marker', vm.createContext({ + [hiddenName]: { marker: hiddenName }, + })), hiddenName); + } + const baselineGlobalSandbox = {}; + assert.strictEqual(vm.runInNewContext('Object.defineProperty(this, "encodeURI", { value: 42, configurable: true }); "ok"', baselineGlobalSandbox), 'ok'); + assert.strictEqual(baselineGlobalSandbox.encodeURI, 42); + const baselineGlobalDescriptor = Object.getOwnPropertyDescriptor(baselineGlobalSandbox, 'encodeURI'); + assert.strictEqual(baselineGlobalDescriptor.value, 42); + assert.strictEqual(baselineGlobalDescriptor.configurable, true); + const untouchedGlobalSandbox = {}; + assert.strictEqual(vm.runInNewContext('1 + 1', untouchedGlobalSandbox), 2); + assert.strictEqual(Object.hasOwn(untouchedGlobalSandbox, 'encodeURI'), false); + const deletedBaselineGlobalSandbox = vm.createContext({}); + assert.strictEqual(vm.runInContext('delete this.encodeURI', deletedBaselineGlobalSandbox), true); + assert.strictEqual(vm.runInContext('typeof encodeURI', deletedBaselineGlobalSandbox), 'undefined'); + assert.strictEqual(vm.runInContext('this.encodeURI = 7; encodeURI', deletedBaselineGlobalSandbox), 7); + assert.strictEqual(deletedBaselineGlobalSandbox.encodeURI, 7); + assert.strictEqual(vm.runInContext('encodeURI', deletedBaselineGlobalSandbox), 7); + assert.strictEqual(new vm.Script('2 + 1', { importModuleDynamically() { throw new Error('unreachable'); } }).runInThisContext(), 3); + assert.strictEqual(vm.compileFunction('return 2', [], { importModuleDynamically() { throw new Error('unreachable'); } })(), 2); + assert.strictEqual(missingImportHelperCount(), missingImportHelperCountBefore); + assert.strictEqual(missingImportFlagHelperCount(), missingImportFlagHelperCountBefore); + assert.strictEqual( + new vm.Script('"import(\\"node:fs\\")"; /import\\("node:fs"\\)/; ({ import() { return 3; } }).import();').runInThisContext(), + 3, + ); + assert.strictEqual( + new vm.Script('({ import(value = /[)]/) { return value.test(")"); } }).import();').runInThisContext(), + true, + ); + assert.strictEqual( + await new vm.Script('({ async import() { return 4; } }).import().then((value) => value);').runInThisContext(), + 4, + ); + assert.strictEqual( + new vm.Script('({ *import() { yield 5; } }).import().next().value;').runInThisContext(), + 5, + ); + assert.strictEqual( + await new vm.Script('({ async *import() { yield 6; } }).import().next().then((result) => result.value);').runInThisContext(), + 6, + ); + assert.strictEqual( + new vm.Script('const target = {}; ({ set import(value) { target.value = value; } }).import = 7; target.value;').runInThisContext(), + 7, + ); + assert.strictEqual( + new vm.Script('class Example { static import() { return 8; } } Example.import();').runInThisContext(), + 8, + ); + assert.strictEqual( + await new vm.Script('class AsyncExample { static async import() { return 9; } } AsyncExample.import();').runInThisContext(), + 9, + ); + + await assert.rejects( + new vm.Script('import("./message.mjs")').runInThisContext(), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' }, + 'vm.Script without importModuleDynamically rejects dynamic import', + ); + await assert.rejects( + vm.compileFunction('return import("./message.mjs")')(), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' }, + 'vm.compileFunction without importModuleDynamically rejects dynamic import', + ); + await assert.rejects( + vm.runInThisContext('import("./message.mjs")'), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' }, + 'vm.runInThisContext without importModuleDynamically rejects dynamic import', + ); + await assert.rejects( + new vm.Script('import("./message.mjs")').runInNewContext({}), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' }, + 'vm.Script.runInNewContext without importModuleDynamically rejects dynamic import', + ); + await assert.rejects( + new vm.Script('import("./message.mjs")').runInContext(vm.createContext({})), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' }, + 'vm.Script.runInContext without importModuleDynamically rejects dynamic import', + ); + await assert.rejects( + new vm.Script('import("./message.mjs")', { + importModuleDynamically() { throw new Error('unreachable'); }, + }).runInThisContext(), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG' }, + 'vm.Script with importModuleDynamically callback rejects without VM modules flag', + ); + await assert.rejects( + vm.compileFunction('return import("./message.mjs")', [], { + importModuleDynamically() { throw new Error('unreachable'); }, + })(), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG' }, + 'vm.compileFunction with importModuleDynamically callback rejects without VM modules flag', + ); + await assert.rejects( + vm.runInThisContext('import("./message.mjs")', { + importModuleDynamically() { throw new Error('unreachable'); }, + }), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG' }, + 'vm.runInThisContext with importModuleDynamically callback rejects without VM modules flag', + ); + await assert.rejects( + new vm.Script('import("./message.mjs")', { + importModuleDynamically() { throw new Error('unreachable'); }, + }).runInNewContext({}), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG' }, + 'vm.Script.runInNewContext with importModuleDynamically callback rejects without VM modules flag', + ); + await assert.rejects( + new vm.Script('import("./message.mjs")', { + importModuleDynamically() { throw new Error('unreachable'); }, + }).runInContext(vm.createContext({})), + { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG' }, + 'vm.Script.runInContext with importModuleDynamically callback rejects without VM modules flag', + ); + + const missingSourceTextModule = new vm.SourceTextModule('globalThis.vmModuleImportResult = import("dep");'); + await missingSourceTextModule.link(() => {}); + await missingSourceTextModule.evaluate(); + await assert.rejects(globalThis.vmModuleImportResult, { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' }); + delete globalThis.vmModuleImportResult; + + const missingFlagSourceTextModule = new vm.SourceTextModule('globalThis.vmModuleImportResult = import("dep");', { + importModuleDynamically() { throw new Error('unreachable'); }, + }); + await missingFlagSourceTextModule.link(() => {}); + await missingFlagSourceTextModule.evaluate(); + await assert.rejects(globalThis.vmModuleImportResult, { code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG' }); + delete globalThis.vmModuleImportResult; + + const stateModule = new vm.SourceTextModule('throw new Error("vm-state-error");'); + assert.strictEqual(stateModule.status, 'unlinked'); + assert.throws(() => stateModule.namespace, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must not be unlinked or linking', + }); + assert.throws(() => stateModule.error, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be errored', + }); + await assert.rejects(stateModule.link(undefined), { code: 'ERR_INVALID_ARG_TYPE' }); + await stateModule.link(() => {}); + assert.strictEqual(stateModule.status, 'linked'); + await assert.rejects(stateModule.link(() => {}), { code: 'ERR_VM_MODULE_ALREADY_LINKED' }); + await assert.rejects(stateModule.evaluate(false), { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. Received type boolean (false)', + }); + await assert.rejects(stateModule.evaluate({ breakOnSigint: 'a-string' }), { + code: 'ERR_INVALID_ARG_TYPE', + message: "The \"options.breakOnSigint\" property must be of type boolean. Received type string ('a-string')", + }); + await assert.rejects(stateModule.evaluate(), { message: 'vm-state-error' }); + assert.strictEqual(stateModule.status, 'errored'); + assert.strictEqual(stateModule.error.message, 'vm-state-error'); + + const invalidLinkedModule = new vm.SourceTextModule('import "dep";'); + assert.deepStrictEqual(invalidLinkedModule.dependencySpecifiers, ['dep']); + await assert.rejects(invalidLinkedModule.link(() => ({})), { code: 'ERR_VM_MODULE_NOT_MODULE' }); + assert.strictEqual(invalidLinkedModule.status, 'errored'); + assert.deepStrictEqual(new vm.SourceTextModule('// import "dep";').dependencySpecifiers, []); + + const linkedContext = vm.createContext({}); + const contextDepModule = new vm.SourceTextModule('', { context: linkedContext }); + await contextDepModule.link(() => {}); + const differentContextModule = new vm.SourceTextModule('import "dep";'); + await assert.rejects(differentContextModule.link(() => contextDepModule), { code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT' }); + assert.strictEqual(differentContextModule.status, 'errored'); + + const erroredDep = new vm.SourceTextModule('throw new Error("dep-error");'); + await erroredDep.link(() => {}); + await assert.rejects(erroredDep.evaluate(), { message: 'dep-error' }); + const rootWithErroredDep = new vm.SourceTextModule('import "dep";'); + await assert.rejects(rootWithErroredDep.link(() => erroredDep), { + code: 'ERR_VM_MODULE_LINK_FAILURE', + cause: erroredDep.error, + }); + + const originalExecArgv = process.execArgv.slice(); + try { + process.execArgv.push('--experimental-vm-modules'); + const vmDynamicDep = new vm.SourceTextModule('export const value = 7;'); + await vmDynamicDep.link(() => {}); + await vmDynamicDep.evaluate(); + + const moduleCallbackSourceTextModule = new vm.SourceTextModule('globalThis.vmModuleImportResult = import("dep", { with: { kind: "runtime" } });', { + importModuleDynamically(specifier, wrap, attributes) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, moduleCallbackSourceTextModule); + assert.deepStrictEqual(attributes, { __proto__: null, kind: 'runtime' }); + return vmDynamicDep; + }, + }); + await moduleCallbackSourceTextModule.link(() => {}); + await moduleCallbackSourceTextModule.evaluate(); + assert.strictEqual(await globalThis.vmModuleImportResult, vmDynamicDep.namespace); + delete globalThis.vmModuleImportResult; + + const namespaceCallbackSourceTextModule = new vm.SourceTextModule('globalThis.vmModuleImportResult = import("dep");', { + importModuleDynamically(specifier, wrap) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, namespaceCallbackSourceTextModule); + return vmDynamicDep.namespace; + }, + }); + await namespaceCallbackSourceTextModule.link(() => {}); + await namespaceCallbackSourceTextModule.evaluate(); + assert.strictEqual(await globalThis.vmModuleImportResult, vmDynamicDep.namespace); + delete globalThis.vmModuleImportResult; + + const namespaceCallbackScript = new vm.Script('import("dep")', { + importModuleDynamically(specifier, wrap) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, namespaceCallbackScript); + return vmDynamicDep.namespace; + }, + }); + assert.strictEqual(await namespaceCallbackScript.runInThisContext(), vmDynamicDep.namespace); + + const namespaceCallbackFunction = vm.compileFunction('return import("dep")', [], { + importModuleDynamically(specifier, wrap) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, namespaceCallbackFunction); + return vmDynamicDep.namespace; + }, + }); + assert.strictEqual(await namespaceCallbackFunction(), vmDynamicDep.namespace); + + const namespaceBrand = Symbol.for('wasm-rquickjs.vm.namespaceBindings'); + const spoofedNamespaceSourceTextModule = new vm.SourceTextModule('globalThis.vmModuleImportResult = import("dep");', { + importModuleDynamically() { + return { [namespaceBrand]: {} }; + }, + }); + await spoofedNamespaceSourceTextModule.link(() => {}); + await spoofedNamespaceSourceTextModule.evaluate(); + await assert.rejects(globalThis.vmModuleImportResult, { code: 'ERR_VM_MODULE_NOT_MODULE' }); + delete globalThis.vmModuleImportResult; + + const reusableScript = new vm.Script('import("dep")', { + importModuleDynamically(specifier, wrap) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, reusableScript); + return vmDynamicDep; + }, + }); + assert.strictEqual(await reusableScript.runInThisContext(), vmDynamicDep.namespace); + assert.strictEqual(await reusableScript.runInThisContext(), vmDynamicDep.namespace); + + const reusableFunction = vm.compileFunction('return import("dep")', [], { + importModuleDynamically(specifier, wrap) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, reusableFunction); + return vmDynamicDep; + }, + }); + assert.strictEqual(await reusableFunction(), vmDynamicDep.namespace); + assert.strictEqual(await reusableFunction(), vmDynamicDep.namespace); + + const sequentialSourceTextModule = new vm.SourceTextModule([ + 'globalThis.vmModuleImportResult = (async () => {', + ' const first = await import("dep");', + ' const second = await import("dep");', + ' return [first, second];', + '})();', + ].join('\n'), { + importModuleDynamically(specifier, wrap) { + assert.strictEqual(specifier, 'dep'); + assert.strictEqual(wrap, sequentialSourceTextModule); + return vmDynamicDep; + }, + }); + await sequentialSourceTextModule.link(() => {}); + await sequentialSourceTextModule.evaluate(); + assert.deepStrictEqual(await globalThis.vmModuleImportResult, [vmDynamicDep.namespace, vmDynamicDep.namespace]); + delete globalThis.vmModuleImportResult; + } finally { + process.execArgv.length = 0; + for (let i = 0; i < originalExecArgv.length; i++) { + process.execArgv.push(originalExecArgv[i]); + } + delete globalThis.vmModuleImportResult; + } + + const script = new vm.Script('import("./message.mjs")', { + filename: '/vm-default-loader-app/subdir/index.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await script.runInThisContext()).default, { value: 'from-subdir' }); + + const mutableOptions = { + filename: '/vm-default-loader-app/subdir/mutable.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }; + const mutableScript = new vm.Script('import("./message.mjs")', mutableOptions); + mutableOptions.filename = '/vm-default-loader-app/other/mutable.js'; + assert.deepStrictEqual((await mutableScript.runInThisContext()).default, { value: 'from-subdir' }); + + const expressionScript = new vm.Script([ + 'const name = "message.mjs";', + 'import("./" + name);', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/expression.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await expressionScript.runInThisContext()).default, { value: 'from-subdir' }); + + const templateScript = new vm.Script('import(`./message.mjs`)', { + filename: '/vm-default-loader-app/subdir/template.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await templateScript.runInThisContext()).default, { value: 'from-subdir' }); + + const commentScript = new vm.Script('import /* comment */ ("./message.mjs")', { + filename: '/vm-default-loader-app/subdir/comment.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await commentScript.runInThisContext()).default, { value: 'from-subdir' }); + + const templateExpressionScript = new vm.Script([ + 'let imported;', + '`${imported = import("./message.mjs")}`;', + 'imported;', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/template-expression.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await templateExpressionScript.runInThisContext()).default, { value: 'from-subdir' }); + + const dataScript = new vm.Script([ + 'const literal = "import(\\"./message.mjs\\")";', + 'const regex = /import\\("\\.\\/message\\.mjs"\\)/;', + 'function readRegex() { return /import\\("\\.\\/message\\.mjs"\\)/; }', + 'if (true) /import\\("\\.\\/message\\.mjs"\\)/.test(literal);', + 'literal + String(regex) + String(readRegex());', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/string-regex.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.match(dataScript.runInThisContext(), /import/); + + const divisionScript = new vm.Script([ + 'const a = 1, b = 2;', + 'a / b;', + 'import("./message.mjs");', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/division.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await divisionScript.runInThisContext()).default, { value: 'from-subdir' }); + + const propertyScript = new vm.Script([ + 'const obj = { "import"(specifier) { return specifier; } };', + 'obj.import("./message.mjs");', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/property.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.strictEqual(propertyScript.runInThisContext(), './message.mjs'); + + const privatePropertyScript = new vm.Script([ + 'class C {', + ' #import(specifier) { return specifier; }', + ' m() { return this.#import("./message.mjs"); }', + '}', + 'new C().m();', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/private-property.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.strictEqual(privatePropertyScript.runInThisContext(), './message.mjs'); + + const strictScript = new vm.Script([ + '"use strict";', + 'Promise.all([import("./message.mjs"), Promise.resolve((function() { return this; })())]);', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/strict.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + const [strictImport, strictThis] = await strictScript.runInThisContext(); + assert.deepStrictEqual(strictImport.default, { value: 'from-subdir' }); + assert.strictEqual(strictThis, undefined); + + const collisionScript = new vm.Script([ + 'let __wasm_rquickjs_vm_import__ = 1;', + 'import("./message.mjs");', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/collision.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await collisionScript.runInThisContext()).default, { value: 'from-subdir' }); + + const helperNameCollisionScript = new vm.Script([ + 'let __wasm_rquickjs_vm_default_loader_import__ = 1;', + 'import("./message.mjs");', + ].join('\n'), { + filename: '/vm-default-loader-app/subdir/helper-name-collision.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await helperNameCollisionScript.runInThisContext()).default, { value: 'from-subdir' }); + + const builtinScript = new vm.Script('import("node:fs")', { + filename: '/vm-default-loader-app/subdir/builtin.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.strictEqual((await builtinScript.runInThisContext()).existsSync('/vm-default-loader-app'), true); + + const fileUrlScript = new vm.Script('import("./message.mjs")', { + filename: pathToFileURL('/vm-default-loader-app/space dir/index.js').href + '?cache=1', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await fileUrlScript.runInThisContext()).default, { value: 'from-space' }); + + assert.deepStrictEqual((await vm.runInNewContext('import("./message.mjs")', {}, { + filename: '/vm-default-loader-app/subdir/run-in-new-context.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + })).default, { value: 'from-subdir' }); + + assert.deepStrictEqual((await vm.runInNewContext('import("./message.mjs")', { globalThis: {} }, { + filename: '/vm-default-loader-app/subdir/shadow-globalthis.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + })).default, { value: 'from-subdir' }); + + const newContextScript = new vm.Script('import("./message.mjs")', { + filename: '/vm-default-loader-app/subdir/script-new-context.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await newContextScript.runInNewContext({})).default, { value: 'from-subdir' }); + + const scriptContext = vm.createContext({}); + const contextDefaultScript = new vm.Script('import("./message.mjs")', { + filename: '/vm-default-loader-app/subdir/script-context.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await contextDefaultScript.runInContext(scriptContext)).default, { value: 'from-subdir' }); + + const shadowGlobalContext = vm.createContext({ globalThis: {} }); + const shadowGlobalContextScript = new vm.Script('import("./message.mjs")', { + filename: '/vm-default-loader-app/subdir/script-context-shadow-globalthis.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await shadowGlobalContextScript.runInContext(shadowGlobalContext)).default, { value: 'from-subdir' }); + + const context = vm.createContext({}, { + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + const contextScript = new vm.Script('import("./message.mjs")'); + await assert.rejects(contextScript.runInContext(context)); + const originalContextCwd = process.cwd(); + try { + process.chdir('/vm-default-loader-app/subdir'); + const contextEvalScript = new vm.Script('Promise.resolve("import(\\"./message.mjs\\")").then(eval)'); + const contextEvalResult = await contextEvalScript.runInContext(context); + assert.strictEqual(JSON.stringify(contextEvalResult.default || contextEvalResult), '{"value":"from-subdir"}'); + } finally { + process.chdir(originalContextCwd); + } + + const compiled = vm.compileFunction('return import("./" + name)', ['name'], { + filename: '/vm-default-loader-app/subdir/function.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await compiled('message.mjs')).default, { value: 'from-subdir' }); + + const compiledMissing = vm.compileFunction('return import("./missing.mjs")', [], { + filename: '/vm-default-loader-app/subdir/function-missing.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + await assert.rejects(compiledMissing(), { code: 'ERR_MODULE_NOT_FOUND' }); + + const originalCwd = process.cwd(); + try { + process.chdir('/vm-default-loader-app'); + const cwdScript = new vm.Script('import("./message.mjs")', { + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + assert.deepStrictEqual((await cwdScript.runInThisContext()).default, { value: 'from-cwd' }); + } finally { + process.chdir(originalCwd); + } + + await assert.rejects( + new vm.Script('import("./missing.mjs")', { + filename: '/vm-default-loader-app/subdir/index.js', + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }).runInThisContext(), + { code: 'ERR_MODULE_NOT_FOUND' }, + ); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testVmSourceTextModuleLinkSemantics = async () => { + try { + const { SourceTextModule } = await import('node:vm'); + + const defaultSource = new SourceTextModule([ + 'const __wasm_rquickjs_vm_default_export = "local";', + 'export default 5;', + ].join('\n')); + const defaultConsumer = new SourceTextModule([ + 'import five from "default-source";', + 'export const value = five;', + ].join('\n')); + await defaultConsumer.link((specifier) => { + assert.strictEqual(specifier, 'default-source'); + return defaultSource; + }); + await defaultConsumer.evaluate(); + assert.strictEqual(defaultConsumer.namespace.value, 5); + + const namedDefault = new SourceTextModule([ + 'export default function getAnswer() { return 42; }', + 'export const visible = getAnswer();', + ].join('\n')); + await namedDefault.link(() => { + throw new Error('unexpected dependency'); + }); + await namedDefault.evaluate(); + assert.strictEqual(namedDefault.namespace.default(), 42); + assert.strictEqual(namedDefault.namespace.visible, 42); + + const dependency = new SourceTextModule([ + 'export default "default-value";', + 'export const named = "named-value";', + ].join('\n')); + const namespaceConsumer = new SourceTextModule([ + 'import value, { named } from "dependency";', + 'import * as ns from "dependency";', + 'export const combined = value + ":" + named + ":" + ns.default + ":" + ns.named;', + ].join('\n')); + await namespaceConsumer.link((specifier) => { + assert.strictEqual(specifier, 'dependency'); + return dependency; + }); + await namespaceConsumer.evaluate(); + assert.strictEqual(namespaceConsumer.namespace.combined, 'default-value:named-value:default-value:named-value'); + + const ambiguousA = new SourceTextModule('export const shared = "a"; export const onlyA = "a";'); + const ambiguousB = new SourceTextModule('export const shared = "b"; export const onlyB = "b";'); + const star = new SourceTextModule('export * from "a"; export * from "b";'); + await star.link((specifier) => specifier === 'a' ? ambiguousA : ambiguousB); + await star.evaluate(); + assert.strictEqual('shared' in star.namespace, false); + assert.strictEqual(star.namespace.onlyA, 'a'); + assert.strictEqual(star.namespace.onlyB, 'b'); + + const common = new SourceTextModule('export const x = 1;'); + const starA = new SourceTextModule('export * from "common";'); + const starB = new SourceTextModule('export * from "common";'); + const duplicateSameBinding = new SourceTextModule('export * from "star-a"; export * from "star-b";'); + await duplicateSameBinding.link((specifier) => { + if (specifier === 'common') return common; + if (specifier === 'star-a') return starA; + if (specifier === 'star-b') return starB; + throw new Error(`unexpected specifier: ${specifier}`); + }); + await duplicateSameBinding.evaluate(); + assert.strictEqual(duplicateSameBinding.namespace.x, 1); + + const fromExport = new SourceTextModule('export const from = "from-name";'); + const fromImport = new SourceTextModule('import { from as x } from "from-export"; export const value = x;'); + await fromImport.link(() => fromExport); + await fromImport.evaluate(); + assert.strictEqual(fromImport.namespace.value, 'from-name'); + + const multilineDependency = new SourceTextModule('export default "default"; export const a = "a";'); + const multilineImport = new SourceTextModule([ + 'import value', + 'from "multiline-dependency";', + 'import { a }', + 'from "multiline-dependency";', + 'export const result = value + a;', + ].join('\n')); + await multilineImport.link(() => multilineDependency); + await multilineImport.evaluate(); + assert.strictEqual(multilineImport.namespace.result, 'defaulta'); + + const multilineExport = new SourceTextModule([ + 'export *', + 'from "multiline-dependency";', + ].join('\n')); + await multilineExport.link(() => multilineDependency); + await multilineExport.evaluate(); + assert.strictEqual(multilineExport.namespace.a, 'a'); + assert.strictEqual('default' in multilineExport.namespace, false); + + let attributesSeen = null; + const attributes = new SourceTextModule('import "dep" with { n1: "v1", "n-two": "v2" };'); + await attributes.link((specifier, _module, extra) => { + assert.strictEqual(specifier, 'dep'); + attributesSeen = extra; + return new SourceTextModule(''); + }); + assert.strictEqual(attributesSeen.attributes.n1, 'v1'); + assert.strictEqual(attributesSeen.assert.n1, 'v1'); + assert.strictEqual(attributesSeen.attributes['n-two'], 'v2'); + + const cycleA = new SourceTextModule([ + 'import getValue from "cycle-b";', + 'export let value = 1;', + 'value = 2;', + 'export default getValue();', + ].join('\n')); + const cycleB = new SourceTextModule([ + 'import { value } from "cycle-a";', + 'export default function getValue() { return value; }', + ].join('\n')); + await cycleA.link((specifier) => specifier === 'cycle-b' ? cycleB : cycleA); + await cycleA.evaluate(); + assert.strictEqual(cycleA.namespace.default, 2); + + const compoundA = new SourceTextModule([ + 'import getValue from "compound-b";', + 'export let value = 1;', + 'value += 2;', + 'value++;', + 'if (true) { value = 7; }', + 'export default getValue();', + ].join('\n')); + const compoundB = new SourceTextModule([ + 'import { value } from "compound-a";', + 'export default function getValue() { return value; }', + ].join('\n')); + await compoundA.link((specifier) => specifier === 'compound-b' ? compoundB : compoundA); + await compoundA.evaluate(); + assert.strictEqual(compoundA.namespace.default, 7); + + const shadowA = new SourceTextModule([ + 'import getValue from "shadow-b";', + 'export let value = 1;', + 'function f(value) { value = 2; }', + 'f(0);', + 'export default getValue();', + ].join('\n')); + const shadowB = new SourceTextModule([ + 'import { value } from "shadow-a";', + 'export default function getValue() { return value; }', + ].join('\n')); + await shadowA.link((specifier) => specifier === 'shadow-b' ? shadowB : shadowA); + await shadowA.evaluate(); + assert.strictEqual(shadowA.namespace.default, 1); + + const closureA = new SourceTextModule([ + 'import getValue from "closure-b";', + 'export let value = 1;', + 'function setValue() { value = 2; }', + 'setValue();', + 'export default getValue();', + ].join('\n')); + const closureB = new SourceTextModule([ + 'import { value } from "closure-a";', + 'export default function getValue() { return value; }', + ].join('\n')); + await closureA.link((specifier) => specifier === 'closure-b' ? closureB : closureA); + await closureA.evaluate(); + assert.strictEqual(closureA.namespace.default, 2); + + const localShadowA = new SourceTextModule([ + 'import getValue from "local-shadow-b";', + 'export let value = 1;', + 'function f() { let value = 0; value = 2; }', + 'f();', + 'export default getValue();', + ].join('\n')); + const localShadowB = new SourceTextModule([ + 'import { value } from "local-shadow-a";', + 'export default function getValue() { return value; }', + ].join('\n')); + await localShadowA.link((specifier) => specifier === 'local-shadow-b' ? localShadowB : localShadowA); + await localShadowA.evaluate(); + assert.strictEqual(localShadowA.namespace.default, 1); + + const multilineAssignmentA = new SourceTextModule([ + 'import getValue from "multiline-assignment-b";', + 'export let value = 0;', + 'value = 1', + ' + 2;', + 'export default getValue();', + ].join('\n')); + const multilineAssignmentB = new SourceTextModule([ + 'import { value } from "multiline-assignment-a";', + 'export default function getValue() { return value; }', + ].join('\n')); + await multilineAssignmentA.link((specifier) => specifier === 'multiline-assignment-b' ? multilineAssignmentB : multilineAssignmentA); + await multilineAssignmentA.evaluate(); + assert.strictEqual(multilineAssignmentA.namespace.default, 3); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testRequireEsmErrorHandling = async () => { + try { + fs.mkdirSync('/require-esm-errors-app', { recursive: true }); + fs.writeFileSync('/require-esm-errors-app/runtime-error.mjs', [ + 'throw new Error("hello");', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/reference-error.mjs', [ + 'Object.defineProperty(exports, "__esModule", { value: true });', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/ambiguous-reference.js', [ + 'Object.defineProperty(exports, "__esModule", { value: true });', + 'const require = () => {};', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/valid-transpiled.js', [ + 'Object.defineProperty(exports, "__esModule", { value: true });', + 'exports.foo = "foo";', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/module-exports-marker.mjs', [ + 'const value = { marker: true };', + 'export default "namespace default";', + 'export { value as "module.exports" };', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/cjs-missing-named.cjs', [ + 'module.exports = { missing: "runtime" };', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/cjs-default-named.cjs', [ + 'module.exports = { defaultNamed: true };', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/cjs-quoted-named.cjs', [ + 'module.exports = {};', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/import-missing-named.mjs', [ + 'import { missing } from "./cjs-missing-named.cjs";', + 'export default missing;', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/import-default-named.mjs', [ + 'import { default as cjsDefault } from "./cjs-default-named.cjs";', + 'export default cjsDefault;', + ].join('\n')); + fs.writeFileSync('/require-esm-errors-app/import-quoted-named.mjs', [ + 'import { "missing-name" as missingName } from "./cjs-quoted-named.cjs";', + 'export default missingName;', + ].join('\n')); + fs.mkdirSync('/require-esm-errors-app/node_modules/warn-pkg/trailing-pattern-slash', { recursive: true }); + fs.writeFileSync('/require-esm-errors-app/node_modules/warn-pkg/package.json', JSON.stringify({ + type: 'module', + exports: { + './trailing-pattern-slash*': './trailing-pattern-slash*index.mjs', + }, + })); + fs.writeFileSync('/require-esm-errors-app/node_modules/warn-pkg/trailing-pattern-slash/index.mjs', 'export default { warned: true };'); + fs.writeFileSync('/require-esm-errors-app/import-warn-pkg.mjs', [ + 'import warned from "warn-pkg/trailing-pattern-slash/";', + 'export default warned;', + ].join('\n')); + + const { createRequire } = await import('node:module'); + const require = createRequire('/require-esm-errors-app/main.cjs'); + + assert.throws(() => require('/require-esm-errors-app/runtime-error.mjs'), { + message: 'hello', + }); + assert.throws(() => require('/require-esm-errors-app/reference-error.mjs'), { + name: 'ReferenceError', + }); + assert.throws(() => require('/require-esm-errors-app/ambiguous-reference.js'), { + name: 'ReferenceError', + }); + assert.strictEqual(require('/require-esm-errors-app/valid-transpiled.js').foo, 'foo'); + assert.deepStrictEqual(require('/require-esm-errors-app/module-exports-marker.mjs'), { marker: true }); + assert.deepStrictEqual((await import('/require-esm-errors-app/import-default-named.mjs')).default, { + defaultNamed: true, + }); + const requireEsmPackageWarnings = []; + const onRequireEsmPackageWarning = (warning) => requireEsmPackageWarnings.push(warning); + process.on('warning', onRequireEsmPackageWarning); + try { + assert.deepStrictEqual(require('/require-esm-errors-app/import-warn-pkg.mjs').default, { warned: true }); + await new Promise((resolve) => process.nextTick(resolve)); + } finally { + process.removeListener('warning', onRequireEsmPackageWarning); + } + assert.deepStrictEqual(requireEsmPackageWarnings.map((warning) => warning.code), ['DEP0155']); + assert.match(requireEsmPackageWarnings[0].message, /package\.json imported from \/require-esm-errors-app\/import-warn-pkg\.mjs\./); + await assert.rejects(() => import('/require-esm-errors-app/import-missing-named.mjs'), { + name: 'SyntaxError', + message: [ + "Named export 'missing' not found. The requested module './cjs-missing-named.cjs' is a CommonJS module, which may not support all module.exports as named exports.", + 'CommonJS modules can always be imported via the default export, for example using:', + '', + "import pkg from './cjs-missing-named.cjs';", + 'const { missing } = pkg;', + '', + ].join('\n'), + }); + await assert.rejects(() => import('/require-esm-errors-app/import-quoted-named.mjs'), { + name: 'SyntaxError', + message: [ + 'Named export \'missing-name\' not found. The requested module \'./cjs-quoted-named.cjs\' is a CommonJS module, which may not support all module.exports as named exports.', + 'CommonJS modules can always be imported via the default export, for example using:', + '', + "import pkg from './cjs-quoted-named.cjs';", + 'const { "missing-name": missingName } = pkg;', + '', + ].join('\n'), + }); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testRequireEsmTlaRetry = async () => { + try { + fs.mkdirSync('/require-esm-tla-app', { recursive: true }); + fs.writeFileSync('/require-esm-tla-app/tla-success.mjs', [ + 'await Promise.resolve();', + 'export const hello = "world";', + ].join('\n')); + + const { createRequire } = await import('node:module'); + const require = createRequire('/require-esm-tla-app/main.cjs'); + + assert.throws(() => require('/require-esm-tla-app/tla-success.mjs'), { + code: 'ERR_REQUIRE_ASYNC_MODULE', + }); + + const first = await import('/require-esm-tla-app/tla-success.mjs'); + const second = await import('/require-esm-tla-app/tla-success.mjs'); + assert.strictEqual(first.hello, 'world'); + assert.strictEqual(second.hello, 'world'); + assert.strictEqual(first, second); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testRequireEsmRejectionTracking = async () => { + try { + fs.mkdirSync('/require-esm-rejection-app', { recursive: true }); + fs.writeFileSync('/require-esm-rejection-app/pending-tla.mjs', [ + 'await new Promise(() => {});', + 'export const value = 1;', + ].join('\n')); + fs.writeFileSync('/require-esm-rejection-app/throw-with-unhandled.mjs', [ + 'Promise.reject(new Error("side rejection"));', + 'throw new Error("module failure");', + ].join('\n')); + fs.writeFileSync('/require-esm-rejection-app/shared-reason.mjs', [ + 'const shared = new Error("shared reason");', + 'Promise.reject(shared);', + 'throw shared;', + ].join('\n')); + fs.writeFileSync('/require-esm-rejection-app/saved-reason.mjs', [ + 'throw globalThis.__requireEsmSavedReason;', + ].join('\n')); + + const { createRequire } = await import('node:module'); + const require = createRequire('/require-esm-rejection-app/main.cjs'); + + const originalCatch = Object.getOwnPropertyDescriptor(Promise.prototype, 'catch'); + Object.defineProperty(Promise.prototype, 'catch', { + configurable: true, + get() { + throw new Error('poisoned Promise.prototype.catch'); + }, + }); + try { + assert.throws(() => require('/require-esm-rejection-app/pending-tla.mjs'), { + code: 'ERR_REQUIRE_ASYNC_MODULE', + }); + } finally { + Object.defineProperty(Promise.prototype, 'catch', originalCatch); + } + + const unhandled = []; + const onUnhandled = (reason) => unhandled.push(reason && reason.message); + process.on('unhandledRejection', onUnhandled); + try { + assert.throws(() => require('/require-esm-rejection-app/throw-with-unhandled.mjs'), { + message: 'module failure', + }); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + assert.throws(() => require('/require-esm-rejection-app/shared-reason.mjs'), { + message: 'shared reason', + }); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + globalThis.__requireEsmSavedReason = new Error('saved reason'); + assert.throws(() => require('/require-esm-rejection-app/saved-reason.mjs'), { + message: 'saved reason', + }); + Promise.reject(globalThis.__requireEsmSavedReason); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + } finally { + process.removeListener('unhandledRejection', onUnhandled); + } + assert.deepStrictEqual(unhandled, ['side rejection', 'shared reason', 'saved reason']); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testRequireEsmCycleGuards = async () => { + try { + fs.mkdirSync('/require-esm-cycle-app', { recursive: true }); + fs.writeFileSync('/require-esm-cycle-app/a.mjs', [ + 'import { createRequire } from "node:module";', + 'const require = createRequire(import.meta.url);', + 'let cycleCode;', + 'try {', + ' require("./a.mjs");', + '} catch (error) {', + ' cycleCode = error && error.code;', + '}', + 'export const value = 1;', + 'export { cycleCode };', + ].join('\n')); + fs.writeFileSync('/require-esm-cycle-app/syntax-detected.js', [ + 'import { createRequire } from "node:module";', + 'const require = createRequire(import.meta.url);', + 'let cycleCode;', + 'try {', + ' require("./syntax-detected.js");', + '} catch (error) {', + ' cycleCode = error && error.code;', + '}', + 'export const value = 2;', + 'export { cycleCode };', + ].join('\n')); + fs.writeFileSync('/require-esm-cycle-app/commented-alias.mjs', [ + 'import { createRequire /* comment */ as makeRequire } from "node:module";', + 'const require = makeRequire /* comment */ (import.meta.url);', + 'let cycleCode;', + 'try {', + ' require("./commented-alias.mjs");', + '} catch (error) {', + ' cycleCode = error && error.code;', + '}', + 'export const value = 3;', + 'export { cycleCode };', + ].join('\n')); + fs.writeFileSync('/require-esm-cycle-app/string-name-alias.mjs', [ + 'import { "createRequire" as makeRequire } from "node:module";', + 'const require = makeRequire(import.meta.url);', + 'let cycleCode;', + 'try {', + ' require("./string-name-alias.mjs");', + '} catch (error) {', + ' cycleCode = error && error.code;', + '}', + 'export const value = 4;', + 'export { cycleCode };', + ].join('\n')); + fs.writeFileSync('/require-esm-cycle-app/commented-static-edge-a.mjs', [ + 'import { cycleCode } from /* graph edge */ "./commented-static-edge-b.mjs";', + 'export const value = 5;', + 'export { cycleCode };', + ].join('\n')); + fs.writeFileSync('/require-esm-cycle-app/commented-static-edge-b.mjs', [ + 'import { createRequire } from "node:module";', + 'const require = createRequire(import.meta.url);', + 'let cycleCode;', + 'try {', + ' require("./commented-static-edge-b.mjs");', + '} catch (error) {', + ' cycleCode = error && error.code;', + '}', + 'export { cycleCode };', + ].join('\n')); + + const { createRequire } = await import('node:module'); + const require = createRequire('/require-esm-cycle-app/main.cjs'); + + const arrayIterator = Array.prototype[Symbol.iterator]; + delete Array.prototype[Symbol.iterator]; + try { + const ns = require('/require-esm-cycle-app/a.mjs'); + assert.strictEqual(ns.value, 1); + assert.strictEqual(ns.cycleCode, 'ERR_REQUIRE_CYCLE_MODULE'); + const detected = require('/require-esm-cycle-app/syntax-detected.js'); + assert.strictEqual(detected.value, 2); + assert.strictEqual(detected.cycleCode, 'ERR_REQUIRE_CYCLE_MODULE'); + const commentedAlias = require('/require-esm-cycle-app/commented-alias.mjs'); + assert.strictEqual(commentedAlias.value, 3); + assert.strictEqual(commentedAlias.cycleCode, 'ERR_REQUIRE_CYCLE_MODULE'); + const stringNameAlias = require('/require-esm-cycle-app/string-name-alias.mjs'); + assert.strictEqual(stringNameAlias.value, 4); + assert.strictEqual(stringNameAlias.cycleCode, 'ERR_REQUIRE_CYCLE_MODULE'); + const commentedStaticEdge = require('/require-esm-cycle-app/commented-static-edge-a.mjs'); + assert.strictEqual(commentedStaticEdge.value, 5); + assert.strictEqual(commentedStaticEdge.cycleCode, 'ERR_REQUIRE_CYCLE_MODULE'); + } finally { + Array.prototype[Symbol.iterator] = arrayIterator; + } + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsSymlinkCircularCache = async () => { + try { + const { createRequire } = await import('node:module'); + const root = '/cjs-symlink-cycle-app'; + const moduleA = `${root}/node_modules/moduleA`; + const moduleB = `${root}/node_modules/moduleB`; + const moduleALink = `${moduleB}/node_modules/moduleA`; + const moduleBLink = `${moduleA}/node_modules/moduleB`; + + fs.mkdirSync(`${moduleA}/node_modules`, { recursive: true }); + fs.mkdirSync(`${moduleB}/node_modules`, { recursive: true }); + fs.symlinkSync(moduleA, moduleALink); + fs.symlinkSync(moduleB, moduleBLink); + fs.writeFileSync(`${root}/index.cjs`, 'module.exports = require("moduleA");'); + fs.writeFileSync(`${moduleA}/index.js`, 'module.exports = { b: require("moduleB") };'); + fs.writeFileSync(`${moduleB}/index.js`, 'module.exports = { a: require("moduleA") };'); + + const require = createRequire(`${root}/index.cjs`); + const obj = require(`${root}/index.cjs`); + assert.ok(obj); + assert.ok(obj.b); + assert.ok(obj.b.a); + assert.ok(!obj.b.a.b); + + const cacheKeys = Object.keys(require.cache).filter((key) => key.startsWith(root)); + assert.strictEqual(cacheKeys.some((key) => key.includes('/moduleA/node_modules/moduleB/')), false); + assert.strictEqual(cacheKeys.some((key) => key.includes('/moduleB/node_modules/moduleA/')), false); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsNodeModuleLoadingCompat = async () => { + try { + const { createRequire } = await import('node:module'); + const root = '/cjs-node-module-loading-app'; + const require = createRequire(`${root}/entry.cjs`); + + fs.mkdirSync(`${root}/missing-main-with-index`, { recursive: true }); + fs.writeFileSync(`${root}/missing-main-with-index/package.json`, JSON.stringify({ main: 'missing.js' })); + fs.writeFileSync(`${root}/missing-main-with-index/index.js`, 'module.exports = { ok: true };'); + assert.deepStrictEqual(require(`${root}/missing-main-with-index`), { ok: true }); + + fs.mkdirSync(`${root}/missing-main-no-index`, { recursive: true }); + fs.writeFileSync(`${root}/missing-main-no-index/package.json`, JSON.stringify({ main: 'missing.js' })); + assert.throws(() => require(`${root}/missing-main-no-index`), { + code: 'MODULE_NOT_FOUND', + path: `${root}/missing-main-no-index/package.json`, + requestPath: `${root}/missing-main-no-index`, + }); + + require.extensions['.test'] = function(module, filename) { + const content = fs.readFileSync(filename, 'utf8').replace('VALUE', 'module.exports.value'); + module._compile(content, filename); + }; + fs.writeFileSync(`${root}/custom.test`, 'VALUE = 42;'); + assert.strictEqual(require(`${root}/custom`).value, 42); + + fs.mkdirSync(`${root}/parent/child/node_modules/target`, { recursive: true }); + fs.writeFileSync(`${root}/parent/child/node_modules/target/index.js`, 'module.exports = { from: "child" };'); + fs.writeFileSync(`${root}/parent/child/index.js`, 'exports.module = module; exports.loaded = require("target");'); + fs.writeFileSync(`${root}/parent/index.js`, [ + 'const child = require("./child");', + 'module.exports = { fromModuleRequire: child.module.require("target"), fromChildRequire: child.loaded };', + ].join('\n')); + const parent = require(`${root}/parent`); + assert.deepStrictEqual(parent.fromModuleRequire, { from: 'child' }); + assert.strictEqual(parent.fromModuleRequire, parent.fromChildRequire); + + fs.mkdirSync(`${root}/node_modules/no-exports-cjs/subdir`, { recursive: true }); + fs.mkdirSync(`${root}/node_modules/no-exports-cjs/empty-dir`, { recursive: true }); + fs.writeFileSync(`${root}/node_modules/no-exports-cjs/package.json`, JSON.stringify({ type: 'commonjs' })); + fs.writeFileSync(`${root}/node_modules/no-exports-cjs/exact.js`, 'module.exports = { value: "exact" };'); + fs.writeFileSync(`${root}/node_modules/no-exports-cjs/no-ext.js`, 'module.exports = { value: "extension" };'); + fs.writeFileSync(`${root}/node_modules/no-exports-cjs/subdir/index.js`, 'module.exports = { value: "directory" };'); + fs.writeFileSync(`${root}/node_modules/no-exports-cjs/sp%20ce.js`, 'module.exports = { value: "encoded" };'); + fs.writeFileSync(`${root}/node_modules/no-exports-cjs/native.node`, 'not a native addon'); + fs.mkdirSync(`${root}/node_modules/exports-blocks-cjs`, { recursive: true }); + fs.writeFileSync(`${root}/node_modules/exports-blocks-cjs/package.json`, JSON.stringify({ + exports: './public.js', + })); + fs.writeFileSync(`${root}/node_modules/exports-blocks-cjs/public.js`, 'module.exports = { value: "public" };'); + fs.writeFileSync(`${root}/node_modules/exports-blocks-cjs/private.js`, 'module.exports = { value: "private" };'); + fs.mkdirSync(`${root}/node_modules/native-main`, { recursive: true }); + fs.writeFileSync(`${root}/node_modules/native-main/package.json`, JSON.stringify({ main: 'addon' })); + fs.writeFileSync(`${root}/node_modules/native-main/addon.node`, 'not a native addon'); + fs.mkdirSync(`${root}/node_modules/native-index`, { recursive: true }); + fs.writeFileSync(`${root}/node_modules/native-index/index.node`, 'not a native addon'); + assert.deepStrictEqual(require('no-exports-cjs/exact.js'), { value: 'exact' }); + assert.deepStrictEqual(require('no-exports-cjs/no-ext'), { value: 'extension' }); + assert.deepStrictEqual(require('no-exports-cjs/subdir'), { value: 'directory' }); + assert.deepStrictEqual(require('no-exports-cjs/sp%20ce.js'), { value: 'encoded' }); + assert.throws(() => require('no-exports-cjs/empty-dir'), { code: 'MODULE_NOT_FOUND' }); + assert.throws(() => require('no-exports-cjs/native'), { code: 'ERR_DLOPEN_FAILED', message: /native\.node/ }); + assert.deepStrictEqual(require('exports-blocks-cjs'), { value: 'public' }); + assert.throws(() => require('exports-blocks-cjs/private.js'), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }); + assert.throws(() => require('native-main'), { code: 'ERR_DLOPEN_FAILED', message: /addon\.node/ }); + assert.throws(() => require('native-index'), { code: 'ERR_DLOPEN_FAILED', message: /index\.node/ }); + + fs.writeFileSync(`${root}/bom.js`, '\uFEFFmodule.exports = 42;'); + fs.writeFileSync(`${root}/bom.json`, '\uFEFF42'); + fs.writeFileSync(`${root}/bom-shebang-shebang.js`, '\uFEFF#!shebang\n#!shebang\nmodule.exports = 1;'); + fs.writeFileSync(`${root}/shebang-bom.js`, '#!shebang\n\uFEFFmodule.exports = 42;'); + assert.strictEqual(require(`${root}/bom.js`), 42); + assert.strictEqual(require(`${root}/bom.json`), 42); + assert.throws(() => require(`${root}/bom-shebang-shebang.js`), { name: 'SyntaxError' }); + assert.strictEqual(require(`${root}/shebang-bom.js`), 42); + + require.extensions['.reg'] = require.extensions['.js']; + fs.mkdirSync(`${root}/dir-index-reg`, { recursive: true }); + fs.writeFileSync(`${root}/dir-index-reg/index.reg`, 'exports.value = "index.reg";'); + assert.strictEqual(require(`${root}/dir-index-reg`).value, 'index.reg'); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsNestedDependencyCacheShape = async () => { + try { + const { createRequire } = await import('node:module'); + const root = '/cjs-nested-dependency-cache-app'; + + fs.mkdirSync(`${root}/b/package`, { recursive: true }); + fs.writeFileSync(`${root}/b/package/index.js`, [ + 'exports.hello = "world";', + ].join('\n')); + fs.writeFileSync(`${root}/b/d.js`, [ + 'let value = "D";', + 'exports.D = function() { return value; };', + ].join('\n')); + fs.writeFileSync(`${root}/b/c.js`, [ + 'const d = require("./d");', + 'const package = require("./package");', + 'if (package.hello !== "world") throw new Error("bad package");', + 'let value = "C";', + 'exports.SomeClass = function() {};', + 'exports.C = function() { return value; };', + 'exports.D = function() { return d.D(); };', + ].join('\n')); + fs.writeFileSync(`${root}/a.js`, [ + 'const c = require("./b/c");', + 'let value = "A";', + 'exports.SomeClass = c.SomeClass;', + 'exports.A = function() { return value; };', + 'exports.C = function() { return c.C(); };', + 'exports.D = function() { return c.D(); };', + 'exports.number = 42;', + ].join('\n')); + + const require = createRequire(`${root}/entry.cjs`); + const withExtension = require(`${root}/a.js`); + const withoutExtension = require(`${root}/a`); + const c = require(`${root}/b/c`); + const d = require(`${root}/b/d`); + + assert.strictEqual(withExtension, withoutExtension); + assert.strictEqual(withExtension.number, 42); + assert.strictEqual(withExtension.A(), 'A'); + assert.strictEqual(withExtension.C(), 'C'); + assert.strictEqual(withExtension.D(), 'D'); + assert.ok(new withExtension.SomeClass() instanceof c.SomeClass); + assert.strictEqual(d.D(), 'D'); + + const aCacheKeys = Object.keys(require.cache).filter((key) => key === `${root}/a.js`); + assert.deepStrictEqual(aCacheKeys, [`${root}/a.js`]); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const testCjsModuleChildrenGraph = async () => { + try { + const { createRequire } = await import('node:module'); + const root = '/cjs-module-children-app'; + + fs.mkdirSync(`${root}/nested`, { recursive: true }); + fs.writeFileSync(`${root}/nested/grandchild.js`, 'exports.name = "grandchild";'); + fs.writeFileSync(`${root}/nested/child.js`, [ + 'exports.grandchild = require("./grandchild");', + 'exports.module = module;', + ].join('\n')); + fs.writeFileSync(`${root}/data.json`, JSON.stringify({ name: 'json' })); + fs.writeFileSync(`${root}/custom.test`, 'module.exports.name = "custom";'); + fs.writeFileSync(`${root}/module-require-target.js`, 'exports.name = "module-require-target";'); + fs.writeFileSync(`${root}/throws.js`, 'throw new Error("failed child");'); + fs.writeFileSync(`${root}/native.node`, 'not a native module'); + fs.writeFileSync(`${root}/entry.js`, [ + 'require.extensions[".test"] = function(mod, filename) {', + ' mod._compile(require("fs").readFileSync(filename, "utf8"), filename);', + '};', + 'exports.child = require("./nested/child");', + 'exports.childAgain = require("./nested/child");', + 'exports.json = require("./data.json");', + 'exports.custom = require("./custom.test");', + 'exports.moduleRequireTarget = module.require("./module-require-target");', + 'try { require("./throws"); } catch (err) { exports.throwCode = err.message; }', + 'try { require("./native.node"); } catch (err) { exports.nativeCode = err.code; }', + 'exports.module = module;', + ].join('\n')); + + const require = createRequire(`${root}/main.cjs`); + const entry = require(`${root}/entry.js`); + assert.strictEqual(entry.child, entry.childAgain); + assert.strictEqual(entry.child.grandchild.name, 'grandchild'); + assert.strictEqual(entry.json.name, 'json'); + assert.strictEqual(entry.custom.name, 'custom'); + assert.strictEqual(entry.moduleRequireTarget.name, 'module-require-target'); + assert.strictEqual(entry.throwCode, 'failed child'); + assert.strictEqual(entry.nativeCode, 'ERR_DLOPEN_FAILED'); + + const childIds = entry.module.children.map((child) => child.filename); + assert.deepStrictEqual(childIds, [ + `${root}/nested/child.js`, + `${root}/data.json`, + `${root}/custom.test`, + `${root}/module-require-target.js`, + ]); + assert.strictEqual(childIds.includes(`${root}/throws.js`), false); + assert.strictEqual(childIds.includes(`${root}/native.node`), false); + assert.strictEqual(childIds.filter((filename) => filename === `${root}/nested/child.js`).length, 1); + + const nestedChildIds = entry.child.module.children.map((child) => child.filename); + assert.deepStrictEqual(nestedChildIds, [`${root}/nested/grandchild.js`]); + + return true; + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/examples/runtime/module-resolution/wit/module-resolution.wit b/examples/runtime/module-resolution/wit/module-resolution.wit new file mode 100644 index 00000000..aff3fa4a --- /dev/null +++ b/examples/runtime/module-resolution/wit/module-resolution.wit @@ -0,0 +1,39 @@ +package quickjs:module-resolution; + +world module-resolution { + export test-esm-package-map-edge-cases: func() -> bool; + export test-esm-encoded-relative-paths: func() -> bool; + export test-esm-invalid-package-specifiers: func() -> bool; + export test-esm-data-url-import-attributes: func() -> bool; + export test-esm-json-url-cache-keys: func() -> bool; + export test-static-loader-absolute-entry-specifier: func() -> bool; + export test-registered-loader-module-realm-isolation: func() -> bool; + export test-esm-forbidden-cjs-globals: func() -> bool; + export test-import-meta-resolve: func() -> bool; + export test-cjs-dynamic-import-attribute-scanner: func() -> bool; + export test-loader-commonjs-source-named-exports: func() -> bool; + export test-loader-module-source-validation: func() -> bool; + export test-package-custom-conditions: func() -> bool; + export test-cjs-package-json-parse-cache: func() -> bool; + export test-sync-builtin-esm-exports: func() -> bool; + export test-esm-resolution-error-urls: func() -> bool; + export test-cjs-direct-named-exports: func() -> bool; + export test-esm-imports-side-effect-common-js: func() -> bool; + export test-cjs-define-property-named-exports: func() -> bool; + export test-cjs-reexport-named-exports: func() -> bool; + export test-cjs-analyzer-false-positive-guards: func() -> bool; + export test-cjs-shared-loader-identity: func() -> bool; + export test-module-syntax-detection-and-diagnostics: func() -> bool; + export test-cjs-package-reexport-named-exports: func() -> bool; + export test-find-package-json: func() -> bool; + export test-vm-main-context-default-loader: func() -> bool; + export test-vm-source-text-module-link-semantics: func() -> bool; + export test-require-esm-error-handling: func() -> bool; + export test-require-esm-tla-retry: func() -> bool; + export test-require-esm-rejection-tracking: func() -> bool; + export test-require-esm-cycle-guards: func() -> bool; + export test-cjs-symlink-circular-cache: func() -> bool; + export test-cjs-node-module-loading-compat: func() -> bool; + export test-cjs-nested-dependency-cache-shape: func() -> bool; + export test-cjs-module-children-graph: func() -> bool; +} diff --git a/examples/runtime/node-compat-runner/src/node-compat-runner.js b/examples/runtime/node-compat-runner/src/node-compat-runner.js index dee59b65..f80ce225 100644 --- a/examples/runtime/node-compat-runner/src/node-compat-runner.js +++ b/examples/runtime/node-compat-runner/src/node-compat-runner.js @@ -9,6 +9,8 @@ // // The test does require('../common') which resolves naturally to /home/node/test/common/index.js. +const require = globalThis.require; + // Drain pending microtasks/timers by yielding multiple times. // Many stream tests need several event loop turns to complete. // Uses increasing delays to handle both quick microtask chains and slower timers. @@ -43,13 +45,19 @@ function drainAsync() { // (set via rquickjs 0.10's set_host_promise_rejection_tracker) emits // process.emit('unhandledRejection', reason) which we listen for here. var _firstUnhandledRejection = null; +var _firstUnhandledRejectionHadTestListener = false; function installRejectionTracking() { _firstUnhandledRejection = null; + _firstUnhandledRejectionHadTestListener = false; function onUnhandledRejection(reason) { if (!_firstUnhandledRejection) { _firstUnhandledRejection = reason; + _firstUnhandledRejectionHadTestListener = + globalThis.process && + typeof globalThis.process.listenerCount === 'function' && + globalThis.process.listenerCount('unhandledRejection') > 1; } } @@ -62,8 +70,10 @@ function installRejectionTracking() { globalThis.process.removeListener('unhandledRejection', onUnhandledRejection); } var rejection = _firstUnhandledRejection; + var hadTestListener = _firstUnhandledRejectionHadTestListener; _firstUnhandledRejection = null; - return rejection; + _firstUnhandledRejectionHadTestListener = false; + return hadTestListener ? null : rejection; }; } @@ -89,6 +99,56 @@ function parseTestFlags(testPath) { return flags; } +function packageConditionsFromFlags(flags) { + var conditions = []; + function add(condition) { + if (condition) conditions.push(condition); + } + for (var i = 0; i < flags.length; i++) { + var flag = String(flags[i]); + if (flag.indexOf('--conditions=') === 0) { + add(flag.slice('--conditions='.length)); + } else if (flag === '--conditions' || flag === '-C') { + if (i + 1 < flags.length) { + add(String(flags[++i])); + } + } + } + return conditions; +} + +function experimentalLoadersFromFlags(flags) { + var loaders = []; + for (var i = 0; i < flags.length; i++) { + var flag = String(flags[i]); + if (flag.indexOf('--experimental-loader=') === 0) { + loaders.push(flag.slice('--experimental-loader='.length)); + } else if (flag.indexOf('--loader=') === 0) { + loaders.push(flag.slice('--loader='.length)); + } else if (flag === '--experimental-loader' || flag === '--loader') { + if (i + 1 < flags.length) { + loaders.push(String(flags[++i])); + } + } + } + return loaders; +} + +function preloadImportsFromFlags(flags) { + var imports = []; + for (var i = 0; i < flags.length; i++) { + var flag = String(flags[i]); + if (flag.indexOf('--import=') === 0) { + imports.push(flag.slice('--import='.length)); + } else if (flag === '--import') { + if (i + 1 < flags.length) { + imports.push(String(flags[++i])); + } + } + } + return imports; +} + function applyTestFlagsToProcess(testPath) { if (!globalThis.process) return; @@ -102,12 +162,120 @@ function applyTestFlagsToProcess(testPath) { for (var i = 0; i < flags.length; i++) { globalThis.process.execArgv.push(flags[i]); } + globalThis.__wasm_rquickjs_package_conditions = packageConditionsFromFlags(flags); + return flags; +} + +async function awaitRegisteredLoadersFrom(startIndex) { + if (typeof globalThis.__wasm_rquickjs_start_registered_loader !== 'function') return; + var registeredLoaders = globalThis.__wasm_rquickjs_registered_loaders || []; + for (var i = startIndex; i < registeredLoaders.length; i++) { + await globalThis.__wasm_rquickjs_start_registered_loader(registeredLoaders[i]); + } +} + +function resolvePreloadImport(specifier, cwd, cwdUrl) { + var value = String(specifier); + if (/^(?:data|file|node):/.test(value)) return value; + if (value[0] === '/' || value.indexOf('./') === 0 || value.indexOf('../') === 0) { + var pathBuiltin = require('node:path'); + var urlBuiltin = require('node:url'); + return urlBuiltin.pathToFileURL(pathBuiltin.resolve(cwd, value)).href; + } + if (typeof globalThis.__wasm_rquickjs_import_meta_resolve === 'function') { + return globalThis.__wasm_rquickjs_import_meta_resolve(cwdUrl, value); + } + return value; +} + +async function installPreloadImportsFromFlags(flags) { + var imports = preloadImportsFromFlags(flags || []); + if (imports.length === 0) return; + + var urlBuiltin = require('node:url'); + var cwd = globalThis.process && typeof globalThis.process.cwd === 'function' + ? globalThis.process.cwd() + : '/home/node'; + var cwdUrl = urlBuiltin.pathToFileURL(cwd.endsWith('/') ? cwd : cwd + '/').href; + + for (var i = 0; i < imports.length; i++) { + var loaderStartIndex = Array.isArray(globalThis.__wasm_rquickjs_registered_loaders) + ? globalThis.__wasm_rquickjs_registered_loaders.length + : 0; + await import(resolvePreloadImport(imports[i], cwd, cwdUrl)); + await awaitRegisteredLoadersFrom(loaderStartIndex); + } +} + +async function installExperimentalLoadersFromFlags(flags) { + var loaders = experimentalLoadersFromFlags(flags || []); + if (loaders.length === 0) return null; + + var previousLoaders = globalThis.__wasm_rquickjs_registered_loaders; + var previousLoaderSnapshot = Array.isArray(previousLoaders) ? previousLoaders.slice() : null; + var moduleBuiltin = await import('node:module'); + var urlBuiltin = require('node:url'); + var cwd = globalThis.process && typeof globalThis.process.cwd === 'function' + ? globalThis.process.cwd() + : '/home/node'; + var cwdUrl = urlBuiltin.pathToFileURL(cwd.endsWith('/') ? cwd : cwd + '/').href; + var loaderStartIndex = Array.isArray(globalThis.__wasm_rquickjs_registered_loaders) + ? globalThis.__wasm_rquickjs_registered_loaders.length + : 0; + + for (var i = 0; i < loaders.length; i++) { + moduleBuiltin.register(loaders[i], { parentURL: cwdUrl }); + } + await awaitRegisteredLoadersFrom(loaderStartIndex); + + return function restoreExperimentalLoaders() { + if (previousLoaders === undefined) { + delete globalThis.__wasm_rquickjs_registered_loaders; + } else if (previousLoaderSnapshot) { + previousLoaders.length = 0; + for (var i = 0; i < previousLoaderSnapshot.length; i++) { + previousLoaders.push(previousLoaderSnapshot[i]); + } + globalThis.__wasm_rquickjs_registered_loaders = previousLoaders; + } else { + globalThis.__wasm_rquickjs_registered_loaders = previousLoaders; + } + }; +} + +function withSuppressedModuleRequireDiagnostics(fn) { + if (typeof globalThis.__wasm_rquickjs_with_suppressed_module_require_diagnostics === 'function') { + return globalThis.__wasm_rquickjs_with_suppressed_module_require_diagnostics(fn); + } + return fn(); +} + +async function prepareStaticRegisteredLoaderGraph(testPath) { + if ( + !Array.isArray(globalThis.__wasm_rquickjs_registered_loaders) || + globalThis.__wasm_rquickjs_registered_loaders.length === 0 || + typeof globalThis.__wasm_rquickjs_prepare_static_registered_loader_graph !== 'function' + ) { + return; + } + globalThis.__wasm_rquickjs_static_registered_loader_cache = Object.create(null); + var urlBuiltin = require('node:url'); + await globalThis.__wasm_rquickjs_prepare_static_registered_loader_graph( + urlBuiltin.pathToFileURL(testPath).href, + testPath, + import.meta.url, + ); } export const runTest = async (testPath) => { var restorePromise = null; var restoreArgv = null; var restoreCwd = null; + var restoreLoaders = null; + var previousNodeTestEntryFile = globalThis.__wasm_rquickjs_node_test_entry_file; + var hadPackageConditions = Object.prototype.hasOwnProperty.call(globalThis, '__wasm_rquickjs_package_conditions'); + var previousPackageConditions = globalThis.__wasm_rquickjs_package_conditions; + globalThis.__wasm_rquickjs_node_test_entry_file = testPath; if (globalThis.process) { var originalArgv = Array.isArray(globalThis.process.argv) ? globalThis.process.argv.slice() : null; @@ -139,13 +307,17 @@ export const runTest = async (testPath) => { } try { - applyTestFlagsToProcess(testPath); + var flags = applyTestFlagsToProcess(testPath) || []; + restoreLoaders = await installExperimentalLoadersFromFlags(flags); + await installPreloadImportsFromFlags(flags); // Reset mustCall tracking for this test var commonMod; try { - commonMod = require('node:module') - .createRequire('/home/node/test/common/index.js')('/home/node/test/common/index.js'); + commonMod = withSuppressedModuleRequireDiagnostics(function() { + return require('node:module') + .createRequire('/home/node/test/common/index.js')('/home/node/test/common/index.js'); + }); } catch(e) {} if (commonMod && typeof commonMod._resetMustCalls === 'function') { commonMod._resetMustCalls(); @@ -154,6 +326,7 @@ export const runTest = async (testPath) => { restorePromise = installRejectionTracking(); if (testPath.endsWith('.mjs')) { + await prepareStaticRegisteredLoaderGraph(testPath); await import(testPath); } else { // Use createRequire('/') so the test module gets parent: null, @@ -163,7 +336,9 @@ export const runTest = async (testPath) => { testRequire(testPath); } // Await any pending async tests from node:test - var testModule = require('node:test'); + var testModule = withSuppressedModuleRequireDiagnostics(function() { + return require('node:test'); + }); if (testModule && typeof testModule._awaitPendingTests === 'function') { await testModule._awaitPendingTests(); } @@ -180,8 +355,10 @@ export const runTest = async (testPath) => { // Verify mustCall expectations first var common; try { - common = require('node:module') - .createRequire('/home/node/test/common/index.js')('/home/node/test/common/index.js'); + common = withSuppressedModuleRequireDiagnostics(function() { + return require('node:module') + .createRequire('/home/node/test/common/index.js')('/home/node/test/common/index.js'); + }); } catch(e) {} var mustCallErrors = []; if (common && typeof common._checkMustCalls === 'function') { @@ -220,6 +397,19 @@ export const runTest = async (testPath) => { var fullMsg = (e && e.message) ? (e.message + "\n" + msg) : msg; return "FAIL: " + fullMsg; } finally { + if (restoreLoaders) { + restoreLoaders(); + } + if (previousNodeTestEntryFile === undefined) { + delete globalThis.__wasm_rquickjs_node_test_entry_file; + } else { + globalThis.__wasm_rquickjs_node_test_entry_file = previousNodeTestEntryFile; + } + if (hadPackageConditions) { + globalThis.__wasm_rquickjs_package_conditions = previousPackageConditions; + } else { + delete globalThis.__wasm_rquickjs_package_conditions; + } if (restoreCwd) { restoreCwd(); } diff --git a/examples/runtime/node-modules-app-runner/src/node-modules-app-runner.js b/examples/runtime/node-modules-app-runner/src/node-modules-app-runner.js new file mode 100644 index 00000000..87ee7453 --- /dev/null +++ b/examples/runtime/node-modules-app-runner/src/node-modules-app-runner.js @@ -0,0 +1,20 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +function getRunFunction(module) { + if (module && typeof module.run === 'function') return module.run; + if (module && module.default && typeof module.default.run === 'function') return module.default.run; + throw new Error('Node modules app test module must export run()'); +} + +export const runTest = async (testPath) => { + const module = testPath.endsWith('.cjs') + ? createRequire(testPath)(testPath) + : await import(pathToFileURL(testPath).href); + + const result = await getRunFunction(module)(); + if (typeof result !== 'string' || !result.startsWith('PASS:')) { + throw new Error(`Unexpected node modules app test result: ${result}`); + } + return result; +}; diff --git a/examples/runtime/node-modules-app-runner/wit/node-modules-app-runner.wit b/examples/runtime/node-modules-app-runner/wit/node-modules-app-runner.wit new file mode 100644 index 00000000..40885ec0 --- /dev/null +++ b/examples/runtime/node-modules-app-runner/wit/node-modules-app-runner.wit @@ -0,0 +1,5 @@ +package quickjs:node-modules-app-runner; + +world node-modules-app-runner { + export run-test: func(test-path: string) -> string; +} diff --git a/examples/runtime/source-map/src/source-map.js b/examples/runtime/source-map/src/source-map.js new file mode 100644 index 00000000..6be106c1 --- /dev/null +++ b/examples/runtime/source-map/src/source-map.js @@ -0,0 +1,86 @@ +import fs from 'node:fs'; +import module from 'node:module'; + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function writeJson(path, value) { + fs.writeFileSync(path, JSON.stringify(value)); +} + +export function testSourceMapApi() { + const originalExecArgv = process.execArgv.slice(); + try { + process.execArgv = originalExecArgv.concat('--enable-source-maps'); + + const previousLine = new module.SourceMap({ + sources: ['previous.js'], + names: [], + mappings: 'AAAA;', + }); + assert(previousLine.findEntry(1, 0).generatedLine === 0, 'findEntry previous line'); + + const withTrailingNewline = '/source-map-line-lengths.cjs'; + const lineLengthSource = 'module.exports = 1;\n//# sourceMappingURL=line-lengths.map\n'; + fs.writeFileSync(withTrailingNewline, lineLengthSource); + writeJson('/line-lengths.map', { + version: 3, + sources: ['line-lengths-source.js'], + names: [], + mappings: 'AAAA', + }); + require(withTrailingNewline); + const expectedLineLengths = lineLengthSource.split('\n').map(line => line.length).join(','); + assert(module.findSourceMap(withTrailingNewline).lineLengths.join(',') === expectedLineLengths, 'line lengths'); + + const rawOffsets = '/source-map-raw-offsets.cjs'; + fs.writeFileSync(rawOffsets, '\n\n\n\n\n\n\nmodule.exports = 1;\n//# sourceMappingURL=raw-offsets.map\n'); + writeJson('/raw-offsets.map', { + version: 3, + sources: ['raw-offset-source.js'], + names: [], + mappings: ';;;;;;;AAAA', + }); + require(rawOffsets); + const rawEntry = module.findSourceMap(rawOffsets).findEntry(7, 0); + assert(rawEntry.generatedLine === 7, 'public findEntry uses raw generated line'); + const rawColumnEntry = module.findSourceMap(rawOffsets).findEntry(7, 7); + assert(rawColumnEntry.generatedLine === 7, 'public findEntry uses raw generated line with column offset'); + const rawOrigin = module.findSourceMap(rawOffsets).findOrigin(8, 8); + assert(rawOrigin.lineNumber === 1, 'public findOrigin uses raw generated position'); + + const absoluteSource = module.findSourceMap(rawOffsets).findEntry(7, 0).originalSource; + assert(absoluteSource.startsWith('file://') && absoluteSource.endsWith('/raw-offset-source.js'), 'absolute source URL'); + + const blockDirective = '/source-map-block-directive.cjs'; + fs.writeFileSync(blockDirective, [ + 'module.exports = 1;', + '//# sourceMappingURL=wrong-map.json', + '/*# sourceMappingURL=right-map.json */', + ].join('\n')); + writeJson('/wrong-map.json', { + version: 3, + sources: ['wrong.js'], + names: [], + mappings: 'AAAA', + }); + writeJson('/right-map.json', { + version: 3, + sources: ['right.js'], + names: [], + mappings: 'AAAA', + }); + require(blockDirective); + assert(module.findSourceMap(blockDirective).findEntry(0, 0).originalSource.endsWith('/right.js'), 'last block directive wins'); + + return true; + } catch (e) { + console.log(e && e.stack ? e.stack : String(e)); + return false; + } finally { + process.execArgv = originalExecArgv; + } +} diff --git a/examples/runtime/source-map/wit/source-map.wit b/examples/runtime/source-map/wit/source-map.wit new file mode 100644 index 00000000..49ac1710 --- /dev/null +++ b/examples/runtime/source-map/wit/source-map.wit @@ -0,0 +1,5 @@ +package quickjs:source-map; + +world source-map { + export test-source-map-api: func() -> bool; +} diff --git a/tests/common/js_subtest_parser.rs b/tests/common/js_subtest_parser.rs index c48d6f6c..036c5f5d 100644 --- a/tests/common/js_subtest_parser.rs +++ b/tests/common/js_subtest_parser.rs @@ -1,7 +1,7 @@ use oxc_allocator::Allocator; use oxc_ast::ast::*; use oxc_parser::Parser; -use oxc_span::SourceType; +use oxc_span::{GetSpan, SourceType}; /// Info about a top-level `{ }` block. #[derive(Debug, Clone)] @@ -146,16 +146,90 @@ fn is_require_node_test(expr: &Expression) -> bool { false } -/// Check if an expression is a `test(...)` or `suite(...)` call. +fn call_name<'a>(call: &'a CallExpression<'a>) -> Option<&'a str> { + if let Expression::Identifier(id) = &call.callee { + Some(id.name.as_str()) + } else { + None + } +} + +fn is_test_call_name(name: &str) -> bool { + name == "test" || name == "it" +} + +fn is_suite_call_name(name: &str) -> bool { + name == "suite" || name == "describe" +} + +/// Check if an expression is a discoverable node:test call. fn is_test_or_suite_call(expr: &Expression) -> bool { if let Expression::CallExpression(call) = expr - && let Expression::Identifier(id) = &call.callee + && let Some(name) = call_name(call) { - return id.name == "test" || id.name == "suite" || id.name == "describe"; + return is_test_call_name(name) || is_suite_call_name(name); } false } +fn extract_callback_body<'a>(call: &'a CallExpression<'a>) -> Option<&'a FunctionBody<'a>> { + for arg in call.arguments.iter().rev() { + match arg { + Argument::FunctionExpression(function) => { + if let Some(body) = function.body.as_ref() { + return Some(body); + } + } + Argument::ArrowFunctionExpression(arrow) => { + if !arrow.expression { + return Some(&arrow.body); + } + } + _ => {} + } + } + None +} + +fn build_test_info_from_call(index: usize, call: &CallExpression, span: (u32, u32)) -> TestInfo { + let name = extract_test_name(call) + .map(|n| sanitize_name(&n)) + .unwrap_or_else(|| format!("test_{index:02}")); + let full_name = format!("test_{index:02}_{name}"); + TestInfo { + index, + span, + name: full_name, + } +} + +fn discover_top_level_suite_nested_tests( + call: &CallExpression, + index: &mut usize, +) -> Vec { + let Some(body) = extract_callback_body(call) else { + return Vec::new(); + }; + + let mut tests = Vec::new(); + for stmt in &body.statements { + if let Statement::ExpressionStatement(expr_stmt) = stmt + && let Expression::CallExpression(nested_call) = &expr_stmt.expression + && let Some(name) = call_name(nested_call) + && is_test_call_name(name) + { + tests.push(build_test_info_from_call( + *index, + nested_call, + (expr_stmt.span.start, expr_stmt.span.end), + )); + *index += 1; + } + } + + tests +} + /// Extract test name from a test() call's first argument. fn extract_test_name(call: &CallExpression) -> Option { if let Some(arg) = call.arguments.first() { @@ -182,6 +256,14 @@ fn extract_test_name(call: &CallExpression) -> Option { /// - `path`: file path (used to determine SourceType: .js → CJS, .mjs → ESM) /// - `source`: the JS source code pub fn discover_subtests(path: &str, source: &str) -> SubtestDiscovery { + discover_subtests_with_options(path, source, false) +} + +pub fn discover_subtests_with_options( + path: &str, + source: &str, + nested_node_test: bool, +) -> SubtestDiscovery { let source_type = if path.ends_with(".mjs") { SourceType::mjs() } else { @@ -202,16 +284,29 @@ pub fn discover_subtests(path: &str, source: &str) -> SubtestDiscovery { && let Expression::CallExpression(call) = &expr_stmt.expression && is_test_or_suite_call(&expr_stmt.expression) { - let name = extract_test_name(call) - .map(|n| sanitize_name(&n)) - .unwrap_or_else(|| format!("test_{index:02}")); - let full_name = format!("test_{index:02}_{name}"); - tests.push(TestInfo { - index, - span: (expr_stmt.span.start, expr_stmt.span.end), - name: full_name, - }); - index += 1; + if nested_node_test + && let Some(name) = call_name(call) + && is_suite_call_name(name) + { + let nested_tests = discover_top_level_suite_nested_tests(call, &mut index); + if nested_tests.is_empty() { + tests.push(build_test_info_from_call( + index, + call, + (expr_stmt.span.start, expr_stmt.span.end), + )); + index += 1; + } else { + tests.extend(nested_tests); + } + } else { + tests.push(build_test_info_from_call( + index, + call, + (expr_stmt.span.start, expr_stmt.span.end), + )); + index += 1; + } } } @@ -253,9 +348,32 @@ pub fn discover_subtests(path: &str, source: &str) -> SubtestDiscovery { /// blocks. Top-level non-block code is always preserved. /// Processes in reverse order to preserve byte offsets. pub fn rewrite_for_block(source: &str, blocks: &[BlockInfo], target_index: usize) -> String { + rewrite_for_block_with_options(source, blocks, target_index, false) +} + +/// Rewrite source to run only `target_index`. +/// +/// With `isolate_top_level_expressions`, non-declaration top-level executable +/// statements are also removed. This is useful for legacy block-split fixtures +/// that contain top-level IIFEs before the discoverable blocks. +pub fn rewrite_for_block_with_options( + source: &str, + blocks: &[BlockInfo], + target_index: usize, + isolate_top_level_expressions: bool, +) -> String { let bytes = source.as_bytes(); - let mut result = bytes.to_vec(); - for block in blocks.iter().rev() { + let mut edits: Vec<(usize, usize)> = Vec::new(); + + if isolate_top_level_expressions { + edits.extend(top_level_executable_statement_spans( + source, + blocks, + target_index, + )); + } + + for block in blocks { if block.index != target_index { let start = block.span.0 as usize; let end = block.span.1 as usize; @@ -270,18 +388,88 @@ pub fn rewrite_for_block(source: &str, blocks: &[BlockInfo], target_index: usize let inner_start = start + 1; // after '{' let inner_end = end - 1; // before '}' if inner_start < inner_end { - result.splice(inner_start..inner_end, std::iter::once(b' ')); + edits.push((inner_start, inner_end)); } } } + + edits.sort_by_key(|(start, _)| *start); + let mut result = bytes.to_vec(); + for (start, end) in edits.into_iter().rev() { + if start < end && end <= result.len() { + result.splice(start..end, std::iter::once(b' ')); + } + } String::from_utf8(result).expect("UTF-8 source remained valid after block rewrite") } -/// Rewrite source to filter to a single node:test test by index. +fn top_level_executable_statement_spans( + source: &str, + blocks: &[BlockInfo], + target_index: usize, +) -> Vec<(usize, usize)> { + let source_type = SourceType::cjs(); + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source, source_type).parse(); + let program = &ret.program; + let target_span = blocks + .iter() + .find(|block| block.index == target_index) + .map(|block| block.span); + let mut spans = Vec::new(); + + for stmt in &program.body { + if let Statement::BlockStatement(block) = stmt { + if Some((block.span.start, block.span.end)) == target_span { + continue; + } + continue; + } + if preserve_top_level_statement(stmt) { + continue; + } + let span = stmt.span(); + spans.push((span.start as usize, span.end as usize)); + } + + spans +} + +fn preserve_top_level_statement(stmt: &Statement) -> bool { + match stmt { + Statement::ImportDeclaration(_) + | Statement::VariableDeclaration(_) + | Statement::FunctionDeclaration(_) + | Statement::ClassDeclaration(_) => true, + Statement::ExpressionStatement(expr_stmt) => { + matches!(&expr_stmt.expression, Expression::StringLiteral(_)) + } + _ => false, + } +} + +/// Rewrite source to keep only the targeted discovered node:test call. /// -/// Uses `globalThis.__wasm_rquickjs_node_test_filter` which the node:test -/// polyfill reads on initialization. This works for both CJS and ESM files -/// and doesn't break `'use strict'` directive prologue. -pub fn rewrite_for_node_test(source: &str, target_index: usize) -> String { - format!("globalThis.__wasm_rquickjs_node_test_filter = {target_index};\n{source}") +/// Non-target discovered calls are blanked by span in reverse order to +/// preserve offsets while keeping unrelated top-level code intact. +pub fn rewrite_for_node_test(source: &str, tests: &[TestInfo], target_index: usize) -> String { + let bytes = source.as_bytes(); + let mut result = bytes.to_vec(); + + for test in tests.iter().rev() { + if test.index != target_index { + let start = test.span.0 as usize; + let end = test.span.1 as usize; + if start >= bytes.len() || end > bytes.len() || start >= end { + eprintln!( + "WARNING: node:test span ({},{}) is invalid, skipping rewrite", + start, end + ); + continue; + } + result.splice(start..end, std::iter::once(b' ')); + } + } + + String::from_utf8(result).expect("UTF-8 source remained valid after node:test rewrite") } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 80c7016a..da0d52c1 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -145,11 +145,69 @@ pub struct NodeCompatTestEntry { pub category: NodeCompatCategory, pub reason: Option, pub split: bool, + pub nested_node_test: bool, + pub isolate_block_subtests: bool, pub timeout_secs: u64, pub flaky: bool, pub subtests: Vec, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum NodeModulesAppCategory { + Runnable, + KnownGap, + Deferred, +} + +impl NodeModulesAppCategory { + pub fn from_config_value(value: &str) -> anyhow::Result { + match value { + "runnable" => Ok(Self::Runnable), + "known-gap" | "gap" => Ok(Self::KnownGap), + "deferred" => Ok(Self::Deferred), + other => anyhow::bail!("unknown node_modules_apps category '{other}'"), + } + } + + pub fn label(self) -> &'static str { + match self { + Self::Runnable => "runnable", + Self::KnownGap => "known gap", + Self::Deferred => "deferred", + } + } + + pub fn status_label(self) -> &'static str { + match self { + Self::Runnable => "Passing", + Self::KnownGap => "Known gap", + Self::Deferred => "Deferred", + } + } + + pub fn should_ignore_in_runner(self) -> bool { + !matches!(self, Self::Runnable) + } +} + +#[derive(Debug, Clone)] +pub struct NodeModulesAppTestEntry { + pub file: String, + pub category: NodeModulesAppCategory, + pub coverage: String, + pub reason: Option, + pub timeout_secs: u64, + pub flaky: bool, +} + +#[derive(Debug, Clone)] +pub struct NodeModulesAppEntry { + pub name: String, + pub category: NodeModulesAppCategory, + pub reason: Option, + pub tests: Vec, +} + /// Extract the numeric index from a subtest name like "block_00_foo" or "test_03_bar". /// Panics if the name doesn't match the expected format (config is authoritative). pub fn extract_node_compat_subtest_index(name: &str) -> usize { @@ -233,6 +291,14 @@ pub fn load_node_compat_config(path: &str) -> anyhow::Result anyhow::Result anyhow::Result anyhow::Result> { + let content = fs::read_to_string(path)?; + let json_str = strip_jsonc_comments(&content); + let value: serde_json::Value = serde_json::from_str(&json_str)?; + + let apps_obj = value + .get("apps") + .and_then(|v| v.as_object()) + .ok_or_else(|| anyhow::anyhow!("node_modules_apps config missing 'apps' object"))?; + + let mut apps = Vec::new(); + for (app_name, opts) in apps_obj { + let category = node_modules_app_category_from_value(opts, None)?; + let reason = opts + .get("reason") + .and_then(|v| v.as_str()) + .map(str::to_string); + let default_timeout_secs = opts + .get("timeout") + .and_then(|v| v.as_u64()) + .unwrap_or(DEFAULT_NODE_COMPAT_TEST_TIMEOUT_SECS); + let tests_obj = opts + .get("tests") + .and_then(|v| v.as_object()) + .ok_or_else(|| { + anyhow::anyhow!("node_modules app '{app_name}' missing 'tests' object") + })?; + + let mut tests = Vec::new(); + for (test_file, test_opts) in tests_obj { + let test_category = node_modules_app_category_from_value(test_opts, Some(category))?; + let (coverage, test_reason, timeout_secs, flaky) = match test_opts { + serde_json::Value::String(coverage) => ( + coverage.clone(), + reason.clone(), + default_timeout_secs, + false, + ), + serde_json::Value::Object(_) => { + let coverage = test_opts + .get("coverage") + .or_else(|| test_opts.get("description")) + .and_then(|v| v.as_str()) + .ok_or_else(|| { + anyhow::anyhow!( + "node_modules app '{app_name}' test '{test_file}' missing coverage" + ) + })? + .to_string(); + let test_reason = test_opts + .get("reason") + .and_then(|v| v.as_str()) + .map(str::to_string) + .or_else(|| reason.clone()); + let timeout_secs = test_opts + .get("timeout") + .and_then(|v| v.as_u64()) + .unwrap_or(default_timeout_secs); + let flaky = test_opts + .get("flaky") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + (coverage, test_reason, timeout_secs, flaky) + } + _ => anyhow::bail!( + "node_modules app '{app_name}' test '{test_file}' must be a coverage string or object" + ), + }; + + tests.push(NodeModulesAppTestEntry { + file: test_file.clone(), + category: test_category, + coverage, + reason: test_reason, + timeout_secs, + flaky, + }); + } + tests.sort_by(|a, b| a.file.cmp(&b.file)); + + apps.push(NodeModulesAppEntry { + name: app_name.clone(), + category, + reason, + tests, + }); + } + apps.sort_by(|a, b| a.name.cmp(&b.name)); + + Ok(apps) +} + +fn node_modules_app_category_from_value( + value: &serde_json::Value, + inherited: Option, +) -> anyhow::Result { + if let Some(category) = value.get("category").and_then(|v| v.as_str()) { + return NodeModulesAppCategory::from_config_value(category); + } + if value.get("skip").and_then(|v| v.as_bool()).unwrap_or(false) { + return Ok(NodeModulesAppCategory::KnownGap); + } + Ok(inherited.unwrap_or(NodeModulesAppCategory::Runnable)) +} + /// Recursively copy a directory and all its contents to a destination. pub fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> anyhow::Result<()> { fs::create_dir_all(dst)?; @@ -319,6 +492,24 @@ pub fn setup_node_compat_test_files(temp: &Utf8Path, test_rel_path: &str) -> any let dst_test = suite_dir.join(test_filename); fs::copy(&src_test, &dst_test)?; + // Some vendored ESM tests import sibling test files with relative specifiers. + // The split runner still executes one configured test at a time, but those + // relative imports need the original suite directory shape. + let src_suite_dir = std::path::Path::new("tests/node_compat/suite").join(suite); + if suite == "es-module" && src_suite_dir.exists() { + for entry in fs::read_dir(&src_suite_dir)? { + let entry = entry?; + if entry.file_type()?.is_file() { + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy(); + let dst = suite_dir.join(file_name_str.as_ref()); + if !dst.exists() { + fs::copy(entry.path(), dst)?; + } + } + } + } + // Copy the common shim let src_shim = "tests/node_compat/common-shim/index.js"; let dst_shim = common_dir.join("index.js"); @@ -347,6 +538,22 @@ pub fn setup_node_compat_test_files(temp: &Utf8Path, test_rel_path: &str) -> any } } + // Copy vendored ESM common helpers that are not replaced by local shims. + let vendored_common_dir = std::path::Path::new("tests/node_compat/suite/common"); + if vendored_common_dir.exists() { + for entry in fs::read_dir(vendored_common_dir)? { + let entry = entry?; + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy(); + if entry.file_type()?.is_file() + && file_name_str.ends_with(".mjs") + && !common_dir.join(file_name_str.as_ref()).exists() + { + fs::copy(entry.path(), common_dir.join(file_name_str.as_ref()))?; + } + } + } + // Create /tmp directory for tmpdir shim let tmp_dir = temp.join("tmp"); fs::create_dir_all(&tmp_dir)?; @@ -366,6 +573,10 @@ pub fn setup_node_compat_test_files(temp: &Utf8Path, test_rel_path: &str) -> any copy_dir_recursive(fixtures_src, fixtures_dst.as_std_path())?; } + if test_rel_path == "sequential/test-module-loading.js" && vendored_fixtures_src.exists() { + copy_dir_recursive(vendored_fixtures_src, fixtures_dst.as_std_path())?; + } + Ok(()) } diff --git a/tests/goldenfiles/generated_types_cjs-require_exports.d.ts b/tests/goldenfiles/generated_types_cjs-require_exports.d.ts index 5b178879..8d188d65 100644 --- a/tests/goldenfiles/generated_types_cjs-require_exports.d.ts +++ b/tests/goldenfiles/generated_types_cjs-require_exports.d.ts @@ -8,4 +8,7 @@ declare module 'cjs-require' { export function testRequireJson(): Promise; export function testRequireModuleExportsFunction(): Promise; export function testRequireModuleNotFound(): Promise; + export function testRequirePackageExports(): Promise; + export function testRequirePackageImports(): Promise; + export function testRequirePackageMapEdgeCases(): Promise; } diff --git a/tests/goldenfiles/generated_types_module-resolution_exports.d.ts b/tests/goldenfiles/generated_types_module-resolution_exports.d.ts new file mode 100644 index 00000000..4384ee86 --- /dev/null +++ b/tests/goldenfiles/generated_types_module-resolution_exports.d.ts @@ -0,0 +1,18 @@ +declare module 'module-resolution' { + export function testEsmPackageMapEdgeCases(): Promise; + export function testCjsDirectNamedExports(): Promise; + export function testCjsDefinePropertyNamedExports(): Promise; + export function testCjsReexportNamedExports(): Promise; + export function testCjsAnalyzerFalsePositiveGuards(): Promise; + export function testCjsSharedLoaderIdentity(): Promise; + export function testModuleSyntaxDetectionAndDiagnostics(): Promise; + export function testCjsPackageReexportNamedExports(): Promise; + export function testFindPackageJson(): Promise; + export function testRequireEsmErrorHandling(): Promise; + export function testRequireEsmTlaRetry(): Promise; + export function testRequireEsmCycleGuards(): Promise; + export function testCjsSymlinkCircularCache(): Promise; + export function testCjsNodeModuleLoadingCompat(): Promise; + export function testCjsNestedDependencyCacheShape(): Promise; + export function testCjsModuleChildrenGraph(): Promise; +} diff --git a/tests/goldenfiles/generated_types_node-modules-app-runner_exports.d.ts b/tests/goldenfiles/generated_types_node-modules-app-runner_exports.d.ts new file mode 100644 index 00000000..091dad2a --- /dev/null +++ b/tests/goldenfiles/generated_types_node-modules-app-runner_exports.d.ts @@ -0,0 +1,3 @@ +declare module 'node-modules-app-runner' { + export function runTest(testPath: string): Promise; +} diff --git a/tests/js_subtest_parser.rs b/tests/js_subtest_parser.rs index f803988a..2ee30305 100644 --- a/tests/js_subtest_parser.rs +++ b/tests/js_subtest_parser.rs @@ -5,8 +5,8 @@ test_r::enable!(); mod common; use crate::common::js_subtest_parser::{ - BlockInfo, SubtestDiscovery, discover_subtests, rewrite_for_block, rewrite_for_node_test, - sanitize_name, + BlockInfo, SubtestDiscovery, discover_subtests, discover_subtests_with_options, + rewrite_for_block, rewrite_for_block_with_options, rewrite_for_node_test, sanitize_name, }; use test_r::test; @@ -102,12 +102,33 @@ fn test_rewrite_for_block() { assert!(!result.contains("assert(3)")); } +#[test] +fn test_rewrite_for_block_isolates_top_level_expressions() { + let source = "'use strict';\nconst common = require('../common');\n(async () => { assert(1); })();\n{ assert(2); }\n{ assert(3); }\n"; + let blocks = match discover_subtests("test.js", source) { + SubtestDiscovery::Block(blocks) => blocks, + other => panic!("Expected block discovery, got {:?}", other), + }; + + let result = rewrite_for_block_with_options(source, &blocks, 1, true); + assert!(result.contains("'use strict'")); + assert!(result.contains("const common")); + assert!(!result.contains("assert(1)")); + assert!(!result.contains("assert(2)")); + assert!(result.contains("assert(3)")); +} + #[test] fn test_rewrite_for_node_test() { - let source = "test('a', () => {});\ntest('b', () => {});"; - let result = rewrite_for_node_test(source, 0); - assert!(result.starts_with("globalThis.__wasm_rquickjs_node_test_filter = 0;")); - assert!(result.contains(source)); + let source = + "const { test } = require('node:test');\ntest('a', () => {});\ntest('b', () => {});\n"; + let tests = match discover_subtests("test.js", source) { + SubtestDiscovery::NodeTest(tests) => tests, + other => panic!("Expected NodeTest discovery, got {:?}", other), + }; + let result = rewrite_for_node_test(source, &tests, 1); + assert!(!result.contains("test('a', () => {});")); + assert!(result.contains("test('b', () => {});")); } #[test] @@ -159,6 +180,44 @@ test('another standalone', () => {}); } } +#[test] +fn test_describe_it_nested_discovery() { + let source = r#" +'use strict'; +const { describe, it } = require('node:test'); + +describe('findPackageJSON', () => { + it('first same-process case', () => {}); + it('second same-process case', () => {}); +}); +"#; + match discover_subtests_with_options("test.js", source, true) { + SubtestDiscovery::NodeTest(tests) => { + assert_eq!(tests.len(), 2); + assert_eq!(tests[0].name, "test_00_first_same_process_case"); + assert_eq!(tests[1].name, "test_01_second_same_process_case"); + } + other => panic!("Expected NodeTest discovery, got {:?}", other), + } +} + +#[test] +fn test_describe_it_default_discovers_suite_only() { + let source = r#" +'use strict'; +const { describe, it } = require('node:test'); + +describe('findPackageJSON', () => { + it('first same-process case', () => {}); + it('second same-process case', () => {}); +}); +"#; + match discover_subtests("test.js", source) { + SubtestDiscovery::None => {} + other => panic!("Expected no split for one top-level suite, got {:?}", other), + } +} + #[test] fn test_no_split_for_single_block() { let source = "'use strict';\n{ assert(1); }"; diff --git a/tests/node_compat.rs b/tests/node_compat.rs index 5ddb5877..bfba9147 100644 --- a/tests/node_compat.rs +++ b/tests/node_compat.rs @@ -1,7 +1,8 @@ test_r::enable!(); use crate::common::js_subtest_parser::{ - BlockInfo, SubtestDiscovery, discover_subtests, rewrite_for_block, rewrite_for_node_test, + BlockInfo, SubtestDiscovery, TestInfo, discover_subtests_with_options, + rewrite_for_block_with_options, rewrite_for_node_test, }; use crate::common::{ CompiledTest, GolemPreparedComponent, TestInstance, load_node_compat_config, @@ -54,13 +55,398 @@ fn prepare_node_compat_full( ))) } +#[test_r::test] +async fn runner_import_preload_flag(prepared: &Arc) -> anyhow::Result<()> { + let mut instance = TestInstance::from_golem_prepared(&prepared.0).await?; + instance.set_epoch_deadline(30); + + let suite_dir = instance + .temp_dir_path() + .join("home") + .join("node") + .join("test") + .join("es-module"); + fs::create_dir_all(&suite_dir)?; + fs::write( + suite_dir.join("preload-smoke-preload.mjs"), + "globalThis.__nodeCompatPreloadValue = 41;\n", + )?; + fs::write( + suite_dir.join("preload-smoke.mjs"), + [ + "// Flags: --import ./test/es-module/preload-smoke-preload.mjs", + "if (globalThis.__nodeCompatPreloadValue !== 41) {", + " throw new Error('preload did not run before entry');", + "}", + ] + .join("\n"), + )?; + + let (result, stdout, stderr) = instance + .invoke_and_capture_output_with_stderr( + None, + "run-test", + &[Val::String( + "/home/node/test/es-module/preload-smoke.mjs".to_string(), + )], + ) + .await; + + handle_test_result(result, &stdout, &stderr) +} + +#[test_r::test] +async fn runner_dynamic_import_cache_survives_removed_file( + prepared: &Arc, +) -> anyhow::Result<()> { + let mut instance = TestInstance::from_golem_prepared(&prepared.0).await?; + instance.set_epoch_deadline(30); + + let suite_dir = instance + .temp_dir_path() + .join("home") + .join("node") + .join("test") + .join("es-module"); + fs::create_dir_all(&suite_dir)?; + fs::write( + suite_dir.join("dynamic-import-cache-entry.mjs"), + [ + "import assert from 'node:assert';", + "import fs from 'node:fs/promises';", + "const target = new URL('./dynamic-import-cache-target.mjs', import.meta.url);", + "await assert.rejects(import(target), { code: 'ERR_MODULE_NOT_FOUND' });", + "await fs.writeFile(target, 'export default \"actual target\"\\n');", + "const moduleRecord = await import(target);", + "await fs.rm(target);", + "assert.strictEqual(await import(target), moduleRecord);", + ] + .join("\n"), + )?; + + let (result, stdout, stderr) = instance + .invoke_and_capture_output_with_stderr( + None, + "run-test", + &[Val::String( + "/home/node/test/es-module/dynamic-import-cache-entry.mjs".to_string(), + )], + ) + .await; + + handle_test_result(result, &stdout, &stderr) +} + +#[test_r::test] +async fn runner_static_registered_loader_async_resolve( + prepared: &Arc, +) -> anyhow::Result<()> { + let mut instance = TestInstance::from_golem_prepared(&prepared.0).await?; + instance.set_epoch_deadline(30); + + let suite_dir = instance + .temp_dir_path() + .join("home") + .join("node") + .join("test") + .join("es-module"); + fs::create_dir_all(&suite_dir)?; + fs::write( + suite_dir.join("async-static-loader.mjs"), + [ + "export async function resolve(specifier, context, nextResolve) {", + " if (specifier === './dep.mjs') {", + " return nextResolve('./real.mjs', context);", + " }", + " if (specifier === './generated.mjs') {", + " return { shortCircuit: true, url: 'virtual:generated', format: 'module' };", + " }", + " if (specifier === 'virtual:child') {", + " return { shortCircuit: true, url: new URL('./child.mjs', import.meta.url).href, format: 'module' };", + " }", + " return nextResolve(specifier, context);", + "}", + "export async function load(url, context, nextLoad) {", + " if (url === 'virtual:generated') {", + " return { shortCircuit: true, format: 'module', source: 'import value from \"virtual:child\"; export default value;' };", + " }", + " return nextLoad(url, context);", + "}", + ] + .join("\n"), + )?; + fs::write( + suite_dir.join("async-static-entry.mjs"), + [ + "// Flags: --experimental-loader ./test/es-module/async-static-loader.mjs", + "import value from './dep.mjs';", + "import generated from './generated.mjs';", + "if (value !== 42) throw new Error('static async loader resolve did not run');", + "if (generated !== 7) throw new Error('loader source child import was not prepared');", + ] + .join("\n"), + )?; + fs::write(suite_dir.join("real.mjs"), "export default 42;\n")?; + fs::write(suite_dir.join("child.mjs"), "export default 7;\n")?; + + let (result, stdout, stderr) = instance + .invoke_and_capture_output_with_stderr( + None, + "run-test", + &[Val::String( + "/home/node/test/es-module/async-static-entry.mjs".to_string(), + )], + ) + .await; + + handle_test_result(result, &stdout, &stderr) +} + +#[test_r::test] +async fn runner_programmatic_registered_loader_chain( + prepared: &Arc, +) -> anyhow::Result<()> { + let mut instance = TestInstance::from_golem_prepared(&prepared.0).await?; + instance.set_epoch_deadline(30); + + let suite_dir = instance + .temp_dir_path() + .join("home") + .join("node") + .join("test") + .join("es-module"); + fs::create_dir_all(&suite_dir)?; + fs::write( + suite_dir.join("register-chain-loader-a.mjs"), + [ + "export function resolve(specifier, context, nextResolve) {", + " if (!specifier.startsWith('virtual:registered-chain')) return nextResolve(specifier, context);", + " return nextResolve(`${specifier}:a`, context);", + "}", + ] + .join("\n"), + )?; + fs::write( + suite_dir.join("register-chain-loader-b.mjs"), + [ + "let tag;", + "export function initialize(data) { tag = data.tag; }", + "export function resolve(specifier, context, nextResolve) {", + " if (!specifier.startsWith('virtual:registered-chain')) return nextResolve(specifier, context);", + " return nextResolve(`${specifier}:${tag}`, context);", + "}", + ] + .join("\n"), + )?; + fs::write( + suite_dir.join("register-chain-terminal.mjs"), + [ + "let tag;", + "export function initialize(data) { tag = data.tag; }", + "export function resolve(specifier, context, nextResolve) {", + " if (!specifier.startsWith('virtual:registered-chain')) return nextResolve(specifier, context);", + " return { shortCircuit: true, url: `virtual:done:${specifier}:${tag}`, format: 'module' };", + "}", + "export function load(url, context, nextLoad) {", + " if (!url.startsWith('virtual:done:')) return nextLoad(url, context);", + " return { shortCircuit: true, format: 'module', source: `export default ${JSON.stringify(url)};` };", + "}", + ] + .join("\n"), + )?; + fs::write( + suite_dir.join("register-chain-entry.mjs"), + [ + "import { register } from 'node:module';", + "register('./register-chain-terminal.mjs', { parentURL: import.meta.url, data: { tag: 'terminal' } });", + "register('./register-chain-loader-a.mjs', { parentURL: import.meta.url });", + "register('./register-chain-loader-b.mjs', { parentURL: import.meta.url, data: { tag: 'b' } });", + "register('./register-chain-loader-a.mjs', { parentURL: import.meta.url });", + "const ns = await import('virtual:registered-chain');", + "if (ns.default !== 'virtual:done:virtual:registered-chain:a:b:a:terminal') {", + " throw new Error('programmatic loader chain order mismatch: ' + ns.default);", + "}", + ] + .join("\n"), + )?; + + let (result, stdout, stderr) = instance + .invoke_and_capture_output_with_stderr( + None, + "run-test", + &[Val::String( + "/home/node/test/es-module/register-chain-entry.mjs".to_string(), + )], + ) + .await; + + handle_test_result(result, &stdout, &stderr) +} + +#[test_r::test] +async fn runner_module_load_uses_parent_resolution( + prepared: &Arc, +) -> anyhow::Result<()> { + let mut instance = TestInstance::from_golem_prepared(&prepared.0).await?; + instance.set_epoch_deadline(30); + + let suite_dir = instance + .temp_dir_path() + .join("home") + .join("node") + .join("test") + .join("es-module"); + fs::create_dir_all(&suite_dir)?; + fs::write( + suite_dir.join("module-load-parent.mjs"), + [ + "import Module from 'node:module';", + "const parent = new Module('/home/node/test/es-module/parent.cjs');", + "parent.filename = '/home/node/test/es-module/parent.cjs';", + "parent.path = '/home/node/test/es-module';", + "parent.paths = Module._nodeModulePaths('/home/node/test/es-module');", + "parent.require = () => { throw new Error('Module._load must not call parent.require'); };", + "const loaded = Module._load('./module-load-dep.cjs', parent);", + "if (loaded.marker !== 42) throw new Error('Module._load did not resolve relative to parent');", + "const resolved = Module._resolveFilename('./module-load-dep.cjs', parent);", + "if (!resolved.endsWith('/module-load-dep.cjs')) throw new Error('Module._resolveFilename did not resolve relative to parent: ' + resolved);", + "const viaPrototype = Module.prototype.require.call(parent, './module-load-dep.cjs');", + "if (viaPrototype.marker !== 42) throw new Error('Module.prototype.require did not resolve relative to receiver');", + "const customParent = new Module('synthetic-parent');", + "if (customParent.path !== '.') throw new Error('Module constructor did not derive relative id path: ' + customParent.path);", + "if (Object.prototype.hasOwnProperty.call(customParent, 'paths')) throw new Error('Module constructor should not define own paths');", + "const undefinedId = new Module(undefined);", + "if (undefinedId.id !== '' || undefinedId.path !== '.') throw new Error('Module constructor did not default undefined id');", + "const constructorParent = new Module('/home/node/test/es-module/constructor-parent.cjs');", + "const constructorChild = new Module('/home/node/test/es-module/subdir/constructor-child.cjs', constructorParent);", + "if (constructorParent.path !== '/home/node/test/es-module') throw new Error('Module constructor parent path mismatch: ' + constructorParent.path);", + "if (constructorChild.path !== '/home/node/test/es-module/subdir') throw new Error('Module constructor child path mismatch: ' + constructorChild.path);", + "if (constructorChild.parent !== constructorParent) throw new Error('Module constructor did not expose parent');", + "if (!constructorParent.children.includes(constructorChild)) throw new Error('Module constructor did not add child to parent.children');", + "assertInvalidArgType(() => new Module(null));", + "assertInvalidArgType(() => new Module(0));", + "const arrayLikeParent = { children: { length: 0 } };", + "const arrayLikeChild = new Module('array-like-child', arrayLikeParent);", + "if (arrayLikeParent.children[0] !== arrayLikeChild || arrayLikeParent.children.length !== 1) throw new Error('Module constructor did not append to array-like children');", + "customParent.path = '/not-used-for-bare-resolution';", + "customParent.paths = ['/home/node/test/es-module/custom_lookup'];", + "const packageLoaded = Module._load('parent-only-pkg', customParent);", + "if (packageLoaded.marker !== 84) throw new Error('Module._load did not honor parent.paths');", + "const packageResolved = Module._resolveFilename('parent-only-pkg', customParent);", + "if (!packageResolved.endsWith('/custom_lookup/parent-only-pkg/index.js')) throw new Error('Module._resolveFilename did not honor parent.paths: ' + packageResolved);", + "const pathsOptionResolved = Module._resolveFilename('paths-option-pkg', customParent, false, { paths: ['/home/node/test/es-module/paths_option'] });", + "if (!pathsOptionResolved.endsWith('/paths_option/node_modules/paths-option-pkg/index.js')) throw new Error('Module._resolveFilename did not honor options.paths: ' + pathsOptionResolved);", + "assertModuleNotFound(() => Module._resolveFilename('./missing-paths-option.cjs', parent, false, { paths: ['/home/node/test/es-module/paths_option'] }), '/home/node/test/es-module/parent.cjs');", + "const packageViaPrototype = Module.prototype.require.call(customParent, 'parent-only-pkg');", + "if (packageViaPrototype.marker !== 84) throw new Error('Module.prototype.require did not honor receiver.paths');", + "if (Module._resolveFilename('node:module') !== 'node:module') throw new Error('Module._resolveFilename changed node: builtin specifier');", + "if (Module._resolveFilename('module') !== 'module') throw new Error('Module._resolveFilename changed bare builtin specifier');", + "if (!Module.isBuiltin('module')) throw new Error('Module.isBuiltin did not recognize module');", + "if (!Module.isBuiltin('node:module')) throw new Error('Module.isBuiltin did not recognize node:module');", + "if (!Module.builtinModules.includes('module')) throw new Error('Module.builtinModules is missing module');", + "if (Module.builtinModules.includes('node:module')) throw new Error('Module.builtinModules should not include node:module');", + "if (Module.prototype.require.call(parent, 'node:module').createRequire(import.meta.url).resolve.paths('module') !== null) throw new Error('require.resolve.paths should return null for module');", + "if (Module.prototype.require.call(parent, 'node:module').createRequire(import.meta.url).resolve.paths('node:module') !== null) throw new Error('require.resolve.paths should return null for node:module');", + "const pathOnlyParent = new Module('path-only-parent');", + "pathOnlyParent.path = '/home/node/test/es-module/path_only_base';", + "pathOnlyParent.paths = [];", + "assertModuleNotFound(() => Module._load('./path-only-dep.cjs', pathOnlyParent));", + "assertModuleNotFound(() => Module._resolveFilename('./path-only-dep.cjs', pathOnlyParent));", + "assertModuleNotFound(() => Module.prototype.require.call(pathOnlyParent, './path-only-dep.cjs'));", + "const compiled = new Module('/home/node/test/es-module/compiled-parent.cjs');", + "const compiledPathBefore = compiled.path;", + "compiled._compile('exports.filename = __filename; exports.dirname = __dirname; exports.dep = require(\"./compiled-dep.cjs\");', '/home/node/test/es-module/compiled-parent.cjs');", + "if (compiled.filename !== null) throw new Error('Module.prototype._compile should not mutate synthetic module.filename');", + "if (compiled.loaded !== false) throw new Error('Module.prototype._compile should not mutate synthetic module.loaded');", + "if (compiled.path !== compiledPathBefore) throw new Error('Module.prototype._compile should not mutate synthetic module.path');", + "if (compiled.exports.filename !== '/home/node/test/es-module/compiled-parent.cjs') throw new Error('Module.prototype._compile passed wrong __filename');", + "if (compiled.exports.dirname !== '/home/node/test/es-module') throw new Error('Module.prototype._compile passed wrong __dirname');", + "if (compiled.exports.dep.marker !== 252) throw new Error('Module.prototype._compile require did not resolve relative to filename');", + "const missingCompile = new Module('/home/node/test/es-module/compiled-parent.cjs');", + "assertModuleNotFound(() => missingCompile._compile('require(\"./missing-compiled-dep.cjs\");', '/home/node/test/es-module/compiled-parent.cjs'), '/home/node/test/es-module/compiled-parent.cjs');", + "assertInvalidArgType(() => Module.prototype._compile.call(null, 'exports.x = 1;', '/home/node/test/es-module/null.cjs'));", + "assertInvalidArgType(() => Module.prototype._compile.call({}, 'exports.x = 1;', '/home/node/test/es-module/plain.cjs'));", + "const cacheRequire = Module.createRequire(import.meta.url);", + "cacheRequire('./loaded-compile-target.cjs');", + "const loadedModule = cacheRequire.cache[cacheRequire.resolve('./loaded-compile-target.cjs')];", + "const unboundCompile = loadedModule._compile;", + "assertInvalidArgType(() => unboundCompile('exports.x = 1;', '/home/node/test/es-module/unbound.cjs'));", + "loadedModule._compile('exports.emptyFilename = __filename; exports.emptyDirname = __dirname; exports.emptyDep = require(\"./compiled-dep.cjs\");', '');", + "if (loadedModule.exports.emptyFilename !== '') throw new Error('loaded module _compile should honor empty filename');", + "if (loadedModule.exports.emptyDirname !== '.') throw new Error('loaded module _compile should use dot dirname for empty filename');", + "if (loadedModule.exports.emptyDep.marker !== 252) throw new Error('loaded module _compile empty-filename require should resolve from original module');", + "function assertModuleNotFound(fn) {", + " const expectedStack = arguments.length > 1 ? arguments[1] : undefined;", + " try { fn(); } catch (err) {", + " if (err && err.code === 'MODULE_NOT_FOUND') {", + " if (expectedStack && (!Array.isArray(err.requireStack) || !err.requireStack.includes(expectedStack))) throw err;", + " return;", + " }", + " throw err;", + " }", + " throw new Error('expected MODULE_NOT_FOUND');", + "}", + "function assertInvalidArgType(fn) {", + " try { fn(); } catch (err) { if (err && err.code === 'ERR_INVALID_ARG_TYPE') return; throw err; }", + " throw new Error('expected ERR_INVALID_ARG_TYPE');", + "}", + ] + .join("\n"), + )?; + fs::write( + suite_dir.join("module-load-dep.cjs"), + "module.exports = { marker: 42 };\n", + )?; + let package_dir = suite_dir.join("custom_lookup").join("parent-only-pkg"); + fs::create_dir_all(&package_dir)?; + fs::write( + package_dir.join("index.js"), + "module.exports = { marker: 84 };\n", + )?; + let paths_option_package_dir = suite_dir + .join("paths_option") + .join("node_modules") + .join("paths-option-pkg"); + fs::create_dir_all(&paths_option_package_dir)?; + fs::write( + paths_option_package_dir.join("index.js"), + "module.exports = { marker: 126 };\n", + )?; + let path_only_dir = suite_dir.join("path_only_base"); + fs::create_dir_all(&path_only_dir)?; + fs::write( + path_only_dir.join("path-only-dep.cjs"), + "module.exports = { marker: 168 };\n", + )?; + fs::write( + suite_dir.join("compiled-dep.cjs"), + "module.exports = { marker: 252 };\n", + )?; + fs::write( + suite_dir.join("loaded-compile-target.cjs"), + "module.exports = { marker: 294 };\n", + )?; + + let (result, stdout, stderr) = instance + .invoke_and_capture_output_with_stderr( + None, + "run-test", + &[Val::String( + "/home/node/test/es-module/module-load-parent.mjs".to_string(), + )], + ) + .await; + + handle_test_result(result, &stdout, &stderr) +} + // --- Helper types and functions --- /// Cloneable representation of discovery data for use in test closures. #[derive(Clone)] enum DiscoveryData { Block(Vec), - NodeTest, + NodeTest(Vec), } fn handle_test_result( @@ -214,7 +600,7 @@ fn gen_node_compat_tests(r: &mut DynamicTestRegistration) { } }; - let discovery = discover_subtests(&path, &source); + let discovery = discover_subtests_with_options(&path, &source, entry.nested_node_test); // Staleness check: compare discovered subtest count vs config count let discovered_count = match &discovery { @@ -243,10 +629,13 @@ fn gen_node_compat_tests(r: &mut DynamicTestRegistration) { let path = path.clone(); let subtest_index = subtest.index; let source = source.clone(); + let isolate_block_subtests = entry.isolate_block_subtests; let discovery_clone = match &discovery { SubtestDiscovery::None => None, SubtestDiscovery::Block(blocks) => Some(DiscoveryData::Block(blocks.clone())), - SubtestDiscovery::NodeTest(_) => Some(DiscoveryData::NodeTest), + SubtestDiscovery::NodeTest(tests) => { + Some(DiscoveryData::NodeTest(tests.clone())) + } }; let subtest_flaky = subtest.flaky; @@ -281,11 +670,14 @@ fn gen_node_compat_tests(r: &mut DynamicTestRegistration) { // Rewrite the test file to isolate the target subtest let rewritten = match &discovery_clone { - Some(DiscoveryData::Block(blocks)) => { - rewrite_for_block(&source, blocks, subtest_index) - } - Some(DiscoveryData::NodeTest) => { - rewrite_for_node_test(&source, subtest_index) + Some(DiscoveryData::Block(blocks)) => rewrite_for_block_with_options( + &source, + blocks, + subtest_index, + isolate_block_subtests, + ), + Some(DiscoveryData::NodeTest(tests)) => { + rewrite_for_node_test(&source, tests, subtest_index) } None => source.clone(), }; diff --git a/tests/node_compat/common-shim/index.js b/tests/node_compat/common-shim/index.js index 545434c1..78702aab 100644 --- a/tests/node_compat/common-shim/index.js +++ b/tests/node_compat/common-shim/index.js @@ -5,6 +5,7 @@ 'use strict'; var { inspect } = require('util'); +require('./tmpdir'); var noop = function() {}; var _mustCallChecks = []; diff --git a/tests/node_compat/common-shim/index.mjs b/tests/node_compat/common-shim/index.mjs index 5d21f650..43f538f0 100644 --- a/tests/node_compat/common-shim/index.mjs +++ b/tests/node_compat/common-shim/index.mjs @@ -59,6 +59,7 @@ const { } = common; export { + createRequire, isWindows, isAIX, isSunOS, diff --git a/tests/node_compat/common-shim/tmpdir.js b/tests/node_compat/common-shim/tmpdir.js index aa52aa92..3a53cf54 100644 --- a/tests/node_compat/common-shim/tmpdir.js +++ b/tests/node_compat/common-shim/tmpdir.js @@ -15,7 +15,10 @@ function installLongPathFsShim() { if (fs.__wasmLongPathShimInstalled) { return; } - fs.__wasmLongPathShimInstalled = true; + Object.defineProperty(fs, '__wasmLongPathShimInstalled', { + value: true, + configurable: true, + }); const originalMkdirSync = fs.mkdirSync.bind(fs); const originalExistsSync = fs.existsSync.bind(fs); @@ -128,3 +131,8 @@ const tmpdir = { }; module.exports = tmpdir; +module.exports.fileURL = tmpdir.fileURL; +module.exports.hasEnoughSpace = tmpdir.hasEnoughSpace; +module.exports.path = tmpdir.path; +module.exports.refresh = tmpdir.refresh; +module.exports.resolve = tmpdir.resolve; diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index bc78cbc4..ffa556e2 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -355,7 +355,7 @@ } }, "parallel/test-util-inspect-long-running.js": {}, - "parallel/test-util-inspect-namespace.js": {}, + "parallel/test-util-inspect-namespace.js": { "category": "runnable" }, "parallel/test-util-inspect-proxy.js": { "category": "node-internals", "reason": "requires internalBinding('util').getProxyDetails" }, "parallel/test-util-inspect.js": { "category": "node-internals", @@ -1695,10 +1695,10 @@ "parallel/test-crypto-keygen-async-elliptic-curve-jwk.js": {}, "parallel/test-crypto-keygen-async-encrypted-private-key-der.js": {}, "parallel/test-crypto-keygen-async-encrypted-private-key.js": {}, - "parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js": {}, + "parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js": { "flaky": true, "timeout": 300 }, "parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js": {}, "parallel/test-crypto-keygen-async-explicit-elliptic-curve.js": {}, - "parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js": {}, + "parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js": { "flaky": true, "timeout": 300 }, "parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js": {}, "parallel/test-crypto-keygen-async-named-elliptic-curve.js": {}, "parallel/test-crypto-keygen-async-rsa.js": {}, @@ -2606,7 +2606,7 @@ "parallel/test-console-stdio-setters.js": {}, "parallel/test-console-tty-colors.js": {}, "parallel/test-console-with-frozen-intrinsics.js": {}, - "parallel/test-delayed-require.js": {}, + "parallel/test-delayed-require.js": { "category": "runnable" }, "parallel/test-diagnostics-channel-bind-store.js": { "category": "known-gap", "reason": "diagnostics_channel runStores transformer-error propagation is incomplete" }, "parallel/test-diagnostics-channel-has-subscribers.js": {}, "parallel/test-diagnostics-channel-object-channel-pub-sub.js": {}, @@ -2665,10 +2665,10 @@ "parallel/test-http2-request-response-proto.js": {}, "parallel/test-instanceof.js": {}, "parallel/test-microtask-queue-integration.js": {}, - "parallel/test-module-cache.js": {}, - "parallel/test-module-circular-dependency-warning.js": {}, - "parallel/test-module-loading-deprecated.js": {}, - "parallel/test-module-parent-setter-deprecation.js": {}, + "parallel/test-module-cache.js": { "category": "runnable" }, + "parallel/test-module-circular-dependency-warning.js": { "category": "runnable" }, + "parallel/test-module-loading-deprecated.js": { "category": "runnable" }, + "parallel/test-module-parent-setter-deprecation.js": { "category": "runnable" }, "parallel/test-net-autoselectfamily-ipv4first.js": {}, "parallel/test-net-isip.js": {}, "parallel/test-net-isipv4.js": {}, @@ -2719,7 +2719,7 @@ "block_00_connect_without_calling_dns_lookup": {}, "block_01_connect_with_single_ip_returned_by_dns_lookup": {}, "block_02_connect_with_autoselectfamily_and_single_ip": {}, - "block_03_connect_with_autoselectfamily_and_multiple_ips": { "flaky": true } + "block_03_connect_with_autoselectfamily_and_multiple_ips": { "category": "known-gap", "reason": "net.BlockList with autoSelectFamily and multiple lookup addresses does not yet raise ERR_IP_BLOCKED before connection attempts" } } }, "parallel/test-net-buffersize.js": {}, @@ -2930,7 +2930,7 @@ "parallel/test-net-write-arguments.js": {}, "parallel/test-net-write-cb-on-destroy-before-connect.js": {}, "parallel/test-net-write-connect-write.js": {}, - "parallel/test-net-write-fully-async-buffer.js": { "category": "runnable" }, + "parallel/test-net-write-fully-async-buffer.js": { "category": "known-gap", "reason": "net write backpressure/drain handling for repeated large Buffer writes can hang in the WASM socket implementation" }, "parallel/test-net-write-fully-async-hex-string.js": { "category": "runnable" }, "parallel/test-net-write-slow.js": { "category": "known-gap", "reason": "net.js TCP implementation incomplete - needs event handling and API fixes" }, "parallel/test-process-next-tick.js": {}, @@ -2997,12 +2997,12 @@ "parallel/test-process-uptime.js": {}, "parallel/test-promise-unhandled-issue-43655.js": {}, "parallel/test-regression-object-prototype.js": {}, - "parallel/test-require-empty-main.js": {}, - "parallel/test-require-enoent-dir.js": {}, + "parallel/test-require-empty-main.js": { "category": "runnable" }, + "parallel/test-require-enoent-dir.js": { "category": "runnable" }, "parallel/test-require-extension-over-directory.js": { "category": "runnable" }, - "parallel/test-require-invalid-package.js": {}, - "parallel/test-require-nul.js": {}, - "parallel/test-require-process.js": {}, + "parallel/test-require-invalid-package.js": { "category": "runnable" }, + "parallel/test-require-nul.js": { "category": "runnable" }, + "parallel/test-require-process.js": { "category": "runnable" }, "parallel/test-runner-aliases.js": {}, "parallel/test-runner-filter-warning.js": {}, "parallel/test-runner-source-maps-invalid-json.js": {}, @@ -3062,19 +3062,19 @@ "parallel/test-v8-stop-coverage.js": {}, "parallel/test-v8-take-coverage-noop.js": {}, "parallel/test-v8-take-coverage.js": {}, - "parallel/test-vm-create-context-circular-reference.js": {}, - "parallel/test-vm-cross-context.js": {}, - "parallel/test-vm-data-property-writable.js": {}, - "parallel/test-vm-deleting-property.js": {}, - "parallel/test-vm-global-assignment.js": {}, - "parallel/test-vm-global-configurable-properties.js": {}, - "parallel/test-vm-global-get-own.js": {}, - "parallel/test-vm-new-script-this-context.js": {}, - "parallel/test-vm-parse-abort-on-uncaught-exception.js": {}, - "parallel/test-vm-script-throw-in-tostring.js": {}, - "parallel/test-vm-set-proto-null-on-globalthis.js": {}, - "parallel/test-vm-context-async-script.js": {}, - "parallel/test-vm-static-this.js": {}, + "parallel/test-vm-create-context-circular-reference.js": { "category": "runnable" }, + "parallel/test-vm-cross-context.js": { "category": "runnable" }, + "parallel/test-vm-data-property-writable.js": { "category": "runnable" }, + "parallel/test-vm-deleting-property.js": { "category": "runnable" }, + "parallel/test-vm-global-assignment.js": { "category": "runnable" }, + "parallel/test-vm-global-configurable-properties.js": { "category": "runnable" }, + "parallel/test-vm-global-get-own.js": { "category": "runnable" }, + "parallel/test-vm-new-script-this-context.js": { "category": "runnable" }, + "parallel/test-vm-parse-abort-on-uncaught-exception.js": { "category": "runnable" }, + "parallel/test-vm-script-throw-in-tostring.js": { "category": "runnable" }, + "parallel/test-vm-set-proto-null-on-globalthis.js": { "category": "runnable" }, + "parallel/test-vm-context-async-script.js": { "category": "runnable" }, + "parallel/test-vm-static-this.js": { "category": "runnable" }, "parallel/test-weakref.js": {}, "parallel/test-websocket-disabled.js": { "category": "known-gap", "reason": "--no-experimental-websocket flag is not honored" }, "parallel/test-webstream-string-tag.js": {}, @@ -4042,7 +4042,7 @@ // === sequential tests === "sequential/test-net-server-listen-ipv6-link-local.js": {}, - "sequential/test-require-cache-without-stat.js": {}, + "sequential/test-require-cache-without-stat.js": { "category": "runnable" }, "sequential/test-stream2-fs.js": {}, "sequential/test-https-server-keep-alive-timeout.js": { "category": "wasi-impossible", "reason": "https is not supported in WebAssembly environment" }, "sequential/test-buffer-creation-regression.js": { "category": "engine-difference", "reason": "depends on engine-specific ArrayBuffer OOM RangeError message text in skip path" }, @@ -4066,12 +4066,12 @@ "sequential/test-cluster-send-handle-large-payload.js": { "category": "wasi-impossible", "reason": "cluster requires process forking, not available in WASM" }, // === es-module suite === - "es-module/test-dynamic-import-script-lifetime.js": { "category": "known-gap", "reason": "node:vm does not yet support importModuleDynamically/SyntheticModule semantics used by this dynamic import lifetime test" }, - "es-module/test-esm-loader-cache-clearing.js": {}, - "es-module/test-vm-compile-function-leak.js": {}, - "es-module/test-vm-contextified-script-leak.js": {}, - "es-module/test-esm-import-meta.mjs": {}, - "es-module/test-esm-import-meta-resolve.mjs": { "category": "known-gap", "reason": "common shim is missing ../common/fixtures.mjs and child_process execPath emulation does not fully support the ESM CLI modes this test exercises (--input-type/--import)" }, + "es-module/test-dynamic-import-script-lifetime.js": { "category": "runnable" }, + "es-module/test-esm-loader-cache-clearing.js": { "category": "runnable" }, + "es-module/test-vm-compile-function-leak.js": { "category": "runnable" }, + "es-module/test-vm-contextified-script-leak.js": { "category": "runnable" }, + "es-module/test-esm-import-meta.mjs": { "category": "runnable" }, + "es-module/test-esm-import-meta-resolve.mjs": { "category": "known-gap", "reason": "same-process import.meta.resolve behavior is covered by runtime tests; remaining vendored failure requires child_process execPath emulation for --input-type/--import ESM CLI modes" }, // === node:zlib tests === "parallel/test-zlib-const.js": {}, @@ -5818,26 +5818,24 @@ "es-module/test-cjs-esm-warn.js": { "category": "known-gap", "reason": "common-shim spawnPromisified child emulation does not support --no-experimental-require-module" }, "es-module/test-cjs-legacyMainResolve-permission.js": { "category": "node-internals", "reason": "requires --expose-internals and node:internal/modules/esm/resolve" }, "es-module/test-cjs-legacyMainResolve.js": { "category": "node-internals", "reason": "requires --expose-internals and node:internal/modules/esm/resolve" }, - "es-module/test-cjs-prototype-pollution.js": {}, - "es-module/test-disable-require-module-with-detection.js": {}, - "es-module/test-esm-assertionless-json-import.js": { "category": "known-gap", "reason": "custom ESM loader hooks (--experimental-loader) and assertionless JSON import behavior are not implemented" }, - "es-module/test-esm-cjs-builtins.js": {}, - "es-module/test-esm-cjs-exports.js": { "category": "known-gap", "reason": "ESM<->CJS export interop semantics (including __esModule/default/named export behavior and related errors) are not Node-compatible yet" }, - "es-module/test-esm-cjs-main.js": {}, - "es-module/test-esm-data-urls.js": {}, - "es-module/test-esm-dynamic-import-attribute.js": {}, - "es-module/test-esm-dynamic-import-commonjs.js": {}, - "es-module/test-esm-dynamic-import-mutating-fs.js": {}, - "es-module/test-esm-dynamic-import.js": {}, - "es-module/test-esm-encoded-path-native.js": {}, - "es-module/test-esm-error-cache.js": {}, - "es-module/test-esm-import-attributes-errors.js": {}, + "es-module/test-cjs-prototype-pollution.js": { "category": "runnable" }, + "es-module/test-disable-require-module-with-detection.js": { "category": "known-gap", "reason": "requires simulated Node CLI flag handling for --no-experimental-require-module/--experimental-detect-module" }, + "es-module/test-esm-assertionless-json-import.js": { "category": "runnable" }, + "es-module/test-esm-cjs-builtins.js": { "category": "known-gap", "reason": "child_process execPath emulation does not yet support this ESM/CJS fixture runner path; same-process builtin and CJS interop are covered by runtime and node_compat tests" }, + "es-module/test-esm-cjs-exports.js": { "category": "known-gap", "reason": "child_process execPath emulation does not yet support this ESM/CJS fixture runner path; direct CJS named export interop is covered by test-require-module.js" }, + "es-module/test-esm-cjs-main.js": { "category": "known-gap", "reason": "child_process execPath emulation does not yet support this ESM/CJS fixture runner path; same-process CJS import/require interop is covered by module-interop runtime tests" }, + "es-module/test-esm-data-urls.js": { "category": "runnable" }, + "es-module/test-esm-dynamic-import-attribute.js": { "category": "runnable" }, + "es-module/test-esm-dynamic-import-commonjs.js": { "category": "runnable" }, + "es-module/test-esm-dynamic-import-mutating-fs.js": { "category": "runnable" }, + "es-module/test-esm-dynamic-import.js": { "category": "runnable" }, + "es-module/test-esm-encoded-path-native.js": { "category": "runnable" }, + "es-module/test-esm-error-cache.js": { "category": "runnable" }, + "es-module/test-esm-import-attributes-errors.js": { "category": "runnable" }, "es-module/test-esm-import-attributes-validation.js": { "category": "node-internals", "reason": "imports internal/modules/esm/assert (Node internal module)" }, - "es-module/test-esm-invalid-data-urls.js": {}, - "es-module/test-esm-invalid-pjson.js": {}, + "es-module/test-esm-invalid-data-urls.js": { "category": "runnable" }, + "es-module/test-esm-invalid-pjson.js": { "category": "runnable" }, "es-module/test-esm-loader-modulemap.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", "split": true, "subtests": { "block_00_are_stored_in_the_map": { "category": "node-internals", "reason": "imports internal/modules/esm/{loader,module_map,module_job,create_dynamic_module}" }, @@ -5849,36 +5847,34 @@ }, "es-module/test-esm-loader-search.js": { "category": "node-internals", "reason": "imports internal/modules/esm/resolve (Node internal module)" }, "es-module/test-esm-long-path-win.js": { "category": "node-internals", "reason": "Windows-only test that also imports node:internal/modules/esm/resolve and internal/modules/run_main" }, - "es-module/test-esm-preserve-symlinks-main.js": {}, - "es-module/test-esm-preserve-symlinks.js": {}, + "es-module/test-esm-preserve-symlinks-main.js": { "category": "runnable" }, + "es-module/test-esm-preserve-symlinks.js": { "category": "runnable" }, "es-module/test-esm-repl-imports.js": { "category": "wasi-impossible", "reason": "requires spawning an interactive Node REPL subprocess (--interactive) and driving it via stdin; unsupported in this WASI environment" }, "es-module/test-esm-repl.js": { "category": "wasi-impossible", "reason": "requires spawning an interactive Node REPL subprocess (--interactive) and driving it via stdin; unsupported in this WASI environment" }, - "es-module/test-esm-symlink-main.js": {}, - "es-module/test-esm-symlink-type.js": {}, - "es-module/test-esm-symlink.js": {}, - "es-module/test-esm-type-field-errors-2.js": {}, - "es-module/test-esm-type-field-errors.js": {}, - "es-module/test-esm-undefined-cjs-global-like-variables.js": { "category": "known-gap", "reason": "ESM diagnostics for require/exports globals and package type=module .js error messaging do not match Node yet" }, - "es-module/test-esm-unknown-extension.js": {}, + "es-module/test-esm-symlink-main.js": { "category": "runnable" }, + "es-module/test-esm-symlink-type.js": { "category": "runnable" }, + "es-module/test-esm-symlink.js": { "category": "runnable" }, + "es-module/test-esm-type-field-errors-2.js": { "category": "runnable" }, + "es-module/test-esm-type-field-errors.js": { "category": "runnable" }, + "es-module/test-esm-undefined-cjs-global-like-variables.js": { "category": "runnable" }, + "es-module/test-esm-unknown-extension.js": { "category": "runnable" }, "es-module/test-esm-url-extname.js": { "category": "node-internals", "reason": "uses --expose-internals and imports node:internal/modules/esm/get_format" }, - "es-module/test-esm-windows.js": {}, + "es-module/test-esm-windows.js": { "category": "runnable" }, "es-module/test-loaders-hidden-from-users.js": { "category": "node-internals", "reason": "uses --expose-internals plus Node internals (require('internal/...'), process.binding('natives'))" }, - "es-module/test-require-module-cached-tla.js": {}, - "es-module/test-require-module-conditional-exports-module.js": { "category": "known-gap", "reason": "node:module does not implement package.json exports condition resolution (module-sync/require/import/default)" }, + "es-module/test-require-module-cached-tla.js": { "category": "runnable" }, + "es-module/test-require-module-conditional-exports-module.js": { "category": "runnable" }, "es-module/test-require-module-conditional-exports.js": { - "category": "known-gap", - "reason": "node:module does not implement package.json exports condition resolution (require/import/default)", "split": true, "subtests": { - "block_00_if_only_require_exports_are_defined_return_require_exports": {}, - "block_01_if_both_are_defined_require_is_used": {}, - "block_02_if_import_and_default_are_defined_default_is_used": {} + "block_00_if_only_require_exports_are_defined_return_require_exports": { "category": "runnable" }, + "block_01_if_both_are_defined_require_is_used": { "category": "runnable" }, + "block_02_if_import_and_default_are_defined_default_is_used": { "category": "runnable" } } }, - "es-module/test-require-module-cycle-cjs-esm-esm.js": {}, + "es-module/test-require-module-cycle-cjs-esm-esm.js": { "category": "runnable" }, "es-module/test-require-module-cycle-esm-cjs-esm-esm.js": { "category": "known-gap", - "reason": "QuickJS module system does not support ESM-CJS interop cycle detection", + "reason": "remaining failures run through spawnSync(process.execPath, ...) and assert exact child-process status/stderr cycle diagnostics; direct node modules app same-process module graph coverage lives in tests/node_modules_apps", "split": true, "subtests": { "block_00_a_mjs_b_cjs_c_mjs_a_mjs": {}, @@ -5888,7 +5884,7 @@ }, "es-module/test-require-module-cycle-esm-cjs-esm.js": { "category": "known-gap", - "reason": "QuickJS module system does not support ESM-CJS interop cycle detection", + "reason": "remaining failures run through spawnSync(process.execPath, ...) and assert exact child-process status/stderr cycle diagnostics; direct node modules app same-process module graph coverage lives in tests/node_modules_apps", "split": true, "subtests": { "block_00_require_a_cjs_a_mjs_b_cjs_a_mjs": {}, @@ -5899,7 +5895,7 @@ }, "es-module/test-require-module-cycle-esm-esm-cjs-esm-esm.js": { "category": "known-gap", - "reason": "QuickJS module system does not support ESM-CJS interop cycle detection", + "reason": "remaining failures run through spawnSync(process.execPath, ...) and assert exact child-process status/stderr cycle diagnostics; direct node modules app same-process module graph coverage lives in tests/node_modules_apps", "split": true, "subtests": { "block_00_a_mjs_b_mjs_c_cjs_z_mjs_a_mjs": {}, @@ -5910,7 +5906,7 @@ }, "es-module/test-require-module-cycle-esm-esm-cjs-esm.js": { "category": "known-gap", - "reason": "require()/import cycle handling in ESM graphs is incomplete (missing ERR_REQUIRE_CYCLE_MODULE and can hit QuickJS linker assert)", + "reason": "remaining failures run through spawnSync(process.execPath, ...) and assert exact child-process status/stdout/stderr diagnostics; one TLA/dynamic-import sequencing case can still hit a QuickJS linker assert through process.execPath emulation, but direct same-process node modules app coverage passes", "split": true, "subtests": { "block_00_a_mjs_b_mjs_c_mjs_d_mjs_c_mjs": {}, @@ -5919,72 +5915,229 @@ "block_03_d_mjs_c_mjs_d_mjs": {} } }, - "es-module/test-require-module-default-extension.js": {}, + "es-module/test-require-module-default-extension.js": { "category": "runnable" }, "es-module/test-require-module-defined-esmodule.js": { "split": true, "subtests": { - "block_00_require_esm_should_allow_the_user_override": {}, - "block_01_block_01": {} + "block_00_require_esm_should_allow_the_user_override": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" } } }, - "es-module/test-require-module-detect-entry-point-aou.js": {}, - "es-module/test-require-module-detect-entry-point.js": {}, - "es-module/test-require-module-dont-detect-cjs.js": {}, - "es-module/test-require-module-dynamic-import-1.js": { "category": "known-gap", "reason": "requires CJS named export analysis (cjs-module-lexer) for ESM import of CJS modules" }, - "es-module/test-require-module-dynamic-import-2.js": { "category": "known-gap", "reason": "requires CJS named export analysis (cjs-module-lexer) for ESM import of CJS modules" }, - "es-module/test-require-module-dynamic-import-3.js": {}, - "es-module/test-require-module-dynamic-import-4.js": {}, - "es-module/test-require-module-error-catching.js": { "category": "known-gap", "reason": "QuickJS require(esm) bridge reports async-module semantics before surfacing synchronous ESM evaluation errors" }, + "es-module/test-require-module-detect-entry-point-aou.js": { "category": "runnable" }, + "es-module/test-require-module-detect-entry-point.js": { "category": "runnable" }, + "es-module/test-require-module-dont-detect-cjs.js": { "category": "runnable" }, + "es-module/test-require-module-dynamic-import-1.js": { "category": "runnable" }, + "es-module/test-require-module-dynamic-import-2.js": { "category": "runnable" }, + "es-module/test-require-module-dynamic-import-3.js": { "category": "runnable" }, + "es-module/test-require-module-dynamic-import-4.js": { "category": "runnable" }, + "es-module/test-require-module-error-catching.js": { "category": "runnable" }, "es-module/test-require-module-errors.js": { "category": "engine-difference", "reason": "asserts V8-specific syntax error stderr text/format that differs in QuickJS" }, - "es-module/test-require-module-feature-detect.js": {}, - "es-module/test-require-module-implicit.js": {}, + "es-module/test-require-module-feature-detect.js": { "category": "runnable" }, + "es-module/test-require-module-implicit.js": { "category": "runnable" }, "es-module/test-require-module-preload.js": { "category": "known-gap", "reason": "child_process execPath emulation lacks full --import/--require preload semantics" }, - "es-module/test-require-module-retry-import-errored.js": { "category": "known-gap", "reason": "ESM loader does not correctly retry/resume top-level-await module evaluation after require() throws ERR_REQUIRE_ASYNC_MODULE" }, - "es-module/test-require-module-retry-import-evaluating.js": { "category": "known-gap", "reason": "ESM loader does not correctly retry/resume top-level-await module evaluation after require() throws ERR_REQUIRE_ASYNC_MODULE" }, - "es-module/test-require-module-synchronous-rejection-handling.js": { "category": "known-gap", "reason": "require(esm) rejection handling does not match Node behavior (unexpected unhandledRejection)" }, - "es-module/test-require-module-tla-retry-import-2.js": { "category": "known-gap", "reason": "ESM loader does not correctly recover/reuse cached module state after require() ERR_REQUIRE_ASYNC_MODULE" }, - "es-module/test-require-module-tla-retry-import.js": { "category": "known-gap", "reason": "ESM loader does not correctly recover/reuse cached module state after require() ERR_REQUIRE_ASYNC_MODULE" }, - "es-module/test-require-module-tla-retry-require.js": {}, + "es-module/test-require-module-retry-import-errored.js": { "category": "runnable" }, + "es-module/test-require-module-retry-import-evaluating.js": { "category": "runnable" }, + "es-module/test-require-module-synchronous-rejection-handling.js": { "category": "runnable" }, + "es-module/test-require-module-tla-retry-import-2.js": { "category": "runnable" }, + "es-module/test-require-module-tla-retry-import.js": { "category": "runnable" }, + "es-module/test-require-module-tla-retry-require.js": { "category": "runnable" }, "es-module/test-require-module-tla.js": { "split": true, "subtests": { - "block_00_block_00": {}, + "block_00_block_00": { "category": "runnable" }, "block_01_block_01": { "category": "known-gap", "reason": "child_process execPath emulation does not implement --experimental-print-required-tla diagnostics output" } } }, - "es-module/test-require-module-transpiled.js": {}, - "es-module/test-require-module-twice.js": { "category": "known-gap", "reason": "CJS named export analysis for ESM/CJS interop is incomplete (missing named exports like π)" }, + "es-module/test-require-module-transpiled.js": { "category": "runnable" }, + "es-module/test-require-module-twice.js": { "category": "runnable" }, "es-module/test-require-module-warning.js": { "category": "known-gap", "reason": "child_process execPath emulation does not implement --trace-require-module warning output" }, "es-module/test-require-module-with-detection.js": { - "category": "known-gap", - "reason": "module syntax detection for extensionless/.js sources required by require(esm) is incomplete", "split": true, "subtests": { - "block_00_block_00": { "reason": "inherited: module syntax detection for extensionless/.js sources required by require(esm) is incomplete" }, - "block_01_block_01": { "reason": "inherited: module syntax detection for extensionless/.js sources required by require(esm) is incomplete" } + "block_00_block_00": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" } } }, "es-module/test-require-module.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", + "category": "runnable", "split": true, "subtests": { "block_00_test_named_exports": { "category": "runnable" }, "block_01_test_esm_that_import_esm": { "category": "runnable" }, - "block_02_test_esm_that_import_cjs": { "category": "known-gap", "reason": "CJS named export analysis for ESM/CJS interop is incomplete (missing named exports like π)" }, + "block_02_test_esm_that_import_cjs": { "category": "runnable" }, "block_03_test_esm_that_require_cjs": { "category": "runnable" }, - "block_04_also_test_default_export": { "category": "known-gap", "reason": "package resolution from ESM (node_modules dependency without package.json) is incomplete" }, + "block_04_also_test_default_export": { "category": "runnable" }, "block_05_test_data_import": { "category": "runnable" } } }, "es-module/test-require-node-modules-warning.js": { "category": "known-gap", "reason": "child_process execPath emulation does not implement --trace-require-module warning output" }, - "es-module/test-vm-compile-function-lineoffset.js": { "category": "known-gap", "reason": "vm.compileFunction options range validation (lineOffset/columnOffset) is incomplete" }, - "es-module/test-vm-main-context-default-loader.js": { "category": "known-gap", "reason": "vm.USE_MAIN_CONTEXT_DEFAULT_LOADER behavior for dynamic import resolution is incomplete" }, - "es-module/test-vm-source-text-module-leak.js": { "category": "known-gap", "reason": "common-shim gc helper does not provide V8-style collectability checks used by this leak test" }, - "es-module/test-vm-synthetic-module-leak.js": { "category": "known-gap", "reason": "common-shim gc helper does not provide V8-style collectability checks used by this leak test" }, + "es-module/test-vm-compile-function-lineoffset.js": { "category": "runnable" }, + "es-module/test-vm-main-context-default-loader.js": { "category": "runnable" }, + "es-module/test-vm-source-text-module-leak.js": { "category": "engine-difference", "reason": "uses common/gc checkIfCollectableByCounting, which depends on V8-only v8.queryObjects" }, + "es-module/test-vm-synthetic-module-leak.js": { "category": "runnable" }, "es-module/test-wasm-memory-out-of-bound.js": { "category": "known-gap", "reason": "WebAssembly global is missing in current runtime" }, "es-module/test-wasm-simple.js": { "category": "known-gap", "reason": "WebAssembly global is missing in current runtime" }, "es-module/test-wasm-web-api.js": { "category": "known-gap", "reason": "WebAssembly global is missing in current runtime" }, + + // === es-module completeness sweep (tracked after coverage audit) === + "es-module/test-esm-assert-strict.mjs": { "category": "runnable" }, + "es-module/test-esm-basic-imports.mjs": { "category": "runnable" }, + "es-module/test-esm-child-process-fork-main.mjs": { "category": "wasi-impossible", "reason": "requires child_process.fork IPC semantics, which are not available in WASM" }, + "es-module/test-esm-cjs-load-error-note.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-cjs-named-error.mjs": { "category": "runnable" }, + "es-module/test-esm-custom-exports.mjs": { "category": "runnable" }, + "es-module/test-esm-cyclic-dynamic-import.mjs": { "category": "runnable" }, + "es-module/test-esm-default-type.mjs": { "category": "runnable" }, + "es-module/test-esm-detect-ambiguous.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-dns-promises.mjs": { "category": "runnable" }, + "es-module/test-esm-double-encoding.mjs": { "category": "runnable" }, + "es-module/test-esm-dynamic-import-attribute.mjs": { "category": "runnable" }, + "es-module/test-esm-dynamic-import-commonjs.mjs": { "category": "runnable" }, + "es-module/test-esm-dynamic-import-mutating-fs.mjs": { "category": "known-gap", "reason": "same-process dynamic import cache behavior is covered by runner_dynamic_import_cache_survives_removed_file; full Node test also requires spawned process.execPath --input-type=module support" }, + "es-module/test-esm-encoded-path.mjs": { "category": "runnable" }, + "es-module/test-esm-example-loader.mjs": { "category": "runnable" }, + "es-module/test-esm-experimental-warnings.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-export-not-found.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-exports-deprecations.mjs": { "category": "runnable" }, + "es-module/test-esm-exports.mjs": { "category": "runnable" }, + "es-module/test-esm-extension-lookup-deprecation.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-extensionless-esm-and-wasm.mjs": { + "split": true, + "nestedNodeTest": true, + "subtests": { + "test_00_should_run_as_the_entry_point": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution for extensionless ESM files" }, + "test_01_should_be_importable": { "category": "runnable" }, + "test_02_should_be_importable_from_a_module_scope_under_node_modules": { "category": "runnable" }, + "test_03_should_run_as_the_entry_point": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution plus WebAssembly module loading support" }, + "test_04_should_be_importable": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" }, + "test_05_should_be_importable_from_a_module_scope_under_node_modules": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" }, + "test_06_should_run_as_the_entry_point": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution for extensionless ESM files" }, + "test_07_should_run_on_import": { "category": "runnable" }, + "test_08_should_error_as_the_entry_point": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution to verify extensionless .wasm classification outside module scope" }, + "test_09_should_run_on_import": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" } + } + }, + "es-module/test-esm-forbidden-globals.mjs": { "category": "runnable" }, + "es-module/test-esm-fs-promises.mjs": { "category": "runnable" }, + "es-module/test-esm-import-assertion-warning.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-import-attributes-1.mjs": { "category": "runnable" }, + "es-module/test-esm-import-attributes-2.mjs": { "category": "runnable" }, + "es-module/test-esm-import-attributes-3.mjs": { "category": "runnable" }, + "es-module/test-esm-import-attributes-errors.mjs": { "category": "runnable" }, + "es-module/test-esm-import-flag.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-import-json-named-export.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-imports-deprecations.mjs": { "category": "runnable" }, + "es-module/test-esm-imports.mjs": { "category": "runnable" }, + "es-module/test-esm-initialization.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-json-cache.mjs": { "category": "runnable" }, + "es-module/test-esm-json.mjs": { + "split": true, + "nestedNodeTest": true, + "subtests": { + "test_00_should_load_json": { "category": "runnable" }, + "test_01_should_not_print_an_experimental_warning": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support for child-process JSON module warning assertions" }, + "test_02_should_load_different_modules_when_the_url_is_different": { "category": "runnable" } + } + }, + "es-module/test-esm-live-binding.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-chaining.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-custom-condition.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-default-resolver.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-dependency.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-entry-url.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-event-loop.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-hooks.mjs": { "category": "known-gap", "reason": "loader hooks in this vendored file are exercised through spawned process.execPath CLI loader flags/eval, deferred to simulated Node CLI mode support" }, + "es-module/test-esm-loader-http-imports.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-invalid-format.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-invalid-url.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-mock.mjs": { "category": "wasi-impossible", "reason": "fixture depends on worker_threads MessageChannel and receiveMessageOnPort support, unavailable in the single-threaded WASM runtime" }, + "es-module/test-esm-loader-not-found.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-programmatically.mjs": { "category": "known-gap", "reason": "programmatic loader registration in this vendored file is exercised through spawned process.execPath --eval/--import/--loader CLI mode" }, + "es-module/test-esm-loader-resolve-type.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-spawn-promisified.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-stringify-text.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-thenable.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader-with-source.mjs": { "category": "runnable" }, + "es-module/test-esm-loader-with-syntax-error.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-loader.mjs": { "category": "runnable" }, + "es-module/test-esm-main-lookup.mjs": { "category": "runnable" }, + "es-module/test-esm-module-not-found-commonjs-hint.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-named-exports.mjs": { "category": "runnable" }, + "es-module/test-esm-namespace.mjs": { "category": "runnable" }, + "es-module/test-esm-no-addons.mjs": { "category": "wasi-impossible", "reason": "requires worker_threads/native addon process isolation semantics, which are not available in single-threaded WASM" }, + "es-module/test-esm-non-js.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-nowarn-exports.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-path-posix.mjs": { "category": "runnable" }, + "es-module/test-esm-path-win32.mjs": { "category": "runnable" }, + "es-module/test-esm-pkgname.mjs": { "category": "runnable" }, + "es-module/test-esm-preserve-symlinks-not-found-plain.mjs": { "category": "runnable" }, + "es-module/test-esm-preserve-symlinks-not-found.mjs": { "category": "runnable" }, + "es-module/test-esm-process.mjs": { "category": "runnable" }, + "es-module/test-esm-prototype-pollution.mjs": { "category": "runnable" }, + "es-module/test-esm-recursive-cjs-dependencies.mjs": { "category": "runnable" }, + "es-module/test-esm-require-cache.mjs": { "category": "runnable" }, + "es-module/test-esm-resolve-type.mjs": { "category": "node-internals", "reason": "requires --expose-internals and node:internal/modules/esm/resolve" }, + "es-module/test-esm-scope-node-modules.mjs": { "category": "runnable" }, + "es-module/test-esm-shared-loader-dep.mjs": { "category": "runnable" }, + "es-module/test-esm-shebang.mjs": { "category": "runnable" }, + "es-module/test-esm-snapshot.mjs": { "category": "known-gap", "reason": "V8 startup snapshot fixture mutates CommonJS require.cache; the WASM runner does not model Node/V8 startup snapshot and cache coupling" }, + "es-module/test-esm-source-map.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-syntax-error.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-throw-undefined.mjs": { "category": "runnable" }, + "es-module/test-esm-tla-unfinished.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-tla.mjs": { "category": "runnable" }, + "es-module/test-esm-type-field.mjs": { "category": "runnable" }, + "es-module/test-esm-type-flag-cli-entry.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-type-flag-errors.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-type-flag-loose-files.mjs": { + "split": true, + "nestedNodeTest": true, + "subtests": { + "test_00_should_run_as_esm_a_js_file_that_is_outside_of_any_package_s": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_01_should_run_as_esm_an_extensionless_javascript_file_that_is_o": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_02_should_run_as_wasm_an_extensionless_wasm_file_that_is_outsid": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution plus WebAssembly module loading support" }, + "test_03_should_import_as_esm_a_js_file_that_is_outside_of_any_packag": { "category": "runnable" }, + "test_04_should_import_as_esm_an_extensionless_javascript_file_that_i": { "category": "runnable" }, + "test_05_should_import_as_wasm_an_extensionless_wasm_file_that_is_out": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" }, + "test_06_should_check_as_esm_input_passed_via_check": { "category": "known-gap", "reason": "requires spawned process.execPath --check execution with --experimental-default-type=module" } + } + }, + "es-module/test-esm-type-flag-package-scopes.mjs": { + "split": true, + "nestedNodeTest": true, + "subtests": { + "test_00_should_run_as_esm_an_extensionless_javascript_file_within_a_": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_01_should_import_an_extensionless_javascript_file_within_a_type": { "category": "runnable" }, + "test_02_should_import_an_extensionless_javascript_file_within_a_type": { "category": "runnable" }, + "test_03_should_run_as_wasm_an_extensionless_wasm_file_within_a_type_": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution plus WebAssembly module loading support" }, + "test_04_should_import_as_wasm_an_extensionless_wasm_file_within_a_ty": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" }, + "test_05_should_import_an_extensionless_wasm_file_within_a_type_modul": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" }, + "test_06_should_run_as_esm_a_js_file_within_package_scope_that_has_no": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_07_should_run_as_esm_an_extensionless_javascript_file_within_a_": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_08_should_run_as_wasm_an_extensionless_wasm_file_within_a_packa": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution plus WebAssembly module loading support" }, + "test_09_should_import_as_esm_a_js_file_within_package_scope_that_has": { "category": "runnable" }, + "test_10_should_import_as_esm_an_extensionless_javascript_file_within": { "category": "runnable" }, + "test_11_should_import_as_wasm_an_extensionless_wasm_file_within_a_pa": { "category": "known-gap", "reason": "WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source" }, + "test_12_should_run_as_commonjs_a_js_file_within_package_scope_that_h": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_13_should_import_as_commonjs_a_js_file_within_a_package_scope_t": { "category": "runnable" }, + "test_14_should_run_as_commonjs_an_extensionless_javascript_file_with": { "category": "known-gap", "reason": "requires spawned process.execPath entry-point execution with --experimental-default-type=module" }, + "test_15_should_import_as_commonjs_an_extensionless_javascript_file_w": { "category": "runnable" } + } + }, + "es-module/test-esm-type-flag-string-input.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-esm-type-main.mjs": { "category": "runnable" }, + "es-module/test-esm-util-types.mjs": { "category": "runnable" }, + "es-module/test-esm-virtual-json.mjs": { "category": "runnable" }, + "es-module/test-esm-wasm.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-import-module-conditional-exports-module.mjs": { "category": "runnable" }, + "es-module/test-loaders-unknown-builtin-module.mjs": { "category": "runnable" }, + "es-module/test-loaders-workers-spawned.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI mode support deferred to follow-up PR" }, + "es-module/test-require-as-esm-interop.mjs": { "category": "runnable" }, + "es-module/test-typescript-commonjs.mjs": { "category": "known-gap", "reason": "requires Node TypeScript stripping/Amaro support, which is out of scope for this module PR" }, + "es-module/test-typescript-eval.mjs": { "category": "known-gap", "reason": "requires Node TypeScript stripping/Amaro support, which is out of scope for this module PR" }, + "es-module/test-typescript-module.mjs": { "category": "known-gap", "reason": "requires Node TypeScript stripping/Amaro support, which is out of scope for this module PR" }, + "es-module/test-typescript-transform.mjs": { "category": "known-gap", "reason": "requires Node TypeScript stripping/Amaro support, which is out of scope for this module PR" }, + "es-module/test-typescript.mjs": { "category": "known-gap", "reason": "requires Node TypeScript stripping/Amaro support, which is out of scope for this module PR" }, "parallel/test-abortcontroller-internal.js": { "category": "node-internals", "reason": "requires --expose-internals and internal/event_target (kWeakHandler)" }, "parallel/test-accessor-properties.js": { "category": "node-internals", @@ -6298,14 +6451,14 @@ "parallel/test-dgram-udp6-send-default-host.js": { "category": "known-gap", "reason": "IPv6 sockets are not available in this runtime (common.hasIPv6=false)" }, "parallel/test-dgram-unref-in-cluster.js": { "category": "wasi-impossible", "reason": "cluster requires process forking, not available in WASM" }, "parallel/test-diagnostics-channel-http.js": { "category": "known-gap", "reason": "diagnostics_channel integration for http events is incomplete" }, - "parallel/test-diagnostics-channel-module-import-error.js": { "category": "known-gap", "reason": "diagnostics_channel tracing for module.import events is incomplete" }, - "parallel/test-diagnostics-channel-module-import.js": { "category": "known-gap", "reason": "diagnostics_channel tracing for module.import events is incomplete" }, - "parallel/test-diagnostics-channel-module-require-error.js": { "category": "known-gap", "reason": "diagnostics_channel tracing for module.require events is incomplete" }, - "parallel/test-diagnostics-channel-module-require.js": { "category": "known-gap", "reason": "diagnostics_channel tracing for module.require events is incomplete" }, + "parallel/test-diagnostics-channel-module-import-error.js": { "category": "runnable" }, + "parallel/test-diagnostics-channel-module-import.js": { "category": "runnable" }, + "parallel/test-diagnostics-channel-module-require-error.js": { "category": "runnable" }, + "parallel/test-diagnostics-channel-module-require.js": { "category": "runnable" }, "parallel/test-diagnostics-channel-net.js": { "category": "known-gap", "reason": "diagnostics_channel integration for net events is incomplete" }, "parallel/test-diagnostics-channel-process.js": { "category": "wasi-impossible", "reason": "cluster requires process forking, not available in WASM" }, "parallel/test-diagnostics-channel-worker-threads.js": { "category": "known-gap", "reason": "diagnostics_channel integration for worker_threads events is missing in the worker shim" }, - "parallel/test-directory-import.js": { "category": "known-gap", "reason": "ESM directory import errors do not match Node ERR_UNSUPPORTED_DIR_IMPORT behavior" }, + "parallel/test-directory-import.js": { "category": "runnable" }, "parallel/test-disable-proto-delete.js": { "category": "known-gap", "reason": "--disable-proto=delete semantics differ in QuickJS (__proto__ yields null)" }, "parallel/test-disable-proto-throw.js": { "category": "known-gap", "reason": "--disable-proto=throw flag semantics are not implemented" }, "parallel/test-disable-sigusr1.js": { "category": "wasi-impossible", "reason": "inspector/debugger is not available in WASM" }, @@ -6492,7 +6645,31 @@ } }, "parallel/test-filehandle-readablestream.js": { "category": "known-gap", "reason": "fs/promises FileHandle.readableWebStream support is missing or incomplete" }, - "parallel/test-find-package-json.js": { "category": "known-gap", "reason": "node:module.findPackageJSON API behavior is incomplete" }, + "parallel/test-find-package-json.js": { + "split": true, + "nestedNodeTest": true, + "subtests": { + "test_00_should_throw_when_no_arguments_are_provided": { "category": "runnable" }, + "test_01_should_throw_when_parent_location_is_invalid": { "category": "runnable" }, + "test_02_should_accept_a_file_url_string": { "category": "runnable" }, + "test_03_should_accept_a_file_url_instance": { "category": "runnable" }, + "test_04_should_be_able_to_crawl_up_cjs": { "category": "runnable" }, + "test_05_should_be_able_to_crawl_up_esm": { "category": "runnable" }, + "test_06_can_require_via_package_json": { "category": "runnable" }, + "test_07_should_resolve_root_and_closest_package_json": { + "category": "known-gap", + "reason": "uses child_process spawn path (spawnPromisified)" + }, + "test_08_should_work_within_a_loader": { + "category": "known-gap", + "reason": "requires child process loader/eval flags" + }, + "test_09_should_work_with_async_resolve_hook_registered": { + "category": "known-gap", + "reason": "requires child process loader/eval flags" + } + } + }, "parallel/test-fixed-queue.js": { "category": "node-internals", "reason": "requires --expose-internals and internal/fixed_queue", @@ -6785,50 +6962,51 @@ "parallel/test-microtask-queue-run.js": { "category": "runnable" }, "parallel/test-mime-api.js": { "category": "known-gap", "reason": "util.MIMEType/util.MIMEParams are not implemented" }, "parallel/test-mime-whatwg.js": { "category": "known-gap", "reason": "util.MIMEType parsing API is not implemented" }, - "parallel/test-module-builtin.js": {}, - "parallel/test-module-children.js": {}, - "parallel/test-module-circular-symlinks.js": { "category": "known-gap", "reason": "module cache behavior with circular symlinked dependencies is not Node-compatible" }, + "parallel/test-module-builtin.js": { "category": "runnable" }, + "parallel/test-module-children.js": { "category": "runnable" }, + "parallel/test-module-circular-symlinks.js": { "category": "runnable" }, "parallel/test-module-create-require-multibyte.js": { "split": true, "subtests": { - "block_00_block_00": {}, - "block_01_block_01": {} + "block_00_block_00": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" } } }, - "parallel/test-module-create-require.js": {}, - "parallel/test-module-globalpaths-nodepath.js": {}, - "parallel/test-module-isBuiltin.js": {}, - "parallel/test-module-loading-error.js": {}, - "parallel/test-module-loading-globalpaths.js": { "category": "known-gap", "reason": "node_compat harness does not provide ../common/shared-lib-util for this test setup" }, + "parallel/test-module-create-require.js": { "category": "runnable" }, + "parallel/test-module-globalpaths-nodepath.js": { "category": "runnable" }, + "parallel/test-module-isBuiltin.js": { "category": "runnable" }, + "parallel/test-module-loading-error.js": { "category": "runnable" }, + "parallel/test-module-loading-globalpaths.js": { "category": "known-gap", "reason": "requires child_process execFileSync with copied process.execPath and Node global module path layout" }, "parallel/test-module-main-extension-lookup.js": { "category": "runnable" }, - "parallel/test-module-main-fail.js": {}, - "parallel/test-module-main-preserve-symlinks-fail.js": {}, + "parallel/test-module-main-fail.js": { "category": "runnable" }, + "parallel/test-module-main-preserve-symlinks-fail.js": { "category": "runnable" }, "parallel/test-module-multi-extensions.js": { "split": true, "subtests": { - "block_00_block_00": {}, - "block_01_block_01": {}, - "block_02_block_02": {}, - "block_03_block_03": {}, - "block_04_block_04": {}, - "block_05_block_05": {}, - "block_06_block_06": {} + "block_00_block_00": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" }, + "block_02_block_02": { "category": "runnable" }, + "block_03_block_03": { "category": "runnable" }, + "block_04_block_04": { "category": "runnable" }, + "block_05_block_05": { "category": "runnable" }, + "block_06_block_06": { "category": "runnable" } } }, - "parallel/test-module-nodemodulepaths.js": {}, - "parallel/test-module-parent-deprecation.js": {}, - "parallel/test-module-prototype-mutation.js": {}, + "parallel/test-module-nodemodulepaths.js": { "category": "runnable" }, + "parallel/test-module-parent-deprecation.js": { "category": "runnable" }, + "parallel/test-module-print-timing.mjs": { "category": "known-gap", "reason": "requires simulated process.execPath / Node CLI module_timer and trace-event support" }, + "parallel/test-module-prototype-mutation.js": { "category": "runnable" }, "parallel/test-module-readonly.js": { "category": "wasi-impossible", "reason": "Windows-specific readonly-module filesystem behavior is not applicable in WASI" }, - "parallel/test-module-relative-lookup.js": {}, - "parallel/test-module-run-main-monkey-patch.js": {}, + "parallel/test-module-relative-lookup.js": { "category": "runnable" }, + "parallel/test-module-run-main-monkey-patch.js": { "category": "runnable" }, "parallel/test-module-setsourcemapssupport.js": { "split": true, "subtests": { - "block_00_block_00": {}, - "block_01_block_01": {} + "block_00_block_00": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" } } }, - "parallel/test-module-stat.js": {}, + "parallel/test-module-stat.js": { "category": "runnable" }, "parallel/test-module-strip-types.js": { "category": "known-gap", "reason": "stripTypeScriptTypes requires Amaro support, which is not implemented", @@ -6845,9 +7023,9 @@ "test_08_striptypescripttypes_source_map_when_mode_is_transform_and_s": {} } }, - "parallel/test-module-symlinked-peer-modules.js": {}, - "parallel/test-module-version.js": {}, - "parallel/test-module-wrap.js": {}, + "parallel/test-module-symlinked-peer-modules.js": { "category": "runnable" }, + "parallel/test-module-version.js": { "category": "runnable" }, + "parallel/test-module-wrap.js": { "category": "runnable" }, "parallel/test-module-wrapper.js": { "category": "runnable" }, "parallel/test-navigator.js": { "category": "node-internals", "reason": "requires --expose-internals and internal/navigator" }, "parallel/test-next-tick-errors.js": { "category": "runnable" }, @@ -7117,7 +7295,7 @@ "parallel/test-pipe-unref.js": { "category": "wasi-impossible", "reason": "requires real child_process for fork/event-loop-exit semantics" }, "parallel/test-pipe-writev.js": { "category": "known-gap", "reason": "pipe/net edge case" }, "parallel/test-preload-print-process-argv.js": { "category": "known-gap", "reason": "child_process execPath emulation has incomplete --require preload/argv handling" }, - "parallel/test-preload-self-referential.js": { "category": "known-gap", "reason": "preload module handling edge case" }, + "parallel/test-preload-self-referential.js": { "category": "known-gap", "reason": "child_process execPath emulation lacks cwd-relative --require preload resolution for self-referential packages" }, "parallel/test-preload-worker.js": { "category": "wasi-impossible", "reason": "requires worker_threads which is not available in WASM" }, "parallel/test-preload.js": { "category": "known-gap", "reason": "child_process spawn() stdio stream compatibility (e.g. pipe) is incomplete in execPath emulation" }, "parallel/test-primordials-apply.js": { @@ -7256,7 +7434,7 @@ "parallel/test-promises-unhandled-rejections.js": { "category": "known-gap", "reason": "process unhandledRejection/rejectionHandled/warning mode behavior is incomplete" }, "parallel/test-promises-unhandled-symbol-rejections.js": { "category": "known-gap", "reason": "process unhandledRejection/rejectionHandled/warning mode behavior is incomplete" }, "parallel/test-promises-warning-on-unhandled-rejection.js": { "category": "known-gap", "reason": "process unhandledRejection/rejectionHandled/warning mode behavior is incomplete" }, - "parallel/test-punycode.js": { "category": "known-gap", "reason": "legacy punycode builtin is not wired into CommonJS module resolution" }, + "parallel/test-punycode.js": {}, "parallel/test-queue-microtask-uncaught-asynchooks.js": { "category": "known-gap", "reason": "async_hooks lifecycle events for microtasks are not implemented" }, "parallel/test-queue-microtask.js": { "category": "known-gap", @@ -7489,8 +7667,8 @@ "parallel/test-require-cache.js": { "split": true, "subtests": { - "block_00_block_00": {}, - "block_01_block_01": {} + "block_00_block_00": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" } } }, "parallel/test-require-delete-array-iterator.js": { "category": "runnable" }, @@ -7499,28 +7677,27 @@ "parallel/test-require-extensions-main.js": { "category": "runnable" }, "parallel/test-require-extensions-same-filename-as-dir-trailing-slash.js": { "category": "runnable" }, "parallel/test-require-extensions-same-filename-as-dir.js": { "category": "runnable" }, - "parallel/test-require-invalid-main-no-exports.js": {}, + "parallel/test-require-invalid-main-no-exports.js": { "category": "runnable" }, "parallel/test-require-json.js": { "category": "runnable" }, "parallel/test-require-long-path.js": { "category": "wasi-impossible", "reason": "Windows-specific long-path behavior is not applicable in WASI" }, "parallel/test-require-mjs.js": { "category": "runnable" }, "parallel/test-require-node-prefix.js": { "split": true, "subtests": { - "block_00_all_kinds_of_specifiers_should_work_without_issue": {}, - "block_01_node_prefixed_require_calls_bypass_the_require_cache": {} + "block_00_all_kinds_of_specifiers_should_work_without_issue": { "category": "runnable" }, + "block_01_node_prefixed_require_calls_bypass_the_require_cache": { "category": "runnable" } } }, "parallel/test-require-resolve-opts-paths-relative.js": { "split": true, "subtests": { - "block_00_parent_directory_paths_work_as_intended": {}, - "block_01_current_directory_paths_work_as_intended": {}, - "block_02_sub_directory_paths_work_as_intended": {} + "block_00_parent_directory_paths_work_as_intended": { "category": "runnable" }, + "block_01_current_directory_paths_work_as_intended": { "category": "runnable" }, + "block_02_sub_directory_paths_work_as_intended": { "category": "runnable" } } }, "parallel/test-require-resolve.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", + "category": "runnable", "split": true, "subtests": { "block_00_test_require_resolve_paths": { "category": "runnable" }, @@ -7705,22 +7882,22 @@ "parallel/test-runner-module-mocking.js": { "split": true, "subtests": { - "test_00_input_validation": {}, - "test_01_core_module_mocking_with_namedexports_option": {}, - "test_02_cjs_mocking_with_namedexports_option": {}, - "test_03_esm_mocking_with_namedexports_option": {}, - "test_04_modules_cannot_be_mocked_multiple_times_at_once": {}, - "test_05_mocks_are_automatically_restored": {}, - "test_06_mocks_can_be_restored_independently": {}, - "test_07_core_module_mocks_can_be_used_by_both_module_systems": {}, - "test_08_node_core_module_mocks_can_be_used_by_both_module_systems": {}, - "test_09_cjs_mocks_can_be_used_by_both_module_systems": {}, - "test_10_relative_paths_can_be_used_by_both_module_systems": {}, + "test_00_input_validation": { "category": "runnable" }, + "test_01_core_module_mocking_with_namedexports_option": { "category": "runnable" }, + "test_02_cjs_mocking_with_namedexports_option": { "category": "runnable" }, + "test_03_esm_mocking_with_namedexports_option": { "category": "runnable" }, + "test_04_modules_cannot_be_mocked_multiple_times_at_once": { "category": "runnable" }, + "test_05_mocks_are_automatically_restored": { "category": "runnable" }, + "test_06_mocks_can_be_restored_independently": { "category": "runnable" }, + "test_07_core_module_mocks_can_be_used_by_both_module_systems": { "category": "runnable" }, + "test_08_node_core_module_mocks_can_be_used_by_both_module_systems": { "category": "runnable" }, + "test_09_cjs_mocks_can_be_used_by_both_module_systems": { "category": "runnable" }, + "test_10_relative_paths_can_be_used_by_both_module_systems": { "category": "runnable" }, "test_11_node_modules_can_be_used_by_both_module_systems": { "category": "known-gap", "reason": "WASM child emulation does not support --experimental-test-module-mocks CLI flag" }, - "test_12_file_imports_are_supported_in_esm_only": {}, - "test_13_mocked_modules_do_not_impact_unmocked_modules": {}, - "test_14_defaultexports_work_with_cjs_mocks_in_both_module_systems": {}, - "test_15_defaultexports_work_with_esm_mocks_in_both_module_systems": {}, + "test_12_file_imports_are_supported_in_esm_only": { "category": "runnable" }, + "test_13_mocked_modules_do_not_impact_unmocked_modules": { "category": "runnable" }, + "test_14_defaultexports_work_with_cjs_mocks_in_both_module_systems": { "category": "runnable" }, + "test_15_defaultexports_work_with_esm_mocks_in_both_module_systems": { "category": "runnable" }, "test_16_wrong_import_syntax_should_throw_error_after_module_mocking": { "category": "known-gap", "reason": "WASM child emulation does not support --experimental-test-module-mocks/--experimental-default-type flags" }, "test_17_should_throw_err_access_denied_when_permission_model_is_enab": { "category": "known-gap", "reason": "WASM child emulation does not support --permission/--experimental-test-module-mocks flags" }, "test_18_should_work_when_allow_worker_is_passed_and_permission_model": { "category": "known-gap", "reason": "WASM child emulation does not support --permission/--allow-worker/--experimental-test-module-mocks flags" } @@ -8085,22 +8262,20 @@ "parallel/test-socket-writes-before-passed-to-tls-socket.js": { "category": "wasi-impossible", "reason": "requires TLS socket wrapping over raw net sockets" }, "parallel/test-socketaddress.js": { "category": "node-internals", "reason": "requires internal/socketaddress and internal/test/binding" }, "parallel/test-source-map-api.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", "split": true, "subtests": { - "block_00_it_should_throw_with_invalid_args": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_01_findsourcemap_should_return_undefined_when_no_source_map_is_": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_02_non_exceptional_case": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_03_source_map_attached_to_error": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_04_sourcemap_can_be_instantiated_with_source_map_v3_object_as_p": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_05_error_when_receiving_a_malformed_mappings": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_06_sourcemap_can_be_instantiated_with_index_source_map_v3_objec": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_07_test_various_known_decodings_to_ensure_decodevlq_works_corre": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" }, - "block_08_observed_see_https_github_com_mozilla_source_map_pull_92": { "category": "known-gap", "reason": "module SourceMap/findSourceMap API is not fully implemented" } + "block_00_it_should_throw_with_invalid_args": { "category": "runnable" }, + "block_01_findsourcemap_should_return_undefined_when_no_source_map_is_": { "category": "runnable" }, + "block_02_non_exceptional_case": { "category": "runnable" }, + "block_03_source_map_attached_to_error": { "category": "engine-difference", "reason": "native QuickJS Error.prepareStackTrace CallSite positions include the CJS wrapper offset" }, + "block_04_sourcemap_can_be_instantiated_with_source_map_v3_object_as_p": { "category": "runnable" }, + "block_05_error_when_receiving_a_malformed_mappings": { "category": "runnable" }, + "block_06_sourcemap_can_be_instantiated_with_index_source_map_v3_objec": { "category": "runnable" }, + "block_07_test_various_known_decodings_to_ensure_decodevlq_works_corre": { "category": "runnable" }, + "block_08_observed_see_https_github_com_mozilla_source_map_pull_92": { "category": "runnable" } } }, - "parallel/test-source-map-cjs-require-cache.js": { "category": "known-gap", "reason": "source-map cache eviction via findSourceMap()/GC is incomplete" }, + "parallel/test-source-map-cjs-require-cache.js": { "category": "runnable" }, "parallel/test-spawn-cmd-named-pipe.js": { "category": "wasi-impossible", "reason": "Windows-only named pipe/cmd shell behavior is not available in WASI" }, "parallel/test-sqlite-custom-functions.js": {}, "parallel/test-sqlite-data-types.js": {}, @@ -8993,26 +9168,23 @@ }, "parallel/test-vfs.js": { "category": "node-internals", "reason": "depends on experimental Module._stat and CommonJS loader implementation details" }, "parallel/test-vm-access-process-env.js": { "category": "known-gap", "reason": "process.env defaults are incomplete (PATH is missing in VM context)" }, - "parallel/test-vm-api-handles-getter-errors.js": {}, - "parallel/test-vm-attributes-property-not-on-sandbox.js": { "category": "known-gap", "reason": "vm context property descriptor behavior is incomplete for sandbox accessors" }, + "parallel/test-vm-api-handles-getter-errors.js": { "category": "runnable" }, + "parallel/test-vm-attributes-property-not-on-sandbox.js": { "category": "runnable" }, "parallel/test-vm-basic.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", "split": true, + "isolateBlockSubtests": true, "subtests": { - "block_00_vm_runinnewcontext": { "category": "known-gap", "reason": "vm.runInNewContext does not propagate global writes back to the sandbox object" }, - "block_01_vm_runincontext": { "category": "known-gap", "reason": "vm.runInContext contextification/write-back semantics are incomplete" }, - "block_02_vm_runinthiscontext": { "category": "known-gap", "reason": "process object tagging differs from Node (Object.prototype.toString.call(process))" }, - "block_03_vm_runinnewcontext": { "category": "known-gap", "reason": "vm.createContext options argument validation and error fidelity are incomplete" }, - "block_04_vm_createcontext": { "category": "known-gap", "reason": "vm.createContext argument validation and error codes are incomplete" }, - "block_05_run_script_with_filename": { "category": "known-gap", "reason": "vm run* filename option does not set stack trace file locations correctly" }, - "block_06_vm_compilefunction": { "category": "known-gap", "reason": "vm.compileFunction validation, options handling, and error fidelity are incomplete" } + "block_00_vm_runinnewcontext": { "category": "runnable" }, + "block_01_vm_runincontext": { "category": "runnable" }, + "block_02_vm_runinthiscontext": { "category": "runnable" }, + "block_03_vm_runinnewcontext": { "category": "runnable" }, + "block_04_vm_createcontext": { "category": "runnable" }, + "block_05_run_script_with_filename": { "category": "runnable" }, + "block_06_vm_compilefunction": { "category": "runnable" } } }, "parallel/test-vm-cached-data.js": { "category": "engine-difference", "reason": "vm.Script cachedData/produceCachedData relies on V8 code cache format unavailable in QuickJS" }, "parallel/test-vm-codegen.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", "split": true, "subtests": { "block_00_block_00": { "category": "known-gap", "reason": "WebAssembly global is missing in VM contexts" }, @@ -9021,8 +9193,6 @@ } }, "parallel/test-vm-context-dont-contextify.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", "split": true, "subtests": { "block_00_block_00": { "category": "known-gap", "reason": "vm.constants.DONT_CONTEXTIFY and vanilla-context behavior are not implemented" }, @@ -9035,27 +9205,27 @@ "block_07_block_07": { "category": "known-gap", "reason": "vm.constants.DONT_CONTEXTIFY and vanilla-context behavior are not implemented" } } }, - "parallel/test-vm-context-property-forwarding.js": { "category": "known-gap", "reason": "vm context property forwarding and indexed descriptor behavior are incomplete" }, - "parallel/test-vm-context.js": { "category": "known-gap", "reason": "vm contextification write-back and runInContext semantics are incomplete" }, - "parallel/test-vm-create-and-run-in-context.js": { "category": "known-gap", "reason": "vm contextification does not propagate var/global writes correctly" }, - "parallel/test-vm-create-context-accessors.js": { "category": "known-gap", "reason": "vm.createContext does not preserve sandbox accessor properties during evaluation" }, - "parallel/test-vm-create-context-arg.js": { "category": "known-gap", "reason": "vm.createContext argument type validation and error codes are incomplete" }, + "parallel/test-vm-context-property-forwarding.js": { "category": "runnable" }, + "parallel/test-vm-context.js": { "category": "known-gap", "reason": "vm runInContext filename stack formatting with lineOffset/columnOffset is incomplete" }, + "parallel/test-vm-create-and-run-in-context.js": { "category": "runnable" }, + "parallel/test-vm-create-context-accessors.js": { "category": "runnable" }, + "parallel/test-vm-create-context-arg.js": { "category": "runnable" }, "parallel/test-vm-createcacheddata.js": { "category": "engine-difference", "reason": "vm.Script.createCachedData relies on V8 code cache internals unavailable in QuickJS" }, - "parallel/test-vm-dynamic-import-callback-missing-flag.js": { "category": "known-gap", "reason": "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG behavior is not implemented" }, - "parallel/test-vm-function-declaration.js": { "category": "known-gap", "reason": "function declaration/global binding semantics in vm contexts are incomplete" }, - "parallel/test-vm-function-redefinition.js": { "category": "known-gap", "reason": "function declarations are not persisted correctly across vm.runInContext calls" }, - "parallel/test-vm-getters.js": { "category": "known-gap", "reason": "vm context does not preserve sandbox getter descriptors on the global object" }, - "parallel/test-vm-global-define-property.js": { "category": "known-gap", "reason": "defining global accessor properties in vm contexts does not round-trip to the sandbox correctly" }, + "parallel/test-vm-dynamic-import-callback-missing-flag.js": { "category": "runnable" }, + "parallel/test-vm-function-declaration.js": { "category": "runnable" }, + "parallel/test-vm-function-redefinition.js": { "category": "runnable" }, + "parallel/test-vm-getters.js": { "category": "runnable" }, + "parallel/test-vm-global-define-property.js": { "category": "runnable" }, "parallel/test-vm-global-identity.js": { "category": "known-gap", "reason": "vm context global object identity/proxy semantics differ from Node" }, - "parallel/test-vm-global-non-writable-properties.js": { "category": "known-gap", "reason": "non-writable global property semantics in vm contexts are incomplete" }, - "parallel/test-vm-global-property-enumerator.js": { "category": "known-gap", "reason": "vm context global property enumeration includes unexpected runtime globals" }, - "parallel/test-vm-global-property-interceptors.js": { "category": "known-gap", "reason": "global property descriptor/interceptor behavior in vm contexts is incomplete" }, + "parallel/test-vm-global-non-writable-properties.js": { "category": "known-gap", "reason": "strict assignment to non-writable vm global throws QuickJS read-only wording instead of Node's message" }, + "parallel/test-vm-global-property-enumerator.js": { "category": "runnable" }, + "parallel/test-vm-global-property-interceptors.js": { "category": "known-gap", "reason": "vm non-configurable global redefine throws QuickJS wording instead of Node's Cannot redefine property message" }, "parallel/test-vm-global-property-prototype.js": { "category": "known-gap", "reason": "vm context prototype chain and own-property lookup semantics are incomplete" }, - "parallel/test-vm-global-setter.js": { "category": "known-gap", "reason": "vm global getter/setter descriptors are not exposed correctly on contextified objects" }, + "parallel/test-vm-global-setter.js": { "category": "known-gap", "reason": "non-writable vm global assignment throws QuickJS read-only wording instead of Node's Cannot redefine property message" }, "parallel/test-vm-harmony-symbols.js": { "category": "known-gap", "reason": "per-context Symbol/global binding behavior is incomplete in vm contexts" }, - "parallel/test-vm-indexed-properties.js": { "category": "known-gap", "reason": "indexed property definitions on vm globals do not propagate to the sandbox" }, - "parallel/test-vm-inherited_properties.js": { "category": "known-gap", "reason": "runInNewContext own-vs-inherited property assignment semantics are incomplete" }, - "parallel/test-vm-is-context.js": { "category": "known-gap", "reason": "vm.isContext argument validation and TypeError behavior are incomplete" }, + "parallel/test-vm-indexed-properties.js": { "category": "runnable" }, + "parallel/test-vm-inherited_properties.js": { "category": "known-gap", "reason": "vm inherited sandbox property lookup/assignment semantics require contextified global proxy behavior" }, + "parallel/test-vm-is-context.js": { "category": "runnable" }, "parallel/test-vm-low-stack-space.js": { "category": "known-gap", "reason": "stack-overflow recovery around vm.runInThisContext/runInNewContext traps in WASM" }, "parallel/test-vm-measure-memory-lazy.js": { "category": "engine-difference", @@ -9071,29 +9241,29 @@ "parallel/test-vm-measure-memory-multi-context.js": { "category": "engine-difference", "reason": "vm.measureMemory depends on V8 heap introspection APIs unavailable in QuickJS" }, "parallel/test-vm-measure-memory.js": { "category": "engine-difference", "reason": "vm.measureMemory depends on V8 heap introspection APIs unavailable in QuickJS" }, "parallel/test-vm-module-basic.js": { - "category": "known-gap", - "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)", + "category": "runnable", "split": true, + "isolateBlockSubtests": true, "subtests": { - "block_00_check_inspection_of_the_instance": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_01_block_01": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_02_check_dependencies_getter_returns_same_object_every_time": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_03_check_the_impossibility_of_creating_an_abstract_instance_of_": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_04_check_to_throws_invalid_exportnames": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_05_https_github_com_nodejs_node_issues_32806": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_06_check_to_throws_invalid_evaluatecallback": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_07_check_to_throws_invalid_options": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" }, - "block_08_test_compilefunction_importmoduledynamically": { "category": "known-gap", "reason": "vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling)" } + "block_00_check_inspection_of_the_instance": { "category": "runnable" }, + "block_01_block_01": { "category": "runnable" }, + "block_02_check_dependencies_getter_returns_same_object_every_time": { "category": "runnable" }, + "block_03_check_the_impossibility_of_creating_an_abstract_instance_of_": { "category": "runnable" }, + "block_04_check_to_throws_invalid_exportnames": { "category": "runnable" }, + "block_05_https_github_com_nodejs_node_issues_32806": { "category": "runnable" }, + "block_06_check_to_throws_invalid_evaluatecallback": { "category": "runnable" }, + "block_07_check_to_throws_invalid_options": { "category": "runnable" }, + "block_08_test_compilefunction_importmoduledynamically": { "category": "runnable" } } }, "parallel/test-vm-module-cached-data.js": { "category": "engine-difference", "reason": "SourceTextModule cachedData depends on V8 code cache internals unavailable in QuickJS" }, - "parallel/test-vm-module-dynamic-import.js": { "category": "known-gap", "reason": "importModuleDynamically callback and error semantics are incomplete for vm.Script and vm.SourceTextModule" }, - "parallel/test-vm-module-dynamic-namespace.js": { "category": "known-gap", "reason": "dynamic import callback handling does not correctly support module namespace return values" }, - "parallel/test-vm-module-errors.js": { "category": "known-gap", "reason": "vm.Module/SourceTextModule state machine and Node-compatible error validation are incomplete" }, - "parallel/test-vm-module-import-meta.js": { "category": "known-gap", "reason": "SourceTextModule import.meta initialization hook is not implemented" }, - "parallel/test-vm-module-link.js": { "category": "known-gap", "reason": "SourceTextModule linker/dependency parsing semantics are incomplete (imports, cycles, and attributes)" }, - "parallel/test-vm-module-reevaluate.js": {}, - "parallel/test-vm-module-synthetic.js": { "category": "known-gap", "reason": "vm.SyntheticModule API behavior is missing/incomplete" }, + "parallel/test-vm-module-dynamic-import.js": { "category": "runnable" }, + "parallel/test-vm-module-dynamic-namespace.js": { "category": "runnable" }, + "parallel/test-vm-module-errors.js": { "category": "runnable" }, + "parallel/test-vm-module-import-meta.js": { "category": "runnable" }, + "parallel/test-vm-module-link.js": { "category": "runnable" }, + "parallel/test-vm-module-reevaluate.js": { "category": "runnable" }, + "parallel/test-vm-module-synthetic.js": { "category": "runnable" }, "parallel/test-vm-new-script-new-context.js": { "split": true, "subtests": { @@ -9101,32 +9271,32 @@ "block_01_block_01": {}, "block_02_block_02": {}, "block_03_block_03": {}, - "block_04_block_04": { "category": "known-gap", "reason": "runInNewContext does not propagate global writes back to the sandbox correctly" }, + "block_04_block_04": { "category": "runnable" }, "block_05_block_05": {}, "block_06_block_06": {}, - "block_07_block_07": { "category": "known-gap", "reason": "Script.runInNewContext this-binding/type validation behavior does not match Node" } + "block_07_block_07": { "category": "runnable" } } }, - "parallel/test-vm-no-dynamic-import-callback.js": { "category": "known-gap", "reason": "missing importModuleDynamically callback does not raise ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING" }, - "parallel/test-vm-not-strict.js": { "category": "known-gap", "reason": "runInThisContext/runInContext sloppy-mode var/delete semantics are incorrect" }, - "parallel/test-vm-options-validation.js": { "category": "known-gap", "reason": "vm.Script constructor/run option validation and error codes are incomplete" }, - "parallel/test-vm-ownkeys.js": { "category": "known-gap", "reason": "context marker Symbol(vm.context) leaks into sandbox property enumeration" }, - "parallel/test-vm-ownpropertynames.js": { "category": "known-gap", "reason": "context marker Symbol(vm.context) leaks into sandbox property enumeration" }, - "parallel/test-vm-ownpropertysymbols.js": { "category": "known-gap", "reason": "context marker Symbol(vm.context) leaks into sandbox property enumeration" }, - "parallel/test-vm-preserves-property.js": { "category": "known-gap", "reason": "createContext does not preserve non-enumerable/non-writable sandbox property descriptors" }, + "parallel/test-vm-no-dynamic-import-callback.js": { "category": "runnable" }, + "parallel/test-vm-not-strict.js": { "category": "known-gap", "reason": "runInContext sloppy-mode var/delete semantics still require contextified global script bindings" }, + "parallel/test-vm-options-validation.js": { "category": "runnable" }, + "parallel/test-vm-ownkeys.js": { "category": "runnable" }, + "parallel/test-vm-ownpropertynames.js": { "category": "runnable" }, + "parallel/test-vm-ownpropertysymbols.js": { "category": "runnable" }, + "parallel/test-vm-preserves-property.js": { "category": "runnable" }, "parallel/test-vm-property-not-on-sandbox.js": { "category": "known-gap", "reason": "contextified global proxy identity/property fallback semantics are incomplete" }, "parallel/test-vm-proxies.js": { "category": "known-gap", "reason": "vm contexts do not provide the expected per-context Proxy behavior" }, - "parallel/test-vm-proxy-failure-CP.js": { "category": "known-gap", "reason": "createContext incorrectly triggers Proxy getOwnPropertyDescriptor traps" }, - "parallel/test-vm-run-in-new-context.js": { "category": "known-gap", "reason": "runInNewContext sandbox binding and write-back semantics are incomplete" }, + "parallel/test-vm-proxy-failure-CP.js": { "category": "runnable" }, + "parallel/test-vm-run-in-new-context.js": { "category": "known-gap", "reason": "vm runInNewContext filename stack formatting is incomplete" }, "parallel/test-vm-set-property-proxy.js": { "category": "known-gap", "reason": "runInNewContext assignment with Proxy sandbox does not match Node trap behavior" }, "parallel/test-vm-sigint-existing-handler.js": { "category": "wasi-impossible", "reason": "breakOnSigint requires SIGINT delivery/handler semantics unavailable in WASI" }, "parallel/test-vm-sigint.js": { "category": "wasi-impossible", "reason": "breakOnSigint requires SIGINT delivery/handler semantics unavailable in WASI" }, - "parallel/test-vm-source-map-url.js": { "category": "known-gap", "reason": "vm.Script.sourceMapURL parsing for //# sourceMappingURL comments is not implemented" }, - "parallel/test-vm-strict-assign.js": { "category": "known-gap", "reason": "contextified assignment semantics for strict/non-strict writes to non-writable globals are incorrect" }, - "parallel/test-vm-strict-mode.js": { "category": "known-gap", "reason": "strict-mode assignment semantics in vm contexts differ from Node" }, - "parallel/test-vm-symbols.js": { "category": "known-gap", "reason": "runInContext does not preserve symbol/prototype property access on contextified objects" }, - "parallel/test-vm-syntax-error-message.js": {}, - "parallel/test-vm-syntax-error-stderr.js": {}, + "parallel/test-vm-source-map-url.js": { "category": "runnable" }, + "parallel/test-vm-strict-assign.js": { "category": "known-gap", "reason": "strict assignment to non-writable vm global throws QuickJS read-only wording instead of Node's message" }, + "parallel/test-vm-strict-mode.js": { "category": "runnable" }, + "parallel/test-vm-symbols.js": { "category": "known-gap", "reason": "vm prototype method lookup requires contextified global proxy behavior without exposing the sandbox prototype" }, + "parallel/test-vm-syntax-error-message.js": { "category": "runnable" }, + "parallel/test-vm-syntax-error-stderr.js": { "category": "runnable" }, "parallel/test-vm-timeout-escape-promise-2.js": { "category": "known-gap", "reason": "timeout enforcement with microtaskMode='afterEvaluate' is incomplete" }, "parallel/test-vm-timeout-escape-promise-module.js": { "category": "known-gap", "reason": "timeout enforcement with microtaskMode='afterEvaluate' is incomplete" }, "parallel/test-vm-timeout-escape-promise.js": { "category": "known-gap", "reason": "timeout enforcement with microtaskMode='afterEvaluate' is incomplete" }, @@ -10251,24 +10421,7 @@ } }, "sequential/test-inspector-port-cluster.js": { "category": "wasi-impossible", "reason": "inspector/debugger is not available in WASM" }, - "sequential/test-module-loading.js": { - "category": "unevaluated", - "reason": "newly discovered, not yet evaluated", - "split": true, - "subtests": { - "block_00_block_00": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_01_block_01": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_02_block_02": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_03_block_03": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_04_block_04": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_05_block_05": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_06_block_06": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_07_block_07": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_08_block_08": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_09_block_09": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" }, - "block_10_block_10": { "category": "known-gap", "reason": "full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics" } - } - }, + "sequential/test-module-loading.js": { "category": "runnable", "split": false }, "sequential/test-next-tick-error-spin.js": { "category": "known-gap", "reason": "domain error/nextTick behavior depends on async_hooks semantics that are incomplete" }, "sequential/test-perf-hooks.js": { "category": "unevaluated", @@ -10289,7 +10442,7 @@ "block_02_block_02": { "category": "known-gap", "reason": "perf_hooks.monitorEventLoopDelay is not implemented" } } }, - "sequential/test-pipe.js": { "category": "runnable" }, + "sequential/test-pipe.js": { "category": "known-gap", "reason": "HTTP request piping into a raw TCP stream with large payloads can hang in current net/http stream backpressure handling" }, "sequential/test-process-title.js": { "category": "known-gap", "reason": "child_process -p/process.title behavior is incomplete in WASM child emulation" }, "sequential/test-process-warnings.js": {}, "sequential/test-repl-timeout-throw.js": { "category": "known-gap", "reason": "child_process.spawn emulation does not support --interactive REPL sessions" }, diff --git a/tests/node_compat/report.md b/tests/node_compat/report.md index 2fc4b106..0c227e37 100644 --- a/tests/node_compat/report.md +++ b/tests/node_compat/report.md @@ -1,6 +1,6 @@ # Node.js v22.14.0 Compatibility Inventory -Generated: 2026-05-20 | Source: `tests/node_compat/config.jsonc` | Engine: wasm-rquickjs (QuickJS) +Generated: 2026-07-03 | Source: `tests/node_compat/config.jsonc` | Engine: wasm-rquickjs (QuickJS) This report is generated from `config.jsonc` only. It does **not** run the vendored tests itself. Entries classified as `runnable` are reported as passing because the `node_compat` PR test executes runnable entries and fails CI if any of them fail. @@ -8,19 +8,19 @@ This report is generated from `config.jsonc` only. It does **not** run the vendo Primary compatibility is measured over the public API surface we can provide: CI-enforced passing (`runnable`) plus `known-gap`. WASI-impossible tests, engine differences, unevaluated tests, and Node.js-internals tests are acknowledged separately and excluded from the primary percentage. -**Primary compatibility (CI-enforced):** 3085/4295 (71.8%) +**Primary compatibility (CI-enforced):** 3240/4423 (73.3%) | Classification | Count | Primary % | Public inventory % | All listed % | |----------------|-------|-----------|--------------------|--------------| -| ✅ passing (runnable) | 3085 | 71.8% | 55.0% | 45.8% | -| 🧩 known gap | 1210 | 28.2% | 21.6% | 18.0% | -| 🚫 WASI-impossible (excluded) | 1153 | — | 20.6% | 17.1% | -| ⚙️ engine difference (excluded) | 162 | — | 2.9% | 2.4% | +| ✅ passing (runnable) | 3240 | 73.3% | 56.4% | 47.2% | +| 🧩 known gap | 1183 | 26.7% | 20.6% | 17.2% | +| 🚫 WASI-impossible (excluded) | 1156 | — | 20.1% | 16.8% | +| ⚙️ engine difference (excluded) | 164 | — | 2.9% | 2.4% | | ❔ unevaluated (excluded) | 0 | — | 0.0% | 0.0% | -| 🔒 Node.js internals (excluded) | 1121 | — | — | 16.7% | -| **Total** | **6731** | | | **100.0%** | +| 🔒 Node.js internals (excluded) | 1122 | — | — | 16.3% | +| **Total** | **6865** | | | **100.0%** | -Secondary full-public compatibility, including public tests that are currently excluded from primary: **3085/5610 (55.0%)**. +Secondary full-public compatibility, including public tests that are currently excluded from primary: **3240/5743 (56.4%)**. ## Inventory by Module @@ -39,7 +39,7 @@ Secondary full-public compatibility, including public tests that are currently e | console | 31 | 29 | 1 | 0 | 0 | 0 | 1 | 96.7% | 96.7% | | crypto | 239 | 204 | 11 | 8 | 0 | 0 | 16 | 94.9% | 91.5% | | dgram | 118 | 23 | 74 | 7 | 0 | 0 | 14 | 23.7% | 22.1% | -| diagnostics_channel | 33 | 18 | 12 | 1 | 2 | 0 | 0 | 60.0% | 54.5% | +| diagnostics_channel | 33 | 22 | 8 | 1 | 2 | 0 | 0 | 73.3% | 66.7% | | dns | 42 | 2 | 27 | 0 | 0 | 0 | 13 | 6.9% | 6.9% | | domain | 61 | 28 | 20 | 12 | 0 | 0 | 1 | 58.3% | 46.7% | | encoding | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 100.0% | 100.0% | @@ -50,14 +50,14 @@ Secondary full-public compatibility, including public tests that are currently e | fs | 482 | 374 | 12 | 20 | 5 | 0 | 71 | 96.9% | 91.0% | | global | 11 | 4 | 5 | 0 | 0 | 0 | 2 | 44.4% | 44.4% | | heap | 22 | 0 | 0 | 15 | 7 | 0 | 0 | 0.0% | 0.0% | -| http | 898 | 244 | 305 | 267 | 2 | 0 | 80 | 44.4% | 29.8% | +| http | 898 | 243 | 306 | 267 | 2 | 0 | 80 | 44.3% | 29.7% | | inspector | 95 | 1 | 0 | 93 | 0 | 0 | 1 | 100.0% | 1.1% | | internal | 53 | 1 | 0 | 0 | 0 | 0 | 52 | 100.0% | 100.0% | -| module | 184 | 102 | 62 | 7 | 1 | 0 | 12 | 62.2% | 59.3% | -| net | 223 | 150 | 36 | 19 | 1 | 0 | 17 | 80.6% | 72.8% | +| module | 174 | 121 | 33 | 7 | 1 | 0 | 12 | 78.6% | 74.7% | +| net | 223 | 147 | 39 | 19 | 1 | 0 | 17 | 79.0% | 71.4% | | node | 8 | 0 | 0 | 1 | 0 | 0 | 7 | 0.0% | 0.0% | | os | 6 | 5 | 0 | 0 | 0 | 0 | 1 | 100.0% | 100.0% | -| other | 469 | 101 | 92 | 83 | 11 | 0 | 182 | 52.3% | 35.2% | +| other | 613 | 188 | 144 | 86 | 12 | 0 | 183 | 56.6% | 43.7% | | path | 16 | 16 | 0 | 0 | 0 | 0 | 0 | 100.0% | 100.0% | | perf_hooks | 41 | 3 | 34 | 2 | 0 | 0 | 2 | 8.1% | 7.7% | | permission | 55 | 4 | 38 | 9 | 2 | 0 | 2 | 9.5% | 7.5% | @@ -81,7 +81,7 @@ Secondary full-public compatibility, including public tests that are currently e | url | 29 | 28 | 0 | 0 | 0 | 0 | 1 | 100.0% | 100.0% | | util | 174 | 90 | 8 | 0 | 0 | 0 | 76 | 91.8% | 91.8% | | v8 | 45 | 14 | 1 | 0 | 30 | 0 | 0 | 93.3% | 31.1% | -| vm | 121 | 25 | 84 | 3 | 9 | 0 | 0 | 22.9% | 20.7% | +| vm | 121 | 74 | 34 | 3 | 10 | 0 | 0 | 68.5% | 61.2% | | webcrypto | 107 | 43 | 21 | 1 | 0 | 0 | 42 | 67.2% | 66.2% | | webstreams | 68 | 67 | 0 | 0 | 0 | 0 | 1 | 100.0% | 100.0% | | whatwg | 261 | 54 | 21 | 0 | 0 | 0 | 186 | 72.0% | 72.0% | @@ -92,16 +92,20 @@ Secondary full-public compatibility, including public tests that are currently e | File | Subtests | Passing | Gap | WASI-impossible | Engine diff | Unevaluated | Internals | |------|----------|----------|-----|-----------------|-------------|-------------|-----------| +| `test-esm-extensionless-esm-and-wasm.mjs` | 10 | 3 | 7 | 0 | 0 | 0 | 0 | +| `test-esm-json.mjs` | 3 | 2 | 1 | 0 | 0 | 0 | 0 | | `test-esm-loader-modulemap.js` | 5 | 0 | 0 | 0 | 0 | 0 | 5 | -| `test-require-module-conditional-exports.js` | 3 | 0 | 3 | 0 | 0 | 0 | 0 | +| `test-esm-type-flag-loose-files.mjs` | 7 | 2 | 5 | 0 | 0 | 0 | 0 | +| `test-esm-type-flag-package-scopes.mjs` | 16 | 6 | 10 | 0 | 0 | 0 | 0 | +| `test-require-module-conditional-exports.js` | 3 | 3 | 0 | 0 | 0 | 0 | 0 | | `test-require-module-cycle-esm-cjs-esm-esm.js` | 3 | 0 | 3 | 0 | 0 | 0 | 0 | | `test-require-module-cycle-esm-cjs-esm.js` | 4 | 0 | 4 | 0 | 0 | 0 | 0 | | `test-require-module-cycle-esm-esm-cjs-esm-esm.js` | 4 | 0 | 4 | 0 | 0 | 0 | 0 | | `test-require-module-cycle-esm-esm-cjs-esm.js` | 4 | 0 | 4 | 0 | 0 | 0 | 0 | | `test-require-module-defined-esmodule.js` | 2 | 2 | 0 | 0 | 0 | 0 | 0 | | `test-require-module-tla.js` | 2 | 1 | 1 | 0 | 0 | 0 | 0 | -| `test-require-module-with-detection.js` | 2 | 0 | 2 | 0 | 0 | 0 | 0 | -| `test-require-module.js` | 6 | 4 | 2 | 0 | 0 | 0 | 0 | +| `test-require-module-with-detection.js` | 2 | 2 | 0 | 0 | 0 | 0 | 0 | +| `test-require-module.js` | 6 | 6 | 0 | 0 | 0 | 0 | 0 | | `test-abortcontroller.js` | 19 | 19 | 0 | 0 | 0 | 0 | 0 | | `test-aborted-util.js` | 5 | 4 | 0 | 1 | 0 | 0 | 0 | | `test-abortsignal-cloneable.js` | 3 | 3 | 0 | 0 | 0 | 0 | 0 | @@ -223,6 +227,7 @@ Secondary full-public compatibility, including public tests that are currently e | `test-eventtarget-memoryleakwarning.js` | 8 | 0 | 0 | 0 | 0 | 0 | 8 | | `test-eventtarget.js` | 61 | 0 | 0 | 0 | 0 | 0 | 61 | | `test-file.js` | 16 | 16 | 0 | 0 | 0 | 0 | 0 | +| `test-find-package-json.js` | 10 | 7 | 3 | 0 | 0 | 0 | 0 | | `test-fixed-queue.js` | 3 | 0 | 0 | 0 | 0 | 0 | 3 | | `test-freeze-intrinsics.js` | 4 | 0 | 4 | 0 | 0 | 0 | 0 | | `test-fs-access.js` | 3 | 0 | 0 | 0 | 0 | 0 | 3 | @@ -363,7 +368,7 @@ Secondary full-public compatibility, including public tests that are currently e | `test-net-autoselectfamily-default.js` | 2 | 2 | 0 | 0 | 0 | 0 | 0 | | `test-net-autoselectfamily.js` | 4 | 3 | 1 | 0 | 0 | 0 | 0 | | `test-net-better-error-messages-path.js` | 2 | 2 | 0 | 0 | 0 | 0 | 0 | -| `test-net-blocklist.js` | 4 | 4 | 0 | 0 | 0 | 0 | 0 | +| `test-net-blocklist.js` | 4 | 3 | 1 | 0 | 0 | 0 | 0 | | `test-net-bytes-written-large.js` | 3 | 3 | 0 | 0 | 0 | 0 | 0 | | `test-net-connect-options-port.js` | 4 | 0 | 4 | 0 | 0 | 0 | 0 | | `test-net-normalize-args.js` | 2 | 0 | 0 | 0 | 0 | 0 | 2 | @@ -467,7 +472,7 @@ Secondary full-public compatibility, including public tests that are currently e | `test-snapshot-typescript.js` | 2 | 0 | 0 | 0 | 2 | 0 | 0 | | `test-snapshot-umd.js` | 2 | 0 | 0 | 0 | 2 | 0 | 0 | | `test-snapshot-warning.js` | 3 | 0 | 0 | 0 | 3 | 0 | 0 | -| `test-source-map-api.js` | 9 | 0 | 9 | 0 | 0 | 0 | 0 | +| `test-source-map-api.js` | 9 | 8 | 0 | 0 | 1 | 0 | 0 | | `test-source-map-enable.js` | 23 | 23 | 0 | 0 | 0 | 0 | 0 | | `test-sqlite-database-sync.js` | 5 | 5 | 0 | 0 | 0 | 0 | 0 | | `test-sqlite-session.js` | 14 | 13 | 1 | 0 | 0 | 0 | 0 | @@ -587,12 +592,12 @@ Secondary full-public compatibility, including public tests that are currently e | `test-v8-query-objects.js` | 5 | 0 | 0 | 0 | 5 | 0 | 0 | | `test-v8-serdes.js` | 14 | 0 | 0 | 0 | 14 | 0 | 0 | | `test-validators.js` | 7 | 0 | 0 | 0 | 0 | 0 | 7 | -| `test-vm-basic.js` | 7 | 0 | 7 | 0 | 0 | 0 | 0 | +| `test-vm-basic.js` | 7 | 7 | 0 | 0 | 0 | 0 | 0 | | `test-vm-codegen.js` | 3 | 0 | 3 | 0 | 0 | 0 | 0 | | `test-vm-context-dont-contextify.js` | 8 | 0 | 8 | 0 | 0 | 0 | 0 | | `test-vm-measure-memory-lazy.js` | 4 | 0 | 0 | 0 | 4 | 0 | 0 | -| `test-vm-module-basic.js` | 9 | 0 | 9 | 0 | 0 | 0 | 0 | -| `test-vm-new-script-new-context.js` | 8 | 6 | 2 | 0 | 0 | 0 | 0 | +| `test-vm-module-basic.js` | 9 | 9 | 0 | 0 | 0 | 0 | 0 | +| `test-vm-new-script-new-context.js` | 8 | 8 | 0 | 0 | 0 | 0 | 0 | | `test-webcrypto-constructors.js` | 19 | 19 | 0 | 0 | 0 | 0 | 0 | | `test-webcrypto-derivebits.js` | 4 | 0 | 2 | 0 | 0 | 0 | 2 | | `test-webcrypto-derivekey.js` | 6 | 0 | 3 | 0 | 0 | 0 | 3 | @@ -669,7 +674,6 @@ Secondary full-public compatibility, including public tests that are currently e | `test-fs-watch.js` | 6 | 3 | 3 | 0 | 0 | 0 | 0 | | `test-heapdump.js` | 4 | 0 | 0 | 0 | 4 | 0 | 0 | | `test-init.js` | 3 | 3 | 0 | 0 | 0 | 0 | 0 | -| `test-module-loading.js` | 11 | 0 | 11 | 0 | 0 | 0 | 0 | | `test-net-server-address.js` | 5 | 5 | 0 | 0 | 0 | 0 | 0 | | `test-net-server-bind.js` | 5 | 5 | 0 | 0 | 0 | 0 | 0 | | `test-perf-hooks.js` | 2 | 0 | 2 | 0 | 0 | 0 | 0 | @@ -680,34 +684,34 @@ Secondary full-public compatibility, including public tests that are currently e ## Classified Non-Runnable Tests -### known gap (1210) +### known gap (1183) | Reason | Count | Example entries | |--------|-------|-----------------| | node:http2 public API is a stub in WebAssembly runtime | 106 | `parallel/test-http2-head-request.js`, `parallel/test-http2-info-headers.js`, `parallel/test-http2-invalidargtypes-errors.js`, ... (+103) | +| requires simulated process.execPath / Node CLI mode support deferred to follow-up PR | 29 | `es-module/test-esm-cjs-load-error-note.mjs`, `es-module/test-esm-detect-ambiguous.mjs`, `es-module/test-esm-experimental-warnings.mjs`, ... (+26) | | stream edge case not yet handled | 22 | `parallel/test-stream-compose.js#block_17_block_17`, `parallel/test-stream-drop-take.js#block_01_don_t_wait_for_next_item_in_the_original_stream_when_already`, `parallel/test-stream-duplex-from.js#block_17_block_17`, ... (+19) | | process.permission and --permission CLI semantics are incomplete in execPath emulation | 18 | `parallel/test-cli-permission-deny-fs.js#block_00_block_00`, `parallel/test-cli-permission-deny-fs.js#block_01_block_01`, `parallel/test-cli-permission-deny-fs.js#block_02_block_02`, ... (+15) | | wasi:sockets UDP implementation crashes in wasmtime | 14 | `parallel/test-dgram-connect-send-callback-buffer.js`, `parallel/test-dgram-connect-send-callback-multi-buffer.js`, `parallel/test-dgram-connect-send-default-host.js`, ... (+11) | | domain module depends on async_hooks, not fully working | 13 | `parallel/test-domain-promise.js#block_00_block_00`, `parallel/test-domain-promise.js#block_01_block_01`, `parallel/test-domain-promise.js#block_03_block_03`, ... (+10) | | inherited: dns.getServers()/setServers default-server behavior and validation are not Node-compatible | 12 | `parallel/test-dns.js#block_00_verify_that_setservers_handles_arrays_with_holes_and_other_o`, `parallel/test-dns.js#block_01_block_01`, `parallel/test-dns.js#block_02_block_02`, ... (+9) | | node:readline module is not yet supported in WebAssembly environment | 12 | `parallel/test-readline-keys.js`, `parallel/test-readline-position.js`, `parallel/test-readline-reopen.js`, ... (+9) | -| QuickJS module system does not support ESM-CJS interop cycle detection | 11 | `es-module/test-require-module-cycle-esm-cjs-esm-esm.js#block_00_a_mjs_b_cjs_c_mjs_a_mjs`, `es-module/test-require-module-cycle-esm-cjs-esm-esm.js#block_01_b_cjs_c_mjs_a_mjs_b_cjs`, `es-module/test-require-module-cycle-esm-cjs-esm-esm.js#block_02_c_mjs_a_mjs_b_cjs_c_mjs`, ... (+8) | -| full script module-loading test still exposes incomplete main-module/cache/package-main edge semantics | 11 | `sequential/test-module-loading.js#block_00_block_00`, `sequential/test-module-loading.js#block_01_block_01`, `sequential/test-module-loading.js#block_02_block_02`, ... (+8) | | inherited: process.permission and --permission CLI semantics are incomplete in execPath emulation | 11 | `parallel/test-permission-allow-child-process-cli.js#block_00_guarantee_the_initial_state`, `parallel/test-permission-allow-child-process-cli.js#block_01_to_spawn_unless_allow_child_process_is_sent`, `parallel/test-permission-allow-wasi-cli.js#block_00_guarantee_the_initial_state`, ... (+8) | | net.js TCP implementation incomplete - needs event handling and API fixes | 11 | `parallel/test-net-connect-nodelay.js`, `parallel/test-net-connect-paused-connection.js`, `parallel/test-net-during-close.js`, ... (+8) | +| remaining failures run through spawnSync(process.execPath, ...) and assert exact child-process status/stderr cycle diagnostics; direct node modules app same-process module graph coverage lives in tests/node_modules_apps | 11 | `es-module/test-require-module-cycle-esm-cjs-esm-esm.js#block_00_a_mjs_b_cjs_c_mjs_a_mjs`, `es-module/test-require-module-cycle-esm-cjs-esm-esm.js#block_01_b_cjs_c_mjs_a_mjs_b_cjs`, `es-module/test-require-module-cycle-esm-cjs-esm-esm.js#block_02_c_mjs_a_mjs_b_cjs_c_mjs`, ... (+8) | | wasi:sockets UDP implementation hangs in wasmtime | 11 | `parallel/test-dgram-implicit-bind.js`, `parallel/test-dgram-multicast-set-interface.js#block_00_block_00`, `parallel/test-dgram-multicast-set-interface.js#block_02_block_02`, ... (+8) | | dgram multicast membership APIs are not implemented (ENOSYS) | 10 | `parallel/test-dgram-membership.js#block_02_addmembership_with_no_argument_should_throw`, `parallel/test-dgram-membership.js#block_03_dropmembership_with_no_argument_should_throw`, `parallel/test-dgram-membership.js#block_04_addmembership_with_invalid_multicast_address_should_throw`, ... (+7) | | async_hooks not fully implemented | 9 | `parallel/test-async-hooks-destroy-on-gc.js`, `parallel/test-async-hooks-disable-during-promise.js`, `parallel/test-async-hooks-disable-gc-tracking.js`, ... (+6) | -| module SourceMap/findSourceMap API is not fully implemented | 9 | `parallel/test-source-map-api.js#block_00_it_should_throw_with_invalid_args`, `parallel/test-source-map-api.js#block_01_findsourcemap_should_return_undefined_when_no_source_map_is_`, `parallel/test-source-map-api.js#block_02_non_exceptional_case`, ... (+6) | | spawn() AbortSignal handling is incomplete (exit code/signal/error semantics differ from Node) | 9 | `parallel/test-child-process-spawn-controller.js#block_00_block_00`, `parallel/test-child-process-spawn-controller.js#block_01_block_01`, `parallel/test-child-process-spawn-controller.js#block_02_block_02`, ... (+6) | | spawnSync() returns ENOSYS for non-execPath commands; Node expects ENOENT after option validation | 9 | `parallel/test-child-process-spawnsync-validation-errors.js#block_00_block_00`, `parallel/test-child-process-spawnsync-validation-errors.js#block_01_block_01`, `parallel/test-child-process-spawnsync-validation-errors.js#block_02_block_02`, ... (+6) | | stripTypeScriptTypes requires Amaro support, which is not implemented | 9 | `parallel/test-module-strip-types.js#test_00_striptypescripttypes`, `parallel/test-module-strip-types.js#test_01_striptypescripttypes_explicit`, `parallel/test-module-strip-types.js#test_02_striptypescripttypes_code_is_not_a_string`, ... (+6) | -| vm.SourceTextModule/SyntheticModule behavior is incomplete (status transitions, validation, and timeout handling) | 9 | `parallel/test-vm-module-basic.js#block_00_check_inspection_of_the_instance`, `parallel/test-vm-module-basic.js#block_01_block_01`, `parallel/test-vm-module-basic.js#block_02_check_dependencies_getter_returns_same_object_every_time`, ... (+6) | | Intl is not available in current runtime | 8 | `parallel/test-intl-v8BreakIterator.js`, `parallel/test-intl.js`, `parallel/test-whatwg-encoding-custom-textdecoder-fatal.js`, ... (+5) | | process unhandledRejection/rejectionHandled/warning mode behavior is incomplete | 8 | `parallel/test-promise-unhandled-silent-no-hook.js`, `parallel/test-promise-unhandled-silent.js`, `parallel/test-promise-unhandled-warn-no-hook.js`, ... (+5) | | vm.constants.DONT_CONTEXTIFY and vanilla-context behavior are not implemented | 8 | `parallel/test-vm-context-dont-contextify.js#block_00_block_00`, `parallel/test-vm-context-dont-contextify.js#block_01_block_01`, `parallel/test-vm-context-dont-contextify.js#block_02_block_02`, ... (+5) | +| WebAssembly module loading for .wasm files is not implemented; binary input is currently treated as JS source | 7 | `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_04_should_be_importable`, `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_05_should_be_importable_from_a_module_scope_under_node_modules`, `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_09_should_run_on_import`, ... (+4) | | common-shim spawnPromisified child emulation does not support --experimental-webstorage/--localstorage-file flags | 7 | `parallel/test-webstorage.js#test_01_emits_a_warning_when_used`, `parallel/test-webstorage.js#test_02_storage_instances_cannot_be_created_in_userland`, `parallel/test-webstorage.js#test_03_sessionstorage_is_not_persisted`, ... (+4) | | inherited: Intl is not available in current runtime | 7 | `parallel/test-icu-transcode.js#block_00_block_00`, `parallel/test-icu-transcode.js#block_01_block_01`, `parallel/test-icu-transcode.js#block_02_test_that_uint8array_arguments_are_okay`, ... (+4) | +| requires spawned process.execPath entry-point execution with --experimental-default-type=module | 7 | `es-module/test-esm-type-flag-loose-files.mjs#test_00_should_run_as_esm_a_js_file_that_is_outside_of_any_package_s`, `es-module/test-esm-type-flag-loose-files.mjs#test_01_should_run_as_esm_an_extensionless_javascript_file_that_is_o`, `es-module/test-esm-type-flag-package-scopes.mjs#test_00_should_run_as_esm_an_extensionless_javascript_file_within_a_`, ... (+4) | | WebAssembly global is missing in current runtime | 6 | `es-module/test-wasm-memory-out-of-bound.js`, `es-module/test-wasm-simple.js`, `es-module/test-wasm-web-api.js`, ... (+3) | | fork() AbortSignal handling is incomplete (exit code/signal/error semantics differ from Node) | 6 | `parallel/test-child-process-fork-abort-signal.js#block_00_block_00`, `parallel/test-child-process-fork-abort-signal.js#block_01_block_01`, `parallel/test-child-process-fork-abort-signal.js#block_02_block_02`, ... (+3) | | inherited: common.canCreateSymLink shim always returns false, so symlink permission tests are skipped | 6 | `parallel/test-permission-fs-symlink-target-write.js#block_00_block_00`, `parallel/test-permission-fs-symlink-target-write.js#block_01_block_01`, `parallel/test-permission-fs-symlink.js#block_00_block_00`, ... (+3) | @@ -718,6 +722,7 @@ Secondary full-public compatibility, including public tests that are currently e | inherited: perf_hooks PerformanceResourceTiming/markResourceTiming behavior is incomplete | 5 | `parallel/test-perf-hooks-resourcetiming.js#block_00_performanceresourcetiming_should_not_be_initialized_external`, `parallel/test-perf-hooks-resourcetiming.js#block_01_using_performance_getentries`, `parallel/test-perf-hooks-resourcetiming.js#block_02_default_values`, ... (+2) | | node:readline createInterface/async iterator API is not implemented | 5 | `parallel/test-readline-async-iterators-backpressure.js`, `parallel/test-readline-async-iterators-destroy.js`, `parallel/test-readline-async-iterators.js`, ... (+2) | | process.getActiveResourcesInfo() is not implemented | 5 | `parallel/test-process-getactiveresources-track-active-handles.js`, `parallel/test-process-getactiveresources-track-active-requests.js`, `parallel/test-process-getactiveresources-track-interval-lifetime.js`, ... (+2) | +| requires Node TypeScript stripping/Amaro support, which is out of scope for this module PR | 5 | `es-module/test-typescript-commonjs.mjs`, `es-module/test-typescript-eval.mjs`, `es-module/test-typescript-module.mjs`, ... (+2) | | util.format output formatting differences | 5 | `parallel/test-util-format.js#block_00_block_00`, `parallel/test-util-format.js#block_01_string_format_specifier_including_tostring_properties_on_the`, `parallel/test-util-format.js#block_02_symbol_toprimitive_handling_for_string_format_specifier`, ... (+2) | | WASM child emulation does not support Node.js --test CLI output behavior | 4 | `parallel/test-runner-extraneous-async-activity.js#block_00_block_00`, `parallel/test-runner-extraneous-async-activity.js#block_01_block_01`, `parallel/test-runner-extraneous-async-activity.js#block_02_block_02`, ... (+1) | | crypto.scrypt/scryptSync support is missing (test reports 'no scrypt support') | 4 | `parallel/test-crypto-scrypt.js#block_00_block_00`, `parallel/test-crypto-scrypt.js#block_01_block_01`, `parallel/test-crypto-scrypt.js#block_02_block_02`, ... (+1) | @@ -727,7 +732,8 @@ Secondary full-public compatibility, including public tests that are currently e | isMarkedAsUntransferable() and related mark/query behavior are incomplete | 4 | `parallel/test-worker-message-transfer-port-mark-as-untransferable.js#block_00_block_00`, `parallel/test-worker-message-transfer-port-mark-as-untransferable.js#block_01_block_01`, `parallel/test-worker-message-transfer-port-mark-as-untransferable.js#block_02_block_02`, ... (+1) | | markAsUncloneable and DataCloneError semantics are incomplete | 4 | `parallel/test-worker-message-mark-as-uncloneable.js#block_00_uncloneables_cannot_be_cloned_during_message_posting`, `parallel/test-worker-message-mark-as-uncloneable.js#block_01_uncloneables_cannot_be_cloned_during_structured_cloning`, `parallel/test-worker-message-mark-as-uncloneable.js#block_02_markasuncloneable_cannot_affect_arraybuffer`, ... (+1) | | promisified exec()/execFile() contract is incomplete (promise.child is not a ChildProcess instance) | 4 | `parallel/test-child-process-promisified.js#block_00_block_00`, `parallel/test-child-process-promisified.js#block_01_block_01`, `parallel/test-child-process-promisified.js#block_02_block_02`, ... (+1) | -| require()/import cycle handling in ESM graphs is incomplete (missing ERR_REQUIRE_CYCLE_MODULE and can hit QuickJS linker assert) | 4 | `es-module/test-require-module-cycle-esm-esm-cjs-esm.js#block_00_a_mjs_b_mjs_c_mjs_d_mjs_c_mjs`, `es-module/test-require-module-cycle-esm-esm-cjs-esm.js#block_01_b_mjs_c_mjs_d_mjs_c_mjs`, `es-module/test-require-module-cycle-esm-esm-cjs-esm.js#block_02_c_mjs_d_mjs_c_mjs`, ... (+1) | +| remaining failures run through spawnSync(process.execPath, ...) and assert exact child-process status/stdout/stderr diagnostics; one TLA/dynamic-import sequencing case can still hit a QuickJS linker assert through process.execPath emulation, but direct same-process node modules app coverage passes | 4 | `es-module/test-require-module-cycle-esm-esm-cjs-esm.js#block_00_a_mjs_b_mjs_c_mjs_d_mjs_c_mjs`, `es-module/test-require-module-cycle-esm-esm-cjs-esm.js#block_01_b_mjs_c_mjs_d_mjs_c_mjs`, `es-module/test-require-module-cycle-esm-esm-cjs-esm.js#block_02_c_mjs_d_mjs_c_mjs`, ... (+1) | +| requires spawned process.execPath entry-point execution plus WebAssembly module loading support | 4 | `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_03_should_run_as_the_entry_point`, `es-module/test-esm-type-flag-loose-files.mjs#test_02_should_run_as_wasm_an_extensionless_wasm_file_that_is_outsid`, `es-module/test-esm-type-flag-package-scopes.mjs#test_03_should_run_as_wasm_an_extensionless_wasm_file_within_a_type_`, ... (+1) | | timeout enforcement with microtaskMode='afterEvaluate' is incomplete | 4 | `parallel/test-vm-timeout-escape-promise-2.js`, `parallel/test-vm-timeout-escape-promise-module.js`, `parallel/test-vm-timeout-escape-promise.js`, ... (+1) | | unhandled-rejection mode and uncaughtException bridging semantics are incomplete | 4 | `parallel/test-promise-unhandled-default.js`, `parallel/test-promise-unhandled-error.js`, `parallel/test-promise-unhandled-throw-handler.js`, ... (+1) | | wasi:http client does not surface 103 Early Hints as 'information' events | 4 | `parallel/test-http-early-hints.js#block_00_block_00`, `parallel/test-http-early-hints.js#block_01_block_01`, `parallel/test-http-early-hints.js#block_03_block_03`, ... (+1) | @@ -739,7 +745,6 @@ Secondary full-public compatibility, including public tests that are currently e | child_process.spawn pipe mode does not provide functional child.stdin | 3 | `parallel/test-stdin-pipe-large.js`, `parallel/test-stdin-pipe-resume.js`, `parallel/test-stdin-script-child-option.js` | | common.canCreateSymLink shim always returns false, so symlink tests are skipped | 3 | `parallel/test-fs-symlink-buffer-path.js`, `parallel/test-fs-symlink-dir.js`, `parallel/test-fs-symlink.js` | | common/gc async_hooks-based GC tracking is not implemented in the WASM test shim | 3 | `sequential/test-gc-http-client-onerror.js`, `sequential/test-gc-http-client-timeout.js`, `sequential/test-gc-http-client.js` | -| context marker Symbol(vm.context) leaks into sandbox property enumeration | 3 | `parallel/test-vm-ownkeys.js`, `parallel/test-vm-ownpropertynames.js`, `parallel/test-vm-ownpropertysymbols.js` | | crypto.X509Certificate API is not implemented | 3 | `parallel/test-x509-escaping.js#block_01_test_escaping_rules_for_subject_alternative_names`, `parallel/test-x509-escaping.js#block_02_test_escaping_rules_for_authority_info_access`, `parallel/test-x509-escaping.js#block_03_test_escaping_rules_for_the_subject_field` | | dgram send() callback overload path has JS/native argument conversion bugs | 3 | `parallel/test-dgram-send-callback-buffer-length-empty-address.js`, `parallel/test-dgram-send-callback-buffer-length.js`, `parallel/test-dgram-send-callback-buffer.js` | | dgram socket buffer size APIs may hang | 3 | `parallel/test-dgram-socket-buffer-size.js#block_02_block_02`, `parallel/test-dgram-socket-buffer-size.js#block_04_block_04`, `parallel/test-dgram-socket-buffer-size.js#block_05_block_05` | @@ -753,7 +758,6 @@ Secondary full-public compatibility, including public tests that are currently e | inherited: server parser accepts bare-LF header separators instead of replying 400 and closing | 3 | `parallel/test-http-missing-header-separator-lf.js#block_00_block_00`, `parallel/test-http-missing-header-separator-lf.js#block_01_block_01`, `parallel/test-http-missing-header-separator-lf.js#block_02_block_02` | | inherited: setServers argument validation (ERR_INVALID_ARG_TYPE details) is incomplete for dns and dns/promises | 3 | `parallel/test-dns-setservers-type-check.js#block_00_block_00`, `parallel/test-dns-setservers-type-check.js#block_01_block_01`, `parallel/test-dns-setservers-type-check.js#block_02_this_test_for_dns_promises` | | net edge case not yet handled | 3 | `parallel/test-net-autoselectfamily.js#block_01_test_that_only_the_last_successful_connection_is_established`, `parallel/test-net-connect-reset.js`, `parallel/test-net-pingpong.js` | -| node:module does not implement package.json exports condition resolution (require/import/default) | 3 | `es-module/test-require-module-conditional-exports.js#block_00_if_only_require_exports_are_defined_return_require_exports`, `es-module/test-require-module-conditional-exports.js#block_01_if_both_are_defined_require_is_used`, `es-module/test-require-module-conditional-exports.js#block_02_if_import_and_default_are_defined_default_is_used` | | node:readline Interface constructor/options are not implemented | 3 | `parallel/test-readline-interface-escapecodetimeout.js`, `parallel/test-readline-interface-no-trailing-newline.js`, `parallel/test-readline-interface-recursive-writes.js` | | node:test concurrency scheduling/completion semantics are incomplete | 3 | `parallel/test-runner-concurrency.js#test_00_concurrency_option_boolean_true`, `parallel/test-runner-concurrency.js#test_01_concurrency_option_boolean_false`, `parallel/test-runner-concurrency.js#test_02_concurrency_true_implies_infinity` | | node_compat common shim is missing ../common/wpt harness | 3 | `parallel/test-whatwg-events-event-constructors.js`, `parallel/test-whatwg-events-eventtarget-this-of-listener.js`, `parallel/test-whatwg-url-custom-searchparams-sort.js` | @@ -762,11 +766,8 @@ Secondary full-public compatibility, including public tests that are currently e | setUncaughtExceptionCaptureCallback does not fully intercept thrown uncaught exceptions | 3 | `parallel/test-process-exception-capture-should-abort-on-uncaught-setflagsfromstring.js`, `parallel/test-process-exception-capture-should-abort-on-uncaught.js`, `parallel/test-process-exception-capture.js` | | spawn() stdio validation/pipe semantics are not Node-compatible in WASM emulation | 3 | `parallel/test-child-process-stdio.js#block_00_test_stdio_piping`, `parallel/test-child-process-stdio.js#block_02_asset_options_invariance`, `parallel/test-child-process-stdio.js#block_03_test_stdout_buffering` | | test runner edge case | 3 | `parallel/test-runner-filetest-location.js`, `parallel/test-runner-root-after-with-refed-handles.js`, `parallel/test-runner-todo-skip-tests.js` | -| CJS named export analysis for ESM/CJS interop is incomplete (missing named exports like π) | 2 | `es-module/test-require-module-twice.js`, `es-module/test-require-module.js#block_02_test_esm_that_import_cjs` | | CLI/NODE_OPTIONS max-http-header-size propagation in child process emulation is incomplete | 2 | `parallel/test-set-http-max-http-headers.js#test_01_test_01`, `parallel/test-set-http-max-http-headers.js#test_02_same_checks_using_node_options_if_it_is_supported` | | DSA keygen currently supports only modern key sizes; legacy 512-bit variant fails | 2 | `parallel/test-crypto-keygen-async-dsa-key-object.js`, `parallel/test-crypto-keygen-async-dsa.js` | -| ESM loader does not correctly recover/reuse cached module state after require() ERR_REQUIRE_ASYNC_MODULE | 2 | `es-module/test-require-module-tla-retry-import-2.js`, `es-module/test-require-module-tla-retry-import.js` | -| ESM loader does not correctly retry/resume top-level-await module evaluation after require() throws ERR_REQUIRE_ASYNC_MODULE | 2 | `es-module/test-require-module-retry-import-errored.js`, `es-module/test-require-module-retry-import-evaluating.js` | | HTTP keep-alive socket identity reuse across sequential requests is not implemented | 2 | `parallel/test-http-keepalive-client.js`, `parallel/test-http-keepalive-request.js` | | IncomingMessage 'aborted' event is not emitted when the server destroys a keep-alive response | 2 | `parallel/test-http-client-aborted-event.js#block_00_block_00`, `parallel/test-http-client-aborted-event.js#block_01_block_01` | | TextDecoderStream invalid-encoding errors are not Node-compatible yet | 2 | `parallel/test-whatwg-webstreams-encoding.js#block_00_block_00`, `parallel/test-whatwg-webstreams-encoding.js#block_01_block_01` | @@ -776,12 +777,9 @@ Secondary full-public compatibility, including public tests that are currently e | child_process execPath emulation does not fully match spawnSync({ encoding }) behavior for --check stdin runs | 2 | `parallel/test-cli-syntax-piped-bad.js`, `parallel/test-cli-syntax-piped-good.js` | | child_process execPath emulation does not implement --trace-require-module warning output | 2 | `es-module/test-require-module-warning.js`, `es-module/test-require-node-modules-warning.js` | | child_process.spawn emulation does not support --interactive REPL sessions | 2 | `parallel/test-repl-array-prototype-tempering.js`, `sequential/test-repl-timeout-throw.js` | -| common-shim gc helper does not provide V8-style collectability checks used by this leak test | 2 | `es-module/test-vm-source-text-module-leak.js`, `es-module/test-vm-synthetic-module-leak.js` | | crypto.X509Certificate.checkHost is not available | 2 | `parallel/test-x509-escaping.js#block_06_the_subject_must_be_ignored_if_a_dnsname_subject_alternative`, `parallel/test-x509-escaping.js#block_07_exists_even_if_other_subject_alternative_names_exist` | | dgram send() callback does not report bytes correctly for multi-buffer payloads | 2 | `parallel/test-dgram-send-callback-multi-buffer.js`, `parallel/test-dgram-send-multi-buffer-copy.js` | | dgram socket buffer size APIs do not match Node error semantics | 2 | `parallel/test-dgram-socket-buffer-size.js#block_00_block_00`, `parallel/test-dgram-socket-buffer-size.js#block_01_block_01` | -| diagnostics_channel tracing for module.import events is incomplete | 2 | `parallel/test-diagnostics-channel-module-import-error.js`, `parallel/test-diagnostics-channel-module-import.js` | -| diagnostics_channel tracing for module.require events is incomplete | 2 | `parallel/test-diagnostics-channel-module-require-error.js`, `parallel/test-diagnostics-channel-module-require.js` | | dns.resolveAny/Resolver.resolveAny protocol handling is not implemented | 2 | `parallel/test-dns-resolveany-bad-ancount.js`, `parallel/test-dns-resolveany.js` | | domain/setUncaughtExceptionCaptureCallback interaction is incomplete | 2 | `parallel/test-domain-load-after-set-uncaught-exception-capture.js`, `parallel/test-domain-set-uncaught-exception-capture-after-load.js` | | execPath child emulation does not yet support trace-events CLI arg parsing used by -e runs | 2 | `parallel/test-trace-events-fs-async.js`, `parallel/test-trace-events-fs-sync.js` | @@ -791,7 +789,6 @@ Secondary full-public compatibility, including public tests that are currently e | inherited: dgram multicast loopback API is not implemented (ENOSYS) | 2 | `parallel/test-dgram-multicast-loopback.js#block_00_block_00`, `parallel/test-dgram-multicast-loopback.js#block_01_block_01` | | inherited: dgram setBroadcast API is not implemented (ENOSYS) | 2 | `parallel/test-dgram-setBroadcast.js#block_00_block_00`, `parallel/test-dgram-setBroadcast.js#block_01_block_01` | | inherited: listen(options) argument validation/error semantics are not fully Node-compatible | 2 | `parallel/test-net-server-listen-options.js#block_01_block_01`, `parallel/test-net-server-listen-options.js#block_02_block_02` | -| inherited: module syntax detection for extensionless/.js sources required by require(esm) is incomplete | 2 | `es-module/test-require-module-with-detection.js#block_00_block_00`, `es-module/test-require-module-with-detection.js#block_01_block_01` | | inherited: process.getActiveResourcesInfo() is not implemented | 2 | `parallel/test-process-getactiveresources-track-timer-lifetime.js#block_00_block_00`, `parallel/test-process-getactiveresources-track-timer-lifetime.js#block_01_block_01` | | inherited: queueMicrotask argument validation/error codes are incomplete | 2 | `parallel/test-queue-microtask.js#block_00_block_00`, `parallel/test-queue-microtask.js#block_01_block_01` | | inherited: requires perf_hooks.PerformanceObserver with net detail | 2 | `parallel/test-net-perf_hooks.js#block_00_block_00`, `parallel/test-net-perf_hooks.js#block_01_block_01` | @@ -803,8 +800,10 @@ Secondary full-public compatibility, including public tests that are currently e | process.permission worker-thread restrictions are incomplete | 2 | `parallel/test-permission-dc-worker-threads.js`, `parallel/test-permission-worker-threads-cli.js` | | process.report.writeReport and permission-model integration are missing | 2 | `parallel/test-permission-fs-write-report.js#block_00_block_00`, `parallel/test-permission-fs-write-report.js#block_01_block_01` | | promisified exec()/execFile() rejection errors miss stdout/stderr fields | 2 | `parallel/test-child-process-promisified.js#block_04_block_04`, `parallel/test-child-process-promisified.js#block_05_block_05` | -| requires CJS named export analysis (cjs-module-lexer) for ESM import of CJS modules | 2 | `es-module/test-require-module-dynamic-import-1.js`, `es-module/test-require-module-dynamic-import-2.js` | +| requires child process loader/eval flags | 2 | `parallel/test-find-package-json.js#test_08_should_work_within_a_loader`, `parallel/test-find-package-json.js#test_09_should_work_with_async_resolve_hook_registered` | +| requires spawned process.execPath entry-point execution for extensionless ESM files | 2 | `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_00_should_run_as_the_entry_point`, `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_06_should_run_as_the_entry_point` | | spawn() timeout/killSignal behavior is not Node-compatible in WASM emulation | 2 | `parallel/test-child-process-spawn-timeout-kill-signal.js#block_00_block_00`, `parallel/test-child-process-spawn-timeout-kill-signal.js#block_01_block_01` | +| strict assignment to non-writable vm global throws QuickJS read-only wording instead of Node's message | 2 | `parallel/test-vm-global-non-writable-properties.js`, `parallel/test-vm-strict-assign.js` | | tls.connect() stub throws instead of constructing a TLSSocket for allowHalfOpen option checks | 2 | `parallel/test-tls-connect-allow-half-open-option.js#block_00_block_00`, `parallel/test-tls-connect-allow-half-open-option.js#block_01_block_01` | | uncaughtExceptionMonitor event behavior in child_process flows is incomplete | 2 | `parallel/test-process-uncaught-exception-monitor.js#block_00_block_00`, `parallel/test-process-uncaught-exception-monitor.js#block_01_block_01` | | vm timeout interrupt is surfaced as a wasm trap instead of ERR_SCRIPT_EXECUTION_TIMEOUT | 2 | `parallel/test-vm-timeout.js`, `sequential/test-vm-timeout-rethrow.js` | @@ -862,10 +861,6 @@ Secondary full-public compatibility, including public tests that are currently e | ECDH key import/deriveBits compatibility for test vectors is incomplete | 1 | `parallel/test-webcrypto-derivebits-ecdh.js` | | ECDH key import/deriveKey compatibility for test vectors is incomplete | 1 | `parallel/test-webcrypto-derivekey-ecdh.js` | | ECDSA key import/sign/verify compatibility for test vectors is incomplete | 1 | `parallel/test-webcrypto-sign-verify-ecdsa.js` | -| ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG behavior is not implemented | 1 | `parallel/test-vm-dynamic-import-callback-missing-flag.js` | -| ESM diagnostics for require/exports globals and package type=module .js error messaging do not match Node yet | 1 | `es-module/test-esm-undefined-cjs-global-like-variables.js` | -| ESM directory import errors do not match Node ERR_UNSUPPORTED_DIR_IMPORT behavior | 1 | `parallel/test-directory-import.js` | -| ESM<->CJS export interop semantics (including __esModule/default/named export behavior and related errors) are not Node-compatible yet | 1 | `es-module/test-esm-cjs-exports.js` | | EdDSA sign/verify vector compatibility is incomplete | 1 | `parallel/test-webcrypto-sign-verify-eddsa.js` | | Error.prepareStackTrace default behavior is incomplete | 1 | `parallel/test-error-prepare-stack-trace.js` | | EventEmitter captureRejections option validation/behavior is incomplete | 1 | `parallel/test-event-capture-rejections.js` | @@ -887,6 +882,7 @@ Secondary full-public compatibility, including public tests that are currently e | HTTP parser does not emit Node-compatible HPE_INVALID_TRANSFER_ENCODING clientError semantics | 1 | `parallel/test-http-server-reject-chunked-with-content-length.js` | | HTTP parser handling for blank request headers and 400 response framing is incomplete | 1 | `parallel/test-http-blank-header.js` | | HTTP parser/clientError path does not reject duplicate Content-Length with HPE_UNEXPECTED_CONTENT_LENGTH | 1 | `parallel/test-http-double-content-length.js` | +| HTTP request piping into a raw TCP stream with large payloads can hang in current net/http stream backpressure handling | 1 | `sequential/test-pipe.js` | | HTTP request piping with constrained agent sockets can stall queued requests | 1 | `parallel/test-http-pipe-fs.js` | | HTTP request streaming/pipe backpressure behavior is not fully Node-compatible | 1 | `parallel/test-pipe-file-to-http.js` | | HTTP response serialization/header ordering differs from Node for first-chunk single-byte encodings | 1 | `parallel/test-http-outgoing-first-chunk-singlebyte-encoding.js` | @@ -897,6 +893,7 @@ Secondary full-public compatibility, including public tests that are currently e | HTTP server incorrectly emits chunked terminator semantics for 204/304 responses | 1 | `parallel/test-http-chunked-304.js` | | HTTP server parser does not emit Node-compatible HPE_HEADER_OVERFLOW/431 behavior for oversized headers | 1 | `parallel/test-http-header-overflow.js` | | HTTP server socket.setEncoding('') error path (ERR_HTTP_SOCKET_ENCODING) is not Node-compatible | 1 | `parallel/test-http-socket-encoding-error.js` | +| HTTP/1.0 keep-alive client/server framing is not Node-compatible; consistently fails on CI even with retries | 1 | `parallel/test-http-1.0-keep-alive.js` | | HTTP/1.0 keep-alive response connection-closing semantics are not Node-compatible | 1 | `parallel/test-http-wget.js` | | Happy Eyeballs autoSelectFamily over custom dual-stack DNS is not wired through wasi:http transport | 1 | `parallel/test-http-autoselectfamily.js` | | Host header generation ignores globalAgent.defaultPort and incorrectly includes the port | 1 | `parallel/test-http-default-port.js` | @@ -927,7 +924,6 @@ Secondary full-public compatibility, including public tests that are currently e | OutgoingMessage implicit Content-Length/Transfer-Encoding and Connection header behavior is not Node-compatible | 1 | `parallel/test-http-content-length.js` | | OutgoingMessage.getHeaders() shape is not Node-compatible (null-prototype object expected) | 1 | `parallel/test-http-mutable-headers.js` | | Overridden globalAgent socket bookkeeping (agent.sockets/close lifecycle) is not Node-compatible | 1 | `parallel/test-http-client-override-global-agent.js` | -| QuickJS require(esm) bridge reports async-module semantics before surfacing synchronous ESM evaluation errors | 1 | `es-module/test-require-module-error-catching.js` | | QuickJS stack frame formatting differs for Error objects whose name is a non-string object | 1 | `parallel/test-util-inspect.js#block_97_block_97` | | RSA imported-key algorithm metadata compatibility is incomplete | 1 | `parallel/test-webcrypto-encrypt-decrypt-rsa.js` | | RSA key import/export metadata compatibility is incomplete | 1 | `parallel/test-webcrypto-export-import-rsa.js` | @@ -947,7 +943,6 @@ Secondary full-public compatibility, including public tests that are currently e | Resolver.cancel() behavior for in-flight reverse lookups is not implemented | 1 | `parallel/test-dns-cancel-reverse-lookup.js` | | Resolver.cancel() for callback-based in-flight queries is not implemented | 1 | `parallel/test-dns-channel-cancel.js` | | Resolver.cancel() for promise-based in-flight queries is not implemented | 1 | `parallel/test-dns-channel-cancel-promise.js` | -| Script.runInNewContext this-binding/type validation behavior does not match Node | 1 | `parallel/test-vm-new-script-new-context.js#block_07_block_07` | | ServerResponse.addTrailers()/IncomingMessage.trailers behavior is incomplete | 1 | `parallel/test-http-set-trailers.js` | | ServerResponse.end() repeated-call error/callback behavior is not Node-compatible | 1 | `parallel/test-http-outgoing-end-multiple.js` | | ServerResponse.getHeaders() returns a plain object instead of a null-prototype object | 1 | `parallel/test-http-set-header-chain.js` | @@ -956,9 +951,8 @@ Secondary full-public compatibility, including public tests that are currently e | ServerResponse.write() after end does not follow Node-compatible ERR_STREAM_WRITE_AFTER_END behavior | 1 | `parallel/test-http-res-write-after-end.js` | | ServerResponse.writeEarlyHints() argument validation is incomplete (missing expected ERR_INVALID_ARG_VALUE throws) | 1 | `parallel/test-http-early-hints-invalid-argument.js` | | ServerResponse.writeHead() does not throw ERR_HTTP_TRAILER_INVALID when Trailer is set with Content-Length | 1 | `parallel/test-http-server-de-chunked-trailer.js` | -| SourceTextModule import.meta initialization hook is not implemented | 1 | `parallel/test-vm-module-import-meta.js` | -| SourceTextModule linker/dependency parsing semantics are incomplete (imports, cycles, and attributes) | 1 | `parallel/test-vm-module-link.js` | | Timeout listener bookkeeping on keep-alive sockets is not Node-compatible | 1 | `parallel/test-http-client-timeout-option-listeners.js` | +| V8 startup snapshot fixture mutates CommonJS require.cache; the WASM runner does not model Node/V8 startup snapshot and cache coupling | 1 | `es-module/test-esm-snapshot.mjs` | | WASI UDP ping-pong over loopback does not reliably deliver datagrams in the local runtime despite Node-compatible hostname resolution | 1 | `sequential/test-dgram-pingpong.js` | | WASM child emulation does not support --experimental-test-module-mocks CLI flag | 1 | `parallel/test-runner-module-mocking.js#test_11_node_modules_can_be_used_by_both_module_systems` | | WASM child emulation does not support --experimental-test-module-mocks/--experimental-default-type flags | 1 | `parallel/test-runner-module-mocking.js#test_16_wrong_import_syntax_should_throw_error_after_module_mocking` | @@ -1015,7 +1009,11 @@ Secondary full-public compatibility, including public tests that are currently e | child_process execPath emulation does not implement --completion-bash output | 1 | `parallel/test-bash-completion.js` | | child_process execPath emulation does not implement --experimental-print-required-tla diagnostics output | 1 | `es-module/test-require-module-tla.js#block_01_block_01` | | child_process execPath emulation does not yet match Node CLI argument validation/exit codes | 1 | `parallel/test-cli-bad-options.js` | +| child_process execPath emulation does not yet support this ESM/CJS fixture runner path; direct CJS named export interop is covered by test-require-module.js | 1 | `es-module/test-esm-cjs-exports.js` | +| child_process execPath emulation does not yet support this ESM/CJS fixture runner path; same-process CJS import/require interop is covered by module-interop runtime tests | 1 | `es-module/test-esm-cjs-main.js` | +| child_process execPath emulation does not yet support this ESM/CJS fixture runner path; same-process builtin and CJS interop are covered by runtime and node_compat tests | 1 | `es-module/test-esm-cjs-builtins.js` | | child_process execPath emulation has incomplete --require preload/argv handling | 1 | `parallel/test-preload-print-process-argv.js` | +| child_process execPath emulation lacks cwd-relative --require preload resolution for self-referential packages | 1 | `parallel/test-preload-self-referential.js` | | child_process execPath emulation lacks full --import/--require preload semantics | 1 | `es-module/test-require-module-preload.js` | | child_process execPath emulation lacks full NODE_OPTIONS and CLI flag semantics | 1 | `parallel/test-cli-node-options.js` | | child_process fork IPC/stdout stream behavior is incomplete | 1 | `parallel/test-process-external-stdio-close.js` | @@ -1029,7 +1027,6 @@ Secondary full-public compatibility, including public tests that are currently e | client does not emit information event for 100 Continue on custom createConnection streams | 1 | `parallel/test-http-parser-multiple-execute.js` | | clientError does not expose Node-compatible parse error details (missing code HPE_INVALID_TRANSFER_ENCODING) | 1 | `parallel/test-http-invalid-te.js` | | codeGeneration.wasm enforcement is incomplete and WebAssembly is unavailable in the context | 1 | `parallel/test-vm-codegen.js#block_02_block_02` | -| common shim is missing ../common/fixtures.mjs and child_process execPath emulation does not fully support the ESM CLI modes this test exercises (--input-type/--import) | 1 | `es-module/test-esm-import-meta-resolve.mjs` | | common-shim expectWarning() behavior is not implemented | 1 | `parallel/test-common-expect-warning.js` | | common-shim mustCall/countdown failure output differs from Node in child-process emulation | 1 | `parallel/test-common-countdown.js` | | common-shim mustNotCall() error formatting differs from Node's test harness | 1 | `parallel/test-common-must-not-call.js` | @@ -1042,15 +1039,10 @@ Secondary full-public compatibility, including public tests that are currently e | common/gc onGC callback tracking is not implemented in the WASM test shim | 1 | `parallel/test-primitive-timer-leak.js` | | console.* does not publish diagnostics_channel events yet | 1 | `parallel/test-console-diagnostics-channels.js` | | contextCodeGeneration/codeGeneration options do not block string eval with the expected EvalError | 1 | `parallel/test-vm-codegen.js#block_01_block_01` | -| contextified assignment semantics for strict/non-strict writes to non-writable globals are incorrect | 1 | `parallel/test-vm-strict-assign.js` | | contextified global proxy identity/property fallback semantics are incomplete | 1 | `parallel/test-vm-property-not-on-sandbox.js` | -| createContext does not preserve non-enumerable/non-writable sandbox property descriptors | 1 | `parallel/test-vm-preserves-property.js` | -| createContext incorrectly triggers Proxy getOwnPropertyDescriptor traps | 1 | `parallel/test-vm-proxy-failure-CP.js` | -| custom ESM loader hooks (--experimental-loader) and assertionless JSON import behavior are not implemented | 1 | `es-module/test-esm-assertionless-json-import.js` | | decoding empty-passphrase encrypted PEM traps in the WASM crypto backend | 1 | `parallel/test-crypto-keygen-empty-passphrase-no-prompt.js` | | deep async recursion intended to exercise V8 stack recovery can trap the QuickJS/WASM runtime before JavaScript can catch and log the RangeError | 1 | `parallel/test-ttywrap-stack.js` | | default clientError path does not send/close with Node-compatible 400 Bad Request behavior | 1 | `parallel/test-http-server-destroy-socket-on-client-error.js` | -| defining global accessor properties in vm contexts does not round-trip to the sandbox correctly | 1 | `parallel/test-vm-global-define-property.js` | | depends on WebCrypto ECDH P-521 deriveKey support | 1 | `parallel/test-webcrypto-derivekey.js#block_03_test_default_key_lengths` | | destroying zlib Transform with in-flight pipe data has callback/event ordering differences | 1 | `parallel/test-zlib-destroy-pipe.js` | | dgram bind path does not invoke default dns.lookup | 1 | `parallel/test-dgram-custom-lookup.js#block_01_block_01` | @@ -1083,7 +1075,6 @@ Secondary full-public compatibility, including public tests that are currently e | dotenv CLI --env-file parsing is incomplete | 1 | `parallel/test-dotenv.js` | | dotenv CLI flags are incomplete in execPath child emulation | 1 | `parallel/test-dotenv-edge-cases.js` | | duplicate Set-Cookie response header handling/lifecycle is not fully Node-compatible | 1 | `parallel/test-http-set-cookies.js` | -| dynamic import callback handling does not correctly support module namespace return values | 1 | `parallel/test-vm-module-dynamic-namespace.js` | | emulated child_process inline eval does not keep the child alive for dynamic import() resolution | 1 | `parallel/test-runner-import-no-scheme.js` | | events.EventEmitterAsyncResource API and ERR_INVALID_THIS branding are incomplete | 1 | `parallel/test-eventemitter-asyncresource.js` | | events.once() with EventTarget does not handle sequential waits correctly | 1 | `parallel/test-eventtarget-once-twice.js` | @@ -1118,11 +1109,8 @@ Secondary full-public compatibility, including public tests that are currently e | fs.writeFile(fd, ...) on read-only descriptor does not callback with EBADF | 1 | `parallel/test-fs-writefile-with-fd.js#block_02_test_read_only_file_descriptor` | | fs.writeFileSync accepts invalid data types instead of ERR_INVALID_ARG_TYPE | 1 | `parallel/test-fs-write-file-sync.js#block_05_test_writefilesync_with_an_invalid_input` | | fs/promises FileHandle.readableWebStream support is missing or incomplete | 1 | `parallel/test-filehandle-readablestream.js` | -| function declaration/global binding semantics in vm contexts are incomplete | 1 | `parallel/test-vm-function-declaration.js` | -| function declarations are not persisted correctly across vm.runInContext calls | 1 | `parallel/test-vm-function-redefinition.js` | | global performance object lacks Node perf_hooks API surface | 1 | `parallel/test-performance-global.js` | | global process/Buffer accessor setter semantics are incomplete | 1 | `parallel/test-global-setters.js` | -| global property descriptor/interceptor behavior in vm contexts is incomplete | 1 | `parallel/test-vm-global-property-interceptors.js` | | global web streams and node:stream/web exports are inconsistent | 1 | `parallel/test-global-webstreams.js` | | globalThis shape differs from Node.js | 1 | `parallel/test-global.js` | | half-open/pipelined HTTP/1.1 server behavior is not fully Node-compatible | 1 | `parallel/test-http-server.js` | @@ -1139,9 +1127,7 @@ Secondary full-public compatibility, including public tests that are currently e | http.get({ createConnection }) callback/return-value and async error propagation semantics are incomplete | 1 | `parallel/test-http-createConnection.js` | | http.request host header formatting for IPv6 literals is incorrect (missing [::1]:port form) | 1 | `parallel/test-http-host-header-ipv6-fail.js` | | https socket lifecycle/unref semantics over wasi:http are incomplete | 1 | `parallel/test-https-agent-unref-socket.js` | -| importModuleDynamically callback and error semantics are incomplete for vm.Script and vm.SourceTextModule | 1 | `parallel/test-vm-module-dynamic-import.js` | | importing scrypt-encrypted PKCS#8 keys traps in the WASM crypto backend | 1 | `parallel/test-crypto-key-objects.js#block_05_block_05` | -| indexed property definitions on vm globals do not propagate to the sandbox | 1 | `parallel/test-vm-indexed-properties.js` | | inherited: Resolver#setLocalAddress validation/error behavior is not implemented | 1 | `parallel/test-dns-setlocaladdress.js#block_01_verify_that_setlocaladdress_throws_if_called_with_an_invalid` | | invalid EC private keys do not raise Node-compatible DataError | 1 | `parallel/test-webcrypto-export-import-ec.js#block_01_bad_private_keys` | | invalid repeated Transfer-Encoding handling differs from Node | 1 | `parallel/test-http-transfer-encoding-repeated-chunked.js` | @@ -1150,15 +1136,15 @@ Secondary full-public compatibility, including public tests that are currently e | keep-alive socket reuse plus drain/backpressure behavior for corked responses is not Node-compatible | 1 | `parallel/test-http-outgoing-end-cork.js` | | keep-alive socket timeout/reuse race handling is not Node-compatible | 1 | `parallel/test-http-keep-alive-timeout-race-condition.js` | | large raw pipelined request load (10k) exhausts current WASM/runtime resources | 1 | `parallel/test-http-pipeline-requests-connection-leak.js` | -| legacy punycode builtin is not wired into CommonJS module resolution | 1 | `parallel/test-punycode.js` | +| loader hooks in this vendored file are exercised through spawned process.execPath CLI loader flags/eval, deferred to simulated Node CLI mode support | 1 | `es-module/test-esm-loader-hooks.mjs` | | maxRequestsPerSocket keep-alive header behavior (Keep-Alive/Connection framing) is not Node-compatible | 1 | `parallel/test-http-keep-alive-max-requests.js` | -| missing importModuleDynamically callback does not raise ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING | 1 | `parallel/test-vm-no-dynamic-import-callback.js` | | mixed headersTimeout/requestTimeout handling is not Node-compatible | 1 | `sequential/test-http-server-request-timeouts-mixed.js` | -| module cache behavior with circular symlinked dependencies is not Node-compatible | 1 | `parallel/test-module-circular-symlinks.js` | | moveMessagePortToContext cross-context object/prototype semantics are incomplete | 1 | `parallel/test-worker-message-port-move.js` | | native rquickjs URL accessors report Rust conversion errors for invalid receivers before JS can normalize them to V8/Web IDL private-member messages | 1 | `parallel/test-whatwg-url-invalidthis.js` | | native rquickjs URL class property enumeration order does not match Web IDL order and descriptors are not fully configurable from JS | 1 | `parallel/test-whatwg-url-custom-properties.js` | | net reusePort listen option/support probing is incomplete | 1 | `parallel/test-net-reuseport.js` | +| net write backpressure/drain handling for repeated large Buffer writes can hang in the WASM socket implementation | 1 | `parallel/test-net-write-fully-async-buffer.js` | +| net.BlockList with autoSelectFamily and multiple lookup addresses does not yet raise ERR_IP_BLOCKED before connection attempts | 1 | `parallel/test-net-blocklist.js#block_03_connect_with_autoselectfamily_and_multiple_ips` | | net.Server blockList enforcement is incomplete | 1 | `parallel/test-net-server-blocklist.js` | | net.Server captureRejections async error propagation is incomplete | 1 | `parallel/test-net-server-capture-rejection.js` | | node-compat runner drainAsync() relies on global setTimeout after this test deletes timer globals | 1 | `parallel/test-timers-api-refs.js` | @@ -1168,8 +1154,6 @@ Secondary full-public compatibility, including public tests that are currently e | node:http client socketPath transport flow is incomplete (unix-socket request hangs) | 1 | `parallel/test-http-client-pipe-end.js` | | node:https Agent constructor compatibility is incomplete (call without new) | 1 | `parallel/test-https-agent-constructor.js` | | node:https Agent#getName TLS option keying is incomplete | 1 | `parallel/test-https-agent-getname.js` | -| node:module does not implement package.json exports condition resolution (module-sync/require/import/default) | 1 | `es-module/test-require-module-conditional-exports-module.js` | -| node:module.findPackageJSON API behavior is incomplete | 1 | `parallel/test-find-package-json.js` | | node:sqlite applyChangeset conflict-resolution behavior is incomplete | 1 | `parallel/test-sqlite-session.js#test_05_conflict_resolution` | | node:sqlite rejects mixed named+positional parameters where Node accepts them | 1 | `parallel/test-sqlite-statement-sync.js#test_06_statementsync_prototype_expandedsql` | | node:test mock timers Date behavior is incomplete | 1 | `parallel/test-runner-mock-timers-date.js` | @@ -1177,14 +1161,11 @@ Secondary full-public compatibility, including public tests that are currently e | node:test standalone runner output/cancellation summary differs in WASM child emulation | 1 | `parallel/test-runner-misc.js` | | node:test t.assert.fileSnapshot validation behavior is incomplete | 1 | `parallel/test-runner-snapshot-file-tests.js#test_00_t_assert_filesnapshot_validation` | | node:test t.assert.ok does not preserve Node-compatible assertion stack formatting | 1 | `parallel/test-runner-assert.js#test_01_t_assert_ok_correctly_parses_the_stacktrace` | -| node:vm does not yet support importModuleDynamically/SyntheticModule semantics used by this dynamic import lifetime test | 1 | `es-module/test-dynamic-import-script-lifetime.js` | | node_compat harness copies only the target test file, so required sibling ./test-tls-destroy-stream.js is missing | 1 | `parallel/test-tls-destroy-stream-12.js` | | node_compat harness copies only the target test file, so required sibling ./test-tls-net-socket-keepalive.js is missing | 1 | `parallel/test-tls-net-socket-keepalive-12.js` | -| node_compat harness does not provide ../common/shared-lib-util for this test setup | 1 | `parallel/test-module-loading-globalpaths.js` | | node_compat test fixture module ../common/process-exit-code-cases is not resolved in this runtime | 1 | `parallel/test-process-exit-code.js` | -| non-writable global property semantics in vm contexts are incomplete | 1 | `parallel/test-vm-global-non-writable-properties.js` | +| non-writable vm global assignment throws QuickJS read-only wording instead of Node's Cannot redefine property message | 1 | `parallel/test-vm-global-setter.js` | | options.agent validation/lifecycle is not fully Node-compatible | 1 | `parallel/test-http-client-reject-unexpected-agent.js` | -| package resolution from ESM (node_modules dependency without package.json) is incomplete | 1 | `es-module/test-require-module.js#block_04_also_test_default_export` | | passive listener semantics are incomplete (test currently self-skips) | 1 | `parallel/test-whatwg-events-add-event-listener-options-passive.js#block_01_block_01` | | per-context Symbol/global binding behavior is incomplete in vm contexts | 1 | `parallel/test-vm-harmony-symbols.js` | | perf_hooks HTTP PerformanceEntry emission/detail fields are incomplete | 1 | `parallel/test-http-perf_hooks.js` | @@ -1199,9 +1180,7 @@ Secondary full-public compatibility, including public tests that are currently e | postMessage function cloning should throw DataCloneError | 1 | `parallel/test-worker-message-port-transfer-native.js#block_00_block_00` | | postMessage transferList argument validation is not Node-compatible yet | 1 | `parallel/test-worker-message-port.js#block_05_block_05` | | posting a port to its target and channel-loss warning semantics are incomplete | 1 | `parallel/test-worker-message-port-transfer-target.js` | -| preload module handling edge case | 1 | `parallel/test-preload-self-referential.js` | | process 'multipleResolves' event semantics are not implemented | 1 | `parallel/test-promise-swallowed-event.js` | -| process object tagging differs from Node (Object.prototype.toString.call(process)) | 1 | `parallel/test-vm-basic.js#block_02_vm_runinthiscontext` | | process prototype chain is not fully Node-compatible (prototype is not EventEmitter-based) | 1 | `parallel/test-process-prototype.js` | | process uncaughtException handling inside http client callbacks is incomplete | 1 | `parallel/test-http-catch-uncaughtexception.js` | | process unhandledRejection/warning semantics are incomplete | 1 | `parallel/test-promise-handled-rejection-no-warning.js` | @@ -1217,6 +1196,7 @@ Secondary full-public compatibility, including public tests that are currently e | process.setuid()/process.setgid() are stubbed and do not mutate credentials | 1 | `parallel/test-process-uid-gid.js` | | process.stdin.destroy() is not implemented | 1 | `parallel/test-net-listen-after-destroying-stdin.js` | | process.stdout is not yet a full stream.Writable implementation | 1 | `parallel/test-stdout-pipeline-destroy.js` | +| programmatic loader registration in this vendored file is exercised through spawned process.execPath --eval/--import/--loader CLI mode | 1 | `es-module/test-esm-loader-programmatically.mjs` | | rawHeaders/rawTrailers casing and duplicate-header preservation differ from Node semantics | 1 | `parallel/test-http-raw-headers.js` | | rawHeaders/rawTrailers duplicate-header ordering and casing are not Node-compatible | 1 | `parallel/test-http-multiple-headers.js` | | receiveBlockList filtering/close behavior is incomplete | 1 | `parallel/test-dgram-blocklist.js#block_02_block_02` | @@ -1229,28 +1209,31 @@ Secondary full-public compatibility, including public tests that are currently e | request drain captureRejections path hangs when request is never finalized with end() under wasi:http | 1 | `parallel/test-http-outgoing-message-capture-rejection.js#block_01_block_01` | | request header population/normalization (for example Accept) is incomplete | 1 | `parallel/test-http.js` | | request/response pause-resume flow control does not complete with Node-compatible behavior | 1 | `parallel/test-http-pause.js` | -| require(esm) rejection handling does not match Node behavior (unexpected unhandledRejection) | 1 | `es-module/test-require-module-synchronous-rejection-handling.js` | | requires ERR_INVALID_ARG_TYPE validation on resolve methods (not yet implemented) | 1 | `parallel/test-dns-resolvens-typeerror.js` | | requires HTTP server functionality, we only support clients | 1 | `parallel/test-diagnostic-channel-http-response-created.js` | | requires Intl/timezone data support that is not available in the current runtime | 1 | `parallel/test-datetime-change-notify.js` | | requires V8-style GC/finalization behavior for rapidly churned HTTP client requests; current QuickJS/WASM runtime does not collect all watched request objects reliably | 1 | `parallel/test-gc-http-client-connaborted.js` | | requires V8-style GC/finalization behavior for rapidly churned net sockets with timeouts; current QuickJS/WASM runtime does not collect all watched socket objects reliably | 1 | `parallel/test-gc-net-timeout.js` | | requires actual TCP socket reuse with remotePort identity tracking via server; wasi:http creates new connections per request | 1 | `parallel/test-http-agent-scheduling.js` | +| requires child_process execFileSync with copied process.execPath and Node global module path layout | 1 | `parallel/test-module-loading-globalpaths.js` | | requires createConnection to forward keepAlive/keepAliveInitialDelay options; wasi:http does not use Agent.createConnection for outbound requests | 1 | `parallel/test-http-agent-keepalive-delay.js` | | requires fd option for listen | 1 | `parallel/test-net-listen-fd0.js` | | requires net.createServer with pauseOnConnect and socket.localPort; wasi:http does not expose socket-level properties | 1 | `parallel/test-http-agent-reuse-drained-socket-only.js` | | requires onread option with buffer/callback | 1 | `parallel/test-net-onread-static-buffer.js` | | requires raw TCP response with obsolete HTTP line-folded headers; wasi:http rejects them | 1 | `parallel/test-http-multi-line-headers.js` | | requires remote server close detection on idle keep-alive sockets and socket hang up errors; wasi:http creates independent connections per request with no shared socket lifecycle | 1 | `parallel/test-http-agent-keepalive.js` | +| requires simulated Node CLI flag handling for --no-experimental-require-module/--experimental-detect-module | 1 | `es-module/test-disable-require-module-with-detection.js` | +| requires simulated process.execPath / Node CLI mode support for child-process JSON module warning assertions | 1 | `es-module/test-esm-json.mjs#test_01_should_not_print_an_experimental_warning` | +| requires simulated process.execPath / Node CLI module_timer and trace-event support | 1 | `parallel/test-module-print-timing.mjs` | +| requires spawned process.execPath --check execution with --experimental-default-type=module | 1 | `es-module/test-esm-type-flag-loose-files.mjs#test_06_should_check_as_esm_input_passed_via_check` | +| requires spawned process.execPath entry-point execution to verify extensionless .wasm classification outside module scope | 1 | `es-module/test-esm-extensionless-esm-and-wasm.mjs#test_08_should_error_as_the_entry_point` | | response writable state around aborted proxy close is not Node-compatible | 1 | `parallel/test-http-writable-true-after-close.js` | | response write + socket-error path does not preserve the expected truncated raw HTTP ending | 1 | `parallel/test-http-header-badrequest.js` | -| runInContext does not preserve symbol/prototype property access on contextified objects | 1 | `parallel/test-vm-symbols.js` | +| runInContext sloppy-mode var/delete semantics still require contextified global script bindings | 1 | `parallel/test-vm-not-strict.js` | | runInNewContext assignment with Proxy sandbox does not match Node trap behavior | 1 | `parallel/test-vm-set-property-proxy.js` | -| runInNewContext does not propagate global writes back to the sandbox correctly | 1 | `parallel/test-vm-new-script-new-context.js#block_04_block_04` | -| runInNewContext own-vs-inherited property assignment semantics are incomplete | 1 | `parallel/test-vm-inherited_properties.js` | -| runInNewContext sandbox binding and write-back semantics are incomplete | 1 | `parallel/test-vm-run-in-new-context.js` | -| runInThisContext/runInContext sloppy-mode var/delete semantics are incorrect | 1 | `parallel/test-vm-not-strict.js` | | same-component node:http client->server calls via wasi:http can deadlock in this scenario | 1 | `parallel/test-http-write-head-after-set-header.js` | +| same-process dynamic import cache behavior is covered by runner_dynamic_import_cache_survives_removed_file; full Node test also requires spawned process.execPath --input-type=module support | 1 | `es-module/test-esm-dynamic-import-mutating-fs.mjs` | +| same-process import.meta.resolve behavior is covered by runtime tests; remaining vendored failure requires child_process execPath emulation for --input-type/--import ESM CLI modes | 1 | `es-module/test-esm-import-meta-resolve.mjs` | | sendBlockList connect path can crash in WASI UDP implementation | 1 | `parallel/test-dgram-blocklist.js#block_00_block_00` | | sendBlockList send() callback path is not Node-compatible and can hang | 1 | `parallel/test-dgram-blocklist.js#block_01_block_01` | | sequential path is stale in vendored suite; equivalent Upgrade timeout-disabling semantics are not Node-compatible | 1 | `sequential/test-http-server-request-timeout-upgrade.js` | @@ -1286,7 +1269,6 @@ Secondary full-public compatibility, including public tests that are currently e | setImmediate queue turn semantics are unstable and can trap in the timeout scheduler | 1 | `parallel/test-timers-immediate-queue.js` | | setInterval scheduling incorrectly includes callback execution time | 1 | `sequential/test-timers-set-interval-excludes-callback-duration.js` | | snapshot update/read flow via node:test is incomplete in WASM child emulation | 1 | `parallel/test-runner-snapshot-file-tests.js#test_01_t_assert_filesnapshot_update_read_flow` | -| source-map cache eviction via findSourceMap()/GC is incomplete | 1 | `parallel/test-source-map-cjs-require-cache.js` | | spawn() stdio handling is incomplete: non-requested stderr stream is still created | 1 | `sequential/test-child-process-exit.js` | | spawn() timeout validation path hangs in WASM child emulation | 1 | `parallel/test-child-process-spawn-timeout-kill-signal.js#block_02_block_02` | | spawn() timeout+AbortSignal cleanup path hangs in WASM child emulation | 1 | `parallel/test-child-process-spawn-timeout-kill-signal.js#block_03_block_03` | @@ -1300,7 +1282,6 @@ Secondary full-public compatibility, including public tests that are currently e | stream.finished() behavior for destroyed IncomingMessage is not Node-compatible | 1 | `parallel/test-http-client-finished.js` | | stream.write()/console.log tick scheduling is not fully Node-compatible | 1 | `parallel/test-stream-writable-samecb-singletick.js` | | stream/web compression constructor error codes are not Node-compatible yet | 1 | `parallel/test-whatwg-webstreams-compression.js` | -| strict-mode assignment semantics in vm contexts differ from Node | 1 | `parallel/test-vm-strict-mode.js` | | subtle.digest unsupported-algorithm error semantics do not match Node | 1 | `parallel/test-webcrypto-digest.js` | | timeout option does not reliably emit request timeout before close | 1 | `parallel/test-http-client-timeout-option.js` | | timers/promises scheduler constructor and error-code semantics are not fully Node-compatible | 1 | `parallel/test-timers-promises-scheduler.js` | @@ -1317,37 +1298,21 @@ Secondary full-public compatibility, including public tests that are currently e | uncaughtException handling after response end can stall socket cleanup | 1 | `parallel/test-http-end-throw-socket-handling.js` | | uncaughtException rethrow exit-code semantics are incomplete | 1 | `parallel/test-unhandled-exception-rethrow-error.js` | | uses V8 native %GetUndetectable() syntax which QuickJS cannot evaluate | 1 | `parallel/test-util-inspect.js#block_83_https_github_com_nodejs_node_issues_31889` | +| uses child_process spawn path (spawnPromisified) | 1 | `parallel/test-find-package-json.js#test_07_should_resolve_root_and_closest_package_json` | | util.MIMEType parsing API is not implemented | 1 | `parallel/test-mime-whatwg.js` | | util.MIMEType/util.MIMEParams are not implemented | 1 | `parallel/test-mime-api.js` | | util.debuglog formatting/callback behavior is not fully Node-compatible | 1 | `sequential/test-util-debug.js` | | v8.setFlagsFromString argument validation error fidelity is incomplete | 1 | `parallel/test-v8-flag-type-check.js` | | valid EC key vectors fail to import | 1 | `parallel/test-webcrypto-export-import-ec.js#block_00_block_00` | | verify() returns non-Node error code/message for unsupported key types | 1 | `parallel/test-crypto-sign-verify.js#block_18_block_18` | -| vm context does not preserve sandbox getter descriptors on the global object | 1 | `parallel/test-vm-getters.js` | | vm context global object identity/proxy semantics differ from Node | 1 | `parallel/test-vm-global-identity.js` | -| vm context global property enumeration includes unexpected runtime globals | 1 | `parallel/test-vm-global-property-enumerator.js` | -| vm context property descriptor behavior is incomplete for sandbox accessors | 1 | `parallel/test-vm-attributes-property-not-on-sandbox.js` | -| vm context property forwarding and indexed descriptor behavior are incomplete | 1 | `parallel/test-vm-context-property-forwarding.js` | | vm context prototype chain and own-property lookup semantics are incomplete | 1 | `parallel/test-vm-global-property-prototype.js` | -| vm contextification does not propagate var/global writes correctly | 1 | `parallel/test-vm-create-and-run-in-context.js` | -| vm contextification write-back and runInContext semantics are incomplete | 1 | `parallel/test-vm-context.js` | | vm contexts do not provide the expected per-context Proxy behavior | 1 | `parallel/test-vm-proxies.js` | -| vm global getter/setter descriptors are not exposed correctly on contextified objects | 1 | `parallel/test-vm-global-setter.js` | -| vm run* filename option does not set stack trace file locations correctly | 1 | `parallel/test-vm-basic.js#block_05_run_script_with_filename` | -| vm.Module/SourceTextModule state machine and Node-compatible error validation are incomplete | 1 | `parallel/test-vm-module-errors.js` | -| vm.Script constructor/run option validation and error codes are incomplete | 1 | `parallel/test-vm-options-validation.js` | -| vm.Script.sourceMapURL parsing for //# sourceMappingURL comments is not implemented | 1 | `parallel/test-vm-source-map-url.js` | -| vm.SyntheticModule API behavior is missing/incomplete | 1 | `parallel/test-vm-module-synthetic.js` | -| vm.USE_MAIN_CONTEXT_DEFAULT_LOADER behavior for dynamic import resolution is incomplete | 1 | `es-module/test-vm-main-context-default-loader.js` | -| vm.compileFunction options range validation (lineOffset/columnOffset) is incomplete | 1 | `es-module/test-vm-compile-function-lineoffset.js` | -| vm.compileFunction validation, options handling, and error fidelity are incomplete | 1 | `parallel/test-vm-basic.js#block_06_vm_compilefunction` | -| vm.createContext argument type validation and error codes are incomplete | 1 | `parallel/test-vm-create-context-arg.js` | -| vm.createContext argument validation and error codes are incomplete | 1 | `parallel/test-vm-basic.js#block_04_vm_createcontext` | -| vm.createContext does not preserve sandbox accessor properties during evaluation | 1 | `parallel/test-vm-create-context-accessors.js` | -| vm.createContext options argument validation and error fidelity are incomplete | 1 | `parallel/test-vm-basic.js#block_03_vm_runinnewcontext` | -| vm.isContext argument validation and TypeError behavior are incomplete | 1 | `parallel/test-vm-is-context.js` | -| vm.runInContext contextification/write-back semantics are incomplete | 1 | `parallel/test-vm-basic.js#block_01_vm_runincontext` | -| vm.runInNewContext does not propagate global writes back to the sandbox object | 1 | `parallel/test-vm-basic.js#block_00_vm_runinnewcontext` | +| vm inherited sandbox property lookup/assignment semantics require contextified global proxy behavior | 1 | `parallel/test-vm-inherited_properties.js` | +| vm non-configurable global redefine throws QuickJS wording instead of Node's Cannot redefine property message | 1 | `parallel/test-vm-global-property-interceptors.js` | +| vm prototype method lookup requires contextified global proxy behavior without exposing the sandbox prototype | 1 | `parallel/test-vm-symbols.js` | +| vm runInContext filename stack formatting with lineOffset/columnOffset is incomplete | 1 | `parallel/test-vm-context.js` | +| vm runInNewContext filename stack formatting is incomplete | 1 | `parallel/test-vm-run-in-new-context.js` | | wasi module and --permission integration are incomplete | 1 | `parallel/test-permission-wasi.js` | | wasi:http client does not surface HPE_INVALID_TRANSFER_ENCODING parse errors from raw TCP responses | 1 | `parallel/test-http-client-reject-chunked-with-content-length.js` | | wasi:http client does not surface HPE_LF_EXPECTED parse errors from raw TCP responses | 1 | `parallel/test-http-client-reject-cr-no-lf.js` | @@ -1370,7 +1335,7 @@ Secondary full-public compatibility, including public tests that are currently e | zlib invalid compressed input error event/callback behavior differs from Node | 1 | `parallel/test-zlib-invalid-input.js` | | zlib stream bytesWritten/bytesRead accounting and end/data callbacks differ from Node | 1 | `parallel/test-zlib-bytes-read.js` | -### WASI-impossible (1153) +### WASI-impossible (1156) | Reason | Count | Example entries | |--------|-------|-----------------| @@ -1454,6 +1419,7 @@ Secondary full-public compatibility, including public tests that are currently e | depends on real worker_threads exit-event behavior across a separate JS context, which is not available in single-threaded WASM | 1 | `parallel/test-worker-on-process-exit.js` | | depends on real worker_threads terminate() interrupting an in-flight DNS query, which is not available in single-threaded WASM | 1 | `parallel/test-worker-dns-terminate-during-query.js` | | depends on worker_threads-based event loop utilization behavior | 1 | `parallel/test-performance-eventlooputil.js` | +| fixture depends on worker_threads MessageChannel and receiveMessageOnPort support, unavailable in the single-threaded WASM runtime | 1 | `es-module/test-esm-loader-mock.mjs` | | host signal delivery and SIGINT interruption semantics are not available in WASI | 1 | `parallel/test-sigint-infinite-loop.js` | | http2 is not implemented | 1 | `parallel/test-http2-compat-client-upload-reject.js` | | https.createServer (TLS server) is not supported in WebAssembly environment | 1 | `sequential/test-https-connect-localport.js` | @@ -1487,6 +1453,7 @@ Secondary full-public compatibility, including public tests that are currently e | requires child_process.exec subprocess behavior | 1 | `parallel/test-error-reporting.js` | | requires child_process.exec which is not available in WASM | 1 | `parallel/test-child-process-exec-cwd.js` | | requires child_process.execSync which is not available in WASM | 1 | `parallel/test-domain-abort-on-uncaught.js` | +| requires child_process.fork IPC semantics, which are not available in WASM | 1 | `es-module/test-esm-child-process-fork-main.mjs` | | requires child_process.fork(), which is unavailable in WASI | 1 | `parallel/test-http-server-stale-close.js` | | requires child_process.spawn of a separate Node process to reproduce stack-overflow behavior | 1 | `sequential/test-fs-stat-sync-overflow.js` | | requires child_process.spawn of a separate server process | 1 | `sequential/test-net-response-size.js` | @@ -1526,6 +1493,7 @@ Secondary full-public compatibility, including public tests that are currently e | requires worker_threads to interrupt generatePrime; worker_threads is unavailable in WASM | 1 | `parallel/test-crypto-prime.js#block_09_block_09` | | requires worker_threads trace propagation | 1 | `parallel/test-trace-events-async-hooks-worker.js` | | requires worker_threads, which are unavailable in WASM | 1 | `sequential/test-vm-break-on-sigint.js` | +| requires worker_threads/native addon process isolation semantics, which are not available in single-threaded WASM | 1 | `es-module/test-esm-no-addons.mjs` | | sending host process signals is not supported in WASI | 1 | `parallel/test-process-kill-null.js` | | test is gated to Linux/macOS/Windows shell behavior and excludes WASI | 1 | `parallel/test-stdin-from-file-spawn.js` | | tests Worker terminate() during http2.respondWithFile() in the worker; requires real worker_threads execution which is not available in single-threaded WASM | 1 | `parallel/test-worker-terminate-http2-respond-with-file.js` | @@ -1540,7 +1508,7 @@ Secondary full-public compatibility, including public tests that are currently e | wasi:http does not expose custom HTTP reason phrases (status messages) | 1 | `parallel/test-http-response-status-message.js` | | wasi:http normalizes response header names, so raw header case preservation assertions cannot be satisfied | 1 | `parallel/test-http-write-head.js` | -### engine difference (162) +### engine difference (164) | Reason | Count | Example entries | |--------|-------|-----------------| @@ -1559,6 +1527,7 @@ Secondary full-public compatibility, including public tests that are currently e | targets V8 external string internals/limits that QuickJS does not replicate | 3 | `parallel/test-stringbytes-external.js#block_00_block_00`, `parallel/test-stringbytes-external.js#block_01_block_01`, `parallel/test-stringbytes-external.js#block_02_https_github_com_nodejs_node_issues_1024` | | --heapsnapshot-signal depends on V8 heap snapshot support, unavailable in QuickJS | 2 | `sequential/test-heapdump-flag-custom-dir.js`, `sequential/test-heapdump-flag.js` | | --use-largepages is a V8 startup flag not applicable to QuickJS/WASM | 2 | `parallel/test-startup-large-pages.js#block_00_block_00`, `parallel/test-startup-large-pages.js#block_01_block_01` | +| uses common/gc checkIfCollectableByCounting, which depends on V8-only v8.queryObjects | 2 | `es-module/test-vm-source-text-module-leak.js`, `parallel/test-diagnostics-channel-memory-leak.js` | | v8.getHeapSnapshot is V8-specific and unavailable in QuickJS | 2 | `parallel/test-heapdump-async-hooks-init-promise.js`, `parallel/test-v8-getheapsnapshot-twice.js` | | v8.serialize/deserialize are V8-specific and unavailable in QuickJS | 2 | `parallel/test-v8-deserialize-buffer.js`, `parallel/test-v8-serialize-leak.js` | | v8.writeHeapSnapshot is a V8-specific API and is unavailable in QuickJS | 2 | `parallel/test-permission-fs-write-v8.js#block_00_block_00`, `parallel/test-permission-fs-write-v8.js#block_01_block_01` | @@ -1572,8 +1541,8 @@ Secondary full-public compatibility, including public tests that are currently e | depends on V8 native syntax and runtime flags not available in QuickJS | 1 | `parallel/test-v8-flags.js` | | depends on engine-specific ArrayBuffer OOM RangeError message text in skip path | 1 | `sequential/test-buffer-creation-regression.js` | | expects V8 heap space statistics that QuickJS does not expose | 1 | `parallel/test-v8-stats.js` | +| native QuickJS Error.prepareStackTrace CallSite positions include the CJS wrapper offset | 1 | `parallel/test-source-map-api.js#block_03_source_map_attached_to_error` | | uses V8 natives syntax intrinsics (`%DebugPrint`, `%HaveSameMap`, `%CollectGarbage`) unavailable in QuickJS | 1 | `parallel/test-http-same-map.js` | -| uses common/gc checkIfCollectableByCounting, which depends on V8-only v8.queryObjects | 1 | `parallel/test-diagnostics-channel-memory-leak.js` | | uses v8.getHeapSnapshot, which is V8-specific and unavailable in QuickJS | 1 | `parallel/test-http2-ping-settings-heapdump.js` | | v8.cachedDataVersionTag depends on V8 internals unavailable in QuickJS | 1 | `parallel/test-v8-version-tag.js` | | v8.getHeapSnapshot heap-introspection behavior is V8-specific and unavailable in QuickJS | 1 | `sequential/test-get-heapsnapshot-options.js` | @@ -1586,7 +1555,7 @@ Secondary full-public compatibility, including public tests that are currently e _No entries._ -### Node.js internals (1121) +### Node.js internals (1122) | Reason | Count | Example entries | |--------|-------|-----------------| @@ -1655,6 +1624,7 @@ _No entries._ | inherited: requires --expose-internals and internalBinding('cares_wrap') to stub getaddrinfo | 3 | `parallel/test-dns-lookup.js#block_00_block_00`, `parallel/test-dns-lookup.js#block_01_block_01`, `parallel/test-dns-lookup.js#block_02_block_02` | | inherited: uses --expose-internals with dgram._createSocketHandle and internal/test/binding | 3 | `parallel/test-dgram-create-socket-handle-fd.js#block_00_return_a_negative_number_if_the_existing_fd_is_invalid`, `parallel/test-dgram-create-socket-handle-fd.js#block_01_return_a_negative_number_if_the_type_of_fd_is_not_udp`, `parallel/test-dgram-create-socket-handle-fd.js#block_02_create_a_bound_handle` | | requires --expose-internals and internal/options | 3 | `parallel/test-options-binding.js`, `parallel/test-pending-deprecation.js`, `parallel/test-worker-cli-options.js` | +| requires --expose-internals and node:internal/modules/esm/resolve | 3 | `es-module/test-cjs-legacyMainResolve-permission.js`, `es-module/test-cjs-legacyMainResolve.js`, `es-module/test-esm-resolve-type.mjs` | | requires internal/test/binding and internalBinding('js_stream') | 3 | `parallel/test-util-types.js#block_00_block_00`, `parallel/test-util-types.js#block_01_block_01`, `parallel/test-util-types.js#block_02_block_02` | | requires internal/test/binding internalBinding('tcp_wrap') | 3 | `parallel/test-tcp-wrap-connect.js`, `parallel/test-tcp-wrap-listen.js`, `parallel/test-tcp-wrap.js` | | uses --expose-internals and internal/errors AbortError | 3 | `parallel/test-errors-aborterror.js#block_00_block_00`, `parallel/test-errors-aborterror.js#block_01_block_01`, `parallel/test-errors-aborterror.js#block_02_block_02` | @@ -1676,7 +1646,6 @@ _No entries._ | requires --expose-internals and internal/error_serdes | 2 | `sequential/test-error-serdes.js#block_00_block_00`, `sequential/test-error-serdes.js#block_01_block_01` | | requires --expose-internals and internal/priority_queue | 2 | `parallel/test-priority-queue.js#block_00_block_00`, `parallel/test-priority-queue.js#block_01_block_01` | | requires --expose-internals and internal/timers | 2 | `parallel/test-child-process-http-socket-leak.js`, `parallel/test-tls-wrap-timeout.js` | -| requires --expose-internals and node:internal/modules/esm/resolve | 2 | `es-module/test-cjs-legacyMainResolve-permission.js`, `es-module/test-cjs-legacyMainResolve.js` | | requires --expose-internals and require('internal/js_stream_socket') | 2 | `parallel/test-stream-wrap-encoding.js#block_00_block_00`, `parallel/test-stream-wrap-encoding.js#block_01_block_01` | | requires --expose-internals plus internal/js_stream_socket and internalBinding('stream_wrap') | 2 | `parallel/test-stream-wrap-drain.js`, `parallel/test-stream-wrap.js` | | requires internal _tls_common module | 2 | `parallel/test-tls-translate-peer-certificate.js#block_00_block_00`, `parallel/test-tls-translate-peer-certificate.js#block_01_block_01` | diff --git a/tests/node_compat_config_report.rs b/tests/node_compat_config_report.rs index 60f84264..e709616f 100644 --- a/tests/node_compat_config_report.rs +++ b/tests/node_compat_config_report.rs @@ -18,12 +18,14 @@ use common::{ NodeCompatCategory, NodeCompatTestEntry, classify_test, load_node_compat_config, strip_jsonc_comments, }; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fs; +use std::path::Path; use test_r::test; const CONFIG_PATH: &str = "tests/node_compat/config.jsonc"; const REPORT_PATH: &str = "tests/node_compat/report.md"; +const SUITE_ROOT: &str = "tests/node_compat/suite"; #[derive(Debug, Clone)] struct InventoryItem { @@ -141,6 +143,85 @@ fn generate_node_compat_config_report() -> anyhow::Result<()> { Ok(()) } +#[test] +fn module_related_node_compat_entries_are_configured() -> anyhow::Result<()> { + let entries = load_node_compat_config(CONFIG_PATH)?; + let configured: BTreeSet<_> = entries.into_iter().map(|entry| entry.path).collect(); + let expected = collect_module_related_entrypoints()?; + + let missing: Vec<_> = expected + .into_iter() + .filter(|entry| !configured.contains(entry)) + .collect(); + + assert!( + missing.is_empty(), + "module-related node_compat tests are vendored but missing from {CONFIG_PATH}:\n{}", + missing.join("\n") + ); + + Ok(()) +} + +fn collect_module_related_entrypoints() -> anyhow::Result> { + let mut entries = BTreeSet::new(); + collect_matching_files("es-module", is_es_module_entrypoint, &mut entries)?; + collect_matching_files("parallel", is_parallel_module_entrypoint, &mut entries)?; + collect_matching_files("sequential", is_sequential_module_entrypoint, &mut entries)?; + Ok(entries) +} + +fn collect_matching_files( + suite_dir: &str, + predicate: fn(&str) -> bool, + entries: &mut BTreeSet, +) -> anyhow::Result<()> { + let dir = Path::new(SUITE_ROOT).join(suite_dir); + for entry in fs::read_dir(&dir)? { + let entry = entry?; + if !entry.file_type()?.is_file() { + continue; + } + + let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); + if predicate(&file_name) { + entries.insert(format!("{suite_dir}/{file_name}")); + } + } + Ok(()) +} + +fn is_js_entrypoint(name: &str) -> bool { + name.starts_with("test-") && (name.ends_with(".js") || name.ends_with(".mjs")) +} + +fn is_es_module_entrypoint(name: &str) -> bool { + is_js_entrypoint(name) +} + +fn is_parallel_module_entrypoint(name: &str) -> bool { + is_js_entrypoint(name) + && [ + "test-module-", + "test-module.", + "test-require-", + "test-require.", + "test-cjs-", + "test-cjs.", + "test-esm-", + "test-esm.", + "test-commonjs-", + "test-commonjs.", + ] + .iter() + .any(|prefix| name.starts_with(prefix)) +} + +fn is_sequential_module_entrypoint(name: &str) -> bool { + is_js_entrypoint(name) && name.starts_with("test-module") +} + fn expand_entries(entries: &[NodeCompatTestEntry]) -> Vec { let mut items = Vec::new(); for entry in entries { diff --git a/tests/node_compat_validate.rs b/tests/node_compat_validate.rs index 060da817..d98d29b4 100644 --- a/tests/node_compat_validate.rs +++ b/tests/node_compat_validate.rs @@ -19,7 +19,8 @@ mod common; use camino::Utf8Path; use common::js_subtest_parser::{ - SubtestDiscovery, discover_subtests, rewrite_for_block, rewrite_for_node_test, + SubtestDiscovery, discover_subtests_with_options, rewrite_for_block_with_options, + rewrite_for_node_test, }; use common::{ CompiledTest, GolemPreparedComponent, NodeCompatCategory, TestInstance, @@ -43,6 +44,8 @@ struct ValidationCase { reason: Option, timeout_secs: u64, subtest_index: Option, + nested_node_test: bool, + isolate_block_subtests: bool, } #[derive(Debug)] @@ -162,6 +165,8 @@ fn load_cases() -> anyhow::Result> { reason: subtest.reason, timeout_secs: entry.timeout_secs, subtest_index: Some(subtest.index), + nested_node_test: entry.nested_node_test, + isolate_block_subtests: entry.isolate_block_subtests, }); } } else { @@ -172,6 +177,8 @@ fn load_cases() -> anyhow::Result> { reason: entry.reason, timeout_secs: entry.timeout_secs, subtest_index: None, + nested_node_test: entry.nested_node_test, + isolate_block_subtests: entry.isolate_block_subtests, }); } } @@ -221,10 +228,16 @@ async fn run_case( setup_node_compat_test_files(instance.temp_dir_path(), &case.path)?; if let Some(index) = case.subtest_index { - let (source, discovery) = load_split_source(&case.path, source_cache)?; + let (source, discovery) = + load_split_source(&case.path, case.nested_node_test, source_cache)?; let rewritten = match discovery { - SubtestDiscovery::Block(blocks) => rewrite_for_block(source, blocks, index), - SubtestDiscovery::NodeTest(_) => rewrite_for_node_test(source, index), + SubtestDiscovery::Block(blocks) => rewrite_for_block_with_options( + source, + blocks, + index, + case.isolate_block_subtests, + ), + SubtestDiscovery::NodeTest(tests) => rewrite_for_node_test(source, tests, index), SubtestDiscovery::None => source.to_string(), }; let test_filename = case.path.rsplit('/').next().unwrap_or(&case.path); @@ -270,15 +283,17 @@ async fn run_case( fn load_split_source<'a>( path: &str, + nested_node_test: bool, source_cache: &'a mut BTreeMap, ) -> anyhow::Result<(&'a str, &'a SubtestDiscovery)> { - if !source_cache.contains_key(path) { + let cache_key = format!("{path}#{nested_node_test}"); + if !source_cache.contains_key(&cache_key) { let source = fs::read_to_string(format!("tests/node_compat/suite/{path}"))?; - let discovery = discover_subtests(path, &source); - source_cache.insert(path.to_string(), (source, discovery)); + let discovery = discover_subtests_with_options(path, &source, nested_node_test); + source_cache.insert(cache_key.clone(), (source, discovery)); } let (source, discovery) = source_cache - .get(path) + .get(&cache_key) .expect("split source was just inserted"); Ok((source.as_str(), discovery)) } diff --git a/tests/node_modules_apps/README.md b/tests/node_modules_apps/README.md new file mode 100644 index 00000000..f4e2646c --- /dev/null +++ b/tests/node_modules_apps/README.md @@ -0,0 +1,147 @@ +# Node Modules App Tests + +This runtime suite tests unbundled npm apps with real `node_modules` attached to the component filesystem. It is intentionally separate from `tests/libraries/`, which tests Rollup-bundled package usage and records human compatibility notes in `tests/libraries/libraries.md`. + +## What This Suite Covers + +Use node modules app tests for: + +- Node-style package resolution from a filesystem `node_modules` tree +- package `exports` / `imports`, including wildcard patterns +- CJS/ESM interop and same-process `require(esm)` behavior +- package graphs that behave differently when bundled versus installed +- small smoke tests for pure-JS packages that should run without subprocesses or live services + +Do not use node modules app tests for native `.node` bindings, packages that load WASM artifacts, subprocess-heavy behavior, or live network/cloud service calls. + +## Source Of Truth + +`tests/node_modules_apps/config.jsonc` is the source of truth. Runtime tests in `tests/runtime/node_modules_apps.rs` are generated from this config. + +Each app has this shape: + +```jsonc +{ + "apps": { + "example-app": { + "category": "runnable", + "reason": "Short suite description", + "tests": { + "test-01-basic.mjs": "Coverage summary shown in the report" + } + } + } +} +``` + +Test entries can also be objects when a per-test category, reason, or timeout is needed: + +```jsonc +"test-02-edge.cjs": { + "category": "known-gap", + "coverage": "Coverage summary", + "reason": "Specific gap explanation", + "timeout": 180 +} +``` + +Supported categories are: + +| Category | Runner behavior | Report meaning | +|---|---|---| +| `runnable` | Runs in `cargo test --test runtime` | Expected to pass | +| `known-gap` | Ignored | Public behavior is in scope but missing/incomplete | +| `deferred` | Ignored | Intentionally outside this node modules app suite's current scope | + +## App Directory Layout + +Each app lives under `tests/node_modules_apps/apps//`: + +```text +tests/node_modules_apps/apps/example-app/ +├── package.json +├── run-node.mjs +├── test-01-basic.mjs +└── test-02-edge.cjs +``` + +`package.json` should pin direct dependency versions. The harness installs dependencies with: + +```sh +npm install --install-links --ignore-scripts --no-audit --no-fund +``` + +`run-node.mjs` should import or require the requested test file, call its exported `run()` function, print the returned result, and fail if it does not start with `PASS:`. + +## Test Format + +Each test file must export `run()` and return a `PASS:` string: + +```js +import assert from 'node:assert'; + +export async function run() { + assert.strictEqual(1 + 1, 2); + return 'PASS: basic behavior works'; +} +``` + +CommonJS tests can use `.cjs` and `module.exports.run = ...`. + +## How Runtime Tests Run + +For every runnable config entry, the harness: + +1. Copies the app directory to a temporary directory. +2. Runs `npm install --install-links --ignore-scripts --no-audit --no-fund`. +3. Verifies the raw app test with host Node.js: `node run-node.mjs `. +4. Copies the npm app into the WASI preopen as `/app`. +5. Runs `examples/runtime/node-modules-app-runner`, which imports or requires the test from `/app` and executes it against real `/app/node_modules`. + +## Commands + +Node modules app tests are baselined against Node.js 22.14.0, matching CI and +the vendored Node.js compatibility suite. Use nvm before local runs: + +```sh +source ~/.nvm/nvm.sh +nvm install +nvm use +node --version +``` + +Local runs accept Node.js 22.14.0 or newer patch/minor releases within major +22, but print a warning when the exact baseline differs because Node-baselined +fixtures can change across minor releases. CI sets +`NODE_MODULES_APP_STRICT_NODE_BASELINE=1`, which requires exactly Node.js +22.14.0. + +Run the node modules app suite after skeleton changes: + +```sh +./cleanup-skeleton.sh +cargo test --test runtime --features use-golem-wasmtime -- node_modules_app --nocapture +``` + +Run a narrower filter: + +```sh +cargo test --test runtime --features use-golem-wasmtime -- node_modules_app__module_interop --nocapture +``` + +Run the CI-style node modules app group: + +```sh +cargo test --test runtime --features use-golem-wasmtime -- ':tag:group9' +``` + +Node modules app tests run as runtime `group9` in CI. Regular runtime tests use `group1` through `group8`. + +## Relationship To Rollup Library Tests + +| Suite | Purpose | Execution | +|---|---|---| +| `tests/libraries/` | Documents package compatibility when bundled with Rollup like the Golem CLI pipeline | Manual/agent workflow using Rollup, generated wrapper crates, and `wasmtime run` | +| `tests/node_modules_apps/` | CI-enforced regression tests for unbundled apps with real filesystem `node_modules` | Rust runtime harness generated from `config.jsonc` | + +The same npm package may be covered in both suites for different reasons. Rollup tests answer whether bundled usage works; node modules app tests answer whether Node-style installed package loading works. diff --git a/tests/node_modules_apps/apps/cloud-sdk-offline/package.json b/tests/node_modules_apps/apps/cloud-sdk-offline/package.json new file mode 100644 index 00000000..d95fa5bb --- /dev/null +++ b/tests/node_modules_apps/apps/cloud-sdk-offline/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "@anthropic-ai/sdk": "0.39.0", + "@aws-sdk/client-s3": "3.717.0", + "openai": "4.85.4", + "stripe": "17.6.0" + } +} diff --git a/tests/node_modules_apps/apps/cloud-sdk-offline/run-node.mjs b/tests/node_modules_apps/apps/cloud-sdk-offline/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/cloud-sdk-offline/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/cloud-sdk-offline/test-01-openai.mjs b/tests/node_modules_apps/apps/cloud-sdk-offline/test-01-openai.mjs new file mode 100644 index 00000000..32c80844 --- /dev/null +++ b/tests/node_modules_apps/apps/cloud-sdk-offline/test-01-openai.mjs @@ -0,0 +1,11 @@ +import assert from 'node:assert'; +import OpenAI from 'openai'; + +export const run = () => { + const client = new OpenAI({ apiKey: 'sk-test', baseURL: 'https://example.invalid/v1' }); + assert.strictEqual(typeof client.chat.completions.create, 'function'); + assert.strictEqual(typeof client.files.create, 'function'); + const error = new OpenAI.APIError(400, { error: { message: 'bad request' } }, 'bad request', {}); + assert.strictEqual(error.status, 400); + return 'PASS: OpenAI SDK imports and exposes offline client surfaces from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/cloud-sdk-offline/test-02-anthropic.mjs b/tests/node_modules_apps/apps/cloud-sdk-offline/test-02-anthropic.mjs new file mode 100644 index 00000000..db5ecb0b --- /dev/null +++ b/tests/node_modules_apps/apps/cloud-sdk-offline/test-02-anthropic.mjs @@ -0,0 +1,11 @@ +import assert from 'node:assert'; +import Anthropic from '@anthropic-ai/sdk'; + +export const run = () => { + const client = new Anthropic({ apiKey: 'sk-ant-test', baseURL: 'https://example.invalid' }); + assert.strictEqual(typeof client.messages.create, 'function'); + assert.strictEqual(typeof client.models.list, 'function'); + const error = new Anthropic.APIError(401, { error: { message: 'unauthorized' } }, 'unauthorized', {}); + assert.strictEqual(error.status, 401); + return 'PASS: Anthropic SDK imports and exposes offline client surfaces from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/cloud-sdk-offline/test-03-aws-s3.mjs b/tests/node_modules_apps/apps/cloud-sdk-offline/test-03-aws-s3.mjs new file mode 100644 index 00000000..9e4f6e05 --- /dev/null +++ b/tests/node_modules_apps/apps/cloud-sdk-offline/test-03-aws-s3.mjs @@ -0,0 +1,22 @@ +import assert from 'node:assert'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const { GetObjectCommand, PutObjectCommand, S3Client } = require('@aws-sdk/client-s3'); + +export const run = () => { + const client = new S3Client({ + region: 'us-east-1', + endpoint: 'https://example.invalid', + credentials: { accessKeyId: 'test', secretAccessKey: 'test' }, + }); + assert.strictEqual(typeof client.send, 'function'); + + const put = new PutObjectCommand({ Bucket: 'bucket', Key: 'key', Body: 'body' }); + assert.strictEqual(put.input.Bucket, 'bucket'); + assert.strictEqual(put.input.Key, 'key'); + + const get = new GetObjectCommand({ Bucket: 'bucket', Key: 'key' }); + assert.deepStrictEqual(get.input, { Bucket: 'bucket', Key: 'key' }); + return 'PASS: AWS S3 SDK command construction works from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/cloud-sdk-offline/test-04-stripe.cjs b/tests/node_modules_apps/apps/cloud-sdk-offline/test-04-stripe.cjs new file mode 100644 index 00000000..ee8312a0 --- /dev/null +++ b/tests/node_modules_apps/apps/cloud-sdk-offline/test-04-stripe.cjs @@ -0,0 +1,11 @@ +const assert = require('node:assert'); +const Stripe = require('stripe'); + +exports.run = () => { + const stripe = new Stripe('sk_test_123', { apiVersion: '2024-06-20' }); + assert.strictEqual(typeof stripe.customers.create, 'function'); + assert.strictEqual(typeof stripe.paymentIntents.retrieve, 'function'); + const err = new Stripe.errors.StripeCardError({ message: 'declined', code: 'card_declined' }); + assert.strictEqual(err.code, 'card_declined'); + return 'PASS: Stripe SDK imports and exposes offline client surfaces from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/crypto-auth/package.json b/tests/node_modules_apps/apps/crypto-auth/package.json new file mode 100644 index 00000000..d3ef889f --- /dev/null +++ b/tests/node_modules_apps/apps/crypto-auth/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "bcryptjs": "2.4.3", + "cookie": "1.0.2", + "cookie-signature": "1.2.2", + "jose": "5.9.6", + "jsonwebtoken": "9.0.2", + "nanoid": "5.0.9" + } +} diff --git a/tests/node_modules_apps/apps/crypto-auth/run-node.mjs b/tests/node_modules_apps/apps/crypto-auth/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/crypto-auth/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/crypto-auth/test-01-jsonwebtoken-bcrypt.cjs b/tests/node_modules_apps/apps/crypto-auth/test-01-jsonwebtoken-bcrypt.cjs new file mode 100644 index 00000000..205ef4ab --- /dev/null +++ b/tests/node_modules_apps/apps/crypto-auth/test-01-jsonwebtoken-bcrypt.cjs @@ -0,0 +1,15 @@ +const assert = require('node:assert'); +const jwt = require('jsonwebtoken'); +const bcrypt = require('bcryptjs'); + +exports.run = () => { + const token = jwt.sign({ sub: 'user-1', role: 'admin' }, 'secret', { algorithm: 'HS256', expiresIn: '1h' }); + const payload = jwt.verify(token, 'secret', { algorithms: ['HS256'] }); + assert.strictEqual(payload.sub, 'user-1'); + assert.strictEqual(payload.role, 'admin'); + + const hash = bcrypt.hashSync('password', 4); + assert.strictEqual(bcrypt.compareSync('password', hash), true); + assert.strictEqual(bcrypt.compareSync('wrong', hash), false); + return 'PASS: jsonwebtoken and bcryptjs execute from installed CommonJS packages'; +}; diff --git a/tests/node_modules_apps/apps/crypto-auth/test-02-jose.mjs b/tests/node_modules_apps/apps/crypto-auth/test-02-jose.mjs new file mode 100644 index 00000000..f90a0198 --- /dev/null +++ b/tests/node_modules_apps/apps/crypto-auth/test-02-jose.mjs @@ -0,0 +1,15 @@ +import assert from 'node:assert'; +import { SignJWT, jwtVerify } from 'jose'; + +export const run = async () => { + const secret = new TextEncoder().encode('0123456789abcdef0123456789abcdef'); + const token = await new SignJWT({ scope: 'installed-app' }) + .setProtectedHeader({ alg: 'HS256' }) + .setSubject('user-1') + .sign(secret); + const { payload, protectedHeader } = await jwtVerify(token, secret, { algorithms: ['HS256'] }); + assert.strictEqual(protectedHeader.alg, 'HS256'); + assert.strictEqual(payload.sub, 'user-1'); + assert.strictEqual(payload.scope, 'installed-app'); + return 'PASS: jose ESM JWT signing and verification works from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/crypto-auth/test-03-nanoid-cookie.mjs b/tests/node_modules_apps/apps/crypto-auth/test-03-nanoid-cookie.mjs new file mode 100644 index 00000000..27ff83ea --- /dev/null +++ b/tests/node_modules_apps/apps/crypto-auth/test-03-nanoid-cookie.mjs @@ -0,0 +1,24 @@ +import assert from 'node:assert'; +import { createRequire } from 'node:module'; +import { customAlphabet } from 'nanoid'; +import * as cookie from 'cookie'; + +const require = createRequire(import.meta.url); +const signature = require('cookie-signature'); + +export const run = () => { + const makeId = customAlphabet('abc123', 12); + const id = makeId(); + assert.match(id, /^[abc123]{12}$/); + + const serialized = cookie.serialize('session', id, { httpOnly: true, sameSite: 'strict' }); + assert(serialized.includes('HttpOnly')); + const parsed = cookie.parse(`session=${id}; theme=dark`); + assert.strictEqual(parsed.session, id); + assert.strictEqual(parsed.theme, 'dark'); + + const signed = signature.sign(id, 'secret'); + assert.strictEqual(signature.unsign(signed, 'secret'), id); + assert.strictEqual(signature.unsign(signed, 'wrong'), false); + return 'PASS: nanoid, cookie, and cookie-signature work from installed packages'; +}; diff --git a/tests/node_modules_apps/apps/data-formats/package.json b/tests/node_modules_apps/apps/data-formats/package.json new file mode 100644 index 00000000..f017efa3 --- /dev/null +++ b/tests/node_modules_apps/apps/data-formats/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "csv-parse": "5.6.0", + "msgpackr": "1.11.5", + "papaparse": "5.5.3", + "protobufjs": "7.5.3", + "xml2js": "0.6.2", + "yaml": "2.8.0" + } +} diff --git a/tests/node_modules_apps/apps/data-formats/run-node.mjs b/tests/node_modules_apps/apps/data-formats/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/data-formats/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/data-formats/test-01-csv.cjs b/tests/node_modules_apps/apps/data-formats/test-01-csv.cjs new file mode 100644 index 00000000..c56950d1 --- /dev/null +++ b/tests/node_modules_apps/apps/data-formats/test-01-csv.cjs @@ -0,0 +1,12 @@ +const assert = require('node:assert'); +const Papa = require('papaparse'); +const { parse } = require('csv-parse/sync'); + +exports.run = () => { + const csv = 'name,count\na,1\nb,2\n'; + const papa = Papa.parse(csv, { header: true, dynamicTyping: true, skipEmptyLines: true }); + assert.deepStrictEqual(papa.data, [{ name: 'a', count: 1 }, { name: 'b', count: 2 }]); + const parsed = parse(csv, { columns: true, cast: true }); + assert.deepStrictEqual(parsed, [{ name: 'a', count: 1 }, { name: 'b', count: 2 }]); + return 'PASS: CSV parsers execute from installed packages'; +}; diff --git a/tests/node_modules_apps/apps/data-formats/test-02-yaml-xml.cjs b/tests/node_modules_apps/apps/data-formats/test-02-yaml-xml.cjs new file mode 100644 index 00000000..16df850a --- /dev/null +++ b/tests/node_modules_apps/apps/data-formats/test-02-yaml-xml.cjs @@ -0,0 +1,16 @@ +const assert = require('node:assert'); +const YAML = require('yaml'); +const { parseStringPromise, Builder } = require('xml2js'); + +exports.run = async () => { + const parsedYaml = YAML.parse('name: installed-app\nitems:\n - one\n - two\n'); + assert.deepStrictEqual(parsedYaml, { name: 'installed-app', items: ['one', 'two'] }); + assert.match(YAML.stringify(parsedYaml), /installed-app/); + + const parsedXml = await parseStringPromise('one', { explicitArray: false }); + assert.strictEqual(parsedXml.root.item._, 'one'); + assert.strictEqual(parsedXml.root.item.$.id, '1'); + const xml = new Builder({ headless: true }).buildObject({ root: { item: 'two' } }); + assert.match(xml, /two<\/item>/); + return 'PASS: YAML and XML parsers execute from installed packages'; +}; diff --git a/tests/node_modules_apps/apps/data-formats/test-03-binary-protobuf.cjs b/tests/node_modules_apps/apps/data-formats/test-03-binary-protobuf.cjs new file mode 100644 index 00000000..32e18f0a --- /dev/null +++ b/tests/node_modules_apps/apps/data-formats/test-03-binary-protobuf.cjs @@ -0,0 +1,16 @@ +const assert = require('node:assert'); +const { pack, unpack } = require('msgpackr'); +const protobuf = require('protobufjs'); + +exports.run = () => { + const input = { name: 'installed-app', count: 3, nested: { ok: true } }; + assert.deepStrictEqual(unpack(pack(input)), input); + + const Awesome = new protobuf.Type('Awesome') + .add(new protobuf.Field('name', 1, 'string')) + .add(new protobuf.Field('count', 2, 'int32')); + const message = Awesome.create({ name: 'protobuf', count: 7 }); + const decoded = Awesome.decode(Awesome.encode(message).finish()); + assert.deepStrictEqual(Awesome.toObject(decoded), { name: 'protobuf', count: 7 }); + return 'PASS: msgpackr and protobufjs execute from installed packages'; +}; diff --git a/tests/node_modules_apps/apps/db-clients-offline/package.json b/tests/node_modules_apps/apps/db-clients-offline/package.json new file mode 100644 index 00000000..909b5aef --- /dev/null +++ b/tests/node_modules_apps/apps/db-clients-offline/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "drizzle-orm": "0.38.4", + "knex": "3.1.0", + "mongodb": "6.12.0", + "mysql2": "3.11.5", + "pg": "8.13.1", + "redis": "4.7.0" + } +} diff --git a/tests/node_modules_apps/apps/db-clients-offline/run-node.mjs b/tests/node_modules_apps/apps/db-clients-offline/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/db-clients-offline/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/db-clients-offline/test-01-sql-builders.cjs b/tests/node_modules_apps/apps/db-clients-offline/test-01-sql-builders.cjs new file mode 100644 index 00000000..7def3b04 --- /dev/null +++ b/tests/node_modules_apps/apps/db-clients-offline/test-01-sql-builders.cjs @@ -0,0 +1,12 @@ +const assert = require('node:assert'); +const knex = require('knex'); + +exports.run = () => { + const db = knex({ client: 'pg' }); + const query = db('users').select('id', 'name').where({ active: true }).orderBy('id').toSQL(); + assert.strictEqual(query.sql, 'select "id", "name" from "users" where "active" = ? order by "id" asc'); + assert.deepStrictEqual(query.bindings, [true]); + const insert = db('users').insert({ name: 'Alice' }).returning('id').toSQL(); + assert.match(insert.sql, /insert into "users"/); + return 'PASS: knex query builder executes offline from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/db-clients-offline/test-02-pg-mysql.cjs b/tests/node_modules_apps/apps/db-clients-offline/test-02-pg-mysql.cjs new file mode 100644 index 00000000..05e7aae5 --- /dev/null +++ b/tests/node_modules_apps/apps/db-clients-offline/test-02-pg-mysql.cjs @@ -0,0 +1,16 @@ +const assert = require('node:assert'); +const { Client, Pool } = require('pg'); +const mysql = require('mysql2'); + +exports.run = () => { + const pgClient = new Client({ host: 'localhost', port: 5432, user: 'u', password: 'p', database: 'd' }); + assert.strictEqual(pgClient.connectionParameters.host, 'localhost'); + const pool = new Pool({ max: 1 }); + assert.strictEqual(typeof pool.query, 'function'); + pool.end(); + + assert.strictEqual(typeof mysql.createConnection, 'function'); + assert.strictEqual(typeof mysql.createPool, 'function'); + assert.strictEqual(typeof mysql.format('select ? as value', [1]), 'string'); + return 'PASS: pg and mysql2 clients construct offline from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/db-clients-offline/test-03-mongodb-redis.mjs b/tests/node_modules_apps/apps/db-clients-offline/test-03-mongodb-redis.mjs new file mode 100644 index 00000000..702a76d6 --- /dev/null +++ b/tests/node_modules_apps/apps/db-clients-offline/test-03-mongodb-redis.mjs @@ -0,0 +1,14 @@ +import assert from 'node:assert'; +import { MongoClient } from 'mongodb'; +import { createClient } from 'redis'; + +export const run = async () => { + const mongo = new MongoClient('mongodb://localhost:27017/test', { serverSelectionTimeoutMS: 1 }); + assert.strictEqual(mongo.db('test').databaseName, 'test'); + await mongo.close(); + + const redis = createClient({ url: 'redis://localhost:6379' }); + assert.strictEqual(typeof redis.connect, 'function'); + if (typeof redis.quit === 'function') assert.strictEqual(typeof redis.quit, 'function'); + return 'PASS: mongodb and redis clients construct offline from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/db-clients-offline/test-04-drizzle.mjs b/tests/node_modules_apps/apps/db-clients-offline/test-04-drizzle.mjs new file mode 100644 index 00000000..c31a700b --- /dev/null +++ b/tests/node_modules_apps/apps/db-clients-offline/test-04-drizzle.mjs @@ -0,0 +1,17 @@ +import assert from 'node:assert'; +import { getTableColumns, eq, sql } from 'drizzle-orm'; +import { integer, pgTable, serial, text } from 'drizzle-orm/pg-core'; + +export const run = () => { + const users = pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + age: integer('age'), + }); + const columns = getTableColumns(users); + assert.deepStrictEqual(Object.keys(columns).sort(), ['age', 'id', 'name']); + const condition = eq(users.name, 'Alice'); + assert.strictEqual(typeof condition, 'object'); + assert.strictEqual(typeof sql`select 1`, 'object'); + return 'PASS: drizzle-orm schema helpers execute offline from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/fs-template-config/package.json b/tests/node_modules_apps/apps/fs-template-config/package.json new file mode 100644 index 00000000..ee8636c5 --- /dev/null +++ b/tests/node_modules_apps/apps/fs-template-config/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "ejs": "3.1.10", + "fast-glob": "3.3.3", + "handlebars": "4.7.8", + "ini": "5.0.0", + "mustache": "4.2.0", + "toml": "3.0.0" + } +} diff --git a/tests/node_modules_apps/apps/fs-template-config/run-node.mjs b/tests/node_modules_apps/apps/fs-template-config/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/fs-template-config/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/fs-template-config/test-01-config-parsers.cjs b/tests/node_modules_apps/apps/fs-template-config/test-01-config-parsers.cjs new file mode 100644 index 00000000..593bf48f --- /dev/null +++ b/tests/node_modules_apps/apps/fs-template-config/test-01-config-parsers.cjs @@ -0,0 +1,14 @@ +const assert = require('node:assert'); +const ini = require('ini'); +const toml = require('toml'); + +exports.run = () => { + const parsedIni = ini.parse('name=installed-app\n[limits]\ncount=42\n'); + assert.strictEqual(parsedIni.name, 'installed-app'); + assert.strictEqual(parsedIni.limits.count, '42'); + + const parsedToml = toml.parse('name = "installed-app"\n[limits]\ncount = 42\n'); + assert.strictEqual(parsedToml.name, 'installed-app'); + assert.strictEqual(parsedToml.limits.count, 42); + return 'PASS: ini and toml config parsers execute from installed packages'; +}; diff --git a/tests/node_modules_apps/apps/fs-template-config/test-02-template-engines.cjs b/tests/node_modules_apps/apps/fs-template-config/test-02-template-engines.cjs new file mode 100644 index 00000000..a28599a8 --- /dev/null +++ b/tests/node_modules_apps/apps/fs-template-config/test-02-template-engines.cjs @@ -0,0 +1,14 @@ +const assert = require('node:assert'); +const ejs = require('ejs'); +const handlebars = require('handlebars'); +const mustache = require('mustache'); + +exports.run = () => { + assert.strictEqual(ejs.render('Hello <%= name %>', { name: 'EJS' }), 'Hello EJS'); + + const hb = handlebars.compile('Hello {{name}} {{#if ok}}OK{{/if}}'); + assert.strictEqual(hb({ name: 'Handlebars', ok: true }), 'Hello Handlebars OK'); + + assert.strictEqual(mustache.render('Hello {{name}}', { name: 'Mustache' }), 'Hello Mustache'); + return 'PASS: template engines execute from installed packages'; +}; diff --git a/tests/node_modules_apps/apps/fs-template-config/test-03-fast-glob-fs.cjs b/tests/node_modules_apps/apps/fs-template-config/test-03-fast-glob-fs.cjs new file mode 100644 index 00000000..5ec466e1 --- /dev/null +++ b/tests/node_modules_apps/apps/fs-template-config/test-03-fast-glob-fs.cjs @@ -0,0 +1,16 @@ +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); +const fg = require('fast-glob'); + +exports.run = async () => { + const root = path.join(process.cwd(), 'fixtures', 'glob'); + fs.mkdirSync(path.join(root, 'nested'), { recursive: true }); + fs.writeFileSync(path.join(root, 'a.txt'), 'a'); + fs.writeFileSync(path.join(root, 'nested', 'b.txt'), 'b'); + fs.writeFileSync(path.join(root, 'nested', 'ignore.log'), 'log'); + + const entries = await fg('**/*.txt', { cwd: root, onlyFiles: true }); + assert.deepStrictEqual(entries.sort(), ['a.txt', 'nested/b.txt']); + return 'PASS: fast-glob reads files from attached filesystem'; +}; diff --git a/tests/node_modules_apps/apps/http-clients/package.json b/tests/node_modules_apps/apps/http-clients/package.json new file mode 100644 index 00000000..cbf74760 --- /dev/null +++ b/tests/node_modules_apps/apps/http-clients/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "axios": "1.7.9", + "graphql": "16.10.0", + "graphql-request": "7.1.2", + "ky": "1.7.4", + "node-fetch": "3.3.2" + } +} diff --git a/tests/node_modules_apps/apps/http-clients/run-node.mjs b/tests/node_modules_apps/apps/http-clients/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/http-clients/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/http-clients/test-01-axios.cjs b/tests/node_modules_apps/apps/http-clients/test-01-axios.cjs new file mode 100644 index 00000000..a1b34ef6 --- /dev/null +++ b/tests/node_modules_apps/apps/http-clients/test-01-axios.cjs @@ -0,0 +1,23 @@ +const assert = require('node:assert'); +const axios = require('axios'); + +exports.run = async () => { + const client = axios.create({ + adapter: async (config) => ({ + data: { ok: true, url: config.url, header: config.headers.get('x-test') }, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {}, + }), + }); + client.interceptors.request.use((config) => { + config.headers.set('x-test', 'installed-app'); + return config; + }); + const response = await client.get('https://example.invalid/api'); + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(response.data, { ok: true, url: 'https://example.invalid/api', header: 'installed-app' }); + return 'PASS: axios loads from node_modules and custom adapter/interceptors work'; +}; diff --git a/tests/node_modules_apps/apps/http-clients/test-02-fetch-ky.mjs b/tests/node_modules_apps/apps/http-clients/test-02-fetch-ky.mjs new file mode 100644 index 00000000..87b1af39 --- /dev/null +++ b/tests/node_modules_apps/apps/http-clients/test-02-fetch-ky.mjs @@ -0,0 +1,13 @@ +import assert from 'node:assert'; +import fetch from 'node-fetch'; +import ky from 'ky'; + +export const run = async () => { + const fetched = await fetch('data:application/json,%7B%22hello%22%3A%22node-fetch%22%7D'); + assert.deepStrictEqual(await fetched.json(), { hello: 'node-fetch' }); + + const api = ky.create({ prefixUrl: 'https://example.invalid' }); + assert.strictEqual(typeof api.get, 'function'); + assert.strictEqual(typeof api.post, 'function'); + return 'PASS: node-fetch and ky load from installed ESM packages'; +}; diff --git a/tests/node_modules_apps/apps/http-clients/test-03-graphql-request.mjs b/tests/node_modules_apps/apps/http-clients/test-03-graphql-request.mjs new file mode 100644 index 00000000..aacbf6c3 --- /dev/null +++ b/tests/node_modules_apps/apps/http-clients/test-03-graphql-request.mjs @@ -0,0 +1,17 @@ +import assert from 'node:assert'; +import { GraphQLClient, gql } from 'graphql-request'; + +export const run = async () => { + const client = new GraphQLClient('https://example.invalid/graphql', { + fetch: async (_url, init) => { + assert.match(String(init.body), /hello/); + return new Response(JSON.stringify({ data: { hello: 'world' } }), { + status: 200, + headers: { 'content-type': 'application/json' }, + }); + }, + }); + const data = await client.request(gql`query { hello }`); + assert.deepStrictEqual(data, { hello: 'world' }); + return 'PASS: graphql-request builds and executes with a custom fetch from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/logging-observability/package.json b/tests/node_modules_apps/apps/logging-observability/package.json new file mode 100644 index 00000000..1ba83e8e --- /dev/null +++ b/tests/node_modules_apps/apps/logging-observability/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.9.0", + "consola": "3.4.0", + "loglevel": "1.9.2", + "pino": "9.6.0", + "winston": "3.17.0" + } +} diff --git a/tests/node_modules_apps/apps/logging-observability/run-node.mjs b/tests/node_modules_apps/apps/logging-observability/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/logging-observability/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/logging-observability/test-01-loggers.cjs b/tests/node_modules_apps/apps/logging-observability/test-01-loggers.cjs new file mode 100644 index 00000000..0974d3ef --- /dev/null +++ b/tests/node_modules_apps/apps/logging-observability/test-01-loggers.cjs @@ -0,0 +1,22 @@ +const assert = require('node:assert'); +const pino = require('pino'); +const loglevel = require('loglevel'); +const winston = require('winston'); + +exports.run = () => { + const logger = pino({ enabled: false }).child({ component: 'installed-app' }); + assert.strictEqual(typeof logger.info, 'function'); + logger.info({ ok: true }, 'disabled logger should not write'); + + const log = loglevel.getLogger('installed-app'); + log.setLevel('silent'); + assert.strictEqual(log.getLevel(), loglevel.levels.SILENT); + + const formatted = winston.format.combine( + winston.format.timestamp(), + winston.format.json(), + ).transform({ level: 'info', message: 'hello' }); + assert.strictEqual(formatted.level, 'info'); + assert.strictEqual(formatted.message, 'hello'); + return 'PASS: pino, loglevel, and winston load without transports/processes'; +}; diff --git a/tests/node_modules_apps/apps/logging-observability/test-02-consola-otel.mjs b/tests/node_modules_apps/apps/logging-observability/test-02-consola-otel.mjs new file mode 100644 index 00000000..2339419e --- /dev/null +++ b/tests/node_modules_apps/apps/logging-observability/test-02-consola-otel.mjs @@ -0,0 +1,20 @@ +import assert from 'node:assert'; +import { createConsola } from 'consola'; +import { context, propagation, trace } from '@opentelemetry/api'; + +export const run = () => { + const logs = []; + const consola = createConsola({ + reporters: [{ log: (entry) => logs.push(entry) }], + }); + consola.info('hello', { ok: true }); + assert.strictEqual(logs.length, 1); + assert.strictEqual(logs[0].args[0], 'hello'); + + const tracer = trace.getTracer('installed-app'); + assert.strictEqual(typeof tracer.startSpan, 'function'); + const carrier = {}; + propagation.inject(context.active(), carrier); + assert.strictEqual(typeof carrier, 'object'); + return 'PASS: consola and OpenTelemetry API execute from installed ESM packages'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/package.json b/tests/node_modules_apps/apps/module-interop/package.json new file mode 100644 index 00000000..d5b8a16b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/package.json @@ -0,0 +1,41 @@ +{ + "private": true, + "type": "module", + "scripts": { + "test:node": "node run-node.mjs" + }, + "dependencies": { + "cjs-basic": "file:./packages/cjs-basic", + "cjs-lexer-exports-assign": "file:./packages/cjs-lexer-exports-assign", + "cjs-lexer-exports-assign-negative": "file:./packages/cjs-lexer-exports-assign-negative", + "cjs-lexer-define-property": "file:./packages/cjs-lexer-define-property", + "cjs-lexer-helper-reexports": "file:./packages/cjs-lexer-helper-reexports", + "cjs-lexer-helper-reexports-negative": "file:./packages/cjs-lexer-helper-reexports-negative", + "cjs-lexer-package-reexport": "file:./packages/cjs-lexer-package-reexport", + "cjs-lexer-package-reexport-target": "file:./packages/cjs-lexer-package-reexport-target", + "cjs-lexer-keys-reexport": "file:./packages/cjs-lexer-keys-reexport", + "cjs-lexer-object-literal": "file:./packages/cjs-lexer-object-literal", + "cjs-lexer-object-member-value": "file:./packages/cjs-lexer-object-member-value", + "cjs-lexer-object-require-value": "file:./packages/cjs-lexer-object-require-value", + "cjs-reexport-pkg": "file:./packages/cjs-reexport-pkg", + "condition-entry-import-cycle": "file:./packages/condition-entry-import-cycle", + "condition-entry-imports-cycle": "file:./packages/condition-entry-imports-cycle", + "condition-entry-module-sync-cycle": "file:./packages/condition-entry-module-sync-cycle", + "condition-entry-module-sync-imports-cycle": "file:./packages/condition-entry-module-sync-imports-cycle", + "condition-entry-no-cycle": "file:./packages/condition-entry-no-cycle", + "condition-target-import-cycle": "file:./packages/condition-target-import-cycle", + "condition-target-no-cycle": "file:./packages/condition-target-no-cycle", + "cjs-nested-require-pkg": "file:./packages/cjs-nested-require-pkg", + "cycle-require-esm": "file:./packages/cycle-require-esm", + "esm-alias-create-require-cycle": "file:./packages/esm-alias-create-require-cycle", + "esm-already-evaluated": "file:./packages/esm-already-evaluated", + "esm-false-positive-scanner": "file:./packages/esm-false-positive-scanner", + "dual-exports": "file:./packages/dual-exports", + "esm-sync": "file:./packages/esm-sync", + "imports-alias": "file:./packages/imports-alias", + "pattern-exports": "file:./packages/pattern-exports", + "pattern-imports": "file:./packages/pattern-imports", + "pattern-shims": "file:./packages/pattern-shims", + "tla-esm": "file:./packages/tla-esm" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-basic/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-basic/index.cjs new file mode 100644 index 00000000..bd1399aa --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-basic/index.cjs @@ -0,0 +1,3 @@ +exports.alpha = 'alpha'; +exports['bracketed'] = 'bracketed'; +Object.defineProperty(exports, 'defined', { enumerable: true, value: 'defined' }); diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-basic/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-basic/package.json new file mode 100644 index 00000000..1ed22656 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-basic/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-basic", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-define-property/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-define-property/index.cjs new file mode 100644 index 00000000..bb9e89bf --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-define-property/index.cjs @@ -0,0 +1,42 @@ +const dep = { value: 'getter-value' }; +const value = 'shorthand-value'; + +Object.defineProperty(exports, 'valueExport', { value: 'value' }); +Object.defineProperty(module.exports, 'moduleValueExport', { value: 'module-value' }); +Object.defineProperty(exports, 'getterExport', { get() { return dep.value; } }); +Object.defineProperty(exports, 'functionGetterExport', { get: function () { return dep.value; } }); +Object.defineProperty(exports, 'namedFunctionGetterExport', { get: function getValue() { return dep.value; } }); +Object.defineProperty(exports, 'valueThenValue', { value: 'first', value: 'second' }); +Object.defineProperty(exports, 'valueThenString', { value: 'good', 'value': 'string-wins' }); +Object.defineProperty(exports, 'valueThenComputed', { value: 'good', ['value']: 'computed-wins' }); +Object.defineProperty(exports, 'valueThenShorthand', { value: 'first', value }); +Object.defineProperty(exports, 'valueThenMethod', { value: 'first', value() { return 'method-value'; } }); +Object.defineProperty(exports, 'valueThenFalseEnumerable', { value: dep.value, enumerable: false }); + +Object.defineProperty(exports, 'arrowGetter', { get: () => dep.value }); +Object.defineProperty(exports, 'stringKeyGetter', { 'get': function () { return dep.value; } }); +Object.defineProperty(exports, 'stringKeyValue', { 'value': 'string-key-value' }); +Object.defineProperty(exports, 'shorthandValue', { value }); +Object.defineProperty(exports, 'computedValue', { ['value']: 'computed-value' }); +Object.defineProperty(exports, 'multiStatementGetter', { get() { const v = dep.value; return v; } }); +Object.defineProperty(exports, 'helperValueDescriptor', makeDescriptor({ value: dep.value })); +Object.defineProperty(exports, 'parameterGetter', { get(a) { return dep.value; } }); +Object.defineProperty(exports, 'parameterFunctionGetter', { get: function (a) { return dep.value; } }); +Object.defineProperty(exports, 'helperDescriptor', makeDescriptor({ get() { return dep.value; } })); +Object.defineProperty(exports, 'nestedMemberGetter', { get() { return dep.value.nested; } }); +Object.defineProperty(exports, 'nestedBracketGetter', { get() { return dep['value']['nested']; } }); +Object.defineProperty(exports, 'duplicateGet', { get() { return dep.value; }, get: function (a) { return dep.value; } }); +Object.defineProperty(exports, 'stringThenValue', { 'value': 'bad', value: dep.value }); +Object.defineProperty(exports, 'computedThenValue', { ['value']: 'bad', value: dep.value }); +Object.defineProperty(exports, 'writableThenValue', { writable: true, value: dep.value }); +Object.defineProperty(exports, 'configurableThenValue', { configurable: true, value: dep.value }); +Object.defineProperty(exports, 'quotedEnumerableThenValue', { 'enumerable': true, value: dep.value }); +Object.defineProperty(exports, 'hiddenGetter', { enumerable: false, get() { return dep.value; } }); +Object.defineProperty(exports, 'truthyEnumerableGetter', { enumerable: 1, get() { return dep.value; } }); +Object.defineProperty(exports, 'getterThenEnumerable', { get() { return dep.value; }, enumerable: true }); +if (false) Object.defineProperty(exports, 'objectMemberDescriptor', { value: 'bad' }.descriptor); +if (false) Object.defineProperty(exports, 'objectPlusDescriptor', { value: 'bad' } + suffix); + +function makeDescriptor(descriptor) { + return descriptor; +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-define-property/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-define-property/package.json new file mode 100644 index 00000000..12fc4606 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-define-property/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-define-property", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/dep-dynamic.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/dep-dynamic.cjs new file mode 100644 index 00000000..8e7b07d9 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/dep-dynamic.cjs @@ -0,0 +1 @@ +exports.depDynamic = 'dep-dynamic'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/dep-static.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/dep-static.cjs new file mode 100644 index 00000000..69fa0670 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/dep-static.cjs @@ -0,0 +1 @@ +exports.depStatic = 'dep-static'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/index.cjs new file mode 100644 index 00000000..8c3af773 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/index.cjs @@ -0,0 +1,30 @@ +var dynamicName = './dep-dynamic.cjs'; +var dynamic = require(dynamicName); +Object.keys(dynamic).forEach(function (key) { + exports[key] = dynamic[key]; +}); + +var dep = require('./dep-static.cjs'); +var other = {}; +Object.keys(other).forEach(function (key) { + exports[key] = other[key]; +}); + +Object.keys(dep).forEach(function (key) { + exports[key] = transform(dep[key]); +}); + +var _dep = _interopWildcard(require('./dep-static.cjs')); +Object.keys(_dep).forEach(function (key) { + exports[key] = _dep[key]; +}); + +exports.own = 'own-value'; + +function transform(value) { + return value; +} + +function _interopWildcard(obj) { + return obj; +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/package.json new file mode 100644 index 00000000..aae534ec --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign-negative/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-exports-assign-negative", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-direct-guard.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-direct-guard.cjs new file mode 100644 index 00000000..9f9ff15b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-direct-guard.cjs @@ -0,0 +1 @@ +exports.depDirectGuard = 'dep-direct-guard'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-extra.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-extra.cjs new file mode 100644 index 00000000..38664565 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-extra.cjs @@ -0,0 +1 @@ +exports.depGamma = 'dep-gamma'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-main.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-main.cjs new file mode 100644 index 00000000..ab8afa12 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-main.cjs @@ -0,0 +1,2 @@ +exports.depAlpha = 'dep-alpha'; +exports.depBeta = 'dep-beta'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-object-guard.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-object-guard.cjs new file mode 100644 index 00000000..3ce203d2 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-object-guard.cjs @@ -0,0 +1 @@ +exports.depObjectGuard = 'dep-object-guard'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-prototype-guard.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-prototype-guard.cjs new file mode 100644 index 00000000..51be7c90 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/dep-prototype-guard.cjs @@ -0,0 +1 @@ +exports.depPrototypeGuard = 'dep-prototype-guard'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/index.cjs new file mode 100644 index 00000000..29d716ea --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/index.cjs @@ -0,0 +1,38 @@ +var _main = _interopRequireWildcard(require('./dep-main.cjs')); +var extra = require('./dep-extra.cjs'); +var directGuard = require('./dep-direct-guard.cjs'); +var objectGuard = require('./dep-object-guard.cjs'); +var prototypeGuard = require('./dep-prototype-guard.cjs'); +var directExportNames = {}; +var objectExportNames = {}; +var prototypeExportNames = {}; + +Object.keys(_main).forEach(function (key) { + if (key === 'default' || key === '__esModule') return; + if (Object.prototype.hasOwnProperty.call(exports, key)) return; + exports[key] = _main[key]; +}); + +Object.keys(extra).forEach(function (key) { + if (key === 'default' || key === '__esModule') return; + if (key in exports && exports[key] === extra[key]) return; + exports[key] = extra[key]; +}); + +Object.keys(directGuard).forEach(function (key) { + if (key !== 'default' && !directExportNames.hasOwnProperty(key)) exports[key] = directGuard[key]; +}); + +Object.keys(objectGuard).forEach(function (key) { + if (key !== 'default' && !Object.hasOwnProperty.call(objectExportNames, key)) exports[key] = objectGuard[key]; +}); + +Object.keys(prototypeGuard).forEach(function (key) { + if (key !== 'default' && !Object.prototype.hasOwnProperty.call(prototypeExportNames, key)) exports[key] = prototypeGuard[key]; +}); + +exports.own = 'own-value'; + +function _interopRequireWildcard(obj) { + return obj && obj.__esModule ? obj : obj; +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/package.json new file mode 100644 index 00000000..2a08476f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-exports-assign/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-exports-assign", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/dep-dynamic.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/dep-dynamic.cjs new file mode 100644 index 00000000..8e7b07d9 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/dep-dynamic.cjs @@ -0,0 +1 @@ +exports.depDynamic = 'dep-dynamic'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/dep-nested.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/dep-nested.cjs new file mode 100644 index 00000000..48f682b7 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/dep-nested.cjs @@ -0,0 +1 @@ +exports.depNested = 'dep-nested'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/index.cjs new file mode 100644 index 00000000..8a594294 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/index.cjs @@ -0,0 +1,19 @@ +function __export(m) { + for (var p in m) { + if (p === 'default' || p === '__esModule') continue; + exports[p] = m[p]; + } +} + +function nested() { + __export(require('./dep-nested.cjs')); +} + +function other(name) { + __export(require(name)); +} + +nested(); +other('./dep-dynamic.cjs'); + +exports.own = 'own-value'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/package.json new file mode 100644 index 00000000..4086a83a --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports-negative/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-helper-reexports-negative", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-a.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-a.cjs new file mode 100644 index 00000000..5fff0b7f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-a.cjs @@ -0,0 +1 @@ +exports.depAlpha = 'dep-alpha'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-b.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-b.cjs new file mode 100644 index 00000000..8f658995 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-b.cjs @@ -0,0 +1 @@ +exports.depBeta = 'dep-beta'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-c.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-c.cjs new file mode 100644 index 00000000..38664565 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-c.cjs @@ -0,0 +1 @@ +exports.depGamma = 'dep-gamma'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-d.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-d.cjs new file mode 100644 index 00000000..29efe392 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/dep-d.cjs @@ -0,0 +1 @@ +exports.depDelta = 'dep-delta'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/index.cjs new file mode 100644 index 00000000..a0882bf9 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/index.cjs @@ -0,0 +1,29 @@ +function __export(m) { + for (var p in m) { + if (p === 'default' || p === '__esModule') continue; + exports[p] = m[p]; + } +} + +function __exportStar(m, target) { + for (var p in m) { + if (p === 'default' || p === '__esModule') continue; + target[p] = m[p]; + } +} + +var tslib = { + __export: function (m, target) { + __exportStar(m, target); + }, + __exportStar: function (m, target) { + __exportStar(m, target); + }, +}; + +__export(require('./dep-a.cjs')); +__exportStar(require('./dep-b.cjs'), exports); +tslib.__export(require('./dep-c.cjs'), exports); +tslib.__exportStar(require('./dep-d.cjs'), exports); + +exports.own = 'own-value'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/package.json new file mode 100644 index 00000000..f9630e2d --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-helper-reexports/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-helper-reexports", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/dep.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/dep.cjs new file mode 100644 index 00000000..ab8afa12 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/dep.cjs @@ -0,0 +1,2 @@ +exports.depAlpha = 'dep-alpha'; +exports.depBeta = 'dep-beta'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/index.cjs new file mode 100644 index 00000000..fb7c0d5f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/index.cjs @@ -0,0 +1,8 @@ +var dep = require('./dep.cjs'); + +Object.keys(dep).forEach(function (key) { + if (key === 'default' || key === '__esModule') return; + exports[key] = dep[key]; +}); + +exports.own = 'own-value'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/package.json new file mode 100644 index 00000000..604f0c98 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-keys-reexport/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-keys-reexport", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/dep.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/dep.cjs new file mode 100644 index 00000000..ab8afa12 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/dep.cjs @@ -0,0 +1,2 @@ +exports.depAlpha = 'dep-alpha'; +exports.depBeta = 'dep-beta'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/index.cjs new file mode 100644 index 00000000..892134a4 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/index.cjs @@ -0,0 +1,10 @@ +const a = 1; +const c = 2; +const e = 4; + +module.exports = { + a, + b: c, + 'd': e, + ...require('./dep.cjs'), +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-call-spread.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-call-spread.cjs new file mode 100644 index 00000000..bb50d612 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-call-spread.cjs @@ -0,0 +1,5 @@ +const a = 'before-call-spread'; +const b = 'after-call-spread'; +const other = () => ({ ignored: 'ignored' }); + +module.exports = { a, ...other(), b }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-member-spread.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-member-spread.cjs new file mode 100644 index 00000000..eef6e7eb --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-member-spread.cjs @@ -0,0 +1,5 @@ +const a = 'before-member-spread'; +const b = 'after-member-spread'; +const ns = { other: { ignored: 'ignored' } }; + +module.exports = { a, ...ns.other, b }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-paren-spread.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-paren-spread.cjs new file mode 100644 index 00000000..ef542563 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-paren-spread.cjs @@ -0,0 +1,5 @@ +const a = 'before-paren-spread'; +const b = 'after-paren-spread'; +const other = { ignored: 'ignored' }; + +module.exports = { a, ...(other), b }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-spread.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-spread.cjs new file mode 100644 index 00000000..da1e0a22 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/non-require-spread.cjs @@ -0,0 +1,5 @@ +const a = 'before-spread'; +const b = 'after-spread'; +const other = { ignored: 'ignored' }; + +module.exports = { a, ...other, b }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/package.json new file mode 100644 index 00000000..e2c832bd --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-literal/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-object-literal", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/binary.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/binary.cjs new file mode 100644 index 00000000..223debe4 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/binary.cjs @@ -0,0 +1,6 @@ +const value = 1; + +module.exports = { + binaryValue: value + 1, + after: 'not-exported', +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/bracket.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/bracket.cjs new file mode 100644 index 00000000..59316621 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/bracket.cjs @@ -0,0 +1,8 @@ +const ns = { + member: 'member-value', +}; + +module.exports = { + bracketValue: ns['member'], + after: 'not-exported', +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/call.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/call.cjs new file mode 100644 index 00000000..3cf62fd1 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/call.cjs @@ -0,0 +1,10 @@ +const ns = { + member() { + return 'member-value'; + }, +}; + +module.exports = { + callValue: ns.member(), + after: 'not-exported', +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/index.cjs new file mode 100644 index 00000000..be5aba05 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/index.cjs @@ -0,0 +1,15 @@ +const ns = { + member: 'member-value', + nested: { + value: 'nested-value', + }, +}; +const beforeValue = 'before-value'; + +module.exports = { + before: beforeValue, + memberValue: ns.member, + after: 'not-exported', + nestedValue: ns.nested.value, + bracketValue: ns['member'], +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/nested.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/nested.cjs new file mode 100644 index 00000000..618bd319 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/nested.cjs @@ -0,0 +1,10 @@ +const ns = { + nested: { + value: 'nested-value', + }, +}; + +module.exports = { + nestedValue: ns.nested.value, + after: 'not-exported', +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/optional.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/optional.cjs new file mode 100644 index 00000000..1a801aca --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/optional.cjs @@ -0,0 +1,8 @@ +const ns = { + member: 'member-value', +}; + +module.exports = { + optionalValue: ns?.member, + after: 'not-exported', +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/package.json new file mode 100644 index 00000000..a355080c --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/package.json @@ -0,0 +1,4 @@ +{ + "name": "cjs-lexer-object-member-value", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/primitive.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/primitive.cjs new file mode 100644 index 00000000..8df01ddc --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-member-value/primitive.cjs @@ -0,0 +1,9 @@ +const value = 'after-value'; + +module.exports = { + trueValue: true, + falseValue: false, + nullValue: null, + undefinedValue: undefined, + after: value, +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/dep.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/dep.cjs new file mode 100644 index 00000000..5fff0b7f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/dep.cjs @@ -0,0 +1 @@ +exports.depAlpha = 'dep-alpha'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/index.cjs new file mode 100644 index 00000000..8a183b65 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/index.cjs @@ -0,0 +1,7 @@ +const a = 1; + +module.exports = { + a, + b: require('./dep.cjs'), + afterRequire: 3, +}; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/package.json new file mode 100644 index 00000000..583bc43e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-object-require-value/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-lexer-object-require-value", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport-target/actual.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport-target/actual.cjs new file mode 100644 index 00000000..6d40adcc --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport-target/actual.cjs @@ -0,0 +1,3 @@ +exports.alpha = 'pkg-alpha'; +exports.beta = 'pkg-beta'; +exports['runtime-' + 'only'] = 'pkg-runtime-only'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport-target/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport-target/package.json new file mode 100644 index 00000000..47364e17 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport-target/package.json @@ -0,0 +1,6 @@ +{ + "name": "cjs-lexer-package-reexport-target", + "exports": { + "./feature": "./actual.cjs" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport/index.cjs new file mode 100644 index 00000000..87f279e1 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport/index.cjs @@ -0,0 +1 @@ +module.exports = require('cjs-lexer-package-reexport-target/feature'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport/package.json new file mode 100644 index 00000000..eca5a960 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-lexer-package-reexport/package.json @@ -0,0 +1,7 @@ +{ + "name": "cjs-lexer-package-reexport", + "main": "index.cjs", + "dependencies": { + "cjs-lexer-package-reexport-target": "file:../cjs-lexer-package-reexport-target" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-nested-require-pkg/index.js b/tests/node_modules_apps/apps/module-interop/packages/cjs-nested-require-pkg/index.js new file mode 100644 index 00000000..18f11422 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-nested-require-pkg/index.js @@ -0,0 +1,6 @@ +if (true) { + const require = () => 'local'; + require(); +} + +module.exports = { ok: true }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-nested-require-pkg/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-nested-require-pkg/package.json new file mode 100644 index 00000000..ae15d1ff --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-nested-require-pkg/package.json @@ -0,0 +1,5 @@ +{ + "name": "cjs-nested-require-pkg", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-reexport-pkg/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cjs-reexport-pkg/index.cjs new file mode 100644 index 00000000..10c6bc5b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-reexport-pkg/index.cjs @@ -0,0 +1 @@ +module.exports = require('cjs-basic'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/cjs-reexport-pkg/package.json b/tests/node_modules_apps/apps/module-interop/packages/cjs-reexport-pkg/package.json new file mode 100644 index 00000000..3190ac70 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cjs-reexport-pkg/package.json @@ -0,0 +1,8 @@ +{ + "name": "cjs-reexport-pkg", + "version": "1.0.0", + "main": "index.cjs", + "dependencies": { + "cjs-basic": "file:../cjs-basic" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-import-cycle/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-import-cycle/entry.mjs new file mode 100644 index 00000000..22c6b092 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-import-cycle/entry.mjs @@ -0,0 +1,4 @@ +import 'condition-target-import-cycle'; + +export const value = 'entry'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-import-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-import-cycle/package.json new file mode 100644 index 00000000..b4cb00c0 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-import-cycle/package.json @@ -0,0 +1,15 @@ +{ + "name": "condition-entry-import-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "require": "./entry.mjs", + "default": "./entry.mjs" + } + }, + "dependencies": { + "condition-target-import-cycle": "file:../condition-target-import-cycle" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-bridge.cjs new file mode 100644 index 00000000..c4bd8bfe --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./dep-import.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-import.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-import.mjs new file mode 100644 index 00000000..cbfae9cf --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-import.mjs @@ -0,0 +1,4 @@ +import './dep-bridge.cjs'; + +export const value = 'dep-import'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-sync.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-sync.mjs new file mode 100644 index 00000000..41545b16 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/dep-sync.mjs @@ -0,0 +1,2 @@ +export const value = 'dep-sync'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/entry.mjs new file mode 100644 index 00000000..63caadaa --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/entry.mjs @@ -0,0 +1,4 @@ +import '#dep'; + +export const value = 'entry'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/package.json new file mode 100644 index 00000000..b40840bd --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-imports-cycle/package.json @@ -0,0 +1,20 @@ +{ + "name": "condition-entry-imports-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "require": "./entry.mjs", + "default": "./entry.mjs" + } + }, + "imports": { + "#dep": { + "import": "./dep-import.mjs", + "module-sync": "./dep-sync.mjs", + "require": "./dep-sync.mjs", + "default": "./dep-sync.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/bridge.cjs new file mode 100644 index 00000000..120f861b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./entry.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/entry-import.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/entry-import.mjs new file mode 100644 index 00000000..c93a9fd3 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/entry-import.mjs @@ -0,0 +1,2 @@ +export const value = 'import'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/entry.mjs new file mode 100644 index 00000000..976f984d --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/entry.mjs @@ -0,0 +1,4 @@ +import './bridge.cjs'; + +export const value = 'module-sync'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/package.json new file mode 100644 index 00000000..5fea76d0 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-cycle/package.json @@ -0,0 +1,12 @@ +{ + "name": "condition-entry-module-sync-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "import": "./entry-import.mjs", + "default": "./entry-import.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-bridge.cjs new file mode 100644 index 00000000..f8068ea2 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./dep-sync.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-import.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-import.mjs new file mode 100644 index 00000000..fd115c03 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-import.mjs @@ -0,0 +1,2 @@ +export const value = 'dep-import'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-sync.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-sync.mjs new file mode 100644 index 00000000..4563cadc --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/dep-sync.mjs @@ -0,0 +1,4 @@ +import './dep-bridge.cjs'; + +export const value = 'dep-sync'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/entry.mjs new file mode 100644 index 00000000..63caadaa --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/entry.mjs @@ -0,0 +1,4 @@ +import '#dep'; + +export const value = 'entry'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/package.json new file mode 100644 index 00000000..771a2c14 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-module-sync-imports-cycle/package.json @@ -0,0 +1,19 @@ +{ + "name": "condition-entry-module-sync-imports-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "import": "./entry.mjs", + "default": "./entry.mjs" + } + }, + "imports": { + "#dep": { + "module-sync": "./dep-sync.mjs", + "import": "./dep-import.mjs", + "default": "./dep-import.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-no-cycle/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-no-cycle/entry.mjs new file mode 100644 index 00000000..7cb272a5 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-no-cycle/entry.mjs @@ -0,0 +1,4 @@ +import target from 'condition-target-no-cycle'; + +export const ok = target.ok; +export default { ok }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-entry-no-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-no-cycle/package.json new file mode 100644 index 00000000..f91308b1 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-entry-no-cycle/package.json @@ -0,0 +1,15 @@ +{ + "name": "condition-entry-no-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "require": "./entry.mjs", + "default": "./entry.mjs" + } + }, + "dependencies": { + "condition-target-no-cycle": "file:../condition-target-no-cycle" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/bridge.cjs new file mode 100644 index 00000000..d068d271 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./import.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/import.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/import.mjs new file mode 100644 index 00000000..1b7dfc7e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/import.mjs @@ -0,0 +1,4 @@ +import './bridge.cjs'; + +export const value = 'import-branch'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/package.json new file mode 100644 index 00000000..45a88573 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/package.json @@ -0,0 +1,13 @@ +{ + "name": "condition-target-import-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "import": "./import.mjs", + "module-sync": "./sync.mjs", + "require": "./sync.mjs", + "default": "./sync.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/sync.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/sync.mjs new file mode 100644 index 00000000..56568ff2 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-import-cycle/sync.mjs @@ -0,0 +1,2 @@ +export const value = 'sync-branch'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/bridge.cjs new file mode 100644 index 00000000..47f9e067 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/bridge.cjs @@ -0,0 +1 @@ +module.exports = require('condition-target-no-cycle'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/import.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/import.mjs new file mode 100644 index 00000000..1e79a6e4 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/import.mjs @@ -0,0 +1,4 @@ +import './bridge.cjs'; + +export const ok = true; +export default { ok }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/package.json new file mode 100644 index 00000000..2405acca --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/package.json @@ -0,0 +1,13 @@ +{ + "name": "condition-target-no-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "import": "./import.mjs", + "module-sync": "./sync.mjs", + "require": "./sync.mjs", + "default": "./sync.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/sync.mjs b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/sync.mjs new file mode 100644 index 00000000..73f3b99e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/condition-target-no-cycle/sync.mjs @@ -0,0 +1,2 @@ +export const ok = false; +export default { ok }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/bridge.cjs new file mode 100644 index 00000000..a6d2f8e8 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./esm.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/esm.mjs b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/esm.mjs new file mode 100644 index 00000000..846c1638 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/esm.mjs @@ -0,0 +1,4 @@ +import bridge from './bridge.cjs'; + +export const bridgeOutcome = bridge && bridge.outcome; +export default { bridgeOutcome }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/index.cjs new file mode 100644 index 00000000..573eb793 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/index.cjs @@ -0,0 +1,6 @@ +try { + require('./esm.mjs'); + exports.outcome = 'no-error'; +} catch (error) { + exports.outcome = error && (error.code || error.name); +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/package.json b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/package.json new file mode 100644 index 00000000..3b2b1c9a --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/cycle-require-esm/package.json @@ -0,0 +1,5 @@ +{ + "name": "cycle-require-esm", + "version": "1.0.0", + "main": "index.cjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/default.mjs b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/default.mjs new file mode 100644 index 00000000..eab91ee3 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/default.mjs @@ -0,0 +1,2 @@ +export const mode = 'default'; +export default { mode }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/feature.cjs b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/feature.cjs new file mode 100644 index 00000000..d9dcd8ac --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/feature.cjs @@ -0,0 +1 @@ +exports.featureMode = 'feature-require'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/feature.mjs b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/feature.mjs new file mode 100644 index 00000000..d1d3078f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/feature.mjs @@ -0,0 +1 @@ +export const featureMode = 'feature-import'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/import.mjs b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/import.mjs new file mode 100644 index 00000000..2c7b71c5 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/import.mjs @@ -0,0 +1,2 @@ +export const mode = 'import'; +export default { mode }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/package.json b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/package.json new file mode 100644 index 00000000..bc23768e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/package.json @@ -0,0 +1,17 @@ +{ + "name": "dual-exports", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "import": "./import.mjs", + "module-sync": "./sync.mjs", + "require": "./require.cjs", + "default": "./default.mjs" + }, + "./feature": { + "import": "./feature.mjs", + "require": "./feature.cjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/require.cjs b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/require.cjs new file mode 100644 index 00000000..475cbcb0 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/require.cjs @@ -0,0 +1 @@ +exports.mode = 'require'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/dual-exports/sync.mjs b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/sync.mjs new file mode 100644 index 00000000..81a8c207 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/dual-exports/sync.mjs @@ -0,0 +1,2 @@ +export const mode = 'module-sync'; +export default { mode }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/bridge.cjs new file mode 100644 index 00000000..120f861b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./entry.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/entry.mjs new file mode 100644 index 00000000..1adafe03 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/entry.mjs @@ -0,0 +1,7 @@ +import { createRequire as makeRequire } from 'node:module'; + +const req = makeRequire(import.meta.url); +const bridge = req('./bridge.cjs'); + +export const value = bridge.value; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/package.json b/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/package.json new file mode 100644 index 00000000..c09f3e72 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-alias-create-require-cycle/package.json @@ -0,0 +1,12 @@ +{ + "name": "esm-alias-create-require-cycle", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "require": "./entry.mjs", + "default": "./entry.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/bridge.cjs b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/bridge.cjs new file mode 100644 index 00000000..bcfd1873 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/bridge.cjs @@ -0,0 +1 @@ +module.exports = require('./ready.mjs'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/entry.mjs new file mode 100644 index 00000000..c21c9866 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/entry.mjs @@ -0,0 +1,5 @@ +import './ready.mjs'; +import './bridge.cjs'; + +export const value = 'entry'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/package.json b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/package.json new file mode 100644 index 00000000..a3b48c04 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/package.json @@ -0,0 +1,12 @@ +{ + "name": "esm-already-evaluated", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "require": "./entry.mjs", + "default": "./entry.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/ready.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/ready.mjs new file mode 100644 index 00000000..753e004b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-already-evaluated/ready.mjs @@ -0,0 +1,2 @@ +export const value = 'ready'; +export default { value }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-false-positive-scanner/entry.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-false-positive-scanner/entry.mjs new file mode 100644 index 00000000..c5d94737 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-false-positive-scanner/entry.mjs @@ -0,0 +1,18 @@ +const obj = { + require() { + return { ok: true }; + }, +}; + +const req = createRequire; + +function createRequire() { + return () => ({ ok: true }); +} + +const localRequire = createRequire(); + +export const propertyRequireResult = obj.require('./entry.mjs'); +export const nonCallCreateRequireAlias = typeof req === 'function' ? { ok: true } : { ok: false }; +export const localCreateRequireResult = localRequire('./entry.mjs'); +export default { propertyRequireResult, nonCallCreateRequireAlias, localCreateRequireResult }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-false-positive-scanner/package.json b/tests/node_modules_apps/apps/module-interop/packages/esm-false-positive-scanner/package.json new file mode 100644 index 00000000..0dd21122 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-false-positive-scanner/package.json @@ -0,0 +1,12 @@ +{ + "name": "esm-false-positive-scanner", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "module-sync": "./entry.mjs", + "import": "./entry.mjs", + "default": "./entry.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-sync/explicit-es-module.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/explicit-es-module.mjs new file mode 100644 index 00000000..33952861 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/explicit-es-module.mjs @@ -0,0 +1,3 @@ +export const __esModule = false; +export const named = 'named'; +export default 'default'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-sync/index.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/index.mjs new file mode 100644 index 00000000..80013c7f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/index.mjs @@ -0,0 +1,11 @@ +export const answer = 42; +export const named = 'named'; +export let count = 0; + +export function inc() { + count++; +} + +export default function esmSyncDefault() { + return 'default-call'; +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-sync/module-exports-override.mjs b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/module-exports-override.mjs new file mode 100644 index 00000000..baec482b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/module-exports-override.mjs @@ -0,0 +1,5 @@ +const override = { answer: 42 }; + +export { override as 'module.exports' }; +export const named = 'named'; +export default 'default'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/esm-sync/package.json b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/package.json new file mode 100644 index 00000000..477bfb33 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/esm-sync/package.json @@ -0,0 +1,10 @@ +{ + "name": "esm-sync", + "version": "1.0.0", + "type": "module", + "exports": { + ".": "./index.mjs", + "./explicit-es-module": "./explicit-es-module.mjs", + "./module-exports-override": "./module-exports-override.mjs" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/imports-alias/dep.cjs b/tests/node_modules_apps/apps/module-interop/packages/imports-alias/dep.cjs new file mode 100644 index 00000000..c35f3997 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/imports-alias/dep.cjs @@ -0,0 +1 @@ +module.exports = { value: 'aliased-dependency' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/imports-alias/index.mjs b/tests/node_modules_apps/apps/module-interop/packages/imports-alias/index.mjs new file mode 100644 index 00000000..cab6cfcb --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/imports-alias/index.mjs @@ -0,0 +1,4 @@ +import dep from '#dep'; + +export const aliasValue = dep.value; +export default dep; diff --git a/tests/node_modules_apps/apps/module-interop/packages/imports-alias/package.json b/tests/node_modules_apps/apps/module-interop/packages/imports-alias/package.json new file mode 100644 index 00000000..2a451d6c --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/imports-alias/package.json @@ -0,0 +1,9 @@ +{ + "name": "imports-alias", + "version": "1.0.0", + "type": "module", + "exports": "./index.mjs", + "imports": { + "#dep": "./dep.cjs" + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/cjs/gamma.cjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/cjs/gamma.cjs new file mode 100644 index 00000000..e8875ee8 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/cjs/gamma.cjs @@ -0,0 +1 @@ +module.exports = { branch: 'require', name: 'gamma' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/package.json b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/package.json new file mode 100644 index 00000000..31ceeb97 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/package.json @@ -0,0 +1,17 @@ +{ + "name": "pattern-exports", + "version": "1.0.0", + "type": "module", + "exports": { + "./features/*": "./src/*.mjs", + "./sync/*": { + "module-sync": "./sync/*-sync.mjs", + "import": "./sync/*-import.mjs", + "default": "./sync/*-default.mjs" + }, + "./cjs/*": { + "require": "./cjs/*.cjs", + "default": "./src/*.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/src/alpha.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/src/alpha.mjs new file mode 100644 index 00000000..82fe2484 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/src/alpha.mjs @@ -0,0 +1 @@ +export default { feature: 'alpha' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-default.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-default.mjs new file mode 100644 index 00000000..04c02a3b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-default.mjs @@ -0,0 +1 @@ +export default { branch: 'default', name: 'beta' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-import.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-import.mjs new file mode 100644 index 00000000..7521998b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-import.mjs @@ -0,0 +1 @@ +export default { branch: 'import', name: 'beta' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-sync.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-sync.mjs new file mode 100644 index 00000000..152c3762 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-exports/sync/beta-sync.mjs @@ -0,0 +1 @@ +export default { branch: 'module-sync', name: 'beta' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/index.cjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/index.cjs new file mode 100644 index 00000000..96948c6a --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/index.cjs @@ -0,0 +1 @@ +module.exports = require('#internal/value'); diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/internal/value.cjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/internal/value.cjs new file mode 100644 index 00000000..401efc1f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/internal/value.cjs @@ -0,0 +1 @@ +module.exports = { value: 'internal-value' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/internal/value.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/internal/value.mjs new file mode 100644 index 00000000..957f0d0e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/internal/value.mjs @@ -0,0 +1 @@ +export default { value: 'internal-value-esm' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/package.json b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/package.json new file mode 100644 index 00000000..9e78ad6d --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-imports/package.json @@ -0,0 +1,11 @@ +{ + "name": "pattern-imports", + "version": "1.0.0", + "main": "index.cjs", + "imports": { + "#internal/*": { + "require": "./internal/*.cjs", + "default": "./internal/*.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime-node.cjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime-node.cjs new file mode 100644 index 00000000..4aa667ed --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime-node.cjs @@ -0,0 +1 @@ +module.exports = { runtime: 'node-require' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime-node.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime-node.mjs new file mode 100644 index 00000000..a8878827 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime-node.mjs @@ -0,0 +1 @@ +export default { runtime: 'node' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime.mjs b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime.mjs new file mode 100644 index 00000000..61f06b25 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/_shims/auto/runtime.mjs @@ -0,0 +1 @@ +export default { runtime: 'default' }; diff --git a/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/package.json b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/package.json new file mode 100644 index 00000000..a5d3df7b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/pattern-shims/package.json @@ -0,0 +1,14 @@ +{ + "name": "pattern-shims", + "version": "1.0.0", + "type": "module", + "exports": { + "./_shims/auto/*": { + "node": { + "default": "./_shims/auto/*-node.mjs", + "require": "./_shims/auto/*-node.cjs" + }, + "default": "./_shims/auto/*.mjs" + } + } +} diff --git a/tests/node_modules_apps/apps/module-interop/packages/tla-esm/index.mjs b/tests/node_modules_apps/apps/module-interop/packages/tla-esm/index.mjs new file mode 100644 index 00000000..7a0356b7 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/tla-esm/index.mjs @@ -0,0 +1,2 @@ +await Promise.resolve(); +export const value = 'ready-after-tla'; diff --git a/tests/node_modules_apps/apps/module-interop/packages/tla-esm/package.json b/tests/node_modules_apps/apps/module-interop/packages/tla-esm/package.json new file mode 100644 index 00000000..1bcab0b5 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/packages/tla-esm/package.json @@ -0,0 +1,6 @@ +{ + "name": "tla-esm", + "version": "1.0.0", + "type": "module", + "exports": "./index.mjs" +} diff --git a/tests/node_modules_apps/apps/module-interop/run-node.mjs b/tests/node_modules_apps/apps/module-interop/run-node.mjs new file mode 100644 index 00000000..e19f083f --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/run-node.mjs @@ -0,0 +1,23 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') { + throw new Error(`${testPath} does not export run()`); +} + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) { + process.exit(1); +} diff --git a/tests/node_modules_apps/apps/module-interop/test-01-esm-import-cjs.js b/tests/node_modules_apps/apps/module-interop/test-01-esm-import-cjs.js new file mode 100644 index 00000000..c7e8390d --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-01-esm-import-cjs.js @@ -0,0 +1,13 @@ +import assert from 'node:assert'; +import cjsDefault, { alpha, bracketed } from 'cjs-basic'; +import reexported, { alpha as reexportedAlpha, bracketed as reexportedBracketed } from 'cjs-reexport-pkg'; + +export const run = () => { + assert.strictEqual(cjsDefault.alpha, 'alpha'); + assert.strictEqual(alpha, 'alpha'); + assert.strictEqual(bracketed, 'bracketed'); + assert.strictEqual(reexported.alpha, 'alpha'); + assert.strictEqual(reexportedAlpha, 'alpha'); + assert.strictEqual(reexportedBracketed, 'bracketed'); + return 'PASS: ESM imports named/default exports from installed CJS packages'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-02-cjs-require-esm.cjs b/tests/node_modules_apps/apps/module-interop/test-02-cjs-require-esm.cjs new file mode 100644 index 00000000..f3508f42 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-02-cjs-require-esm.cjs @@ -0,0 +1,56 @@ +const assert = require('node:assert'); + +exports.run = () => { + const esm = require('esm-sync'); + const esmDefault = typeof esm === 'function' ? esm : esm.default; + assert.strictEqual(typeof esmDefault, 'function'); + assert.strictEqual(esmDefault(), 'default-call'); + assert.strictEqual(esm.answer, 42); + assert.strictEqual(esm.named, 'named'); + assert.strictEqual(esm.count, 0); + esm.inc(); + assert.strictEqual(esm.count, 1); + + assert.deepStrictEqual(Object.keys(esm), ['__esModule', 'answer', 'count', 'default', 'inc', 'named']); + assert.strictEqual(Object.getPrototypeOf(esm), null); + assert.strictEqual(Object.isSealed(esm), true); + assert.strictEqual(Object.isExtensible(esm), false); + assert.deepStrictEqual(Object.getOwnPropertyDescriptor(esm, '__esModule'), { + value: true, + writable: true, + enumerable: true, + configurable: false, + }); + assert.deepStrictEqual(Object.getOwnPropertyDescriptor(esm, 'default'), { + value: esm.default, + writable: true, + enumerable: true, + configurable: false, + }); + assert.deepStrictEqual(Object.getOwnPropertyDescriptor(esm, 'count'), { + value: 1, + writable: true, + enumerable: true, + configurable: false, + }); + esm.named = 'changed'; + assert.strictEqual(esm.named, 'named'); + assert.throws(() => Object.defineProperty(esm, 'extra', { value: true }), TypeError); + + const explicit = require('esm-sync/explicit-es-module'); + assert.strictEqual(explicit.__esModule, false); + assert.deepStrictEqual(Object.keys(explicit), ['__esModule', 'default', 'named']); + assert.deepStrictEqual(Object.getOwnPropertyDescriptor(explicit, '__esModule'), { + value: false, + writable: true, + enumerable: true, + configurable: false, + }); + + const override = require('esm-sync/module-exports-override'); + assert.deepStrictEqual(override, { answer: 42 }); + assert.notStrictEqual(Object.getPrototypeOf(override), null); + assert.strictEqual(Object.isSealed(override), false); + + return 'PASS: CJS requires an installed synchronous ESM package'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-03-package-exports-imports.js b/tests/node_modules_apps/apps/module-interop/test-03-package-exports-imports.js new file mode 100644 index 00000000..6a296d79 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-03-package-exports-imports.js @@ -0,0 +1,13 @@ +import assert from 'node:assert'; +import dualDefault, { mode as dualMode } from 'dual-exports'; +import { featureMode } from 'dual-exports/feature'; +import aliasDefault, { aliasValue } from 'imports-alias'; + +export const run = () => { + assert.strictEqual(dualDefault.mode, 'import'); + assert.strictEqual(dualMode, 'import'); + assert.strictEqual(featureMode, 'feature-import'); + assert.strictEqual(aliasDefault.value, 'aliased-dependency'); + assert.strictEqual(aliasValue, 'aliased-dependency'); + return 'PASS: package exports, subpaths, and imports aliases work from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-04-cycle-require-esm.cjs b/tests/node_modules_apps/apps/module-interop/test-04-cycle-require-esm.cjs new file mode 100644 index 00000000..2b7df70e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-04-cycle-require-esm.cjs @@ -0,0 +1,7 @@ +const assert = require('node:assert'); + +exports.run = () => { + const cycle = require('cycle-require-esm'); + assert.strictEqual(cycle.outcome, 'ERR_REQUIRE_CYCLE_MODULE'); + return 'PASS: installed package CJS require(esm) cycle reports ERR_REQUIRE_CYCLE_MODULE'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-05-tla-require.cjs b/tests/node_modules_apps/apps/module-interop/test-05-tla-require.cjs new file mode 100644 index 00000000..cf5a2da5 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-05-tla-require.cjs @@ -0,0 +1,8 @@ +const assert = require('node:assert'); + +exports.run = async () => { + assert.throws(() => require('tla-esm'), { code: 'ERR_REQUIRE_ASYNC_MODULE' }); + const imported = await import('tla-esm'); + assert.strictEqual(imported.value, 'ready-after-tla'); + return 'PASS: installed TLA ESM rejects require() and still supports dynamic import'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-06-conditional-import-graph.cjs b/tests/node_modules_apps/apps/module-interop/test-06-conditional-import-graph.cjs new file mode 100644 index 00000000..3aa9d0aa --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-06-conditional-import-graph.cjs @@ -0,0 +1,6 @@ +const assert = require('node:assert'); + +exports.run = () => { + assert.throws(() => require('condition-entry-import-cycle'), { code: 'ERR_REQUIRE_CYCLE_MODULE' }); + return 'PASS: require(esm) graph scanning follows package import conditions for static ESM imports'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-07-conditional-import-no-false-positive.cjs b/tests/node_modules_apps/apps/module-interop/test-07-conditional-import-no-false-positive.cjs new file mode 100644 index 00000000..5a6760d1 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-07-conditional-import-no-false-positive.cjs @@ -0,0 +1,8 @@ +const assert = require('node:assert'); + +exports.run = () => { + const result = require('condition-entry-no-cycle'); + assert.strictEqual(result.ok, true); + assert.strictEqual(result.default.ok, true); + return 'PASS: require(esm) graph scanning does not mark package module-sync branches for static ESM imports'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-08-conditional-imports-alias-graph.cjs b/tests/node_modules_apps/apps/module-interop/test-08-conditional-imports-alias-graph.cjs new file mode 100644 index 00000000..e1aa0d3d --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-08-conditional-imports-alias-graph.cjs @@ -0,0 +1,6 @@ +const assert = require('node:assert'); + +exports.run = () => { + assert.throws(() => require('condition-entry-imports-cycle'), { code: 'ERR_REQUIRE_CYCLE_MODULE' }); + return 'PASS: require(esm) graph scanning follows package imports aliases with import conditions'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-09-create-require-alias-cycle.cjs b/tests/node_modules_apps/apps/module-interop/test-09-create-require-alias-cycle.cjs new file mode 100644 index 00000000..00e554fd --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-09-create-require-alias-cycle.cjs @@ -0,0 +1,6 @@ +const assert = require('node:assert'); + +exports.run = () => { + assert.throws(() => require('esm-alias-create-require-cycle'), { code: 'ERR_REQUIRE_CYCLE_MODULE' }); + return 'PASS: require(esm) graph scanning handles createRequire alias cycles'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-10-already-evaluated-dependency.cjs b/tests/node_modules_apps/apps/module-interop/test-10-already-evaluated-dependency.cjs new file mode 100644 index 00000000..80d06543 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-10-already-evaluated-dependency.cjs @@ -0,0 +1,8 @@ +const assert = require('node:assert'); + +exports.run = () => { + const result = require('esm-already-evaluated'); + assert.strictEqual(result.value, 'entry'); + assert.strictEqual(result.default.value, 'entry'); + return 'PASS: CJS bridge can require an already evaluated ESM dependency'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-11-module-sync-before-import-graph.cjs b/tests/node_modules_apps/apps/module-interop/test-11-module-sync-before-import-graph.cjs new file mode 100644 index 00000000..29c2d873 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-11-module-sync-before-import-graph.cjs @@ -0,0 +1,6 @@ +const assert = require('node:assert'); + +exports.run = () => { + assert.throws(() => require('condition-entry-module-sync-cycle'), { code: 'ERR_REQUIRE_CYCLE_MODULE' }); + return 'PASS: require(esm) graph scanning honors module-sync before import in exports'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-12-module-sync-before-imports-alias-graph.cjs b/tests/node_modules_apps/apps/module-interop/test-12-module-sync-before-imports-alias-graph.cjs new file mode 100644 index 00000000..04d1733e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-12-module-sync-before-imports-alias-graph.cjs @@ -0,0 +1,6 @@ +const assert = require('node:assert'); + +exports.run = () => { + assert.throws(() => require('condition-entry-module-sync-imports-cycle'), { code: 'ERR_REQUIRE_CYCLE_MODULE' }); + return 'PASS: require(esm) graph scanning honors module-sync before import in package imports'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-13-scanner-false-positive-guards.cjs b/tests/node_modules_apps/apps/module-interop/test-13-scanner-false-positive-guards.cjs new file mode 100644 index 00000000..0f7e0696 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-13-scanner-false-positive-guards.cjs @@ -0,0 +1,12 @@ +const assert = require('node:assert'); + +exports.run = () => { + const scanner = require('esm-false-positive-scanner'); + assert.strictEqual(scanner.propertyRequireResult.ok, true); + assert.strictEqual(scanner.nonCallCreateRequireAlias.ok, true); + assert.strictEqual(scanner.localCreateRequireResult.ok, true); + + const cjsWithNestedRequire = require('cjs-nested-require-pkg'); + assert.deepStrictEqual(cjsWithNestedRequire, { ok: true }); + return 'PASS: graph scanners avoid property require, non-call createRequire, local createRequire, and nested CJS require false positives'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-14-exports-patterns.mjs b/tests/node_modules_apps/apps/module-interop/test-14-exports-patterns.mjs new file mode 100644 index 00000000..e495f5bb --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-14-exports-patterns.mjs @@ -0,0 +1,13 @@ +import assert from 'node:assert'; +import feature from 'pattern-exports/features/alpha'; +import syncFeature from 'pattern-exports/sync/beta'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + +export const run = () => { + assert.deepStrictEqual(feature, { feature: 'alpha' }); + assert.deepStrictEqual(syncFeature, { branch: 'module-sync', name: 'beta' }); + assert.deepStrictEqual(require('pattern-exports/cjs/gamma'), { branch: 'require', name: 'gamma' }); + return 'PASS: package exports wildcard patterns resolve for ESM, module-sync, and CJS require'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-15-imports-patterns.cjs b/tests/node_modules_apps/apps/module-interop/test-15-imports-patterns.cjs new file mode 100644 index 00000000..a658303b --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-15-imports-patterns.cjs @@ -0,0 +1,7 @@ +const assert = require('node:assert'); + +exports.run = () => { + const pkg = require('pattern-imports'); + assert.deepStrictEqual(pkg, { value: 'internal-value' }); + return 'PASS: package imports wildcard patterns resolve through installed packages'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-16-shim-patterns.mjs b/tests/node_modules_apps/apps/module-interop/test-16-shim-patterns.mjs new file mode 100644 index 00000000..3475704e --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-16-shim-patterns.mjs @@ -0,0 +1,7 @@ +import assert from 'node:assert'; +import runtime from 'pattern-shims/_shims/auto/runtime'; + +export const run = () => { + assert.deepStrictEqual(runtime, { runtime: 'node' }); + return 'PASS: OpenAI-style _shims/auto wildcard export patterns resolve'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-17-cjs-lexer-parity.mjs b/tests/node_modules_apps/apps/module-interop/test-17-cjs-lexer-parity.mjs new file mode 100644 index 00000000..01126166 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-17-cjs-lexer-parity.mjs @@ -0,0 +1,117 @@ +import assert from 'node:assert'; +import objectLiteralDefault, { + a as literalA, + b as literalB, + d as literalD, + depAlpha as literalDepAlpha, + depBeta as literalDepBeta, +} from 'cjs-lexer-object-literal'; +import callbackDefault, { + depAlpha as callbackDepAlpha, + depBeta as callbackDepBeta, + own as callbackOwn, +} from 'cjs-lexer-keys-reexport'; +import requireValueDefault, { + a as requireValueA, + b as requireValueB, +} from 'cjs-lexer-object-require-value'; +import * as requireValueNs from 'cjs-lexer-object-require-value'; +import memberValueDefault, { + before as memberBefore, + memberValue, +} from 'cjs-lexer-object-member-value'; +import * as memberValueNs from 'cjs-lexer-object-member-value'; +import nestedValueDefault, { nestedValue } from 'cjs-lexer-object-member-value/nested.cjs'; +import * as nestedValueNs from 'cjs-lexer-object-member-value/nested.cjs'; +import bracketValueDefault, { bracketValue } from 'cjs-lexer-object-member-value/bracket.cjs'; +import * as bracketValueNs from 'cjs-lexer-object-member-value/bracket.cjs'; +import callValueDefault, { callValue } from 'cjs-lexer-object-member-value/call.cjs'; +import * as callValueNs from 'cjs-lexer-object-member-value/call.cjs'; +import binaryValueDefault, { binaryValue } from 'cjs-lexer-object-member-value/binary.cjs'; +import * as binaryValueNs from 'cjs-lexer-object-member-value/binary.cjs'; +import optionalValueDefault, { optionalValue } from 'cjs-lexer-object-member-value/optional.cjs'; +import * as optionalValueNs from 'cjs-lexer-object-member-value/optional.cjs'; +import primitiveValueDefault, { + trueValue, + falseValue, + nullValue, + undefinedValue, + after as primitiveAfter, +} from 'cjs-lexer-object-member-value/primitive.cjs'; +import nonRequireSpreadDefault, { + a as nonRequireSpreadA, + b as nonRequireSpreadB, +} from 'cjs-lexer-object-literal/non-require-spread.cjs'; +import callSpreadDefault from 'cjs-lexer-object-literal/non-require-call-spread.cjs'; +import * as callSpreadNs from 'cjs-lexer-object-literal/non-require-call-spread.cjs'; +import memberSpreadDefault from 'cjs-lexer-object-literal/non-require-member-spread.cjs'; +import * as memberSpreadNs from 'cjs-lexer-object-literal/non-require-member-spread.cjs'; +import parenSpreadDefault from 'cjs-lexer-object-literal/non-require-paren-spread.cjs'; +import * as parenSpreadNs from 'cjs-lexer-object-literal/non-require-paren-spread.cjs'; + +export const run = () => { + assert.strictEqual(literalA, 1); + assert.strictEqual(literalB, 2); + assert.strictEqual(literalD, 4); + assert.strictEqual(literalDepAlpha, 'dep-alpha'); + assert.strictEqual(literalDepBeta, 'dep-beta'); + assert.strictEqual(objectLiteralDefault.depAlpha, 'dep-alpha'); + + assert.strictEqual(callbackDepAlpha, 'dep-alpha'); + assert.strictEqual(callbackDepBeta, 'dep-beta'); + assert.strictEqual(callbackOwn, 'own-value'); + assert.strictEqual(callbackDefault.own, 'own-value'); + + assert.strictEqual(requireValueA, 1); + assert.deepStrictEqual(requireValueB, { depAlpha: 'dep-alpha' }); + assert.strictEqual(Object.hasOwn(requireValueNs, 'depAlpha'), false); + assert.strictEqual(requireValueDefault.afterRequire, 3); + assert.strictEqual(Object.hasOwn(requireValueNs, 'afterRequire'), false); + + assert.strictEqual(memberBefore, 'before-value'); + assert.strictEqual(memberValue, 'member-value'); + assert.strictEqual(memberValueDefault.after, 'not-exported'); + assert.strictEqual(Object.hasOwn(memberValueNs, 'after'), false); + + assert.strictEqual(nestedValue, 'nested-value'); + assert.strictEqual(nestedValueDefault.after, 'not-exported'); + assert.strictEqual(Object.hasOwn(nestedValueNs, 'after'), false); + + assert.strictEqual(bracketValue, 'member-value'); + assert.strictEqual(bracketValueDefault.after, 'not-exported'); + assert.strictEqual(Object.hasOwn(bracketValueNs, 'after'), false); + + assert.strictEqual(callValue, 'member-value'); + assert.strictEqual(callValueDefault.after, 'not-exported'); + assert.strictEqual(Object.hasOwn(callValueNs, 'after'), false); + + assert.strictEqual(binaryValue, 2); + assert.strictEqual(binaryValueDefault.after, 'not-exported'); + assert.strictEqual(Object.hasOwn(binaryValueNs, 'after'), false); + + assert.strictEqual(optionalValue, 'member-value'); + assert.strictEqual(optionalValueDefault.after, 'not-exported'); + assert.strictEqual(Object.hasOwn(optionalValueNs, 'after'), false); + + assert.strictEqual(trueValue, true); + assert.strictEqual(falseValue, false); + assert.strictEqual(nullValue, null); + assert.strictEqual(undefinedValue, undefined); + assert.strictEqual(primitiveAfter, 'after-value'); + assert.strictEqual(primitiveValueDefault.after, 'after-value'); + + assert.strictEqual(nonRequireSpreadA, 'before-spread'); + assert.strictEqual(nonRequireSpreadB, 'after-spread'); + assert.strictEqual(nonRequireSpreadDefault.ignored, 'ignored'); + assert.strictEqual(callSpreadNs.a, 'before-call-spread'); + assert.strictEqual(callSpreadDefault.b, 'after-call-spread'); + assert.strictEqual(Object.hasOwn(callSpreadNs, 'b'), false); + assert.strictEqual(memberSpreadNs.a, 'before-member-spread'); + assert.strictEqual(memberSpreadDefault.b, 'after-member-spread'); + assert.strictEqual(Object.hasOwn(memberSpreadNs, 'b'), false); + assert.strictEqual(parenSpreadNs.a, 'before-paren-spread'); + assert.strictEqual(parenSpreadDefault.b, 'after-paren-spread'); + assert.strictEqual(Object.hasOwn(parenSpreadNs, 'b'), false); + + return 'PASS: CJS lexer parity object-literal and keys reexport patterns'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-18-cjs-lexer-helper-reexports.mjs b/tests/node_modules_apps/apps/module-interop/test-18-cjs-lexer-helper-reexports.mjs new file mode 100644 index 00000000..98922910 --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-18-cjs-lexer-helper-reexports.mjs @@ -0,0 +1,37 @@ +import assert from 'node:assert'; +import helperDefault, { + depAlpha, + depBeta, + depGamma, + depDelta, + own as helperOwn, +} from 'cjs-lexer-helper-reexports'; +import packageReexportDefault, { + alpha as packageAlpha, + beta as packageBeta, +} from 'cjs-lexer-package-reexport'; +import * as packageReexportNs from 'cjs-lexer-package-reexport'; + +const negativeNs = await import('cjs-lexer-helper-reexports-negative'); + +export const run = () => { + assert.strictEqual(depAlpha, 'dep-alpha'); + assert.strictEqual(depBeta, 'dep-beta'); + assert.strictEqual(depGamma, 'dep-gamma'); + assert.strictEqual(depDelta, 'dep-delta'); + assert.strictEqual(helperOwn, 'own-value'); + assert.strictEqual(helperDefault.depDelta, 'dep-delta'); + assert.strictEqual(packageAlpha, 'pkg-alpha'); + assert.strictEqual(packageBeta, 'pkg-beta'); + assert.strictEqual(packageReexportDefault.alpha, 'pkg-alpha'); + assert.strictEqual(packageReexportDefault['runtime-only'], 'pkg-runtime-only'); + assert.deepStrictEqual(Object.keys(packageReexportNs).sort(), ['alpha', 'beta', 'default']); + + assert.strictEqual(negativeNs.default.depNested, 'dep-nested'); + assert.strictEqual(negativeNs.default.depDynamic, 'dep-dynamic'); + assert.strictEqual(Object.hasOwn(negativeNs, 'depNested'), false); + assert.strictEqual(Object.hasOwn(negativeNs, 'depDynamic'), false); + assert.strictEqual(Object.hasOwn(negativeNs, 'own'), true); + + return 'PASS: CJS lexer parity helper reexport patterns'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-19-cjs-lexer-exports-assign.mjs b/tests/node_modules_apps/apps/module-interop/test-19-cjs-lexer-exports-assign.mjs new file mode 100644 index 00000000..e472ebbf --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-19-cjs-lexer-exports-assign.mjs @@ -0,0 +1,35 @@ +import assert from 'node:assert'; +import assignDefault, { + depAlpha, + depBeta, + depDirectGuard, + depGamma, + depObjectGuard, + depPrototypeGuard, + own, +} from 'cjs-lexer-exports-assign'; + +const negativeNs = await import('cjs-lexer-exports-assign-negative'); + +export const run = () => { + assert.strictEqual(depAlpha, 'dep-alpha'); + assert.strictEqual(depBeta, 'dep-beta'); + assert.strictEqual(depGamma, 'dep-gamma'); + assert.strictEqual(depDirectGuard, 'dep-direct-guard'); + assert.strictEqual(depObjectGuard, 'dep-object-guard'); + assert.strictEqual(depPrototypeGuard, 'dep-prototype-guard'); + assert.strictEqual(own, 'own-value'); + assert.strictEqual(assignDefault.depAlpha, 'dep-alpha'); + assert.strictEqual(assignDefault.depGamma, 'dep-gamma'); + assert.strictEqual(assignDefault.depDirectGuard, 'dep-direct-guard'); + assert.strictEqual(assignDefault.depObjectGuard, 'dep-object-guard'); + assert.strictEqual(assignDefault.depPrototypeGuard, 'dep-prototype-guard'); + + assert.strictEqual(negativeNs.default.depDynamic, 'dep-dynamic'); + assert.strictEqual(negativeNs.default.depStatic, 'dep-static'); + assert.strictEqual(Object.hasOwn(negativeNs, 'depDynamic'), false); + assert.strictEqual(Object.hasOwn(negativeNs, 'depStatic'), false); + assert.strictEqual(Object.hasOwn(negativeNs, 'own'), true); + + return 'PASS: CJS lexer parity EXPORTS_ASSIGN and export-star guard patterns'; +}; diff --git a/tests/node_modules_apps/apps/module-interop/test-20-cjs-lexer-define-property.mjs b/tests/node_modules_apps/apps/module-interop/test-20-cjs-lexer-define-property.mjs new file mode 100644 index 00000000..89a50b4a --- /dev/null +++ b/tests/node_modules_apps/apps/module-interop/test-20-cjs-lexer-define-property.mjs @@ -0,0 +1,83 @@ +import assert from 'node:assert'; +import definePropertyDefault, { + functionGetterExport, + getterExport, + moduleValueExport, + namedFunctionGetterExport, + valueExport, + valueThenComputed, + valueThenMethod, + valueThenShorthand, + valueThenString, + valueThenValue, + valueThenFalseEnumerable, +} from 'cjs-lexer-define-property'; +import * as definePropertyNs from 'cjs-lexer-define-property'; + +export const run = () => { + assert.strictEqual(valueExport, 'value'); + assert.strictEqual(moduleValueExport, 'module-value'); + assert.strictEqual(getterExport, 'getter-value'); + assert.strictEqual(functionGetterExport, 'getter-value'); + assert.strictEqual(namedFunctionGetterExport, 'getter-value'); + assert.strictEqual(valueThenValue, 'second'); + assert.strictEqual(valueThenString, 'string-wins'); + assert.strictEqual(valueThenComputed, 'computed-wins'); + assert.strictEqual(valueThenShorthand, 'shorthand-value'); + assert.strictEqual(typeof valueThenMethod, 'function'); + assert.strictEqual(valueThenMethod(), 'method-value'); + assert.strictEqual(valueThenFalseEnumerable, 'getter-value'); + + assert.strictEqual(definePropertyDefault.arrowGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.stringKeyGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.stringKeyValue, 'string-key-value'); + assert.strictEqual(definePropertyDefault.shorthandValue, 'shorthand-value'); + assert.strictEqual(definePropertyDefault.computedValue, 'computed-value'); + assert.strictEqual(definePropertyDefault.multiStatementGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.helperValueDescriptor, 'getter-value'); + assert.strictEqual(definePropertyDefault.parameterGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.parameterFunctionGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.helperDescriptor, 'getter-value'); + assert.strictEqual(definePropertyDefault.nestedMemberGetter, undefined); + assert.strictEqual(definePropertyDefault.nestedBracketGetter, undefined); + assert.strictEqual(definePropertyDefault.duplicateGet, 'getter-value'); + assert.strictEqual(definePropertyDefault.stringThenValue, 'getter-value'); + assert.strictEqual(definePropertyDefault.computedThenValue, 'getter-value'); + assert.strictEqual(definePropertyDefault.valueThenFalseEnumerable, 'getter-value'); + assert.strictEqual(definePropertyDefault.writableThenValue, 'getter-value'); + assert.strictEqual(definePropertyDefault.configurableThenValue, 'getter-value'); + assert.strictEqual(definePropertyDefault.quotedEnumerableThenValue, 'getter-value'); + assert.strictEqual(definePropertyDefault.hiddenGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.truthyEnumerableGetter, 'getter-value'); + assert.strictEqual(definePropertyDefault.getterThenEnumerable, 'getter-value'); + assert.strictEqual(definePropertyDefault.objectMemberDescriptor, undefined); + assert.strictEqual(definePropertyDefault.objectPlusDescriptor, undefined); + + assert.strictEqual(Object.hasOwn(definePropertyNs, 'arrowGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'stringKeyGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'stringKeyValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'shorthandValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'computedValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'multiStatementGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'helperValueDescriptor'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'parameterGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'parameterFunctionGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'helperDescriptor'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'nestedMemberGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'nestedBracketGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'duplicateGet'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'stringThenValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'computedThenValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'writableThenValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'configurableThenValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'quotedEnumerableThenValue'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'hiddenGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'truthyEnumerableGetter'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'getterThenEnumerable'), false); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'objectMemberDescriptor'), true); + assert.strictEqual(Object.hasOwn(definePropertyNs, 'objectPlusDescriptor'), true); + assert.strictEqual(definePropertyNs.objectMemberDescriptor, undefined); + assert.strictEqual(definePropertyNs.objectPlusDescriptor, undefined); + + return 'PASS: CJS lexer parity Object.defineProperty descriptor patterns'; +}; diff --git a/tests/node_modules_apps/apps/popular-pure-js/package.json b/tests/node_modules_apps/apps/popular-pure-js/package.json new file mode 100644 index 00000000..3a1f0917 --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "ajv": "8.17.1", + "chalk": "5.4.1", + "date-fns": "4.1.0", + "debug": "4.4.1", + "dotenv": "16.4.7", + "lodash": "4.17.21", + "ms": "2.1.3", + "rxjs": "7.8.2", + "semver": "7.7.2", + "uuid": "11.1.0", + "zod": "3.25.76" + } +} diff --git a/tests/node_modules_apps/apps/popular-pure-js/run-node.mjs b/tests/node_modules_apps/apps/popular-pure-js/run-node.mjs new file mode 100644 index 00000000..e19f083f --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/run-node.mjs @@ -0,0 +1,23 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') { + throw new Error(`${testPath} does not export run()`); +} + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) { + process.exit(1); +} diff --git a/tests/node_modules_apps/apps/popular-pure-js/test-01-cjs-utilities.cjs b/tests/node_modules_apps/apps/popular-pure-js/test-01-cjs-utilities.cjs new file mode 100644 index 00000000..969d99bd --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/test-01-cjs-utilities.cjs @@ -0,0 +1,18 @@ +const assert = require('node:assert'); +const lodash = require('lodash'); +const semver = require('semver'); +const createDebug = require('debug'); +const ms = require('ms'); + +exports.run = () => { + const input = [{ group: 'a', value: 1 }, { group: 'a', value: 2 }, { group: 'b', value: 3 }]; + assert.deepStrictEqual(lodash.mapValues(lodash.groupBy(input, 'group'), (items) => lodash.sumBy(items, 'value')), { a: 3, b: 3 }); + assert.strictEqual(semver.satisfies('2.3.4', '^2.0.0'), true); + assert.strictEqual(semver.inc('1.2.3', 'minor'), '1.3.0'); + assert.strictEqual(ms('2 hours'), 7200000); + assert.strictEqual(ms(1500), '2s'); + const debug = createDebug('installed-app:test'); + assert.strictEqual(typeof debug, 'function'); + debug('debug output is disabled by default'); + return 'PASS: classic CJS utility packages load and execute from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/popular-pure-js/test-02-modern-esm.mjs b/tests/node_modules_apps/apps/popular-pure-js/test-02-modern-esm.mjs new file mode 100644 index 00000000..57000888 --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/test-02-modern-esm.mjs @@ -0,0 +1,16 @@ +import assert from 'node:assert'; +import chalk from 'chalk'; +import { z } from 'zod'; +import { parse as parseUuid, stringify as stringifyUuid, v5 as uuidv5, validate as validateUuid } from 'uuid'; + +export const run = () => { + assert.strictEqual(chalk.red('plain'), 'plain'); + + const schema = z.object({ id: z.string().uuid(), tags: z.array(z.string()).default([]) }); + const id = uuidv5('installed-app', uuidv5.URL); + const parsed = schema.parse({ id }); + assert.deepStrictEqual(parsed, { id, tags: [] }); + assert.strictEqual(validateUuid(id), true); + assert.strictEqual(stringifyUuid(parseUuid(id)), id); + return 'PASS: modern ESM and exports-heavy packages load from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/popular-pure-js/test-03-date-fns-subpaths.mjs b/tests/node_modules_apps/apps/popular-pure-js/test-03-date-fns-subpaths.mjs new file mode 100644 index 00000000..600a1124 --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/test-03-date-fns-subpaths.mjs @@ -0,0 +1,11 @@ +import assert from 'node:assert'; +import { addDays } from 'date-fns/addDays'; +import { formatISO } from 'date-fns/formatISO'; +import { parseISO } from 'date-fns/parseISO'; + +export const run = () => { + const start = parseISO('2026-06-16T00:00:00.000Z'); + const result = addDays(start, 3); + assert.strictEqual(formatISO(result, { representation: 'date' }), '2026-06-19'); + return 'PASS: date-fns subpath exports resolve from installed node_modules'; +}; diff --git a/tests/node_modules_apps/apps/popular-pure-js/test-04-dotenv-fs.cjs b/tests/node_modules_apps/apps/popular-pure-js/test-04-dotenv-fs.cjs new file mode 100644 index 00000000..9319a8ea --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/test-04-dotenv-fs.cjs @@ -0,0 +1,14 @@ +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); +const dotenv = require('dotenv'); + +exports.run = () => { + const envPath = path.join(process.cwd(), 'fixtures', 'sample.env'); + fs.mkdirSync(path.dirname(envPath), { recursive: true }); + fs.writeFileSync(envPath, 'APP_NAME=installed-app\nAPP_COUNT=42\n'); + const parsed = dotenv.config({ path: envPath, processEnv: {} }); + assert.deepStrictEqual(parsed.parsed, { APP_NAME: 'installed-app', APP_COUNT: '42' }); + assert.deepStrictEqual(dotenv.parse(Buffer.from('A=1\nB=two\n')), { A: '1', B: 'two' }); + return 'PASS: dotenv reads configuration files from the attached filesystem'; +}; diff --git a/tests/node_modules_apps/apps/popular-pure-js/test-05-ajv.cjs b/tests/node_modules_apps/apps/popular-pure-js/test-05-ajv.cjs new file mode 100644 index 00000000..07834cad --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/test-05-ajv.cjs @@ -0,0 +1,19 @@ +const assert = require('node:assert'); +const Ajv = require('ajv'); + +exports.run = () => { + const ajv = new Ajv({ allErrors: true }); + const validate = ajv.compile({ + type: 'object', + required: ['name', 'count'], + properties: { + name: { type: 'string', minLength: 2 }, + count: { type: 'integer', minimum: 1 }, + }, + additionalProperties: false, + }); + assert.strictEqual(validate({ name: 'ok', count: 2 }), true); + assert.strictEqual(validate({ name: 'x', count: 0, extra: true }), false); + assert(validate.errors.length >= 2); + return 'PASS: ajv compiles and runs schemas from installed CommonJS package graph'; +}; diff --git a/tests/node_modules_apps/apps/popular-pure-js/test-06-rxjs.mjs b/tests/node_modules_apps/apps/popular-pure-js/test-06-rxjs.mjs new file mode 100644 index 00000000..de3791ae --- /dev/null +++ b/tests/node_modules_apps/apps/popular-pure-js/test-06-rxjs.mjs @@ -0,0 +1,14 @@ +import assert from 'node:assert'; +import { firstValueFrom, of } from 'rxjs'; +import { map, reduce } from 'rxjs/operators'; + +export const run = async () => { + const result = await firstValueFrom( + of(1, 2, 3).pipe( + map((value) => value * 2), + reduce((sum, value) => sum + value, 0), + ), + ); + assert.strictEqual(result, 12); + return 'PASS: rxjs package exports and operator subpaths execute from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/validation-schema/package.json b/tests/node_modules_apps/apps/validation-schema/package.json new file mode 100644 index 00000000..8a4ba502 --- /dev/null +++ b/tests/node_modules_apps/apps/validation-schema/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "joi": "17.13.3", + "superstruct": "2.0.2", + "valibot": "1.1.0", + "yup": "1.6.1" + } +} diff --git a/tests/node_modules_apps/apps/validation-schema/run-node.mjs b/tests/node_modules_apps/apps/validation-schema/run-node.mjs new file mode 100644 index 00000000..8536adb1 --- /dev/null +++ b/tests/node_modules_apps/apps/validation-schema/run-node.mjs @@ -0,0 +1,19 @@ +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const testPath = process.argv[2]; +if (!testPath) { + console.error('Usage: node run-node.mjs '); + process.exit(1); +} + +const mod = testPath.endsWith('.cjs') + ? createRequire(import.meta.url)(`./${testPath}`) + : await import(pathToFileURL(new URL(testPath, import.meta.url).pathname).href); + +const run = mod.run || mod.default?.run; +if (typeof run !== 'function') throw new Error(`${testPath} does not export run()`); + +const result = await run(); +console.log(result); +if (typeof result !== 'string' || !result.startsWith('PASS:')) process.exit(1); diff --git a/tests/node_modules_apps/apps/validation-schema/test-01-joi-yup.cjs b/tests/node_modules_apps/apps/validation-schema/test-01-joi-yup.cjs new file mode 100644 index 00000000..851e2229 --- /dev/null +++ b/tests/node_modules_apps/apps/validation-schema/test-01-joi-yup.cjs @@ -0,0 +1,14 @@ +const assert = require('node:assert'); +const Joi = require('joi'); +const yup = require('yup'); + +exports.run = async () => { + const joiSchema = Joi.object({ name: Joi.string().min(2).required(), count: Joi.number().integer().min(1).required() }); + assert.deepStrictEqual(joiSchema.validate({ name: 'ok', count: 3 }).value, { name: 'ok', count: 3 }); + assert(joiSchema.validate({ name: 'x', count: 0 }).error); + + const yupSchema = yup.object({ name: yup.string().required(), count: yup.number().min(1).required() }); + assert.deepStrictEqual(await yupSchema.validate({ name: 'ok', count: 3 }), { name: 'ok', count: 3 }); + await assert.rejects(() => yupSchema.validate({ name: '', count: 0 })); + return 'PASS: joi and yup validation packages execute from node_modules'; +}; diff --git a/tests/node_modules_apps/apps/validation-schema/test-02-superstruct-valibot.mjs b/tests/node_modules_apps/apps/validation-schema/test-02-superstruct-valibot.mjs new file mode 100644 index 00000000..291f7874 --- /dev/null +++ b/tests/node_modules_apps/apps/validation-schema/test-02-superstruct-valibot.mjs @@ -0,0 +1,14 @@ +import assert from 'node:assert'; +import { assert as structAssert, number, object, string } from 'superstruct'; +import * as v from 'valibot'; + +export const run = () => { + const StructSchema = object({ name: string(), count: number() }); + structAssert({ name: 'ok', count: 3 }, StructSchema); + assert.throws(() => structAssert({ name: 'ok', count: 'bad' }, StructSchema)); + + const ValibotSchema = v.object({ name: v.string(), count: v.number() }); + assert.deepStrictEqual(v.parse(ValibotSchema, { name: 'ok', count: 3 }), { name: 'ok', count: 3 }); + assert.throws(() => v.parse(ValibotSchema, { name: 'ok', count: 'bad' })); + return 'PASS: superstruct and valibot ESM validation packages execute from node_modules'; +}; diff --git a/tests/node_modules_apps/config.jsonc b/tests/node_modules_apps/config.jsonc new file mode 100644 index 00000000..c5a9b101 --- /dev/null +++ b/tests/node_modules_apps/config.jsonc @@ -0,0 +1,118 @@ +{ + "apps": { + "module-interop": { + "category": "runnable", + "reason": "Synthetic npm-installed app covering CJS/ESM/package graph behavior", + "tests": { + "test-01-esm-import-cjs.js": "ESM app imports named/default exports from installed CJS packages", + "test-02-cjs-require-esm.cjs": "CJS app requires an installed synchronous ESM package", + "test-03-package-exports-imports.js": "Installed packages use conditional exports, subpaths, and package imports aliases", + "test-04-cycle-require-esm.cjs": "CJS require(esm) cycle inside an installed package reports ERR_REQUIRE_CYCLE_MODULE", + "test-05-tla-require.cjs": "CJS require() of installed TLA ESM reports ERR_REQUIRE_ASYNC_MODULE and dynamic import still works", + "test-06-conditional-import-graph.cjs": "Graph scanning follows import conditions for static ESM package imports", + "test-07-conditional-import-no-false-positive.cjs": "Graph scanning does not mark module-sync branches for static ESM package imports", + "test-08-conditional-imports-alias-graph.cjs": "Graph scanning follows import conditions for package imports aliases", + "test-09-create-require-alias-cycle.cjs": "Graph scanning handles createRequire aliases in ESM modules", + "test-10-already-evaluated-dependency.cjs": "CJS bridge can require an already evaluated ESM dependency", + "test-11-module-sync-before-import-graph.cjs": "Graph scanning honors module-sync before import in package exports", + "test-12-module-sync-before-imports-alias-graph.cjs": "Graph scanning honors module-sync before import in package imports aliases", + "test-13-scanner-false-positive-guards.cjs": "Graph scanning avoids property require, non-call createRequire, local createRequire, and nested CJS require false positives", + "test-14-exports-patterns.mjs": "Package exports wildcard patterns resolve for ESM, module-sync, and CJS require", + "test-15-imports-patterns.cjs": "Package imports wildcard patterns resolve for CJS require", + "test-16-shim-patterns.mjs": "OpenAI-style _shims/auto wildcard package exports resolve", + "test-17-cjs-lexer-parity.mjs": "Node-baselined cjs-module-lexer parity for module.exports object literals, spread require reexports, and Object.keys callback assignment reexports", + "test-18-cjs-lexer-helper-reexports.mjs": "Node-baselined cjs-module-lexer parity for __export/__exportStar helper reexport patterns", + "test-19-cjs-lexer-exports-assign.mjs": "Node-baselined cjs-module-lexer parity for EXPORTS_ASSIGN require bindings and documented export-star guard variants", + "test-20-cjs-lexer-define-property.mjs": "Node-baselined cjs-module-lexer parity for Object.defineProperty descriptor export patterns" + } + }, + "popular-pure-js": { + "category": "runnable", + "reason": "Popular pure-JS npm packages installed as a real app with node_modules attached as filesystem", + "tests": { + "test-01-cjs-utilities.cjs": "Classic CommonJS utilities and transitive dependencies", + "test-02-modern-esm.mjs": "Modern ESM and exports-heavy packages", + "test-03-date-fns-subpaths.mjs": "Package subpath exports", + "test-04-dotenv-fs.cjs": "Filesystem-backed configuration loading", + "test-05-ajv.cjs": "Larger CommonJS validation package graph", + "test-06-rxjs.mjs": "RxJS package exports and operator subpaths" + } + }, + "http-clients": { + "category": "runnable", + "reason": "HTTP client packages installed as a real app with node_modules attached as filesystem; tests avoid external network by using custom fetch/adapter paths", + "tests": { + "test-01-axios.cjs": "Axios CommonJS load, custom adapter, and interceptors", + "test-02-fetch-ky.mjs": "node-fetch and ky ESM package loading with local data/custom fetch paths", + "test-03-graphql-request.mjs": "graphql-request client execution with custom fetch" + } + }, + "crypto-auth": { + "category": "runnable", + "reason": "Authentication and crypto-adjacent pure-JS packages installed as a real app with node_modules attached as filesystem", + "tests": { + "test-01-jsonwebtoken-bcrypt.cjs": "jsonwebtoken and bcryptjs CommonJS execution", + "test-02-jose.mjs": "jose ESM JWT signing and verification", + "test-03-nanoid-cookie.mjs": "nanoid, cookie, and cookie-signature package interop" + } + }, + "data-formats": { + "category": "runnable", + "reason": "Data parsing and serialization packages installed as a real app with node_modules attached as filesystem", + "tests": { + "test-01-csv.cjs": "papaparse and csv-parse CommonJS CSV parsing", + "test-02-yaml-xml.cjs": "yaml and xml2js parsing/serialization", + "test-03-binary-protobuf.cjs": "msgpackr and protobufjs binary serialization" + } + }, + "fs-template-config": { + "category": "runnable", + "reason": "Configuration, templating, and filesystem glob packages installed as a real app with node_modules attached as filesystem", + "tests": { + "test-01-config-parsers.cjs": "ini and toml config parsing", + "test-02-template-engines.cjs": "ejs, handlebars, and mustache rendering", + "test-03-fast-glob-fs.cjs": "fast-glob filesystem traversal" + } + }, + "validation-schema": { + "category": "runnable", + "reason": "Validation packages installed as a real app with node_modules attached as filesystem", + "tests": { + "test-01-joi-yup.cjs": "joi and yup validation", + "test-02-superstruct-valibot.mjs": "superstruct and valibot validation" + } + }, + "logging-observability": { + "category": "runnable", + "reason": "Logging and observability packages installed as a real app with node_modules attached as filesystem; tests avoid subprocesses/transports", + "tests": { + "test-01-loggers.cjs": "pino, loglevel, and winston API loading without transports/processes", + "test-02-consola-otel.mjs": "consola and OpenTelemetry API loading" + } + }, + "cloud-sdk-offline": { + "category": "runnable", + "reason": "Cloud SDK packages installed as a real app with node_modules attached as filesystem; tests use offline constructors/API shapes only", + "tests": { + "test-01-openai.mjs": "OpenAI SDK offline client surface", + "test-02-anthropic.mjs": "Anthropic SDK offline client surface", + "test-03-aws-s3.mjs": "AWS S3 SDK offline client and command construction", + "test-04-stripe.cjs": "Stripe SDK offline client surface" + } + }, + "db-clients-offline": { + "category": "runnable", + "reason": "Database client packages installed as a real app with node_modules attached as filesystem; tests avoid network connections", + "tests": { + "test-01-sql-builders.cjs": "knex query builder offline execution", + "test-02-pg-mysql.cjs": "pg and mysql2 client construction without connecting", + "test-03-mongodb-redis.mjs": { + "coverage": "mongodb and redis client construction without connecting", + "flaky": true, + "reason": "Passes focused but can trap in string_decoder under concurrent group9 load" + }, + "test-04-drizzle.mjs": "drizzle-orm schema helpers offline execution" + } + } + } +} diff --git a/tests/runtime/cjs_require.rs b/tests/runtime/cjs_require.rs index 90a5e8c7..572f923c 100644 --- a/tests/runtime/cjs_require.rs +++ b/tests/runtime/cjs_require.rs @@ -145,3 +145,54 @@ async fn cjs_require_module_not_found( assert_eq!(r, Some(Val::Bool(true))); Ok(()) } + +#[test] +async fn cjs_require_package_exports( + #[tagged_as("cjs_require")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-package-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_require_package_imports( + #[tagged_as("cjs_require")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-package-imports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_require_package_map_edge_cases( + #[tagged_as("cjs_require")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-package-map-edge-cases", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} diff --git a/tests/runtime/diagnostics_channel.rs b/tests/runtime/diagnostics_channel.rs index bd825343..a13dcbd6 100644 --- a/tests/runtime/diagnostics_channel.rs +++ b/tests/runtime/diagnostics_channel.rs @@ -126,6 +126,72 @@ async fn diagnostics_channel_api( r["tracingChannelCtorError"].as_bool().unwrap(), "tracingChannelCtorError" ); + + // Module require/import tracing + assert!( + r["moduleRequireResult"].as_bool().unwrap(), + "moduleRequireResult" + ); + assert!( + r["moduleRequireTrace"].as_bool().unwrap(), + "moduleRequireTrace" + ); + assert!( + r["moduleRequireSameObject"].as_bool().unwrap(), + "moduleRequireSameObject" + ); + assert!( + r["moduleImportResult"].as_bool().unwrap(), + "moduleImportResult" + ); + assert!( + r["moduleImportTrace"].as_bool().unwrap(), + "moduleImportTrace" + ); + assert!( + r["moduleImportEndSameObject"].as_bool().unwrap(), + "moduleImportEndSameObject" + ); + assert!( + r["moduleImportAsyncStartSameObject"].as_bool().unwrap(), + "moduleImportAsyncStartSameObject" + ); + assert!( + r["moduleImportAsyncEndSameObject"].as_bool().unwrap(), + "moduleImportAsyncEndSameObject" + ); + assert!( + r["moduleRequireNestedResult"].as_bool().unwrap(), + "moduleRequireNestedResult" + ); + assert!( + r["moduleRequireNestedTrace"].as_bool().unwrap(), + "moduleRequireNestedTrace" + ); + assert!( + r["moduleImportCoerceOnce"].as_bool().unwrap(), + "moduleImportCoerceOnce" + ); + assert!( + r["moduleImportShadowParentResult"].as_bool().unwrap(), + "moduleImportShadowParentResult" + ); + assert!( + r["moduleImportShadowParent"].as_bool().unwrap(), + "moduleImportShadowParent" + ); + assert!( + r["moduleImportInvalidOptionsTrace"].as_bool().unwrap(), + "moduleImportInvalidOptionsTrace" + ); + assert!( + r["moduleImportUnsupportedAttrTrace"].as_bool().unwrap(), + "moduleImportUnsupportedAttrTrace" + ); + assert!( + r["moduleImportMixedAttrPriority"].as_bool().unwrap(), + "moduleImportMixedAttrPriority" + ); } else { anyhow::bail!("Expected string result from test function"); } diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 5107fecf..c54591c9 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -28,12 +28,15 @@ mod fetch; mod fs; mod imports; mod intl; +mod module_resolution; mod node_http; +mod node_modules_apps; mod os; mod path; mod pollable; mod response_constructor; mod response_static; +mod source_map; mod sqlite; mod stateful1; mod streams; @@ -44,7 +47,7 @@ mod url; mod v8_stack_trace; mod xhr; -// Tag suites into 8 groups for parallel CI matrix execution +// Tag suites into runtime groups for parallel CI matrix execution. tag_suite!(crypto, group1); tag_suite!(fetch, group2); @@ -75,6 +78,7 @@ tag_suite!(sqlite, group6); tag_suite!(url, group7); tag_suite!(cjs_require, group7); +tag_suite!(module_resolution, group7); tag_suite!(timeout, group7); tag_suite!(buffer, group7); tag_suite!(bigint_roundtrip, group7); @@ -82,12 +86,15 @@ tag_suite!(imports, group7); tag_suite!(response_static, group8); tag_suite!(v8_stack_trace, group8); +tag_suite!(source_map, group8); tag_suite!(structured_clone, group8); tag_suite!(node_http, group8); tag_suite!(intl, group8); tag_suite!(example1, group8); tag_suite!(example2, group8); +tag_suite!(node_modules_apps, group9); + #[test_dep(tagged_as = "example3", scope = Cloneable)] async fn compiled_example3() -> CompiledTest { let path = Utf8Path::new("examples/runtime/example3"); diff --git a/tests/runtime/module_resolution.rs b/tests/runtime/module_resolution.rs new file mode 100644 index 00000000..0e5c0643 --- /dev/null +++ b/tests/runtime/module_resolution.rs @@ -0,0 +1,607 @@ +use crate::common::{CompiledTest, invoke_and_capture_output}; +use camino::Utf8Path; +use test_r::{test, test_dep}; +use wasmtime::component::Val; + +#[test_dep(tagged_as = "module_resolution", scope = Cloneable)] +async fn compiled_module_resolution() -> CompiledTest { + let path = Utf8Path::new("examples/runtime/module-resolution"); + CompiledTest::new(path, true) + .await + .expect("Failed to compile module_resolution") +} + +#[test] +async fn esm_package_map_edge_cases( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-package-map-edge-cases", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_encoded_relative_paths( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-encoded-relative-paths", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_invalid_package_specifiers( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-invalid-package-specifiers", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_data_url_import_attributes( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-data-url-import-attributes", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_json_url_cache_keys( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-json-url-cache-keys", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn static_loader_absolute_entry_specifier( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-static-loader-absolute-entry-specifier", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn registered_loader_module_realm_isolation( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-registered-loader-module-realm-isolation", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_forbidden_cjs_globals( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-forbidden-cjs-globals", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn import_meta_resolve( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-import-meta-resolve", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_dynamic_import_attribute_scanner( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-dynamic-import-attribute-scanner", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn loader_commonjs_source_named_exports( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-loader-commonjs-source-named-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn loader_module_source_validation( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-loader-module-source-validation", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn package_custom_conditions( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-package-custom-conditions", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_package_json_parse_cache( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-package-json-parse-cache", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn sync_builtin_esm_exports( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-sync-builtin-esm-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_resolution_error_urls( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-resolution-error-urls", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_direct_named_exports( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-direct-named-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn esm_imports_side_effect_commonjs( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-esm-imports-side-effect-common-js", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_define_property_named_exports( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-define-property-named-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_reexport_named_exports( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-reexport-named-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_analyzer_false_positive_guards( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-analyzer-false-positive-guards", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_shared_loader_identity( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-shared-loader-identity", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn module_syntax_detection_and_diagnostics( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-module-syntax-detection-and-diagnostics", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_package_reexport_named_exports( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-package-reexport-named-exports", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn find_package_json( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-find-package-json", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn vm_main_context_default_loader( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-vm-main-context-default-loader", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn vm_source_text_module_link_semantics( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-vm-source-text-module-link-semantics", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn require_esm_error_handling( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-esm-error-handling", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn require_esm_tla_retry( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-esm-tla-retry", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn require_esm_rejection_tracking( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-esm-rejection-tracking", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn require_esm_cycle_guards( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-require-esm-cycle-guards", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_symlink_circular_cache( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-symlink-circular-cache", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_node_module_loading_compat( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-node-module-loading-compat", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_nested_dependency_cache_shape( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-nested-dependency-cache-shape", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} + +#[test] +async fn cjs_module_children_graph( + #[tagged_as("module_resolution")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = invoke_and_capture_output( + compiled_test.wasm_path(), + None, + "test-cjs-module-children-graph", + &[], + ) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +} diff --git a/tests/runtime/node_modules_apps.rs b/tests/runtime/node_modules_apps.rs new file mode 100644 index 00000000..237a4804 --- /dev/null +++ b/tests/runtime/node_modules_apps.rs @@ -0,0 +1,264 @@ +use crate::common::{ + CompiledTest, NodeModulesAppEntry, NodeModulesAppTestEntry, TestInstance, copy_dir_recursive, + load_node_modules_apps_config, +}; +use camino::{Utf8Path, Utf8PathBuf}; +use camino_tempfile::Utf8TempDir; +use std::env; +use std::fs; +use std::process::Command; +use std::sync::Arc; +use test_r::core::{DynamicTestRegistration, TestProperties}; +use test_r::{test_dep, test_gen}; +use wasmtime::component::Val; + +const CONFIG_PATH: &str = "tests/node_modules_apps/config.jsonc"; +const NODE_MODULES_APP_BASELINE_MAJOR: u32 = 22; +const NODE_MODULES_APP_BASELINE_MINOR: u32 = 14; +const NODE_MODULES_APP_BASELINE_PATCH: u32 = 0; +const NODE_MODULES_APP_STRICT_BASELINE_ENV: &str = "NODE_MODULES_APP_STRICT_NODE_BASELINE"; +const FLAKY_MAX_ATTEMPTS: usize = 3; + +#[test_dep(tagged_as = "node_modules_app_runner", scope = Cloneable)] +async fn compiled_node_modules_app_runner() -> CompiledTest { + let path = Utf8Path::new("examples/runtime/node-modules-app-runner"); + CompiledTest::new_with_features(path, true, crate::common::FeatureCombination::FullNoLogging) + .await + .expect("Failed to compile node-modules-app-runner") +} + +struct PreparedNodeModulesApp { + _temp_dir: Utf8TempDir, + app_dir: Utf8PathBuf, +} + +fn prepare_node_modules_app(app_name: &str) -> anyhow::Result { + let source_dir = Utf8Path::new("tests") + .join("node_modules_apps") + .join("apps") + .join(app_name); + let temp_dir = Utf8TempDir::new()?; + let app_dir = temp_dir.path().join("app"); + + copy_dir_recursive(source_dir.as_std_path(), app_dir.as_std_path())?; + + let status = Command::new("npm") + .arg("install") + .arg("--install-links") + .arg("--ignore-scripts") + .arg("--no-audit") + .arg("--no-fund") + .current_dir(&app_dir) + .status()?; + anyhow::ensure!(status.success(), "npm install failed for {app_name}"); + + Ok(PreparedNodeModulesApp { + _temp_dir: temp_dir, + app_dir, + }) +} + +fn strict_node_baseline_enabled() -> bool { + env::var(NODE_MODULES_APP_STRICT_BASELINE_ENV) + .map(|value| { + matches!( + value.to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) + .unwrap_or(false) +} + +fn parse_node_version(version: &str) -> anyhow::Result<(u32, u32, u32)> { + let mut parts = version.trim().split('.'); + let major = parts + .next() + .ok_or_else(|| anyhow::anyhow!("missing Node.js major version"))? + .parse::()?; + let minor = parts + .next() + .ok_or_else(|| anyhow::anyhow!("missing Node.js minor version"))? + .parse::()?; + let patch = parts + .next() + .ok_or_else(|| anyhow::anyhow!("missing Node.js patch version"))? + .parse::()?; + Ok((major, minor, patch)) +} + +fn ensure_node_supports_require_esm() -> anyhow::Result { + let output = Command::new("node") + .arg("-p") + .arg("process.versions.node") + .output()?; + anyhow::ensure!( + output.status.success(), + "failed to determine host Node.js version: {}", + String::from_utf8_lossy(&output.stderr), + ); + let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let (major, minor, patch) = parse_node_version(&version)?; + let baseline = format!( + "{NODE_MODULES_APP_BASELINE_MAJOR}.{NODE_MODULES_APP_BASELINE_MINOR}.{NODE_MODULES_APP_BASELINE_PATCH}" + ); + + if strict_node_baseline_enabled() { + anyhow::ensure!( + (major, minor, patch) + == ( + NODE_MODULES_APP_BASELINE_MAJOR, + NODE_MODULES_APP_BASELINE_MINOR, + NODE_MODULES_APP_BASELINE_PATCH + ), + "node_modules app strict Node baseline requires Node.js {baseline}; found {version}", + ); + return Ok(version); + } + + anyhow::ensure!( + major == NODE_MODULES_APP_BASELINE_MAJOR + && (minor > NODE_MODULES_APP_BASELINE_MINOR + || (minor == NODE_MODULES_APP_BASELINE_MINOR + && patch >= NODE_MODULES_APP_BASELINE_PATCH)), + "node_modules app tests require Node.js major {NODE_MODULES_APP_BASELINE_MAJOR} at or after {baseline}; found {version}", + ); + + if (minor, patch) + != ( + NODE_MODULES_APP_BASELINE_MINOR, + NODE_MODULES_APP_BASELINE_PATCH, + ) + { + eprintln!( + "warning: node_modules app tests are baselined against Node.js {baseline}; local Node.js {version} may differ in Node-baselined fixture behavior" + ); + } + + Ok(version) +} + +fn verify_with_node(app: &PreparedNodeModulesApp, test_file: &str) -> anyhow::Result<()> { + let node_version = ensure_node_supports_require_esm()?; + println!("Verifying node_modules app baseline with Node.js {node_version}"); + let output = Command::new("node") + .arg("run-node.mjs") + .arg(test_file) + .current_dir(&app.app_dir) + .output()?; + anyhow::ensure!( + output.status.success(), + "Node baseline failed for {}:\n[stdout]\n{}\n[stderr]\n{}", + test_file, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + Ok(()) +} + +async fn run_node_modules_app_test( + compiled_test: &CompiledTest, + app_name: &str, + test_file: &str, + timeout_secs: u64, +) -> anyhow::Result<()> { + let app = prepare_node_modules_app(app_name)?; + verify_with_node(&app, test_file)?; + + let mut instance = TestInstance::new(compiled_test.wasm_path()).await?; + instance.set_epoch_deadline(timeout_secs); + + let mounted_app_dir = instance.temp_dir_path().join("app"); + fs::create_dir_all(&mounted_app_dir)?; + copy_dir_recursive(app.app_dir.as_std_path(), mounted_app_dir.as_std_path())?; + + let guest_test_path = format!("/app/{test_file}"); + let (result, stdout, stderr) = instance + .invoke_and_capture_output_with_stderr(None, "run-test", &[Val::String(guest_test_path)]) + .await; + + let result = result?; + println!("Output:\n{}", stdout); + if !stderr.trim().is_empty() { + println!("Stderr:\n{}", stderr); + } + match result { + Some(Val::String(s)) if s.starts_with("PASS:") => Ok(()), + other => anyhow::bail!("Unexpected node_modules app result: {:?}", other), + } +} + +fn test_name(app: &NodeModulesAppEntry, test: &NodeModulesAppTestEntry) -> String { + format!( + "node_modules_app__{}__{}", + sanitize_name(&app.name), + sanitize_name(&test.file) + ) +} + +fn sanitize_name(value: &str) -> String { + value + .chars() + .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) + .collect() +} + +#[test_gen] +fn gen_node_modules_app_tests(r: &mut DynamicTestRegistration) { + let apps = + load_node_modules_apps_config(CONFIG_PATH).expect("Failed to load node_modules app config"); + let dependency_name = "compiledtest_node_modules_app_runner".to_string(); + + for app in apps { + for test in app.tests.clone() { + let app_name = app.name.clone(); + let test_file = test.file.clone(); + let timeout_secs = test.timeout_secs; + let flaky = test.flaky; + let generated_test_name = test_name(&app, &test); + let props = TestProperties { + is_ignored: test.category.should_ignore_in_runner(), + ..TestProperties::unit_test() + }; + + r.add_async_test( + generated_test_name, + props, + Some(vec![dependency_name.clone()]), + move |deps| { + let compiled_test: Arc = deps + .get("compiledtest_node_modules_app_runner") + .expect("CompiledTest dependency not found") + .downcast::() + .expect("CompiledTest type mismatch"); + let app_name = app_name.clone(); + let test_file = test_file.clone(); + Box::pin(async move { + let max_attempts = if flaky { FLAKY_MAX_ATTEMPTS } else { 1 }; + let mut last_err = None; + for attempt in 1..=max_attempts { + match run_node_modules_app_test( + compiled_test.as_ref(), + &app_name, + &test_file, + timeout_secs, + ) + .await + { + Ok(()) => return Ok(()), + Err(e) => { + if flaky && attempt < max_attempts { + println!( + "Flaky node_modules app test attempt {attempt}/{max_attempts} failed, retrying: {e:?}" + ); + } + last_err = Some(e); + } + } + } + Err(last_err.expect("at least one node_modules app test attempt")) + }) + }, + ); + } + } +} diff --git a/tests/runtime/source_map.rs b/tests/runtime/source_map.rs new file mode 100644 index 00000000..9fdcf8ae --- /dev/null +++ b/tests/runtime/source_map.rs @@ -0,0 +1,25 @@ +use crate::common::{CompiledTest, invoke_and_capture_output}; +use camino::Utf8Path; +use test_r::{test, test_dep}; +use wasmtime::component::Val; + +#[test_dep(tagged_as = "source_map", scope = Cloneable)] +async fn compiled_source_map() -> CompiledTest { + let path = Utf8Path::new("examples/runtime/source-map"); + CompiledTest::new(path, true) + .await + .expect("Failed to compile source-map") +} + +#[test] +async fn source_map_api( + #[tagged_as("source_map")] compiled_test: &CompiledTest, +) -> anyhow::Result<()> { + let (r, output) = + invoke_and_capture_output(compiled_test.wasm_path(), None, "test-source-map-api", &[]) + .await; + let r = r?; + println!("Output:\n{}", output); + assert_eq!(r, Some(Val::Bool(true))); + Ok(()) +}