From 3735a1a54225a058914f4356c2d271bb12fab6a7 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Fri, 7 Jun 2024 22:51:32 +0530 Subject: [PATCH 001/139] fix(ext/node): support stdin child_process IPC & fd stdout/stderr (#24106) Add supports for "ipc" and fd options in child_process spawn API. Internal changes: Adds a hidden rid and "ipc_for_internal_use" option to Deno.Command. Used by `node:child_process` Example: ```js const out = fs.openSync("./logfile.txt", 'a') const proc = spawn(process.execPath, ["./main.mjs", "child"], { stdio: ["ipc", out, "inherit"] }); ``` Ref #16753 --- ext/node/polyfills/internal/child_process.ts | 19 +++++++--- runtime/ops/process.rs | 38 +++++++++++++------- tests/specs/node/stdio_ipc/__test__.jsonc | 5 +++ tests/specs/node/stdio_ipc/main.mjs | 16 +++++++++ tests/specs/node/stdio_ipc/main.out | 1 + 5 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 tests/specs/node/stdio_ipc/__test__.jsonc create mode 100644 tests/specs/node/stdio_ipc/main.mjs create mode 100644 tests/specs/node/stdio_ipc/main.out diff --git a/ext/node/polyfills/internal/child_process.ts b/ext/node/polyfills/internal/child_process.ts index b6137e0d17dd6..cabae63ee4e49 100644 --- a/ext/node/polyfills/internal/child_process.ts +++ b/ext/node/polyfills/internal/child_process.ts @@ -362,17 +362,25 @@ export class ChildProcess extends EventEmitter { } } -const supportedNodeStdioTypes: NodeStdio[] = ["pipe", "ignore", "inherit"]; +const supportedNodeStdioTypes: NodeStdio[] = [ + "pipe", + "ignore", + "inherit", + "ipc", +]; function toDenoStdio( pipe: NodeStdio | number | Stream | null | undefined, ): DenoStdio { if (pipe instanceof Stream) { return "inherit"; } + if (typeof pipe === "number") { + /* Assume it's a rid returned by fs APIs */ + return pipe; + } if ( - !supportedNodeStdioTypes.includes(pipe as NodeStdio) || - typeof pipe === "number" + !supportedNodeStdioTypes.includes(pipe as NodeStdio) ) { notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`); } @@ -385,6 +393,8 @@ function toDenoStdio( return "null"; case "inherit": return "inherit"; + case "ipc": + return "ipc_for_internal_use"; default: notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`); } @@ -1083,8 +1093,7 @@ function toDenoArgs(args: string[]): string[] { if (useRunArgs) { // -A is not ideal, but needed to propagate permissions. - // --unstable is needed for Node compat. - denoArgs.unshift("run", "-A", "--unstable"); + denoArgs.unshift("run", "-A"); } return denoArgs; diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index b894b35db7376..ecf6ef49bb9e0 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -37,11 +37,12 @@ use std::os::unix::process::CommandExt; pub const UNSTABLE_FEATURE_NAME: &str = "process"; #[derive(Copy, Clone, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub enum Stdio { Inherit, Piped, Null, + IpcForInternalUse, } impl Stdio { @@ -50,6 +51,7 @@ impl Stdio { Stdio::Inherit => std::process::Stdio::inherit(), Stdio::Piped => std::process::Stdio::piped(), Stdio::Null => std::process::Stdio::null(), + _ => unreachable!(), } } } @@ -72,6 +74,9 @@ impl<'de> Deserialize<'de> for StdioOrRid { "inherit" => Ok(StdioOrRid::Stdio(Stdio::Inherit)), "piped" => Ok(StdioOrRid::Stdio(Stdio::Piped)), "null" => Ok(StdioOrRid::Stdio(Stdio::Null)), + "ipc_for_internal_use" => { + Ok(StdioOrRid::Stdio(Stdio::IpcForInternalUse)) + } val => Err(serde::de::Error::unknown_variant( val, &["inherit", "piped", "null"], @@ -102,6 +107,10 @@ impl StdioOrRid { } } } + + pub fn is_ipc(&self) -> bool { + matches!(self, StdioOrRid::Stdio(Stdio::IpcForInternalUse)) + } } deno_core::extension!( @@ -150,9 +159,9 @@ pub struct SpawnArgs { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChildStdio { - stdin: Stdio, - stdout: Stdio, - stderr: Stdio, + stdin: StdioOrRid, + stdout: StdioOrRid, + stderr: StdioOrRid, } #[derive(Serialize)] @@ -210,7 +219,7 @@ type CreateCommand = (std::process::Command, Option); fn create_command( state: &mut OpState, - args: SpawnArgs, + mut args: SpawnArgs, api_name: &str, ) -> Result { state @@ -249,14 +258,19 @@ fn create_command( command.uid(uid); } - command.stdin(args.stdio.stdin.as_stdio()); + if args.stdio.stdin.is_ipc() { + args.ipc = Some(0); + } else { + command.stdin(args.stdio.stdin.as_stdio(state)?); + } + command.stdout(match args.stdio.stdout { - Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?, - value => value.as_stdio(), + StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(1).as_stdio(state)?, + value => value.as_stdio(state)?, }); command.stderr(match args.stdio.stderr { - Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?, - value => value.as_stdio(), + StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(2).as_stdio(state)?, + value => value.as_stdio(state)?, }); #[cfg(unix)] @@ -608,8 +622,8 @@ fn op_spawn_sync( state: &mut OpState, #[serde] args: SpawnArgs, ) -> Result { - let stdout = matches!(args.stdio.stdout, Stdio::Piped); - let stderr = matches!(args.stdio.stderr, Stdio::Piped); + let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped)); + let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped)); let (mut command, _) = create_command(state, args, "Deno.Command().outputSync()")?; let output = command.output().with_context(|| { diff --git a/tests/specs/node/stdio_ipc/__test__.jsonc b/tests/specs/node/stdio_ipc/__test__.jsonc new file mode 100644 index 0000000000000..8ec9880fd5719 --- /dev/null +++ b/tests/specs/node/stdio_ipc/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "run -A main.mjs", + "output": "main.out", + "exitCode": 0 +} diff --git a/tests/specs/node/stdio_ipc/main.mjs b/tests/specs/node/stdio_ipc/main.mjs new file mode 100644 index 0000000000000..4a1a8ddbda759 --- /dev/null +++ b/tests/specs/node/stdio_ipc/main.mjs @@ -0,0 +1,16 @@ +import { spawn } from "node:child_process"; +import process from "node:process"; + +if (process.argv[2] === "child") { + process.send("hahah"); +} else { + const proc = spawn(process.execPath, ["./main.mjs", "child"], { + stdio: ["ipc", "inherit", "inherit"], + }); + + proc.on("message", function (msg) { + console.log(`msg: ${msg}`); + proc.kill(); + Deno.exit(0); + }); +} diff --git a/tests/specs/node/stdio_ipc/main.out b/tests/specs/node/stdio_ipc/main.out new file mode 100644 index 0000000000000..7979ca2ebdd55 --- /dev/null +++ b/tests/specs/node/stdio_ipc/main.out @@ -0,0 +1 @@ +msg: hahah From 9dc0e33e67d331c7d95b95d5a6eb380d4b9c732e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 7 Jun 2024 18:47:15 +0100 Subject: [PATCH 002/139] fix: upgrade deno_core (#24128) Includes https://github.com/denoland/deno_core/pull/770 necessary for https://github.com/denoland/deno/pull/24101. Also includes https://github.com/denoland/deno_core/pull/769 that fixes https://github.com/denoland/deno/issues/24098 and https://github.com/denoland/deno/issues/24069 and https://github.com/denoland/deno/issues/24089. --- Cargo.lock | 31 ++++++++++------- Cargo.toml | 2 +- ext/node/global.rs | 66 +++++++++++++++++++----------------- ext/node/ops/vm_internal.rs | 67 ++++++++++++++++++++++--------------- 4 files changed, 94 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f29b9995e88b..fa928f07ca387 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1303,9 +1303,9 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.284.0" +version = "0.285.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a5c809e81be26fcfbbce4275573251f6a156137b67059889e9e38f73e75b63" +checksum = "1b2df681c5746ff035671d98a3aa906f818024bb1bb9b8f9122a38cdc6a1b6e4" dependencies = [ "anyhow", "bincode", @@ -1759,9 +1759,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.160.0" +version = "0.161.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517e54d41a2da6a69b8f534294334d79d9115ddd43aea88a5ceefdb717e6d85e" +checksum = "504faf12efd80bdef6dc4d693072479dd916f397a151893963db66fb061f0c9d" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -5763,9 +5763,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.193.0" +version = "0.194.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ec612dfc7ab70330b5405e8015b25e637bbfe1d79c4bd173557933aea66e76" +checksum = "c56fad5568bad7902594c0b2fd33f7b97c1eb539837cc9a7f9d7742ca1c77883" dependencies = [ "num-bigint", "serde", @@ -7337,9 +7337,9 @@ dependencies = [ [[package]] name = "v8" -version = "0.92.0" +version = "0.93.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234589219e37a7496cbce73d971586db8369871be2420372c45a579b6a919b15" +checksum = "abc3ff12243d345cc697c151b29fbb8ad1f898e3f8b7aa1386701ff9f2c7cbbf" dependencies = [ "bitflags 2.5.0", "fslock", @@ -7347,7 +7347,7 @@ dependencies = [ "home", "miniz_oxide", "once_cell", - "which 5.0.0", + "which 6.0.1", ] [[package]] @@ -7640,15 +7640,14 @@ dependencies = [ [[package]] name = "which" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys 0.48.0", + "winsafe", ] [[package]] @@ -7888,6 +7887,12 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wtf8" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 26f242036cc90..d3bdf17f2f1c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ repository = "https://github.com/denoland/deno" [workspace.dependencies] deno_ast = { version = "=0.39.1", features = ["transpiling"] } -deno_core = { version = "0.284.0" } +deno_core = { version = "0.285.0" } deno_bench_util = { version = "0.149.0", path = "./bench_util" } deno_lockfile = "0.20.0" diff --git a/ext/node/global.rs b/ext/node/global.rs index d2fe7a1e57ad6..5c95b2f8fba02 100644 --- a/ext/node/global.rs +++ b/ext/node/global.rs @@ -15,13 +15,13 @@ use crate::resolution::NodeResolverRc; // these mapped functions per-thread. We should revisit it in the future and // ideally remove altogether. thread_local! { - pub static GETTER_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = getter.map_fn_to(); - pub static SETTER_MAP_FN: v8::GenericNamedPropertySetterCallback<'static> = setter.map_fn_to(); - pub static QUERY_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = query.map_fn_to(); - pub static DELETER_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = deleter.map_fn_to(); - pub static ENUMERATOR_MAP_FN: v8::GenericNamedPropertyEnumeratorCallback<'static> = enumerator.map_fn_to(); - pub static DEFINER_MAP_FN: v8::GenericNamedPropertyDefinerCallback<'static> = definer.map_fn_to(); - pub static DESCRIPTOR_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = descriptor.map_fn_to(); + pub static GETTER_MAP_FN: v8::NamedPropertyGetterCallback<'static> = getter.map_fn_to(); + pub static SETTER_MAP_FN: v8::NamedPropertySetterCallback<'static> = setter.map_fn_to(); + pub static QUERY_MAP_FN: v8::NamedPropertyGetterCallback<'static> = query.map_fn_to(); + pub static DELETER_MAP_FN: v8::NamedPropertyGetterCallback<'static> = deleter.map_fn_to(); + pub static ENUMERATOR_MAP_FN: v8::NamedPropertyEnumeratorCallback<'static> = enumerator.map_fn_to(); + pub static DEFINER_MAP_FN: v8::NamedPropertyDefinerCallback<'static> = definer.map_fn_to(); + pub static DESCRIPTOR_MAP_FN: v8::NamedPropertyGetterCallback<'static> = descriptor.map_fn_to(); } /// Convert an ASCII string to a UTF-16 byte encoding of the string. @@ -287,9 +287,9 @@ pub fn getter<'s>( key: v8::Local<'s, v8::Name>, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { if !is_managed_key(scope, key) { - return; + return v8::Intercepted::No; }; let this = args.this(); @@ -311,10 +311,11 @@ pub fn getter<'s>( undefined.into(), &[inner.into(), key.into(), this.into()], ) else { - return; + return v8::Intercepted::No; }; rv.set(value); + v8::Intercepted::Yes } pub fn setter<'s>( @@ -323,9 +324,9 @@ pub fn setter<'s>( value: v8::Local<'s, v8::Value>, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { if !is_managed_key(scope, key) { - return; + return v8::Intercepted::No; }; let this = args.this(); @@ -348,10 +349,11 @@ pub fn setter<'s>( undefined.into(), &[inner.into(), key.into(), value, this.into()], ) else { - return; + return v8::Intercepted::No; }; rv.set(success); + v8::Intercepted::Yes } pub fn query<'s>( @@ -359,9 +361,9 @@ pub fn query<'s>( key: v8::Local<'s, v8::Name>, _args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { if !is_managed_key(scope, key) { - return; + return v8::Intercepted::No; }; let mode = current_mode(scope); @@ -373,15 +375,16 @@ pub fn query<'s>( let inner = v8::Local::new(scope, inner); let Some(true) = inner.has_own_property(scope, key) else { - return; + return v8::Intercepted::No; }; let Some(attributes) = inner.get_property_attributes(scope, key.into()) else { - return; + return v8::Intercepted::No; }; rv.set_uint32(attributes.as_u32()); + v8::Intercepted::Yes } pub fn deleter<'s>( @@ -389,9 +392,9 @@ pub fn deleter<'s>( key: v8::Local<'s, v8::Name>, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { if !is_managed_key(scope, key) { - return; + return v8::Intercepted::No; }; let mode = current_mode(scope); @@ -404,17 +407,18 @@ pub fn deleter<'s>( let inner = v8::Local::new(scope, inner); let Some(success) = inner.delete(scope, key.into()) else { - return; + return v8::Intercepted::No; }; if args.should_throw_on_error() && !success { let message = v8::String::new(scope, "Cannot delete property").unwrap(); let exception = v8::Exception::type_error(scope, message); scope.throw_exception(exception); - return; + return v8::Intercepted::Yes; } rv.set_bool(success); + v8::Intercepted::Yes } pub fn enumerator<'s>( @@ -450,10 +454,10 @@ pub fn definer<'s>( key: v8::Local<'s, v8::Name>, descriptor: &v8::PropertyDescriptor, args: v8::PropertyCallbackArguments<'s>, - mut rv: v8::ReturnValue, -) { + _rv: v8::ReturnValue, +) -> v8::Intercepted { if !is_managed_key(scope, key) { - return; + return v8::Intercepted::No; }; let mode = current_mode(scope); @@ -466,17 +470,16 @@ pub fn definer<'s>( let inner = v8::Local::new(scope, inner); let Some(success) = inner.define_property(scope, key, descriptor) else { - return; + return v8::Intercepted::No; }; if args.should_throw_on_error() && !success { let message = v8::String::new(scope, "Cannot define property").unwrap(); let exception = v8::Exception::type_error(scope, message); scope.throw_exception(exception); - return; } - rv.set_bool(success); + v8::Intercepted::Yes } pub fn descriptor<'s>( @@ -484,9 +487,9 @@ pub fn descriptor<'s>( key: v8::Local<'s, v8::Name>, _args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { if !is_managed_key(scope, key) { - return; + return v8::Intercepted::No; }; let mode = current_mode(scope); @@ -502,12 +505,13 @@ pub fn descriptor<'s>( let Some(descriptor) = inner.get_own_property_descriptor(scope, key) else { scope.rethrow().expect("to have caught an exception"); - return; + return v8::Intercepted::Yes; }; if descriptor.is_undefined() { - return; + return v8::Intercepted::No; } rv.set(descriptor); + v8::Intercepted::Yes } diff --git a/ext/node/ops/vm_internal.rs b/ext/node/ops/vm_internal.rs index 9c641954e260c..bbdcba632d1e0 100644 --- a/ext/node/ops/vm_internal.rs +++ b/ext/node/ops/vm_internal.rs @@ -215,12 +215,12 @@ extern "C" fn c_noop(_: *const v8::FunctionCallbackInfo) {} // // See NOTE in ext/node/global.rs#L12 thread_local! { - pub static GETTER_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = property_getter.map_fn_to(); - pub static SETTER_MAP_FN: v8::GenericNamedPropertySetterCallback<'static> = property_setter.map_fn_to(); - pub static DELETER_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = property_deleter.map_fn_to(); - pub static ENUMERATOR_MAP_FN: v8::GenericNamedPropertyEnumeratorCallback<'static> = property_enumerator.map_fn_to(); - pub static DEFINER_MAP_FN: v8::GenericNamedPropertyDefinerCallback<'static> = property_definer.map_fn_to(); - pub static DESCRIPTOR_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = property_descriptor.map_fn_to(); + pub static GETTER_MAP_FN: v8::NamedPropertyGetterCallback<'static> = property_getter.map_fn_to(); + pub static SETTER_MAP_FN: v8::NamedPropertySetterCallback<'static> = property_setter.map_fn_to(); + pub static DELETER_MAP_FN: v8::NamedPropertyGetterCallback<'static> = property_deleter.map_fn_to(); + pub static ENUMERATOR_MAP_FN: v8::NamedPropertyEnumeratorCallback<'static> = property_enumerator.map_fn_to(); + pub static DEFINER_MAP_FN: v8::NamedPropertyDefinerCallback<'static> = property_definer.map_fn_to(); + pub static DESCRIPTOR_MAP_FN: v8::NamedPropertyGetterCallback<'static> = property_descriptor.map_fn_to(); } thread_local! { @@ -286,7 +286,7 @@ fn property_getter<'s>( key: v8::Local<'s, v8::Name>, args: v8::PropertyCallbackArguments<'s>, mut ret: v8::ReturnValue, -) { +) -> v8::Intercepted { let ctx = ContextifyContext::get(scope, args.this()).unwrap(); let sandbox = ctx.sandbox(scope); @@ -308,7 +308,10 @@ fn property_getter<'s>( } ret.set(rv); + return v8::Intercepted::Yes; } + + v8::Intercepted::No } fn property_setter<'s>( @@ -317,7 +320,7 @@ fn property_setter<'s>( value: v8::Local<'s, v8::Value>, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let ctx = ContextifyContext::get(scope, args.this()).unwrap(); let (attributes, is_declared_on_global_proxy) = match ctx @@ -339,7 +342,7 @@ fn property_setter<'s>( read_only |= attributes.is_read_only(); if read_only { - return; + return v8::Intercepted::No; } // true for x = 5 @@ -363,15 +366,15 @@ fn property_setter<'s>( && is_contextual_store && !is_function { - return; + return v8::Intercepted::No; } if !is_declared && key.is_symbol() { - return; + return v8::Intercepted::No; }; if ctx.sandbox(scope).set(scope, key.into(), value).is_none() { - return; + return v8::Intercepted::No; } if is_declared_on_sandbox { @@ -394,10 +397,13 @@ fn property_setter<'s>( .unwrap_or(false) { rv.set(value); + return v8::Intercepted::Yes; } } } } + + v8::Intercepted::No } fn property_deleter<'s>( @@ -405,17 +411,18 @@ fn property_deleter<'s>( key: v8::Local<'s, v8::Name>, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let ctx = ContextifyContext::get(scope, args.this()).unwrap(); let context = ctx.context(scope); let sandbox = ctx.sandbox(scope); let context_scope = &mut v8::ContextScope::new(scope, context); if !sandbox.delete(context_scope, key.into()).unwrap_or(false) { - return; + return v8::Intercepted::No; } rv.set_bool(false); + v8::Intercepted::Yes } fn property_enumerator<'s>( @@ -443,7 +450,7 @@ fn property_definer<'s>( desc: &v8::PropertyDescriptor, args: v8::PropertyCallbackArguments<'s>, _: v8::ReturnValue, -) { +) -> v8::Intercepted { let ctx = ContextifyContext::get(scope, args.this()).unwrap(); let context = ctx.context(scope); @@ -461,7 +468,7 @@ fn property_definer<'s>( // If the property is set on the global as read_only, don't change it on // the global or sandbox. if is_declared && read_only && dont_delete { - return; + return v8::Intercepted::No; } let sandbox = ctx.sandbox(scope); @@ -512,6 +519,8 @@ fn property_definer<'s>( define_prop_on_sandbox(scope, &mut desc_for_sandbox); } } + + v8::Intercepted::Yes } fn property_descriptor<'s>( @@ -519,7 +528,7 @@ fn property_descriptor<'s>( key: v8::Local<'s, v8::Name>, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let ctx = ContextifyContext::get(scope, args.this()).unwrap(); let context = ctx.context(scope); @@ -529,8 +538,11 @@ fn property_descriptor<'s>( if sandbox.has_own_property(scope, key).unwrap_or(false) { if let Some(desc) = sandbox.get_own_property_descriptor(scope, key) { rv.set(desc); + return v8::Intercepted::Yes; } } + + v8::Intercepted::No } fn uint32_to_name<'s>( @@ -547,9 +559,9 @@ fn indexed_property_getter<'s>( index: u32, args: v8::PropertyCallbackArguments<'s>, rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let key = uint32_to_name(scope, index); - property_getter(scope, key, args, rv); + property_getter(scope, key, args, rv) } fn indexed_property_setter<'s>( @@ -558,9 +570,9 @@ fn indexed_property_setter<'s>( value: v8::Local<'s, v8::Value>, args: v8::PropertyCallbackArguments<'s>, rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let key = uint32_to_name(scope, index); - property_setter(scope, key, value, args, rv); + property_setter(scope, key, value, args, rv) } fn indexed_property_deleter<'s>( @@ -568,19 +580,20 @@ fn indexed_property_deleter<'s>( index: u32, args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let ctx = ContextifyContext::get(scope, args.this()).unwrap(); let context = ctx.context(scope); let sandbox = ctx.sandbox(scope); let context_scope = &mut v8::ContextScope::new(scope, context); if !sandbox.delete_index(context_scope, index).unwrap_or(false) { - return; + return v8::Intercepted::No; } // Delete failed on the sandbox, intercept and do not delete on // the global object. rv.set_bool(false); + v8::Intercepted::No } fn indexed_property_definer<'s>( @@ -589,9 +602,9 @@ fn indexed_property_definer<'s>( descriptor: &v8::PropertyDescriptor, args: v8::PropertyCallbackArguments<'s>, rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let key = uint32_to_name(scope, index); - property_definer(scope, key, descriptor, args, rv); + property_definer(scope, key, descriptor, args, rv) } fn indexed_property_descriptor<'s>( @@ -599,7 +612,7 @@ fn indexed_property_descriptor<'s>( index: u32, args: v8::PropertyCallbackArguments<'s>, rv: v8::ReturnValue, -) { +) -> v8::Intercepted { let key = uint32_to_name(scope, index); - property_descriptor(scope, key, args, rv); + property_descriptor(scope, key, args, rv) } From 22d34f7012c48a25435b38c0c306085c614bbea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 8 Jun 2024 03:19:23 +0100 Subject: [PATCH 003/139] chore: upgrade deno_core to 0.287.0 (#24145) Includes: - [fix: lossily UTF8 decode module source text](https://github.com/denoland/deno_core/commit/ec38e428c6a887ca240068fd37be8ca0de6c2f18) - [fix: `ModuleMap` was not dropped if it had pending futures](https://github.com/denoland/deno_core/commit/5d12f510a1b2db2f6c7def8129c92202ad0f91ff) --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa928f07ca387..346651dc5ae25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1303,9 +1303,9 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.285.0" +version = "0.287.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2df681c5746ff035671d98a3aa906f818024bb1bb9b8f9122a38cdc6a1b6e4" +checksum = "8659fd7969621bea436a6d214589120c1fe22483753e19b009b7110e987e0acf" dependencies = [ "anyhow", "bincode", @@ -1759,9 +1759,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.161.0" +version = "0.163.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504faf12efd80bdef6dc4d693072479dd916f397a151893963db66fb061f0c9d" +checksum = "bdc9fd52e5fca85a834cc040ae1c6c675d8b718db5e14bbbf76996486dcc17fa" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -5763,9 +5763,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.194.0" +version = "0.196.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56fad5568bad7902594c0b2fd33f7b97c1eb539837cc9a7f9d7742ca1c77883" +checksum = "411789e6a59e2169c1bc198562de15241a368dde8961cb18dd1bddbba5e2e5f5" dependencies = [ "num-bigint", "serde", diff --git a/Cargo.toml b/Cargo.toml index d3bdf17f2f1c4..b1d807d96f701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ repository = "https://github.com/denoland/deno" [workspace.dependencies] deno_ast = { version = "=0.39.1", features = ["transpiling"] } -deno_core = { version = "0.285.0" } +deno_core = { version = "0.287.0" } deno_bench_util = { version = "0.149.0", path = "./bench_util" } deno_lockfile = "0.20.0" From c1f23c578881b85ae79b524a60160d8f4fb7151b Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sat, 8 Jun 2024 18:36:13 +0200 Subject: [PATCH 004/139] fix(ext/node): lossy UTF-8 read node_modules files (#24140) Previously various reads of files in `node_modules` would error on invalid UTF-8. These were cases involving: - reading package.json from Rust - reading package.json from JS - reading CommonJS files from JS - reading CommonJS files from Rust (for ESM translation) - reading ESM files from Rust --- cli/node.rs | 2 +- cli/resolver.rs | 7 +++++- cli/util/gitignore.rs | 2 +- ext/fs/interface.rs | 25 +++++++++++++------ ext/fs/ops.rs | 25 ++++++------------- ext/node/ops/require.rs | 2 +- ext/node/package_json.rs | 2 +- .../lossy-utf8-module/1.0.0/index.js | 1 + .../lossy-utf8-module/1.0.0/package.json | 6 +++++ .../lossy-utf8-package-json/1.0.0/index.js | 1 + .../1.0.0/package.json | 7 ++++++ .../lossy-utf8-script/1.0.0/index.js | 1 + .../lossy-utf8-script/1.0.0/package.json | 6 +++++ .../npm/lossy_utf8_module/__test__.jsonc | 5 ++++ tests/specs/npm/lossy_utf8_module/main.mjs | 3 +++ tests/specs/npm/lossy_utf8_module/main.out | 3 +++ .../lossy_utf8_package_json/__test__.jsonc | 5 ++++ .../npm/lossy_utf8_package_json/main.mjs | 3 +++ .../npm/lossy_utf8_package_json/main.out | 3 +++ .../npm/lossy_utf8_script/__test__.jsonc | 5 ++++ tests/specs/npm/lossy_utf8_script/main.mjs | 3 +++ tests/specs/npm/lossy_utf8_script/main.out | 3 +++ .../lossy_utf8_script_from_cjs/__test__.jsonc | 6 +++++ .../npm/lossy_utf8_script_from_cjs/main.mjs | 10 ++++++++ .../npm/lossy_utf8_script_from_cjs/main.out | 4 +++ tests/util/server/src/npm.rs | 5 ++-- 26 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/index.js create mode 100644 tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/package.json create mode 100644 tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/index.js create mode 100644 tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/package.json create mode 100644 tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/index.js create mode 100644 tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/package.json create mode 100644 tests/specs/npm/lossy_utf8_module/__test__.jsonc create mode 100644 tests/specs/npm/lossy_utf8_module/main.mjs create mode 100644 tests/specs/npm/lossy_utf8_module/main.out create mode 100644 tests/specs/npm/lossy_utf8_package_json/__test__.jsonc create mode 100644 tests/specs/npm/lossy_utf8_package_json/main.mjs create mode 100644 tests/specs/npm/lossy_utf8_package_json/main.out create mode 100644 tests/specs/npm/lossy_utf8_script/__test__.jsonc create mode 100644 tests/specs/npm/lossy_utf8_script/main.mjs create mode 100644 tests/specs/npm/lossy_utf8_script/main.out create mode 100644 tests/specs/npm/lossy_utf8_script_from_cjs/__test__.jsonc create mode 100644 tests/specs/npm/lossy_utf8_script_from_cjs/main.mjs create mode 100644 tests/specs/npm/lossy_utf8_script_from_cjs/main.out diff --git a/cli/node.rs b/cli/node.rs index c696fcac930e5..5ecbacdc72ffd 100644 --- a/cli/node.rs +++ b/cli/node.rs @@ -125,7 +125,7 @@ impl CjsCodeAnalyzer for CliCjsCodeAnalyzer { None => { self .fs - .read_text_file_async(specifier.to_file_path().unwrap(), None) + .read_text_file_lossy_async(specifier.to_file_path().unwrap(), None) .await? } }; diff --git a/cli/resolver.rs b/cli/resolver.rs index 301cd0666dfe6..3edc6f429247f 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -320,7 +320,12 @@ impl NpmModuleLoader { let code = if self.cjs_resolutions.contains(specifier) { // translate cjs to esm if it's cjs and inject node globals - let code = String::from_utf8(code)?; + let code = match String::from_utf8_lossy(&code) { + Cow::Owned(code) => code, + // SAFETY: `String::from_utf8_lossy` guarantees that the result is valid + // UTF-8 if `Cow::Borrowed` is returned. + Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(code) }, + }; ModuleSourceCode::String( self .node_code_translator diff --git a/cli/util/gitignore.rs b/cli/util/gitignore.rs index 12a450d64e019..4538e0912f8b8 100644 --- a/cli/util/gitignore.rs +++ b/cli/util/gitignore.rs @@ -105,7 +105,7 @@ impl GitIgnoreTree { }); let current = self .fs - .read_text_file_sync(&dir_path.join(".gitignore"), None) + .read_text_file_lossy_sync(&dir_path.join(".gitignore"), None) .ok() .and_then(|text| { let mut builder = ignore::gitignore::GitignoreBuilder::new(dir_path); diff --git a/ext/fs/interface.rs b/ext/fs/interface.rs index 70f9fdf636a17..5031dc1349519 100644 --- a/ext/fs/interface.rs +++ b/ext/fs/interface.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; @@ -284,24 +285,32 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { self.stat_sync(path).is_ok() } - fn read_text_file_sync( + fn read_text_file_lossy_sync( &self, path: &Path, access_check: Option, ) -> FsResult { let buf = self.read_file_sync(path, access_check)?; - String::from_utf8(buf).map_err(|err| { - std::io::Error::new(std::io::ErrorKind::InvalidData, err).into() - }) + Ok(string_from_utf8_lossy(buf)) } - async fn read_text_file_async<'a>( + async fn read_text_file_lossy_async<'a>( &'a self, path: PathBuf, access_check: Option>, ) -> FsResult { let buf = self.read_file_async(path, access_check).await?; - String::from_utf8(buf).map_err(|err| { - std::io::Error::new(std::io::ErrorKind::InvalidData, err).into() - }) + Ok(string_from_utf8_lossy(buf)) + } +} + +// Like String::from_utf8_lossy but operates on owned values +#[inline(always)] +fn string_from_utf8_lossy(buf: Vec) -> String { + match String::from_utf8_lossy(&buf) { + // buf contained non-utf8 chars than have been patched + Cow::Owned(s) => s, + // SAFETY: if Borrowed then the buf only contains utf8 chars, + // we do this instead of .into_owned() to avoid copying the input buf + Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) }, } } diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index 8e715d8258a5a..57b0e0b9e0bb1 100644 --- a/ext/fs/ops.rs +++ b/ext/fs/ops.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::borrow::Cow; use std::cell::RefCell; use std::io; use std::io::SeekFrom; @@ -1333,11 +1332,11 @@ where let fs = state.borrow::().clone(); let mut access_check = sync_permission_check::

(state.borrow_mut(), "Deno.readFileSync()"); - let buf = fs - .read_file_sync(&path, Some(&mut access_check)) + let str = fs + .read_text_file_lossy_sync(&path, Some(&mut access_check)) .map_err(|error| map_permission_error("readfile", error, &path))?; - Ok(string_from_utf8_lossy(buf)) + Ok(str) } #[op2(async)] @@ -1361,9 +1360,10 @@ where (state.borrow::().clone(), cancel_handle) }; - let fut = fs.read_file_async(path.clone(), Some(&mut access_check)); + let fut = + fs.read_text_file_lossy_async(path.clone(), Some(&mut access_check)); - let buf = if let Some(cancel_handle) = cancel_handle { + let str = if let Some(cancel_handle) = cancel_handle { let res = fut.or_cancel(cancel_handle).await; if let Some(cancel_rid) = cancel_rid { @@ -1379,18 +1379,7 @@ where .map_err(|error| map_permission_error("readfile", error, &path))? }; - Ok(string_from_utf8_lossy(buf)) -} - -// Like String::from_utf8_lossy but operates on owned values -fn string_from_utf8_lossy(buf: Vec) -> String { - match String::from_utf8_lossy(&buf) { - // buf contained non-utf8 chars than have been patched - Cow::Owned(s) => s, - // SAFETY: if Borrowed then the buf only contains utf8 chars, - // we do this instead of .into_owned() to avoid copying the input buf - Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) }, - } + Ok(str) } fn to_seek_from(offset: i64, whence: i32) -> Result { diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index de26870013773..3e1f1c6ae1019 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -451,7 +451,7 @@ where let file_path = PathBuf::from(file_path); ensure_read_permission::

(state, &file_path)?; let fs = state.borrow::(); - Ok(fs.read_text_file_sync(&file_path, None)?) + Ok(fs.read_text_file_lossy_sync(&file_path, None)?) } #[op2] diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs index adae7d63446c5..a19a2d64d8aca 100644 --- a/ext/node/package_json.rs +++ b/ext/node/package_json.rs @@ -82,7 +82,7 @@ impl PackageJson { return Ok(CACHE.with(|cache| cache.borrow()[&path].clone())); } - let source = match fs.read_text_file_sync(&path, None) { + let source = match fs.read_text_file_lossy_sync(&path, None) { Ok(source) => source, Err(err) if err.kind() == ErrorKind::NotFound => { return Ok(Rc::new(PackageJson::empty(path))); diff --git a/tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/index.js b/tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/index.js new file mode 100644 index 0000000000000..411f3c372e0a0 --- /dev/null +++ b/tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/index.js @@ -0,0 +1 @@ +export default 'þþÿÿ'; \ No newline at end of file diff --git a/tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/package.json b/tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/package.json new file mode 100644 index 0000000000000..1260655fba502 --- /dev/null +++ b/tests/registry/npm/@denotest/lossy-utf8-module/1.0.0/package.json @@ -0,0 +1,6 @@ +{ + "name": "@denotest/lossy-utf8-script", + "version": "1.0.0", + "type": "module", + "dependencies": {} +} diff --git a/tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/index.js b/tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/index.js new file mode 100644 index 0000000000000..34b58e6e126c2 --- /dev/null +++ b/tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/index.js @@ -0,0 +1 @@ +export default "hello"; diff --git a/tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/package.json b/tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/package.json new file mode 100644 index 0000000000000..5ca42d0d1232d --- /dev/null +++ b/tests/registry/npm/@denotest/lossy-utf8-package-json/1.0.0/package.json @@ -0,0 +1,7 @@ +{ + "name": "@denotest/lossy-utf8-package-json", + "version": "1.0.0", + "type": "module", + "dependencies": {}, + "files": ["þþÿÿ"] +} \ No newline at end of file diff --git a/tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/index.js b/tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/index.js new file mode 100644 index 0000000000000..adbfd382a77f8 --- /dev/null +++ b/tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/index.js @@ -0,0 +1 @@ +module.exports = 'þþÿÿ'; \ No newline at end of file diff --git a/tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/package.json b/tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/package.json new file mode 100644 index 0000000000000..e23605945c150 --- /dev/null +++ b/tests/registry/npm/@denotest/lossy-utf8-script/1.0.0/package.json @@ -0,0 +1,6 @@ +{ + "name": "@denotest/lossy-utf8-script", + "version": "1.0.0", + "type": "commonjs", + "dependencies": {} +} diff --git a/tests/specs/npm/lossy_utf8_module/__test__.jsonc b/tests/specs/npm/lossy_utf8_module/__test__.jsonc new file mode 100644 index 0000000000000..1ee7d2d6ce416 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_module/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "run main.mjs", + "output": "main.out", + "exitCode": 0 +} diff --git a/tests/specs/npm/lossy_utf8_module/main.mjs b/tests/specs/npm/lossy_utf8_module/main.mjs new file mode 100644 index 0000000000000..54cfbb16aefdd --- /dev/null +++ b/tests/specs/npm/lossy_utf8_module/main.mjs @@ -0,0 +1,3 @@ +import mod from "npm:@denotest/lossy-utf8-module@1.0.0"; + +console.log(mod); diff --git a/tests/specs/npm/lossy_utf8_module/main.out b/tests/specs/npm/lossy_utf8_module/main.out new file mode 100644 index 0000000000000..0e96f9ebb36f5 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_module/main.out @@ -0,0 +1,3 @@ +Download http://localhost:4260/@denotest/lossy-utf8-module +Download http://localhost:4260/@denotest/lossy-utf8-module/1.0.0.tgz +���� diff --git a/tests/specs/npm/lossy_utf8_package_json/__test__.jsonc b/tests/specs/npm/lossy_utf8_package_json/__test__.jsonc new file mode 100644 index 0000000000000..1ee7d2d6ce416 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_package_json/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "run main.mjs", + "output": "main.out", + "exitCode": 0 +} diff --git a/tests/specs/npm/lossy_utf8_package_json/main.mjs b/tests/specs/npm/lossy_utf8_package_json/main.mjs new file mode 100644 index 0000000000000..9a63eb604f2d7 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_package_json/main.mjs @@ -0,0 +1,3 @@ +import mod from "npm:@denotest/lossy-utf8-package-json@1.0.0"; + +console.log(mod); diff --git a/tests/specs/npm/lossy_utf8_package_json/main.out b/tests/specs/npm/lossy_utf8_package_json/main.out new file mode 100644 index 0000000000000..99aa5ab61f26f --- /dev/null +++ b/tests/specs/npm/lossy_utf8_package_json/main.out @@ -0,0 +1,3 @@ +Download http://localhost:4260/@denotest/lossy-utf8-package-json +Download http://localhost:4260/@denotest/lossy-utf8-package-json/1.0.0.tgz +hello diff --git a/tests/specs/npm/lossy_utf8_script/__test__.jsonc b/tests/specs/npm/lossy_utf8_script/__test__.jsonc new file mode 100644 index 0000000000000..1ee7d2d6ce416 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_script/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "run main.mjs", + "output": "main.out", + "exitCode": 0 +} diff --git a/tests/specs/npm/lossy_utf8_script/main.mjs b/tests/specs/npm/lossy_utf8_script/main.mjs new file mode 100644 index 0000000000000..59fe555aae8a5 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_script/main.mjs @@ -0,0 +1,3 @@ +import mod from "npm:@denotest/lossy-utf8-script@1.0.0"; + +console.log(mod); diff --git a/tests/specs/npm/lossy_utf8_script/main.out b/tests/specs/npm/lossy_utf8_script/main.out new file mode 100644 index 0000000000000..180ecdf1c3d3b --- /dev/null +++ b/tests/specs/npm/lossy_utf8_script/main.out @@ -0,0 +1,3 @@ +Download http://localhost:4260/@denotest/lossy-utf8-script +Download http://localhost:4260/@denotest/lossy-utf8-script/1.0.0.tgz +���� diff --git a/tests/specs/npm/lossy_utf8_script_from_cjs/__test__.jsonc b/tests/specs/npm/lossy_utf8_script_from_cjs/__test__.jsonc new file mode 100644 index 0000000000000..c8d353de00cc4 --- /dev/null +++ b/tests/specs/npm/lossy_utf8_script_from_cjs/__test__.jsonc @@ -0,0 +1,6 @@ +{ + "args": "run --node-modules-dir --allow-read main.mjs", + "output": "main.out", + "exitCode": 0, + "tempDir": true +} diff --git a/tests/specs/npm/lossy_utf8_script_from_cjs/main.mjs b/tests/specs/npm/lossy_utf8_script_from_cjs/main.mjs new file mode 100644 index 0000000000000..a9e70adfb8ffe --- /dev/null +++ b/tests/specs/npm/lossy_utf8_script_from_cjs/main.mjs @@ -0,0 +1,10 @@ +import { createRequire } from "node:module"; + +// Import this so that deno_graph knows to download this file. +if (false) import("npm:@denotest/lossy-utf8-script@1.0.0"); + +const require = createRequire(import.meta.url); + +const mod = require("@denotest/lossy-utf8-script"); + +console.log(mod); diff --git a/tests/specs/npm/lossy_utf8_script_from_cjs/main.out b/tests/specs/npm/lossy_utf8_script_from_cjs/main.out new file mode 100644 index 0000000000000..4f062a2aefb7b --- /dev/null +++ b/tests/specs/npm/lossy_utf8_script_from_cjs/main.out @@ -0,0 +1,4 @@ +Download http://localhost:4260/@denotest/lossy-utf8-script +Download http://localhost:4260/@denotest/lossy-utf8-script/1.0.0.tgz +Initialize @denotest/lossy-utf8-script@1.0.0 +���� diff --git a/tests/util/server/src/npm.rs b/tests/util/server/src/npm.rs index 66b7bddcd7249..e7d8d96ababe3 100644 --- a/tests/util/server/src/npm.rs +++ b/tests/util/server/src/npm.rs @@ -226,10 +226,11 @@ fn get_npm_package( tarballs.insert(version.clone(), tarball_bytes); let package_json_path = version_folder.join("package.json"); - let package_json_text = fs::read_to_string(&package_json_path) - .with_context(|| { + let package_json_bytes = + fs::read(&package_json_path).with_context(|| { format!("Error reading package.json at {}", package_json_path) })?; + let package_json_text = String::from_utf8_lossy(&package_json_bytes); let mut version_info: serde_json::Map = serde_json::from_str(&package_json_text)?; version_info.insert("dist".to_string(), dist.into()); From 585ba28d479ea1db20e80c318055c448cb13391b Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sat, 8 Jun 2024 18:36:43 +0200 Subject: [PATCH 005/139] fix(ext/websocket): correctly order messages when sending blobs (#24133) Previously the asynchronous read of the blob would not block sends that are started later. We now do this, but in such a way as to not regress performance in the common case of not using `Blob`. --- ext/websocket/01_websocket.js | 75 +++++++++++++++++++++++++++-------- tests/unit/websocket_test.ts | 25 ++++++++++++ 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/ext/websocket/01_websocket.js b/ext/websocket/01_websocket.js index 6c9bbc1575166..9b1b062441abb 100644 --- a/ext/websocket/01_websocket.js +++ b/ext/websocket/01_websocket.js @@ -25,6 +25,8 @@ const { ArrayBufferIsView, ArrayPrototypeJoin, ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, ArrayPrototypeSome, ErrorPrototypeToString, ObjectDefineProperties, @@ -41,7 +43,6 @@ const { SymbolFor, SymbolIterator, TypedArrayPrototypeGetByteLength, - Uint8Array, } = primordials; import { URL } from "ext:deno_url/00_url.js"; @@ -111,11 +112,14 @@ const _extensions = Symbol("[[extensions]]"); const _protocol = Symbol("[[protocol]]"); const _binaryType = Symbol("[[binaryType]]"); const _eventLoop = Symbol("[[eventLoop]]"); +const _sendQueue = Symbol("[[sendQueue]]"); +const _queueSend = Symbol("[[queueSend]]"); const _server = Symbol("[[server]]"); const _idleTimeoutDuration = Symbol("[[idleTimeout]]"); const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]"); const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]"); + class WebSocket extends EventTarget { constructor(url, protocols = []) { super(); @@ -129,6 +133,8 @@ class WebSocket extends EventTarget { this[_binaryType] = "blob"; this[_idleTimeoutDuration] = 0; this[_idleTimeoutTimeout] = undefined; + this[_sendQueue] = []; + const prefix = "Failed to construct 'WebSocket'"; webidl.requiredArguments(arguments.length, 1, prefix); url = webidl.converters.USVString(url, prefix, "Argument 1"); @@ -326,22 +332,26 @@ class WebSocket extends EventTarget { throw new DOMException("readyState not OPEN", "InvalidStateError"); } - if (ArrayBufferIsView(data)) { - op_ws_send_binary(this[_rid], data); - } else if (isArrayBuffer(data)) { - op_ws_send_binary(this[_rid], new Uint8Array(data)); - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { - PromisePrototypeThen( - // deno-lint-ignore prefer-primordials - data.slice().arrayBuffer(), - (ab) => op_ws_send_binary_ab(this[_rid], ab), - ); + if (this[_sendQueue].length === 0) { + // Fast path if the send queue is empty, for example when only synchronous + // data is being sent. + if (ArrayBufferIsView(data)) { + op_ws_send_binary(this[_rid], data); + } else if (isArrayBuffer(data)) { + op_ws_send_binary_ab(this[_rid], data); + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { + this[_queueSend](data); + } else { + const string = String(data); + op_ws_send_text( + this[_rid], + string, + ); + } } else { - const string = String(data); - op_ws_send_text( - this[_rid], - string, - ); + // Slower path if the send queue is not empty, for example when sending + // asynchronous data like a Blob. + this[_queueSend](data); } } @@ -508,6 +518,38 @@ class WebSocket extends EventTarget { } } + async [_queueSend](data) { + const queue = this[_sendQueue]; + + ArrayPrototypePush(queue, data); + + if (queue.length > 1) { + // There is already a send in progress, so we just push to the queue + // and let that task handle sending of this data. + return; + } + + while (queue.length > 0) { + const data = queue[0]; + if (ArrayBufferIsView(data)) { + op_ws_send_binary(this[_rid], data); + } else if (isArrayBuffer(data)) { + op_ws_send_binary_ab(this[_rid], data); + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { + // deno-lint-ignore prefer-primordials + const ab = await data.slice().arrayBuffer(); + op_ws_send_binary_ab(this[_rid], ab); + } else { + const string = String(data); + op_ws_send_text( + this[_rid], + string, + ); + } + ArrayPrototypeShift(queue); + } + } + [_serverHandleIdleTimeout]() { if (this[_idleTimeoutDuration]) { clearTimeout(this[_idleTimeoutTimeout]); @@ -608,6 +650,7 @@ function createWebSocketBranded() { socket[_binaryType] = "arraybuffer"; socket[_idleTimeoutDuration] = 0; socket[_idleTimeoutTimeout] = undefined; + socket[_sendQueue] = []; return socket; } diff --git a/tests/unit/websocket_test.ts b/tests/unit/websocket_test.ts index 223b13404dbab..74e85052e269a 100644 --- a/tests/unit/websocket_test.ts +++ b/tests/unit/websocket_test.ts @@ -706,6 +706,31 @@ Deno.test("echo arraybuffer with binaryType arraybuffer", async () => { await promise; }); +Deno.test("echo blob mixed with string", async () => { + const { promise, resolve } = Promise.withResolvers(); + const ws = new WebSocket("ws://localhost:4242"); + ws.binaryType = "arraybuffer"; + const blob = new Blob(["foo"]); + ws.onerror = () => fail(); + ws.onopen = () => { + ws.send(blob); + ws.send("bar"); + }; + const messages: (ArrayBuffer | string)[] = []; + ws.onmessage = (e) => { + messages.push(e.data); + if (messages.length === 2) { + assertEquals(messages[0], new Uint8Array([102, 111, 111]).buffer); + assertEquals(messages[1], "bar"); + ws.close(); + } + }; + ws.onclose = () => { + resolve(); + }; + await promise; +}); + Deno.test("Event Handlers order", async () => { const { promise, resolve } = Promise.withResolvers(); const ws = new WebSocket("ws://localhost:4242"); From 3f3bb6a829d61b2c12c3c63b8f8c7e5f929e3587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 9 Jun 2024 00:54:26 +0100 Subject: [PATCH 006/139] fix(ext/net): make node:http2 work with DENO_FUTURE=1 (#24144) --- ext/node/polyfills/http2.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index efd71ff932686..7a9d91097df66 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -5,6 +5,7 @@ // deno-lint-ignore-file prefer-primordials import { core, primordials } from "ext:core/mod.js"; +const { internalRidSymbol } = core; import { op_http2_client_get_response, op_http2_client_get_response_body_chunk, @@ -405,7 +406,7 @@ export class ClientHttp2Session extends Http2Session { const connPromise = new Promise((resolve) => { const eventName = url.startsWith("https") ? "secureConnect" : "connect"; socket.once(eventName, () => { - const rid = socket[kHandle][kStreamBaseField].rid; + const rid = socket[kHandle][kStreamBaseField][internalRidSymbol]; nextTick(() => { resolve(rid); }); From ed13d36e4bd9f36e0b8bce6a5e1c25a5b1f5560f Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 9 Jun 2024 02:02:47 +0200 Subject: [PATCH 007/139] fix(ext/node): add crypto and zlib constants (#24151) --- ext/node/polyfills/constants.ts | 188 ++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/ext/node/polyfills/constants.ts b/ext/node/polyfills/constants.ts index 5ea078dbd7c26..691621edc4c60 100644 --- a/ext/node/polyfills/constants.ts +++ b/ext/node/polyfills/constants.ts @@ -4,6 +4,8 @@ import { constants as fsConstants } from "node:fs"; import { constants as osConstants } from "node:os"; +import { constants as cryptoConstants } from "node:crypto"; +import { constants as zlibConstants } from "node:zlib"; export default { ...fsConstants, @@ -11,6 +13,8 @@ export default { ...osConstants.errno, ...osConstants.signals, ...osConstants.priority, + ...cryptoConstants, + ...zlibConstants, }; export const { @@ -180,3 +184,187 @@ export const { SIGXCPU, SIGXFSZ, } = osConstants.signals; +export const { + OPENSSL_VERSION_NUMBER, + SSL_OP_ALL, + SSL_OP_ALLOW_NO_DHE_KEX, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION, + SSL_OP_CIPHER_SERVER_PREFERENCE, + SSL_OP_CISCO_ANYCONNECT, + SSL_OP_COOKIE_EXCHANGE, + SSL_OP_CRYPTOPRO_TLSEXT_BUG, + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, + SSL_OP_EPHEMERAL_RSA, + SSL_OP_LEGACY_SERVER_CONNECT, + SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER, + SSL_OP_MICROSOFT_SESS_ID_BUG, + SSL_OP_MSIE_SSLV2_RSA_PADDING, + SSL_OP_NETSCAPE_CA_DN_BUG, + SSL_OP_NETSCAPE_CHALLENGE_BUG, + SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG, + SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG, + SSL_OP_NO_COMPRESSION, + SSL_OP_NO_ENCRYPT_THEN_MAC, + SSL_OP_NO_QUERY_MTU, + SSL_OP_NO_RENEGOTIATION, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION, + SSL_OP_NO_SSLv2, + SSL_OP_NO_SSLv3, + SSL_OP_NO_TICKET, + SSL_OP_NO_TLSv1, + SSL_OP_NO_TLSv1_1, + SSL_OP_NO_TLSv1_2, + SSL_OP_NO_TLSv1_3, + SSL_OP_PKCS1_CHECK_1, + SSL_OP_PKCS1_CHECK_2, + SSL_OP_PRIORITIZE_CHACHA, + SSL_OP_SINGLE_DH_USE, + SSL_OP_SINGLE_ECDH_USE, + SSL_OP_SSLEAY_080_CLIENT_DH_BUG, + SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG, + SSL_OP_TLS_BLOCK_PADDING_BUG, + SSL_OP_TLS_D5_BUG, + SSL_OP_TLS_ROLLBACK_BUG, + ENGINE_METHOD_RSA, + ENGINE_METHOD_DSA, + ENGINE_METHOD_DH, + ENGINE_METHOD_RAND, + ENGINE_METHOD_EC, + ENGINE_METHOD_CIPHERS, + ENGINE_METHOD_DIGESTS, + ENGINE_METHOD_PKEY_METHS, + ENGINE_METHOD_PKEY_ASN1_METHS, + ENGINE_METHOD_ALL, + ENGINE_METHOD_NONE, + DH_CHECK_P_NOT_SAFE_PRIME, + DH_CHECK_P_NOT_PRIME, + DH_UNABLE_TO_CHECK_GENERATOR, + DH_NOT_SUITABLE_GENERATOR, + ALPN_ENABLED, + RSA_PKCS1_PADDING, + RSA_SSLV23_PADDING, + RSA_NO_PADDING, + RSA_PKCS1_OAEP_PADDING, + RSA_X931_PADDING, + RSA_PKCS1_PSS_PADDING, + RSA_PSS_SALTLEN_DIGEST, + RSA_PSS_SALTLEN_MAX_SIGN, + RSA_PSS_SALTLEN_AUTO, + defaultCoreCipherList, + TLS1_VERSION, + TLS1_1_VERSION, + TLS1_2_VERSION, + TLS1_3_VERSION, + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_UNCOMPRESSED, + POINT_CONVERSION_HYBRID, +} = cryptoConstants; +export const { + Z_NO_FLUSH, + Z_PARTIAL_FLUSH, + Z_SYNC_FLUSH, + Z_FULL_FLUSH, + Z_FINISH, + Z_BLOCK, + Z_OK, + Z_STREAM_END, + Z_NEED_DICT, + Z_ERRNO, + Z_STREAM_ERROR, + Z_DATA_ERROR, + Z_MEM_ERROR, + Z_BUF_ERROR, + Z_VERSION_ERROR, + Z_NO_COMPRESSION, + Z_BEST_SPEED, + Z_BEST_COMPRESSION, + Z_DEFAULT_COMPRESSION, + Z_FILTERED, + Z_HUFFMAN_ONLY, + Z_RLE, + Z_FIXED, + Z_DEFAULT_STRATEGY, + ZLIB_VERNUM, + DEFLATE, + INFLATE, + GZIP, + GUNZIP, + DEFLATERAW, + INFLATERAW, + UNZIP, + BROTLI_DECODE, + BROTLI_ENCODE, + Z_MIN_WINDOWBITS, + Z_MAX_WINDOWBITS, + Z_DEFAULT_WINDOWBITS, + Z_MIN_CHUNK, + Z_MAX_CHUNK, + Z_DEFAULT_CHUNK, + Z_MIN_MEMLEVEL, + Z_MAX_MEMLEVEL, + Z_DEFAULT_MEMLEVEL, + Z_MIN_LEVEL, + Z_MAX_LEVEL, + Z_DEFAULT_LEVEL, + BROTLI_OPERATION_PROCESS, + BROTLI_OPERATION_FLUSH, + BROTLI_OPERATION_FINISH, + BROTLI_OPERATION_EMIT_METADATA, + BROTLI_PARAM_MODE, + BROTLI_MODE_GENERIC, + BROTLI_MODE_TEXT, + BROTLI_MODE_FONT, + BROTLI_DEFAULT_MODE, + BROTLI_PARAM_QUALITY, + BROTLI_MIN_QUALITY, + BROTLI_MAX_QUALITY, + BROTLI_DEFAULT_QUALITY, + BROTLI_PARAM_LGWIN, + BROTLI_MIN_WINDOW_BITS, + BROTLI_MAX_WINDOW_BITS, + BROTLI_LARGE_MAX_WINDOW_BITS, + BROTLI_DEFAULT_WINDOW, + BROTLI_PARAM_LGBLOCK, + BROTLI_MIN_INPUT_BLOCK_BITS, + BROTLI_MAX_INPUT_BLOCK_BITS, + BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING, + BROTLI_PARAM_SIZE_HINT, + BROTLI_PARAM_LARGE_WINDOW, + BROTLI_PARAM_NPOSTFIX, + BROTLI_PARAM_NDIRECT, + BROTLI_DECODER_RESULT_ERROR, + BROTLI_DECODER_RESULT_SUCCESS, + BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT, + BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT, + BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION, + BROTLI_DECODER_PARAM_LARGE_WINDOW, + BROTLI_DECODER_NO_ERROR, + BROTLI_DECODER_SUCCESS, + BROTLI_DECODER_NEEDS_MORE_INPUT, + BROTLI_DECODER_NEEDS_MORE_OUTPUT, + BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE, + BROTLI_DECODER_ERROR_FORMAT_RESERVED, + BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE, + BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET, + BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME, + BROTLI_DECODER_ERROR_FORMAT_CL_SPACE, + BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE, + BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT, + BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1, + BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2, + BROTLI_DECODER_ERROR_FORMAT_TRANSFORM, + BROTLI_DECODER_ERROR_FORMAT_DICTIONARY, + BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS, + BROTLI_DECODER_ERROR_FORMAT_PADDING_1, + BROTLI_DECODER_ERROR_FORMAT_PADDING_2, + BROTLI_DECODER_ERROR_FORMAT_DISTANCE, + BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET, + BROTLI_DECODER_ERROR_INVALID_ARGUMENTS, + BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES, + BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS, + BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP, + BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1, + BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2, + BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES, + BROTLI_DECODER_ERROR_UNREACHABLE, +} = zlibConstants; From f5d749d922e6434b876ac322ce0c1aeb9af1078c Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 9 Jun 2024 02:03:07 +0200 Subject: [PATCH 008/139] fix(ext/http): print `[]` around ipv6 addresses (#24150) --- ext/http/00_serve.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/http/00_serve.ts b/ext/http/00_serve.ts index 1f83ce73d17c9..7c665ac154d53 100644 --- a/ext/http/00_serve.ts +++ b/ext/http/00_serve.ts @@ -34,6 +34,7 @@ const { ObjectPrototypeIsPrototypeOf, PromisePrototypeCatch, PromisePrototypeThen, + StringPrototypeIncludes, Symbol, TypeError, TypedArrayPrototypeGetSymbolToStringTag, @@ -656,14 +657,19 @@ function serve(arg1, arg2) { // If the hostname is "0.0.0.0", we display "localhost" in console // because browsers in Windows don't resolve "0.0.0.0". // See the discussion in https://github.com/denoland/deno_std/issues/1165 - const hostname = addr.hostname == "0.0.0.0" ? "localhost" : addr.hostname; + const hostname = addr.hostname == "0.0.0.0" || addr.hostname == "::" + ? "localhost" + : addr.hostname; addr.hostname = hostname; const onListen = (scheme) => { if (options.onListen) { options.onListen(addr); } else { - console.log(`Listening on ${scheme}${addr.hostname}:${addr.port}/`); + const host = StringPrototypeIncludes(addr.hostname, ":") + ? `[${addr.hostname}]` + : addr.hostname; + console.log(`Listening on ${scheme}${host}:${addr.port}/`); } }; From 32f5b4808ef7591401b46ea5bb3d680c18dedf20 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 9 Jun 2024 02:03:24 +0200 Subject: [PATCH 009/139] fix(ext/web): correct string tag for MessageEvent (#24134) --- ext/web/02_event.js | 2 +- tests/wpt/runner/expectation.json | 2 +- tests/wpt/wpt.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/web/02_event.js b/ext/web/02_event.js index 510085aacc384..985578bcc2860 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -1230,7 +1230,7 @@ class MessageEvent extends Event { } // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CloseEvent"; + [SymbolToStringTag] = "MessageEvent"; } const MessageEventPrototype = MessageEvent.prototype; diff --git a/tests/wpt/runner/expectation.json b/tests/wpt/runner/expectation.json index e3db367832259..14fe7d9325c90 100644 --- a/tests/wpt/runner/expectation.json +++ b/tests/wpt/runner/expectation.json @@ -12355,7 +12355,7 @@ "return-value.worker.html": true, "event-ports-dedicated.html": true, "imagedata-cloned-canvas-in-array.html": false, - "message-event.html": false, + "message-event.html": true, "second-argument-dictionary.html": true, "second-argument-null-in-array.html": true, "second-argument-null.html": true, diff --git a/tests/wpt/wpt.ts b/tests/wpt/wpt.ts index 5fa56e41eb49d..f6d19110334f0 100755 --- a/tests/wpt/wpt.ts +++ b/tests/wpt/wpt.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno run --unstable --allow-write --allow-read --allow-net --allow-env --allow-run +#!/usr/bin/env -S deno run --allow-write --allow-read --allow-net --allow-env --allow-run // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // This script is used to run WPT tests for Deno. From 31154ff95899166a2535fc859c1fca2cbf19d422 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 8 Jun 2024 20:05:28 -0400 Subject: [PATCH 010/139] fix(check): attempt to resolve types from pkg before `@types` pkg (#24152) I've been meaning to fix this for ages, but I finally ran into it here: https://github.com/dsherret/ts-ast-viewer/actions/runs/9432038675/job/25981325408 We need to resolve the `@types` package as a fallback instead of eagerly resolving it. --- cli/npm/byonm.rs | 19 +------ cli/npm/common.rs | 22 ------- cli/npm/managed/mod.rs | 4 +- cli/npm/managed/resolvers/common.rs | 2 - cli/npm/managed/resolvers/global.rs | 32 +---------- cli/npm/managed/resolvers/local.rs | 12 ---- cli/resolver.rs | 12 +--- ext/node/analyze.rs | 1 - ext/node/lib.rs | 1 - ext/node/ops/require.rs | 1 - ext/node/resolution.rs | 57 ++++++++++++++++++- .../__test__.jsonc | 7 +++ .../check_prefers_non_types_node_pkg/main.ts | 3 + .../@types/lz-string/package.json | 12 ++++ .../node_modules/lz-string/index.d.ts | 1 + .../node_modules/lz-string/index.js | 1 + .../node_modules/lz-string/package.json | 4 ++ .../package.json | 6 ++ 18 files changed, 96 insertions(+), 101 deletions(-) create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/main.ts create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/package.json diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs index bfb9bed7f76aa..ec3fb00e94789 100644 --- a/cli/npm/byonm.rs +++ b/cli/npm/byonm.rs @@ -12,7 +12,6 @@ use deno_core::error::AnyError; use deno_core::serde_json; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NpmResolver; use deno_runtime::deno_node::PackageJson; use deno_semver::package::PackageReq; @@ -23,7 +22,6 @@ use crate::args::NpmProcessStateKind; use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs; use deno_runtime::fs_util::specifier_to_file_path; -use super::common::types_package_name; use super::CliNpmResolver; use super::InnerCliNpmResolverRef; @@ -97,20 +95,13 @@ impl NpmResolver for ByonmCliNpmResolver { &self, name: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result { fn inner( fs: &dyn FileSystem, name: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result { let referrer_file = specifier_to_file_path(referrer)?; - let types_pkg_name = if mode.is_types() && !name.starts_with("@types/") { - Some(types_package_name(name)) - } else { - None - }; let mut current_folder = referrer_file.parent().unwrap(); loop { let node_modules_folder = if current_folder.ends_with("node_modules") { @@ -119,14 +110,6 @@ impl NpmResolver for ByonmCliNpmResolver { Cow::Owned(current_folder.join("node_modules")) }; - // attempt to resolve the types package first, then fallback to the regular package - if let Some(types_pkg_name) = &types_pkg_name { - let sub_dir = join_package_name(&node_modules_folder, types_pkg_name); - if fs.is_dir_sync(&sub_dir) { - return Ok(sub_dir); - } - } - let sub_dir = join_package_name(&node_modules_folder, name); if fs.is_dir_sync(&sub_dir) { return Ok(sub_dir); @@ -146,7 +129,7 @@ impl NpmResolver for ByonmCliNpmResolver { ); } - let path = inner(&*self.fs, name, referrer, mode)?; + let path = inner(&*self.fs, name, referrer)?; Ok(self.fs.realpath_sync(&path)?) } diff --git a/cli/npm/common.rs b/cli/npm/common.rs index bf45aa5b816a6..c55f73cd50828 100644 --- a/cli/npm/common.rs +++ b/cli/npm/common.rs @@ -3,14 +3,6 @@ use deno_npm::npm_rc::RegistryConfig; use reqwest::header; -/// Gets the corresponding @types package for the provided package name. -pub fn types_package_name(package_name: &str) -> String { - debug_assert!(!package_name.starts_with("@types/")); - // Scoped packages will get two underscores for each slash - // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages - format!("@types/{}", package_name.replace('/', "__")) -} - // TODO(bartlomieju): support more auth methods besides token and basic auth pub fn maybe_auth_header_for_npm_registry( registry_config: &RegistryConfig, @@ -31,17 +23,3 @@ pub fn maybe_auth_header_for_npm_registry( None } - -#[cfg(test)] -mod test { - use super::types_package_name; - - #[test] - fn test_types_package_name() { - assert_eq!(types_package_name("name"), "@types/name"); - assert_eq!( - types_package_name("@scoped/package"), - "@types/@scoped__package" - ); - } -} diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs index 2298ea9bd71d0..d3045a1c9e229 100644 --- a/cli/npm/managed/mod.rs +++ b/cli/npm/managed/mod.rs @@ -23,7 +23,6 @@ use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NpmResolver; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; @@ -531,11 +530,10 @@ impl NpmResolver for ManagedCliNpmResolver { &self, name: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result { let path = self .fs_resolver - .resolve_package_folder_from_package(name, referrer, mode)?; + .resolve_package_folder_from_package(name, referrer)?; let path = canonicalize_path_maybe_not_exists_with_fs(&path, self.fs.as_ref())?; log::debug!("Resolved {} from {} to {}", name, referrer, path.display()); diff --git a/cli/npm/managed/resolvers/common.rs b/cli/npm/managed/resolvers/common.rs index 767b3f9c6dc5d..35f368cb54bfb 100644 --- a/cli/npm/managed/resolvers/common.rs +++ b/cli/npm/managed/resolvers/common.rs @@ -19,7 +19,6 @@ use deno_npm::NpmPackageId; use deno_npm::NpmResolutionPackage; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NodeResolutionMode; use crate::npm::managed::cache::TarballCache; @@ -41,7 +40,6 @@ pub trait NpmPackageFsResolver: Send + Sync { &self, name: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result; fn resolve_package_cache_folder_id_from_specifier( diff --git a/cli/npm/managed/resolvers/global.rs b/cli/npm/managed/resolvers/global.rs index bfd2f4de36f97..d10b33e7df522 100644 --- a/cli/npm/managed/resolvers/global.rs +++ b/cli/npm/managed/resolvers/global.rs @@ -11,16 +11,12 @@ use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::url::Url; -use deno_npm::resolution::PackageNotFoundFromReferrerError; use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; -use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NodeResolutionMode; -use super::super::super::common::types_package_name; use super::super::cache::NpmCache; use super::super::cache::TarballCache; use super::super::resolution::NpmResolution; @@ -57,17 +53,6 @@ impl GlobalNpmPackageResolver { system_info, } } - - fn resolve_types_package( - &self, - package_name: &str, - referrer_pkg_id: &NpmPackageCacheFolderId, - ) -> Result> { - let types_name = types_package_name(package_name); - self - .resolution - .resolve_package_from_package(&types_name, referrer_pkg_id) - } } #[async_trait(?Send)] @@ -92,7 +77,6 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { &self, name: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result { let Some(referrer_pkg_id) = self .cache @@ -100,19 +84,9 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { else { bail!("could not find npm package for '{}'", referrer); }; - let pkg = if mode.is_types() && !name.starts_with("@types/") { - // attempt to resolve the types package first, then fallback to the regular package - match self.resolve_types_package(name, &referrer_pkg_id) { - Ok(pkg) => pkg, - Err(_) => self - .resolution - .resolve_package_from_package(name, &referrer_pkg_id)?, - } - } else { - self - .resolution - .resolve_package_from_package(name, &referrer_pkg_id)? - }; + let pkg = self + .resolution + .resolve_package_from_package(name, &referrer_pkg_id)?; self.package_folder(&pkg.id) } diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index e93d400e1692f..d338720b67a4a 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -39,14 +39,12 @@ use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NodeResolutionMode; use deno_semver::package::PackageNv; use serde::Deserialize; use serde::Serialize; use crate::npm::cache_dir::mixed_case_package_name_encode; -use super::super::super::common::types_package_name; use super::super::cache::NpmCache; use super::super::cache::TarballCache; use super::super::resolution::NpmResolution; @@ -175,7 +173,6 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { &self, name: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result { let Some(local_path) = self.resolve_folder_for_specifier(referrer)? else { bail!("could not find npm package for '{}'", referrer); @@ -190,15 +187,6 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { Cow::Owned(current_folder.join("node_modules")) }; - // attempt to resolve the types package first, then fallback to the regular package - if mode.is_types() && !name.starts_with("@types/") { - let sub_dir = - join_package_name(&node_modules_folder, &types_package_name(name)); - if self.fs.is_dir_sync(&sub_dir) { - return Ok(sub_dir); - } - } - let sub_dir = join_package_name(&node_modules_folder, name); if self.fs.is_dir_sync(&sub_dir) { return Ok(sub_dir); diff --git a/cli/resolver.rs b/cli/resolver.rs index 3edc6f429247f..994f03f36d8e1 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -514,14 +514,11 @@ impl CliGraphResolver { &self, specifier: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, original_err: AnyError, resolver: &ByonmCliNpmResolver, ) -> Result<(), AnyError> { if let Ok((pkg_name, _, _)) = parse_npm_pkg_name(specifier, referrer) { - match resolver - .resolve_package_folder_from_package(&pkg_name, referrer, mode) - { + match resolver.resolve_package_folder_from_package(&pkg_name, referrer) { Ok(_) => { return Err(original_err); } @@ -650,7 +647,6 @@ impl Resolver for CliGraphResolver { .check_surface_byonm_node_error( specifier, referrer, - to_node_mode(mode), anyhow!("Cannot find \"{}\"", specifier), resolver, ) @@ -659,11 +655,7 @@ impl Resolver for CliGraphResolver { Err(err) => { self .check_surface_byonm_node_error( - specifier, - referrer, - to_node_mode(mode), - err, - resolver, + specifier, referrer, err, resolver, ) .map_err(ResolveError::Other)?; } diff --git a/ext/node/analyze.rs b/ext/node/analyze.rs index df68cb0fc45a3..3b06a90e08268 100644 --- a/ext/node/analyze.rs +++ b/ext/node/analyze.rs @@ -307,7 +307,6 @@ impl NodeCodeTranslator { let module_dir = self.npm_resolver.resolve_package_folder_from_package( package_specifier.as_str(), referrer, - mode, )?; let package_json_path = module_dir.join("package.json"); diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 36c13f8a56d7b..094bea3aeec75 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -162,7 +162,6 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync { &self, specifier: &str, referrer: &ModuleSpecifier, - mode: NodeResolutionMode, ) -> Result; fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool; diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index 3e1f1c6ae1019..3fde6c31ad87f 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -198,7 +198,6 @@ pub fn op_require_resolve_deno_dir( &ModuleSpecifier::from_file_path(&parent_filename).unwrap_or_else(|_| { panic!("Url::from_file_path: [{:?}]", parent_filename) }), - NodeResolutionMode::Execution, ) .ok() .map(|p| p.to_string_lossy().to_string()) diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs index 834b465cde195..8047ac4ecb57e 100644 --- a/ext/node/resolution.rs +++ b/ext/node/resolution.rs @@ -1037,9 +1037,45 @@ impl NodeResolver { } } + let result = self.resolve_package_subpath_for_package( + &package_name, + &package_subpath, + referrer, + referrer_kind, + conditions, + mode, + ); + if mode.is_types() && !matches!(result, Ok(Some(_))) { + // try to resolve with the @types/node package + let package_name = types_package_name(&package_name); + if let Ok(Some(result)) = self.resolve_package_subpath_for_package( + &package_name, + &package_subpath, + referrer, + referrer_kind, + conditions, + mode, + ) { + return Ok(Some(result)); + } + } + + result + } + + #[allow(clippy::too_many_arguments)] + fn resolve_package_subpath_for_package( + &self, + package_name: &str, + package_subpath: &str, + referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, + conditions: &[&str], + mode: NodeResolutionMode, + ) -> Result, AnyError> { let package_dir_path = self .npm_resolver - .resolve_package_folder_from_package(&package_name, referrer, mode)?; + .resolve_package_folder_from_package(package_name, referrer)?; let package_json_path = package_dir_path.join("package.json"); // todo: error with this instead when can't find package @@ -1060,7 +1096,7 @@ impl NodeResolver { .load_package_json(&mut AllowAllNodePermissions, package_json_path)?; self.resolve_package_subpath( &package_json, - &package_subpath, + package_subpath, referrer, referrer_kind, conditions, @@ -1600,6 +1636,14 @@ fn pattern_key_compare(a: &str, b: &str) -> i32 { 0 } +/// Gets the corresponding @types package for the provided package name. +fn types_package_name(package_name: &str) -> String { + debug_assert!(!package_name.starts_with("@types/")); + // Scoped packages will get two underscores for each slash + // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages + format!("@types/{}", package_name.replace('/', "__")) +} + #[cfg(test)] mod tests { use deno_core::serde_json::json; @@ -1780,4 +1824,13 @@ mod tests { assert_eq!(actual.to_string_lossy(), *expected); } } + + #[test] + fn test_types_package_name() { + assert_eq!(types_package_name("name"), "@types/name"); + assert_eq!( + types_package_name("@scoped/package"), + "@types/@scoped__package" + ); + } } diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc b/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc new file mode 100644 index 0000000000000..ed3827ef640ad --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc @@ -0,0 +1,7 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "args": "check --quiet main.ts", + "output": "" +} diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts b/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts new file mode 100644 index 0000000000000..8774bdbfc2bda --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts @@ -0,0 +1,3 @@ +import { compressToEncodedURIComponent } from "lz-string"; + +console.log(compressToEncodedURIComponent("Hello, World!")); diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json new file mode 100644 index 0000000000000..afe623e003b6b --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json @@ -0,0 +1,12 @@ +{ + "name": "@types/lz-string", + "version": "1.5.0", + "description": "Stub TypeScript definitions entry for lz-string, which provides its own types definitions", + "main": "", + "scripts": {}, + "license": "MIT", + "dependencies": { + "lz-string": "*" + }, + "deprecated": "This is a stub types definition. lz-string provides its own type definitions, so you do not need this installed." +} diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts new file mode 100644 index 0000000000000..b6abfd8ba5f38 --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts @@ -0,0 +1 @@ +export function compressToEncodedURIComponent(input: string): string; diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js new file mode 100644 index 0000000000000..603b710ba3679 --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js @@ -0,0 +1 @@ +module.exports.compressToEncodedURIComponent = (a) => a; diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json new file mode 100644 index 0000000000000..f8bfd5d988d5b --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json @@ -0,0 +1,4 @@ +{ + "name": "lz-string", + "version": "1.5.0" +} \ No newline at end of file diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/package.json b/tests/specs/npm/check_prefers_non_types_node_pkg/package.json new file mode 100644 index 0000000000000..ea3b2d26f4007 --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "lz-string": "*", + "@types/lz-string": "*" + } +} From 3be0a1e8b44782b2d3c3970e0e6fa7fb11be8131 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 10 Jun 2024 07:46:28 -0400 Subject: [PATCH 011/139] chore: add test for worker shutting down during npm import (#24109) --- .../__test__.jsonc | 13 +++++++++++++ .../npm/worker_shutdown_during_npm_import/main.out | 4 ++++ .../npm/worker_shutdown_during_npm_import/main.ts | 6 ++++++ .../worker_shutdown_during_npm_import/specifiers.ts | 11 +++++++++++ .../worker_shutdown_during_npm_import/worker1.ts | 7 +++++++ .../worker_shutdown_during_npm_import/worker2.ts | 7 +++++++ 6 files changed, 48 insertions(+) create mode 100644 tests/specs/npm/worker_shutdown_during_npm_import/__test__.jsonc create mode 100644 tests/specs/npm/worker_shutdown_during_npm_import/main.out create mode 100644 tests/specs/npm/worker_shutdown_during_npm_import/main.ts create mode 100644 tests/specs/npm/worker_shutdown_during_npm_import/specifiers.ts create mode 100644 tests/specs/npm/worker_shutdown_during_npm_import/worker1.ts create mode 100644 tests/specs/npm/worker_shutdown_during_npm_import/worker2.ts diff --git a/tests/specs/npm/worker_shutdown_during_npm_import/__test__.jsonc b/tests/specs/npm/worker_shutdown_during_npm_import/__test__.jsonc new file mode 100644 index 0000000000000..0be815e7d705a --- /dev/null +++ b/tests/specs/npm/worker_shutdown_during_npm_import/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "tempDir": true, + "tests": { + "with_lock": { + "args": "run -A --quiet --lock=deno.lock main.ts", + "output": "main.out" + }, + "without_lock": { + "args": "run -A --quiet main.ts", + "output": "main.out" + } + } +} diff --git a/tests/specs/npm/worker_shutdown_during_npm_import/main.out b/tests/specs/npm/worker_shutdown_during_npm_import/main.out new file mode 100644 index 0000000000000..649f816afbe5a --- /dev/null +++ b/tests/specs/npm/worker_shutdown_during_npm_import/main.out @@ -0,0 +1,4 @@ +[UNORDERED_START] +1 +2 +[UNORDERED_END] diff --git a/tests/specs/npm/worker_shutdown_during_npm_import/main.ts b/tests/specs/npm/worker_shutdown_during_npm_import/main.ts new file mode 100644 index 0000000000000..068cef6b68292 --- /dev/null +++ b/tests/specs/npm/worker_shutdown_during_npm_import/main.ts @@ -0,0 +1,6 @@ +new Worker(new URL("./worker1.ts", import.meta.url), { + type: "module", +}); +new Worker(new URL("./worker2.ts", import.meta.url), { + type: "module", +}); diff --git a/tests/specs/npm/worker_shutdown_during_npm_import/specifiers.ts b/tests/specs/npm/worker_shutdown_during_npm_import/specifiers.ts new file mode 100644 index 0000000000000..46116f842eed8 --- /dev/null +++ b/tests/specs/npm/worker_shutdown_during_npm_import/specifiers.ts @@ -0,0 +1,11 @@ +export default [ + "npm:chalk@4", + "npm:react@18.2", + "npm:preact@10.19", + "npm:ajv", + "npm:has", + "npm:picocolors", + "npm:@denotest/esm-basic", + "npm:@denotest/add", + "npm:@denotest/subtract", +]; diff --git a/tests/specs/npm/worker_shutdown_during_npm_import/worker1.ts b/tests/specs/npm/worker_shutdown_during_npm_import/worker1.ts new file mode 100644 index 0000000000000..1acf253b64b13 --- /dev/null +++ b/tests/specs/npm/worker_shutdown_during_npm_import/worker1.ts @@ -0,0 +1,7 @@ +import specifiers from "./specifiers.ts"; +await new Promise((resolve) => setTimeout(() => resolve(), 20)); + +await Promise.all(specifiers.map((specifier) => import(specifier))); + +console.log(1); +self.close(); diff --git a/tests/specs/npm/worker_shutdown_during_npm_import/worker2.ts b/tests/specs/npm/worker_shutdown_during_npm_import/worker2.ts new file mode 100644 index 0000000000000..7a381e93980c1 --- /dev/null +++ b/tests/specs/npm/worker_shutdown_during_npm_import/worker2.ts @@ -0,0 +1,7 @@ +import specifiers from "./specifiers.ts"; +// start importing, but close after waiting a short amount of time +specifiers.map((specifier) => import(specifier)); +await new Promise((resolve) => setTimeout(() => resolve(), 20)); +console.log(2); +self.close(); +console.log("WILL NOT BE PRINTED"); From 4fd3d5a86e45c4dcbaaa277cfb7f1087ddebfa48 Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Mon, 10 Jun 2024 20:00:56 +0530 Subject: [PATCH 012/139] fix(ext/node): send data frame with end_stream flag on _final call (#24147) --- ext/node/ops/http2.rs | 4 ++-- ext/node/polyfills/http2.ts | 23 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ext/node/ops/http2.rs b/ext/node/ops/http2.rs index e206db61bf7d1..abf7eae5d885b 100644 --- a/ext/node/ops/http2.rs +++ b/ext/node/ops/http2.rs @@ -344,6 +344,7 @@ pub async fn op_http2_client_send_data( state: Rc>, #[smi] stream_rid: ResourceId, #[buffer] data: JsBuffer, + end_of_stream: bool, ) -> Result<(), AnyError> { let resource = state .borrow() @@ -351,8 +352,7 @@ pub async fn op_http2_client_send_data( .get::(stream_rid)?; let mut stream = RcRef::map(&resource, |r| &r.stream).borrow_mut().await; - // TODO(bartlomieju): handle end of stream - stream.send_data(data.to_vec().into(), false)?; + stream.send_data(data.to_vec().into(), end_of_stream)?; Ok(()) } diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 7a9d91097df66..2a3b4f7f37513 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -978,7 +978,7 @@ export class ClientHttp2Stream extends Duplex { return; } - shutdownWritable(this, cb); + shutdownWritable(this, cb, this.#rid); } // TODO(bartlomieju): needs a proper cleanup @@ -1176,15 +1176,30 @@ export class ClientHttp2Stream extends Duplex { } } -function shutdownWritable(stream, callback) { +function shutdownWritable(stream, callback, streamRid) { debugHttp2(">>> shutdownWritable", callback); const state = stream[kState]; if (state.shutdownWritableCalled) { + debugHttp2(">>> shutdownWritable() already called"); return callback(); } state.shutdownWritableCalled = true; - onStreamTrailers(stream); - callback(); + if (state.flags & STREAM_FLAGS_HAS_TRAILERS) { + onStreamTrailers(stream); + callback(); + } else { + op_http2_client_send_data(streamRid, new Uint8Array(), true) + .then(() => { + callback(); + stream[kMaybeDestroy](); + core.tryClose(streamRid); + }) + .catch((e) => { + callback(e); + core.tryClose(streamRid); + stream._destroy(e); + }); + } // TODO(bartlomieju): might have to add "finish" event listener here, // check it. } From 7c5dbd5d54770dba5e56442b633e9597403ef5da Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Mon, 10 Jun 2024 17:03:17 +0100 Subject: [PATCH 013/139] feat(lsp): workspace jsr resolution (#24121) --- cli/jsr.rs | 219 +-------------------------- cli/lsp/config.rs | 163 ++++++++++++++++---- cli/lsp/documents.rs | 2 +- cli/lsp/jsr.rs | 267 +++++++++++++++++++++++++++++++++ cli/lsp/language_server.rs | 2 +- cli/lsp/resolver.rs | 9 +- tests/integration/lsp_tests.rs | 137 ++++++++++++----- 7 files changed, 505 insertions(+), 294 deletions(-) diff --git a/cli/jsr.rs b/cli/jsr.rs index e582ab9f0e06e..87a54af22b791 100644 --- a/cli/jsr.rs +++ b/cli/jsr.rs @@ -3,207 +3,14 @@ use crate::args::jsr_url; use crate::file_fetcher::FileFetcher; use dashmap::DashMap; -use deno_cache_dir::HttpCache; -use deno_core::parking_lot::Mutex; use deno_core::serde_json; -use deno_core::ModuleSpecifier; use deno_graph::packages::JsrPackageInfo; use deno_graph::packages::JsrPackageVersionInfo; -use deno_lockfile::Lockfile; use deno_runtime::deno_permissions::PermissionsContainer; -use deno_semver::jsr::JsrPackageReqReference; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; -use std::borrow::Cow; use std::sync::Arc; -/// Keep in sync with `JsrFetchResolver`! -#[derive(Debug)] -pub struct JsrCacheResolver { - nv_by_req: DashMap>, - /// The `module_graph` field of the version infos should be forcibly absent. - /// It can be large and we don't want to store it. - info_by_nv: DashMap>>, - info_by_name: DashMap>>, - cache: Arc, -} - -impl JsrCacheResolver { - pub fn new( - cache: Arc, - lockfile: Option>>, - ) -> Self { - let nv_by_req = DashMap::new(); - if let Some(lockfile) = lockfile { - for (req_url, nv_url) in &lockfile.lock().content.packages.specifiers { - let Some(req) = req_url.strip_prefix("jsr:") else { - continue; - }; - let Some(nv) = nv_url.strip_prefix("jsr:") else { - continue; - }; - let Ok(req) = PackageReq::from_str(req) else { - continue; - }; - let Ok(nv) = PackageNv::from_str(nv) else { - continue; - }; - nv_by_req.insert(req, Some(nv)); - } - } - Self { - nv_by_req, - info_by_nv: Default::default(), - info_by_name: Default::default(), - cache: cache.clone(), - } - } - - pub fn req_to_nv(&self, req: &PackageReq) -> Option { - if let Some(nv) = self.nv_by_req.get(req) { - return nv.value().clone(); - } - let maybe_get_nv = || { - let name = req.name.clone(); - let package_info = self.package_info(&name)?; - // Find the first matching version of the package which is cached. - let mut versions = package_info.versions.keys().collect::>(); - versions.sort(); - let version = versions - .into_iter() - .rev() - .find(|v| { - if req.version_req.tag().is_some() || !req.version_req.matches(v) { - return false; - } - let nv = PackageNv { - name: name.clone(), - version: (*v).clone(), - }; - self.package_version_info(&nv).is_some() - }) - .cloned()?; - Some(PackageNv { name, version }) - }; - let nv = maybe_get_nv(); - self.nv_by_req.insert(req.clone(), nv.clone()); - nv - } - - pub fn jsr_to_registry_url( - &self, - req_ref: &JsrPackageReqReference, - ) -> Option { - let req = req_ref.req().clone(); - let maybe_nv = self.req_to_nv(&req); - let nv = maybe_nv.as_ref()?; - let info = self.package_version_info(nv)?; - let path = info.export(&normalize_export_name(req_ref.sub_path()))?; - jsr_url() - .join(&format!("{}/{}/{}", &nv.name, &nv.version, &path)) - .ok() - } - - pub fn lookup_export_for_path( - &self, - nv: &PackageNv, - path: &str, - ) -> Option { - let info = self.package_version_info(nv)?; - let path = path.strip_prefix("./").unwrap_or(path); - let mut sloppy_fallback = None; - for (export, path_) in info.exports() { - let path_ = path_.strip_prefix("./").unwrap_or(path_); - if path_ == path { - return Some(export.strip_prefix("./").unwrap_or(export).to_string()); - } - // TSC in some cases will suggest a `.js` import path for a `.d.ts` source - // file. - if sloppy_fallback.is_none() { - let path = path - .strip_suffix(".js") - .or_else(|| path.strip_suffix(".mjs")) - .or_else(|| path.strip_suffix(".cjs")) - .unwrap_or(path); - let path_ = path_ - .strip_suffix(".d.ts") - .or_else(|| path_.strip_suffix(".d.mts")) - .or_else(|| path_.strip_suffix(".d.cts")) - .unwrap_or(path_); - if path_ == path { - sloppy_fallback = - Some(export.strip_prefix("./").unwrap_or(export).to_string()); - } - } - } - sloppy_fallback - } - - pub fn lookup_req_for_nv(&self, nv: &PackageNv) -> Option { - for entry in self.nv_by_req.iter() { - let Some(nv_) = entry.value() else { - continue; - }; - if nv_ == nv { - return Some(entry.key().clone()); - } - } - None - } - - pub fn package_info(&self, name: &str) -> Option> { - if let Some(info) = self.info_by_name.get(name) { - return info.value().clone(); - } - let read_cached_package_info = || { - let meta_url = jsr_url().join(&format!("{}/meta.json", name)).ok()?; - let meta_bytes = read_cached_url(&meta_url, &self.cache)?; - serde_json::from_slice::(&meta_bytes).ok() - }; - let info = read_cached_package_info().map(Arc::new); - self.info_by_name.insert(name.to_string(), info.clone()); - info - } - - pub fn package_version_info( - &self, - nv: &PackageNv, - ) -> Option> { - if let Some(info) = self.info_by_nv.get(nv) { - return info.value().clone(); - } - let read_cached_package_version_info = || { - let meta_url = jsr_url() - .join(&format!("{}/{}_meta.json", &nv.name, &nv.version)) - .ok()?; - let meta_bytes = read_cached_url(&meta_url, &self.cache)?; - partial_jsr_package_version_info_from_slice(&meta_bytes).ok() - }; - let info = read_cached_package_version_info().map(Arc::new); - self.info_by_nv.insert(nv.clone(), info.clone()); - info - } - - pub fn did_cache(&self) { - self.nv_by_req.retain(|_, nv| nv.is_some()); - self.info_by_nv.retain(|_, info| info.is_some()); - self.info_by_name.retain(|_, info| info.is_some()); - } -} - -fn read_cached_url( - url: &ModuleSpecifier, - cache: &Arc, -) -> Option> { - cache - .read_file_bytes( - &cache.cache_item_key(url).ok()?, - None, - deno_cache_dir::GlobalToLocalCopy::Disallow, - ) - .ok()? -} - /// This is similar to a subset of `JsrCacheResolver` which fetches rather than /// just reads the cache. Keep in sync! #[derive(Debug)] @@ -304,33 +111,9 @@ impl JsrFetchResolver { } } -// TODO(nayeemrmn): This is duplicated from a private function in deno_graph -// 0.65.1. Make it public or cleanup otherwise. -fn normalize_export_name(sub_path: Option<&str>) -> Cow { - let Some(sub_path) = sub_path else { - return Cow::Borrowed("."); - }; - if sub_path.is_empty() || matches!(sub_path, "/" | ".") { - Cow::Borrowed(".") - } else { - let sub_path = if sub_path.starts_with('/') { - Cow::Owned(format!(".{}", sub_path)) - } else if !sub_path.starts_with("./") { - Cow::Owned(format!("./{}", sub_path)) - } else { - Cow::Borrowed(sub_path) - }; - if let Some(prefix) = sub_path.strip_suffix('/') { - Cow::Owned(prefix.to_string()) - } else { - sub_path - } - } -} - /// This is a roundabout way of deserializing `JsrPackageVersionInfo`, /// because we only want the `exports` field and `module_graph` is large. -fn partial_jsr_package_version_info_from_slice( +pub fn partial_jsr_package_version_info_from_slice( slice: &[u8], ) -> serde_json::Result { let mut info = serde_json::from_slice::(slice)?; diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index f0338889571c8..e445d34f038fe 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -17,6 +17,7 @@ use deno_ast::MediaType; use deno_config::FmtOptionsConfig; use deno_config::TsConfig; use deno_core::anyhow::anyhow; +use deno_core::normalize_path; use deno_core::parking_lot::Mutex; use deno_core::serde::de::DeserializeOwned; use deno_core::serde::Deserialize; @@ -31,6 +32,8 @@ use deno_npm::npm_rc::ResolvedNpmRc; use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::fs_util::specifier_to_file_path; +use deno_semver::package::PackageNv; +use deno_semver::Version; use import_map::ImportMap; use lsp::Url; use lsp_types::ClientCapabilities; @@ -1077,6 +1080,17 @@ impl LspTsConfig { } } +#[derive(Debug, Clone)] +pub struct LspWorkspaceConfig { + pub members: Vec, +} + +#[derive(Debug, Clone)] +pub struct LspPackageConfig { + pub nv: PackageNv, + pub exports: Value, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConfigWatchedFileType { DenoJson, @@ -1103,6 +1117,14 @@ pub struct ConfigData { pub npmrc: Option>, pub import_map: Option>, pub import_map_from_settings: bool, + pub package_config: Option>, + pub is_workspace_root: bool, + /// Workspace member directories. For a workspace root this will be a list of + /// members. For a member this will be the same list, representing self and + /// siblings. For a solitary package this will be `vec![self.scope]`. These + /// are the list of packages to override with local resolutions for this + /// config scope. + pub workspace_members: Arc>, watched_files: HashMap, } @@ -1110,7 +1132,7 @@ impl ConfigData { async fn load( config_file_specifier: Option<&ModuleSpecifier>, scope: &ModuleSpecifier, - parent: Option<(&ModuleSpecifier, &ConfigData)>, + workspace_root: Option<(&ModuleSpecifier, &ConfigData)>, settings: &Settings, file_fetcher: Option<&Arc>, ) -> Self { @@ -1127,7 +1149,7 @@ impl ConfigData { Self::load_inner( Some(config_file), scope, - parent, + workspace_root, settings, file_fetcher, ) @@ -1139,8 +1161,14 @@ impl ConfigData { specifier.as_str(), err ); - let mut data = - Self::load_inner(None, scope, parent, settings, file_fetcher).await; + let mut data = Self::load_inner( + None, + scope, + workspace_root, + settings, + file_fetcher, + ) + .await; data .watched_files .insert(specifier.clone(), ConfigWatchedFileType::DenoJson); @@ -1158,14 +1186,15 @@ impl ConfigData { } } } else { - Self::load_inner(None, scope, parent, settings, file_fetcher).await + Self::load_inner(None, scope, workspace_root, settings, file_fetcher) + .await } } async fn load_inner( config_file: Option, scope: &ModuleSpecifier, - parent: Option<(&ModuleSpecifier, &ConfigData)>, + workspace_root: Option<(&ModuleSpecifier, &ConfigData)>, settings: &Settings, file_fetcher: Option<&Arc>, ) -> Self { @@ -1190,12 +1219,12 @@ impl ConfigData { } let mut fmt_options = None; - if let Some((_, parent_data)) = parent { + if let Some((_, workspace_data)) = workspace_root { let has_own_fmt_options = config_file .as_ref() .is_some_and(|config_file| config_file.json.fmt.is_some()); if !has_own_fmt_options { - fmt_options = Some(parent_data.fmt_options.clone()) + fmt_options = Some(workspace_data.fmt_options.clone()) } } let fmt_options = fmt_options.unwrap_or_else(|| { @@ -1221,14 +1250,14 @@ impl ConfigData { }); let mut lint_options_rules = None; - if let Some((_, parent_data)) = parent { + if let Some((_, workspace_data)) = workspace_root { let has_own_lint_options = config_file .as_ref() .is_some_and(|config_file| config_file.json.lint.is_some()); if !has_own_lint_options { lint_options_rules = Some(( - parent_data.lint_options.clone(), - parent_data.lint_rules.clone(), + workspace_data.lint_options.clone(), + workspace_data.lint_rules.clone(), )) } } @@ -1474,6 +1503,44 @@ impl ConfigData { } } + let package_config = config_file.as_ref().and_then(|c| { + Some(LspPackageConfig { + nv: PackageNv { + name: c.json.name.clone()?, + version: Version::parse_standard(c.json.version.as_ref()?).ok()?, + }, + exports: c.json.exports.clone()?, + }) + }); + + let is_workspace_root = config_file + .as_ref() + .is_some_and(|c| !c.json.workspaces.is_empty()); + let workspace_members = if is_workspace_root { + Arc::new( + config_file + .as_ref() + .map(|c| { + c.json + .workspaces + .iter() + .flat_map(|p| { + let dir_specifier = c.specifier.join(p).ok()?; + let dir_path = specifier_to_file_path(&dir_specifier).ok()?; + Url::from_directory_path(normalize_path(dir_path)).ok() + }) + .collect() + }) + .unwrap_or_default(), + ) + } else if let Some((_, workspace_data)) = workspace_root { + workspace_data.workspace_members.clone() + } else if config_file.as_ref().is_some_and(|c| c.json.name.is_some()) { + Arc::new(vec![scope.clone()]) + } else { + Arc::new(vec![]) + }; + ConfigData { scope: scope.clone(), config_file: config_file.map(Arc::new), @@ -1490,6 +1557,9 @@ impl ConfigData { npmrc, import_map: import_map.map(Arc::new), import_map_from_settings, + package_config: package_config.map(Arc::new), + is_workspace_root, + workspace_members, watched_files, } } @@ -1639,27 +1709,57 @@ impl ConfigTree { } for specifier in workspace_files { - if specifier.path().ends_with("/deno.json") - || specifier.path().ends_with("/deno.jsonc") + if !(specifier.path().ends_with("/deno.json") + || specifier.path().ends_with("/deno.jsonc")) { - if let Ok(scope) = specifier.join(".") { - if !scopes.contains_key(&scope) { - let parent = scopes - .iter() - .rev() - .find(|(s, _)| scope.as_str().starts_with(s.as_str())); - let data = ConfigData::load( - Some(specifier), - &scope, - parent, - settings, - Some(file_fetcher), - ) - .await; - scopes.insert(scope, data); + continue; + } + let Ok(scope) = specifier.join(".") else { + continue; + }; + if scopes.contains_key(&scope) { + continue; + } + let data = ConfigData::load( + Some(specifier), + &scope, + None, + settings, + Some(file_fetcher), + ) + .await; + if data.is_workspace_root { + for member_scope in data.workspace_members.iter() { + if scopes.contains_key(member_scope) { + continue; } + let Ok(member_path) = specifier_to_file_path(member_scope) else { + continue; + }; + let Some(config_file_path) = Some(member_path.join("deno.json")) + .filter(|p| p.exists()) + .or_else(|| { + Some(member_path.join("deno.jsonc")).filter(|p| p.exists()) + }) + else { + continue; + }; + let Ok(config_file_specifier) = Url::from_file_path(config_file_path) + else { + continue; + }; + let member_data = ConfigData::load( + Some(&config_file_specifier), + member_scope, + Some((&scope, &data)), + settings, + Some(file_fetcher), + ) + .await; + scopes.insert(member_scope.clone(), member_data); } } + scopes.insert(scope, data); } for folder_uri in settings.by_workspace_folder.keys() { @@ -1741,8 +1841,11 @@ fn resolve_node_modules_dir( fn resolve_lockfile_from_path(lockfile_path: PathBuf) -> Option { match read_lockfile_at_path(lockfile_path) { Ok(value) => { - if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) { - lsp_log!(" Resolved lockfile: \"{}\"", specifier); + if value.filename.exists() { + if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) + { + lsp_log!(" Resolved lockfile: \"{}\"", specifier); + } } Some(value) } diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index c7323d0c85840..5624ccb568636 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -1056,7 +1056,7 @@ impl Documents { Cow::Owned( self .resolver - .jsr_to_registry_url(&jsr_req_ref, file_referrer)?, + .jsr_to_resource_url(&jsr_req_ref, file_referrer)?, ) } else { Cow::Borrowed(specifier) diff --git a/cli/lsp/jsr.rs b/cli/lsp/jsr.rs index 27db4b0c8d19f..52d48c1156e50 100644 --- a/cli/lsp/jsr.rs +++ b/cli/lsp/jsr.rs @@ -1,20 +1,287 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::args::jsr_api_url; +use crate::args::jsr_url; use crate::file_fetcher::FileFetcher; +use crate::jsr::partial_jsr_package_version_info_from_slice; use crate::jsr::JsrFetchResolver; use dashmap::DashMap; +use deno_cache_dir::HttpCache; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::serde_json; +use deno_graph::packages::JsrPackageInfo; +use deno_graph::packages::JsrPackageInfoVersion; +use deno_graph::packages::JsrPackageVersionInfo; +use deno_graph::ModuleSpecifier; use deno_runtime::deno_permissions::PermissionsContainer; +use deno_semver::jsr::JsrPackageReqReference; use deno_semver::package::PackageNv; +use deno_semver::package::PackageReq; use deno_semver::Version; use serde::Deserialize; +use std::borrow::Cow; +use std::collections::HashMap; use std::sync::Arc; +use super::config::Config; +use super::config::ConfigData; use super::search::PackageSearchApi; +/// Keep in sync with `JsrFetchResolver`! +#[derive(Debug)] +pub struct JsrCacheResolver { + nv_by_req: DashMap>, + /// The `module_graph` fields of the version infos should be forcibly absent. + /// It can be large and we don't want to store it. + info_by_nv: DashMap>>, + info_by_name: DashMap>>, + workspace_scope_by_name: HashMap, + cache: Arc, +} + +impl JsrCacheResolver { + pub fn new( + cache: Arc, + config_data: Option<&ConfigData>, + config: &Config, + ) -> Self { + let nv_by_req = DashMap::new(); + let info_by_nv = DashMap::new(); + let info_by_name = DashMap::new(); + let mut workspace_scope_by_name = HashMap::new(); + if let Some(config_data) = config_data { + let config_data_by_scope = config.tree.data_by_scope(); + for member_scope in config_data.workspace_members.as_ref() { + let Some(member_data) = config_data_by_scope.get(member_scope) else { + continue; + }; + let Some(package_config) = member_data.package_config.as_ref() else { + continue; + }; + info_by_name.insert( + package_config.nv.name.clone(), + Some(Arc::new(JsrPackageInfo { + versions: [( + package_config.nv.version.clone(), + JsrPackageInfoVersion { yanked: false }, + )] + .into_iter() + .collect(), + })), + ); + info_by_nv.insert( + package_config.nv.clone(), + Some(Arc::new(JsrPackageVersionInfo { + exports: package_config.exports.clone(), + module_graph_1: None, + module_graph_2: None, + manifest: Default::default(), + })), + ); + workspace_scope_by_name + .insert(package_config.nv.name.clone(), member_scope.clone()); + } + } + if let Some(lockfile) = config_data.and_then(|d| d.lockfile.as_ref()) { + for (req_url, nv_url) in &lockfile.lock().content.packages.specifiers { + let Some(req) = req_url.strip_prefix("jsr:") else { + continue; + }; + let Some(nv) = nv_url.strip_prefix("jsr:") else { + continue; + }; + let Ok(req) = PackageReq::from_str(req) else { + continue; + }; + let Ok(nv) = PackageNv::from_str(nv) else { + continue; + }; + nv_by_req.insert(req, Some(nv)); + } + } + Self { + nv_by_req, + info_by_nv, + info_by_name, + workspace_scope_by_name, + cache: cache.clone(), + } + } + + pub fn req_to_nv(&self, req: &PackageReq) -> Option { + if let Some(nv) = self.nv_by_req.get(req) { + return nv.value().clone(); + } + let maybe_get_nv = || { + let name = req.name.clone(); + let package_info = self.package_info(&name)?; + // Find the first matching version of the package which is cached. + let mut versions = package_info.versions.keys().collect::>(); + versions.sort(); + let version = versions + .into_iter() + .rev() + .find(|v| { + if req.version_req.tag().is_some() || !req.version_req.matches(v) { + return false; + } + let nv = PackageNv { + name: name.clone(), + version: (*v).clone(), + }; + self.package_version_info(&nv).is_some() + }) + .cloned()?; + Some(PackageNv { name, version }) + }; + let nv = maybe_get_nv(); + self.nv_by_req.insert(req.clone(), nv.clone()); + nv + } + + pub fn jsr_to_resource_url( + &self, + req_ref: &JsrPackageReqReference, + ) -> Option { + let req = req_ref.req().clone(); + let maybe_nv = self.req_to_nv(&req); + let nv = maybe_nv.as_ref()?; + let info = self.package_version_info(nv)?; + let path = info.export(&normalize_export_name(req_ref.sub_path()))?; + if let Some(workspace_scope) = self.workspace_scope_by_name.get(&nv.name) { + workspace_scope.join(path).ok() + } else { + jsr_url() + .join(&format!("{}/{}/{}", &nv.name, &nv.version, &path)) + .ok() + } + } + + pub fn lookup_export_for_path( + &self, + nv: &PackageNv, + path: &str, + ) -> Option { + let info = self.package_version_info(nv)?; + let path = path.strip_prefix("./").unwrap_or(path); + let mut sloppy_fallback = None; + for (export, path_) in info.exports() { + let path_ = path_.strip_prefix("./").unwrap_or(path_); + if path_ == path { + return Some(export.strip_prefix("./").unwrap_or(export).to_string()); + } + // TSC in some cases will suggest a `.js` import path for a `.d.ts` source + // file. + if sloppy_fallback.is_none() { + let path = path + .strip_suffix(".js") + .or_else(|| path.strip_suffix(".mjs")) + .or_else(|| path.strip_suffix(".cjs")) + .unwrap_or(path); + let path_ = path_ + .strip_suffix(".d.ts") + .or_else(|| path_.strip_suffix(".d.mts")) + .or_else(|| path_.strip_suffix(".d.cts")) + .unwrap_or(path_); + if path_ == path { + sloppy_fallback = + Some(export.strip_prefix("./").unwrap_or(export).to_string()); + } + } + } + sloppy_fallback + } + + pub fn lookup_req_for_nv(&self, nv: &PackageNv) -> Option { + for entry in self.nv_by_req.iter() { + let Some(nv_) = entry.value() else { + continue; + }; + if nv_ == nv { + return Some(entry.key().clone()); + } + } + None + } + + pub fn package_info(&self, name: &str) -> Option> { + if let Some(info) = self.info_by_name.get(name) { + return info.value().clone(); + } + let read_cached_package_info = || { + let meta_url = jsr_url().join(&format!("{}/meta.json", name)).ok()?; + let meta_bytes = read_cached_url(&meta_url, &self.cache)?; + serde_json::from_slice::(&meta_bytes).ok() + }; + let info = read_cached_package_info().map(Arc::new); + self.info_by_name.insert(name.to_string(), info.clone()); + info + } + + pub fn package_version_info( + &self, + nv: &PackageNv, + ) -> Option> { + if let Some(info) = self.info_by_nv.get(nv) { + return info.value().clone(); + } + let read_cached_package_version_info = || { + let meta_url = jsr_url() + .join(&format!("{}/{}_meta.json", &nv.name, &nv.version)) + .ok()?; + let meta_bytes = read_cached_url(&meta_url, &self.cache)?; + partial_jsr_package_version_info_from_slice(&meta_bytes).ok() + }; + let info = read_cached_package_version_info().map(Arc::new); + self.info_by_nv.insert(nv.clone(), info.clone()); + info + } + + pub fn did_cache(&self) { + self.nv_by_req.retain(|_, nv| nv.is_some()); + self.info_by_nv.retain(|_, info| info.is_some()); + self.info_by_name.retain(|_, info| info.is_some()); + } +} + +fn read_cached_url( + url: &ModuleSpecifier, + cache: &Arc, +) -> Option> { + cache + .read_file_bytes( + &cache.cache_item_key(url).ok()?, + None, + deno_cache_dir::GlobalToLocalCopy::Disallow, + ) + .ok()? +} + +// TODO(nayeemrmn): This is duplicated from a private function in deno_graph +// 0.65.1. Make it public or cleanup otherwise. +fn normalize_export_name(sub_path: Option<&str>) -> Cow { + let Some(sub_path) = sub_path else { + return Cow::Borrowed("."); + }; + if sub_path.is_empty() || matches!(sub_path, "/" | ".") { + Cow::Borrowed(".") + } else { + let sub_path = if sub_path.starts_with('/') { + Cow::Owned(format!(".{}", sub_path)) + } else if !sub_path.starts_with("./") { + Cow::Owned(format!("./{}", sub_path)) + } else { + Cow::Borrowed(sub_path) + }; + if let Some(prefix) = sub_path.strip_suffix('/') { + Cow::Owned(prefix.to_string()) + } else { + sub_path + } + } +} + #[derive(Debug)] pub struct CliJsrSearchApi { file_fetcher: Arc, diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 466c5b430c4a9..f8a9652258b96 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -1471,7 +1471,7 @@ impl Inner { { if let Some(url) = self .resolver - .jsr_to_registry_url(&jsr_req_ref, file_referrer) + .jsr_to_resource_url(&jsr_req_ref, file_referrer) { result = format!("{result} (<{url}>)"); } diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 348eae76f6671..d0a515063b883 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -5,7 +5,6 @@ use crate::args::package_json; use crate::args::CacheSetting; use crate::graph_util::CliJsrUrlProvider; use crate::http_util::HttpClientProvider; -use crate::jsr::JsrCacheResolver; use crate::lsp::config::Config; use crate::lsp::config::ConfigData; use crate::npm::create_cli_npm_resolver_for_lsp; @@ -51,6 +50,7 @@ use std::rc::Rc; use std::sync::Arc; use super::cache::LspCache; +use super::jsr::JsrCacheResolver; #[derive(Debug, Clone)] pub struct LspResolver { @@ -99,7 +99,8 @@ impl LspResolver { ); let jsr_resolver = Some(Arc::new(JsrCacheResolver::new( cache.root_vendor_or_global(), - config_data.and_then(|d| d.lockfile.clone()), + config_data, + config, ))); let redirect_resolver = Some(Arc::new(RedirectResolver::new( cache.root_vendor_or_global(), @@ -212,12 +213,12 @@ impl LspResolver { .collect() } - pub fn jsr_to_registry_url( + pub fn jsr_to_resource_url( &self, req_ref: &JsrPackageReqReference, _file_referrer: Option<&ModuleSpecifier>, ) -> Option { - self.jsr_resolver.as_ref()?.jsr_to_registry_url(req_ref) + self.jsr_resolver.as_ref()?.jsr_to_resource_url(req_ref) } pub fn jsr_lookup_export_for_path( diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 581d436bb9efd..25fb695b48e05 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -11974,22 +11974,22 @@ fn lsp_vendor_dir() { client.shutdown(); } #[test] -fn lsp_deno_json_scopes_fmt_config() { +fn lsp_deno_json_workspace_fmt_config() { let context = TestContextBuilder::new().use_temp_cwd().build(); let temp_dir = context.temp_dir(); - temp_dir.create_dir_all("project1"); temp_dir.write( - "project1/deno.json", + "deno.json", json!({ + "workspaces": ["project1", "project2"], "fmt": { "semiColons": false, }, }) .to_string(), ); - temp_dir.create_dir_all("project2"); + temp_dir.create_dir_all("project1"); temp_dir.write( - "project2/deno.json", + "project1/deno.json", json!({ "fmt": { "singleQuote": true, @@ -11997,13 +11997,13 @@ fn lsp_deno_json_scopes_fmt_config() { }) .to_string(), ); - temp_dir.create_dir_all("project2/project3"); - temp_dir.write("project2/project3/deno.json", json!({}).to_string()); + temp_dir.create_dir_all("project2"); + temp_dir.write("project2/deno.json", json!({}).to_string()); let mut client = context.new_lsp_command().build(); client.initialize_default(); client.did_open(json!({ "textDocument": { - "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + "uri": temp_dir.uri().join("file.ts").unwrap(), "languageId": "typescript", "version": 1, "text": "console.log(\"\");\n", @@ -12013,7 +12013,7 @@ fn lsp_deno_json_scopes_fmt_config() { "textDocument/formatting", json!({ "textDocument": { - "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + "uri": temp_dir.uri().join("file.ts").unwrap(), }, "options": { "tabSize": 2, @@ -12033,7 +12033,7 @@ fn lsp_deno_json_scopes_fmt_config() { ); client.did_open(json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), "languageId": "typescript", "version": 1, "text": "console.log(\"\");\n", @@ -12043,7 +12043,7 @@ fn lsp_deno_json_scopes_fmt_config() { "textDocument/formatting", json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), }, "options": { "tabSize": 2, @@ -12061,11 +12061,11 @@ fn lsp_deno_json_scopes_fmt_config() { "newText": "''", }]) ); - // `project2/project3/file.ts` should use the fmt settings from - // `project2/deno.json`, since `project2/project3/deno.json` has no fmt field. + // `project2/file.ts` should use the fmt settings from `deno.json`, since it + // has no fmt field. client.did_open(json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(), + "uri": temp_dir.uri().join("project2/file.ts").unwrap(), "languageId": "typescript", "version": 1, "text": "console.log(\"\");\n", @@ -12075,7 +12075,7 @@ fn lsp_deno_json_scopes_fmt_config() { "textDocument/formatting", json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(), + "uri": temp_dir.uri().join("project2/file.ts").unwrap(), }, "options": { "tabSize": 2, @@ -12087,23 +12087,23 @@ fn lsp_deno_json_scopes_fmt_config() { res, json!([{ "range": { - "start": { "line": 0, "character": 12 }, - "end": { "line": 0, "character": 14 }, + "start": { "line": 0, "character": 15 }, + "end": { "line": 0, "character": 16 }, }, - "newText": "''", + "newText": "", }]) ); client.shutdown(); } #[test] -fn lsp_deno_json_scopes_lint_config() { +fn lsp_deno_json_workspace_lint_config() { let context = TestContextBuilder::new().use_temp_cwd().build(); let temp_dir = context.temp_dir(); - temp_dir.create_dir_all("project1"); temp_dir.write( - "project1/deno.json", + "deno.json", json!({ + "workspaces": ["project1", "project2"], "lint": { "rules": { "include": ["camelcase"], @@ -12112,9 +12112,9 @@ fn lsp_deno_json_scopes_lint_config() { }) .to_string(), ); - temp_dir.create_dir_all("project2"); + temp_dir.create_dir_all("project1"); temp_dir.write( - "project2/deno.json", + "project1/deno.json", json!({ "lint": { "rules": { @@ -12124,13 +12124,13 @@ fn lsp_deno_json_scopes_lint_config() { }) .to_string(), ); - temp_dir.create_dir_all("project2/project3"); - temp_dir.write("project2/project3/deno.json", json!({}).to_string()); + temp_dir.create_dir_all("project2"); + temp_dir.write("project2/deno.json", json!({}).to_string()); let mut client = context.new_lsp_command().build(); client.initialize_default(); let diagnostics = client.did_open(json!({ "textDocument": { - "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + "uri": temp_dir.uri().join("file.ts").unwrap(), "languageId": "typescript", "version": 1, "text": r#" @@ -12143,7 +12143,7 @@ fn lsp_deno_json_scopes_lint_config() { assert_eq!( json!(diagnostics.messages_with_source("deno-lint")), json!({ - "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + "uri": temp_dir.uri().join("file.ts").unwrap(), "diagnostics": [{ "range": { "start": { "line": 2, "character": 14 }, @@ -12161,13 +12161,13 @@ fn lsp_deno_json_scopes_lint_config() { "textDocument/didClose", json!({ "textDocument": { - "uri": temp_dir.uri().join("project1/file.ts").unwrap(), + "uri": temp_dir.uri().join("file.ts").unwrap(), }, }), ); let diagnostics = client.did_open(json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), "languageId": "typescript", "version": 1, "text": r#" @@ -12180,7 +12180,7 @@ fn lsp_deno_json_scopes_lint_config() { assert_eq!( json!(diagnostics.messages_with_source("deno-lint")), json!({ - "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), "diagnostics": [{ "range": { "start": { "line": 1, "character": 8 }, @@ -12198,16 +12198,15 @@ fn lsp_deno_json_scopes_lint_config() { "textDocument/didClose", json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/file.ts").unwrap(), + "uri": temp_dir.uri().join("project1/file.ts").unwrap(), }, }), ); - // `project2/project3/file.ts` should use the lint settings from - // `project2/deno.json`, since `project2/project3/deno.json` has no lint - // field. + // `project2/file.ts` should use the lint settings from `deno.json`, since it + // has no lint field. let diagnostics = client.did_open(json!({ "textDocument": { - "uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(), + "uri": temp_dir.uri().join("project2/file.ts").unwrap(), "languageId": "typescript", "version": 1, "text": r#" @@ -12220,16 +12219,16 @@ fn lsp_deno_json_scopes_lint_config() { assert_eq!( json!(diagnostics.messages_with_source("deno-lint")), json!({ - "uri": temp_dir.uri().join("project2/project3/file.ts").unwrap(), + "uri": temp_dir.uri().join("project2/file.ts").unwrap(), "diagnostics": [{ "range": { - "start": { "line": 1, "character": 8 }, - "end": { "line": 1, "character": 27 }, + "start": { "line": 2, "character": 14 }, + "end": { "line": 2, "character": 28 }, }, "severity": 2, - "code": "ban-untagged-todo", + "code": "camelcase", "source": "deno-lint", - "message": "TODO should be tagged with (@username) or (#issue)\nAdd a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)", + "message": "Identifier 'snake_case_var' is not in camel case.\nConsider renaming `snake_case_var` to `snakeCaseVar`", }], "version": 1, }) @@ -12237,6 +12236,64 @@ fn lsp_deno_json_scopes_lint_config() { client.shutdown(); } +#[test] +fn lsp_deno_json_workspace_jsr_resolution() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let temp_dir = context.temp_dir(); + temp_dir.write( + "deno.json", + json!({ + "workspaces": ["project1"], + }) + .to_string(), + ); + temp_dir.create_dir_all("project1"); + temp_dir.write( + "project1/deno.json", + json!({ + "name": "@org/project1", + "version": "1.0.0", + "exports": { + ".": "./mod.ts", + }, + }) + .to_string(), + ); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + client.did_open(json!({ + "textDocument": { + "uri": temp_dir.uri().join("file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "import \"jsr:@org/project1@^1.0.0\";\n", + }, + })); + let res = client.write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": temp_dir.uri().join("file.ts").unwrap(), + }, + "position": { "line": 0, "character": 7 }, + }), + ); + assert_eq!( + res, + json!({ + "contents": { + "kind": "markdown", + "value": format!("**Resolved Dependency**\n\n**Code**: jsr​:​@org/project1​@^1.0.0 (<{}project1/mod.ts>)\n", temp_dir.uri()), + }, + "range": { + "start": { "line": 0, "character": 7 }, + "end": { "line": 0, "character": 33 }, + }, + }), + ); + client.shutdown(); +} + #[test] fn lsp_import_unstable_bare_node_builtins_auto_discovered() { let context = TestContextBuilder::new().use_temp_cwd().build(); From e3b2ee183bc7497ec0432bc764678f5eda6495a7 Mon Sep 17 00:00:00 2001 From: snek Date: Mon, 10 Jun 2024 09:20:44 -0700 Subject: [PATCH 014/139] fix: Rewrite Node-API (#24101) Phase 1 node-api rewrite --- cli/napi/README.md | 7 +- cli/napi/async.rs | 102 - cli/napi/env.rs | 174 - .../generated_symbol_exports_list_linux.def | 2 +- .../generated_symbol_exports_list_macos.def | 11 +- .../generated_symbol_exports_list_windows.def | 11 +- cli/napi/js_native_api.rs | 5079 ++++++++++------- cli/napi/mod.rs | 4 +- cli/napi/node_api.rs | 1020 ++++ cli/napi/sym/lib.rs | 13 +- cli/napi/sym/symbol_exports.json | 11 +- cli/napi/threadsafe_functions.rs | 290 - cli/napi/util.rs | 284 + ext/napi/function.rs | 39 +- ext/napi/lib.rs | 284 +- ext/napi/value.rs | 13 + ext/node/polyfills/01_require.js | 7 +- tests/napi/init_test.js | 3 +- tests/napi/object_wrap_test.js | 3 +- tests/napi/src/object_wrap.rs | 9 +- tools/util.js | 16 +- 21 files changed, 4431 insertions(+), 2951 deletions(-) delete mode 100644 cli/napi/async.rs delete mode 100644 cli/napi/env.rs create mode 100644 cli/napi/node_api.rs delete mode 100644 cli/napi/threadsafe_functions.rs diff --git a/cli/napi/README.md b/cli/napi/README.md index ec637e1bd2fba..7b359ac6ecc26 100644 --- a/cli/napi/README.md +++ b/cli/napi/README.md @@ -3,11 +3,8 @@ This directory contains source for Deno's Node-API implementation. It depends on `napi_sym` and `deno_napi`. -- [`async.rs`](./async.rs) - Asynchronous work related functions. -- [`env.rs`](./env.rs) - Environment related functions. -- [`js_native_api.rs`](./js_native_api.rs) - V8/JS related functions. -- [`thread_safe_function.rs`](./threadsafe_functions.rs) - Thread safe function - related functions. +Files are generally organized the same as in Node.js's implementation to ease in +ensuring compatibility. ## Adding a new function diff --git a/cli/napi/async.rs b/cli/napi/async.rs deleted file mode 100644 index 115aa742d789d..0000000000000 --- a/cli/napi/async.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_runtime::deno_napi::*; - -use crate::check_env; -use crate::napi::threadsafe_functions::SendPtr; - -#[repr(C)] -pub struct AsyncWork { - pub data: *mut c_void, - pub execute: napi_async_execute_callback, - pub complete: napi_async_complete_callback, -} - -unsafe impl Send for AsyncWork {} -unsafe impl Sync for AsyncWork {} - -#[napi_sym::napi_sym] -fn napi_create_async_work( - _env: *mut Env, - _async_resource: napi_value, - _async_resource_name: napi_value, - execute: napi_async_execute_callback, - complete: napi_async_complete_callback, - data: *mut c_void, - result: *mut napi_async_work, -) -> napi_status { - let mut work = AsyncWork { - data, - execute, - complete, - }; - let work_box = Box::new(work); - *result = transmute::<*mut AsyncWork, _>(Box::into_raw(work_box)); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_cancel_async_work( - _env: &mut Env, - _async_work: napi_async_work, -) -> napi_status { - napi_ok -} - -/// Frees a previously allocated work object. -#[napi_sym::napi_sym] -fn napi_delete_async_work( - _env: &mut Env, - work: napi_async_work, -) -> napi_status { - let work = Box::from_raw(work as *mut AsyncWork); - drop(work); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_queue_async_work( - env_ptr: *mut Env, - work: napi_async_work, -) -> napi_status { - let work: &AsyncWork = &*(work as *const AsyncWork); - let Some(env) = env_ptr.as_mut() else { - return napi_invalid_arg; - }; - - let send_env = SendPtr(env_ptr); - - #[inline(always)] - fn do_work(ptr: SendPtr, work: &AsyncWork) { - // SAFETY: This is a valid async work queue call and it runs on the event loop thread - unsafe { - (work.execute)(ptr.0 as napi_env, work.data); - (work.complete)(ptr.0 as napi_env, napi_ok, work.data); - } - } - - env.add_async_work(move || do_work(send_env, work)); - - napi_ok -} - -// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops. -#[napi_sym::napi_sym] -fn napi_async_init( - env: *mut Env, - _async_resource: napi_value, - _async_resource_name: napi_value, - result: *mut *mut (), -) -> napi_status { - check_env!(env); - *result = ptr::null_mut(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_async_destroy(env: *mut Env, async_context: *mut ()) -> napi_status { - check_env!(env); - assert!(async_context.is_null()); - napi_ok -} diff --git a/cli/napi/env.rs b/cli/napi/env.rs deleted file mode 100644 index 34948cde3949f..0000000000000 --- a/cli/napi/env.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_runtime::deno_napi::*; -use std::os::raw::c_char; - -/// # Safety -/// -/// It's an N-API symbol -#[no_mangle] -pub unsafe extern "C" fn napi_fatal_error( - location: *const c_char, - location_len: isize, - message: *const c_char, - message_len: isize, -) -> ! { - let location = if location.is_null() { - None - } else { - Some(if location_len < 0 { - std::ffi::CStr::from_ptr(location).to_str().unwrap() - } else { - let slice = std::slice::from_raw_parts( - location as *const u8, - location_len as usize, - ); - std::str::from_utf8(slice).unwrap() - }) - }; - let message = if message_len < 0 { - std::ffi::CStr::from_ptr(message).to_str().unwrap() - } else { - let slice = - std::slice::from_raw_parts(message as *const u8, message_len as usize); - std::str::from_utf8(slice).unwrap() - }; - panic!( - "Fatal exception triggered by napi_fatal_error!\nLocation: {location:?}\n{message}" - ); -} - -// napi-3 - -#[napi_sym::napi_sym] -fn napi_fatal_exception(env: *mut Env, value: napi_value) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - let value = transmute::>(value); - let error = value.to_rust_string_lossy(&mut env.scope()); - panic!("Fatal exception triggered by napi_fatal_exception!\n{error}"); -} - -#[napi_sym::napi_sym] -fn napi_add_env_cleanup_hook( - env: *mut Env, - hook: extern "C" fn(*const c_void), - data: *const c_void, -) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - - { - let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); - if env_cleanup_hooks - .iter() - .any(|pair| pair.0 == hook && pair.1 == data) - { - panic!("Cleanup hook with this data already registered"); - } - env_cleanup_hooks.push((hook, data)); - } - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_remove_env_cleanup_hook( - env: *mut Env, - hook: extern "C" fn(*const c_void), - data: *const c_void, -) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - - { - let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); - // Hooks are supposed to be removed in LIFO order - let maybe_index = env_cleanup_hooks - .iter() - .rposition(|&pair| pair.0 == hook && pair.1 == data); - - if let Some(index) = maybe_index { - env_cleanup_hooks.remove(index); - } else { - panic!("Cleanup hook with this data not found"); - } - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_open_callback_scope( - _env: *mut Env, - _resource_object: napi_value, - _context: napi_value, - _result: *mut napi_callback_scope, -) -> napi_status { - // we open scope automatically when it's needed - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_close_callback_scope( - _env: *mut Env, - _scope: napi_callback_scope, -) -> napi_status { - // we close scope automatically when it's needed - napi_ok -} - -#[napi_sym::napi_sym] -fn node_api_get_module_file_name( - env: *mut Env, - result: *mut *const c_char, -) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - - let shared = env.shared(); - *result = shared.filename; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_module_register(module: *const NapiModule) -> napi_status { - MODULE_TO_REGISTER.with(|cell| { - let mut slot = cell.borrow_mut(); - let prev = slot.replace(module); - assert!(prev.is_none()); - }); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_uv_event_loop( - _env: *mut Env, - uv_loop: *mut *mut (), -) -> napi_status { - // There is no uv_loop in Deno - *uv_loop = std::ptr::null_mut(); - napi_ok -} - -const NODE_VERSION: napi_node_version = napi_node_version { - major: 18, - minor: 13, - patch: 0, - release: "Deno\0".as_ptr() as *const c_char, -}; - -#[napi_sym::napi_sym] -fn napi_get_node_version( - env: *mut Env, - result: *mut *const napi_node_version, -) -> napi_status { - crate::check_env!(env); - crate::check_arg!(env, result); - - *result = &NODE_VERSION as *const napi_node_version; - napi_ok -} diff --git a/cli/napi/generated_symbol_exports_list_linux.def b/cli/napi/generated_symbol_exports_list_linux.def index eceac0a438134..06e94f05bbf49 100644 --- a/cli/napi/generated_symbol_exports_list_linux.def +++ b/cli/napi/generated_symbol_exports_list_linux.def @@ -1 +1 @@ -{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; }; \ No newline at end of file +{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "node_api_create_external_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "node_api_create_external_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "node_api_symbol_for"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; "node_api_create_property_key_utf16"; "napi_type_tag_object"; "napi_check_object_type_tag"; "node_api_post_finalizer"; "napi_add_async_cleanup_hook"; "napi_remove_async_cleanup_hook"; }; \ No newline at end of file diff --git a/cli/napi/generated_symbol_exports_list_macos.def b/cli/napi/generated_symbol_exports_list_macos.def index be69d297fe41b..cac7100c6fdf4 100644 --- a/cli/napi/generated_symbol_exports_list_macos.def +++ b/cli/napi/generated_symbol_exports_list_macos.def @@ -34,6 +34,7 @@ _napi_create_external _napi_get_threadsafe_function_context _napi_get_null _napi_create_string_utf16 +_node_api_create_external_string_utf16 _napi_get_value_bigint_uint64 _napi_module_register _napi_is_typedarray @@ -74,6 +75,7 @@ _napi_adjust_external_memory _napi_release_threadsafe_function _napi_delete_async_work _napi_create_string_latin1 +_node_api_create_external_string_latin1 _napi_is_array _napi_unref_threadsafe_function _napi_throw_error @@ -137,8 +139,15 @@ _napi_create_uint32 _napi_has_element _napi_create_external_arraybuffer _napi_create_symbol +_node_api_symbol_for _napi_coerce_to_string _napi_create_type_error _napi_fatal_exception _napi_create_async_work -_napi_async_init \ No newline at end of file +_napi_async_init +_node_api_create_property_key_utf16 +_napi_type_tag_object +_napi_check_object_type_tag +_node_api_post_finalizer +_napi_add_async_cleanup_hook +_napi_remove_async_cleanup_hook \ No newline at end of file diff --git a/cli/napi/generated_symbol_exports_list_windows.def b/cli/napi/generated_symbol_exports_list_windows.def index 45f5d3aabdecb..5386b46e54f8c 100644 --- a/cli/napi/generated_symbol_exports_list_windows.def +++ b/cli/napi/generated_symbol_exports_list_windows.def @@ -36,6 +36,7 @@ EXPORTS napi_get_threadsafe_function_context napi_get_null napi_create_string_utf16 + node_api_create_external_string_utf16 napi_get_value_bigint_uint64 napi_module_register napi_is_typedarray @@ -76,6 +77,7 @@ EXPORTS napi_release_threadsafe_function napi_delete_async_work napi_create_string_latin1 + node_api_create_external_string_latin1 napi_is_array napi_unref_threadsafe_function napi_throw_error @@ -139,8 +141,15 @@ EXPORTS napi_has_element napi_create_external_arraybuffer napi_create_symbol + node_api_symbol_for napi_coerce_to_string napi_create_type_error napi_fatal_exception napi_create_async_work - napi_async_init \ No newline at end of file + napi_async_init + node_api_create_property_key_utf16 + napi_type_tag_object + napi_check_object_type_tag + node_api_post_finalizer + napi_add_async_cleanup_hook + napi_remove_async_cleanup_hook \ No newline at end of file diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs index 428c4a04a041c..cbce113dc0a29 100644 --- a/cli/napi/js_native_api.rs +++ b/cli/napi/js_native_api.rs @@ -1,2016 +1,3046 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. #![allow(non_upper_case_globals)] +#![deny(unsafe_op_in_unsafe_fn)] + +const NAPI_VERSION: u32 = 9; use deno_runtime::deno_napi::*; use libc::INT_MAX; -use v8::BackingStore; -use v8::UniqueRef; +use super::util::check_new_from_utf8; +use super::util::check_new_from_utf8_len; use super::util::get_array_buffer_ptr; +use super::util::make_external_backing_store; +use super::util::napi_clear_last_error; +use super::util::napi_set_last_error; +use super::util::v8_name_from_property_descriptor; +use crate::check_arg; +use crate::check_env; use deno_runtime::deno_napi::function::create_function; use deno_runtime::deno_napi::function::create_function_template; use deno_runtime::deno_napi::function::CallbackInfo; +use napi_sym::napi_sym; use std::ptr::NonNull; -#[macro_export] -macro_rules! check_env { - ($env: expr) => { - if $env.is_null() { - return napi_invalid_arg; - } - }; +#[derive(Debug, Clone, Copy, PartialEq)] +enum ReferenceOwnership { + Runtime, + Userland, } -#[inline] -unsafe fn napi_value_unchecked(val: napi_value) -> v8::Local { - transmute::>(val) +enum ReferenceState { + Strong(v8::Global), + Weak(v8::Weak), } -#[macro_export] -macro_rules! return_error_status_if_false { - ($env: expr, $condition: expr, $status: ident) => { - if !$condition { - return Err( - $crate::napi::js_native_api::napi_set_last_error( - $env, - $status, - 0, - std::ptr::null_mut(), - ) - .into(), - ); - } - }; +struct Reference { + env: *mut Env, + state: ReferenceState, + ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, } -#[macro_export] -macro_rules! return_status_if_false { - ($env: expr, $condition: expr, $status: ident) => { - if !$condition { - return $crate::napi::js_native_api::napi_set_last_error( - $env, - $status, - 0, - std::ptr::null_mut(), - ); +impl Reference { + fn new( + env: *mut Env, + value: v8::Local, + initial_ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, + ) -> Box { + let isolate = unsafe { &mut *(*env).isolate_ptr }; + + let mut reference = Box::new(Reference { + env, + state: ReferenceState::Strong(v8::Global::new(isolate, value)), + ref_count: initial_ref_count, + ownership, + finalize_cb, + finalize_data, + finalize_hint, + }); + + if initial_ref_count == 0 { + reference.set_weak(); } - }; -} -fn check_new_from_utf8_len<'s>( - env: *mut Env, - str_: *const c_char, - len: usize, -) -> Result, napi_status> { - return_error_status_if_false!( - env, - (len == NAPI_AUTO_LENGTH) || len <= INT_MAX as _, - napi_invalid_arg - ); - return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg); - let string = if len == NAPI_AUTO_LENGTH { - let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str(); - return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); - result.unwrap() - } else { - let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) }; - let result = std::str::from_utf8(string); - return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); - result.unwrap() - }; - let result = { - let env = unsafe { &mut *env }; - v8::String::new(&mut env.scope(), string) - }; - return_error_status_if_false!(env, result.is_some(), napi_generic_failure); - Ok(result.unwrap()) -} + reference + } -#[inline] -fn check_new_from_utf8<'s>( - env: *mut Env, - str_: *const c_char, -) -> Result, napi_status> { - check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH) -} + fn ref_(&mut self) -> u32 { + self.ref_count += 1; + if self.ref_count == 1 { + self.set_strong(); + } + self.ref_count + } -#[macro_export] -macro_rules! status_call { - ($call: expr) => { - let status = $call; - if status != napi_ok { - return status; + fn unref(&mut self) -> u32 { + let old_ref_count = self.ref_count; + if self.ref_count > 0 { + self.ref_count -= 1; } - }; -} + if old_ref_count == 1 && self.ref_count == 0 { + self.set_weak(); + } + self.ref_count + } -// Macro to check napi arguments. -// If nullptr, return napi_invalid_arg. -#[macro_export] -macro_rules! check_arg { - ($env: expr, $ptr: expr) => { - $crate::return_status_if_false!($env, !$ptr.is_null(), napi_invalid_arg); - }; -} + fn reset(&mut self) { + self.finalize_cb = None; + self.finalize_data = std::ptr::null_mut(); + self.finalize_hint = std::ptr::null_mut(); + } -macro_rules! check_arg_option { - ($env: expr, $opt: expr) => { - $crate::return_status_if_false!($env, $opt.is_some(), napi_invalid_arg); - }; -} + fn set_strong(&mut self) { + if let ReferenceState::Weak(w) = &self.state { + let isolate = unsafe { &mut *(*self.env).isolate_ptr }; + if let Some(g) = w.to_global(isolate) { + self.state = ReferenceState::Strong(g); + } + } + } -fn napi_clear_last_error(env: *mut Env) { - let env = unsafe { &mut *env }; - env.last_error.error_code = napi_ok; - env.last_error.engine_error_code = 0; - env.last_error.engine_reserved = std::ptr::null_mut(); - env.last_error.error_message = std::ptr::null_mut(); -} + fn set_weak(&mut self) { + let reference = self as *mut Reference; + if let ReferenceState::Strong(g) = &self.state { + let cb = Box::new(move |_: &mut v8::Isolate| { + Reference::weak_callback(reference) + }); + let isolate = unsafe { &mut *(*self.env).isolate_ptr }; + self.state = + ReferenceState::Weak(v8::Weak::with_finalizer(isolate, g, cb)); + } + } -pub(crate) fn napi_set_last_error( - env: *mut Env, - error_code: napi_status, - engine_error_code: i32, - engine_reserved: *mut c_void, -) -> napi_status { - let env = unsafe { &mut *env }; - env.last_error.error_code = error_code; - env.last_error.engine_error_code = engine_error_code; - env.last_error.engine_reserved = engine_reserved; - error_code -} + fn weak_callback(reference: *mut Reference) { + let reference = unsafe { &mut *reference }; -/// Returns napi_value that represents a new JavaScript Array. -#[napi_sym::napi_sym] -fn napi_create_array(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Array::new(&mut env.scope(), 0).into(); - napi_ok -} + let finalize_cb = reference.finalize_cb; + let finalize_data = reference.finalize_data; + let finalize_hint = reference.finalize_hint; + reference.reset(); -#[napi_sym::napi_sym] -fn napi_create_array_with_length( - env: *mut Env, - len: i32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Array::new(&mut env.scope(), len).into(); - napi_ok -} + if let Some(finalize_cb) = finalize_cb { + unsafe { + finalize_cb(reference.env as _, finalize_data, finalize_hint); + } + } -#[napi_sym::napi_sym] -fn napi_create_arraybuffer( - env: *mut Env, - len: usize, - data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; + if reference.ownership == ReferenceOwnership::Runtime { + unsafe { drop(Reference::from_raw(reference)) } + } + } - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); + fn into_raw(r: Box) -> *mut Reference { + Box::into_raw(r) } - *result = value.into(); - napi_ok + unsafe fn from_raw(r: *mut Reference) -> Box { + unsafe { Box::from_raw(r) } + } + + unsafe fn remove(r: *mut Reference) { + let r = unsafe { &mut *r }; + if r.ownership == ReferenceOwnership::Userland { + r.reset(); + } else { + unsafe { drop(Reference::from_raw(r)) } + } + } } -#[napi_sym::napi_sym] -fn napi_create_bigint_int64( +#[napi_sym] +fn napi_get_last_error_info( env: *mut Env, - value: i64, - result: *mut napi_value, + result: *mut *const napi_extended_error_info, ) -> napi_status { - check_env!(env); + let env = check_env!(env); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into(); + + if env.last_error.error_code == napi_ok { + napi_clear_last_error(env); + } else { + env.last_error.error_message = + ERROR_MESSAGES[env.last_error.error_code as usize].as_ptr(); + } + + unsafe { + *result = &env.last_error; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_bigint_uint64( - env: *mut Env, - value: u64, +#[napi_sym] +fn napi_create_function( + env: &mut Env, + name: *const c_char, + length: usize, + cb: napi_callback, + cb_info: napi_callback_info, result: *mut napi_value, ) -> napi_status { - check_env!(env); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + check_arg!(env, cb); + + let name = if !name.is_null() { + match unsafe { check_new_from_utf8_len(env, name, length) } { + Ok(s) => Some(s), + Err(status) => return status, + } + } else { + None + }; + + unsafe { + *result = create_function(env, name, cb, cb_info).into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_bigint_words( - env: *mut Env, - sign_bit: bool, - word_count: usize, - words: *const u64, - result: *mut napi_value, +#[napi_sym] +#[allow(clippy::too_many_arguments)] +fn napi_define_class<'s>( + env: &'s mut Env, + utf8name: *const c_char, + length: usize, + constructor: napi_callback, + callback_data: *mut c_void, + property_count: usize, + properties: *const napi_property_descriptor, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - check_arg!(env, words); - let env = unsafe { &mut *env }; check_arg!(env, result); + check_arg!(env, constructor); - if word_count > INT_MAX as _ { - return napi_invalid_arg; + if property_count > 0 { + check_arg!(env, properties); } - match v8::BigInt::new_from_words( - &mut env.scope(), - sign_bit, - std::slice::from_raw_parts(words, word_count), - ) { - Some(value) => { - *result = value.into(); + let name = match unsafe { check_new_from_utf8_len(env, utf8name, length) } { + Ok(string) => string, + Err(status) => return status, + }; + + let tpl = unsafe { + create_function_template(env, Some(name), constructor, callback_data) + }; + + let napi_properties: &[napi_property_descriptor] = if property_count > 0 { + unsafe { std::slice::from_raw_parts(properties, property_count) } + } else { + &[] + }; + let mut static_property_count = 0; + + for p in napi_properties { + if p.attributes & napi_static != 0 { + // Will be handled below + static_property_count += 1; + continue; } - None => { - return napi_invalid_arg; + + let name = match unsafe { v8_name_from_property_descriptor(env, p) } { + Ok(name) => name, + Err(status) => return status, + }; + + let method = p.method; + let getter = p.getter; + let setter = p.setter; + + if getter.is_some() || setter.is_some() { + let getter: Option> = if getter.is_some() + { + Some(unsafe { create_function_template(env, None, p.getter, p.data) }) + } else { + None + }; + let setter: Option> = if setter.is_some() + { + Some(unsafe { create_function_template(env, None, p.setter, p.data) }) + } else { + None + }; + + let mut accessor_property = v8::PropertyAttribute::NONE; + if getter.is_some() + && setter.is_some() + && (p.attributes & napi_writable) == 0 + { + accessor_property = + accessor_property | v8::PropertyAttribute::READ_ONLY; + } + if p.attributes & napi_enumerable == 0 { + accessor_property = + accessor_property | v8::PropertyAttribute::DONT_ENUM; + } + if p.attributes & napi_configurable == 0 { + accessor_property = + accessor_property | v8::PropertyAttribute::DONT_DELETE; + } + + let proto = tpl.prototype_template(&mut env.scope()); + proto.set_accessor_property(name, getter, setter, accessor_property); + } else if method.is_some() { + let function = + unsafe { create_function_template(env, None, p.method, p.data) }; + let proto = tpl.prototype_template(&mut env.scope()); + proto.set(name, function.into()); + } else { + let proto = tpl.prototype_template(&mut env.scope()); + proto.set(name, p.value.unwrap().into()); } } - napi_ok -} + let env_ptr = env as *mut Env; + let value: v8::Local = + tpl.get_function(&mut env.scope()).unwrap().into(); -#[napi_sym::napi_sym] -fn napi_create_buffer( - env: *mut Env, - len: usize, - data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); + unsafe { + *result = value.into(); + } + + if static_property_count > 0 { + let mut static_descriptors = Vec::with_capacity(static_property_count); + + for p in napi_properties { + if p.attributes & napi_static != 0 { + static_descriptors.push(*p); + } + } + + crate::status_call!(unsafe { + napi_define_properties( + env_ptr, + *result, + static_descriptors.len(), + static_descriptors.as_ptr(), + ) + }); } - let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_buffer_copy( - env: *mut Env, - len: usize, - data: *mut u8, - result_data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - let ptr = get_array_buffer_ptr(value); - std::ptr::copy(data, ptr, len); - if !result_data.is_null() { - *result_data = ptr; - } - let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_bool( +#[napi_sym] +fn napi_get_property_names( env: *mut Env, - value: napi_value, + object: napi_value, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_boolean(&mut env.scope()); - let value: v8::Local = coerced.into(); - *result = value.into(); - napi_ok + unsafe { + napi_get_all_property_names( + env, + object, + napi_key_include_prototypes, + napi_key_enumerable | napi_key_skip_symbols, + napi_key_numbers_to_strings, + result, + ) + } } -#[napi_sym::napi_sym] -fn napi_coerce_to_number( - env: *mut Env, - value: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_get_all_property_names<'s>( + env: &'s mut Env, + object: napi_value, + key_mode: napi_key_collection_mode, + key_filter: napi_key_filter, + key_conversion: napi_key_conversion, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Some(coerced) = value.to_number(&mut env.scope()) else { - return napi_number_expected; + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(obj) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let mut filter = v8::PropertyFilter::ALL_PROPERTIES; + + if key_filter & napi_key_writable != 0 { + filter = filter | v8::PropertyFilter::ONLY_WRITABLE; + } + if key_filter & napi_key_enumerable != 0 { + filter = filter | v8::PropertyFilter::ONLY_ENUMERABLE; + } + if key_filter & napi_key_configurable != 0 { + filter = filter | v8::PropertyFilter::ONLY_CONFIGURABLE; + } + if key_filter & napi_key_skip_strings != 0 { + filter = filter | v8::PropertyFilter::SKIP_STRINGS; + } + if key_filter & napi_key_skip_symbols != 0 { + filter = filter | v8::PropertyFilter::SKIP_SYMBOLS; + } + + let key_mode = match key_mode { + napi_key_include_prototypes => v8::KeyCollectionMode::IncludePrototypes, + napi_key_own_only => v8::KeyCollectionMode::OwnOnly, + _ => return napi_invalid_arg, + }; + + let key_conversion = match key_conversion { + napi_key_keep_numbers => v8::KeyConversionMode::KeepNumbers, + napi_key_numbers_to_strings => v8::KeyConversionMode::ConvertToString, + _ => return napi_invalid_arg, + }; + + let filter = v8::GetPropertyNamesArgsBuilder::new() + .mode(key_mode) + .property_filter(filter) + .index_filter(v8::IndexFilter::IncludeIndices) + .key_conversion(key_conversion) + .build(); + + let property_names = match obj.get_property_names(scope, filter) { + Some(n) => n, + None => return napi_generic_failure, }; - let value: v8::Local = coerced.into(); - *result = value.into(); + + unsafe { + *result = property_names.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_object( - env: *mut Env, +#[napi_sym] +fn napi_set_property( + env: &mut Env, + object: napi_value, + key: napi_value, value: napi_value, - result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_object(&mut env.scope()).unwrap(); - let value: v8::Local = coerced.into(); - *result = value.into(); + check_arg!(env, key); + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if object.set(scope, key.unwrap(), value.unwrap()).is_none() { + return napi_generic_failure; + }; + napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_string( - env: *mut Env, - value: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_string(&mut env.scope()).unwrap(); - let value: v8::Local = coerced.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_dataview( - env: *mut Env, - len: usize, - data: *mut *mut u8, - byte_offset: usize, - result: *mut napi_value, +#[napi_sym] +fn napi_has_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - check_arg!(env, data); - let env = unsafe { &mut *env }; + check_arg!(env, key); check_arg!(env, result); - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); - } - let context = &mut env.scope().get_current_context(); - let global = context.global(&mut env.scope()); - let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); - let data_view = global.get(&mut env.scope(), data_view_name.into()).unwrap(); - let Ok(data_view) = v8::Local::::try_from(data_view) else { - return napi_function_expected; + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); - let byte_length = v8::Number::new(&mut env.scope(), len as f64); - let value = data_view - .new_instance( - &mut env.scope(), - &[value.into(), byte_offset.into(), byte_length.into()], - ) - .unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_date( - env: *mut Env, - time: f64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value: v8::Local = - v8::Date::new(&mut env.scope(), time).unwrap().into(); - *result = value.into(); + let Some(has) = object.has(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + unsafe { + *result = has; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_double( - env: *mut Env, - value: f64, - result: *mut napi_value, +#[napi_sym] +fn napi_get_property<'s>( + env: &'s mut Env, + object: napi_value, + key: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); + check_arg!(env, key); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Number::new(&mut env.scope(), value).into(); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(value) = object.get(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + napi_ok } -fn set_error_code( - env: *mut Env, - error: v8::Local, - code: napi_value, - code_cstring: *const c_char, +#[napi_sym] +fn napi_delete_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, ) -> napi_status { - if code.is_some() || !code_cstring.is_null() { - let err_object: v8::Local = error.try_into().unwrap(); + check_arg!(env, key); - let code_value: v8::Local = if code.is_some() { - let mut code_value = unsafe { napi_value_unchecked(code) }; - return_status_if_false!( - env, - code_value.is_string(), - napi_string_expected - ); - code_value - } else { - let name = match check_new_from_utf8(env, code_cstring) { - Ok(s) => s, - Err(status) => return status, - }; - name.into() - }; + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; - let mut scope = unsafe { &mut *env }.scope(); - let code_key = v8::String::new(&mut scope, "code").unwrap(); + let Some(deleted) = object.delete(scope, key.unwrap()) else { + return napi_generic_failure; + }; - if err_object - .set(&mut scope, code_key.into(), code_value) - .is_none() - { - return napi_generic_failure; + if !result.is_null() { + unsafe { + *result = deleted; } } napi_ok } -#[napi_sym::napi_sym] -fn napi_create_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_has_own_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); + check_arg!(env, key); check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_type_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); - check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::type_error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Ok(key) = v8::Local::::try_from(key.unwrap()) else { + return napi_name_expected; + }; + + let Some(has_own) = object.has_own_property(scope, key) else { + return napi_generic_failure; + }; + + unsafe { + *result = has_own; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_range_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_has_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + result: *mut bool, ) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); + let env_ptr = env as *mut Env; check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::range_error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_external( - env_ptr: *mut Env, - value: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - let external: v8::Local = - v8::External::new(&mut env.scope(), value).into(); + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; - let value = weak_local(env_ptr, external, value, finalize_cb, finalize_hint); + let Some(has_property) = object.has(&mut env.scope(), key.into()) else { + return napi_generic_failure; + }; + + unsafe { + *result = has_property; + } - *result = transmute(value); napi_ok } -pub type BackingStoreDeleterCallback = unsafe extern "C" fn( - data: *mut c_void, - byte_length: usize, - deleter_data: *mut c_void, -); +#[napi_sym] +fn napi_set_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + value: napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + let env_ptr = env as *mut Env; -extern "C" { - fn v8__ArrayBuffer__NewBackingStore__with_data( - data: *mut c_void, - byte_length: usize, - deleter: BackingStoreDeleterCallback, - deleter_data: *mut c_void, - ) -> *mut BackingStore; -} + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; -struct BufferFinalizer { - env: *mut Env, - finalize_cb: napi_finalize, - finalize_data: *mut c_void, - finalize_hint: *mut c_void, -} + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; -impl BufferFinalizer { - fn into_raw(self) -> *mut BufferFinalizer { - Box::into_raw(Box::new(self)) - } -} + let value = value.unwrap(); -impl Drop for BufferFinalizer { - fn drop(&mut self) { - unsafe { - (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint); - } + if !object + .set(&mut env.scope(), key.into(), value) + .unwrap_or(false) + { + return napi_generic_failure; } -} - -pub extern "C" fn backing_store_deleter_callback( - data: *mut c_void, - _byte_length: usize, - deleter_data: *mut c_void, -) { - let mut finalizer = - unsafe { Box::from_raw(deleter_data as *mut BufferFinalizer) }; - finalizer.finalize_data = data; + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_external_arraybuffer( - env_ptr: *mut Env, - data: *mut c_void, - byte_length: usize, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, +#[napi_sym] +fn napi_get_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; + check_arg!(env, result); + let env_ptr = env as *mut Env; - let finalizer = BufferFinalizer { - env: env_ptr, - finalize_data: ptr::null_mut(), - finalize_cb, - finalize_hint, + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; }; - let store: UniqueRef = - transmute(v8__ArrayBuffer__NewBackingStore__with_data( - data, - byte_length, - backing_store_deleter_callback, - finalizer.into_raw() as _, - )); + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; - let ab = - v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); - let value: v8::Local = ab.into(); + let Some(value) = object.get(&mut env.scope(), key.into()) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } - *result = value.into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_create_external_buffer( - env_ptr: *mut Env, - byte_length: usize, - data: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, +#[napi_sym] +fn napi_set_element<'s>( + env: &'s mut Env, + object: napi_value<'s>, + index: u32, + value: napi_value<'s>, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - let finalizer = BufferFinalizer { - env: env_ptr, - finalize_data: ptr::null_mut(), - finalize_cb, - finalize_hint, + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - let store: UniqueRef = - transmute(v8__ArrayBuffer__NewBackingStore__with_data( - data, - byte_length, - backing_store_deleter_callback, - finalizer.into_raw() as _, - )); + if !object + .set_index(scope, index, value.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } - let ab = - v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); - let value = - v8::Uint8Array::new(&mut env.scope(), ab, 0, byte_length).unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_create_function( - env: *mut Env, - name: *const c_char, - length: usize, - cb: napi_callback, - cb_info: napi_callback_info, - result: *mut napi_value, +#[napi_sym] +fn napi_has_element( + env: &mut Env, + object: napi_value, + index: u32, + result: *mut bool, ) -> napi_status { - check_env!(env); check_arg!(env, result); - check_arg_option!(env, cb); - let name = if let Some(name) = name.as_ref() { - match check_new_from_utf8_len(env, name, length) { - Ok(s) => Some(s), - Err(status) => return status, - } - } else { - None + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - *result = create_function(env, name, cb, cb_info).into(); - napi_ok -} + let Some(has) = object.has_index(scope, index) else { + return napi_generic_failure; + }; -#[napi_sym::napi_sym] -fn napi_create_int32( - env: *mut Env, - value: i32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Integer::new(&mut env.scope(), value).into(); - napi_ok -} + unsafe { + *result = has; + } -#[napi_sym::napi_sym] -fn napi_create_uint32( - env: *mut Env, - value: u32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_create_int64( - env: *mut Env, - value: i64, - result: *mut napi_value, +#[napi_sym] +fn napi_get_element<'s>( + env: &'s mut Env, + object: napi_value, + index: u32, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Number::new(&mut env.scope(), value as f64).into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_object(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = v8::Object::new(&mut env.scope()); - *result = object.into(); - napi_ok -} + let scope = &mut env.scope(); -#[napi_sym::napi_sym] -fn napi_create_promise( - env: *mut Env, - deferred: *mut napi_deferred, - promise_out: *mut napi_value, + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(value) = object.get_index(scope, index) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_delete_element( + env: &mut Env, + object: napi_value, + index: u32, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); - let mut global = v8::Global::new(&mut env.scope(), resolver); - let mut global_ptr = global.into_raw(); - let promise = resolver.get_promise(&mut env.scope()); - *deferred = global_ptr.as_mut() as *mut _ as napi_deferred; - *promise_out = promise.into(); + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(deleted) = object.delete_index(scope, index) else { + return napi_generic_failure; + }; + + if !result.is_null() { + unsafe { + *result = deleted; + } + } napi_ok } -#[napi_sym::napi_sym] -fn napi_create_reference( +#[napi_sym] +fn napi_define_properties( + env: &mut Env, + object: napi_value, + property_count: usize, + properties: *const napi_property_descriptor, +) -> napi_status { + let env_ptr = env as *mut Env; + + if property_count > 0 { + check_arg!(env, properties); + } + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let properties = if property_count == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(properties, property_count) } + }; + for property in properties { + let property_name = + match unsafe { v8_name_from_property_descriptor(env_ptr, property) } { + Ok(name) => name, + Err(status) => return status, + }; + + let writable = property.attributes & napi_writable != 0; + let enumerable = property.attributes & napi_enumerable != 0; + let configurable = property.attributes & napi_configurable != 0; + + if property.getter.is_some() || property.setter.is_some() { + let local_getter: v8::Local = if property.getter.is_some() { + unsafe { + create_function(env_ptr, None, property.getter, property.data).into() + } + } else { + v8::undefined(scope).into() + }; + let local_setter: v8::Local = if property.setter.is_some() { + unsafe { + create_function(env_ptr, None, property.setter, property.data).into() + } + } else { + v8::undefined(scope).into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else if property.method.is_some() { + let method: v8::Local = { + let function = unsafe { + create_function(env_ptr, None, property.method, property.data) + }; + function.into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(method, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_generic_failure; + } + } else { + let value = property.value.unwrap(); + + if enumerable & writable & configurable { + if !object + .create_data_property(scope, property_name, value) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else { + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(value, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } + } + } + + napi_ok +} + +#[napi_sym] +fn napi_object_freeze(env: &mut Env, object: napi_value) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_integrity_level(scope, v8::IntegrityLevel::Frozen) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_object_seal(env: &mut Env, object: napi_value) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_integrity_level(scope, v8::IntegrityLevel::Sealed) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_is_array( env: *mut Env, value: napi_value, - _initial_refcount: u32, - result: *mut napi_ref, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let value = value.unwrap(); + + unsafe { + *result = value.is_array(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_array_length( + env: &mut Env, + value: napi_value, + result: *mut u32, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let value = value.unwrap(); + + match v8::Local::::try_from(value) { + Ok(array) => { + unsafe { + *result = array.length(); + } + napi_ok + } + Err(_) => napi_array_expected, + } +} + +#[napi_sym] +fn napi_strict_equals( + env: &mut Env, + lhs: napi_value, + rhs: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, lhs); + check_arg!(env, rhs); + check_arg!(env, result); + + unsafe { + *result = lhs.unwrap().strict_equals(rhs.unwrap()); + } + + napi_ok +} + +#[napi_sym] +fn napi_get_prototype<'s>( + env: &'s mut Env, + object: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(proto) = object.get_prototype(scope) else { + return napi_generic_failure; + }; + + unsafe { + *result = proto.into(); + } - let value = napi_value_unchecked(value); - let global = v8::Global::new(&mut env.scope(), value); - let mut global_ptr = global.into_raw(); - *result = transmute::, napi_ref>(global_ptr); napi_ok } -#[napi_sym::napi_sym] +#[napi_sym] +fn napi_create_object( + env_ptr: *mut Env, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Object::new(&mut env.scope()).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_array( + env_ptr: *mut Env, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Array::new(&mut env.scope(), 0).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_array_with_length( + env_ptr: *mut Env, + length: usize, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Array::new(&mut env.scope(), length as _).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] fn napi_create_string_latin1( - env: *mut Env, - string: *const u8, + env_ptr: *mut Env, + string: *const c_char, length: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); if length > 0 { check_arg!(env, string); } - check_arg!(env, result); - return_status_if_false!( + crate::return_status_if_false!( env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, napi_invalid_arg ); - let string = if length == NAPI_AUTO_LENGTH { - std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap() - .as_bytes() + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string as _, + if length == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(string).to_bytes().len() + } else { + length + }, + ) + } } else { - std::slice::from_raw_parts(string, length) + &[] }; - let Some(v8str) = v8::String::new_from_one_byte( + + let Some(string) = v8::String::new_from_one_byte( &mut env.scope(), - string, + buffer, v8::NewStringType::Normal, ) else { - return napi_generic_failure; + return napi_set_last_error(env_ptr, napi_generic_failure); }; - *result = v8str.into(); - napi_ok + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_string_utf16( - env: *mut Env, - string: *const u16, +#[napi_sym] +fn napi_create_string_utf8( + env_ptr: *mut Env, + string: *const c_char, length: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); if length > 0 { check_arg!(env, string); } - check_arg!(env, result); - return_status_if_false!( + crate::return_status_if_false!( env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, napi_invalid_arg ); - let string = if length == NAPI_AUTO_LENGTH { - let s = std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap(); - std::slice::from_raw_parts(s.as_ptr() as *const u16, s.len()) + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string as _, + if length == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(string).to_bytes().len() + } else { + length + }, + ) + } } else { - std::slice::from_raw_parts(string, length) + &[] }; - match v8::String::new_from_two_byte( + let Some(string) = v8::String::new_from_utf8( &mut env.scope(), - string, + buffer, v8::NewStringType::Normal, - ) { - Some(v8str) => { - *result = v8str.into(); - } - None => return napi_generic_failure, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); } - napi_ok + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_string_utf8( - env: *mut Env, - string: *const u8, +#[napi_sym] +fn napi_create_string_utf16( + env_ptr: *mut Env, + string: *const u16, length: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); if length > 0 { check_arg!(env, string); } - check_arg!(env, result); - return_status_if_false!( + crate::return_status_if_false!( env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, napi_invalid_arg ); - let string = if length == NAPI_AUTO_LENGTH { - std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap() + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string, + if length == NAPI_AUTO_LENGTH { + let mut length = 0; + while *(string.add(length)) != 0 { + length += 1; + } + length + } else { + length + }, + ) + } } else { - let string = std::slice::from_raw_parts(string, length); - std::str::from_utf8(string).unwrap() + &[] }; - let v8str = v8::String::new(&mut env.scope(), string).unwrap(); - *result = v8str.into(); - napi_ok + let Some(string) = v8::String::new_from_two_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Normal, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_symbol( - env: *mut Env, - description: napi_value, - result: *mut napi_value, +#[napi_sym] +fn node_api_create_external_string_latin1( + env_ptr: *mut Env, + _string: *const c_char, + _length: usize, + _nogc_finalize_callback: napi_finalize, + _finalize_hint: *mut c_void, + _result: *mut napi_value, + _copied: *mut bool, ) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; + return napi_set_last_error(env_ptr, napi_generic_failure); +} - let scope = &mut env.scope(); - let description = if let Some(d) = *description { - let Some(d) = d.to_string(scope) else { - return napi_string_expected; - }; - Some(d) - } else { - None - }; - *result = v8::Symbol::new(scope, description).into(); - napi_ok +#[napi_sym] +fn node_api_create_external_string_utf16( + env_ptr: *mut Env, + _string: *const u16, + _length: usize, + _nogc_finalize_callback: napi_finalize, + _finalize_hint: *mut c_void, + _result: *mut napi_value, + _copied: *mut bool, +) -> napi_status { + return napi_set_last_error(env_ptr, napi_generic_failure); } -#[napi_sym::napi_sym] -fn napi_create_typedarray( - env: *mut Env, - ty: napi_typedarray_type, +#[napi_sym] +fn node_api_create_property_key_utf16( + env_ptr: *mut Env, + string: *const u16, length: usize, - arraybuffer: napi_value, - byte_offset: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let ab = napi_value_unchecked(arraybuffer); - let Ok(ab) = v8::Local::::try_from(ab) else { - return napi_arraybuffer_expected; - }; - let typedarray: v8::Local = match ty { - napi_uint8_array => { - v8::Uint8Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint8_clamped_array => { - v8::Uint8ClampedArray::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int8_array => { - v8::Int8Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint16_array => { - v8::Uint16Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int16_array => { - v8::Int16Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint32_array => { - v8::Uint32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int32_array => { - v8::Int32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_float32_array => { - v8::Float32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_float64_array => { - v8::Float64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_bigint64_array => { - v8::BigInt64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_biguint64_array => { - v8::BigUint64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() + let env = check_env!(env_ptr); + if length > 0 { + check_arg!(env, string); + } + crate::return_status_if_false!( + env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, + napi_invalid_arg + ); + + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string, + if length == NAPI_AUTO_LENGTH { + let mut length = 0; + while *(string.add(length)) != 0 { + length += 1; + } + length + } else { + length + }, + ) } - _ => { - return napi_invalid_arg; + } else { + &[] + }; + + let Some(string) = v8::String::new_from_two_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Internalized, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_double( + env_ptr: *mut Env, + value: f64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Number::new(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_int32( + env_ptr: *mut Env, + value: i32, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Integer::new(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_uint32( + env_ptr: *mut Env, + value: u32, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_int64( + env_ptr: *mut Env, + value: i64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Number::new(&mut env.scope(), value as _).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_int64( + env_ptr: *mut Env, + value: i64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_uint64( + env_ptr: *mut Env, + value: u64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_words<'s>( + env: &'s mut Env, + sign_bit: bool, + word_count: usize, + words: *const u64, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, words); + check_arg!(env, result); + + if word_count > INT_MAX as _ { + return napi_invalid_arg; + } + + match v8::BigInt::new_from_words(&mut env.scope(), sign_bit, unsafe { + std::slice::from_raw_parts(words, word_count) + }) { + Some(value) => unsafe { + *result = value.into(); + }, + None => { + return napi_generic_failure; + } + } + + napi_ok +} + +#[napi_sym] +fn napi_get_boolean( + env: *mut Env, + value: bool, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::Boolean::new(env.isolate(), value).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_create_symbol( + env_ptr: *mut Env, + description: napi_value, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + let description = if let Some(d) = *description { + let Some(d) = d.to_string(&mut env.scope()) else { + return napi_set_last_error(env, napi_string_expected); + }; + Some(d) + } else { + None + }; + + unsafe { + *result = v8::Symbol::new(&mut env.scope(), description).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn node_api_symbol_for( + env: *mut Env, + utf8description: *const c_char, + length: usize, + result: *mut napi_value, +) -> napi_status { + { + let env = check_env!(env); + check_arg!(env, result); + + let description_string = + match unsafe { check_new_from_utf8_len(env, utf8description, length) } { + Ok(s) => s, + Err(status) => return napi_set_last_error(env, status), + }; + + unsafe { + *result = + v8::Symbol::for_key(&mut env.scope(), description_string).into(); + } + } + + napi_clear_last_error(env) +} + +macro_rules! napi_create_error_impl { + ($env_ptr:ident, $code:ident, $msg:ident, $result:ident, $error:ident) => {{ + let env_ptr = $env_ptr; + let code = $code; + let msg = $msg; + let result = $result; + + let env = check_env!(env_ptr); + check_arg!(env, msg); + check_arg!(env, result); + + let Some(message) = + msg.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; + + let error = v8::Exception::$error(&mut env.scope(), message); + + if let Some(code) = *code { + let error_obj: v8::Local = error.try_into().unwrap(); + let code_key = v8::String::new(&mut env.scope(), "code").unwrap(); + if !error_obj + .set(&mut env.scope(), code_key.into(), code) + .unwrap_or(false) + { + return napi_set_last_error(env_ptr, napi_generic_failure); + } } + + unsafe { + *result = error.into(); + } + + return napi_clear_last_error(env_ptr); + }}; +} + +#[napi_sym] +fn napi_create_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, error) +} + +#[napi_sym] +fn napi_create_type_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, type_error) +} + +#[napi_sym] +fn napi_create_range_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, range_error) +} + +#[napi_sym] +fn node_api_create_syntax_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, syntax_error) +} + +pub fn get_value_type(value: v8::Local) -> Option { + if value.is_undefined() { + Some(napi_undefined) + } else if value.is_null() { + Some(napi_null) + } else if value.is_external() { + Some(napi_external) + } else if value.is_boolean() { + Some(napi_boolean) + } else if value.is_number() { + Some(napi_number) + } else if value.is_big_int() { + Some(napi_bigint) + } else if value.is_string() { + Some(napi_string) + } else if value.is_symbol() { + Some(napi_symbol) + } else if value.is_function() { + Some(napi_function) + } else if value.is_object() { + Some(napi_object) + } else { + None + } +} + +#[napi_sym] +fn napi_typeof( + env: *mut Env, + value: napi_value, + result: *mut napi_valuetype, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let Some(ty) = get_value_type(value.unwrap()) else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + unsafe { + *result = ty; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::undefined(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::null(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_cb_info( + env: *mut Env, + cbinfo: napi_callback_info, + argc: *mut i32, + argv: *mut napi_value, + this_arg: *mut napi_value, + data: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + if !argv.is_null() { + check_arg!(env, argc); + let argc = unsafe { *argc as usize }; + for i in 0..argc { + let mut arg = args.get(i as _); + unsafe { + *argv.add(i) = arg.into(); + } + } + } + + if !argc.is_null() { + unsafe { + *argc = args.length(); + } + } + + if !this_arg.is_null() { + unsafe { + *this_arg = args.this().into(); + } + } + + if !data.is_null() { + unsafe { + *data = cbinfo.cb_info; + } + } + + napi_clear_last_error(env); + napi_ok +} + +#[napi_sym] +fn napi_get_new_target( + env: *mut Env, + cbinfo: napi_callback_info, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + check_arg!(env, result); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + unsafe { + *result = args.new_target().into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_call_function( + env_ptr: *mut Env, + recv: napi_value, + func: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, recv); + let args = if argc > 0 { + check_arg!(env, argv); + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local, argc) + } + } else { + &[] + }; + + let Some(func) = + func.and_then(|f| v8::Local::::try_from(f).ok()) + else { + return napi_set_last_error(env, napi_function_expected); + }; + + let Some(v) = func.call(&mut env.scope(), recv.unwrap(), args) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + if !result.is_null() { + unsafe { + *result = v.into(); + } + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = std::mem::transmute::, v8::Local>( + env.global, + ) + .into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_throw(env: *mut Env, error: napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, error); + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + let error = error.unwrap(); + env.scope().throw_exception(error); + let error = v8::Global::new(&mut env.scope(), error); + env.last_exception = Some(error); + + napi_clear_last_error(env) +} + +macro_rules! napi_throw_error_impl { + ($env:ident, $code:ident, $msg:ident, $error:ident) => {{ + let env = check_env!($env); + let env_ptr = env as *mut Env; + let code = $code; + let msg = $msg; + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + let str_ = match unsafe { check_new_from_utf8(env, msg) } { + Ok(s) => s, + Err(status) => return status, + }; + + let error = v8::Exception::$error(&mut env.scope(), str_); + + if !code.is_null() { + let error_obj: v8::Local = error.try_into().unwrap(); + let code = match unsafe { check_new_from_utf8(env_ptr, code) } { + Ok(s) => s, + Err(status) => return napi_set_last_error(env, status), + }; + let code_key = v8::String::new(&mut env.scope(), "code").unwrap(); + if !error_obj + .set(&mut env.scope(), code_key.into(), code.into()) + .unwrap_or(false) + { + return napi_set_last_error(env, napi_generic_failure); + } + } + + env.scope().throw_exception(error); + let error = v8::Global::new(&mut env.scope(), error); + env.last_exception = Some(error); + + napi_clear_last_error(env) + }}; +} + +#[napi_sym] +fn napi_throw_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, error) +} + +#[napi_sym] +fn napi_throw_type_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, type_error) +} + +#[napi_sym] +fn napi_throw_range_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, range_error) +} + +#[napi_sym] +fn node_api_throw_syntax_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, syntax_error) +} + +#[napi_sym] +fn napi_is_error( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_native_error(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_value_double( + env_ptr: *mut Env, + value: napi_value, + result: *mut f64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(number) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_number_expected); + }; + + unsafe { + *result = number.value(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_int32( + env_ptr: *mut Env, + value: napi_value, + result: *mut i32, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(value) = value.unwrap().int32_value(&mut env.scope()) else { + return napi_set_last_error(env, napi_number_expected); + }; + + unsafe { + *result = value; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_uint32( + env_ptr: *mut Env, + value: napi_value, + result: *mut u32, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(value) = value.unwrap().uint32_value(&mut env.scope()) else { + return napi_set_last_error(env, napi_number_expected); }; - *result = typedarray.into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_make_callback( - env: *mut Env, - async_context: *mut c_void, - recv: napi_value, - func: napi_value, - argc: isize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, recv); - if argc > 0 { - check_arg!(env, argv); + unsafe { + *result = value; } - if !async_context.is_null() { - log::info!("napi_make_callback: async_context is not supported"); - } + return napi_clear_last_error(env_ptr); +} - let recv = napi_value_unchecked(recv); - let func = napi_value_unchecked(func); +#[napi_sym] +fn napi_get_value_int64( + env_ptr: *mut Env, + value: napi_value, + result: *mut i64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); - let Ok(func) = v8::Local::::try_from(func) else { - return napi_function_expected; + let Some(number) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_number_expected); }; - let argv: &[v8::Local] = - transmute(std::slice::from_raw_parts(argv, argc as usize)); - let ret = func.call(&mut env.scope(), recv, argv); - *result = transmute::>, napi_value>(ret); - napi_ok + + let value = number.value(); + + unsafe { + if value.is_finite() { + *result = value as _; + } else { + *result = 0; + } + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bigint_int64( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, result: *mut i64, lossless: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + check_arg!(env, lossless); + + let Some(bigint) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + let (result_, lossless_) = bigint.i64_value(); - *result = result_; - *lossless = lossless_; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok + + unsafe { + *result = result_; + *lossless = lossless_; + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bigint_uint64( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, result: *mut u64, lossless: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + check_arg!(env, lossless); + + let Some(bigint) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + let (result_, lossless_) = bigint.u64_value(); - *result = result_; - *lossless = lossless_; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok + + unsafe { + *result = result_; + *lossless = lossless_; + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bigint_words( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, sign_bit: *mut i32, word_count: *mut usize, words: *mut u64, ) -> napi_status { - check_env!(env); - // TODO(bartlomieju): - // check_arg!(env, value); + let env = check_env!(env_ptr); + check_arg!(env, value); check_arg!(env, word_count); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let big = match value.to_big_int(&mut env.scope()) { - Some(b) => b, - None => return napi_bigint_expected, + let Some(bigint) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); }; + let word_count_int; if sign_bit.is_null() && words.is_null() { - word_count_int = big.word_count(); + word_count_int = bigint.word_count(); } else { check_arg!(env, sign_bit); check_arg!(env, words); - let out_words = std::slice::from_raw_parts_mut(words, *word_count); - let (sign, slice_) = big.to_words_array(out_words); + let out_words = + unsafe { std::slice::from_raw_parts_mut(words, *word_count) }; + let (sign, slice_) = bigint.to_words_array(out_words); word_count_int = slice_.len(); - *sign_bit = sign as i32; + unsafe { + *sign_bit = sign as i32; + } } - *word_count = word_count_int; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok + unsafe { + *word_count = word_count_int; + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bool( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.boolean_value(&mut env.scope()); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_double( - env: *mut Env, - value: napi_value, - result: *mut f64, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - return_status_if_false!(env, value.is_number(), napi_number_expected); - *result = value.number_value(&mut env.scope()).unwrap(); - napi_ok -} + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_get_value_external( - _env: *mut Env, - value: napi_value, - result: *mut *mut c_void, -) -> napi_status { - let value = napi_value_unchecked(value); - let ext = v8::Local::::try_from(value).unwrap(); - *result = ext.value(); - napi_ok -} + let Some(boolean) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_boolean_expected); + }; -#[napi_sym::napi_sym] -fn napi_get_value_int32( - env: *mut Env, - value: napi_value, - result: *mut i32, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.int32_value(&mut env.scope()).unwrap(); - napi_ok -} + unsafe { + *result = boolean.is_true(); + } -#[napi_sym::napi_sym] -fn napi_get_value_int64( - env: *mut Env, - value: napi_value, - result: *mut i64, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.integer_value(&mut env.scope()).unwrap(); - napi_ok + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_string_latin1( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, - buf: *mut u8, + buf: *mut c_char, bufsize: usize, result: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } + let env = check_env!(env_ptr); + check_arg!(env, value); - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.utf8_length(&mut env.scope()); + let Some(value) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; if buf.is_null() { - *result = string_len; + check_arg!(env, result); + unsafe { + *result = value.length(); + } } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write_one_byte( + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write_one_byte( &mut env.scope(), buffer, 0, v8::WriteOptions::NO_NULL_TERMINATION, ); - buf.add(copied).write(0); + unsafe { + buf.add(copied).write(0); + } if !result.is_null() { - *result = copied; + unsafe { + *result = copied; + } } } else if !result.is_null() { - *result = string_len; + unsafe { + *result = 0; + } } - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_string_utf8( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, buf: *mut u8, bufsize: usize, result: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } + let env = check_env!(env_ptr); + check_arg!(env, value); - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.utf8_length(&mut env.scope()); + let Some(value) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; if buf.is_null() { - *result = string_len; + check_arg!(env, result); + unsafe { + *result = value.length(); + } } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write_utf8( + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write_utf8( &mut env.scope(), buffer, None, - v8::WriteOptions::NO_NULL_TERMINATION - | v8::WriteOptions::REPLACE_INVALID_UTF8, + v8::WriteOptions::REPLACE_INVALID_UTF8 + | v8::WriteOptions::NO_NULL_TERMINATION, ); - buf.add(copied).write(0); + unsafe { + buf.add(copied).write(0); + } if !result.is_null() { - *result = copied; + unsafe { + *result = copied; + } } } else if !result.is_null() { - *result = string_len; + unsafe { + *result = 0; + } } - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_string_utf16( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, buf: *mut u16, bufsize: usize, result: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } + let env = check_env!(env_ptr); + check_arg!(env, value); - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.length(); + let Some(value) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; if buf.is_null() { - *result = string_len; + check_arg!(env, result); + unsafe { + *result = value.length(); + } } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write( + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write( &mut env.scope(), buffer, 0, v8::WriteOptions::NO_NULL_TERMINATION, ); - buf.add(copied).write(0); + unsafe { + buf.add(copied).write(0); + } if !result.is_null() { - *result = copied; + unsafe { + *result = copied; + } + } + } else if !result.is_null() { + unsafe { + *result = 0; + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_coerce_to_bool<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let coerced = value.unwrap().to_boolean(&mut env.scope()); + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_coerce_to_number<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_number(&mut env.scope()) else { + return napi_number_expected; + }; + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_coerce_to_object<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_object(&mut env.scope()) else { + return napi_object_expected; + }; + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_coerce_to_string<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_string(&mut env.scope()) else { + return napi_string_expected; + }; + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_wrap( + env: &mut Env, + js_object: napi_value, + native_object: *mut c_void, + finalize_cb: Option, + finalize_hint: *mut c_void, + result: *mut napi_ref, +) -> napi_status { + check_arg!(env, js_object); + let env_ptr = env as *mut Env; + + let Some(obj) = + js_object.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap); + + if obj + .has_private(&mut env.scope(), napi_wrap) + .unwrap_or(false) + { + return napi_invalid_arg; + } + + if !result.is_null() { + check_arg!(env, finalize_cb); + } + + let ownership = if result.is_null() { + ReferenceOwnership::Runtime + } else { + ReferenceOwnership::Userland + }; + let reference = Reference::new( + env_ptr, + obj.into(), + 0, + ownership, + finalize_cb, + native_object, + finalize_hint, + ); + + let reference = Reference::into_raw(reference) as *mut c_void; + + if !result.is_null() { + check_arg!(env, finalize_cb); + unsafe { + *result = reference; + } + } + + let external = v8::External::new(&mut env.scope(), reference); + assert!(obj + .set_private(&mut env.scope(), napi_wrap, external.into()) + .unwrap()); + + napi_ok +} + +fn unwrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, + keep: bool, +) -> napi_status { + check_arg!(env, obj); + if keep { + check_arg!(env, result); + } + + let Some(obj) = obj.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap); + let Some(val) = obj.get_private(&mut env.scope(), napi_wrap) else { + return napi_invalid_arg; + }; + + let Ok(external) = v8::Local::::try_from(val) else { + return napi_invalid_arg; + }; + + let reference = external.value() as *mut Reference; + let reference = unsafe { &mut *reference }; + + if !result.is_null() { + unsafe { + *result = reference.finalize_data; } - } else if !result.is_null() { - *result = string_len; + } + + if !keep { + assert!(obj + .delete_private(&mut env.scope(), napi_wrap) + .unwrap_or(false)); + unsafe { Reference::remove(reference) }; } napi_ok } -#[napi_sym::napi_sym] -fn napi_get_value_uint32( - env: *mut Env, - value: napi_value, - result: *mut u32, +#[napi_sym] +fn napi_unwrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.uint32_value(&mut env.scope()).unwrap(); - napi_ok + unwrap(env, obj, result, true) } -#[napi_sym::napi_sym] -fn napi_add_finalizer( - env_ptr: *mut Env, - js_object: napi_value, - native_object: *mut c_void, - finalize_cb: napi_finalize, +#[napi_sym] +fn napi_remove_wrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, +) -> napi_status { + unwrap(env, obj, result, false) +} + +struct ExternalWrapper { + data: *mut c_void, + type_tag: Option, +} + +#[napi_sym] +fn napi_create_external<'s>( + env: &'s mut Env, + data: *mut c_void, + finalize_cb: Option, finalize_hint: *mut c_void, - result: *mut napi_ref, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env_ptr); + let env_ptr = env as *mut Env; + check_arg!(env, result); - let value = napi_value_unchecked(js_object); - let value = - weak_local(env_ptr, value, native_object, finalize_cb, finalize_hint); + let wrapper = Box::new(ExternalWrapper { + data, + type_tag: None, + }); - if !result.is_null() { - *result = transmute(value); + let wrapper = Box::into_raw(wrapper); + let external = v8::External::new(&mut env.scope(), wrapper as _); + + if let Some(finalize_cb) = finalize_cb { + Reference::into_raw(Reference::new( + env_ptr, + external.into(), + 0, + ReferenceOwnership::Runtime, + Some(finalize_cb), + data, + finalize_hint, + )); + } + + unsafe { + *result = external.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_adjust_external_memory( - env: *mut Env, - change_in_bytes: i64, - adjusted_value: *mut i64, +#[napi_sym] +fn napi_type_tag_object( + env: &mut Env, + object_or_external: napi_value, + type_tag: *const napi_type_tag, ) -> napi_status { - check_env!(env); - check_arg!(env, adjusted_value); + check_arg!(env, object_or_external); + check_arg!(env, type_tag); - let env = unsafe { &mut *env }; - let isolate = &mut *env.isolate_ptr; - *adjusted_value = - isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); + let val = object_or_external.unwrap(); - napi_clear_last_error(env); - napi_ok -} + if let Ok(external) = v8::Local::::try_from(val) { + let wrapper_ptr = external.value() as *mut ExternalWrapper; + let wrapper = unsafe { &mut *wrapper_ptr }; + if wrapper.type_tag.is_some() { + return napi_invalid_arg; + } + wrapper.type_tag = Some(unsafe { *type_tag }); + return napi_ok; + } -#[napi_sym::napi_sym] -fn napi_call_function( - env: *mut Env, - recv: napi_value, - func: napi_value, - argc: usize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let recv = napi_value_unchecked(recv); - let func = napi_value_unchecked(func); - let Ok(func) = v8::Local::::try_from(func) else { - return napi_function_expected; + let Some(object) = val.to_object(&mut env.scope()) else { + return napi_object_expected; }; - let argv: &[v8::Local] = - transmute(std::slice::from_raw_parts(argv, argc)); - let ret = func.call(&mut env.scope(), recv, argv); - if !result.is_null() { - *result = transmute::>, napi_value>(ret); - } + let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag); - napi_ok -} + if object.has_private(&mut env.scope(), key).unwrap_or(false) { + return napi_invalid_arg; + } -#[napi_sym::napi_sym] -fn napi_close_escapable_handle_scope( - _env: *mut Env, - _scope: napi_escapable_handle_scope, -) -> napi_status { - // TODO: do this properly - napi_ok -} + let slice = unsafe { std::slice::from_raw_parts(type_tag as *const u64, 2) }; + let Some(tag) = v8::BigInt::new_from_words(&mut env.scope(), false, slice) + else { + return napi_generic_failure; + }; -#[napi_sym::napi_sym] -fn napi_close_handle_scope( - env: *mut Env, - _scope: napi_handle_scope, -) -> napi_status { - let env = &mut *env; - if env.open_handle_scopes == 0 { - return napi_handle_scope_mismatch; + if !object + .set_private(&mut env.scope(), key, tag.into()) + .unwrap_or(false) + { + return napi_generic_failure; } - // TODO: We are not opening a handle scope, therefore we cannot close it - // TODO: this is also not implemented in napi_open_handle_scope - // let _scope = &mut *(scope as *mut v8::HandleScope); - env.open_handle_scopes -= 1; + napi_ok } -#[napi_sym::napi_sym] -fn napi_define_class( - env_ptr: *mut Env, - name: *const c_char, - length: isize, - constructor: napi_callback, - callback_data: *mut c_void, - property_count: usize, - properties: *const napi_property_descriptor, - result: *mut napi_value, +#[napi_sym] +fn napi_check_object_type_tag( + env: &mut Env, + object_or_external: napi_value, + type_tag: *const napi_type_tag, + result: *mut bool, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; + check_arg!(env, object_or_external); + check_arg!(env, type_tag); check_arg!(env, result); - check_arg_option!(env, constructor); - if property_count > 0 { - check_arg!(env, properties); - } + let type_tag = unsafe { *type_tag }; - let name = if length == -1 { - let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() else { - return napi_invalid_arg; - }; - s - } else { - let slice = std::slice::from_raw_parts(name as *const u8, length as usize); - std::str::from_utf8(slice).unwrap() - }; + let val = object_or_external.unwrap(); - let tpl = - create_function_template(env_ptr, Some(name), constructor, callback_data); + if let Ok(external) = v8::Local::::try_from(val) { + let wrapper_ptr = external.value() as *mut ExternalWrapper; + let wrapper = unsafe { &mut *wrapper_ptr }; + unsafe { + *result = match wrapper.type_tag { + Some(t) => t == type_tag, + None => false, + }; + }; + return napi_ok; + } - let scope = &mut env.scope(); - let napi_properties: &[napi_property_descriptor] = - std::slice::from_raw_parts(properties, property_count); - let mut static_property_count = 0; + let Some(object) = val.to_object(&mut env.scope()) else { + return napi_object_expected; + }; - for p in napi_properties { - if p.attributes & napi_static != 0 { - // Will be handled below - static_property_count += 1; - continue; - } + let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag); - let name = if !p.utf8name.is_null() { - let name_str = CStr::from_ptr(p.utf8name).to_str().unwrap(); - v8::String::new(scope, name_str).unwrap() - } else { - transmute::>(p.name) - }; + let Some(val) = object.get_private(&mut env.scope(), key) else { + return napi_generic_failure; + }; - let method = p.method; - let getter = p.getter; - let setter = p.setter; + unsafe { + *result = false; + } - if getter.is_some() || setter.is_some() { - let getter: Option> = if getter.is_some() - { - Some(create_function_template(env_ptr, None, p.getter, p.data)) - } else { - None - }; - let setter: Option> = if setter.is_some() - { - Some(create_function_template(env_ptr, None, p.setter, p.data)) + if let Ok(bigint) = v8::Local::::try_from(val) { + let mut words = [0u64; 2]; + let (sign, words) = bigint.to_words_array(&mut words); + if !sign { + let pass = if words.len() == 2 { + type_tag.lower == words[0] && type_tag.upper == words[1] + } else if words.len() == 1 { + type_tag.lower == words[0] && type_tag.upper == 0 + } else if words.is_empty() { + type_tag.lower == 0 && type_tag.upper == 0 } else { - None + false }; - - let mut accessor_property = v8::PropertyAttribute::NONE; - if getter.is_some() - && setter.is_some() - && (p.attributes & napi_writable) == 0 - { - accessor_property = - accessor_property | v8::PropertyAttribute::READ_ONLY; - } - if p.attributes & napi_enumerable == 0 { - accessor_property = - accessor_property | v8::PropertyAttribute::DONT_ENUM; - } - if p.attributes & napi_configurable == 0 { - accessor_property = - accessor_property | v8::PropertyAttribute::DONT_DELETE; + unsafe { + *result = pass; } - - let proto = tpl.prototype_template(scope); - proto.set_accessor_property( - name.into(), - getter, - setter, - accessor_property, - ); - } else if method.is_some() { - let function = create_function_template(env_ptr, None, p.method, p.data); - let proto = tpl.prototype_template(scope); - proto.set(name.into(), function.into()); - } else { - let proto = tpl.prototype_template(scope); - proto.set( - name.into(), - transmute::>(p.value), - ); } } - let value: v8::Local = tpl.get_function(scope).unwrap().into(); - *result = value.into(); + napi_ok +} - if static_property_count > 0 { - let mut static_descriptors = Vec::with_capacity(static_property_count); +#[napi_sym] +fn napi_get_value_external( + env: *mut Env, + value: napi_value, + result: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); - for p in napi_properties { - if p.attributes & napi_static != 0 { - static_descriptors.push(*p); - } - } + let Some(external) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; - status_call!(napi_define_properties( - env_ptr, - *result, - static_descriptors.len(), - static_descriptors.as_ptr(), - )); + let wrapper_ptr = external.value() as *const ExternalWrapper; + let wrapper = unsafe { &*wrapper_ptr }; + + unsafe { + *result = wrapper.data; } - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_define_properties( - env_ptr: *mut Env, - obj: napi_value, - property_count: usize, - properties: *const napi_property_descriptor, +#[napi_sym] +fn napi_create_reference( + env: *mut Env, + value: napi_value, + initial_refcount: u32, + result: *mut napi_ref, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - if property_count > 0 { - check_arg!(env, properties); - } + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); - let scope = &mut env.scope(); + let value = value.unwrap(); - let Ok(object) = v8::Local::::try_from(napi_value_unchecked(obj)) - else { - return napi_object_expected; - }; + let reference = Reference::new( + env, + value, + initial_refcount, + ReferenceOwnership::Userland, + None, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); - let properties = std::slice::from_raw_parts(properties, property_count); - for property in properties { - let name = if !property.utf8name.is_null() { - let name_str = CStr::from_ptr(property.utf8name).to_str().unwrap(); - let Some(name_v8_str) = v8::String::new(scope, name_str) else { - return napi_generic_failure; - }; - name_v8_str.into() - } else { - let property_value = napi_value_unchecked(property.name); - let Ok(prop) = v8::Local::::try_from(property_value) else { - return napi_name_expected; - }; - prop - }; + let ptr = Reference::into_raw(reference); - if property.getter.is_some() || property.setter.is_some() { - let local_getter: v8::Local = if property.getter.is_some() { - create_function(env_ptr, None, property.getter, property.data).into() - } else { - v8::undefined(scope).into() - }; - let local_setter: v8::Local = if property.setter.is_some() { - create_function(env_ptr, None, property.setter, property.data).into() - } else { - v8::undefined(scope).into() - }; + unsafe { + *result = ptr as _; + } - let mut desc = - v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); - desc.set_enumerable(property.attributes & napi_enumerable != 0); - desc.set_configurable(property.attributes & napi_configurable != 0); + napi_clear_last_error(env) +} - let define_maybe = object.define_property(scope, name, &desc); - return_status_if_false!( - env_ptr, - define_maybe.is_some(), - napi_invalid_arg - ); - } else if property.method.is_some() { - let value: v8::Local = { - let function = - create_function(env_ptr, None, property.method, property.data); - function.into() - }; - return_status_if_false!( - env_ptr, - object.set(scope, name.into(), value).is_some(), - napi_invalid_arg - ); - } else { - let value = napi_value_unchecked(property.value); - return_status_if_false!( - env_ptr, - object.set(scope, name.into(), value).is_some(), - napi_invalid_arg - ); - } - } +#[napi_sym] +fn napi_delete_reference(env: *mut Env, ref_: napi_ref) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); - napi_ok + let reference = unsafe { Reference::from_raw(ref_ as _) }; + + drop(reference); + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_delete_element( +#[napi_sym] +fn napi_reference_ref( env: *mut Env, - value: napi_value, - index: u32, - result: *mut bool, + ref_: napi_ref, + result: *mut u32, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - *result = obj.delete_index(&mut env.scope(), index).unwrap_or(false); - napi_ok + let env = check_env!(env); + check_arg!(env, ref_); + + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + let count = reference.ref_(); + + if !result.is_null() { + unsafe { + *result = count; + } + } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_delete_property( +#[napi_sym] +fn napi_reference_unref( env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut bool, + ref_: napi_ref, + result: *mut u32, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); - check_arg!(env, result); + let env = check_env!(env); + check_arg!(env, ref_); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; - }; + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + if reference.ref_count == 0 { + return napi_set_last_error(env, napi_generic_failure); + } + + let count = reference.unref(); - let Some(deleted) = object.delete(scope, key.unwrap_unchecked()) else { - return napi_generic_failure; - }; + if !result.is_null() { + unsafe { + *result = count; + } + } - *result = deleted; - napi_ok + napi_clear_last_error(env) } -// TODO: properly implement ref counting stuff -#[napi_sym::napi_sym] -fn napi_delete_reference(_env: *mut Env, _nref: napi_ref) -> napi_status { - napi_ok -} +#[napi_sym] +fn napi_get_reference_value( + env_ptr: *mut Env, + ref_: napi_ref, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, ref_); + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status { - check_env!(env); + let reference = unsafe { &mut *(ref_ as *mut Reference) }; - let value = napi_value_unchecked(value); - let Ok(ab) = v8::Local::::try_from(value) else { - return napi_arraybuffer_expected; + let value = match &reference.state { + ReferenceState::Strong(g) => Some(v8::Local::new(&mut env.scope(), g)), + ReferenceState::Weak(w) => w.to_local(&mut env.scope()), }; - if !ab.is_detachable() { - return napi_detachable_arraybuffer_expected; + unsafe { + *result = value.into(); } - // Expected to crash for None. - ab.detach(None).unwrap(); - - napi_clear_last_error(env); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_escape_handle<'s>( - _env: *mut Env, - _handle_scope: napi_escapable_handle_scope, - escapee: napi_value<'s>, - result: *mut napi_value<'s>, +#[napi_sym] +fn napi_open_handle_scope( + env: *mut Env, + _result: *mut napi_handle_scope, ) -> napi_status { - // TODO - *result = escapee; - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_all_property_names(_env: *mut Env) -> napi_status { - // TODO - napi_ok +#[napi_sym] +fn napi_close_handle_scope( + env: *mut Env, + _scope: napi_handle_scope, +) -> napi_status { + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_and_clear_last_exception( +#[napi_sym] +fn napi_open_escapable_handle_scope( env: *mut Env, - result: *mut napi_value, + _result: *mut napi_escapable_handle_scope, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - // TODO: just return undefined for now we don't cache - // exceptions in env. - let value: v8::Local = v8::undefined(&mut env.scope()).into(); - *result = value.into(); - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_array_length( - _env: *mut Env, - value: napi_value, - result: *mut u32, +#[napi_sym] +fn napi_close_escapable_handle_scope( + env: *mut Env, + _scope: napi_escapable_handle_scope, ) -> napi_status { - let value = napi_value_unchecked(value); - *result = v8::Local::::try_from(value).unwrap().length(); - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_arraybuffer_info( - _env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, +#[napi_sym] +fn napi_escape_handle<'s>( + env: *mut Env, + _scope: napi_escapable_handle_scope, + escapee: napi_value<'s>, + result: *mut napi_value<'s>, ) -> napi_status { - let value = napi_value_unchecked(value); - let buf = v8::Local::::try_from(value).unwrap(); - if !data.is_null() { - *data = get_array_buffer_ptr(buf); - } - if !length.is_null() { - *length = buf.byte_length(); + let env = check_env!(env); + + unsafe { + *result = escapee; } - napi_ok + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_boolean( - env: *mut Env, - value: bool, - result: *mut napi_value, +#[napi_sym] +fn napi_new_instance<'s>( + env: &'s mut Env, + constructor: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); + check_arg!(env, constructor); + if argc > 0 { + check_arg!(env, argv); + } check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Boolean::new(env.isolate(), value).into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_get_buffer_info( - env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Ok(buf) = v8::Local::::try_from(value) else { - return napi_arraybuffer_expected; + let Some(func) = + constructor.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_invalid_arg; }; - let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); - let Ok(abuf) = v8::Local::::try_from( - buf.get(&mut env.scope(), buffer_name.into()).unwrap(), - ) else { - return napi_arraybuffer_expected; + + let args = if argc > 0 { + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local, argc) + } + } else { + &[] }; - if !data.is_null() { - *data = get_array_buffer_ptr(abuf); - } - if !length.is_null() { - *length = abuf.byte_length(); + + let Some(value) = func.new_instance(&mut env.scope(), args) else { + return napi_pending_exception; + }; + + unsafe { + *result = value.into(); } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_cb_info( - env: *mut Env, - cbinfo: napi_callback_info, - argc: *mut i32, - argv: *mut napi_value, - this_arg: *mut napi_value, - data: *mut *mut c_void, +#[napi_sym] +fn napi_instanceof( + env: &mut Env, + object: napi_value, + constructor: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg!(env, cbinfo); + check_arg!(env, object); + check_arg!(env, result); - let cbinfo: &CallbackInfo = &*(cbinfo as *const CallbackInfo); - let args = &*(cbinfo.args as *const v8::FunctionCallbackArguments); + let Some(ctor) = constructor.and_then(|v| v.to_object(&mut env.scope())) + else { + return napi_object_expected; + }; - if !argv.is_null() { - check_arg!(env, argc); - let mut v_argv = std::slice::from_raw_parts_mut(argv, argc as usize); - for i in 0..*argc { - let mut arg = args.get(i); - v_argv[i as usize] = arg.into(); + if !ctor.is_function() { + unsafe { + napi_throw_type_error( + env, + "ERR_NAPI_CONS_FUNCTION\0".as_ptr() as _, + "Constructor must be a function\0".as_ptr() as _, + ); } + return napi_function_expected; } - if !argc.is_null() { - *argc = args.length(); - } - - if !this_arg.is_null() { - let mut this = args.this(); - *this_arg = this.into(); - } + let Some(res) = object.unwrap().instance_of(&mut env.scope(), ctor) else { + return napi_generic_failure; + }; - if !data.is_null() { - *data = cbinfo.cb_info; + unsafe { + *result = res; } - napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] -fn napi_get_dataview_info( - env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, +#[napi_sym] +fn napi_is_exception_pending( + env_ptr: *mut Env, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Ok(buf) = v8::Local::::try_from(value) else { - return napi_invalid_arg; - }; - let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); - let Ok(abuf) = v8::Local::::try_from( - buf.get(&mut env.scope(), buffer_name.into()).unwrap(), - ) else { - return napi_invalid_arg; - }; - if !data.is_null() { - *data = get_array_buffer_ptr(abuf); + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = env.last_exception.is_some(); } - *length = abuf.byte_length(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_get_date_value( - env: *mut Env, - value: napi_value, - result: *mut f64, -) -> napi_status { - check_env!(env); - let value = napi_value_unchecked(value); - return_status_if_false!(env, value.is_date(), napi_date_expected); - let env = unsafe { &mut *env }; - let Ok(date) = v8::Local::::try_from(value) else { - return napi_date_expected; - }; - // TODO: should be value of - *result = date.number_value(&mut env.scope()).unwrap(); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_get_element( - env: *mut Env, - object: napi_value, - index: u32, +#[napi_sym] +fn napi_get_and_clear_last_exception( + env_ptr: *mut Env, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let Ok(object) = v8::Local::::try_from(object) else { - return napi_invalid_arg; - }; - let value: v8::Local = - object.get_index(&mut env.scope(), index).unwrap(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); + let env = check_env!(env_ptr); check_arg!(env, result); - let value: v8::Local = - transmute::, v8::Local>((*env).global); - *result = value.into(); - napi_clear_last_error(env); - napi_ok + let ex: v8::Local = + if let Some(last_exception) = env.last_exception.take() { + v8::Local::new(&mut env.scope(), last_exception) + } else { + v8::undefined(&mut env.scope()).into() + }; + + unsafe { + *result = ex.into(); + } + + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_get_instance_data( +#[napi_sym] +fn napi_is_arraybuffer( env: *mut Env, - result: *mut *mut c_void, + value: napi_value, + result: *mut bool, ) -> napi_status { - let env = &mut *env; - let shared = env.shared(); - *result = shared.instance_data; - napi_ok -} + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); -// TODO(bartlomieju): this function is broken -#[napi_sym::napi_sym] -fn napi_get_last_error_info( - _env: *mut Env, - error_code: *mut *const napi_extended_error_info, -) -> napi_status { - let err_info = Box::new(napi_extended_error_info { - error_message: std::ptr::null(), - engine_reserved: std::ptr::null_mut(), - engine_error_code: 0, - error_code: napi_ok, - }); + unsafe { + *result = value.unwrap().is_array_buffer(); + } - *error_code = Box::into_raw(err_info); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_named_property( - env: *mut Env, - object: napi_value, - utf8_name: *const c_char, - result: *mut napi_value, +#[napi_sym] +fn napi_create_arraybuffer<'s>( + env: &'s mut Env, + len: usize, + data: *mut *mut c_void, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let utf8_name = std::ffi::CStr::from_ptr(utf8_name); - let name = - v8::String::new(&mut env.scope(), &utf8_name.to_string_lossy()).unwrap(); - let value: v8::Local = object - .to_object(&mut env.scope()) - .unwrap() - .get(&mut env.scope(), name.into()) - .unwrap(); - *result = value.into(); - napi_ok -} + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_get_new_target( - _env: &mut Env, - cbinfo: &CallbackInfo, - result: &mut v8::Local, -) -> napi_status { - let info = &*(cbinfo.args as *const v8::FunctionCallbackArguments); - *result = info.new_target(); - napi_ok -} + let buffer = v8::ArrayBuffer::new(&mut env.scope(), len); + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(buffer) as _; + } + } + + unsafe { + *result = buffer.into(); + } -#[napi_sym::napi_sym] -fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::null(env.isolate()).into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_get_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_create_external_arraybuffer<'s>( + env: &'s mut Env, + data: *mut c_void, + byte_length: usize, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = transmute::>(object); - let key = napi_value_unchecked(key); - let value: v8::Local = object.get(&mut env.scope(), key).unwrap(); - *result = value.into(); + check_arg!(env, result); + + let store = make_external_backing_store( + env, + data, + byte_length, + std::ptr::null_mut(), + finalize_cb, + finalize_hint, + ); + + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + let value: v8::Local = ab.into(); + + unsafe { + *result = value.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_property_names( +#[napi_sym] +fn napi_get_arraybuffer_info( env: *mut Env, - object: napi_value, - result: *mut napi_value, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let array: v8::Local = object - .to_object(&mut env.scope()) - .unwrap() - .get_property_names(&mut env.scope(), Default::default()) - .unwrap(); - let value: v8::Local = array.into(); - *result = value.into(); + let env = check_env!(env); + check_arg!(env, value); + + let Some(buf) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(buf); + } + } + + if !length.is_null() { + unsafe { + *length = buf.byte_length(); + } + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_prototype( +#[napi_sym] +fn napi_is_typedarray( env: *mut Env, value: napi_value, - result: *mut napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let proto = obj.get_prototype(&mut env.scope()).unwrap(); - *result = proto.into(); + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_typed_array(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_reference_value( - env: *mut Env, - reference: napi_ref, - result: *mut napi_value, +#[napi_sym] +fn napi_create_typedarray<'s>( + env: &'s mut Env, + ty: napi_typedarray_type, + length: usize, + arraybuffer: napi_value, + byte_offset: usize, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let value = transmute::>(reference); - *result = value.into(); + check_arg!(env, arraybuffer); + check_arg!(env, result); + + let Some(ab) = + arraybuffer.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_arraybuffer_expected; + }; + + macro_rules! create { + ($TypedArray:ident, $size_of_element:expr) => {{ + let soe = $size_of_element; + if soe > 1 && byte_offset % soe != 0 { + let message = v8::String::new( + &mut env.scope(), + format!( + "start offset of {} should be multiple of {}", + stringify!($TypedArray), + soe + ) + .as_str(), + ) + .unwrap(); + let exc = v8::Exception::range_error(&mut env.scope(), message); + env.scope().throw_exception(exc); + return napi_pending_exception; + } + + if length * soe + byte_offset > ab.byte_length() { + let message = + v8::String::new(&mut env.scope(), "Invalid typed array length") + .unwrap(); + let exc = v8::Exception::range_error(&mut env.scope(), message); + env.scope().throw_exception(exc); + return napi_pending_exception; + } + + let Some(ta) = + v8::$TypedArray::new(&mut env.scope(), ab, byte_offset, length) + else { + return napi_generic_failure; + }; + ta.into() + }}; + } + + let typedarray: v8::Local = match ty { + napi_uint8_array => create!(Uint8Array, 1), + napi_uint8_clamped_array => create!(Uint8ClampedArray, 1), + napi_int8_array => create!(Int8Array, 1), + napi_uint16_array => create!(Uint16Array, 2), + napi_int16_array => create!(Int16Array, 2), + napi_uint32_array => create!(Uint32Array, 4), + napi_int32_array => create!(Int32Array, 4), + napi_float32_array => create!(Float32Array, 4), + napi_float64_array => create!(Float64Array, 8), + napi_bigint64_array => create!(BigInt64Array, 8), + napi_biguint64_array => create!(BigUint64Array, 8), + _ => { + return napi_invalid_arg; + } + }; + + unsafe { + *result = typedarray.into(); + } + napi_ok } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_typedarray_info( - env: *mut Env, + env_ptr: *mut Env, typedarray: napi_value, type_: *mut napi_typedarray_type, length: *mut usize, @@ -2018,818 +3048,539 @@ fn napi_get_typedarray_info( arraybuffer: *mut napi_value, byte_offset: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(typedarray); - let Ok(array) = v8::Local::::try_from(value) else { - return napi_invalid_arg; + let env = check_env!(env_ptr); + check_arg!(env, typedarray); + + let Some(array) = + typedarray.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_invalid_arg); }; if !type_.is_null() { - if value.is_int8_array() { - *type_ = napi_int8_array; - } else if value.is_uint8_array() { - *type_ = napi_uint8_array; - } else if value.is_uint8_clamped_array() { - *type_ = napi_uint8_clamped_array; - } else if value.is_int16_array() { - *type_ = napi_int16_array; - } else if value.is_uint16_array() { - *type_ = napi_uint16_array; - } else if value.is_int32_array() { - *type_ = napi_int32_array; - } else if value.is_uint32_array() { - *type_ = napi_uint32_array; - } else if value.is_float32_array() { - *type_ = napi_float32_array; - } else if value.is_float64_array() { - *type_ = napi_float64_array; + let tatype = if array.is_int8_array() { + napi_int8_array + } else if array.is_uint8_array() { + napi_uint8_array + } else if array.is_uint8_clamped_array() { + napi_uint8_clamped_array + } else if array.is_int16_array() { + napi_int16_array + } else if array.is_uint16_array() { + napi_uint16_array + } else if array.is_int32_array() { + napi_int32_array + } else if array.is_uint32_array() { + napi_uint32_array + } else if array.is_float32_array() { + napi_float32_array + } else if array.is_float64_array() { + napi_float64_array + } else if array.is_big_int64_array() { + napi_bigint64_array + } else if array.is_big_uint64_array() { + napi_biguint64_array + } else { + unreachable!(); + }; + + unsafe { + *type_ = tatype; } } if !length.is_null() { - *length = array.length(); + unsafe { + *length = array.length(); + } } - if !data.is_null() || !arraybuffer.is_null() { - let buf = array.buffer(&mut env.scope()).unwrap(); - if !data.is_null() { - *data = get_array_buffer_ptr(buf) as *mut c_void; + if !data.is_null() { + unsafe { + *data = array.data(); } - if !arraybuffer.is_null() { + } + + if !arraybuffer.is_null() { + let buf = array.buffer(&mut env.scope()).unwrap(); + unsafe { *arraybuffer = buf.into(); } } if !byte_offset.is_null() { - *byte_offset = array.byte_offset(); + unsafe { + *byte_offset = array.byte_offset(); + } } - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::undefined(env.isolate()).into(); - napi_ok -} - -pub const NAPI_VERSION: u32 = 8; - -#[napi_sym::napi_sym] -fn napi_get_version(_: napi_env, version: *mut u32) -> napi_status { - *version = NAPI_VERSION; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_element( - env: *mut Env, - value: napi_value, - index: u32, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - *result = obj.has_index(&mut env.scope(), index).unwrap_or(false); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_named_property( - env: *mut Env, - value: napi_value, - key: *const c_char, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let key = CStr::from_ptr(key).to_str().unwrap(); - let key = v8::String::new(&mut env.scope(), key).unwrap(); - *result = obj.has(&mut env.scope(), key.into()).unwrap_or(false); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_has_own_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut bool, +#[napi_sym] +fn napi_create_dataview<'s>( + env: &'s mut Env, + byte_length: usize, + arraybuffer: napi_value, + byte_offset: usize, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); + check_arg!(env, arraybuffer); check_arg!(env, result); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { + let Some(buffer) = + arraybuffer.and_then(|v| v8::Local::::try_from(v).ok()) + else { return napi_invalid_arg; }; - if key.is_none() { - return napi_invalid_arg; + if byte_length + byte_offset > buffer.byte_length() { + unsafe { + return napi_throw_range_error( + env, + "ERR_NAPI_INVALID_DATAVIEW_ARGS\0".as_ptr() as _, + "byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in\0".as_ptr() as _, + ); + } } - let Ok(key) = v8::Local::::try_from(key.unwrap()) else { - return napi_name_expected; - }; - let Some(has_own) = object.has_own_property(scope, key) else { - return napi_generic_failure; + // let dataview = v8::DataView::new(&mut env, buffer, byte_offset, byte_length); + let dataview = { + let context = &mut env.scope().get_current_context(); + let global = context.global(&mut env.scope()); + let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); + let data_view = + global.get(&mut env.scope(), data_view_name.into()).unwrap(); + let Ok(data_view) = v8::Local::::try_from(data_view) else { + return napi_function_expected; + }; + let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); + let byte_length = v8::Number::new(&mut env.scope(), byte_length as f64); + let Some(dv) = data_view.new_instance( + &mut env.scope(), + &[buffer.into(), byte_offset.into(), byte_length.into()], + ) else { + return napi_generic_failure; + }; + dv }; - *result = has_own; - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); - check_arg!(env, result); - - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; - }; + unsafe { + *result = dataview.into(); + } - let Some(has) = object.has(scope, key.unwrap_unchecked()) else { - return napi_generic_failure; - }; - *result = has; napi_ok } -#[napi_sym::napi_sym] -fn napi_instanceof( +#[napi_sym] +fn napi_is_dataview( env: *mut Env, value: napi_value, - constructor: napi_value, result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, constructor); - check_arg_option!(env, value); + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); - let value = napi_value_unchecked(value); - let constructor = napi_value_unchecked(constructor); - let Some(ctor) = constructor.to_object(&mut env.scope()) else { - return napi_object_expected; - }; - if !ctor.is_function() { - return napi_function_expected; + unsafe { + *result = value.unwrap().is_data_view(); } - let Some(res) = value.instance_of(&mut env.scope(), ctor) else { - return napi_generic_failure; - }; - - *result = res; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_array( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_array(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_arraybuffer( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_array_buffer(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_buffer( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - // TODO: should we assume Buffer as Uint8Array in Deno? - // or use std/node polyfill? - *result = value.is_typed_array(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_is_dataview( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_data_view(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_date( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_date(); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_is_detached_arraybuffer( - env: *mut Env, - value: napi_value, - result: *mut bool, +#[napi_sym] +fn napi_get_dataview_info( + env_ptr: *mut Env, + dataview: napi_value, + byte_length: *mut usize, + data: *mut *mut c_void, + arraybuffer: *mut napi_value, + byte_offset: *mut usize, ) -> napi_status { - check_env!(env); - check_arg!(env, result); + let env = check_env!(env_ptr); + check_arg!(env, dataview); - let value = napi_value_unchecked(value); - - *result = match v8::Local::::try_from(value) { - Ok(array_buffer) => array_buffer.was_detached(), - Err(_) => false, + let Some(array) = + dataview.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_invalid_arg; }; - napi_clear_last_error(env); + if !byte_length.is_null() { + unsafe { + *byte_length = array.byte_length(); + } + } - napi_ok -} + if !arraybuffer.is_null() { + let Some(buffer) = array.buffer(&mut env.scope()) else { + return napi_generic_failure; + }; -#[napi_sym::napi_sym] -fn napi_is_error( - env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - { - check_env!(env); - if value.is_none() { - return napi_invalid_arg; + unsafe { + *arraybuffer = buffer.into(); } - check_arg!(env, result); - - let value = napi_value_unchecked(value); - *result = value.is_native_error(); } - napi_clear_last_error(env); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_is_exception_pending(_env: *mut Env, result: *mut bool) -> napi_status { - // TODO - *result = false; - napi_ok -} + if !data.is_null() { + unsafe { + *data = array.data(); + } + } -#[napi_sym::napi_sym] -fn napi_is_promise( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_promise(); - napi_ok -} + if !byte_offset.is_null() { + unsafe { + *byte_offset = array.byte_offset(); + } + } -#[napi_sym::napi_sym] -fn napi_is_typedarray( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_typed_array(); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_new_instance( - env: *mut Env, - constructor: napi_value, - argc: usize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let constructor = napi_value_unchecked(constructor); - let Ok(constructor) = v8::Local::::try_from(constructor) else { - return napi_function_expected; - }; - let args: &[v8::Local] = - transmute(std::slice::from_raw_parts(argv, argc)); - let inst = constructor.new_instance(&mut env.scope(), args).unwrap(); - let value: v8::Local = inst.into(); - *result = value.into(); - napi_ok -} +#[napi_sym] +fn napi_get_version(env: *mut Env, result: *mut u32) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_object_freeze( - env: &mut Env, - object: v8::Local, -) -> napi_status { - let object = object.to_object(&mut env.scope()).unwrap(); - if object - .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Frozen) - .is_none() - { - return napi_generic_failure; - }; + unsafe { + *result = NAPI_VERSION; + } - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_object_seal( - env: &mut Env, - object: v8::Local, +#[napi_sym] +fn napi_create_promise<'s>( + env: &'s mut Env, + deferred: *mut napi_deferred, + promise: *mut napi_value<'s>, ) -> napi_status { - let object = object.to_object(&mut env.scope()).unwrap(); - if object - .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Sealed) - .is_none() - { - return napi_generic_failure; - } + check_arg!(env, deferred); + check_arg!(env, promise); - napi_ok -} + let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); -#[napi_sym::napi_sym] -fn napi_open_escapable_handle_scope( - _env: *mut Env, - _result: *mut napi_escapable_handle_scope, -) -> napi_status { - // TODO: do this properly - napi_ok -} + let global = v8::Global::new(&mut env.scope(), resolver); + let global_ptr = global.into_raw().as_ptr() as napi_deferred; -#[napi_sym::napi_sym] -fn napi_open_handle_scope( - env: *mut Env, - _result: *mut napi_handle_scope, -) -> napi_status { - let env = &mut *env; + let p = resolver.get_promise(&mut env.scope()); - // TODO: this is also not implemented in napi_close_handle_scope - // *result = &mut env.scope() as *mut _ as napi_handle_scope; - env.open_handle_scopes += 1; - napi_ok -} + unsafe { + *deferred = global_ptr; + } -#[napi_sym::napi_sym] -fn napi_reference_ref() -> napi_status { - // TODO - napi_ok -} + unsafe { + *promise = p.into(); + } -#[napi_sym::napi_sym] -fn napi_reference_unref() -> napi_status { - // TODO napi_ok } -#[napi_sym::napi_sym] -fn napi_reject_deferred( - env: *mut Env, +#[napi_sym] +fn napi_resolve_deferred( + env: &mut Env, deferred: napi_deferred, - error: napi_value, + result: napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + check_arg!(env, result); + check_arg!(env, deferred); + let isolate = unsafe { &mut *env.isolate_ptr }; let deferred_ptr = - NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); - // TODO(@littledivy): Use Global::from_raw instead casting to local. - // SAFETY: Isolate is still alive unless the module is doing something weird, - // global data is the size of a pointer. - // Global pointer is obtained from napi_create_promise - let resolver = transmute::< - NonNull, - v8::Local, - >(deferred_ptr); - resolver - .reject(&mut env.scope(), napi_value_unchecked(error)) - .unwrap(); - napi_ok -} + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .resolve(&mut env.scope(), result.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } -#[napi_sym::napi_sym] -fn napi_remove_wrap(env: *mut Env, value: napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - obj.delete_private(&mut env.scope(), napi_wrap).unwrap(); napi_ok } -#[napi_sym::napi_sym] -fn napi_resolve_deferred( - env: *mut Env, +#[napi_sym] +fn napi_reject_deferred( + env: &mut Env, deferred: napi_deferred, result: napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + check_arg!(env, result); + check_arg!(env, deferred); + + let isolate = unsafe { &mut *env.isolate_ptr }; let deferred_ptr = - NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); - // TODO(@littledivy): Use Global::from_raw instead casting to local. - // SAFETY: Isolate is still alive unless the module is doing something weird, - // global data is the size of a pointer. - // Global pointer is obtained from napi_create_promise - let resolver = transmute::< - NonNull, - v8::Local, - >(deferred_ptr); - resolver - .resolve(&mut env.scope(), napi_value_unchecked(result)) - .unwrap(); + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .reject(&mut env.scope(), result.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_run_script( +#[napi_sym] +fn napi_is_promise( env: *mut Env, - script: napi_value, - result: *mut napi_value, + value: napi_value, + is_promise: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, is_promise); - let script = napi_value_unchecked(script); - if !script.is_string() { - // TODO: - // napi_set_last_error - return napi_string_expected; + unsafe { + *is_promise = value.unwrap().is_promise(); } - let script = script.to_string(&mut env.scope()).unwrap(); - let script = v8::Script::compile(&mut env.scope(), script, None); - if script.is_none() { - return napi_generic_failure; - } - let script = script.unwrap(); - let rv = script.run(&mut env.scope()); + napi_clear_last_error(env) +} - if let Some(rv) = rv { - *result = rv.into(); - } else { +#[napi_sym] +fn napi_create_date<'s>( + env: &'s mut Env, + time: f64, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let Some(date) = v8::Date::new(&mut env.scope(), time) else { return napi_generic_failure; + }; + + unsafe { + *result = date.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_set_element( +#[napi_sym] +fn napi_is_date( env: *mut Env, - object: napi_value, - index: u32, value: napi_value, + is_date: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let Ok(object) = v8::Local::::try_from(object) else { - return napi_invalid_arg; - }; - let value = napi_value_unchecked(value); - object.set_index(&mut env.scope(), index, value).unwrap(); - napi_ok -} + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, is_date); -#[napi_sym::napi_sym] -fn napi_set_instance_data( - env: *mut Env, - data: *mut c_void, - finalize_cb: Option, - finalize_hint: *mut c_void, -) -> napi_status { - let env = &mut *env; - let shared = env.shared_mut(); - shared.instance_data = data; - shared.data_finalize = if finalize_cb.is_some() { - finalize_cb - } else { - None - }; - shared.data_finalize_hint = finalize_hint; - napi_ok + unsafe { + *is_date = value.unwrap().is_date(); + } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_set_named_property( - env: *mut Env, - object: napi_value, - name: *const c_char, +#[napi_sym] +fn napi_get_date_value( + env: &mut Env, value: napi_value, + result: *mut f64, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let name = CStr::from_ptr(name).to_str().unwrap(); - let object = transmute::>(object); - let value = napi_value_unchecked(value); - let name = v8::String::new(&mut env.scope(), name).unwrap(); - object.set(&mut env.scope(), name.into(), value).unwrap(); + check_arg!(env, result); + + let Some(date) = value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_date_expected; + }; + + unsafe { + *result = date.value_of(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_set_property( - env: *mut Env, - object: napi_value, - key: napi_value, - value: napi_value, +#[napi_sym] +fn napi_run_script<'s>( + env: &'s mut Env, + script: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); - check_arg_option!(env, value); + check_arg!(env, script); + check_arg!(env, result); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; + let Some(script) = + script.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_string_expected; }; - if object - .set(scope, key.unwrap_unchecked(), value.unwrap_unchecked()) - .is_none() - { + let Some(script) = v8::Script::compile(&mut env.scope(), script, None) else { return napi_generic_failure; }; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_strict_equals( - env: *mut Env, - lhs: napi_value, - rhs: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, lhs); - check_arg_option!(env, rhs); + let Some(rv) = script.run(&mut env.scope()) else { + return napi_generic_failure; + }; - *result = lhs.unwrap_unchecked().strict_equals(rhs.unwrap_unchecked()); - napi_ok -} + unsafe { + *result = rv.into(); + } -#[napi_sym::napi_sym] -fn napi_throw(env: *mut Env, error: napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let error = napi_value_unchecked(error); - env.scope().throw_exception(error); napi_ok } -#[napi_sym::napi_sym] -fn napi_throw_error( - env: *mut Env, - code: *const c_char, - msg: *const c_char, +#[napi_sym] +fn napi_add_finalizer( + env_ptr: *mut Env, + value: napi_value, + finalize_data: *mut c_void, + finalize_cb: Option, + finalize_hint: *mut c_void, + result: *mut napi_ref, ) -> napi_status { - // TODO: add preamble here + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, finalize_cb); - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; + let Some(value) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); + let ownership = if result.is_null() { + ReferenceOwnership::Runtime + } else { + ReferenceOwnership::Userland + }; + + let reference = Reference::new( + env, + value.into(), + 0, + ownership, + finalize_cb, + finalize_data, + finalize_hint, + ); - unsafe { &mut *env }.scope().throw_exception(error); + if !result.is_null() { + unsafe { + *result = Reference::into_raw(reference) as _; + } } - napi_clear_last_error(env); - napi_ok + + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_throw_range_error( +#[napi_sym] +fn node_api_post_finalizer( env: *mut Env, - code: *const c_char, - msg: *const c_char, + _finalize_cb: napi_finalize, + _finalize_data: *mut c_void, + _finalize_hint: *mut c_void, ) -> napi_status { - // TODO: add preamble here - - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::range_error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - unsafe { &mut *env }.scope().throw_exception(error); - } - napi_clear_last_error(env); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_throw_type_error( +#[napi_sym] +fn napi_adjust_external_memory( env: *mut Env, - code: *const c_char, - msg: *const c_char, + change_in_bytes: i64, + adjusted_value: *mut i64, ) -> napi_status { - // TODO: add preamble here + let env = check_env!(env); + check_arg!(env, adjusted_value); - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::type_error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - unsafe { &mut *env }.scope().throw_exception(error); - } - napi_clear_last_error(env); - napi_ok -} + let isolate = unsafe { &mut *env.isolate_ptr }; -pub fn get_value_type(value: v8::Local) -> Option { - if value.is_undefined() { - Some(napi_undefined) - } else if value.is_null() { - Some(napi_null) - } else if value.is_external() { - Some(napi_external) - } else if value.is_boolean() { - Some(napi_boolean) - } else if value.is_number() { - Some(napi_number) - } else if value.is_big_int() { - Some(napi_bigint) - } else if value.is_string() { - Some(napi_string) - } else if value.is_symbol() { - Some(napi_symbol) - } else if value.is_function() { - Some(napi_function) - } else if value.is_object() { - Some(napi_object) - } else { - None + unsafe { + *adjusted_value = + isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_typeof( +#[napi_sym] +fn napi_set_instance_data( env: *mut Env, - value: napi_value, - result: *mut napi_valuetype, + data: *mut c_void, + finalize_cb: Option, + finalize_hint: *mut c_void, ) -> napi_status { - check_env!(env); - check_arg_option!(env, value); - check_arg!(env, result); + let env = check_env!(env); - let Some(ty) = get_value_type(value.unwrap()) else { - return napi_invalid_arg; - }; - *result = ty; - napi_ok + env.shared_mut().instance_data = Some(InstanceData { + data, + finalize_cb, + finalize_hint, + }); + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_unwrap( +#[napi_sym] +fn napi_get_instance_data( env: *mut Env, - value: napi_value, - result: *mut *mut c_void, + data: *mut *mut c_void, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - let ext = obj.get_private(&mut env.scope(), napi_wrap).unwrap(); - let Some(ext) = v8::Local::::try_from(ext).ok() else { - return napi_invalid_arg; + let env = check_env!(env); + check_arg!(env, data); + + let instance_data = match &env.shared().instance_data { + Some(v) => v.data, + None => std::ptr::null_mut(), }; - *result = ext.value(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_wrap( - env: *mut Env, - value: napi_value, - native_object: *mut c_void, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - let ext = v8::External::new(&mut env.scope(), native_object); - obj.set_private(&mut env.scope(), napi_wrap, ext.into()); - napi_ok + unsafe { *data = instance_data }; + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn node_api_throw_syntax_error( - env: *mut Env, - _code: *const c_char, - msg: *const c_char, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; +#[napi_sym] +fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); - // let code = CStr::from_ptr(code).to_str().unwrap(); - let msg = CStr::from_ptr(msg).to_str().unwrap(); + let Some(ab) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_arraybuffer_expected); + }; - // let code = v8::String::new(&mut env.scope(), code).unwrap(); - let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + if !ab.is_detachable() { + return napi_set_last_error(env, napi_detachable_arraybuffer_expected); + } - let error = v8::Exception::syntax_error(&mut env.scope(), msg); - env.scope().throw_exception(error); + // Expected to crash for None. + ab.detach(None).unwrap(); + napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] -fn node_api_create_syntax_error( - env: *mut Env, - _code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_is_detached_arraybuffer( + env_ptr: *mut Env, + arraybuffer: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - // let code = napi_value_unchecked(code); - let msg = napi_value_unchecked(msg); + let env = check_env!(env_ptr); + check_arg!(env, arraybuffer); + check_arg!(env, result); - let msg = msg.to_string(&mut env.scope()).unwrap(); + let is_detached = match arraybuffer + .and_then(|v| v8::Local::::try_from(v).ok()) + { + Some(ab) => ab.was_detached(), + None => false, + }; - let error = v8::Exception::syntax_error(&mut env.scope(), msg); - *result = error.into(); + unsafe { + *result = is_detached; + } - napi_ok + napi_clear_last_error(env) } diff --git a/cli/napi/mod.rs b/cli/napi/mod.rs index 697ec06e35ef4..122d2ff0607f9 100644 --- a/cli/napi/mod.rs +++ b/cli/napi/mod.rs @@ -15,8 +15,6 @@ //! 2. Add the function's identifier to this JSON list. //! 3. Finally, run `tools/napi/generate_symbols_list.js` to update `cli/napi/generated_symbol_exports_list_*.def`. -pub mod r#async; -pub mod env; pub mod js_native_api; -pub mod threadsafe_functions; +pub mod node_api; pub mod util; diff --git a/cli/napi/node_api.rs b/cli/napi/node_api.rs new file mode 100644 index 0000000000000..28a11d614d3ba --- /dev/null +++ b/cli/napi/node_api.rs @@ -0,0 +1,1020 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +#![deny(unsafe_op_in_unsafe_fn)] + +use super::util::get_array_buffer_ptr; +use super::util::make_external_backing_store; +use super::util::napi_clear_last_error; +use super::util::napi_set_last_error; +use super::util::SendPtr; +use crate::check_arg; +use crate::check_env; +use deno_core::V8CrossThreadTaskSpawner; +use deno_runtime::deno_napi::*; +use napi_sym::napi_sym; +use std::ptr::NonNull; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicU8; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +#[napi_sym] +fn napi_module_register(module: *const NapiModule) -> napi_status { + MODULE_TO_REGISTER.with(|cell| { + let mut slot = cell.borrow_mut(); + let prev = slot.replace(module); + assert!(prev.is_none()); + }); + napi_ok +} + +#[napi_sym] +fn napi_add_env_cleanup_hook( + env: *mut Env, + fun: Option, + arg: *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, fun); + + let fun = fun.unwrap(); + + let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); + if env_cleanup_hooks + .iter() + .any(|pair| pair.0 == fun && pair.1 == arg) + { + panic!("Cleanup hook with this data already registered"); + } + env_cleanup_hooks.push((fun, arg)); + + napi_ok +} + +#[napi_sym] +fn napi_remove_env_cleanup_hook( + env: *mut Env, + fun: Option, + arg: *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, fun); + + let fun = fun.unwrap(); + + let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); + // Hooks are supposed to be removed in LIFO order + let maybe_index = env_cleanup_hooks + .iter() + .rposition(|&pair| pair.0 == fun && pair.1 == arg); + + if let Some(index) = maybe_index { + env_cleanup_hooks.remove(index); + } else { + panic!("Cleanup hook with this data not found"); + } + + napi_ok +} + +type AsyncCleanupHandle = (*mut Env, napi_async_cleanup_hook, *mut c_void); + +unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) { + unsafe { + let (env, hook, arg) = *Box::::from_raw(arg as _); + hook(env as _, arg); + } +} + +#[napi_sym] +fn napi_add_async_cleanup_hook( + env: *mut Env, + hook: Option, + arg: *mut c_void, + remove_handle: *mut napi_async_cleanup_hook_handle, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, hook); + + let hook = hook.unwrap(); + + let handle = + Box::into_raw(Box::::new((env, hook, arg))) as _; + + unsafe { + napi_add_env_cleanup_hook(env, Some(async_cleanup_handler), handle); + } + + if !remove_handle.is_null() { + unsafe { + *remove_handle = handle; + } + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_remove_async_cleanup_hook( + remove_handle: napi_async_cleanup_hook_handle, +) -> napi_status { + if remove_handle.is_null() { + return napi_invalid_arg; + } + + let handle = unsafe { &*(remove_handle as *mut AsyncCleanupHandle) }; + + let env = unsafe { &mut *handle.0 }; + + unsafe { + napi_remove_env_cleanup_hook( + env, + Some(async_cleanup_handler), + remove_handle, + ); + } + + napi_ok +} + +#[napi_sym] +fn napi_fatal_exception(env: &mut Env, err: napi_value) -> napi_status { + check_arg!(env, err); + + let report_error = unsafe { + std::mem::transmute::, v8::Local>( + env.report_error, + ) + }; + + let this = v8::undefined(&mut env.scope()); + if report_error + .call(&mut env.scope(), this.into(), &[err.unwrap()]) + .is_none() + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +#[allow(clippy::print_stderr)] +fn napi_fatal_error( + location: *const c_char, + location_len: usize, + message: *const c_char, + message_len: usize, +) -> napi_status { + let location = if location.is_null() { + None + } else { + unsafe { + Some(if location_len == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(location).to_str().unwrap() + } else { + let slice = std::slice::from_raw_parts( + location as *const u8, + location_len as usize, + ); + std::str::from_utf8(slice).unwrap() + }) + } + }; + + let message = if message_len == NAPI_AUTO_LENGTH { + unsafe { std::ffi::CStr::from_ptr(message).to_str().unwrap() } + } else { + let slice = unsafe { + std::slice::from_raw_parts(message as *const u8, message_len as usize) + }; + std::str::from_utf8(slice).unwrap() + }; + + if let Some(location) = location { + eprintln!("NODE API FATAL ERROR: {} {}", location, message); + } else { + eprintln!("NODE API FATAL ERROR: {}", message); + } + + std::process::abort(); +} + +#[napi_sym] +fn napi_open_callback_scope( + env: *mut Env, + _resource_object: napi_value, + _context: napi_value, + result: *mut napi_callback_scope, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + // we open scope automatically when it's needed + unsafe { + *result = std::ptr::null_mut(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_close_callback_scope( + env: *mut Env, + scope: napi_callback_scope, +) -> napi_status { + let env = check_env!(env); + // we close scope automatically when it's needed + assert!(scope.is_null()); + napi_clear_last_error(env) +} + +// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops. +#[napi_sym] +fn napi_async_init( + env: *mut Env, + _async_resource: napi_value, + _async_resource_name: napi_value, + result: *mut napi_async_context, +) -> napi_status { + let env = check_env!(env); + unsafe { + *result = ptr::null_mut(); + } + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_async_destroy( + env: *mut Env, + async_context: napi_async_context, +) -> napi_status { + let env = check_env!(env); + assert!(async_context.is_null()); + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_make_callback<'s>( + env: &'s mut Env, + _async_context: napi_async_context, + recv: napi_value, + func: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, recv); + if argc > 0 { + check_arg!(env, argv); + } + + let Some(recv) = recv.and_then(|v| v.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let Some(func) = + func.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_function_expected; + }; + + let args = if argc > 0 { + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local, argc) + } + } else { + &[] + }; + + // TODO: async_context + + let Some(v) = func.call(&mut env.scope(), recv.into(), args) else { + return napi_generic_failure; + }; + + unsafe { + *result = v.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_buffer<'s>( + env: &'s mut Env, + length: usize, + data: *mut *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let ab = v8::ArrayBuffer::new(&mut env.scope(), length); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + let Some(buffer) = + buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) + else { + return napi_generic_failure; + }; + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(ab) as _; + } + } + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_external_buffer<'s>( + env: &'s mut Env, + length: usize, + data: *mut c_void, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let store = make_external_backing_store( + env, + data, + length, + ptr::null_mut(), + finalize_cb, + finalize_hint, + ); + + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + let Some(buffer) = + buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) + else { + return napi_generic_failure; + }; + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_buffer_copy<'s>( + env: &'s mut Env, + length: usize, + data: *mut c_void, + result_data: *mut *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let ab = v8::ArrayBuffer::new(&mut env.scope(), length); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + let Some(buffer) = + buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) + else { + return napi_generic_failure; + }; + + let ptr = get_array_buffer_ptr(ab) as *mut c_void; + unsafe { + std::ptr::copy(data, ptr, length); + } + + if !result_data.is_null() { + unsafe { + *result_data = ptr; + } + } + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_is_buffer( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + + let Some(is_buffer) = value + .unwrap() + .instance_of(&mut env.scope(), buffer_constructor.into()) + else { + return napi_set_last_error(env, napi_generic_failure); + }; + + unsafe { + *result = is_buffer; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_buffer_info( + env: *mut Env, + value: napi_value, + data: *mut *mut c_void, + length: *mut usize, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + + let Some(ta) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + + if !ta + .instance_of(&mut env.scope(), buffer_constructor.into()) + .unwrap_or(false) + { + return napi_set_last_error(env, napi_invalid_arg); + } + + if !data.is_null() { + unsafe { + *data = ta.data(); + } + } + + if !length.is_null() { + unsafe { + *length = ta.byte_length(); + } + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_node_version( + env: *mut Env, + result: *mut *const napi_node_version, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + const NODE_VERSION: napi_node_version = napi_node_version { + major: 20, + minor: 11, + patch: 1, + release: c"Deno".as_ptr(), + }; + + unsafe { + *result = &NODE_VERSION as *const napi_node_version; + } + + napi_clear_last_error(env) +} + +struct AsyncWork { + state: AtomicU8, + env: *mut Env, + _async_resource: v8::Global, + _async_resource_name: String, + execute: napi_async_execute_callback, + complete: Option, + data: *mut c_void, +} + +impl AsyncWork { + const IDLE: u8 = 0; + const QUEUED: u8 = 1; + const RUNNING: u8 = 2; +} + +#[napi_sym] +fn napi_create_async_work( + env: *mut Env, + async_resource: napi_value, + async_resource_name: napi_value, + execute: Option, + complete: Option, + data: *mut c_void, + result: *mut napi_async_work, +) -> napi_status { + let env_ptr = env; + let env = check_env!(env); + check_arg!(env, execute); + check_arg!(env, result); + + let resource = if let Some(v) = *async_resource { + let Some(resource) = v.to_object(&mut env.scope()) else { + return napi_set_last_error(env, napi_object_expected); + }; + resource + } else { + v8::Object::new(&mut env.scope()) + }; + + let Some(resource_name) = + async_resource_name.and_then(|v| v.to_string(&mut env.scope())) + else { + return napi_set_last_error(env, napi_string_expected); + }; + + let resource_name = resource_name.to_rust_string_lossy(&mut env.scope()); + + let work = Box::new(AsyncWork { + state: AtomicU8::new(AsyncWork::IDLE), + env: env_ptr, + _async_resource: v8::Global::new(&mut env.scope(), resource), + _async_resource_name: resource_name, + execute: execute.unwrap(), + complete, + data, + }); + + unsafe { + *result = Box::into_raw(work) as _; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_delete_async_work(env: *mut Env, work: napi_async_work) -> napi_status { + let env = check_env!(env); + check_arg!(env, work); + + drop(unsafe { Box::::from_raw(work as _) }); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_uv_event_loop(env: *mut Env, uv_loop: *mut *mut ()) -> napi_status { + let env = check_env!(env); + check_arg!(env, uv_loop); + // There is no uv_loop in Deno + napi_set_last_error(env, napi_generic_failure) +} + +#[napi_sym] +fn napi_queue_async_work(env: *mut Env, work: napi_async_work) -> napi_status { + let env = check_env!(env); + check_arg!(env, work); + + let work = unsafe { &*(work as *mut AsyncWork) }; + + let result = + work + .state + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { + // allow queue if idle or if running, but not if already queued. + if state == AsyncWork::IDLE || state == AsyncWork::RUNNING { + Some(AsyncWork::QUEUED) + } else { + None + } + }); + + if result.is_err() { + return napi_clear_last_error(env); + } + + let work = SendPtr(work); + + env.add_async_work(move || { + let work = work.take(); + let work = unsafe { &*work }; + + let state = work.state.compare_exchange( + AsyncWork::QUEUED, + AsyncWork::RUNNING, + Ordering::SeqCst, + Ordering::SeqCst, + ); + + if state.is_ok() { + unsafe { + (work.execute)(work.env as _, work.data); + } + + // reset back to idle if its still marked as running + let _ = work.state.compare_exchange( + AsyncWork::RUNNING, + AsyncWork::IDLE, + Ordering::SeqCst, + Ordering::Relaxed, + ); + } + + if let Some(complete) = work.complete { + let status = if state.is_ok() { + napi_ok + } else if state == Err(AsyncWork::IDLE) { + napi_cancelled + } else { + napi_generic_failure + }; + + unsafe { + complete(work.env as _, status, work.data); + } + } + + // `complete` probably deletes this `work`, so don't use it here. + }); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_cancel_async_work(env: *mut Env, work: napi_async_work) -> napi_status { + let env = check_env!(env); + check_arg!(env, work); + + let work = unsafe { &*(work as *mut AsyncWork) }; + + let _ = work.state.compare_exchange( + AsyncWork::QUEUED, + AsyncWork::IDLE, + Ordering::SeqCst, + Ordering::Relaxed, + ); + + napi_clear_last_error(env) +} + +extern "C" fn default_call_js_cb( + env: napi_env, + js_callback: napi_value, + _context: *mut c_void, + _data: *mut c_void, +) { + if let Some(js_callback) = *js_callback { + if let Ok(js_callback) = v8::Local::::try_from(js_callback) { + let env = unsafe { &mut *(env as *mut Env) }; + let scope = &mut env.scope(); + let recv = v8::undefined(scope); + js_callback.call(scope, recv.into(), &[]); + } + } +} + +struct TsFn { + env: *mut Env, + func: Option>, + _max_queue_size: usize, + thread_count: AtomicUsize, + thread_finalize_data: *mut c_void, + thread_finalize_cb: Option, + context: *mut c_void, + call_js_cb: napi_threadsafe_function_call_js, + _resource: v8::Global, + _resource_name: String, + is_closing: AtomicBool, + is_closed: Arc, + sender: V8CrossThreadTaskSpawner, + is_ref: AtomicBool, +} + +impl Drop for TsFn { + fn drop(&mut self) { + assert!(self + .is_closed + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok()); + + self.unref(); + + if let Some(finalizer) = self.thread_finalize_cb { + unsafe { + (finalizer)(self.env as _, self.thread_finalize_data, ptr::null_mut()); + } + } + } +} + +impl TsFn { + pub fn acquire(&self) -> napi_status { + if self.is_closing.load(Ordering::SeqCst) { + return napi_closing; + } + self.thread_count.fetch_add(1, Ordering::Relaxed); + napi_ok + } + + pub fn release( + tsfn: *mut TsFn, + mode: napi_threadsafe_function_release_mode, + ) -> napi_status { + let tsfn = unsafe { &mut *tsfn }; + + let result = tsfn.thread_count.fetch_update( + Ordering::Relaxed, + Ordering::Relaxed, + |x| { + if x == 0 { + None + } else { + Some(x - 1) + } + }, + ); + + if result.is_err() { + return napi_invalid_arg; + } + + if (result == Ok(1) || mode == napi_tsfn_abort) + && tsfn + .is_closing + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + let tsfnptr = SendPtr(tsfn); + // drop must be queued in order to preserve ordering consistent + // with Node.js and so that the finalizer runs on the main thread. + tsfn.sender.spawn(move |_| { + let tsfn = unsafe { Box::from_raw(tsfnptr.take() as *mut TsFn) }; + drop(tsfn); + }); + } + + napi_ok + } + + pub fn ref_(&self) -> napi_status { + if self + .is_ref + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + let env = unsafe { &mut *self.env }; + env.threadsafe_function_ref(); + } + napi_ok + } + + pub fn unref(&self) -> napi_status { + if self + .is_ref + .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + let env = unsafe { &mut *self.env }; + env.threadsafe_function_unref(); + } + + napi_ok + } + + pub fn call( + &self, + data: *mut c_void, + _mode: napi_threadsafe_function_call_mode, + ) -> napi_status { + // TODO: + // if self.max_queue_size > 0 && queued >= self.max_queue_size { + // if mode == napi_tsfn_blocking { + // wait somehow + // } else { + // return napi_queue_full; + // } + // } + + if self.is_closing.load(Ordering::SeqCst) { + return napi_closing; + } + + let is_closed = self.is_closed.clone(); + let tsfn = SendPtr(self); + let data = SendPtr(data); + let context = SendPtr(self.context); + let call_js_cb = self.call_js_cb; + self.sender.spawn(move |scope: &mut v8::HandleScope| { + let data = data.take(); + + // if is_closed then tsfn is freed, don't read from it. + if is_closed.load(Ordering::Relaxed) { + unsafe { + call_js_cb( + std::ptr::null_mut(), + None::>.into(), + context.take() as _, + data as _, + ); + } + } else { + let tsfn = tsfn.take(); + + let tsfn = unsafe { &*tsfn }; + + let func = tsfn.func.as_ref().map(|f| v8::Local::new(scope, f)); + + unsafe { + (tsfn.call_js_cb)( + tsfn.env as _, + func.into(), + tsfn.context, + data as _, + ); + } + } + }); + + napi_ok + } +} + +#[napi_sym] +#[allow(clippy::too_many_arguments)] +fn napi_create_threadsafe_function( + env: *mut Env, + func: napi_value, + async_resource: napi_value, + async_resource_name: napi_value, + max_queue_size: usize, + initial_thread_count: usize, + thread_finalize_data: *mut c_void, + thread_finalize_cb: Option, + context: *mut c_void, + call_js_cb: Option, + result: *mut napi_threadsafe_function, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, async_resource_name); + if initial_thread_count == 0 { + return napi_set_last_error(env, napi_invalid_arg); + } + check_arg!(env, result); + + let func = if let Some(value) = *func { + let Ok(func) = v8::Local::::try_from(value) else { + return napi_set_last_error(env, napi_function_expected); + }; + Some(v8::Global::new(&mut env.scope(), func)) + } else { + check_arg!(env, call_js_cb); + None + }; + + let resource = if let Some(v) = *async_resource { + let Some(resource) = v.to_object(&mut env.scope()) else { + return napi_set_last_error(env, napi_object_expected); + }; + resource + } else { + v8::Object::new(&mut env.scope()) + }; + let resource = v8::Global::new(&mut env.scope(), resource); + + let Some(resource_name) = + async_resource_name.and_then(|v| v.to_string(&mut env.scope())) + else { + return napi_set_last_error(env, napi_string_expected); + }; + let resource_name = resource_name.to_rust_string_lossy(&mut env.scope()); + + let mut tsfn = Box::new(TsFn { + env, + func, + _max_queue_size: max_queue_size, + thread_count: AtomicUsize::new(initial_thread_count), + thread_finalize_data, + thread_finalize_cb, + context, + call_js_cb: call_js_cb.unwrap_or(default_call_js_cb), + _resource: resource, + _resource_name: resource_name, + is_closing: AtomicBool::new(false), + is_closed: Arc::new(AtomicBool::new(false)), + is_ref: AtomicBool::new(false), + sender: env.async_work_sender.clone(), + }); + + tsfn.ref_(); + + unsafe { + *result = Box::into_raw(tsfn) as _; + } + + napi_clear_last_error(env) +} + +/// Maybe called from any thread. +#[napi_sym] +fn napi_get_threadsafe_function_context( + func: napi_threadsafe_function, + result: *mut *const c_void, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *const TsFn) }; + unsafe { + *result = tsfn.context; + } + napi_ok +} + +#[napi_sym] +fn napi_call_threadsafe_function( + func: napi_threadsafe_function, + data: *mut c_void, + is_blocking: napi_threadsafe_function_call_mode, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *mut TsFn) }; + tsfn.call(data, is_blocking) +} + +#[napi_sym] +fn napi_acquire_threadsafe_function( + tsfn: napi_threadsafe_function, +) -> napi_status { + assert!(!tsfn.is_null()); + let tsfn = unsafe { &*(tsfn as *mut TsFn) }; + tsfn.acquire() +} + +#[napi_sym] +fn napi_release_threadsafe_function( + tsfn: napi_threadsafe_function, + mode: napi_threadsafe_function_release_mode, +) -> napi_status { + assert!(!tsfn.is_null()); + TsFn::release(tsfn as _, mode) +} + +#[napi_sym] +fn napi_unref_threadsafe_function( + _env: &mut Env, + func: napi_threadsafe_function, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *mut TsFn) }; + tsfn.unref() +} + +#[napi_sym] +fn napi_ref_threadsafe_function( + _env: &mut Env, + func: napi_threadsafe_function, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *mut TsFn) }; + tsfn.ref_() +} + +#[napi_sym] +fn node_api_get_module_file_name( + env: *mut Env, + result: *mut *const c_char, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = env.shared().filename.as_ptr() as _; + } + + napi_clear_last_error(env) +} diff --git a/cli/napi/sym/lib.rs b/cli/napi/sym/lib.rs index 33f039b5fdff7..e2826306b91ca 100644 --- a/cli/napi/sym/lib.rs +++ b/cli/napi/sym/lib.rs @@ -20,17 +20,12 @@ pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream { let name = &func.sig.ident; assert!( exports.symbols.contains(&name.to_string()), - "tools/napi/sym/symbol_exports.json is out of sync!" + "cli/napi/sym/symbol_exports.json is out of sync!" ); - let block = &func.block; - let inputs = &func.sig.inputs; - let generics = &func.sig.generics; TokenStream::from(quote! { - // SAFETY: it's an NAPI function. - #[no_mangle] - pub unsafe extern "C" fn #name #generics (#inputs) -> napi_status { - #block - } + crate::napi_wrap! { + #func + } }) } diff --git a/cli/napi/sym/symbol_exports.json b/cli/napi/sym/symbol_exports.json index ba1bba67a64f4..64b548d496a0d 100644 --- a/cli/napi/sym/symbol_exports.json +++ b/cli/napi/sym/symbol_exports.json @@ -36,6 +36,7 @@ "napi_get_threadsafe_function_context", "napi_get_null", "napi_create_string_utf16", + "node_api_create_external_string_utf16", "napi_get_value_bigint_uint64", "napi_module_register", "napi_is_typedarray", @@ -76,6 +77,7 @@ "napi_release_threadsafe_function", "napi_delete_async_work", "napi_create_string_latin1", + "node_api_create_external_string_latin1", "napi_is_array", "napi_unref_threadsafe_function", "napi_throw_error", @@ -139,10 +141,17 @@ "napi_has_element", "napi_create_external_arraybuffer", "napi_create_symbol", + "node_api_symbol_for", "napi_coerce_to_string", "napi_create_type_error", "napi_fatal_exception", "napi_create_async_work", - "napi_async_init" + "napi_async_init", + "node_api_create_property_key_utf16", + "napi_type_tag_object", + "napi_check_object_type_tag", + "node_api_post_finalizer", + "napi_add_async_cleanup_hook", + "napi_remove_async_cleanup_hook" ] } diff --git a/cli/napi/threadsafe_functions.rs b/cli/napi/threadsafe_functions.rs deleted file mode 100644 index 2ab4886276b44..0000000000000 --- a/cli/napi/threadsafe_functions.rs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_core::futures::channel::mpsc; -use deno_core::V8CrossThreadTaskSpawner; -use deno_runtime::deno_napi::*; -use once_cell::sync::Lazy; -use std::mem::forget; -use std::ptr::NonNull; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; - -#[repr(transparent)] -pub struct SendPtr(pub *const T); - -unsafe impl Send for SendPtr {} -unsafe impl Sync for SendPtr {} - -static TS_FN_ID_COUNTER: Lazy = Lazy::new(|| AtomicUsize::new(0)); - -pub struct TsFn { - pub id: usize, - pub env: *mut Env, - pub maybe_func: Option>, - pub maybe_call_js_cb: Option, - pub context: *mut c_void, - pub thread_counter: usize, - pub ref_counter: Arc, - finalizer: Option, - finalizer_data: *mut c_void, - sender: V8CrossThreadTaskSpawner, - tsfn_sender: mpsc::UnboundedSender, -} - -impl Drop for TsFn { - fn drop(&mut self) { - let env = unsafe { self.env.as_mut().unwrap() }; - env.remove_threadsafe_function_ref_counter(self.id); - if let Some(finalizer) = self.finalizer { - unsafe { - (finalizer)(self.env as _, self.finalizer_data, ptr::null_mut()); - } - } - } -} - -impl TsFn { - pub fn acquire(&mut self) -> napi_status { - self.thread_counter += 1; - napi_ok - } - - pub fn release(mut self) -> napi_status { - self.thread_counter -= 1; - if self.thread_counter == 0 { - if self - .tsfn_sender - .unbounded_send(ThreadSafeFunctionStatus::Dead) - .is_err() - { - return napi_generic_failure; - } - drop(self); - } else { - forget(self); - } - napi_ok - } - - pub fn ref_(&mut self) -> napi_status { - self - .ref_counter - .fetch_add(1, std::sync::atomic::Ordering::SeqCst); - napi_ok - } - - pub fn unref(&mut self) -> napi_status { - let _ = self.ref_counter.fetch_update( - std::sync::atomic::Ordering::SeqCst, - std::sync::atomic::Ordering::SeqCst, - |x| { - if x == 0 { - None - } else { - Some(x - 1) - } - }, - ); - - napi_ok - } - - pub fn call(&self, data: *mut c_void, is_blocking: bool) { - let js_func = self.maybe_func.clone(); - - let env = SendPtr(self.env); - let context = SendPtr(self.context); - let data = SendPtr(data); - - #[inline(always)] - fn spawn( - sender: &V8CrossThreadTaskSpawner, - is_blocking: bool, - f: impl FnOnce(&mut v8::HandleScope) + Send + 'static, - ) { - if is_blocking { - sender.spawn_blocking(f); - } else { - sender.spawn(f); - } - } - - if let Some(call_js_cb) = self.maybe_call_js_cb { - if let Some(func) = js_func { - let func = SendPtr(func.into_raw().as_ptr()); - #[inline(always)] - fn call( - scope: &mut v8::HandleScope, - call_js_cb: napi_threadsafe_function_call_js, - func: SendPtr, - env: SendPtr, - context: SendPtr, - data: SendPtr, - ) { - // SAFETY: This is a valid global from above - let func: v8::Global = unsafe { - v8::Global::::from_raw( - scope, - NonNull::new_unchecked(func.0 as _), - ) - }; - let func: v8::Local = - func.open(scope).to_object(scope).unwrap().into(); - // SAFETY: env is valid for the duration of the callback. - // data lifetime is users responsibility. - unsafe { - call_js_cb(env.0 as _, func.into(), context.0 as _, data.0 as _) - } - } - spawn(&self.sender, is_blocking, move |scope| { - call(scope, call_js_cb, func, env, context, data); - }); - } else { - #[inline(always)] - fn call( - call_js_cb: napi_threadsafe_function_call_js, - env: SendPtr, - context: SendPtr, - data: SendPtr, - ) { - // SAFETY: env is valid for the duration of the callback. - // data lifetime is users responsibility. - unsafe { - call_js_cb( - env.0 as _, - std::mem::zeroed(), - context.0 as _, - data.0 as _, - ) - } - } - spawn(&self.sender, is_blocking, move |_| { - call(call_js_cb, env, context, data); - }); - } - } else { - spawn(&self.sender, is_blocking, |_| { - // TODO: func.call - }); - }; - } -} - -#[napi_sym::napi_sym] -fn napi_create_threadsafe_function( - env: *mut Env, - func: napi_value, - _async_resource: napi_value, - _async_resource_name: napi_value, - _max_queue_size: usize, - initial_thread_count: usize, - thread_finalize_data: *mut c_void, - thread_finalize_cb: Option, - context: *mut c_void, - maybe_call_js_cb: Option, - result: *mut napi_threadsafe_function, -) -> napi_status { - let Some(env_ref) = env.as_mut() else { - return napi_generic_failure; - }; - if initial_thread_count == 0 { - return napi_invalid_arg; - } - - let mut maybe_func = None; - - if let Some(value) = *func { - let Ok(func) = v8::Local::::try_from(value) else { - return napi_function_expected; - }; - maybe_func = Some(v8::Global::new(&mut env_ref.scope(), func)); - } - - let id = TS_FN_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - - let tsfn = TsFn { - id, - maybe_func, - maybe_call_js_cb, - context, - thread_counter: initial_thread_count, - sender: env_ref.async_work_sender.clone(), - finalizer: thread_finalize_cb, - finalizer_data: thread_finalize_data, - tsfn_sender: env_ref.threadsafe_function_sender.clone(), - ref_counter: Arc::new(AtomicUsize::new(1)), - env, - }; - - env_ref - .add_threadsafe_function_ref_counter(tsfn.id, tsfn.ref_counter.clone()); - - if env_ref - .threadsafe_function_sender - .unbounded_send(ThreadSafeFunctionStatus::Alive) - .is_err() - { - return napi_generic_failure; - } - *result = transmute::, _>(Box::new(tsfn)); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_acquire_threadsafe_function( - tsfn: napi_threadsafe_function, - _mode: napi_threadsafe_function_release_mode, -) -> napi_status { - let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn); - tsfn.acquire() -} - -#[napi_sym::napi_sym] -fn napi_unref_threadsafe_function( - _env: &mut Env, - tsfn: napi_threadsafe_function, -) -> napi_status { - let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn); - tsfn.unref() -} - -/// Maybe called from any thread. -#[napi_sym::napi_sym] -pub fn napi_get_threadsafe_function_context( - func: napi_threadsafe_function, - result: *mut *const c_void, -) -> napi_status { - let tsfn: &TsFn = &*(func as *const TsFn); - *result = tsfn.context; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_call_threadsafe_function( - func: napi_threadsafe_function, - data: *mut c_void, - is_blocking: napi_threadsafe_function_call_mode, -) -> napi_status { - let tsfn: &TsFn = &*(func as *const TsFn); - tsfn.call(data, is_blocking != 0); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_ref_threadsafe_function( - _env: &mut Env, - func: napi_threadsafe_function, -) -> napi_status { - let tsfn: &mut TsFn = &mut *(func as *mut TsFn); - tsfn.ref_() -} - -#[napi_sym::napi_sym] -fn napi_release_threadsafe_function( - tsfn: napi_threadsafe_function, - _mode: napi_threadsafe_function_release_mode, -) -> napi_status { - let tsfn: Box = Box::from_raw(tsfn as *mut TsFn); - tsfn.release() -} diff --git a/cli/napi/util.rs b/cli/napi/util.rs index edf109460f509..cd3ef14a2d6e3 100644 --- a/cli/napi/util.rs +++ b/cli/napi/util.rs @@ -1,8 +1,292 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_runtime::deno_napi::*; +use libc::INT_MAX; + +#[repr(transparent)] +pub struct SendPtr(pub *const T); + +impl SendPtr { + // silly function to get around `clippy::redundant_locals` + pub fn take(self) -> *const T { + self.0 + } +} + +unsafe impl Send for SendPtr {} +unsafe impl Sync for SendPtr {} pub fn get_array_buffer_ptr(ab: v8::Local) -> *mut u8 { // SAFETY: Thanks to the null pointer optimization, NonNull and Option> are guaranteed // to have the same size and alignment. unsafe { std::mem::transmute(ab.data()) } } + +struct BufferFinalizer { + env: *mut Env, + finalize_cb: napi_finalize, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, +} + +impl Drop for BufferFinalizer { + fn drop(&mut self) { + unsafe { + (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint); + } + } +} + +pub extern "C" fn backing_store_deleter_callback( + data: *mut c_void, + _byte_length: usize, + deleter_data: *mut c_void, +) { + let mut finalizer = + unsafe { Box::::from_raw(deleter_data as _) }; + + finalizer.finalize_data = data; + + drop(finalizer); +} + +pub fn make_external_backing_store( + env: *mut Env, + data: *mut c_void, + byte_length: usize, + finalize_data: *mut c_void, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, +) -> v8::UniqueRef { + let finalizer = Box::new(BufferFinalizer { + env, + finalize_data, + finalize_cb, + finalize_hint, + }); + + unsafe { + v8::ArrayBuffer::new_backing_store_from_ptr( + data, + byte_length, + backing_store_deleter_callback, + Box::into_raw(finalizer) as _, + ) + } +} + +#[macro_export] +macro_rules! check_env { + ($env: expr) => {{ + let env = $env; + if env.is_null() { + return napi_invalid_arg; + } + unsafe { &mut *env } + }}; +} + +#[macro_export] +macro_rules! return_error_status_if_false { + ($env: expr, $condition: expr, $status: ident) => { + if !$condition { + return Err( + $crate::napi::util::napi_set_last_error($env, $status).into(), + ); + } + }; +} + +#[macro_export] +macro_rules! return_status_if_false { + ($env: expr, $condition: expr, $status: ident) => { + if !$condition { + return $crate::napi::util::napi_set_last_error($env, $status); + } + }; +} + +pub(crate) unsafe fn check_new_from_utf8_len<'s>( + env: *mut Env, + str_: *const c_char, + len: usize, +) -> Result, napi_status> { + let env = unsafe { &mut *env }; + return_error_status_if_false!( + env, + (len == NAPI_AUTO_LENGTH) || len <= INT_MAX as _, + napi_invalid_arg + ); + return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg); + let string = if len == NAPI_AUTO_LENGTH { + let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str(); + return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); + result.unwrap() + } else { + let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) }; + let result = std::str::from_utf8(string); + return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); + result.unwrap() + }; + let result = { + let env = unsafe { &mut *(env as *mut Env) }; + v8::String::new(&mut env.scope(), string) + }; + return_error_status_if_false!(env, result.is_some(), napi_generic_failure); + Ok(result.unwrap()) +} + +#[inline] +pub(crate) unsafe fn check_new_from_utf8<'s>( + env: *mut Env, + str_: *const c_char, +) -> Result, napi_status> { + unsafe { check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH) } +} + +pub(crate) unsafe fn v8_name_from_property_descriptor<'s>( + env: *mut Env, + p: &'s napi_property_descriptor, +) -> Result, napi_status> { + if !p.utf8name.is_null() { + unsafe { check_new_from_utf8(env, p.utf8name).map(|v| v.into()) } + } else { + match *p.name { + Some(v) => match v.try_into() { + Ok(name) => Ok(name), + Err(_) => Err(napi_name_expected), + }, + None => Err(napi_name_expected), + } + } +} + +pub(crate) fn napi_clear_last_error(env: *mut Env) -> napi_status { + let env = unsafe { &mut *env }; + env.last_error.error_code = napi_ok; + env.last_error.engine_error_code = 0; + env.last_error.engine_reserved = std::ptr::null_mut(); + env.last_error.error_message = std::ptr::null_mut(); + napi_ok +} + +pub(crate) fn napi_set_last_error( + env: *mut Env, + error_code: napi_status, +) -> napi_status { + let env = unsafe { &mut *env }; + env.last_error.error_code = error_code; + error_code +} + +#[macro_export] +macro_rules! status_call { + ($call: expr) => { + let status = $call; + if status != napi_ok { + return status; + } + }; +} + +pub trait Nullable { + fn is_null(&self) -> bool; +} + +impl Nullable for *mut T { + fn is_null(&self) -> bool { + (*self).is_null() + } +} + +impl Nullable for *const T { + fn is_null(&self) -> bool { + (*self).is_null() + } +} + +impl Nullable for Option { + fn is_null(&self) -> bool { + self.is_none() + } +} + +impl<'s> Nullable for napi_value<'s> { + fn is_null(&self) -> bool { + self.is_none() + } +} + +// TODO: replace Nullable with some sort of "CheckedUnwrap" trait +// *mut T -> &mut MaybeUninit +// Option -> T +// napi_value -> Local +#[macro_export] +macro_rules! check_arg { + ($env: expr, $ptr: expr) => { + $crate::return_status_if_false!( + $env, + !$crate::napi::util::Nullable::is_null(&$ptr), + napi_invalid_arg + ); + }; +} + +#[macro_export] +macro_rules! napi_wrap { + ( $( # $attr:tt )* fn $name:ident $( < $( $x:lifetime ),* > )? ( $env:ident : & $( $lt:lifetime )? mut Env $( , $ident:ident : $ty:ty )* $(,)? ) -> napi_status $body:block ) => { + $( # $attr )* + #[no_mangle] + pub unsafe extern "C" fn $name $( < $( $x ),* > )? ( env_ptr : *mut Env , $( $ident : $ty ),* ) -> napi_status { + let env: & $( $lt )? mut Env = $crate::check_env!(env_ptr); + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + $crate::napi::util::napi_clear_last_error(env); + + let scope_env = unsafe { &mut *env_ptr }; + let scope = &mut scope_env.scope(); + let try_catch = &mut v8::TryCatch::new(scope); + + #[inline(always)] + fn inner $( < $( $x ),* > )? ( $env: & $( $lt )? mut Env , $( $ident : $ty ),* ) -> napi_status $body + + log::trace!("NAPI ENTER: {}", stringify!($name)); + + let result = inner( env, $( $ident ),* ); + + log::trace!("NAPI EXIT: {} {}", stringify!($name), result); + + if let Some(exception) = try_catch.exception() { + let env = unsafe { &mut *env_ptr }; + let global = v8::Global::new(env.isolate(), exception); + env.last_exception = Some(global); + return $crate::napi::util::napi_set_last_error(env_ptr, napi_pending_exception); + } + + if result != napi_ok { + return $crate::napi::util::napi_set_last_error(env_ptr, result); + } + + return result; + } + }; + + ( $( # $attr:tt )* fn $name:ident $( < $( $x:lifetime ),* > )? ( $( $ident:ident : $ty:ty ),* $(,)? ) -> napi_status $body:block ) => { + $( # $attr )* + #[no_mangle] + pub unsafe extern "C" fn $name $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status { + #[inline(always)] + fn inner $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status $body + + log::trace!("NAPI ENTER: {}", stringify!($name)); + + let result = inner( $( $ident ),* ); + + log::trace!("NAPI EXIT: {} {}", stringify!($name), result); + + result + } + }; +} diff --git a/ext/napi/function.rs b/ext/napi/function.rs index 5cc23dcd0fcad..2d2933b954094 100644 --- a/ext/napi/function.rs +++ b/ext/napi/function.rs @@ -27,9 +27,10 @@ impl CallbackInfo { } extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) { - let info = unsafe { &*info }; - let args = v8::FunctionCallbackArguments::from_function_callback_info(info); - let mut rv = v8::ReturnValue::from_function_callback_info(info); + let callback_info = unsafe { &*info }; + let args = + v8::FunctionCallbackArguments::from_function_callback_info(callback_info); + let mut rv = v8::ReturnValue::from_function_callback_info(callback_info); // SAFETY: create_function guarantees that the data is a CallbackInfo external. let info_ptr: *mut CallbackInfo = unsafe { let external_value = v8::Local::::cast(args.data()); @@ -43,19 +44,29 @@ extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) { if let Some(f) = info.cb { // SAFETY: calling user provided function pointer. let value = unsafe { f(info.env, info_ptr as *mut _) }; - // SAFETY: napi_value is represented as v8::Local internally. - rv.set(unsafe { transmute::>(value) }); + if let Some(exc) = unsafe { &mut *(info.env as *mut Env) } + .last_exception + .take() + { + let scope = unsafe { &mut v8::CallbackScope::new(callback_info) }; + let exc = v8::Local::new(scope, exc); + scope.throw_exception(exc); + } + if let Some(value) = *value { + rv.set(value); + } } } -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn create_function<'a>( +/// # Safety +/// env_ptr must be valid +pub unsafe fn create_function<'a>( env_ptr: *mut Env, name: Option>, cb: napi_callback, cb_info: napi_callback_info, ) -> v8::Local<'a, v8::Function> { - let env: &mut Env = unsafe { &mut *env_ptr }; + let env = unsafe { &mut *env_ptr }; let scope = &mut env.scope(); let external = v8::External::new( @@ -74,14 +85,15 @@ pub fn create_function<'a>( function } -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn create_function_template<'a>( +/// # Safety +/// env_ptr must be valid +pub unsafe fn create_function_template<'a>( env_ptr: *mut Env, - name: Option<&str>, + name: Option>, cb: napi_callback, cb_info: napi_callback_info, ) -> v8::Local<'a, v8::FunctionTemplate> { - let env: &mut Env = unsafe { &mut *env_ptr }; + let env = unsafe { &mut *env_ptr }; let scope = &mut env.scope(); let external = v8::External::new( @@ -92,8 +104,7 @@ pub fn create_function_template<'a>( .data(external.into()) .build(scope); - if let Some(name) = name { - let v8str = v8::String::new(scope, name).unwrap(); + if let Some(v8str) = name { function.set_class_name(v8str); } diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs index f4fa33438fd7d..39b303f8603a0 100644 --- a/ext/napi/lib.rs +++ b/ext/napi/lib.rs @@ -8,18 +8,14 @@ use core::ptr::NonNull; use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::futures::channel::mpsc; use deno_core::op2; -use deno_core::parking_lot::Mutex; +use deno_core::ExternalOpsTracker; use deno_core::OpState; use deno_core::V8CrossThreadTaskSpawner; use std::cell::RefCell; -use std::ffi::CString; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; use std::thread_local; #[cfg(unix)] @@ -32,7 +28,6 @@ use libloading::os::windows::*; // `use deno_napi::*` pub use deno_core::v8; pub use std::ffi::CStr; -pub use std::mem::transmute; pub use std::os::raw::c_char; pub use std::os::raw::c_void; pub use std::ptr; @@ -52,6 +47,7 @@ pub type napi_callback_scope = *mut c_void; pub type napi_escapable_handle_scope = *mut c_void; pub type napi_async_cleanup_hook_handle = *mut c_void; pub type napi_async_work = *mut c_void; +pub type napi_async_context = *mut c_void; pub const napi_ok: napi_status = 0; pub const napi_invalid_arg: napi_status = 1; @@ -75,6 +71,35 @@ pub const napi_date_expected: napi_status = 18; pub const napi_arraybuffer_expected: napi_status = 19; pub const napi_detachable_arraybuffer_expected: napi_status = 20; pub const napi_would_deadlock: napi_status = 21; +pub const napi_no_external_buffers_allowed: napi_status = 22; +pub const napi_cannot_run_js: napi_status = 23; + +pub static ERROR_MESSAGES: &[&CStr] = &[ + c"", + c"Invalid argument", + c"An object was expected", + c"A string was expected", + c"A string or symbol was expected", + c"A function was expected", + c"A number was expected", + c"A boolean was expected", + c"An array was expected", + c"Unknown failure", + c"An exception is pending", + c"The async work item was cancelled", + c"napi_escape_handle already called on scope", + c"Invalid handle scope usage", + c"Invalid callback scope usage", + c"Thread-safe function queue is full", + c"Thread-safe function handle is closing", + c"A bigint was expected", + c"A date was expected", + c"An arraybuffer was expected", + c"A detachable arraybuffer was expected", + c"Main thread would deadlock", + c"External buffers are not allowed", + c"Cannot run JavaScript", +]; pub const NAPI_AUTO_LENGTH: usize = usize::MAX; @@ -83,7 +108,9 @@ thread_local! { } type napi_addon_register_func = - extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; + unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; +type napi_register_module_v1 = + unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; #[repr(C)] #[derive(Clone)] @@ -113,7 +140,7 @@ pub const napi_bigint: napi_valuetype = 9; pub type napi_threadsafe_function_release_mode = i32; pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0; -pub const napi_tsfn_abortext: napi_threadsafe_function_release_mode = 1; +pub const napi_tsfn_abort: napi_threadsafe_function_release_mode = 1; pub type napi_threadsafe_function_call_mode = i32; @@ -153,6 +180,8 @@ pub const napi_float64_array: napi_typedarray_type = 8; pub const napi_bigint64_array: napi_typedarray_type = 9; pub const napi_biguint64_array: napi_typedarray_type = 10; +#[repr(C)] +#[derive(Clone, Copy, PartialEq)] pub struct napi_type_tag { pub lower: u64, pub upper: u64, @@ -187,6 +216,8 @@ pub type napi_threadsafe_function_call_js = unsafe extern "C" fn( pub type napi_async_cleanup_hook = unsafe extern "C" fn(env: napi_env, data: *mut c_void); +pub type napi_cleanup_hook = unsafe extern "C" fn(data: *mut c_void); + pub type napi_property_attributes = i32; pub const napi_default: napi_property_attributes = 0; @@ -233,17 +264,9 @@ pub struct napi_node_version { pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {} impl PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {} -pub type ThreadsafeFunctionRefCounters = Vec<(usize, Arc)>; pub struct NapiState { // Thread safe functions. - pub active_threadsafe_functions: usize, - pub threadsafe_function_receiver: - mpsc::UnboundedReceiver, - pub threadsafe_function_sender: - mpsc::UnboundedSender, - pub env_cleanup_hooks: - Rc>>, - pub tsfn_ref_counters: Arc>, + pub env_cleanup_hooks: Rc>>, } impl Drop for NapiState { @@ -267,7 +290,10 @@ impl Drop for NapiState { continue; } - (hook.0)(hook.1); + unsafe { + (hook.0)(hook.1); + } + { self .env_cleanup_hooks @@ -277,38 +303,44 @@ impl Drop for NapiState { } } } + +#[repr(C)] +#[derive(Debug)] +pub struct InstanceData { + pub data: *mut c_void, + pub finalize_cb: Option, + pub finalize_hint: *mut c_void, +} + #[repr(C)] #[derive(Debug)] /// Env that is shared between all contexts in same native module. pub struct EnvShared { - pub instance_data: *mut c_void, - pub data_finalize: Option, - pub data_finalize_hint: *mut c_void, + pub instance_data: Option, pub napi_wrap: v8::Global, + pub type_tag: v8::Global, pub finalize: Option, pub finalize_hint: *mut c_void, - pub filename: *const c_char, + pub filename: String, } impl EnvShared { - pub fn new(napi_wrap: v8::Global) -> Self { + pub fn new( + napi_wrap: v8::Global, + type_tag: v8::Global, + filename: String, + ) -> Self { Self { - instance_data: std::ptr::null_mut(), - data_finalize: None, - data_finalize_hint: std::ptr::null_mut(), + instance_data: None, napi_wrap, + type_tag, finalize: None, finalize_hint: std::ptr::null_mut(), - filename: std::ptr::null(), + filename, } } } -pub enum ThreadSafeFunctionStatus { - Alive, - Dead, -} - #[repr(C)] pub struct Env { context: NonNull, @@ -316,46 +348,48 @@ pub struct Env { pub open_handle_scopes: usize, pub shared: *mut EnvShared, pub async_work_sender: V8CrossThreadTaskSpawner, - pub threadsafe_function_sender: - mpsc::UnboundedSender, - pub cleanup_hooks: - Rc>>, - pub tsfn_ref_counters: Arc>, + pub cleanup_hooks: Rc>>, + pub external_ops_tracker: ExternalOpsTracker, pub last_error: napi_extended_error_info, + pub last_exception: Option>, pub global: NonNull, + pub buffer_constructor: NonNull, + pub report_error: NonNull, } unsafe impl Send for Env {} unsafe impl Sync for Env {} impl Env { + #[allow(clippy::too_many_arguments)] pub fn new( isolate_ptr: *mut v8::OwnedIsolate, context: v8::Global, global: v8::Global, + buffer_constructor: v8::Global, + report_error: v8::Global, sender: V8CrossThreadTaskSpawner, - threadsafe_function_sender: mpsc::UnboundedSender, - cleanup_hooks: Rc< - RefCell>, - >, - tsfn_ref_counters: Arc>, + cleanup_hooks: Rc>>, + external_ops_tracker: ExternalOpsTracker, ) -> Self { Self { isolate_ptr, context: context.into_raw(), global: global.into_raw(), + buffer_constructor: buffer_constructor.into_raw(), + report_error: report_error.into_raw(), shared: std::ptr::null_mut(), open_handle_scopes: 0, async_work_sender: sender, - threadsafe_function_sender, cleanup_hooks, - tsfn_ref_counters, + external_ops_tracker, last_error: napi_extended_error_info { error_message: std::ptr::null(), engine_reserved: std::ptr::null_mut(), engine_error_code: 0, error_code: napi_ok, }, + last_exception: None, } } @@ -384,7 +418,9 @@ impl Env { // SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is // already on the stack, but we don't have access to it. let context = unsafe { - transmute::, v8::Local>(self.context) + std::mem::transmute::, v8::Local>( + self.context, + ) }; // SAFETY: there must be a `HandleScope` on the stack, this is ensured because // we are in a V8 callback or the module has already opened a `HandleScope` @@ -392,20 +428,12 @@ impl Env { unsafe { v8::CallbackScope::new(context) } } - pub fn add_threadsafe_function_ref_counter( - &mut self, - id: usize, - counter: Arc, - ) { - let mut counters = self.tsfn_ref_counters.lock(); - assert!(!counters.iter().any(|(i, _)| *i == id)); - counters.push((id, counter)); + pub fn threadsafe_function_ref(&mut self) { + self.external_ops_tracker.ref_op(); } - pub fn remove_threadsafe_function_ref_counter(&mut self, id: usize) { - let mut counters = self.tsfn_ref_counters.lock(); - let index = counters.iter().position(|(i, _)| *i == id).unwrap(); - counters.remove(index); + pub fn threadsafe_function_unref(&mut self) { + self.external_ops_tracker.unref_op(); } } @@ -415,14 +443,8 @@ deno_core::extension!(deno_napi, op_napi_open

], state = |state| { - let (threadsafe_function_sender, threadsafe_function_receiver) = - mpsc::unbounded::(); state.put(NapiState { - threadsafe_function_sender, - threadsafe_function_receiver, - active_threadsafe_functions: 0, env_cleanup_hooks: Rc::new(RefCell::new(vec![])), - tsfn_ref_counters: Arc::new(Mutex::new(vec![])), }); }, ); @@ -441,69 +463,21 @@ impl NapiPermissions for deno_permissions::PermissionsContainer { } } -/// # Safety -/// -/// This function is unsafe because it dereferences raw pointer Env. -/// - The caller must ensure that the pointer is valid. -/// - The caller must ensure that the pointer is not freed. -pub unsafe fn weak_local( - env_ptr: *mut Env, - value: v8::Local, - data: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, -) -> Option> { - use std::cell::Cell; - - let env = &mut *env_ptr; - - let weak_ptr = Rc::new(Cell::new(None)); - let scope = &mut env.scope(); - - let weak = v8::Weak::with_finalizer( - scope, - value, - Box::new({ - let weak_ptr = weak_ptr.clone(); - move |isolate| { - finalize_cb(env_ptr as _, data as _, finalize_hint as _); - - // Self-deleting weak. - if let Some(weak_ptr) = weak_ptr.get() { - let weak: v8::Weak = - unsafe { v8::Weak::from_raw(isolate, Some(weak_ptr)) }; - drop(weak); - } - } - }), - ); - - let value = weak.to_local(scope); - let raw = weak.into_raw(); - weak_ptr.set(raw); - - value -} - -#[op2] +#[op2(reentrant)] fn op_napi_open( scope: &mut v8::HandleScope<'scope>, op_state: Rc>, #[string] path: String, global: v8::Local<'scope, v8::Value>, + buffer_constructor: v8::Local<'scope, v8::Function>, + report_error: v8::Local<'scope, v8::Function>, ) -> std::result::Result, AnyError> where NP: NapiPermissions + 'static, { // We must limit the OpState borrow because this function can trigger a // re-borrow through the NAPI module. - let ( - async_work_sender, - tsfn_sender, - isolate_ptr, - cleanup_hooks, - tsfn_ref_counters, - ) = { + let (async_work_sender, isolate_ptr, cleanup_hooks, external_ops_tracker) = { let mut op_state = op_state.borrow_mut(); let permissions = op_state.borrow_mut::(); permissions.check(Some(&PathBuf::from(&path)))?; @@ -511,10 +485,9 @@ where let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>(); ( op_state.borrow::().clone(), - napi_state.threadsafe_function_sender.clone(), *isolate_ptr, napi_state.env_cleanup_hooks.clone(), - napi_state.tsfn_ref_counters.clone(), + op_state.external_ops_tracker.clone(), ) }; @@ -522,23 +495,25 @@ where let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name)); let napi_wrap = v8::Global::new(scope, napi_wrap); + let type_tag_name = v8::String::new(scope, "type_tag").unwrap(); + let type_tag = v8::Private::new(scope, Some(type_tag_name)); + let type_tag = v8::Global::new(scope, type_tag); + // The `module.exports` object. let exports = v8::Object::new(scope); - let mut env_shared = EnvShared::new(napi_wrap); - let cstr = CString::new(&*path).unwrap(); - env_shared.filename = cstr.as_ptr(); - std::mem::forget(cstr); + let env_shared = EnvShared::new(napi_wrap, type_tag, path.clone()); let ctx = scope.get_current_context(); let mut env = Env::new( isolate_ptr, v8::Global::new(scope, ctx), v8::Global::new(scope, global), + v8::Global::new(scope, buffer_constructor), + v8::Global::new(scope, report_error), async_work_sender, - tsfn_sender, cleanup_hooks, - tsfn_ref_counters, + external_ops_tracker, ); env.shared = Box::into_raw(Box::new(env_shared)); let env_ptr = Box::into_raw(Box::new(env)) as _; @@ -567,63 +542,30 @@ where slot.take() }); - if let Some(module_to_register) = maybe_module { + let maybe_exports = if let Some(module_to_register) = maybe_module { // SAFETY: napi_register_module guarantees that `module_to_register` is valid. let nm = unsafe { &*module_to_register }; assert_eq!(nm.nm_version, 1); // SAFETY: we are going blind, calling the register function on the other side. - let maybe_exports = unsafe { - (nm.nm_register_func)( - env_ptr, - std::mem::transmute::, napi_value>(exports.into()), - ) - }; - - let exports = if maybe_exports.is_some() { - // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer - // to a value, they have the same layout - unsafe { - std::mem::transmute::>(maybe_exports) - } - } else { - exports.into() - }; - - // NAPI addons can't be unloaded, so we're going to "forget" the library - // object so it lives till the program exit. - std::mem::forget(library); - return Ok(exports); - } - - // Initializer callback. - // SAFETY: we are going blind, calling the register function on the other side. - - let maybe_exports = unsafe { - let Ok(init) = library - .get:: napi_value>(b"napi_register_module_v1") else { - return Err(type_error(format!("Unable to find napi_register_module_v1 symbol in {}", path))); - }; - init( - env_ptr, - std::mem::transmute::, napi_value>(exports.into()), - ) - }; - - let exports = if maybe_exports.is_some() { - // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer - // to a value, they have the same layout - unsafe { - std::mem::transmute::>(maybe_exports) - } + unsafe { (nm.nm_register_func)(env_ptr, exports.into()) } + } else if let Ok(init) = unsafe { + library.get::(b"napi_register_module_v1") + } { + // Initializer callback. + // SAFETY: we are going blind, calling the register function on the other side. + unsafe { init(env_ptr, exports.into()) } } else { - exports.into() + return Err(type_error(format!( + "Unable to find register Node-API module at {}", + path + ))); }; + let exports = maybe_exports.unwrap_or(exports.into()); + // NAPI addons can't be unloaded, so we're going to "forget" the library // object so it lives till the program exit. std::mem::forget(library); + Ok(exports) } diff --git a/ext/napi/value.rs b/ext/napi/value.rs index c1607f2c24080..6fb96758c197d 100644 --- a/ext/napi/value.rs +++ b/ext/napi/value.rs @@ -37,6 +37,19 @@ where } } +impl<'s, T> From>> for napi_value<'s> +where + v8::Local<'s, T>: Into>, +{ + fn from(v: Option>) -> Self { + if let Some(v) = v { + NapiValue::from(v) + } else { + Self(None, std::marker::PhantomData) + } + } +} + const _: () = { assert!( std::mem::size_of::() == std::mem::size_of::<*mut c_void>() diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index b0e4be89b1a09..10e89b3399004 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -1103,7 +1103,12 @@ Module._extensions[".node"] = function (module, filename) { if (filename.endsWith("fsevents.node")) { throw new Error("Using fsevents module is currently not supported"); } - module.exports = op_napi_open(filename, globalThis); + module.exports = op_napi_open( + filename, + globalThis, + nodeGlobals.Buffer, + reportError, + ); }; function createRequireFromPath(filename) { diff --git a/tests/napi/init_test.js b/tests/napi/init_test.js index 5f25078762e1c..9db99d8a05014 100644 --- a/tests/napi/init_test.js +++ b/tests/napi/init_test.js @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "node:buffer"; import { assert, libSuffix } from "./common.js"; const ops = Deno[Deno.internal].core.ops; @@ -8,7 +9,7 @@ Deno.test("ctr initialization (napi_module_register)", { ignore: Deno.build.os == "windows", }, function () { const path = new URL(`./module.${libSuffix}`, import.meta.url).pathname; - const obj = ops.op_napi_open(path, {}); + const obj = ops.op_napi_open(path, {}, Buffer, reportError); assert(obj != null); assert(typeof obj === "object"); }); diff --git a/tests/napi/object_wrap_test.js b/tests/napi/object_wrap_test.js index f79fd08f89440..de6391fb1cdec 100644 --- a/tests/napi/object_wrap_test.js +++ b/tests/napi/object_wrap_test.js @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "node:buffer"; import { assert, assertEquals, loadTestLibrary } from "./common.js"; const objectWrap = loadTestLibrary(); @@ -30,7 +31,7 @@ Deno.test("napi external finalizer", function () { Deno.test("napi external buffer", function () { let buf = objectWrap.test_external_buffer(); - assertEquals(buf, new Uint8Array([1, 2, 3])); + assertEquals(buf, new Buffer([1, 2, 3])); buf = null; }); diff --git a/tests/napi/src/object_wrap.rs b/tests/napi/src/object_wrap.rs index d04107cf0cc96..8c29caec53597 100644 --- a/tests/napi/src/object_wrap.rs +++ b/tests/napi/src/object_wrap.rs @@ -11,7 +11,6 @@ use std::ptr; pub struct NapiObject { counter: i32, - _wrapper: napi_ref, } impl NapiObject { @@ -33,18 +32,14 @@ impl NapiObject { assert_napi_ok!(napi_get_value_int32(env, args[0], &mut value)); - let mut wrapper: napi_ref = ptr::null_mut(); - let obj = Box::new(Self { - counter: value, - _wrapper: wrapper, - }); + let obj = Box::new(Self { counter: value }); assert_napi_ok!(napi_wrap( env, this, Box::into_raw(obj) as *mut c_void, None, ptr::null_mut(), - &mut wrapper, + ptr::null_mut(), )); return this; diff --git a/tools/util.js b/tools/util.js index 1497a28873673..251aaa1fa7783 100644 --- a/tools/util.js +++ b/tools/util.js @@ -31,11 +31,17 @@ async function getFilesFromGit(baseDir, args) { throw new Error("gitLsFiles failed"); } - const files = output.split("\0").filter((line) => line.length > 0).map( - (filePath) => { - return Deno.realPathSync(join(baseDir, filePath)); - }, - ); + const files = output + .split("\0") + .filter((line) => line.length > 0) + .map((filePath) => { + try { + return Deno.realPathSync(join(baseDir, filePath)); + } catch { + return null; + } + }) + .filter((filePath) => filePath !== null); return files; } From 7996c0df92f53585cf8abb8844793c1e3aaea078 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:36:39 -0700 Subject: [PATCH 015/139] fix(jupyter): Avoid panicking when `DEBUG` env var is set (#24168) Fixes #22050. It seems very unlikely that a user would be intending to enable deno's internal debug logs by setting the DEBUG env var. If they really want that, they can set `RUST_LOG=debug` instead. --- cli/tools/jupyter/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cli/tools/jupyter/mod.rs b/cli/tools/jupyter/mod.rs index 3b08928437564..0dbcfe9ef070b 100644 --- a/cli/tools/jupyter/mod.rs +++ b/cli/tools/jupyter/mod.rs @@ -7,7 +7,6 @@ use crate::tools::repl; use crate::tools::test::create_single_test_event_channel; use crate::tools::test::reporters::PrettyTestReporter; use crate::tools::test::TestEventWorkerSender; -use crate::util::logger; use crate::CliFactory; use deno_core::anyhow::Context; use deno_core::error::generic_error; @@ -52,11 +51,6 @@ pub async fn kernel( let connection_filepath = jupyter_flags.conn_file.unwrap(); - // This env var might be set by notebook - if std::env::var("DEBUG").is_ok() { - logger::init(Some(log::Level::Debug)); - } - let factory = CliFactory::from_flags(flags)?; let cli_options = factory.cli_options(); let main_module = From d74be0842a32e4f08551be8371447254254e7ab4 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 10 Jun 2024 19:56:43 -0400 Subject: [PATCH 016/139] FUTURE: support `deno install @npm:` (#24156) Closes #23144 --- cli/tools/installer.rs | 15 +++- cli/tools/registry/pm.rs | 184 +++++++++++++++++++++++++++++++-------- 2 files changed, 161 insertions(+), 38 deletions(-) diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 34ecc66be8aca..c920489f232a1 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -287,13 +287,20 @@ pub async fn install_command( log::warn!("âš ï¸ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag."); } - let install_flags_global = match install_flags.kind { - InstallKind::Global(flags) => flags, + match install_flags.kind { + InstallKind::Global(global_flags) => { + install_global(flags, global_flags).await + } InstallKind::Local(maybe_add_flags) => { - return install_local(flags, maybe_add_flags).await + install_local(flags, maybe_add_flags).await } - }; + } +} +async fn install_global( + flags: Flags, + install_flags_global: InstallFlagsGlobal, +) -> Result<(), AnyError> { // ensure the module is cached let factory = CliFactory::from_flags(flags.clone())?; factory diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 62d0f604add22..40493c6bf8d15 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -15,8 +15,7 @@ use deno_core::futures::StreamExt; use deno_core::serde_json; use deno_core::ModuleSpecifier; use deno_runtime::deno_node; -use deno_semver::jsr::JsrPackageReqReference; -use deno_semver::npm::NpmPackageReqReference; +use deno_semver::package::PackageReq; use indexmap::IndexMap; use jsonc_parser::ast::ObjectProp; use jsonc_parser::ast::Value; @@ -193,26 +192,10 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> { let mut selected_packages = Vec::with_capacity(add_flags.packages.len()); let mut package_reqs = Vec::with_capacity(add_flags.packages.len()); - for package_name in add_flags.packages.iter() { - let req = if package_name.starts_with("npm:") { - let pkg_req = NpmPackageReqReference::from_str(&format!( - "npm:{}", - package_name.strip_prefix("npm:").unwrap_or(package_name) - )) - .with_context(|| { - format!("Failed to parse package required: {}", package_name) - })?; - AddPackageReq::Npm(pkg_req) - } else { - let pkg_req = JsrPackageReqReference::from_str(&format!( - "jsr:{}", - package_name.strip_prefix("jsr:").unwrap_or(package_name) - )) - .with_context(|| { - format!("Failed to parse package required: {}", package_name) - })?; - AddPackageReq::Jsr(pkg_req) - }; + for entry_text in add_flags.packages.iter() { + let req = AddPackageReq::parse(entry_text).with_context(|| { + format!("Failed to parse package required: {}", entry_text) + })?; package_reqs.push(req); } @@ -351,11 +334,10 @@ async fn find_package_and_select_version_for_req( npm_resolver: Arc, add_package_req: AddPackageReq, ) -> Result { - match add_package_req { - AddPackageReq::Jsr(pkg_ref) => { - let req = pkg_ref.req(); + match add_package_req.value { + AddPackageReqValue::Jsr(req) => { let jsr_prefixed_name = format!("jsr:{}", &req.name); - let Some(nv) = jsr_resolver.req_to_nv(req).await else { + let Some(nv) = jsr_resolver.req_to_nv(&req).await else { return Ok(PackageAndVersion::NotFound(jsr_prefixed_name)); }; let range_symbol = if req.version_req.version_text().starts_with('~') { @@ -364,15 +346,14 @@ async fn find_package_and_select_version_for_req( '^' }; Ok(PackageAndVersion::Selected(SelectedPackage { - import_name: req.name.to_string(), + import_name: add_package_req.alias, package_name: jsr_prefixed_name, version_req: format!("{}{}", range_symbol, &nv.version), })) } - AddPackageReq::Npm(pkg_ref) => { - let req = pkg_ref.req(); + AddPackageReqValue::Npm(req) => { let npm_prefixed_name = format!("npm:{}", &req.name); - let Some(nv) = npm_resolver.req_to_nv(req).await else { + let Some(nv) = npm_resolver.req_to_nv(&req).await else { return Ok(PackageAndVersion::NotFound(npm_prefixed_name)); }; let range_symbol = if req.version_req.version_text().starts_with('~') { @@ -381,7 +362,7 @@ async fn find_package_and_select_version_for_req( '^' }; Ok(PackageAndVersion::Selected(SelectedPackage { - import_name: req.name.to_string(), + import_name: add_package_req.alias, package_name: npm_prefixed_name, version_req: format!("{}{}", range_symbol, &nv.version), })) @@ -389,9 +370,85 @@ async fn find_package_and_select_version_for_req( } } -enum AddPackageReq { - Jsr(JsrPackageReqReference), - Npm(NpmPackageReqReference), +#[derive(Debug, PartialEq, Eq)] +enum AddPackageReqValue { + Jsr(PackageReq), + Npm(PackageReq), +} + +#[derive(Debug, PartialEq, Eq)] +struct AddPackageReq { + alias: String, + value: AddPackageReqValue, +} + +impl AddPackageReq { + pub fn parse(entry_text: &str) -> Result { + enum Prefix { + Jsr, + Npm, + } + + fn parse_prefix(text: &str) -> (Option, &str) { + if let Some(text) = text.strip_prefix("jsr:") { + (Some(Prefix::Jsr), text) + } else if let Some(text) = text.strip_prefix("npm:") { + (Some(Prefix::Npm), text) + } else { + (None, text) + } + } + + // parse the following: + // - alias@npm: + // - other_alias@npm: + // - @alias/other@jsr: + fn parse_alias(entry_text: &str) -> Option<(&str, &str)> { + for prefix in ["npm:", "jsr:"] { + let Some(location) = entry_text.find(prefix) else { + continue; + }; + let prefix = &entry_text[..location]; + if let Some(alias) = prefix.strip_suffix('@') { + return Some((alias, &entry_text[location..])); + } + } + None + } + + let (maybe_prefix, entry_text) = parse_prefix(entry_text); + let (prefix, maybe_alias, entry_text) = match maybe_prefix { + Some(prefix) => (prefix, None, entry_text), + None => match parse_alias(entry_text) { + Some((alias, text)) => { + let (maybe_prefix, entry_text) = parse_prefix(text); + ( + maybe_prefix.unwrap_or(Prefix::Jsr), + Some(alias.to_string()), + entry_text, + ) + } + None => (Prefix::Jsr, None, entry_text), + }, + }; + + match prefix { + Prefix::Jsr => { + let package_req = PackageReq::from_str(entry_text)?; + Ok(AddPackageReq { + alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()), + value: AddPackageReqValue::Jsr(package_req), + }) + } + Prefix::Npm => { + let package_req = PackageReq::from_str(entry_text)?; + Ok(AddPackageReq { + alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()), + value: AddPackageReqValue::Npm(package_req), + }) + } + } + } } fn generate_imports(packages_to_version: Vec<(String, String)>) -> String { @@ -456,3 +513,62 @@ fn update_config_file_content( .map(|formatted_text| formatted_text.unwrap_or_else(|| new_text.clone())) .unwrap_or(new_text) } + +#[cfg(test)] +mod test { + use deno_semver::VersionReq; + + use super::*; + + #[test] + fn test_parse_add_package_req() { + assert_eq!( + AddPackageReq::parse("jsr:foo").unwrap(), + AddPackageReq { + alias: "foo".to_string(), + value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) + } + ); + assert_eq!( + AddPackageReq::parse("alias@jsr:foo").unwrap(), + AddPackageReq { + alias: "alias".to_string(), + value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) + } + ); + assert_eq!( + AddPackageReq::parse("@alias/pkg@npm:foo").unwrap(), + AddPackageReq { + alias: "@alias/pkg".to_string(), + value: AddPackageReqValue::Npm(PackageReq::from_str("foo").unwrap()) + } + ); + assert_eq!( + AddPackageReq::parse("@alias/pkg@jsr:foo").unwrap(), + AddPackageReq { + alias: "@alias/pkg".to_string(), + value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap()) + } + ); + assert_eq!( + AddPackageReq::parse("alias@jsr:foo@^1.5.0").unwrap(), + AddPackageReq { + alias: "alias".to_string(), + value: AddPackageReqValue::Jsr( + PackageReq::from_str("foo@^1.5.0").unwrap() + ) + } + ); + assert_eq!( + AddPackageReq::parse("@scope/pkg@tag").unwrap(), + AddPackageReq { + alias: "@scope/pkg".to_string(), + value: AddPackageReqValue::Jsr(PackageReq { + name: "@scope/pkg".to_string(), + // this is a tag + version_req: VersionReq::parse_from_specifier("tag").unwrap(), + }), + } + ); + } +} From 3d41b486da7dcba49c8a18b45425e356c329d986 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 11 Jun 2024 12:39:44 +0200 Subject: [PATCH 017/139] fix(ext/node): ServerResponse header array handling (#24149) Previously res.setHeader("foo", ["bar", "baz"]) added a single header with a value of `bar,baz`. Really this should add two separate headers. This is visible in `set-cookie` for example. --- ext/node/polyfills/http.ts | 47 ++++++++++++++++++++++++++---------- tests/unit_node/http_test.ts | 31 ++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index ec3fe6e0b4f67..32e69772d6cdd 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -1333,7 +1333,8 @@ function onError(self, error, cb) { export class ServerResponse extends NodeWritable { statusCode = 200; statusMessage?: string = undefined; - #headers = new Headers({}); + #headers: Record = { __proto__: null }; + #hasNonStringHeaders: boolean = false; #readable: ReadableStream; override writable = true; // used by `npm:on-finished` @@ -1411,32 +1412,35 @@ export class ServerResponse extends NodeWritable { this.socket = socket; } - setHeader(name: string, value: string) { - this.#headers.set(name, value); + setHeader(name: string, value: string | string[]) { + if (Array.isArray(value)) { + this.#hasNonStringHeaders = true; + } + this.#headers[name] = value; return this; } getHeader(name: string) { - return this.#headers.get(name) ?? undefined; + return this.#headers[name]; } removeHeader(name: string) { - return this.#headers.delete(name); + delete this.#headers[name]; } getHeaderNames() { - return Array.from(this.#headers.keys()); + return Object.keys(this.#headers); } getHeaders() { - return Object.fromEntries(this.#headers.entries()); + return { __proto__: null, ...this.#headers }; } hasHeader(name: string) { - return this.#headers.has(name); + return Object.hasOwn(this.#headers, name); } writeHead(status: number, headers: Record = {}) { this.statusCode = status; for (const k in headers) { if (Object.hasOwn(headers, k)) { - this.#headers.set(k, headers[k]); + this.setHeader(k, headers[k]); } } return this; @@ -1461,9 +1465,26 @@ export class ServerResponse extends NodeWritable { if (ServerResponse.#bodyShouldBeNull(this.statusCode)) { body = null; } + let headers: Record | [string, string][] = this + .#headers as Record; + if (this.#hasNonStringHeaders) { + headers = []; + // Guard is not needed as this is a null prototype object. + // deno-lint-ignore guard-for-in + for (const key in this.#headers) { + const entry = this.#headers[key]; + if (Array.isArray(entry)) { + for (const value of entry) { + headers.push([key, value]); + } + } else { + headers.push([key, entry]); + } + } + } this.#resolve( new Response(body, { - headers: this.#headers, + headers, status: this.statusCode, statusText: this.statusMessage, }), @@ -1473,11 +1494,11 @@ export class ServerResponse extends NodeWritable { // deno-lint-ignore no-explicit-any override end(chunk?: any, encoding?: any, cb?: any): this { this.finished = true; - if (!chunk && this.#headers.has("transfer-encoding")) { + if (!chunk && "transfer-encoding" in this.#headers) { // FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e., // the trailing "0\r\n", but respondWith() just hangs when I try that. - this.#headers.set("content-length", "0"); - this.#headers.delete("transfer-encoding"); + this.#headers["content-length"] = "0"; + delete this.#headers["transfer-encoding"]; } // @ts-expect-error The signature for cb is stricter than the one implemented here diff --git a/tests/unit_node/http_test.ts b/tests/unit_node/http_test.ts index 9cb409c39bec7..2b26442721e97 100644 --- a/tests/unit_node/http_test.ts +++ b/tests/unit_node/http_test.ts @@ -182,6 +182,33 @@ Deno.test("[node/http] server can respond with 101, 204, 205, 304 status", async } }); +Deno.test("[node/http] multiple set-cookie headers", async () => { + const { promise, resolve } = Promise.withResolvers(); + + const server = http.createServer((_req, res) => { + res.setHeader("Set-Cookie", ["foo=bar", "bar=foo"]); + assertEquals(res.getHeader("Set-Cookie"), ["foo=bar", "bar=foo"]); + res.end(); + }); + + server.listen(async () => { + const res = await fetch( + // deno-lint-ignore no-explicit-any + `http://127.0.0.1:${(server.address() as any).port}/`, + ); + assert(res.ok); + + const setCookieHeaders = res.headers.getSetCookie(); + assertEquals(setCookieHeaders, ["foo=bar", "bar=foo"]); + + await res.body!.cancel(); + + server.close(() => resolve()); + }); + + await promise; +}); + Deno.test("[node/http] IncomingRequest socket has remoteAddress + remotePort", async () => { const { promise, resolve } = Promise.withResolvers(); @@ -1000,8 +1027,8 @@ Deno.test("[node/http] ServerResponse getHeaders", () => { const res = new http.ServerResponse(req); res.setHeader("foo", "bar"); res.setHeader("bar", "baz"); - assertEquals(res.getHeaderNames(), ["bar", "foo"]); - assertEquals(res.getHeaders(), { "bar": "baz", "foo": "bar" }); + assertEquals(res.getHeaderNames(), ["foo", "bar"]); + assertEquals(res.getHeaders(), { "foo": "bar", "bar": "baz" }); }); Deno.test("[node/http] ServerResponse default status code 200", () => { From 6a356aff1380e79d67738c5b43aa2b5fee76600d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 11 Jun 2024 12:41:44 +0100 Subject: [PATCH 018/139] chore: sync up Node.js test files for v20.11.1 (#24066) Co-authored-by: Yoshiya Hinosawa --- ext/node/polyfills/_fs/_fs_read.ts | 4 - ext/node/polyfills/_fs/_fs_write.mjs | 6 +- ext/node/polyfills/_fs/_fs_writeFile.ts | 20 +- ext/node/polyfills/_stream.mjs | 23 +- ext/node/polyfills/internal/buffer.mjs | 31 ++- ext/node/polyfills/internal/cli_table.ts | 5 +- ext/node/polyfills/internal/fs/utils.mjs | 31 +-- ext/node/polyfills/internal/validators.mjs | 15 +- ext/node/polyfills/path/_util.ts | 6 +- ext/node/polyfills/url.ts | 28 ++- tests/node_compat/.gitignore | 1 + tests/node_compat/config.jsonc | 4 +- tests/node_compat/runner/TODO.md | 222 +++++++++++++++--- tests/node_compat/runner/suite | 2 +- tests/node_compat/test.ts | 2 +- .../node_compat/test/common/child_process.js | 106 ++++++++- tests/node_compat/test/common/index.js | 33 +++ tests/node_compat/test/common/index.mjs | 132 +++++------ tests/node_compat/test/common/tmpdir.js | 39 ++- .../test/internet/test-dns-lookup.js | 4 +- .../test-buffer-no-negative-allocation.js | 3 +- .../parallel/test-buffer-over-max-length.js | 13 +- .../test/parallel/test-buffer-slice.js | 4 +- .../test/parallel/test-buffer-slow.js | 3 +- .../test-buffer-tostring-rangeerror.js | 18 +- .../test/parallel/test-console-table.js | 130 +++++----- .../test-diagnostics-channel-pub-sub.js | 7 + .../test-event-emitter-emit-context.js | 2 +- .../test-event-emitter-error-monitor.js | 2 +- .../test/parallel/test-file-write-stream.js | 3 +- .../test/parallel/test-file-write-stream2.js | 3 +- .../test/parallel/test-file-write-stream3.js | 3 +- .../test/parallel/test-file-write-stream4.js | 3 +- .../test/parallel/test-fs-access.js | 39 ++- .../test/parallel/test-fs-append-file-sync.js | 11 +- .../test/parallel/test-fs-copyfile.js | 3 +- .../test/parallel/test-fs-open-flags.js | 3 +- .../test/parallel/test-fs-open-mode-mask.js | 5 +- .../parallel/test-fs-open-numeric-flags.js | 3 +- .../parallel/test-fs-read-stream-autoClose.js | 3 +- .../test/parallel/test-fs-read-stream-fd.js | 3 +- .../parallel/test-fs-read-stream-inherit.js | 18 +- .../node_compat/test/parallel/test-fs-read.js | 2 +- .../test/parallel/test-fs-readv-sync.js | 3 +- .../test/parallel/test-fs-readv.js | 3 +- ...fs-rmdir-recursive-sync-warns-not-found.js | 3 +- ...t-fs-rmdir-recursive-sync-warns-on-file.js | 3 +- ...est-fs-rmdir-recursive-throws-not-found.js | 7 +- .../test-fs-rmdir-recursive-throws-on-file.js | 7 +- ...test-fs-rmdir-recursive-warns-not-found.js | 3 +- .../test-fs-rmdir-recursive-warns-on-file.js | 3 +- .../test/parallel/test-fs-watchfile.js | 8 +- .../test/parallel/test-fs-write-buffer.js | 17 +- .../parallel/test-fs-write-file-buffer.js | 3 +- .../test-fs-write-file-invalid-path.js | 5 +- .../test/parallel/test-fs-write-file-sync.js | 53 +++-- .../test-fs-write-stream-autoclose-option.js | 3 +- ...-fs-write-stream-close-without-callback.js | 3 +- .../test-fs-write-stream-double-close.js | 7 +- .../test/parallel/test-fs-write-stream-end.js | 7 +- .../test/parallel/test-fs-write-stream-fs.js | 5 +- .../test-fs-write-stream-throw-type-error.js | 3 +- .../test/parallel/test-fs-write-stream.js | 3 +- .../test/parallel/test-fs-write-sync.js | 3 +- .../test/parallel/test-fs-write.js | 40 ++-- .../test/parallel/test-fs-writev-sync.js | 3 +- .../test/parallel/test-http-agent-getname.js | 3 +- ...tp-outgoing-internal-headernames-getter.js | 2 +- ....parse-only-support-http-https-protocol.js | 4 +- .../test/parallel/test-net-isipv4.js | 8 +- .../test/parallel/test-net-isipv6.js | 8 +- .../test/parallel/test-parse-args.mjs | 6 +- .../test/parallel/test-path-extname.js | 29 +-- .../test/parallel/test-path-parse-format.js | 4 + tests/node_compat/test/parallel/test-path.js | 8 +- .../test/parallel/test-readline-keys.js | 4 +- .../test/parallel/test-stream-buffer-list.js | 91 ------- .../parallel/test-stream-readable-destroy.js | 2 +- .../test/parallel/test-stream-uint8array.js | 2 +- .../test-stream-writable-decoded-encoding.js | 47 ++++ .../test-stream-writable-end-cb-error.js | 2 +- .../test-stream2-readable-from-list.js | 108 --------- .../test/parallel/test-stream2-writable.js | 11 +- .../test/parallel/test-url-format-whatwg.js | 5 + .../parallel/test-url-parse-invalid-input.js | 30 +++ .../test/parallel/test-url-parse-query.js | 2 +- .../test/parallel/test-url-pathtofileurl.js | 37 ++- .../test/parallel/test-url-relative.js | 25 +- ...vents-add-event-listener-options-signal.js | 9 + 89 files changed, 937 insertions(+), 703 deletions(-) create mode 100644 tests/node_compat/.gitignore delete mode 100644 tests/node_compat/test/parallel/test-stream-buffer-list.js delete mode 100644 tests/node_compat/test/parallel/test-stream2-readable-from-list.js diff --git a/ext/node/polyfills/_fs/_fs_read.ts b/ext/node/polyfills/_fs/_fs_read.ts index cf0c5e51dd90b..e25f41e764507 100644 --- a/ext/node/polyfills/_fs/_fs_read.ts +++ b/ext/node/polyfills/_fs/_fs_read.ts @@ -88,10 +88,6 @@ export function read( if ( !(opt.buffer instanceof Buffer) && !(opt.buffer instanceof Uint8Array) ) { - if (opt.buffer === null) { - // @ts-ignore: Intentionally create TypeError for passing test-fs-read.js#L87 - length = opt.buffer.byteLength; - } throw new ERR_INVALID_ARG_TYPE("buffer", [ "Buffer", "TypedArray", diff --git a/ext/node/polyfills/_fs/_fs_write.mjs b/ext/node/polyfills/_fs/_fs_write.mjs index aa23805bf3f26..1ad6ac492a95b 100644 --- a/ext/node/polyfills/_fs/_fs_write.mjs +++ b/ext/node/polyfills/_fs/_fs_write.mjs @@ -13,7 +13,6 @@ import * as io from "ext:deno_io/12_io.js"; import * as fs from "ext:deno_fs/30_fs.js"; import { getValidatedFd, - showStringCoercionDeprecation, validateOffsetLengthWrite, validateStringAfterArrayBufferView, } from "ext:deno_node/internal/fs/utils.mjs"; @@ -114,9 +113,6 @@ export function write(fd, buffer, offset, length, position, callback) { // `fs.write(fd, string[, position[, encoding]], callback)` validateStringAfterArrayBufferView(buffer, "buffer"); - if (typeof buffer !== "string") { - showStringCoercionDeprecation(); - } if (typeof position !== "function") { if (typeof offset === "function") { @@ -128,7 +124,7 @@ export function write(fd, buffer, offset, length, position, callback) { length = "utf-8"; } - const str = String(buffer); + const str = buffer; validateEncoding(str, length); callback = maybeCallback(position); buffer = Buffer.from(str, length); diff --git a/ext/node/polyfills/_fs/_fs_writeFile.ts b/ext/node/polyfills/_fs/_fs_writeFile.ts index 60b31897ecb1b..f0014c36d2634 100644 --- a/ext/node/polyfills/_fs/_fs_writeFile.ts +++ b/ext/node/polyfills/_fs/_fs_writeFile.ts @@ -20,7 +20,6 @@ import { denoErrorToNodeError, } from "ext:deno_node/internal/errors.ts"; import { - showStringCoercionDeprecation, validateStringAfterArrayBufferView, } from "ext:deno_node/internal/fs/utils.mjs"; import { promisify } from "ext:deno_node/internal/util.mjs"; @@ -32,8 +31,7 @@ interface Writer { export function writeFile( pathOrRid: string | number | URL, - // deno-lint-ignore ban-types - data: string | Uint8Array | Object, + data: string | Uint8Array, optOrCallback: Encodings | CallbackWithError | WriteFileOptions | undefined, callback?: CallbackWithError, ) { @@ -61,10 +59,7 @@ export function writeFile( if (!ArrayBuffer.isView(data)) { validateStringAfterArrayBufferView(data, "data"); - if (typeof data !== "string") { - showStringCoercionDeprecation(); - } - data = Buffer.from(String(data), encoding); + data = Buffer.from(data, encoding); } const isRid = typeof pathOrRid === "number"; @@ -101,15 +96,13 @@ export function writeFile( export const writeFilePromise = promisify(writeFile) as ( pathOrRid: string | number | URL, - // deno-lint-ignore ban-types - data: string | Uint8Array | Object, + data: string | Uint8Array, options?: Encodings | WriteFileOptions, ) => Promise; export function writeFileSync( pathOrRid: string | number | URL, - // deno-lint-ignore ban-types - data: string | Uint8Array | Object, + data: string | Uint8Array, options?: Encodings | WriteFileOptions, ) { pathOrRid = pathOrRid instanceof URL ? pathFromURL(pathOrRid) : pathOrRid; @@ -127,10 +120,7 @@ export function writeFileSync( if (!ArrayBuffer.isView(data)) { validateStringAfterArrayBufferView(data, "data"); - if (typeof data !== "string") { - showStringCoercionDeprecation(); - } - data = Buffer.from(String(data), encoding); + data = Buffer.from(data, encoding); } const isRid = typeof pathOrRid === "number"; diff --git a/ext/node/polyfills/_stream.mjs b/ext/node/polyfills/_stream.mjs index 591f8bb512e05..075705e00f0c9 100644 --- a/ext/node/polyfills/_stream.mjs +++ b/ext/node/polyfills/_stream.mjs @@ -3754,7 +3754,14 @@ var require_writable = __commonJS({ this.destroyed = false; const noDecode = !!(options && options.decodeStrings === false); this.decodeStrings = !noDecode; - this.defaultEncoding = options && options.defaultEncoding || "utf8"; + const defaultEncoding = options?.defaultEncoding; + if (defaultEncoding == null) { + this.defaultEncoding = 'utf8'; + } else if (Buffer2.isEncoding(defaultEncoding)) { + this.defaultEncoding = defaultEncoding; + } else { + throw new ERR_UNKNOWN_ENCODING(defaultEncoding); + } this.length = 0; this.writing = false; this.corked = 0; @@ -3845,10 +3852,12 @@ var require_writable = __commonJS({ const state = stream._writableState; if (typeof encoding === "function") { cb = encoding; - encoding = state.defaultEncoding; + // Simulates https://github.com/nodejs/node/commit/dbed0319ac438dcbd6e92483f3280b1dc6767e00 + encoding = state.objectMode ? undefined : state.defaultEncoding; } else { if (!encoding) { - encoding = state.defaultEncoding; + // Simulates https://github.com/nodejs/node/commit/dbed0319ac438dcbd6e92483f3280b1dc6767e00 + encoding = state.objectMode ? undefined : state.defaultEncoding; } else if (encoding !== "buffer" && !Buffer2.isEncoding(encoding)) { throw new ERR_UNKNOWN_ENCODING(encoding); } @@ -4031,7 +4040,7 @@ var require_writable = __commonJS({ } while (count-- > 0) { state.pendingcb--; - cb(); + cb(null); } if (state.destroyed) { errorBuffer(state); @@ -4158,8 +4167,10 @@ var require_writable = __commonJS({ err = new ERR_STREAM_DESTROYED("end"); } if (typeof cb === "function") { - if (err || state.finished) { + if (err) { process.nextTick(cb, err); + } else if (state.finished) { + process.nextTick(cb, null); } else { state[kOnFinished].push(cb); } @@ -4246,7 +4257,7 @@ var require_writable = __commonJS({ state.finished = true; const onfinishCallbacks = state[kOnFinished].splice(0); for (let i = 0; i < onfinishCallbacks.length; i++) { - onfinishCallbacks[i](); + onfinishCallbacks[i](null); } stream.emit("finish"); if (state.autoDestroy) { diff --git a/ext/node/polyfills/internal/buffer.mjs b/ext/node/polyfills/internal/buffer.mjs index 0521c56aaa487..a051965a391fa 100644 --- a/ext/node/polyfills/internal/buffer.mjs +++ b/ext/node/polyfills/internal/buffer.mjs @@ -32,7 +32,7 @@ import { import { normalizeEncoding } from "ext:deno_node/internal/util.mjs"; import { validateBuffer } from "ext:deno_node/internal/validators.mjs"; import { isUint8Array } from "ext:deno_node/internal/util/types.ts"; -import { ERR_INVALID_STATE } from "ext:deno_node/internal/errors.ts"; +import { ERR_INVALID_STATE, NodeError } from "ext:deno_node/internal/errors.ts"; import { forgivingBase64Encode, forgivingBase64UrlEncode, @@ -167,10 +167,7 @@ Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); Object.setPrototypeOf(Buffer, Uint8Array); function assertSize(size) { - validateNumber(size, "size"); - if (!(size >= 0 && size <= kMaxLength)) { - throw new codes.ERR_INVALID_ARG_VALUE.RangeError("size", size); - } + validateNumber(size, "size", 0, kMaxLength); } function _alloc(size, fill, encoding) { @@ -852,7 +849,14 @@ function _base64Slice(buf, start, end) { const decoder = new TextDecoder(); function _utf8Slice(buf, start, end) { - return decoder.decode(buf.slice(start, end)); + try { + return decoder.decode(buf.slice(start, end)); + } catch (err) { + if (err instanceof TypeError) { + throw new NodeError("ERR_STRING_TOO_LONG", "String too long"); + } + throw err; + } } function _latin1Slice(buf, start, end) { @@ -2297,10 +2301,23 @@ export function boundsError(value, length, type) { ); } -export function validateNumber(value, name) { +export function validateNumber(value, name, min = undefined, max) { if (typeof value !== "number") { throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); } + + if ( + (min != null && value < min) || (max != null && value > max) || + ((min != null || max != null) && Number.isNaN(value)) + ) { + throw new codes.ERR_OUT_OF_RANGE( + name, + `${min != null ? `>= ${min}` : ""}${ + min != null && max != null ? " && " : "" + }${max != null ? `<= ${max}` : ""}`, + value, + ); + } } function checkInt(value, min, max, buf, offset, byteLength) { diff --git a/ext/node/polyfills/internal/cli_table.ts b/ext/node/polyfills/internal/cli_table.ts index 574081ba427f7..9826e524f6c56 100644 --- a/ext/node/polyfills/internal/cli_table.ts +++ b/ext/node/polyfills/internal/cli_table.ts @@ -27,11 +27,10 @@ const renderRow = (row: string[], columnWidths: number[]) => { for (let i = 0; i < row.length; i++) { const cell = row[i]; const len = getStringWidth(cell); - const needed = (columnWidths[i] - len) / 2; + const needed = columnWidths[i] - len; // round(needed) + ceil(needed) will always add up to the amount // of spaces we need while also left justifying the output. - out += " ".repeat(needed) + cell + - " ".repeat(Math.ceil(needed)); + out += cell + " ".repeat(Math.ceil(needed)); if (i !== row.length - 1) { out += tableChars.middle; } diff --git a/ext/node/polyfills/internal/fs/utils.mjs b/ext/node/polyfills/internal/fs/utils.mjs index a1823bb32808b..ec379ed99a060 100644 --- a/ext/node/polyfills/internal/fs/utils.mjs +++ b/ext/node/polyfills/internal/fs/utils.mjs @@ -23,7 +23,6 @@ import { isUint8Array, } from "ext:deno_node/internal/util/types.ts"; import { once } from "ext:deno_node/internal/util.mjs"; -import { deprecate } from "node:util"; import { toPathIfFileURL } from "ext:deno_node/internal/url.ts"; import { validateAbortSignal, @@ -959,24 +958,13 @@ export const getValidMode = hideStackFrames((mode, type) => { export const validateStringAfterArrayBufferView = hideStackFrames( (buffer, name) => { - if (typeof buffer === "string") { - return; - } - - if ( - typeof buffer === "object" && - buffer !== null && - typeof buffer.toString === "function" && - Object.prototype.hasOwnProperty.call(buffer, "toString") - ) { - return; + if (typeof buffer !== "string") { + throw new ERR_INVALID_ARG_TYPE( + name, + ["string", "Buffer", "TypedArray", "DataView"], + buffer, + ); } - - throw new ERR_INVALID_ARG_TYPE( - name, - ["string", "Buffer", "TypedArray", "DataView"], - buffer, - ); }, ); @@ -1005,12 +993,6 @@ export const constants = { kWriteFileMaxChunkSize, }; -export const showStringCoercionDeprecation = deprecate( - () => {}, - "Implicit coercion of objects with own toString property is deprecated.", - "DEP0162", -); - export default { constants, assertEncoding, @@ -1030,7 +1012,6 @@ export default { preprocessSymlinkDestination, realpathCacheKey, getStatsFromBinding, - showStringCoercionDeprecation, stringToFlags, stringToSymlinkType, Stats, diff --git a/ext/node/polyfills/internal/validators.mjs b/ext/node/polyfills/internal/validators.mjs index d4cd955462c7c..58b1a97d7451f 100644 --- a/ext/node/polyfills/internal/validators.mjs +++ b/ext/node/polyfills/internal/validators.mjs @@ -171,10 +171,23 @@ function validateString(value, name) { * @param {unknown} value * @param {string} name */ -function validateNumber(value, name) { +function validateNumber(value, name, min = undefined, max) { if (typeof value !== "number") { throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); } + + if ( + (min != null && value < min) || (max != null && value > max) || + ((min != null || max != null) && Number.isNaN(value)) + ) { + throw new codes.ERR_OUT_OF_RANGE( + name, + `${min != null ? `>= ${min}` : ""}${ + min != null && max != null ? " && " : "" + }${max != null ? `<= ${max}` : ""}`, + value, + ); + } } /** diff --git a/ext/node/polyfills/path/_util.ts b/ext/node/polyfills/path/_util.ts index 7be4829653597..9248c68ae5660 100644 --- a/ext/node/polyfills/path/_util.ts +++ b/ext/node/polyfills/path/_util.ts @@ -106,13 +106,17 @@ export function normalizeString( return res; } +function formatExt(ext) { + return ext ? `${ext[0] === "." ? "" : "."}${ext}` : ""; +} + export function _format( sep: string, pathObject: FormatInputPathObject, ): string { const dir: string | undefined = pathObject.dir || pathObject.root; const base: string = pathObject.base || - (pathObject.name || "") + (pathObject.ext || ""); + (pathObject.name || "") + formatExt(pathObject.ext); if (!dir) return base; if (dir === pathObject.root) return dir + base; return dir + sep + base; diff --git a/ext/node/polyfills/url.ts b/ext/node/polyfills/url.ts index 6633334ba5b98..4eeb0381f6ec5 100644 --- a/ext/node/polyfills/url.ts +++ b/ext/node/polyfills/url.ts @@ -1352,12 +1352,16 @@ function getPathFromURLPosix(url: URL): string { * setter. * - TAB: The tab character is also stripped out by the `pathname` setter. */ -function encodePathChars(filepath: string): string { +function encodePathChars( + filepath: string, + options: { windows?: boolean }, +): string { + const windows = options.windows; if (filepath.includes("%")) { filepath = filepath.replace(percentRegEx, "%25"); } // In posix, backslash is a valid character in paths: - if (!isWindows && filepath.includes("\\")) { + if (!(windows ?? isWindows) && filepath.includes("\\")) { filepath = filepath.replace(backslashRegEx, "%5C"); } if (filepath.includes("\n")) { @@ -1376,11 +1380,17 @@ function encodePathChars(filepath: string): string { * This function ensures that `filepath` is resolved absolutely, and that the URL control characters are correctly encoded when converting into a File URL. * @see Tested in `parallel/test-url-pathtofileurl.js`. * @param filepath The file path string to convert to a file URL. + * @param options The options. * @returns The file URL object. */ -export function pathToFileURL(filepath: string): URL { +export function pathToFileURL( + filepath: string, + options: { windows?: boolean } = {}, +): URL { + validateString(filepath, "path"); + const windows = options?.windows; const outURL = new URL("file://"); - if (isWindows && filepath.startsWith("\\\\")) { + if ((windows ?? isWindows) && filepath.startsWith("\\\\")) { // UNC path format: \\server\share\resource const paths = filepath.split("\\"); if (paths.length <= 3) { @@ -1400,20 +1410,22 @@ export function pathToFileURL(filepath: string): URL { } outURL.hostname = idnaToASCII(hostname); - outURL.pathname = encodePathChars(paths.slice(3).join("/")); + outURL.pathname = encodePathChars(paths.slice(3).join("/"), { windows }); } else { - let resolved = path.resolve(filepath); + let resolved = (windows ?? isWindows) + ? path.win32.resolve(filepath) + : path.posix.resolve(filepath); // path.resolve strips trailing slashes so we must add them back const filePathLast = filepath.charCodeAt(filepath.length - 1); if ( (filePathLast === CHAR_FORWARD_SLASH || - (isWindows && filePathLast === CHAR_BACKWARD_SLASH)) && + ((windows ?? isWindows) && filePathLast === CHAR_BACKWARD_SLASH)) && resolved[resolved.length - 1] !== path.sep ) { resolved += "/"; } - outURL.pathname = encodePathChars(resolved); + outURL.pathname = encodePathChars(resolved, { windows }); } return outURL; } diff --git a/tests/node_compat/.gitignore b/tests/node_compat/.gitignore new file mode 100644 index 0000000000000..e8961f70fdecc --- /dev/null +++ b/tests/node_compat/.gitignore @@ -0,0 +1 @@ +test/.tmp.* diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index 9729507dd0a2b..612c5558a7b24 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -75,7 +75,6 @@ "test-fs-read-stream.js", "test-fs-rmdir-recursive.js", "test-fs-write-file.js", - "test-fs-write.js", "test-http-url.parse-https.request.js", "test-net-better-error-messages-path.js", "test-net-connect-buffer.js", @@ -94,6 +93,7 @@ "test-stdin-from-file-spawn.js", "test-stream-duplex-from.js", "test-ttywrap-invalid-fd.js", + "test-url-parse-invalid-input.js", "test-url-urltooptions.js", "test-util-format.js", "test-util-inspect-namespace.js", @@ -482,7 +482,6 @@ "test-stream-backpressure.js", "test-stream-big-packet.js", "test-stream-big-push.js", - "test-stream-buffer-list.js", "test-stream-construct.js", "test-stream-destroy-event-order.js", "test-stream-duplex-destroy.js", @@ -603,7 +602,6 @@ "test-stream2-push.js", "test-stream2-read-sync-stack.js", "test-stream2-readable-empty-buffer-no-eof.js", - "test-stream2-readable-from-list.js", "test-stream2-readable-legacy-drain.js", "test-stream2-readable-non-empty-end.js", "test-stream2-readable-wrap-destroy.js", diff --git a/tests/node_compat/runner/TODO.md b/tests/node_compat/runner/TODO.md index 0bc19a076649d..ebd6f192f0916 100644 --- a/tests/node_compat/runner/TODO.md +++ b/tests/node_compat/runner/TODO.md @@ -13,6 +13,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [abort/test-signal-handler.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-signal-handler.js) - [abort/test-worker-abort-uncaught-exception.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-worker-abort-uncaught-exception.js) - [abort/test-zlib-invalid-internals-usage.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-zlib-invalid-internals-usage.js) +- [benchmark/test-bechmark-readline.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-bechmark-readline.js) - [benchmark/test-benchmark-assert.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-assert.js) - [benchmark/test-benchmark-async-hooks.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-async-hooks.js) - [benchmark/test-benchmark-blob.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-blob.js) @@ -29,6 +30,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [benchmark/test-benchmark-fs.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-fs.js) - [benchmark/test-benchmark-http.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-http.js) - [benchmark/test-benchmark-http2.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-http2.js) +- [benchmark/test-benchmark-mime.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-mime.js) - [benchmark/test-benchmark-misc.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-misc.js) - [benchmark/test-benchmark-module.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-module.js) - [benchmark/test-benchmark-napi.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-napi.js) @@ -45,28 +47,35 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [benchmark/test-benchmark-url.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-url.js) - [benchmark/test-benchmark-util.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-util.js) - [benchmark/test-benchmark-v8.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-v8.js) +- [benchmark/test-benchmark-validators.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-validators.js) - [benchmark/test-benchmark-vm.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-vm.js) - [benchmark/test-benchmark-webstreams.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-webstreams.js) - [benchmark/test-benchmark-worker.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-worker.js) - [benchmark/test-benchmark-zlib.js](https://github.com/nodejs/node/tree/v18.12.1/test/benchmark/test-benchmark-zlib.js) - [es-module/test-cjs-esm-warn.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-cjs-esm-warn.js) +- [es-module/test-cjs-legacyMainResolve-permission.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-cjs-legacyMainResolve-permission.js) +- [es-module/test-cjs-legacyMainResolve.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-cjs-legacyMainResolve.js) - [es-module/test-cjs-prototype-pollution.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-cjs-prototype-pollution.js) +- [es-module/test-dynamic-import-script-lifetime.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-dynamic-import-script-lifetime.js) - [es-module/test-esm-assertionless-json-import.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-assertionless-json-import.js) - [es-module/test-esm-cjs-builtins.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-cjs-builtins.js) - [es-module/test-esm-cjs-exports.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-cjs-exports.js) - [es-module/test-esm-cjs-main.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-cjs-main.js) - [es-module/test-esm-data-urls.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-data-urls.js) -- [es-module/test-esm-dynamic-import-assertion.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-dynamic-import-assertion.js) +- [es-module/test-esm-dynamic-import-attribute.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-dynamic-import-attribute.js) +- [es-module/test-esm-dynamic-import-commonjs.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-dynamic-import-commonjs.js) +- [es-module/test-esm-dynamic-import-mutating-fs.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-dynamic-import-mutating-fs.js) - [es-module/test-esm-dynamic-import.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-dynamic-import.js) - [es-module/test-esm-encoded-path-native.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-encoded-path-native.js) - [es-module/test-esm-error-cache.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-error-cache.js) -- [es-module/test-esm-import-assertion-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-import-assertion-errors.js) -- [es-module/test-esm-import-assertion-validation.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-import-assertion-validation.js) +- [es-module/test-esm-import-attributes-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-import-attributes-errors.js) +- [es-module/test-esm-import-attributes-validation.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-import-attributes-validation.js) - [es-module/test-esm-invalid-data-urls.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-invalid-data-urls.js) - [es-module/test-esm-invalid-pjson.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-invalid-pjson.js) - [es-module/test-esm-loader-cache-clearing.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-loader-cache-clearing.js) - [es-module/test-esm-loader-modulemap.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-loader-modulemap.js) - [es-module/test-esm-loader-search.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-loader-search.js) +- [es-module/test-esm-named-exports.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-named-exports.js) - [es-module/test-esm-preserve-symlinks-main.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-preserve-symlinks-main.js) - [es-module/test-esm-preserve-symlinks.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-preserve-symlinks.js) - [es-module/test-esm-repl-imports.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-repl-imports.js) @@ -74,11 +83,20 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [es-module/test-esm-symlink-main.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-symlink-main.js) - [es-module/test-esm-symlink-type.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-symlink-type.js) - [es-module/test-esm-symlink.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-symlink.js) -- [es-module/test-esm-type-flag-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-type-flag-errors.js) +- [es-module/test-esm-type-field-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-type-field-errors.js) - [es-module/test-esm-undefined-cjs-global-like-variables.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-undefined-cjs-global-like-variables.js) -- [es-module/test-esm-unknown-or-no-extension.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-unknown-or-no-extension.js) +- [es-module/test-esm-unknown-extension.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-unknown-extension.js) +- [es-module/test-esm-url-extname.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-url-extname.js) - [es-module/test-esm-windows.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-esm-windows.js) - [es-module/test-loaders-hidden-from-users.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-loaders-hidden-from-users.js) +- [es-module/test-vm-compile-function-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-vm-compile-function-leak.js) +- [es-module/test-vm-compile-function-lineoffset.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-vm-compile-function-lineoffset.js) +- [es-module/test-vm-contextified-script-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-vm-contextified-script-leak.js) +- [es-module/test-vm-source-text-module-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-vm-source-text-module-leak.js) +- [es-module/test-vm-synthetic-module-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-vm-synthetic-module-leak.js) +- [es-module/test-wasm-memory-out-of-bound.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-wasm-memory-out-of-bound.js) +- [es-module/test-wasm-simple.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-wasm-simple.js) +- [es-module/test-wasm-web-api.js](https://github.com/nodejs/node/tree/v18.12.1/test/es-module/test-wasm-web-api.js) - [internet/test-corepack-yarn-install.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-corepack-yarn-install.js) - [internet/test-dgram-broadcast-multi-process.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-dgram-broadcast-multi-process.js) - [internet/test-dgram-connect.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-dgram-connect.js) @@ -95,22 +113,24 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [internet/test-https-autoselectfamily-slow-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-https-autoselectfamily-slow-timeout.js) - [internet/test-https-issue-43963.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-https-issue-43963.js) - [internet/test-inspector-help-page.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-inspector-help-page.js) +- [internet/test-net-autoselectfamily-timeout-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-net-autoselectfamily-timeout-close.js) - [internet/test-net-connect-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-net-connect-timeout.js) - [internet/test-net-connect-unref.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-net-connect-unref.js) - [internet/test-snapshot-dns-lookup.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-snapshot-dns-lookup.js) - [internet/test-snapshot-dns-resolve.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-snapshot-dns-resolve.js) - [internet/test-tls-add-ca-cert.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-tls-add-ca-cert.js) +- [internet/test-tls-autoselectfamily-backing-socket.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-tls-autoselectfamily-backing-socket.js) - [internet/test-tls-autoselectfamily-servername.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-tls-autoselectfamily-servername.js) - [internet/test-trace-events-dns.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-trace-events-dns.js) - [internet/test-uv-threadpool-schedule.js](https://github.com/nodejs/node/tree/v18.12.1/test/internet/test-uv-threadpool-schedule.js) +- [known_issues/test-cli-print-var-crypto.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-cli-print-var-crypto.js) - [known_issues/test-cwd-enoent-file.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-cwd-enoent-file.js) - [known_issues/test-dgram-bind-shared-ports-after-port-0.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js) - [known_issues/test-fs-writeFileSync-invalid-windows.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-fs-writeFileSync-invalid-windows.js) - [known_issues/test-http-path-contains-unicode.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-http-path-contains-unicode.js) -- [known_issues/test-http2-trailers-after-session-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-http2-trailers-after-session-close.js) - [known_issues/test-inspector-cluster-port-clash.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-inspector-cluster-port-clash.js) +- [known_issues/test-permission-model-path-resolution.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-permission-model-path-resolution.js) - [known_issues/test-repl-require-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-repl-require-context.js) -- [known_issues/test-shadow-realm-gc.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-shadow-realm-gc.js) - [known_issues/test-stdin-is-always-net.socket.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-stdin-is-always-net.socket.js) - [known_issues/test-stream-writable-sync-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-stream-writable-sync-error.js) - [known_issues/test-url-parse-conformance.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-url-parse-conformance.js) @@ -121,31 +141,14 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [known_issues/test-vm-timeout-escape-nexttick.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-vm-timeout-escape-nexttick.js) - [known_issues/test-vm-timeout-escape-queuemicrotask.js](https://github.com/nodejs/node/tree/v18.12.1/test/known_issues/test-vm-timeout-escape-queuemicrotask.js) - [message/assert_throws_stack.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/assert_throws_stack.js) -- [message/core_line_numbers.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/core_line_numbers.js) - [message/eval_messages.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/eval_messages.js) -- [message/if-error-has-good-stack.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/if-error-has-good-stack.js) - [message/internal_assert.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/internal_assert.js) - [message/internal_assert_fail.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/internal_assert_fail.js) - [message/max_tick_depth.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/max_tick_depth.js) - [message/nexttick_throw.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/nexttick_throw.js) -- [message/promise_unhandled_warn_with_error.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/promise_unhandled_warn_with_error.js) -- [message/source_map_enclosing_function.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/source_map_enclosing_function.js) -- [message/source_map_reference_error_tabs.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/source_map_reference_error_tabs.js) -- [message/source_map_sourcemapping_url_string.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/source_map_sourcemapping_url_string.js) -- [message/source_map_throw_catch.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/source_map_throw_catch.js) -- [message/source_map_throw_icu.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/source_map_throw_icu.js) -- [message/source_map_throw_set_immediate.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/source_map_throw_set_immediate.js) - [message/stdin_messages.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/stdin_messages.js) -- [message/test-no-extra-info-on-fatal-exception.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/test-no-extra-info-on-fatal-exception.js) -- [message/throw_error_with_getter_throw.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/throw_error_with_getter_throw.js) -- [message/throw_null.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/throw_null.js) -- [message/throw_undefined.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/throw_undefined.js) -- [message/timeout_throw.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/timeout_throw.js) -- [message/undefined_reference_in_new_context.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/undefined_reference_in_new_context.js) -- [message/unhandled_promise_trace_warnings.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/unhandled_promise_trace_warnings.js) - [message/util-inspect-error-cause.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/util-inspect-error-cause.js) - [message/util_inspect_error.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/util_inspect_error.js) -- [message/v8_warning.js](https://github.com/nodejs/node/tree/v18.12.1/test/message/v8_warning.js) - [parallel/test-abortcontroller.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-abortcontroller.js) - [parallel/test-aborted-util.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-aborted-util.js) - [parallel/test-abortsignal-cloneable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-abortsignal-cloneable.js) @@ -158,6 +161,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-assert-calltracker-verify.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-calltracker-verify.js) - [parallel/test-assert-checktag.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-checktag.js) - [parallel/test-assert-deep.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-deep.js) +- [parallel/test-assert-esm-cjs-message-verify.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-esm-cjs-message-verify.js) - [parallel/test-assert-fail-deprecation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-fail-deprecation.js) - [parallel/test-assert-first-line.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-first-line.js) - [parallel/test-assert-if-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-assert-if-error.js) @@ -215,6 +219,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-binding-constants.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-binding-constants.js) - [parallel/test-blob-buffer-too-large.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blob-buffer-too-large.js) - [parallel/test-blob-createobjecturl.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blob-createobjecturl.js) +- [parallel/test-blob-file-backed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blob-file-backed.js) - [parallel/test-blob.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blob.js) - [parallel/test-blocklist-clone.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blocklist-clone.js) - [parallel/test-blocklist.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blocklist.js) @@ -230,6 +235,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-buffer-pending-deprecation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-pending-deprecation.js) - [parallel/test-buffer-pool-untransferable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-pool-untransferable.js) - [parallel/test-buffer-prototype-inspect.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-prototype-inspect.js) +- [parallel/test-buffer-set-inspect-max-bytes.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-set-inspect-max-bytes.js) - [parallel/test-buffer-sharedarraybuffer.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-sharedarraybuffer.js) - [parallel/test-buffer-write.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-write.js) - [parallel/test-c-ares.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-c-ares.js) @@ -323,6 +329,8 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-cli-node-print-help.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-node-print-help.js) - [parallel/test-cli-options-negation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-options-negation.js) - [parallel/test-cli-options-precedence.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-options-precedence.js) +- [parallel/test-cli-permission-deny-fs.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-permission-deny-fs.js) +- [parallel/test-cli-permission-multiple-allow.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-permission-multiple-allow.js) - [parallel/test-cli-syntax-eval.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-syntax-eval.js) - [parallel/test-cli-syntax-piped-bad.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-syntax-piped-bad.js) - [parallel/test-cli-syntax-piped-good.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-cli-syntax-piped-good.js) @@ -432,7 +440,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-crypto-async-sign-verify.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-async-sign-verify.js) - [parallel/test-crypto-authenticated-stream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-authenticated-stream.js) - [parallel/test-crypto-authenticated.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-authenticated.js) -- [parallel/test-crypto-binary-default.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-binary-default.js) - [parallel/test-crypto-certificate.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-certificate.js) - [parallel/test-crypto-cipher-decipher.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-cipher-decipher.js) - [parallel/test-crypto-cipheriv-decipheriv.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-cipheriv-decipheriv.js) @@ -440,6 +447,9 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-crypto-des3-wrap.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-des3-wrap.js) - [parallel/test-crypto-dh-constructor.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-constructor.js) - [parallel/test-crypto-dh-curves.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-curves.js) +- [parallel/test-crypto-dh-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-errors.js) +- [parallel/test-crypto-dh-generate-keys.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-generate-keys.js) +- [parallel/test-crypto-dh-group-setters.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-group-setters.js) - [parallel/test-crypto-dh-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-leak.js) - [parallel/test-crypto-dh-modp2-views.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-modp2-views.js) - [parallel/test-crypto-dh-modp2.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-modp2.js) @@ -458,10 +468,41 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-crypto-hash.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-hash.js) - [parallel/test-crypto-key-objects-messageport.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-key-objects-messageport.js) - [parallel/test-crypto-key-objects.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-key-objects.js) +- [parallel/test-crypto-keygen-async-dsa-key-object.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-dsa-key-object.js) +- [parallel/test-crypto-keygen-async-dsa.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-dsa.js) +- [parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js) +- [parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js) +- [parallel/test-crypto-keygen-async-elliptic-curve-jwk.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js) +- [parallel/test-crypto-keygen-async-encrypted-private-key-der.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js) +- [parallel/test-crypto-keygen-async-encrypted-private-key.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-encrypted-private-key.js) +- [parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js) +- [parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js) +- [parallel/test-crypto-keygen-async-explicit-elliptic-curve.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js) +- [parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js) +- [parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js) +- [parallel/test-crypto-keygen-async-named-elliptic-curve.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-named-elliptic-curve.js) +- [parallel/test-crypto-keygen-async-rsa.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-rsa.js) +- [parallel/test-crypto-keygen-bit-length.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-bit-length.js) - [parallel/test-crypto-keygen-deprecation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-deprecation.js) +- [parallel/test-crypto-keygen-dh-classic.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-dh-classic.js) +- [parallel/test-crypto-keygen-duplicate-deprecated-option.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js) +- [parallel/test-crypto-keygen-eddsa.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-eddsa.js) +- [parallel/test-crypto-keygen-empty-passphrase-no-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js) +- [parallel/test-crypto-keygen-empty-passphrase-no-prompt.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js) +- [parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js) +- [parallel/test-crypto-keygen-invalid-parameter-encoding-ec.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-invalid-parameter-encoding-ec.js) +- [parallel/test-crypto-keygen-key-object-without-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-key-object-without-encoding.js) +- [parallel/test-crypto-keygen-key-objects.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-key-objects.js) +- [parallel/test-crypto-keygen-missing-oid.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-missing-oid.js) +- [parallel/test-crypto-keygen-no-rsassa-pss-params.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js) +- [parallel/test-crypto-keygen-non-standard-public-exponent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-non-standard-public-exponent.js) +- [parallel/test-crypto-keygen-promisify.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-promisify.js) +- [parallel/test-crypto-keygen-rfc8017-9-1.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-rfc8017-9-1.js) +- [parallel/test-crypto-keygen-rfc8017-a-2-3.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js) +- [parallel/test-crypto-keygen-rsa-pss.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-rsa-pss.js) +- [parallel/test-crypto-keygen-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-sync.js) - [parallel/test-crypto-keygen.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen.js) - [parallel/test-crypto-lazy-transform-writable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-lazy-transform-writable.js) -- [parallel/test-crypto-modp1-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-modp1-error.js) - [parallel/test-crypto-no-algorithm.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-no-algorithm.js) - [parallel/test-crypto-op-during-process-exit.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-op-during-process-exit.js) - [parallel/test-crypto-padding-aes256.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-padding-aes256.js) @@ -473,6 +514,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-crypto-random.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-random.js) - [parallel/test-crypto-randomfillsync-regression.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-randomfillsync-regression.js) - [parallel/test-crypto-randomuuid.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-randomuuid.js) +- [parallel/test-crypto-rsa-dsa-revert.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-rsa-dsa-revert.js) - [parallel/test-crypto-rsa-dsa.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-rsa-dsa.js) - [parallel/test-crypto-scrypt.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-scrypt.js) - [parallel/test-crypto-secure-heap.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-secure-heap.js) @@ -582,11 +624,23 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-dgram-udp6-send-default-host.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dgram-udp6-send-default-host.js) - [parallel/test-dgram-unref-in-cluster.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dgram-unref-in-cluster.js) - [parallel/test-dgram-unref.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dgram-unref.js) +- [parallel/test-diagnostics-channel-bind-store.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-bind-store.js) - [parallel/test-diagnostics-channel-http-server-start.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-http-server-start.js) - [parallel/test-diagnostics-channel-http.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-http.js) - [parallel/test-diagnostics-channel-memory-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-memory-leak.js) - [parallel/test-diagnostics-channel-net.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-net.js) +- [parallel/test-diagnostics-channel-process.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-process.js) - [parallel/test-diagnostics-channel-safe-subscriber-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-safe-subscriber-errors.js) +- [parallel/test-diagnostics-channel-sync-unsubscribe.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-sync-unsubscribe.js) +- [parallel/test-diagnostics-channel-tracing-channel-args-types.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-args-types.js) +- [parallel/test-diagnostics-channel-tracing-channel-async-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-async-error.js) +- [parallel/test-diagnostics-channel-tracing-channel-async.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-async.js) +- [parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js) +- [parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js) +- [parallel/test-diagnostics-channel-tracing-channel-run-stores.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-run-stores.js) +- [parallel/test-diagnostics-channel-tracing-channel-sync-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-sync-error.js) +- [parallel/test-diagnostics-channel-tracing-channel-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-sync.js) +- [parallel/test-diagnostics-channel-worker-threads.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-worker-threads.js) - [parallel/test-directory-import.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-directory-import.js) - [parallel/test-disable-proto-delete.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-disable-proto-delete.js) - [parallel/test-disable-proto-throw.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-disable-proto-throw.js) @@ -661,6 +715,9 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-domain-vm-promise-isolation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-domain-vm-promise-isolation.js) - [parallel/test-domain-with-abort-on-uncaught-exception.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-domain-with-abort-on-uncaught-exception.js) - [parallel/test-domexception-cause.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-domexception-cause.js) +- [parallel/test-dotenv-edge-cases.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dotenv-edge-cases.js) +- [parallel/test-dotenv-node-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dotenv-node-options.js) +- [parallel/test-dotenv.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dotenv.js) - [parallel/test-double-tls-client.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-double-tls-client.js) - [parallel/test-double-tls-server.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-double-tls-server.js) - [parallel/test-dsa-fips-invalid-key.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-dsa-fips-invalid-key.js) @@ -700,6 +757,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-eslint-prefer-common-mustnotcall.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-prefer-common-mustnotcall.js) - [parallel/test-eslint-prefer-common-mustsucceed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-prefer-common-mustsucceed.js) - [parallel/test-eslint-prefer-primordials.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-prefer-primordials.js) +- [parallel/test-eslint-prefer-proto.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-prefer-proto.js) - [parallel/test-eslint-prefer-util-format-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-prefer-util-format-errors.js) - [parallel/test-eslint-require-common-first.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-require-common-first.js) - [parallel/test-eslint-required-modules.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eslint-required-modules.js) @@ -709,6 +767,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-event-emitter-max-listeners-warning-for-null.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-event-emitter-max-listeners-warning-for-null.js) - [parallel/test-event-emitter-max-listeners-warning-for-symbol.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-event-emitter-max-listeners-warning-for-symbol.js) - [parallel/test-event-emitter-max-listeners-warning.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-event-emitter-max-listeners-warning.js) +- [parallel/test-event-target.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-event-target.js) - [parallel/test-eventemitter-asyncresource.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eventemitter-asyncresource.js) - [parallel/test-events-customevent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-events-customevent.js) - [parallel/test-events-getmaxlisteners.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-events-getmaxlisteners.js) @@ -717,6 +776,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-eventtarget-memoryleakwarning.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eventtarget-memoryleakwarning.js) - [parallel/test-eventtarget-once-twice.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eventtarget-once-twice.js) - [parallel/test-eventtarget.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-eventtarget.js) +- [parallel/test-experimental-shared-value-conveyor.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-experimental-shared-value-conveyor.js) - [parallel/test-file-validate-mode-flag.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-file-validate-mode-flag.js) - [parallel/test-file.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-file.js) - [parallel/test-filehandle-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-filehandle-close.js) @@ -726,6 +786,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-force-repl.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-force-repl.js) - [parallel/test-freelist.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-freelist.js) - [parallel/test-freeze-intrinsics.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-freeze-intrinsics.js) +- [parallel/test-fs-append-file-flush.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-append-file-flush.js) - [parallel/test-fs-assert-encoding-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-assert-encoding-error.js) - [parallel/test-fs-buffer.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-buffer.js) - [parallel/test-fs-buffertype-writesync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-buffertype-writesync.js) @@ -853,7 +914,17 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-fs-watch-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-encoding.js) - [parallel/test-fs-watch-enoent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-enoent.js) - [parallel/test-fs-watch-file-enoent-after-deletion.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-file-enoent-after-deletion.js) -- [parallel/test-fs-watch-recursive.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive.js) +- [parallel/test-fs-watch-recursive-add-file-to-existing-subfolder.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-add-file-to-existing-subfolder.js) +- [parallel/test-fs-watch-recursive-add-file-to-new-folder.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-add-file-to-new-folder.js) +- [parallel/test-fs-watch-recursive-add-file-with-url.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-add-file-with-url.js) +- [parallel/test-fs-watch-recursive-add-file.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-add-file.js) +- [parallel/test-fs-watch-recursive-add-folder.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-add-folder.js) +- [parallel/test-fs-watch-recursive-assert-leaks.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-assert-leaks.js) +- [parallel/test-fs-watch-recursive-promise.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-promise.js) +- [parallel/test-fs-watch-recursive-symlink.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-symlink.js) +- [parallel/test-fs-watch-recursive-update-file.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-update-file.js) +- [parallel/test-fs-watch-recursive-validate.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-validate.js) +- [parallel/test-fs-watch-recursive-watch-file.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-recursive-watch-file.js) - [parallel/test-fs-watch-ref-unref.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-ref-unref.js) - [parallel/test-fs-watch-stop-async.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-stop-async.js) - [parallel/test-fs-watch-stop-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-stop-sync.js) @@ -862,6 +933,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-fs-watchfile-ref-unref.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watchfile-ref-unref.js) - [parallel/test-fs-whatwg-url.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-whatwg-url.js) - [parallel/test-fs-write-buffer-large.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-buffer-large.js) +- [parallel/test-fs-write-file-flush.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-file-flush.js) - [parallel/test-fs-write-file-typedarrays.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-file-typedarrays.js) - [parallel/test-fs-write-negativeoffset.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-negativeoffset.js) - [parallel/test-fs-write-optional-params.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-optional-params.js) @@ -872,6 +944,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-fs-write-stream-err.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-stream-err.js) - [parallel/test-fs-write-stream-file-handle-2.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-stream-file-handle-2.js) - [parallel/test-fs-write-stream-file-handle.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-stream-file-handle.js) +- [parallel/test-fs-write-stream-flush.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-stream-flush.js) - [parallel/test-fs-write-stream-patch-open.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-stream-patch-open.js) - [parallel/test-fs-write-sync-optional-params.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-write-sync-optional-params.js) - [parallel/test-fs-writefile-with-fd.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-writefile-with-fd.js) @@ -881,11 +954,13 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-gc-net-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-gc-net-timeout.js) - [parallel/test-gc-tls-external-memory.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-gc-tls-external-memory.js) - [parallel/test-global-console-exists.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-console-exists.js) +- [parallel/test-global-customevent-disabled.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-customevent-disabled.js) - [parallel/test-global-customevent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-customevent.js) - [parallel/test-global-domexception.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-domexception.js) - [parallel/test-global-encoder.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-encoder.js) - [parallel/test-global-setters.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-setters.js) - [parallel/test-global-webcrypto-classes.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-webcrypto-classes.js) +- [parallel/test-global-webcrypto-disbled.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-webcrypto-disbled.js) - [parallel/test-global-webcrypto.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-webcrypto.js) - [parallel/test-global-webstreams.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global-webstreams.js) - [parallel/test-global.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-global.js) @@ -944,6 +1019,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http-buffer-sanity.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-buffer-sanity.js) - [parallel/test-http-byteswritten.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-byteswritten.js) - [parallel/test-http-catch-uncaughtexception.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-catch-uncaughtexception.js) +- [parallel/test-http-chunk-extensions-limit.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-chunk-extensions-limit.js) - [parallel/test-http-chunk-problem.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-chunk-problem.js) - [parallel/test-http-chunked-304.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-chunked-304.js) - [parallel/test-http-chunked-smuggling.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-chunked-smuggling.js) @@ -965,6 +1041,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http-client-agent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-agent.js) - [parallel/test-http-client-check-http-token.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-check-http-token.js) - [parallel/test-http-client-close-event.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-close-event.js) +- [parallel/test-http-client-close-with-default-agent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-close-with-default-agent.js) - [parallel/test-http-client-default-headers-exist.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-default-headers-exist.js) - [parallel/test-http-client-defaults.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-defaults.js) - [parallel/test-http-client-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-encoding.js) @@ -975,6 +1052,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http-client-immediate-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-immediate-error.js) - [parallel/test-http-client-incomingmessage-destroy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-incomingmessage-destroy.js) - [parallel/test-http-client-invalid-path.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-invalid-path.js) +- [parallel/test-http-client-keep-alive-hint.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-keep-alive-hint.js) - [parallel/test-http-client-keep-alive-release-before-finish.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-keep-alive-release-before-finish.js) - [parallel/test-http-client-override-global-agent.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-override-global-agent.js) - [parallel/test-http-client-parse-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-client-parse-error.js) @@ -1131,6 +1209,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http-proxy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-proxy.js) - [parallel/test-http-raw-headers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-raw-headers.js) - [parallel/test-http-readable-data-event.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-readable-data-event.js) +- [parallel/test-http-remove-connection-header-persists-connection.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-remove-connection-header-persists-connection.js) - [parallel/test-http-remove-header-stays-removed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-remove-header-stays-removed.js) - [parallel/test-http-req-close-robust-from-tampering.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-req-close-robust-from-tampering.js) - [parallel/test-http-req-res-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-req-res-close.js) @@ -1139,6 +1218,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http-request-dont-override-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-dont-override-options.js) - [parallel/test-http-request-end-twice.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-end-twice.js) - [parallel/test-http-request-end.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-end.js) +- [parallel/test-http-request-host-header.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-host-header.js) - [parallel/test-http-request-invalid-method-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-invalid-method-error.js) - [parallel/test-http-request-join-authorization-headers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-join-authorization-headers.js) - [parallel/test-http-request-large-payload.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-request-large-payload.js) @@ -1160,12 +1240,15 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http-response-statuscode.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-response-statuscode.js) - [parallel/test-http-response-writehead-returns-this.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-response-writehead-returns-this.js) - [parallel/test-http-same-map.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-same-map.js) +- [parallel/test-http-server-async-dispose.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-async-dispose.js) - [parallel/test-http-server-capture-rejections.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-capture-rejections.js) - [parallel/test-http-server-client-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-client-error.js) - [parallel/test-http-server-close-all.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-close-all.js) +- [parallel/test-http-server-close-destroy-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-close-destroy-timeout.js) - [parallel/test-http-server-close-idle-wait-response.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-close-idle-wait-response.js) - [parallel/test-http-server-close-idle.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-close-idle.js) - [parallel/test-http-server-connection-list-when-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-connection-list-when-close.js) +- [parallel/test-http-server-connections-checking-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-connections-checking-leak.js) - [parallel/test-http-server-consumed-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-consumed-timeout.js) - [parallel/test-http-server-de-chunked-trailer.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-de-chunked-trailer.js) - [parallel/test-http-server-delete-parser.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-server-delete-parser.js) @@ -1251,6 +1334,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http2-cancel-while-client-reading.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-cancel-while-client-reading.js) - [parallel/test-http2-capture-rejection.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-capture-rejection.js) - [parallel/test-http2-clean-output.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-clean-output.js) +- [parallel/test-http2-client-connection-tunnelling.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-client-connection-tunnelling.js) - [parallel/test-http2-client-data-end.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-client-data-end.js) - [parallel/test-http2-client-destroy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-client-destroy.js) - [parallel/test-http2-client-http1-server.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-client-http1-server.js) @@ -1427,6 +1511,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http2-sensitive-headers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-sensitive-headers.js) - [parallel/test-http2-sent-headers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-sent-headers.js) - [parallel/test-http2-serve-file.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-serve-file.js) +- [parallel/test-http2-server-async-dispose.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-server-async-dispose.js) - [parallel/test-http2-server-close-callback.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-server-close-callback.js) - [parallel/test-http2-server-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-server-errors.js) - [parallel/test-http2-server-http1-client.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-server-http1-client.js) @@ -1458,6 +1543,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http2-settings-unsolicited-ack.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-settings-unsolicited-ack.js) - [parallel/test-http2-short-stream-client-server.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-short-stream-client-server.js) - [parallel/test-http2-single-headers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-single-headers.js) +- [parallel/test-http2-socket-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-socket-close.js) - [parallel/test-http2-socket-proxy-handler-for-has.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-socket-proxy-handler-for-has.js) - [parallel/test-http2-socket-proxy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-socket-proxy.js) - [parallel/test-http2-status-code-invalid.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-status-code-invalid.js) @@ -1471,6 +1557,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-http2-too-many-headers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-too-many-headers.js) - [parallel/test-http2-too-many-settings.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-too-many-settings.js) - [parallel/test-http2-too-many-streams.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-too-many-streams.js) +- [parallel/test-http2-trailers-after-session-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-trailers-after-session-close.js) - [parallel/test-http2-trailers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-trailers.js) - [parallel/test-http2-unbound-socket-proxy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-unbound-socket-proxy.js) - [parallel/test-http2-update-settings.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http2-update-settings.js) @@ -1529,8 +1616,11 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-https-request-arguments.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-request-arguments.js) - [parallel/test-https-resume-after-renew.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-resume-after-renew.js) - [parallel/test-https-selfsigned-no-keycertsign-no-crash.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-selfsigned-no-keycertsign-no-crash.js) +- [parallel/test-https-server-async-dispose.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-async-dispose.js) - [parallel/test-https-server-close-all.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-close-all.js) +- [parallel/test-https-server-close-destroy-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-close-destroy-timeout.js) - [parallel/test-https-server-close-idle.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-close-idle.js) +- [parallel/test-https-server-connections-checking-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-connections-checking-leak.js) - [parallel/test-https-server-headers-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-headers-timeout.js) - [parallel/test-https-server-options-incoming-message.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-options-incoming-message.js) - [parallel/test-https-server-options-server-response.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-https-server-options-server-response.js) @@ -1593,6 +1683,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-inspector-overwrite-config.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-overwrite-config.js) - [parallel/test-inspector-port-zero-cluster.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-port-zero-cluster.js) - [parallel/test-inspector-port-zero.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-port-zero.js) +- [parallel/test-inspector-promises.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-promises.js) - [parallel/test-inspector-reported-host.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-reported-host.js) - [parallel/test-inspector-resource-name-to-url.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-resource-name-to-url.js) - [parallel/test-inspector-runtime-evaluate-with-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector-runtime-evaluate-with-timeout.js) @@ -1609,7 +1700,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-inspector.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-inspector.js) - [parallel/test-instanceof.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-instanceof.js) - [parallel/test-internal-assert.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-internal-assert.js) -- [parallel/test-internal-dtrace.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-internal-dtrace.js) - [parallel/test-internal-error-original-names.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-internal-error-original-names.js) - [parallel/test-internal-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-internal-errors.js) - [parallel/test-internal-fs-syncwritestream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-internal-fs-syncwritestream.js) @@ -1644,6 +1734,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-math-random.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-math-random.js) - [parallel/test-memory-usage-emfile.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-memory-usage-emfile.js) - [parallel/test-memory-usage.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-memory-usage.js) +- [parallel/test-messagechannel.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-messagechannel.js) - [parallel/test-messageevent-brandcheck.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-messageevent-brandcheck.js) - [parallel/test-messageport-hasref.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-messageport-hasref.js) - [parallel/test-messaging-maketransferable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-messaging-maketransferable.js) @@ -1683,9 +1774,9 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-net-after-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-after-close.js) - [parallel/test-net-allow-half-open.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-allow-half-open.js) - [parallel/test-net-autoselectfamily-commandline-option.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-commandline-option.js) +- [parallel/test-net-autoselectfamily-default.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-default.js) - [parallel/test-net-autoselectfamily-ipv4first.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-ipv4first.js) - [parallel/test-net-autoselectfamily.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily.js) -- [parallel/test-net-autoselectfamilydefault.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamilydefault.js) - [parallel/test-net-better-error-messages-listen.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-better-error-messages-listen.js) - [parallel/test-net-binary.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-binary.js) - [parallel/test-net-bind-twice.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-bind-twice.js) @@ -1804,16 +1895,41 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-perf-hooks-histogram.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-perf-hooks-histogram.js) - [parallel/test-perf-hooks-resourcetiming.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-perf-hooks-resourcetiming.js) - [parallel/test-perf-hooks-usertiming.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-perf-hooks-usertiming.js) +- [parallel/test-perf-hooks-worker-timeorigin.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-perf-hooks-worker-timeorigin.js) - [parallel/test-performance-eventlooputil.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-eventlooputil.js) - [parallel/test-performance-function-async.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-function-async.js) - [parallel/test-performance-function.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-function.js) - [parallel/test-performance-gc.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-gc.js) - [parallel/test-performance-global.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-global.js) - [parallel/test-performance-measure.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-measure.js) +- [parallel/test-performance-nodetiming.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-nodetiming.js) - [parallel/test-performance-resourcetimingbufferfull.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-resourcetimingbufferfull.js) - [parallel/test-performance-resourcetimingbuffersize.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performance-resourcetimingbuffersize.js) - [parallel/test-performanceobserver-gc.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performanceobserver-gc.js) - [parallel/test-performanceobserver.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-performanceobserver.js) +- [parallel/test-permission-allow-child-process-cli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-allow-child-process-cli.js) +- [parallel/test-permission-allow-worker-cli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-allow-worker-cli.js) +- [parallel/test-permission-child-process-cli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-child-process-cli.js) +- [parallel/test-permission-experimental.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-experimental.js) +- [parallel/test-permission-fs-read.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-read.js) +- [parallel/test-permission-fs-relative-path.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-relative-path.js) +- [parallel/test-permission-fs-supported.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-supported.js) +- [parallel/test-permission-fs-symlink-relative.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-symlink-relative.js) +- [parallel/test-permission-fs-symlink-target-write.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-symlink-target-write.js) +- [parallel/test-permission-fs-symlink.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-symlink.js) +- [parallel/test-permission-fs-traversal-path.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-traversal-path.js) +- [parallel/test-permission-fs-wildcard.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-wildcard.js) +- [parallel/test-permission-fs-windows-path.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-windows-path.js) +- [parallel/test-permission-fs-write-report.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-write-report.js) +- [parallel/test-permission-fs-write-v8.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-write-v8.js) +- [parallel/test-permission-fs-write.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-fs-write.js) +- [parallel/test-permission-has.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-has.js) +- [parallel/test-permission-inspector.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-inspector.js) +- [parallel/test-permission-processbinding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-processbinding.js) +- [parallel/test-permission-warning-flags.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-warning-flags.js) +- [parallel/test-permission-worker-threads-cli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-permission-worker-threads-cli.js) +- [parallel/test-pipe-abstract-socket-http.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-pipe-abstract-socket-http.js) +- [parallel/test-pipe-abstract-socket.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-pipe-abstract-socket.js) - [parallel/test-pipe-address.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-pipe-address.js) - [parallel/test-pipe-file-to-http.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-pipe-file-to-http.js) - [parallel/test-pipe-head.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-pipe-head.js) @@ -1875,6 +1991,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-process-exception-capture.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-exception-capture.js) - [parallel/test-process-exec-argv.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-exec-argv.js) - [parallel/test-process-execpath.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-execpath.js) +- [parallel/test-process-exit-code-validation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-exit-code-validation.js) - [parallel/test-process-exit-code.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-exit-code.js) - [parallel/test-process-external-stdio-close-spawn.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-external-stdio-close-spawn.js) - [parallel/test-process-external-stdio-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-external-stdio-close.js) @@ -1904,6 +2021,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-process-remove-all-signal-listeners.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-remove-all-signal-listeners.js) - [parallel/test-process-setgroups.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-setgroups.js) - [parallel/test-process-setsourcemapsenabled.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-setsourcemapsenabled.js) +- [parallel/test-process-setuid-io-uring.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-setuid-io-uring.js) - [parallel/test-process-title-cli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-title-cli.js) - [parallel/test-process-uid-gid.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-uid-gid.js) - [parallel/test-process-umask-mask.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-process-umask-mask.js) @@ -1985,6 +2103,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-repl-null-thrown.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-null-thrown.js) - [parallel/test-repl-null.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-null.js) - [parallel/test-repl-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-options.js) +- [parallel/test-repl-permission-model.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-permission-model.js) - [parallel/test-repl-persistent-history.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-persistent-history.js) - [parallel/test-repl-preprocess-top-level-await.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-preprocess-top-level-await.js) - [parallel/test-repl-pretty-custom-stack.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-repl-pretty-custom-stack.js) @@ -2049,16 +2168,21 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-require-symlink.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-require-symlink.js) - [parallel/test-require-unicode.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-require-unicode.js) - [parallel/test-resource-usage.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-resource-usage.js) +- [parallel/test-runner-cli-concurrency.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-cli-concurrency.js) +- [parallel/test-runner-cli-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-cli-timeout.js) - [parallel/test-runner-cli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-cli.js) - [parallel/test-runner-concurrency.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-concurrency.js) - [parallel/test-runner-coverage.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-coverage.js) - [parallel/test-runner-exit-code.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-exit-code.js) - [parallel/test-runner-extraneous-async-activity.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-extraneous-async-activity.js) +- [parallel/test-runner-filetest-location.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-filetest-location.js) - [parallel/test-runner-import-no-scheme.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-import-no-scheme.js) - [parallel/test-runner-misc.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-misc.js) +- [parallel/test-runner-mock-timers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-mock-timers.js) - [parallel/test-runner-mocking.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-mocking.js) - [parallel/test-runner-option-validation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-option-validation.js) - [parallel/test-runner-reporters.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-reporters.js) +- [parallel/test-runner-root-after-with-refed-handles.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-root-after-with-refed-handles.js) - [parallel/test-runner-string-to-regexp.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-string-to-regexp.js) - [parallel/test-runner-test-filter.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-test-filter.js) - [parallel/test-runner-typechecking.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-runner-typechecking.js) @@ -2068,6 +2192,15 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-set-incoming-message-header.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-set-incoming-message-header.js) - [parallel/test-set-process-debug-port.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-set-process-debug-port.js) - [parallel/test-setproctitle.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-setproctitle.js) +- [parallel/test-shadow-realm-allowed-builtin-modules.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-allowed-builtin-modules.js) +- [parallel/test-shadow-realm-custom-loaders.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-custom-loaders.js) +- [parallel/test-shadow-realm-gc-module.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-gc-module.js) +- [parallel/test-shadow-realm-gc.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-gc.js) +- [parallel/test-shadow-realm-globals.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-globals.js) +- [parallel/test-shadow-realm-import-value-resolve.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-import-value-resolve.js) +- [parallel/test-shadow-realm-module.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-module.js) +- [parallel/test-shadow-realm-preload-module.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-preload-module.js) +- [parallel/test-shadow-realm-prepare-stack-trace.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm-prepare-stack-trace.js) - [parallel/test-shadow-realm.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-shadow-realm.js) - [parallel/test-sigint-infinite-loop.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-sigint-infinite-loop.js) - [parallel/test-signal-args.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-signal-args.js) @@ -2075,12 +2208,14 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-signal-handler.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-signal-handler.js) - [parallel/test-signal-safety.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-signal-safety.js) - [parallel/test-signal-unregister.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-signal-unregister.js) -- [parallel/test-single-executable-application.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-single-executable-application.js) +- [parallel/test-single-executable-blob-config-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-single-executable-blob-config-errors.js) +- [parallel/test-single-executable-blob-config.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-single-executable-blob-config.js) - [parallel/test-snapshot-api.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-api.js) - [parallel/test-snapshot-argv1.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-argv1.js) - [parallel/test-snapshot-basic.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-basic.js) - [parallel/test-snapshot-cjs-main.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-cjs-main.js) - [parallel/test-snapshot-console.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-console.js) +- [parallel/test-snapshot-cwd.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-cwd.js) - [parallel/test-snapshot-dns-lookup-localhost-promise.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-dns-lookup-localhost-promise.js) - [parallel/test-snapshot-dns-lookup-localhost.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-dns-lookup-localhost.js) - [parallel/test-snapshot-dns-resolve-localhost-promise.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-dns-resolve-localhost-promise.js) @@ -2090,10 +2225,12 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-snapshot-gzip.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-gzip.js) - [parallel/test-snapshot-incompatible.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-incompatible.js) - [parallel/test-snapshot-namespaced-builtin.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-namespaced-builtin.js) +- [parallel/test-snapshot-net.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-net.js) - [parallel/test-snapshot-typescript.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-typescript.js) - [parallel/test-snapshot-umd.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-umd.js) - [parallel/test-snapshot-warning.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-warning.js) - [parallel/test-snapshot-weak-reference.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-weak-reference.js) +- [parallel/test-snapshot-worker.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-snapshot-worker.js) - [parallel/test-socket-address.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-socket-address.js) - [parallel/test-socket-options-invalid.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-socket-options-invalid.js) - [parallel/test-socket-write-after-fin-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-socket-write-after-fin-error.js) @@ -2158,6 +2295,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-stream-promises.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-promises.js) - [parallel/test-stream-push-order.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-push-order.js) - [parallel/test-stream-readable-async-iterators.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-readable-async-iterators.js) +- [parallel/test-stream-readable-default-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-readable-default-encoding.js) - [parallel/test-stream-readable-dispose.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-readable-dispose.js) - [parallel/test-stream-readable-strategy-option.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-readable-strategy-option.js) - [parallel/test-stream-readable-unpipe-resume.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-readable-unpipe-resume.js) @@ -2323,6 +2461,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-tls-honorcipherorder.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-honorcipherorder.js) - [parallel/test-tls-inception.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-inception.js) - [parallel/test-tls-interleave.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-interleave.js) +- [parallel/test-tls-invalid-pfx.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-invalid-pfx.js) - [parallel/test-tls-invoke-queued.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-invoke-queued.js) - [parallel/test-tls-ip-servername-deprecation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-ip-servername-deprecation.js) - [parallel/test-tls-js-stream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-js-stream.js) @@ -2359,6 +2498,8 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-tls-psk-circuit.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-psk-circuit.js) - [parallel/test-tls-psk-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-psk-errors.js) - [parallel/test-tls-psk-server.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-psk-server.js) +- [parallel/test-tls-reduced-SECLEVEL-in-cipher.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js) +- [parallel/test-tls-reinitialize-listeners.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-reinitialize-listeners.js) - [parallel/test-tls-request-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-request-timeout.js) - [parallel/test-tls-retain-handle-no-abort.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-retain-handle-no-abort.js) - [parallel/test-tls-reuse-host-from-socket.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-tls-reuse-host-from-socket.js) @@ -2454,6 +2595,8 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-unhandled-exception-rethrow-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-unhandled-exception-rethrow-error.js) - [parallel/test-unhandled-exception-with-worker-inuse.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-unhandled-exception-with-worker-inuse.js) - [parallel/test-unicode-node-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-unicode-node-options.js) +- [parallel/test-url-canParse-whatwg.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-url-canParse-whatwg.js) +- [parallel/test-url-is-url.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-url-is-url.js) - [parallel/test-url-null-char.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-url-null-char.js) - [parallel/test-url-parse-format.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-url-parse-format.js) - [parallel/test-utf8-scripts.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-utf8-scripts.js) @@ -2506,6 +2649,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-vm-cross-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-cross-context.js) - [parallel/test-vm-data-property-writable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-data-property-writable.js) - [parallel/test-vm-deleting-property.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-deleting-property.js) +- [parallel/test-vm-dynamic-import-callback-missing-flag.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-dynamic-import-callback-missing-flag.js) - [parallel/test-vm-function-declaration.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-function-declaration.js) - [parallel/test-vm-function-redefinition.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-function-redefinition.js) - [parallel/test-vm-getters.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-getters.js) @@ -2534,6 +2678,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-vm-module-reevaluate.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-module-reevaluate.js) - [parallel/test-vm-module-synthetic.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-module-synthetic.js) - [parallel/test-vm-new-script-new-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-new-script-new-context.js) +- [parallel/test-vm-no-dynamic-import-callback.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-no-dynamic-import-callback.js) - [parallel/test-vm-not-strict.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-not-strict.js) - [parallel/test-vm-options-validation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-options-validation.js) - [parallel/test-vm-parse-abort-on-uncaught-exception.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-parse-abort-on-uncaught-exception.js) @@ -2559,9 +2704,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-vm-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-timeout.js) - [parallel/test-warn-sigprof.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-warn-sigprof.js) - [parallel/test-warn-stream-wrap.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-warn-stream-wrap.js) -- [parallel/test-wasm-memory-out-of-bound.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-wasm-memory-out-of-bound.js) -- [parallel/test-wasm-simple.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-wasm-simple.js) -- [parallel/test-wasm-web-api.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-wasm-web-api.js) - [parallel/test-weakref.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-weakref.js) - [parallel/test-webcrypto-constructors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webcrypto-constructors.js) - [parallel/test-webcrypto-cryptokey-workers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webcrypto-cryptokey-workers.js) @@ -2590,6 +2732,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-webcrypto-util.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webcrypto-util.js) - [parallel/test-webcrypto-webidl.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webcrypto-webidl.js) - [parallel/test-webcrypto-wrap-unwrap.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webcrypto-wrap-unwrap.js) +- [parallel/test-websocket.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-websocket.js) - [parallel/test-webstream-encoding-inspect.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webstream-encoding-inspect.js) - [parallel/test-webstream-readablestream-pipeto.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webstream-readablestream-pipeto.js) - [parallel/test-webstream-string-tag.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-webstream-string-tag.js) @@ -2609,6 +2752,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-whatwg-events-eventtarget-this-of-listener.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-events-eventtarget-this-of-listener.js) - [parallel/test-whatwg-readablebytestream-bad-buffers-and-views.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-readablebytestream-bad-buffers-and-views.js) - [parallel/test-whatwg-readablebytestream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-readablebytestream.js) +- [parallel/test-whatwg-readablebytestreambyob.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-readablebytestreambyob.js) - [parallel/test-whatwg-readablestream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-readablestream.js) - [parallel/test-whatwg-transformstream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-transformstream.js) - [parallel/test-whatwg-url-canparse.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-whatwg-url-canparse.js) @@ -2776,6 +2920,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-wrap-js-stream-exceptions.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-wrap-js-stream-exceptions.js) - [parallel/test-wrap-js-stream-read-stop.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-wrap-js-stream-read-stop.js) - [parallel/test-x509-escaping.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-x509-escaping.js) +- [parallel/test-zlib-brotli-16GB.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-zlib-brotli-16GB.js) - [parallel/test-zlib-brotli-flush.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-zlib-brotli-flush.js) - [parallel/test-zlib-brotli-from-brotli.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-zlib-brotli-from-brotli.js) - [parallel/test-zlib-brotli-from-string.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-zlib-brotli-from-string.js) @@ -2855,6 +3000,7 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [pummel/test-heapdump-fs-promise.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-fs-promise.js) - [pummel/test-heapdump-http2.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-http2.js) - [pummel/test-heapdump-inspector.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-inspector.js) +- [pummel/test-heapdump-shadow-realm.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-shadow-realm.js) - [pummel/test-heapdump-tls.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-tls.js) - [pummel/test-heapdump-worker.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-worker.js) - [pummel/test-heapdump-zlib.js](https://github.com/nodejs/node/tree/v18.12.1/test/pummel/test-heapdump-zlib.js) @@ -2937,6 +3083,8 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [sequential/test-gc-http-client-onerror.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-gc-http-client-onerror.js) - [sequential/test-gc-http-client-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-gc-http-client-timeout.js) - [sequential/test-gc-http-client.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-gc-http-client.js) +- [sequential/test-get-heapsnapshot-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-get-heapsnapshot-options.js) +- [sequential/test-heapdump-flag-custom-dir.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-heapdump-flag-custom-dir.js) - [sequential/test-heapdump-flag.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-heapdump-flag.js) - [sequential/test-heapdump.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-heapdump.js) - [sequential/test-http-econnrefused.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-http-econnrefused.js) @@ -2977,6 +3125,12 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [sequential/test-repl-timeout-throw.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-repl-timeout-throw.js) - [sequential/test-require-cache-without-stat.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-require-cache-without-stat.js) - [sequential/test-resolution-inspect-brk.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-resolution-inspect-brk.js) +- [sequential/test-single-executable-application-disable-experimental-sea-warning.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-single-executable-application-disable-experimental-sea-warning.js) +- [sequential/test-single-executable-application-empty.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-single-executable-application-empty.js) +- [sequential/test-single-executable-application-snapshot-and-code-cache.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-single-executable-application-snapshot-and-code-cache.js) +- [sequential/test-single-executable-application-snapshot.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-single-executable-application-snapshot.js) +- [sequential/test-single-executable-application-use-code-cache.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-single-executable-application-use-code-cache.js) +- [sequential/test-single-executable-application.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-single-executable-application.js) - [sequential/test-stream2-fs.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-stream2-fs.js) - [sequential/test-stream2-stderr-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-stream2-stderr-sync.js) - [sequential/test-timers-block-eventloop.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-timers-block-eventloop.js) @@ -2993,4 +3147,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [sequential/test-worker-eventlooputil.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-worker-eventlooputil.js) - [sequential/test-worker-fshandles-error-on-termination.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-worker-fshandles-error-on-termination.js) - [sequential/test-worker-fshandles-open-close-on-termination.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-worker-fshandles-open-close-on-termination.js) +- [sequential/test-worker-heapsnapshot-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-worker-heapsnapshot-options.js) - [sequential/test-worker-prof.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-worker-prof.js) +- [sequential/test-write-heapsnapshot-options.js](https://github.com/nodejs/node/tree/v18.12.1/test/sequential/test-write-heapsnapshot-options.js) diff --git a/tests/node_compat/runner/suite b/tests/node_compat/runner/suite index b114fad0ec952..d12a68fc49300 160000 --- a/tests/node_compat/runner/suite +++ b/tests/node_compat/runner/suite @@ -1 +1 @@ -Subproject commit b114fad0ec952fddddefc8972c43d2959388bbc1 +Subproject commit d12a68fc4930062c0bed26447a6b5245697e77c1 diff --git a/tests/node_compat/test.ts b/tests/node_compat/test.ts index db4ba4f52d2ee..6f15f2d0b4259 100644 --- a/tests/node_compat/test.ts +++ b/tests/node_compat/test.ts @@ -128,7 +128,7 @@ async function runTest(t: Deno.TestContext, path: string): Promise { } const stderrOutput = decoder.decode(stderr); const repeatCmd = magenta( - `./target/debug/deno test -A tests/node_compat/test.ts -- ${path}`, + `./target/debug/deno test --config tests/config/deno.json -A tests/node_compat/test.ts -- ${path}`, ); const msg = `"${magenta(path)}" failed: diff --git a/tests/node_compat/test/common/child_process.js b/tests/node_compat/test/common/child_process.js index d46ec39d171b1..e30ec0c0008e6 100644 --- a/tests/node_compat/test/common/child_process.js +++ b/tests/node_compat/test/common/child_process.js @@ -8,7 +8,9 @@ 'use strict'; const assert = require('assert'); +const { spawnSync, execFileSync } = require('child_process'); const common = require('./'); +const util = require('util'); // Workaround for Windows Server 2008R2 // When CMD is used to launch a process and CMD is killed too quickly, the @@ -20,14 +22,13 @@ function cleanupStaleProcess(filename) { process.once('beforeExit', () => { const basename = filename.replace(/.*[/\\]/g, ''); try { - require('child_process') - .execFileSync(`${process.env.SystemRoot}\\System32\\wbem\\WMIC.exe`, [ - 'process', - 'where', - `commandline like '%${basename}%child'`, - 'delete', - '/nointeractive', - ]); + execFileSync(`${process.env.SystemRoot}\\System32\\wbem\\WMIC.exe`, [ + 'process', + 'where', + `commandline like '%${basename}%child'`, + 'delete', + '/nointeractive', + ]); } catch { // Ignore failures, there might not be any stale process to clean up. } @@ -48,9 +49,98 @@ function logAfterTime(time) { }, time); } +function checkOutput(str, check) { + if ((check instanceof RegExp && !check.test(str)) || + (typeof check === 'string' && check !== str)) { + return { passed: false, reason: `did not match ${util.inspect(check)}` }; + } + if (typeof check === 'function') { + try { + check(str); + } catch (error) { + return { + passed: false, + reason: `did not match expectation, checker throws:\n${util.inspect(error)}`, + }; + } + } + return { passed: true }; +} + +function expectSyncExit(child, { + status, + signal, + stderr: stderrCheck, + stdout: stdoutCheck, + trim = false, +}) { + const failures = []; + let stderrStr, stdoutStr; + if (status !== undefined && child.status !== status) { + failures.push(`- process terminated with status ${child.status}, expected ${status}`); + } + if (signal !== undefined && child.signal !== signal) { + failures.push(`- process terminated with signal ${child.signal}, expected ${signal}`); + } + + function logAndThrow() { + const tag = `[process ${child.pid}]:`; + console.error(`${tag} --- stderr ---`); + console.error(stderrStr === undefined ? child.stderr.toString() : stderrStr); + console.error(`${tag} --- stdout ---`); + console.error(stdoutStr === undefined ? child.stdout.toString() : stdoutStr); + console.error(`${tag} status = ${child.status}, signal = ${child.signal}`); + throw new Error(`${failures.join('\n')}`); + } + + // If status and signal are not matching expectations, fail early. + if (failures.length !== 0) { + logAndThrow(); + } + + if (stderrCheck !== undefined) { + stderrStr = child.stderr.toString(); + const { passed, reason } = checkOutput(trim ? stderrStr.trim() : stderrStr, stderrCheck); + if (!passed) { + failures.push(`- stderr ${reason}`); + } + } + if (stdoutCheck !== undefined) { + stdoutStr = child.stdout.toString(); + const { passed, reason } = checkOutput(trim ? stdoutStr.trim() : stdoutStr, stdoutCheck); + if (!passed) { + failures.push(`- stdout ${reason}`); + } + } + if (failures.length !== 0) { + logAndThrow(); + } + return { child, stderr: stderrStr, stdout: stdoutStr }; +} + +function spawnSyncAndExit(...args) { + const spawnArgs = args.slice(0, args.length - 1); + const expectations = args[args.length - 1]; + const child = spawnSync(...spawnArgs); + return expectSyncExit(child, expectations); +} + +function spawnSyncAndExitWithoutError(...args) { + const spawnArgs = args.slice(0, args.length); + const expectations = args[args.length - 1]; + const child = spawnSync(...spawnArgs); + return expectSyncExit(child, { + status: 0, + signal: null, + ...expectations, + }); +} + module.exports = { cleanupStaleProcess, logAfterTime, kExpiringChildRunTime, kExpiringParentTimer, + spawnSyncAndExit, + spawnSyncAndExitWithoutError, }; diff --git a/tests/node_compat/test/common/index.js b/tests/node_compat/test/common/index.js index 9f5b4814c197c..2ebb22da28579 100644 --- a/tests/node_compat/test/common/index.js +++ b/tests/node_compat/test/common/index.js @@ -11,10 +11,12 @@ */ 'use strict'; const assert = require("assert"); +const { spawn } = require('child_process'); const path = require("path"); const util = require("util"); const tmpdir = require("./tmpdir"); + function platformTimeout(ms) { return ms; } @@ -442,6 +444,36 @@ const pwdCommand = isWindows ? ['cmd.exe', ['/d', '/c', 'cd']] : ['pwd', []]; + function spawnPromisified(...args) { + let stderr = ''; + let stdout = ''; + + const child = spawn(...args); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { stderr += data; }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { stdout += data; }); + + return new Promise((resolve, reject) => { + child.on('close', (code, signal) => { + resolve({ + code, + signal, + stderr, + stdout, + }); + }); + child.on('error', (code, signal) => { + reject({ + code, + signal, + stderr, + stdout, + }); + }); + }); + } + module.exports = { allowGlobals, expectsError, @@ -464,6 +496,7 @@ module.exports = { printSkipMessage, pwdCommand, skipIfDumbTerminal, + spawnPromisified, isDumbTerminal, isWindows, isAIX, diff --git a/tests/node_compat/test/common/index.mjs b/tests/node_compat/test/common/index.mjs index d5473eaead7c4..d9992e67d766d 100644 --- a/tests/node_compat/test/common/index.mjs +++ b/tests/node_compat/test/common/index.mjs @@ -11,105 +11,105 @@ const require = createRequire(import.meta.url); const common = require('./index.js'); const { - isMainThread, - isWindows, + allowGlobals, + buildType, + canCreateSymLink, + checkoutEOL, + childShouldThrowAndAbort, + createZeroFilledFile, + enoughTestMem, + expectsError, + expectWarning, + getArrayBufferViews, + getBufferSources, + getCallSite, + getTTYfd, + hasCrypto, + hasIPv6, + hasMultiLocalhost, isAIX, - isIBMi, - isLinuxPPCBE, - isSunOS, + isAlive, isDumbTerminal, isFreeBSD, - isOpenBSD, + isIBMi, isLinux, + isLinuxPPCBE, + isMainThread, + isOpenBSD, isOSX, - enoughTestMem, - buildType, + isSunOS, + isWindows, localIPv6Hosts, - opensslCli, - PIPE, - hasCrypto, - hasIPv6, - childShouldThrowAndAbort, - checkoutEOL, - createZeroFilledFile, - platformTimeout, - allowGlobals, mustCall, mustCallAtLeast, - mustSucceed, - hasMultiLocalhost, - skipIfDumbTerminal, - skipIfEslintMissing, - canCreateSymLink, - getCallSite, mustNotCall, mustNotMutateObjectDeep, + mustSucceed, + nodeProcessAborted, + opensslCli, parseTestFlags, + PIPE, + platformTimeout, printSkipMessage, + runWithInvalidFD, skip, - nodeProcessAborted, - isAlive, - expectWarning, - expectsError, - skipIfInspectorDisabled, skipIf32Bits, - getArrayBufferViews, - getBufferSources, - getTTYfd, - runWithInvalidFD, + skipIfDumbTerminal, + skipIfEslintMissing, + skipIfInspectorDisabled, spawnPromisified, } = common; const getPort = () => common.PORT; export { - isMainThread, - isWindows, + allowGlobals, + buildType, + canCreateSymLink, + checkoutEOL, + childShouldThrowAndAbort, + createRequire, + createZeroFilledFile, + enoughTestMem, + expectsError, + expectWarning, + getArrayBufferViews, + getBufferSources, + getCallSite, + getPort, + getTTYfd, + hasCrypto, + hasIPv6, + hasMultiLocalhost, isAIX, - isIBMi, - isLinuxPPCBE, - isSunOS, + isAlive, isDumbTerminal, isFreeBSD, - isOpenBSD, + isIBMi, isLinux, + isLinuxPPCBE, + isMainThread, + isOpenBSD, isOSX, - enoughTestMem, - buildType, + isSunOS, + isWindows, localIPv6Hosts, - opensslCli, - PIPE, - hasCrypto, - hasIPv6, - childShouldThrowAndAbort, - checkoutEOL, - createZeroFilledFile, - platformTimeout, - allowGlobals, mustCall, mustCallAtLeast, - mustSucceed, - hasMultiLocalhost, - skipIfDumbTerminal, - skipIfEslintMissing, - canCreateSymLink, - getCallSite, mustNotCall, mustNotMutateObjectDeep, + mustSucceed, + nodeProcessAborted, + opensslCli, parseTestFlags, + PIPE, + platformTimeout, printSkipMessage, + runWithInvalidFD, skip, - nodeProcessAborted, - isAlive, - expectWarning, - expectsError, - skipIfInspectorDisabled, skipIf32Bits, - getArrayBufferViews, - getBufferSources, - getTTYfd, - runWithInvalidFD, - createRequire, + skipIfDumbTerminal, + skipIfEslintMissing, + skipIfInspectorDisabled, spawnPromisified, - getPort, }; diff --git a/tests/node_compat/test/common/tmpdir.js b/tests/node_compat/test/common/tmpdir.js index 668424cdcbbf8..9315b874711cf 100644 --- a/tests/node_compat/test/common/tmpdir.js +++ b/tests/node_compat/test/common/tmpdir.js @@ -7,12 +7,25 @@ 'use strict'; +const { spawnSync } = require('child_process'); const fs = require('fs'); const path = require('path'); +const { pathToFileURL } = require('url'); const { isMainThread } = require('worker_threads'); -function rmSync(pathname) { - fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true }); +function rmSync(pathname, useSpawn) { + if (useSpawn) { + const escapedPath = pathname.replaceAll('\\', '\\\\'); + spawnSync( + process.execPath, + [ + '-e', + `require("fs").rmSync("${escapedPath}", { maxRetries: 3, recursive: true, force: true });`, + ], + ); + } else { + fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true }); + } } const testRoot = process.env.NODE_TEST_DIR ? @@ -25,25 +38,27 @@ const tmpdirName = '.tmp.' + const tmpPath = path.join(testRoot, tmpdirName); let firstRefresh = true; -function refresh() { - rmSync(tmpPath); +function refresh(useSpawn = false) { + rmSync(tmpPath, useSpawn); fs.mkdirSync(tmpPath); if (firstRefresh) { firstRefresh = false; // Clean only when a test uses refresh. This allows for child processes to // use the tmpdir and only the parent will clean on exit. - process.on('exit', onexit); + process.on('exit', () => { + return onexit(useSpawn); + }); } } -function onexit() { +function onexit(useSpawn) { // Change directory to avoid possible EBUSY if (isMainThread) process.chdir(testRoot); try { - rmSync(tmpPath); + rmSync(tmpPath, useSpawn); } catch (e) { console.error('Can\'t clean tmpdir:', tmpPath); @@ -71,9 +86,17 @@ function hasEnoughSpace(size) { return bavail >= Math.ceil(size / bsize); } +function fileURL(...paths) { + // When called without arguments, add explicit trailing slash + const fullPath = path.resolve(tmpPath + path.sep, ...paths); + + return pathToFileURL(fullPath); +} + module.exports = { + fileURL, + hasEnoughSpace, path: tmpPath, refresh, - hasEnoughSpace, resolve, }; diff --git a/tests/node_compat/test/internet/test-dns-lookup.js b/tests/node_compat/test/internet/test-dns-lookup.js index 8b6c171c943c0..ff9c31544cf31 100644 --- a/tests/node_compat/test/internet/test-dns-lookup.js +++ b/tests/node_compat/test/internet/test-dns-lookup.js @@ -24,7 +24,7 @@ assert.rejects( code: 'ENOTFOUND', message: `getaddrinfo ENOTFOUND ${addresses.NOT_FOUND}`, }, -); +).then(common.mustCall()); assert.rejects( dnsPromises.lookup(addresses.NOT_FOUND, { @@ -36,7 +36,7 @@ assert.rejects( code: 'ENOTFOUND', message: `getaddrinfo ENOTFOUND ${addresses.NOT_FOUND}`, }, -); +).then(common.mustCall()); dns.lookup(addresses.NOT_FOUND, { hints: 0, diff --git a/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js b/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js index 27ccf90db247e..a679f26bb057e 100644 --- a/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js +++ b/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js @@ -12,9 +12,8 @@ const assert = require('assert'); const { SlowBuffer } = require('buffer'); const msg = { - code: 'ERR_INVALID_ARG_VALUE', + code: 'ERR_OUT_OF_RANGE', name: 'RangeError', - message: /^The argument 'size' is invalid\. Received [^"]*$/ }; // Test that negative Buffer length inputs throw errors. diff --git a/tests/node_compat/test/parallel/test-buffer-over-max-length.js b/tests/node_compat/test/parallel/test-buffer-over-max-length.js index 5737ad8736559..c53a9d6110b7f 100644 --- a/tests/node_compat/test/parallel/test-buffer-over-max-length.js +++ b/tests/node_compat/test/parallel/test-buffer-over-max-length.js @@ -15,23 +15,12 @@ const SlowBuffer = buffer.SlowBuffer; const kMaxLength = buffer.kMaxLength; const bufferMaxSizeMsg = { - code: 'ERR_INVALID_ARG_VALUE', + code: 'ERR_OUT_OF_RANGE', name: 'RangeError', - message: /^The argument 'size' is invalid\. Received [^"]*$/ }; -assert.throws(() => Buffer((-1 >>> 0) + 2), bufferMaxSizeMsg); -assert.throws(() => SlowBuffer((-1 >>> 0) + 2), bufferMaxSizeMsg); -assert.throws(() => Buffer.alloc((-1 >>> 0) + 2), bufferMaxSizeMsg); -assert.throws(() => Buffer.allocUnsafe((-1 >>> 0) + 2), bufferMaxSizeMsg); -assert.throws(() => Buffer.allocUnsafeSlow((-1 >>> 0) + 2), bufferMaxSizeMsg); - assert.throws(() => Buffer(kMaxLength + 1), bufferMaxSizeMsg); assert.throws(() => SlowBuffer(kMaxLength + 1), bufferMaxSizeMsg); assert.throws(() => Buffer.alloc(kMaxLength + 1), bufferMaxSizeMsg); assert.throws(() => Buffer.allocUnsafe(kMaxLength + 1), bufferMaxSizeMsg); assert.throws(() => Buffer.allocUnsafeSlow(kMaxLength + 1), bufferMaxSizeMsg); - -// issue GH-4331 -assert.throws(() => Buffer.allocUnsafe(0x100000001), bufferMaxSizeMsg); -assert.throws(() => Buffer.allocUnsafe(0xFFFFFFFFF), bufferMaxSizeMsg); diff --git a/tests/node_compat/test/parallel/test-buffer-slice.js b/tests/node_compat/test/parallel/test-buffer-slice.js index 1ae0c105ad2b9..bd3e8bbb44519 100644 --- a/tests/node_compat/test/parallel/test-buffer-slice.js +++ b/tests/node_compat/test/parallel/test-buffer-slice.js @@ -78,9 +78,9 @@ for (let i = 0, s = buf.toString(); i < buf.length; ++i) { ); } -expectedSameBufs.forEach(([buf1, buf2]) => { +for (const [buf1, buf2] of expectedSameBufs) { assert.strictEqual(Buffer.compare(buf1, buf2), 0); -}); +} const utf16Buf = Buffer.from('0123456789', 'utf16le'); assert.deepStrictEqual(utf16Buf.slice(0, 6), Buffer.from('012', 'utf16le')); diff --git a/tests/node_compat/test/parallel/test-buffer-slow.js b/tests/node_compat/test/parallel/test-buffer-slow.js index b10c4c6dbccfb..577a92a9f6142 100644 --- a/tests/node_compat/test/parallel/test-buffer-slow.js +++ b/tests/node_compat/test/parallel/test-buffer-slow.js @@ -59,9 +59,8 @@ assert.throws(() => SlowBuffer(true), bufferInvalidTypeMsg); // Should throw with invalid length value const bufferMaxSizeMsg = { - code: 'ERR_INVALID_ARG_VALUE', + code: 'ERR_OUT_OF_RANGE', name: 'RangeError', - message: /^The argument 'size' is invalid\. Received [^"]*$/ }; assert.throws(() => SlowBuffer(NaN), bufferMaxSizeMsg); assert.throws(() => SlowBuffer(Infinity), bufferMaxSizeMsg); diff --git a/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js b/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js index dbc69747e3ea0..9ec11056c5c83 100644 --- a/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js +++ b/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js @@ -8,18 +8,22 @@ 'use strict'; require('../common'); -// This test ensures that Node.js throws a RangeError when trying to convert a -// gigantic buffer into a string. +// This test ensures that Node.js throws an Error when trying to convert a +// large buffer into a string. // Regression test for https://github.com/nodejs/node/issues/649. const assert = require('assert'); -const SlowBuffer = require('buffer').SlowBuffer; +const { + SlowBuffer, + constants: { + MAX_STRING_LENGTH, + }, +} = require('buffer'); -const len = 1422561062959; +const len = MAX_STRING_LENGTH + 1; const message = { - code: 'ERR_INVALID_ARG_VALUE', - name: 'RangeError', - message: /^The argument 'size' is invalid\. Received [^"]*$/ + code: 'ERR_STRING_TOO_LONG', + name: 'Error', }; assert.throws(() => Buffer(len).toString('utf8'), message); assert.throws(() => SlowBuffer(len).toString('utf8'), message); diff --git a/tests/node_compat/test/parallel/test-console-table.js b/tests/node_compat/test/parallel/test-console-table.js index da3c66032f35c..991521e8e350b 100644 --- a/tests/node_compat/test/parallel/test-console-table.js +++ b/tests/node_compat/test/parallel/test-console-table.js @@ -45,19 +45,19 @@ test([1, 2, 3], ` ┌─────────┬────────┠│ (index) │ Values │ ├─────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ └─────────┴────────┘ `); test([Symbol(), 5, [10]], ` ┌─────────┬────┬──────────┠-│ (index) │ 0 │ Values │ +│ (index) │ 0 │ Values │ ├─────────┼────┼──────────┤ -│ 0 │ │ Symbol() │ -│ 1 │ │ 5 │ -│ 2 │ 10 │ │ +│ 0 │ │ Symbol() │ +│ 1 │ │ 5 │ +│ 2 │ 10 │ │ └─────────┴────┴──────────┘ `); @@ -65,46 +65,46 @@ test([null, 5], ` ┌─────────┬────────┠│ (index) │ Values │ ├─────────┼────────┤ -│ 0 │ null │ -│ 1 │ 5 │ +│ 0 │ null │ +│ 1 │ 5 │ └─────────┴────────┘ `); test([undefined, 5], ` ┌─────────┬───────────┠-│ (index) │ Values │ +│ (index) │ Values │ ├─────────┼───────────┤ -│ 0 │ undefined │ -│ 1 │ 5 │ +│ 0 │ undefined │ +│ 1 │ 5 │ └─────────┴───────────┘ `); test({ a: 1, b: Symbol(), c: [10] }, ` ┌─────────┬────┬──────────┠-│ (index) │ 0 │ Values │ +│ (index) │ 0 │ Values │ ├─────────┼────┼──────────┤ -│ a │ │ 1 │ -│ b │ │ Symbol() │ -│ c │ 10 │ │ +│ a │ │ 1 │ +│ b │ │ Symbol() │ +│ c │ 10 │ │ └─────────┴────┴──────────┘ `); test(new Map([ ['a', 1], [Symbol(), [2]] ]), ` ┌───────────────────┬──────────┬────────┠-│ (iteration index) │ Key │ Values │ +│ (iteration index) │ Key │ Values │ ├───────────────────┼──────────┼────────┤ -│ 0 │ 'a' │ 1 │ -│ 1 │ Symbol() │ [ 2 ] │ +│ 0 │ 'a' │ 1 │ +│ 1 │ Symbol() │ [ 2 ] │ └───────────────────┴──────────┴────────┘ `); test(new Set([1, 2, Symbol()]), ` ┌───────────────────┬──────────┠-│ (iteration index) │ Values │ +│ (iteration index) │ Values │ ├───────────────────┼──────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ Symbol() │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ Symbol() │ └───────────────────┴──────────┘ `); @@ -112,8 +112,8 @@ test({ a: 1, b: 2 }, ['a'], ` ┌─────────┬───┠│ (index) │ a │ ├─────────┼───┤ -│ a │ │ -│ b │ │ +│ a │ │ +│ b │ │ └─────────┴───┘ `); @@ -121,8 +121,8 @@ test([{ a: 1, b: 2 }, { a: 3, c: 4 }], ['a'], ` ┌─────────┬───┠│ (index) │ a │ ├─────────┼───┤ -│ 0 │ 1 │ -│ 1 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 3 │ └─────────┴───┘ `); @@ -130,9 +130,9 @@ test(new Map([[1, 1], [2, 2], [3, 3]]).entries(), ` ┌───────────────────┬─────┬────────┠│ (iteration index) │ Key │ Values │ ├───────────────────┼─────┼────────┤ -│ 0 │ 1 │ 1 │ -│ 1 │ 2 │ 2 │ -│ 2 │ 3 │ 3 │ +│ 0 │ 1 │ 1 │ +│ 1 │ 2 │ 2 │ +│ 2 │ 3 │ 3 │ └───────────────────┴─────┴────────┘ `); @@ -140,9 +140,9 @@ test(new Map([[1, 1], [2, 2], [3, 3]]).values(), ` ┌───────────────────┬────────┠│ (iteration index) │ Values │ ├───────────────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ └───────────────────┴────────┘ `); @@ -150,9 +150,9 @@ test(new Map([[1, 1], [2, 2], [3, 3]]).keys(), ` ┌───────────────────┬────────┠│ (iteration index) │ Values │ ├───────────────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ └───────────────────┴────────┘ `); @@ -160,9 +160,9 @@ test(new Set([1, 2, 3]).values(), ` ┌───────────────────┬────────┠│ (iteration index) │ Values │ ├───────────────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ └───────────────────┴────────┘ `); @@ -171,15 +171,15 @@ test({ a: { a: 1, b: 2, c: 3 } }, ` ┌─────────┬───┬───┬───┠│ (index) │ a │ b │ c │ ├─────────┼───┼───┼───┤ -│ a │ 1 │ 2 │ 3 │ +│ a │ 1 │ 2 │ 3 │ └─────────┴───┴───┴───┘ `); test({ a: { a: { a: 1, b: 2, c: 3 } } }, ` ┌─────────┬──────────┠-│ (index) │ a │ +│ (index) │ a │ ├─────────┼──────────┤ -│ a │ [Object] │ +│ a │ [Object] │ └─────────┴──────────┘ `); @@ -187,7 +187,7 @@ test({ a: [1, 2] }, ` ┌─────────┬───┬───┠│ (index) │ 0 │ 1 │ ├─────────┼───┼───┤ -│ a │ 1 │ 2 │ +│ a │ 1 │ 2 │ └─────────┴───┴───┘ `); @@ -195,9 +195,9 @@ test({ a: [1, 2, 3, 4, 5], b: 5, c: { e: 5 } }, ` ┌─────────┬───┬───┬───┬───┬───┬───┬────────┠│ (index) │ 0 │ 1 │ 2 │ 3 │ 4 │ e │ Values │ ├─────────┼───┼───┼───┼───┼───┼───┼────────┤ -│ a │ 1 │ 2 │ 3 │ 4 │ 5 │ │ │ -│ b │ │ │ │ │ │ │ 5 │ -│ c │ │ │ │ │ │ 5 │ │ +│ a │ 1 │ 2 │ 3 │ 4 │ 5 │ │ │ +│ b │ │ │ │ │ │ │ 5 │ +│ c │ │ │ │ │ │ 5 │ │ └─────────┴───┴───┴───┴───┴───┴───┴────────┘ `); @@ -205,9 +205,9 @@ test(new Uint8Array([1, 2, 3]), ` ┌─────────┬────────┠│ (index) │ Values │ ├─────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ └─────────┴────────┘ `); @@ -215,9 +215,9 @@ test(Buffer.from([1, 2, 3]), ` ┌─────────┬────────┠│ (index) │ Values │ ├─────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ └─────────┴────────┘ `); @@ -225,7 +225,7 @@ test({ a: undefined }, ['x'], ` ┌─────────┬───┠│ (index) │ x │ ├─────────┼───┤ -│ a │ │ +│ a │ │ └─────────┴───┘ `); @@ -245,23 +245,23 @@ test(new Map(), ` test([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ` ┌─────────┬─────┬─────┠-│ (index) │ a │ b │ +│ (index) │ a │ b │ ├─────────┼─────┼─────┤ -│ 0 │ 1 │ 'Y' │ -│ 1 │ 'Z' │ 2 │ +│ 0 │ 1 │ 'Y' │ +│ 1 │ 'Z' │ 2 │ └─────────┴─────┴─────┘ `); { const line = '─'.repeat(79); - const header = `${' '.repeat(37)}name${' '.repeat(40)}`; + const header = `name${' '.repeat(77)}`; const name = 'very long long long long long long long long long long long ' + 'long long long long'; test([{ name }], ` ┌─────────┬──${line}──┠-│ (index) │ ${header}│ +│ (index) │ ${header} │ ├─────────┼──${line}──┤ -│ 0 │ '${name}' │ +│ 0 │ '${name}' │ └─────────┴──${line}──┘ `); } @@ -270,8 +270,8 @@ test({ foo: 'ï¿¥', bar: 'Â¥' }, ` ┌─────────┬────────┠│ (index) │ Values │ ├─────────┼────────┤ -│ foo │ 'ï¿¥' │ -│ bar │ 'Â¥' │ +│ foo │ 'ï¿¥' │ +│ bar │ 'Â¥' │ └─────────┴────────┘ `); @@ -279,8 +279,8 @@ test({ foo: '你好', bar: 'hello' }, ` ┌─────────┬─────────┠│ (index) │ Values │ ├─────────┼─────────┤ -│ foo │ '你好' │ -│ bar │ 'hello' │ +│ foo │ '你好' │ +│ bar │ 'hello' │ └─────────┴─────────┘ `); @@ -292,8 +292,8 @@ test([{ foo: 10 }, { foo: 20 }], ['__proto__'], ` ┌─────────┬───────────┠│ (index) │ __proto__ │ ├─────────┼───────────┤ -│ 0 │ │ -│ 1 │ │ +│ 0 │ │ +│ 1 │ │ └─────────┴───────────┘ `); assert.strictEqual('0' in Object.prototype, false); diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js b/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js index 8a855482314e8..ef2486f5afb93 100644 --- a/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js @@ -49,3 +49,10 @@ assert.ok(!dc.unsubscribe(name, subscriber)); assert.throws(() => { dc.subscribe(name, null); }, { code: 'ERR_INVALID_ARG_TYPE' }); + +// Reaching zero subscribers should not delete from the channels map as there +// will be no more weakref to incRef if another subscribe happens while the +// channel object itself exists. +channel.subscribe(subscriber); +channel.unsubscribe(subscriber); +channel.subscribe(subscriber); diff --git a/tests/node_compat/test/parallel/test-event-emitter-emit-context.js b/tests/node_compat/test/parallel/test-event-emitter-emit-context.js index 437a1fadbce38..76c22aa9a0236 100644 --- a/tests/node_compat/test/parallel/test-event-emitter-emit-context.js +++ b/tests/node_compat/test/parallel/test-event-emitter-emit-context.js @@ -15,7 +15,7 @@ const EE = new EventEmitter(); // Works as expected if the context has no `constructor.name` { - const ctx = Object.create(null); + const ctx = { __proto__: null }; assert.throws( () => EE.emit.call(ctx, 'error', new Error('foo')), common.expectsError({ name: 'Error', message: 'foo' }) diff --git a/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js b/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js index d6676a8e1aa97..e5df20e601927 100644 --- a/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js +++ b/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js @@ -32,7 +32,7 @@ EE.emit('error', theErr); // Verify it works with once process.nextTick(() => EE.emit('error', theErr)); -assert.rejects(EventEmitter.once(EE, 'notTriggered'), theErr); +assert.rejects(EventEmitter.once(EE, 'notTriggered'), theErr).then(common.mustCall()); // Only error events trigger error monitor EE.on('aEvent', common.mustCall()); diff --git a/tests/node_compat/test/parallel/test-file-write-stream.js b/tests/node_compat/test/parallel/test-file-write-stream.js index 6b8c3d4949ed7..22ebadd1cbefd 100644 --- a/tests/node_compat/test/parallel/test-file-write-stream.js +++ b/tests/node_compat/test/parallel/test-file-write-stream.js @@ -30,10 +30,9 @@ const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); -const fn = path.join(tmpdir.path, 'write.txt'); +const fn = tmpdir.resolve('write.txt'); tmpdir.refresh(); const file = fs.createWriteStream(fn, { highWaterMark: 10 diff --git a/tests/node_compat/test/parallel/test-file-write-stream2.js b/tests/node_compat/test/parallel/test-file-write-stream2.js index 12b7f243d451f..8b23683a75845 100644 --- a/tests/node_compat/test/parallel/test-file-write-stream2.js +++ b/tests/node_compat/test/parallel/test-file-write-stream2.js @@ -30,13 +30,12 @@ require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); -const filepath = path.join(tmpdir.path, 'write.txt'); +const filepath = tmpdir.resolve('write.txt'); const EXPECTED = '012345678910'; diff --git a/tests/node_compat/test/parallel/test-file-write-stream3.js b/tests/node_compat/test/parallel/test-file-write-stream3.js index f34b5f6eaf2ae..2dd5444a659c8 100644 --- a/tests/node_compat/test/parallel/test-file-write-stream3.js +++ b/tests/node_compat/test/parallel/test-file-write-stream3.js @@ -29,13 +29,12 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); -const filepath = path.join(tmpdir.path, 'write_pos.txt'); +const filepath = tmpdir.resolve('write_pos.txt'); const cb_expected = 'write open close write open close write open close '; diff --git a/tests/node_compat/test/parallel/test-file-write-stream4.js b/tests/node_compat/test/parallel/test-file-write-stream4.js index 8a143c2c4067b..9d5cd7d32fc6e 100644 --- a/tests/node_compat/test/parallel/test-file-write-stream4.js +++ b/tests/node_compat/test/parallel/test-file-write-stream4.js @@ -11,13 +11,12 @@ // Refs: https://github.com/nodejs/node/issues/31366 const common = require('../common'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); -const filepath = path.join(tmpdir.path, 'write_pos.txt'); +const filepath = tmpdir.resolve('write_pos.txt'); const fileReadStream = fs.createReadStream(process.execPath); const fileWriteStream = fs.createWriteStream(filepath, { diff --git a/tests/node_compat/test/parallel/test-fs-access.js b/tests/node_compat/test/parallel/test-fs-access.js index a67f9fe07778a..8add7d553edd0 100644 --- a/tests/node_compat/test/parallel/test-fs-access.js +++ b/tests/node_compat/test/parallel/test-fs-access.js @@ -20,15 +20,14 @@ if (common.isIBMi) const assert = require('assert'); const fs = require('fs'); -const path = require('path'); const { internalBinding } = require('internal/test/binding'); const { UV_ENOENT } = internalBinding('uv'); const tmpdir = require('../common/tmpdir'); -const doesNotExist = path.join(tmpdir.path, '__this_should_not_exist'); -const readOnlyFile = path.join(tmpdir.path, 'read_only_file'); -const readWriteFile = path.join(tmpdir.path, 'read_write_file'); +const doesNotExist = tmpdir.resolve('__this_should_not_exist'); +const readOnlyFile = tmpdir.resolve('read_only_file'); +const readWriteFile = tmpdir.resolve('read_write_file'); function createFileWithPerms(file, mode) { fs.writeFileSync(file, ''); @@ -71,10 +70,10 @@ if (!common.isWindows && process.getuid() === 0) { } } -assert.strictEqual(typeof fs.F_OK, 'number'); -assert.strictEqual(typeof fs.R_OK, 'number'); -assert.strictEqual(typeof fs.W_OK, 'number'); -assert.strictEqual(typeof fs.X_OK, 'number'); +assert.strictEqual(typeof fs.constants.F_OK, 'number'); +assert.strictEqual(typeof fs.constants.R_OK, 'number'); +assert.strictEqual(typeof fs.constants.W_OK, 'number'); +assert.strictEqual(typeof fs.constants.X_OK, 'number'); const throwNextTick = (e) => { process.nextTick(() => { throw e; }); }; @@ -84,16 +83,16 @@ fs.access(__filename, common.mustCall(function(...args) { fs.promises.access(__filename) .then(common.mustCall()) .catch(throwNextTick); -fs.access(__filename, fs.R_OK, common.mustCall(function(...args) { +fs.access(__filename, fs.constants.R_OK, common.mustCall(function(...args) { assert.deepStrictEqual(args, [null]); })); -fs.promises.access(__filename, fs.R_OK) +fs.promises.access(__filename, fs.constants.R_OK) .then(common.mustCall()) .catch(throwNextTick); -fs.access(readOnlyFile, fs.R_OK, common.mustCall(function(...args) { +fs.access(readOnlyFile, fs.constants.R_OK, common.mustCall(function(...args) { assert.deepStrictEqual(args, [null]); })); -fs.promises.access(readOnlyFile, fs.R_OK) +fs.promises.access(readOnlyFile, fs.constants.R_OK) .then(common.mustCall()) .catch(throwNextTick); @@ -119,8 +118,8 @@ fs.promises.access(readOnlyFile, fs.R_OK) assert.strictEqual(err.path, readOnlyFile); } } - fs.access(readOnlyFile, fs.W_OK, common.mustCall(expectedError)); - fs.promises.access(readOnlyFile, fs.W_OK) + fs.access(readOnlyFile, fs.constants.W_OK, common.mustCall(expectedError)); + fs.promises.access(readOnlyFile, fs.constants.W_OK) .then(common.mustNotCall(), common.mustCall(expectedError)) .catch(throwNextTick); } @@ -132,18 +131,18 @@ fs.promises.access(readOnlyFile, fs.R_OK) return true; }; assert.throws( - () => { fs.access(100, fs.F_OK, common.mustNotCall()); }, + () => { fs.access(100, fs.constants.F_OK, common.mustNotCall()); }, expectedError ); - fs.promises.access(100, fs.F_OK) + fs.promises.access(100, fs.constants.F_OK) .then(common.mustNotCall(), common.mustCall(expectedError)) .catch(throwNextTick); } assert.throws( () => { - fs.access(__filename, fs.F_OK); + fs.access(__filename, fs.constants.F_OK); }, { code: 'ERR_INVALID_ARG_TYPE', @@ -152,7 +151,7 @@ assert.throws( assert.throws( () => { - fs.access(__filename, fs.F_OK, common.mustNotMutateObjectDeep({})); + fs.access(__filename, fs.constants.F_OK, common.mustNotMutateObjectDeep({})); }, { code: 'ERR_INVALID_ARG_TYPE', @@ -161,14 +160,14 @@ assert.throws( // Regular access should not throw. fs.accessSync(__filename); -const mode = fs.R_OK | fs.W_OK; +const mode = fs.constants.R_OK | fs.constants.W_OK; fs.accessSync(readWriteFile, mode); // Invalid modes should throw. [ false, 1n, - { [Symbol.toPrimitive]() { return fs.R_OK; } }, + { [Symbol.toPrimitive]() { return fs.constants.R_OK; } }, [1], 'r', ].forEach((mode, i) => { diff --git a/tests/node_compat/test/parallel/test-fs-append-file-sync.js b/tests/node_compat/test/parallel/test-fs-append-file-sync.js index bed75f1717e34..fa79ddb55f167 100644 --- a/tests/node_compat/test/parallel/test-fs-append-file-sync.js +++ b/tests/node_compat/test/parallel/test-fs-append-file-sync.js @@ -29,7 +29,6 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const join = require('path').join; const fs = require('fs'); const currentFileData = 'ABCD'; @@ -47,7 +46,7 @@ const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); // Test that empty file will be created and have content added. -const filename = join(tmpdir.path, 'append-sync.txt'); +const filename = tmpdir.resolve('append-sync.txt'); fs.appendFileSync(filename, data); @@ -56,7 +55,7 @@ const fileData = fs.readFileSync(filename); assert.strictEqual(Buffer.byteLength(data), fileData.length); // Test that appends data to a non empty file. -const filename2 = join(tmpdir.path, 'append-sync2.txt'); +const filename2 = tmpdir.resolve('append-sync2.txt'); fs.writeFileSync(filename2, currentFileData); fs.appendFileSync(filename2, data); @@ -67,7 +66,7 @@ assert.strictEqual(Buffer.byteLength(data) + currentFileData.length, fileData2.length); // Test that appendFileSync accepts buffers. -const filename3 = join(tmpdir.path, 'append-sync3.txt'); +const filename3 = tmpdir.resolve('append-sync3.txt'); fs.writeFileSync(filename3, currentFileData); const buf = Buffer.from(data, 'utf8'); @@ -77,7 +76,7 @@ const fileData3 = fs.readFileSync(filename3); assert.strictEqual(buf.length + currentFileData.length, fileData3.length); -const filename4 = join(tmpdir.path, 'append-sync4.txt'); +const filename4 = tmpdir.resolve('append-sync4.txt'); fs.writeFileSync(filename4, currentFileData, common.mustNotMutateObjectDeep({ mode: m })); [ @@ -102,7 +101,7 @@ assert.strictEqual(Buffer.byteLength(String(num)) + currentFileData.length, fileData4.length); // Test that appendFile accepts file descriptors. -const filename5 = join(tmpdir.path, 'append-sync5.txt'); +const filename5 = tmpdir.resolve('append-sync5.txt'); fs.writeFileSync(filename5, currentFileData); const filename5fd = fs.openSync(filename5, 'a+', 0o600); diff --git a/tests/node_compat/test/parallel/test-fs-copyfile.js b/tests/node_compat/test/parallel/test-fs-copyfile.js index 37d7097505642..d00a076f314a6 100644 --- a/tests/node_compat/test/parallel/test-fs-copyfile.js +++ b/tests/node_compat/test/parallel/test-fs-copyfile.js @@ -17,9 +17,8 @@ const { UV_ENOENT, UV_EEXIST } = internalBinding('uv'); -const path = require('path'); const src = fixtures.path('a.js'); -const dest = path.join(tmpdir.path, 'copyfile.out'); +const dest = tmpdir.resolve('copyfile.out'); const { COPYFILE_EXCL, COPYFILE_FICLONE, diff --git a/tests/node_compat/test/parallel/test-fs-open-flags.js b/tests/node_compat/test/parallel/test-fs-open-flags.js index 2bacf8300ba28..89f6bdcb62a06 100644 --- a/tests/node_compat/test/parallel/test-fs-open-flags.js +++ b/tests/node_compat/test/parallel/test-fs-open-flags.js @@ -34,7 +34,6 @@ const fixtures = require('../common/fixtures'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); // 0 if not found in fs.constants const { O_APPEND = 0, @@ -93,7 +92,7 @@ assert.throws( if (common.isLinux || common.isOSX) { const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); - const file = path.join(tmpdir.path, 'a.js'); + const file = tmpdir.resolve('a.js'); fs.copyFileSync(fixtures.path('a.js'), file); fs.open(file, O_DSYNC, common.mustSucceed((fd) => { fs.closeSync(fd); diff --git a/tests/node_compat/test/parallel/test-fs-open-mode-mask.js b/tests/node_compat/test/parallel/test-fs-open-mode-mask.js index 20dd31b8c39ac..30a2cd354d1ee 100644 --- a/tests/node_compat/test/parallel/test-fs-open-mode-mask.js +++ b/tests/node_compat/test/parallel/test-fs-open-mode-mask.js @@ -11,7 +11,6 @@ const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const mode = common.isWindows ? 0o444 : 0o644; @@ -27,7 +26,7 @@ function test(mode, asString) { (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); { - const file = path.join(tmpdir.path, `openSync-${suffix}.txt`); + const file = tmpdir.resolve(`openSync-${suffix}.txt`); const fd = fs.openSync(file, 'w+', input); assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); fs.closeSync(fd); @@ -35,7 +34,7 @@ function test(mode, asString) { } { - const file = path.join(tmpdir.path, `open-${suffix}.txt`); + const file = tmpdir.resolve(`open-${suffix}.txt`); fs.open(file, 'w+', input, common.mustSucceed((fd) => { assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); fs.closeSync(fd); diff --git a/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js b/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js index 2e62a1cfd6ad8..025eff60d2a13 100644 --- a/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js +++ b/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js @@ -10,13 +10,12 @@ require('../common'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); // O_WRONLY without O_CREAT shall fail with ENOENT -const pathNE = path.join(tmpdir.path, 'file-should-not-exist'); +const pathNE = tmpdir.resolve('file-should-not-exist'); assert.throws( () => fs.openSync(pathNE, fs.constants.O_WRONLY), (e) => e.code === 'ENOENT' diff --git a/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js b/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js index 70fd17e00e8d2..2ab7b69e33317 100644 --- a/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js +++ b/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js @@ -9,10 +9,9 @@ const common = require('../common'); const fs = require('fs'); -const path = require('path'); const assert = require('assert'); const tmpdir = require('../common/tmpdir'); -const writeFile = path.join(tmpdir.path, 'write-autoClose.txt'); +const writeFile = tmpdir.resolve('write-autoClose.txt'); tmpdir.refresh(); const file = fs.createWriteStream(writeFile, { autoClose: true }); diff --git a/tests/node_compat/test/parallel/test-fs-read-stream-fd.js b/tests/node_compat/test/parallel/test-fs-read-stream-fd.js index 5562c5796bbcb..ba7c06419a38a 100644 --- a/tests/node_compat/test/parallel/test-fs-read-stream-fd.js +++ b/tests/node_compat/test/parallel/test-fs-read-stream-fd.js @@ -30,9 +30,8 @@ const common = require('../common'); const fs = require('fs'); const assert = require('assert'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); -const file = path.join(tmpdir.path, '/read_stream_fd_test.txt'); +const file = tmpdir.resolve('read_stream_fd_test.txt'); const input = 'hello world'; let output = ''; diff --git a/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js b/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js index 79fa884863660..2f8c139e8c3d0 100644 --- a/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js +++ b/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js @@ -57,7 +57,7 @@ const rangeFile = fixtures.path('x.txt'); } { - const file = fs.createReadStream(fn, Object.create({ encoding: 'utf8' })); + const file = fs.createReadStream(fn, { __proto__: { encoding: 'utf8' } }); file.length = 0; file.on('data', function(data) { assert.strictEqual(typeof data, 'string'); @@ -75,7 +75,7 @@ const rangeFile = fixtures.path('x.txt'); } { - const options = Object.create({ bufferSize: 1, start: 1, end: 2 }); + const options = { __proto__: { bufferSize: 1, start: 1, end: 2 } }; const file = fs.createReadStream(rangeFile, options); assert.strictEqual(file.start, 1); assert.strictEqual(file.end, 2); @@ -89,7 +89,7 @@ const rangeFile = fixtures.path('x.txt'); } { - const options = Object.create({ bufferSize: 1, start: 1 }); + const options = { __proto__: { bufferSize: 1, start: 1 } }; const file = fs.createReadStream(rangeFile, options); assert.strictEqual(file.start, 1); file.data = ''; @@ -103,7 +103,7 @@ const rangeFile = fixtures.path('x.txt'); // https://github.com/joyent/node/issues/2320 { - const options = Object.create({ bufferSize: 1.23, start: 1 }); + const options = { __proto__: { bufferSize: 1.23, start: 1 } }; const file = fs.createReadStream(rangeFile, options); assert.strictEqual(file.start, 1); file.data = ''; @@ -122,7 +122,7 @@ const rangeFile = fixtures.path('x.txt'); assert.throws( () => { - fs.createReadStream(rangeFile, Object.create({ start: 10, end: 2 })); + fs.createReadStream(rangeFile, { __proto__: { start: 10, end: 2 } }); }, { code: 'ERR_OUT_OF_RANGE', @@ -132,7 +132,7 @@ const rangeFile = fixtures.path('x.txt'); } { - const options = Object.create({ start: 0, end: 0 }); + const options = { __proto__: { start: 0, end: 0 } }; const stream = fs.createReadStream(rangeFile, options); assert.strictEqual(stream.start, 0); assert.strictEqual(stream.end, 0); @@ -157,7 +157,7 @@ const rangeFile = fixtures.path('x.txt'); { let data = ''; let file = - fs.createReadStream(rangeFile, Object.create({ autoClose: false })); + fs.createReadStream(rangeFile, { __proto__: { autoClose: false } }); assert.strictEqual(file.autoClose, false); file.on('data', (chunk) => { data += chunk; }); file.on('end', common.mustCall(function() { @@ -171,7 +171,7 @@ const rangeFile = fixtures.path('x.txt'); function fileNext() { // This will tell us if the fd is usable again or not. - file = fs.createReadStream(null, Object.create({ fd: file.fd, start: 0 })); + file = fs.createReadStream(null, { __proto__: { fd: file.fd, start: 0 } }); file.data = ''; file.on('data', function(data) { file.data += data; @@ -188,7 +188,7 @@ const rangeFile = fixtures.path('x.txt'); // Just to make sure autoClose won't close the stream because of error. { - const options = Object.create({ fd: 13337, autoClose: false }); + const options = { __proto__: { fd: 13337, autoClose: false } }; const file = fs.createReadStream(null, options); file.on('data', common.mustNotCall()); file.on('error', common.mustCall()); diff --git a/tests/node_compat/test/parallel/test-fs-read.js b/tests/node_compat/test/parallel/test-fs-read.js index 3afad7aad19d5..e8318df1ec4bd 100644 --- a/tests/node_compat/test/parallel/test-fs-read.js +++ b/tests/node_compat/test/parallel/test-fs-read.js @@ -86,7 +86,7 @@ assert.throws( assert.throws( () => fs.read(fd, { buffer: null }, common.mustNotCall()), - /TypeError: Cannot read properties of null \(reading 'byteLength'\)/, + { code: 'ERR_INVALID_ARG_TYPE' }, 'throws when options.buffer is null' ); diff --git a/tests/node_compat/test/parallel/test-fs-readv-sync.js b/tests/node_compat/test/parallel/test-fs-readv-sync.js index 4af09db6145f7..fd0fc8cdaf619 100644 --- a/tests/node_compat/test/parallel/test-fs-readv-sync.js +++ b/tests/node_compat/test/parallel/test-fs-readv-sync.js @@ -10,7 +10,6 @@ require('../common'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); @@ -20,7 +19,7 @@ const expected = 'ümlaut. Лорем é‹å‹™ãƒ›ã‚½ãƒ¢æŒ‡åŠ आपको कर const exptectedBuff = Buffer.from(expected); const expectedLength = exptectedBuff.length; -const filename = path.join(tmpdir.path, 'readv_sync.txt'); +const filename = tmpdir.resolve('readv_sync.txt'); fs.writeFileSync(filename, exptectedBuff); const allocateEmptyBuffers = (combinedLength) => { diff --git a/tests/node_compat/test/parallel/test-fs-readv.js b/tests/node_compat/test/parallel/test-fs-readv.js index 6d1096dbf336a..42581cfe8bafd 100644 --- a/tests/node_compat/test/parallel/test-fs-readv.js +++ b/tests/node_compat/test/parallel/test-fs-readv.js @@ -9,7 +9,6 @@ const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); @@ -18,7 +17,7 @@ tmpdir.refresh(); const expected = 'ümlaut. Лорем é‹å‹™ãƒ›ã‚½ãƒ¢æŒ‡åŠ आपको करने विकास 紙読決多密所 أضÙ'; let cnt = 0; -const getFileName = () => path.join(tmpdir.path, `readv_${++cnt}.txt`); +const getFileName = () => tmpdir.resolve(`readv_${++cnt}.txt`); const exptectedBuff = Buffer.from(expected); const allocateEmptyBuffers = (combinedLength) => { diff --git a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js index 175e22f02e1c2..121a66e094451 100644 --- a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js +++ b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js @@ -10,7 +10,6 @@ const common = require('../common'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); tmpdir.refresh(); @@ -23,7 +22,7 @@ tmpdir.refresh(); 'DEP0147' ); assert.throws( - () => fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), + () => fs.rmdirSync(tmpdir.resolve('noexist.txt'), { recursive: true }), { code: 'ENOENT' } ); diff --git a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js index b0109c410ee18..b3f4d1fab0211 100644 --- a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js +++ b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js @@ -10,7 +10,6 @@ const common = require('../common'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); tmpdir.refresh(); @@ -21,7 +20,7 @@ tmpdir.refresh(); 'will be removed. Use fs.rm(path, { recursive: true }) instead', 'DEP0147' ); - const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + const filePath = tmpdir.resolve('rmdir-recursive.txt'); fs.writeFileSync(filePath, ''); assert.throws( () => fs.rmdirSync(filePath, { recursive: true }), diff --git a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js index f8ed34e425651..931308aadb916 100644 --- a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js +++ b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js @@ -10,14 +10,13 @@ const common = require('../common'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); tmpdir.refresh(); { assert.throws( () => - fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), { recursive: true }), + fs.rmdirSync(tmpdir.resolve('noexist.txt'), { recursive: true }), { code: 'ENOENT', } @@ -25,7 +24,7 @@ tmpdir.refresh(); } { fs.rmdir( - path.join(tmpdir.path, 'noexist.txt'), + tmpdir.resolve('noexist.txt'), { recursive: true }, common.mustCall((err) => { assert.strictEqual(err.code, 'ENOENT'); @@ -34,7 +33,7 @@ tmpdir.refresh(); } { assert.rejects( - () => fs.promises.rmdir(path.join(tmpdir.path, 'noexist.txt'), + () => fs.promises.rmdir(tmpdir.resolve('noexist.txt'), { recursive: true }), { code: 'ENOENT', diff --git a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js index d16539b06becd..6a48678bc6eac 100644 --- a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js +++ b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js @@ -10,26 +10,25 @@ const common = require('../common'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); tmpdir.refresh(); const code = common.isWindows ? 'ENOENT' : 'ENOTDIR'; { - const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + const filePath = tmpdir.resolve('rmdir-recursive.txt'); fs.writeFileSync(filePath, ''); assert.throws(() => fs.rmdirSync(filePath, { recursive: true }), { code }); } { - const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + const filePath = tmpdir.resolve('rmdir-recursive.txt'); fs.writeFileSync(filePath, ''); fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => { assert.strictEqual(err.code, code); })); } { - const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + const filePath = tmpdir.resolve('rmdir-recursive.txt'); fs.writeFileSync(filePath, ''); assert.rejects(() => fs.promises.rmdir(filePath, { recursive: true }), { code }).then(common.mustCall()); diff --git a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js index 1bd45a313ae64..2eae8b1e307e2 100644 --- a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js +++ b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js @@ -9,7 +9,6 @@ const common = require('../common'); const tmpdir = require('../common/tmpdir'); const fs = require('fs'); -const path = require('path'); tmpdir.refresh(); @@ -22,7 +21,7 @@ tmpdir.refresh(); 'DEP0147' ); fs.rmdir( - path.join(tmpdir.path, 'noexist.txt'), + tmpdir.resolve('noexist.txt'), { recursive: true }, common.mustCall() ); diff --git a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js index 72adc3c117954..89250b048feca 100644 --- a/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js +++ b/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js @@ -10,7 +10,6 @@ const common = require('../common'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); tmpdir.refresh(); @@ -21,7 +20,7 @@ tmpdir.refresh(); 'will be removed. Use fs.rm(path, { recursive: true }) instead', 'DEP0147' ); - const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + const filePath = tmpdir.resolve('rmdir-recursive.txt'); fs.writeFileSync(filePath, ''); fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => { assert.strictEqual(err.code, common.isWindows ? 'ENOENT' : 'ENOTDIR'); diff --git a/tests/node_compat/test/parallel/test-fs-watchfile.js b/tests/node_compat/test/parallel/test-fs-watchfile.js index 7419b276ca4a5..ccbaabe06fa0a 100644 --- a/tests/node_compat/test/parallel/test-fs-watchfile.js +++ b/tests/node_compat/test/parallel/test-fs-watchfile.js @@ -37,7 +37,7 @@ assert.throws(() => { fs.watchFile(new Object(), common.mustNotCall()); }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); -const enoentFile = path.join(tmpdir.path, 'non-existent-file'); +const enoentFile = tmpdir.resolve('non-existent-file'); const expectedStatObject = new fs.Stats( 0, // dev 0, // mode @@ -92,14 +92,14 @@ watcher.on('stop', common.mustCall()); // Watch events should callback with a filename on supported systems. // Omitting AIX. It works but not reliably. if (common.isLinux || common.isOSX || common.isWindows) { - const dir = path.join(tmpdir.path, 'watch'); + const dir = tmpdir.resolve('watch'); fs.mkdir(dir, common.mustCall(function(err) { if (err) assert.fail(err); - fs.watch(dir, common.mustCall(function(eventType, filename) { + const handle = fs.watch(dir, common.mustCall(function(eventType, filename) { clearInterval(interval); - this._handle.close(); + handle.close(); assert.strictEqual(filename, 'foo.txt'); })); diff --git a/tests/node_compat/test/parallel/test-fs-write-buffer.js b/tests/node_compat/test/parallel/test-fs-write-buffer.js index c867d3cd1720d..13ee5f068cbdd 100644 --- a/tests/node_compat/test/parallel/test-fs-write-buffer.js +++ b/tests/node_compat/test/parallel/test-fs-write-buffer.js @@ -29,7 +29,6 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const expected = Buffer.from('hello'); @@ -38,7 +37,7 @@ tmpdir.refresh(); // fs.write with all parameters provided: { - const filename = path.join(tmpdir.path, 'write1.txt'); + const filename = tmpdir.resolve('write1.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, expected.length); @@ -54,7 +53,7 @@ tmpdir.refresh(); // fs.write with a buffer, without the length parameter: { - const filename = path.join(tmpdir.path, 'write2.txt'); + const filename = tmpdir.resolve('write2.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, 2); @@ -70,7 +69,7 @@ tmpdir.refresh(); // fs.write with a buffer, without the offset and length parameters: { - const filename = path.join(tmpdir.path, 'write3.txt'); + const filename = tmpdir.resolve('write3.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, expected.length); @@ -86,7 +85,7 @@ tmpdir.refresh(); // fs.write with the offset passed as undefined followed by the callback: { - const filename = path.join(tmpdir.path, 'write4.txt'); + const filename = tmpdir.resolve('write4.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, expected.length); @@ -102,7 +101,7 @@ tmpdir.refresh(); // fs.write with offset and length passed as undefined followed by the callback: { - const filename = path.join(tmpdir.path, 'write5.txt'); + const filename = tmpdir.resolve('write5.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, expected.length); @@ -118,7 +117,7 @@ tmpdir.refresh(); // fs.write with a Uint8Array, without the offset and length parameters: { - const filename = path.join(tmpdir.path, 'write6.txt'); + const filename = tmpdir.resolve('write6.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, expected.length); @@ -134,7 +133,7 @@ tmpdir.refresh(); // fs.write with invalid offset type { - const filename = path.join(tmpdir.path, 'write7.txt'); + const filename = tmpdir.resolve('write7.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { assert.throws(() => { fs.write(fd, @@ -156,7 +155,7 @@ tmpdir.refresh(); // fs.write with a DataView, without the offset and length parameters: { - const filename = path.join(tmpdir.path, 'write8.txt'); + const filename = tmpdir.resolve('write8.txt'); fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { const cb = common.mustSucceed((written) => { assert.strictEqual(written, expected.length); diff --git a/tests/node_compat/test/parallel/test-fs-write-file-buffer.js b/tests/node_compat/test/parallel/test-fs-write-file-buffer.js index 6de3eadd50cab..d0bfd32aa9981 100644 --- a/tests/node_compat/test/parallel/test-fs-write-file-buffer.js +++ b/tests/node_compat/test/parallel/test-fs-write-file-buffer.js @@ -28,7 +28,6 @@ 'use strict'; require('../common'); -const join = require('path').join; const util = require('util'); const fs = require('fs'); @@ -57,6 +56,6 @@ const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); const buf = Buffer.from(data, 'base64'); -fs.writeFileSync(join(tmpdir.path, 'test.jpg'), buf); +fs.writeFileSync(tmpdir.resolve('test.jpg'), buf); util.log('Done!'); diff --git a/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js b/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js index cec74e7cc492d..063f3e4915d39 100644 --- a/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js +++ b/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js @@ -10,7 +10,6 @@ const common = require('../common'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); if (!common.isWindows) common.skip('This test is for Windows only.'); @@ -25,7 +24,7 @@ const DATA_VALUE = 'hello'; const RESERVED_CHARACTERS = '<>"|?*'; [...RESERVED_CHARACTERS].forEach((ch) => { - const pathname = path.join(tmpdir.path, `somefile_${ch}`); + const pathname = tmpdir.resolve(`somefile_${ch}`); assert.throws( () => { fs.writeFileSync(pathname, DATA_VALUE); @@ -36,7 +35,7 @@ const RESERVED_CHARACTERS = '<>"|?*'; // Test for ':' (NTFS data streams). // Refs: https://msdn.microsoft.com/en-us/library/windows/desktop/bb540537.aspx -const pathname = path.join(tmpdir.path, 'foo:bar'); +const pathname = tmpdir.resolve('foo:bar'); fs.writeFileSync(pathname, DATA_VALUE); let content = ''; diff --git a/tests/node_compat/test/parallel/test-fs-write-file-sync.js b/tests/node_compat/test/parallel/test-fs-write-file-sync.js index d23523ad374b0..e7da8526063e4 100644 --- a/tests/node_compat/test/parallel/test-fs-write-file-sync.js +++ b/tests/node_compat/test/parallel/test-fs-write-file-sync.js @@ -33,7 +33,6 @@ if (!common.isMainThread) common.skip('Setting process.umask is not supported in Workers'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); // On Windows chmod is only able to manipulate read-only bit. Test if creating @@ -48,7 +47,7 @@ tmpdir.refresh(); // Test writeFileSync { - const file = path.join(tmpdir.path, 'testWriteFileSync.txt'); + const file = tmpdir.resolve('testWriteFileSync.txt'); fs.writeFileSync(file, '123', { mode }); const content = fs.readFileSync(file, { encoding: 'utf8' }); @@ -58,7 +57,7 @@ tmpdir.refresh(); // Test appendFileSync { - const file = path.join(tmpdir.path, 'testAppendFileSync.txt'); + const file = tmpdir.resolve('testAppendFileSync.txt'); fs.appendFileSync(file, 'abc', { mode }); const content = fs.readFileSync(file, { encoding: 'utf8' }); @@ -84,7 +83,7 @@ tmpdir.refresh(); return _closeSync(...args); }; - const file = path.join(tmpdir.path, 'testWriteFileSyncFd.txt'); + const file = tmpdir.resolve('testWriteFileSyncFd.txt'); const fd = fs.openSync(file, 'w+', mode); fs.writeFileSync(fd, '123'); @@ -101,7 +100,7 @@ tmpdir.refresh(); // Test writeFileSync with flags { - const file = path.join(tmpdir.path, 'testWriteFileSyncFlags.txt'); + const file = tmpdir.resolve('testWriteFileSyncFlags.txt'); fs.writeFileSync(file, 'hello ', { encoding: 'utf8', flag: 'a' }); fs.writeFileSync(file, 'world!', { encoding: 'utf8', flag: 'a' }); @@ -109,20 +108,36 @@ tmpdir.refresh(); assert.strictEqual(content, 'hello world!'); } -// Test writeFileSync with an object with an own toString function +// Test writeFileSync with no flags { - // Runtime deprecated by DEP0162 - common.expectWarning('DeprecationWarning', - 'Implicit coercion of objects with own toString property is deprecated.', - 'DEP0162'); - const file = path.join(tmpdir.path, 'testWriteFileSyncStringify.txt'); - const data = { - toString() { - return 'hello world!'; - } - }; + const utf8Data = 'hello world!'; + for (const test of [ + { data: utf8Data }, + { data: utf8Data, options: { encoding: 'utf8' } }, + { data: Buffer.from(utf8Data, 'utf8').toString('hex'), options: { encoding: 'hex' } }, + ]) { + const file = tmpdir.resolve(`testWriteFileSyncNewFile_${Math.random()}.txt`); + fs.writeFileSync(file, test.data, test.options); + + const content = fs.readFileSync(file, { encoding: 'utf-8' }); + assert.strictEqual(content, utf8Data); + } +} - fs.writeFileSync(file, data, { encoding: 'utf8', flag: 'a' }); - const content = fs.readFileSync(file, { encoding: 'utf8' }); - assert.strictEqual(content, String(data)); +// Test writeFileSync with an invalid input +{ + const file = tmpdir.resolve('testWriteFileSyncInvalid.txt'); + for (const data of [ + false, 5, {}, [], null, undefined, true, 5n, () => {}, Symbol(), new Map(), + new String('notPrimitive'), + { [Symbol.toPrimitive]: (hint) => 'amObject' }, + { toString() { return 'amObject'; } }, + Promise.resolve('amPromise'), + common.mustNotCall(), + ]) { + assert.throws( + () => fs.writeFileSync(file, data, { encoding: 'utf8', flag: 'a' }), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + } } diff --git a/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js b/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js index d4158b4ad7a9f..480f7dcdb36fb 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js @@ -8,12 +8,11 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); -const file = path.join(tmpdir.path, 'write-autoclose-opt1.txt'); +const file = tmpdir.resolve('write-autoclose-opt1.txt'); tmpdir.refresh(); let stream = fs.createWriteStream(file, { flags: 'w+', autoClose: false }); stream.write('Test1'); diff --git a/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js b/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js index 9cd1248406b19..ad685be77f20a 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js @@ -9,12 +9,11 @@ require('../common'); const fs = require('fs'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); -const s = fs.createWriteStream(path.join(tmpdir.path, 'nocallback')); +const s = fs.createWriteStream(tmpdir.resolve('nocallback')); s.end('hello world'); s.close(); diff --git a/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js b/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js index ae0f1d8f1edfc..0f57333081d98 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js @@ -10,20 +10,19 @@ const common = require('../common'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); { - const s = fs.createWriteStream(path.join(tmpdir.path, 'rw')); + const s = fs.createWriteStream(tmpdir.resolve('rw')); s.close(common.mustCall()); s.close(common.mustCall()); } { - const s = fs.createWriteStream(path.join(tmpdir.path, 'rw2')); + const s = fs.createWriteStream(tmpdir.resolve('rw2')); let emits = 0; s.on('close', () => { @@ -44,7 +43,7 @@ tmpdir.refresh(); } { - const s = fs.createWriteStream(path.join(tmpdir.path, 'rw'), { + const s = fs.createWriteStream(tmpdir.resolve('rw'), { autoClose: false }); diff --git a/tests/node_compat/test/parallel/test-fs-write-stream-end.js b/tests/node_compat/test/parallel/test-fs-write-stream-end.js index d2458cd38c30a..3221a558e8b40 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream-end.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream-end.js @@ -29,21 +29,20 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); { - const file = path.join(tmpdir.path, 'write-end-test0.txt'); + const file = tmpdir.resolve('write-end-test0.txt'); const stream = fs.createWriteStream(file); stream.end(); stream.on('close', common.mustCall()); } { - const file = path.join(tmpdir.path, 'write-end-test1.txt'); + const file = tmpdir.resolve('write-end-test1.txt'); const stream = fs.createWriteStream(file); stream.end('a\n', 'utf8'); stream.on('close', common.mustCall(function() { @@ -53,7 +52,7 @@ tmpdir.refresh(); } { - const file = path.join(tmpdir.path, 'write-end-test2.txt'); + const file = tmpdir.resolve('write-end-test2.txt'); const stream = fs.createWriteStream(file); stream.end(); diff --git a/tests/node_compat/test/parallel/test-fs-write-stream-fs.js b/tests/node_compat/test/parallel/test-fs-write-stream-fs.js index 44511cf81e021..253f5e425240d 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream-fs.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream-fs.js @@ -7,14 +7,13 @@ 'use strict'; const common = require('../common'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); { - const file = path.join(tmpdir.path, 'write-end-test0.txt'); + const file = tmpdir.resolve('write-end-test0.txt'); const stream = fs.createWriteStream(file, { fs: { open: common.mustCall(fs.open), @@ -28,7 +27,7 @@ tmpdir.refresh(); { - const file = path.join(tmpdir.path, 'write-end-test1.txt'); + const file = tmpdir.resolve('write-end-test1.txt'); const stream = fs.createWriteStream(file, { fs: { open: common.mustCall(fs.open), diff --git a/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js b/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js index b60d613b75c3b..988512aa3bdf0 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js @@ -9,11 +9,10 @@ require('../common'); const assert = require('assert'); const fs = require('fs'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); -const example = path.join(tmpdir.path, 'dummy'); +const example = tmpdir.resolve('dummy'); tmpdir.refresh(); // Should not throw. diff --git a/tests/node_compat/test/parallel/test-fs-write-stream.js b/tests/node_compat/test/parallel/test-fs-write-stream.js index a1f7150944b5b..107f22af04dfe 100644 --- a/tests/node_compat/test/parallel/test-fs-write-stream.js +++ b/tests/node_compat/test/parallel/test-fs-write-stream.js @@ -29,12 +29,11 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); -const file = path.join(tmpdir.path, 'write.txt'); +const file = tmpdir.resolve('write.txt'); tmpdir.refresh(); diff --git a/tests/node_compat/test/parallel/test-fs-write-sync.js b/tests/node_compat/test/parallel/test-fs-write-sync.js index ade0849ce6010..e65923102e6b1 100644 --- a/tests/node_compat/test/parallel/test-fs-write-sync.js +++ b/tests/node_compat/test/parallel/test-fs-write-sync.js @@ -29,10 +29,9 @@ 'use strict'; require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); -const filename = path.join(tmpdir.path, 'write.txt'); +const filename = tmpdir.resolve('write.txt'); tmpdir.refresh(); diff --git a/tests/node_compat/test/parallel/test-fs-write.js b/tests/node_compat/test/parallel/test-fs-write.js index 33fcb84cf985c..e10db287fb238 100644 --- a/tests/node_compat/test/parallel/test-fs-write.js +++ b/tests/node_compat/test/parallel/test-fs-write.js @@ -2,8 +2,8 @@ // deno-lint-ignore-file // Copyright Joyent and Node contributors. All rights reserved. MIT license. -// Taken from Node 16.13.0 -// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. // Copyright Joyent, Inc. and other Node contributors. // @@ -30,17 +30,15 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); -const fn = path.join(tmpdir.path, 'write.txt'); -const fn2 = path.join(tmpdir.path, 'write2.txt'); -const fn3 = path.join(tmpdir.path, 'write3.txt'); -const fn4 = path.join(tmpdir.path, 'write4.txt'); -const fn5 = path.join(tmpdir.path, 'write5.txt'); +const fn = tmpdir.resolve('write.txt'); +const fn2 = tmpdir.resolve('write2.txt'); +const fn3 = tmpdir.resolve('write3.txt'); +const fn4 = tmpdir.resolve('write4.txt'); const expected = 'ümlaut.'; const constants = fs.constants; @@ -106,9 +104,6 @@ fs.open(fn, 'w', 0o644, common.mustSucceed((fd) => { fs.write(fd, '', 0, 'utf8', written); })); -// TODO(kt3k): Enable this test when fs.open supports number for `flags` -// paramter. -/* const args = constants.O_CREAT | constants.O_WRONLY | constants.O_TRUNC; fs.open(fn2, args, 0o644, common.mustSucceed((fd) => { const done = common.mustSucceed((written) => { @@ -126,7 +121,6 @@ fs.open(fn2, args, 0o644, common.mustSucceed((fd) => { fs.write(fd, '', 0, 'utf8', written); })); -*/ fs.open(fn3, 'w', 0o644, common.mustSucceed((fd) => { const done = common.mustSucceed((written) => { @@ -137,17 +131,6 @@ fs.open(fn3, 'w', 0o644, common.mustSucceed((fd) => { fs.write(fd, expected, done); })); -fs.open(fn4, 'w', 0o644, common.mustSucceed((fd) => { - const done = common.mustSucceed((written) => { - assert.strictEqual(written, Buffer.byteLength(expected)); - fs.closeSync(fd); - }); - - const data = { - toString() { return expected; } - }; - fs.write(fd, data, done); -})); [false, 'test', {}, [], null, undefined].forEach((i) => { assert.throws( @@ -166,7 +149,14 @@ fs.open(fn4, 'w', 0o644, common.mustSucceed((fd) => { ); }); -[false, 5, {}, [], null, undefined].forEach((data) => { +[ + false, 5, {}, [], null, undefined, true, 5n, () => {}, Symbol(), new Map(), + new String('notPrimitive'), + { [Symbol.toPrimitive]: (hint) => 'amObject' }, + { toString() { return 'amObject'; } }, + Promise.resolve('amPromise'), + common.mustNotCall(), +].forEach((data) => { assert.throws( () => fs.write(1, data, common.mustNotCall()), { @@ -185,7 +175,7 @@ fs.open(fn4, 'w', 0o644, common.mustSucceed((fd) => { { // Regression test for https://github.com/nodejs/node/issues/38168 - const fd = fs.openSync(fn5, 'w'); + const fd = fs.openSync(fn4, 'w'); assert.throws( () => fs.writeSync(fd, 'abc', 0, 'hex'), diff --git a/tests/node_compat/test/parallel/test-fs-writev-sync.js b/tests/node_compat/test/parallel/test-fs-writev-sync.js index ba90f693aae38..35db3675fdbcf 100644 --- a/tests/node_compat/test/parallel/test-fs-writev-sync.js +++ b/tests/node_compat/test/parallel/test-fs-writev-sync.js @@ -9,7 +9,6 @@ require('../common'); const assert = require('assert'); -const path = require('path'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); @@ -17,7 +16,7 @@ tmpdir.refresh(); const expected = 'ümlaut. Лорем é‹å‹™ãƒ›ã‚½ãƒ¢æŒ‡åŠ आपको करने विकास 紙読決多密所 أضÙ'; -const getFileName = (i) => path.join(tmpdir.path, `writev_sync_${i}.txt`); +const getFileName = (i) => tmpdir.resolve(`writev_sync_${i}.txt`); /** * Testing with a array of buffers input diff --git a/tests/node_compat/test/parallel/test-http-agent-getname.js b/tests/node_compat/test/parallel/test-http-agent-getname.js index 43dc09f2cd584..530cc1378d97e 100644 --- a/tests/node_compat/test/parallel/test-http-agent-getname.js +++ b/tests/node_compat/test/parallel/test-http-agent-getname.js @@ -10,7 +10,6 @@ require('../common'); const assert = require('assert'); const http = require('http'); -const path = require('path'); const tmpdir = require('../common/tmpdir'); @@ -48,7 +47,7 @@ assert.strictEqual( ); // unix socket -const socketPath = path.join(tmpdir.path, 'foo', 'bar'); +const socketPath = tmpdir.resolve('foo', 'bar'); assert.strictEqual( agent.getName({ socketPath diff --git a/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js b/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js index 4cca6500067ed..490d814b9987f 100644 --- a/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js +++ b/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js @@ -24,7 +24,7 @@ common.expectWarning('DeprecationWarning', warn, 'DEP0066'); // Tests _headerNames getter result after setting a header. const outgoingMessage = new OutgoingMessage(); outgoingMessage.setHeader('key', 'value'); - const expect = Object.create(null); + const expect = { __proto__: null }; expect.key = 'key'; assert.deepStrictEqual(outgoingMessage._headerNames, expect); } diff --git a/tests/node_compat/test/parallel/test-http-url.parse-only-support-http-https-protocol.js b/tests/node_compat/test/parallel/test-http-url.parse-only-support-http-https-protocol.js index bc90d70b4e82c..98b222f391e1c 100644 --- a/tests/node_compat/test/parallel/test-http-url.parse-only-support-http-https-protocol.js +++ b/tests/node_compat/test/parallel/test-http-url.parse-only-support-http-https-protocol.js @@ -41,7 +41,7 @@ const invalidUrls = [ 'f://some.host/path', ]; -invalidUrls.forEach((invalid) => { +for (const invalid of invalidUrls) { assert.throws( () => { http.request(url.parse(invalid)); }, { @@ -49,4 +49,4 @@ invalidUrls.forEach((invalid) => { name: 'TypeError' } ); -}); +} diff --git a/tests/node_compat/test/parallel/test-net-isipv4.js b/tests/node_compat/test/parallel/test-net-isipv4.js index 1589196d56539..cdc9f22aceaa4 100644 --- a/tests/node_compat/test/parallel/test-net-isipv4.js +++ b/tests/node_compat/test/parallel/test-net-isipv4.js @@ -44,10 +44,10 @@ const v4not = [ '192.168.0.2000000000', ]; -v4.forEach((ip) => { +for (const ip of v4) { assert.strictEqual(net.isIPv4(ip), true); -}); +} -v4not.forEach((ip) => { +for (const ip of v4not) { assert.strictEqual(net.isIPv4(ip), false); -}); +} diff --git a/tests/node_compat/test/parallel/test-net-isipv6.js b/tests/node_compat/test/parallel/test-net-isipv6.js index e74239d5d351c..752aa5aad7387 100644 --- a/tests/node_compat/test/parallel/test-net-isipv6.js +++ b/tests/node_compat/test/parallel/test-net-isipv6.js @@ -242,10 +242,10 @@ const v6not = [ '02001:0000:1234:0000:0000:C1C0:ABCD:0876', ]; -v6.forEach((ip) => { +for (const ip of v6) { assert.strictEqual(net.isIPv6(ip), true); -}); +} -v6not.forEach((ip) => { +for (const ip of v6not) { assert.strictEqual(net.isIPv6(ip), false); -}); +} diff --git a/tests/node_compat/test/parallel/test-parse-args.mjs b/tests/node_compat/test/parallel/test-parse-args.mjs index f8bc14a6c7b58..0d5aa72f6302e 100644 --- a/tests/node_compat/test/parallel/test-parse-args.mjs +++ b/tests/node_compat/test/parallel/test-parse-args.mjs @@ -458,7 +458,7 @@ const candidateGreedyOptions = [ '--foo', ]; -candidateGreedyOptions.forEach((value) => { +for (const value of candidateGreedyOptions) { test(`greedy: when short option with value '${value}' then eaten`, () => { const args = ['-w', value]; const options = { with: { type: 'string', short: 'w' } }; @@ -476,7 +476,7 @@ candidateGreedyOptions.forEach((value) => { const result = parseArgs({ args, options, strict: false }); assert.deepStrictEqual(result, expectedResult); }); -}); +} test('strict: when candidate option value is plain text then does not throw', () => { const args = ['--with', 'abc']; @@ -980,7 +980,7 @@ test('tokens:true should not include the default options after the args input', test('proto as default value must be ignored', () => { const args = []; - const options = Object.create(null); + const options = { __proto__: null }; // eslint-disable-next-line no-proto options.__proto__ = { type: 'string', default: 'HELLO' }; diff --git a/tests/node_compat/test/parallel/test-path-extname.js b/tests/node_compat/test/parallel/test-path-extname.js index f5894058b2de7..8cba90b073a5c 100644 --- a/tests/node_compat/test/parallel/test-path-extname.js +++ b/tests/node_compat/test/parallel/test-path-extname.js @@ -13,7 +13,7 @@ const path = require('path'); const failures = []; const slashRE = /\//g; -[ +const testPaths = [ [__filename, '.js'], ['', ''], ['/path/to/file', ''], @@ -57,10 +57,13 @@ const slashRE = /\//g; ['file//', ''], ['file./', '.'], ['file.//', '.'], -].forEach((test) => { - const expected = test[1]; - [path.posix.extname, path.win32.extname].forEach((extname) => { - let input = test[0]; +]; + +for (const testPath of testPaths) { + const expected = testPath[1]; + const extNames = [path.posix.extname, path.win32.extname]; + for (const extname of extNames) { + let input = testPath[0]; let os; if (extname === path.win32.extname) { input = input.replace(slashRE, '\\'); @@ -73,16 +76,14 @@ const slashRE = /\//g; JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; if (actual !== expected) failures.push(`\n${message}`); - }); - { - const input = `C:${test[0].replace(slashRE, '\\')}`; - const actual = path.win32.extname(input); - const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${ - JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected) - failures.push(`\n${message}`); } -}); + const input = `C:${testPath[0].replace(slashRE, '\\')}`; + const actual = path.win32.extname(input); + const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) + failures.push(`\n${message}`); +} assert.strictEqual(failures.length, 0, failures.join('')); // On Windows, backslash is a path separator. diff --git a/tests/node_compat/test/parallel/test-path-parse-format.js b/tests/node_compat/test/parallel/test-path-parse-format.js index 5b31751f2ed8e..7f4682899ef0a 100644 --- a/tests/node_compat/test/parallel/test-path-parse-format.js +++ b/tests/node_compat/test/parallel/test-path-parse-format.js @@ -231,3 +231,7 @@ function checkFormat(path, testCases) { }); }); } + +// See https://github.com/nodejs/node/issues/44343 +assert.strictEqual(path.format({ name: 'x', ext: 'png' }), 'x.png'); +assert.strictEqual(path.format({ name: 'x', ext: '.png' }), 'x.png'); diff --git a/tests/node_compat/test/parallel/test-path.js b/tests/node_compat/test/parallel/test-path.js index 41b760bff0420..2d0a928723689 100644 --- a/tests/node_compat/test/parallel/test-path.js +++ b/tests/node_compat/test/parallel/test-path.js @@ -42,8 +42,8 @@ function fail(fn) { }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); } -typeErrorTests.forEach((test) => { - [path.posix, path.win32].forEach((namespace) => { +for (const test of typeErrorTests) { + for (const namespace of [path.posix, path.win32]) { fail(namespace.join, test); fail(namespace.resolve, test); fail(namespace.normalize, test); @@ -59,8 +59,8 @@ typeErrorTests.forEach((test) => { if (test !== undefined) { fail(namespace.basename, 'foo', test); } - }); -}); + } +} // path.sep tests // windows diff --git a/tests/node_compat/test/parallel/test-readline-keys.js b/tests/node_compat/test/parallel/test-readline-keys.js index ec68f7faad867..4487e8d711ae0 100644 --- a/tests/node_compat/test/parallel/test-readline-keys.js +++ b/tests/node_compat/test/parallel/test-readline-keys.js @@ -41,9 +41,9 @@ function addTest(sequences, expectedKeys) { keys = []; - sequences.forEach((sequence) => { + for (const sequence of sequences) { fi.write(sequence); - }); + } assert.deepStrictEqual(keys, expectedKeys); } diff --git a/tests/node_compat/test/parallel/test-stream-buffer-list.js b/tests/node_compat/test/parallel/test-stream-buffer-list.js deleted file mode 100644 index 08abd245926f6..0000000000000 --- a/tests/node_compat/test/parallel/test-stream-buffer-list.js +++ /dev/null @@ -1,91 +0,0 @@ -// deno-fmt-ignore-file -// deno-lint-ignore-file - -// Copyright Joyent and Node contributors. All rights reserved. MIT license. -// Taken from Node 18.12.1 -// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. - -// Flags: --expose-internals -'use strict'; -require('../common'); -const assert = require('assert'); -const BufferList = require('internal/streams/buffer_list'); - -// Test empty buffer list. -const emptyList = new BufferList(); - -emptyList.shift(); -assert.deepStrictEqual(emptyList, new BufferList()); - -assert.strictEqual(emptyList.join(','), ''); - -assert.deepStrictEqual(emptyList.concat(0), Buffer.alloc(0)); - -const buf = Buffer.from('foo'); - -function testIterator(list, count) { - // test iterator - let len = 0; - // eslint-disable-next-line no-unused-vars - for (const x of list) { - len++; - } - assert.strictEqual(len, count); -} - -// Test buffer list with one element. -const list = new BufferList(); -testIterator(list, 0); - -list.push(buf); -testIterator(list, 1); -for (const x of list) { - assert.strictEqual(x, buf); -} - -const copy = list.concat(3); -testIterator(copy, 3); - -assert.notStrictEqual(copy, buf); -assert.deepStrictEqual(copy, buf); - -assert.strictEqual(list.join(','), 'foo'); - -const shifted = list.shift(); -testIterator(list, 0); -assert.strictEqual(shifted, buf); -assert.deepStrictEqual(list, new BufferList()); - -{ - const list = new BufferList(); - list.push('foo'); - list.push('bar'); - list.push('foo'); - list.push('bar'); - assert.strictEqual(list.consume(6, true), 'foobar'); - assert.strictEqual(list.consume(6, true), 'foobar'); -} - -{ - const list = new BufferList(); - list.push('foo'); - list.push('bar'); - assert.strictEqual(list.consume(5, true), 'fooba'); -} - -{ - const list = new BufferList(); - list.push(buf); - list.push(buf); - list.push(buf); - list.push(buf); - assert.strictEqual(list.consume(6).toString(), 'foofoo'); - assert.strictEqual(list.consume(6).toString(), 'foofoo'); -} - -{ - const list = new BufferList(); - list.push(buf); - list.push(buf); - assert.strictEqual(list.consume(5).toString(), 'foofo'); -} diff --git a/tests/node_compat/test/parallel/test-stream-readable-destroy.js b/tests/node_compat/test/parallel/test-stream-readable-destroy.js index a8b7b89328376..75cec92dc3f9c 100644 --- a/tests/node_compat/test/parallel/test-stream-readable-destroy.js +++ b/tests/node_compat/test/parallel/test-stream-readable-destroy.js @@ -325,7 +325,7 @@ const assert = require('assert'); assert.rejects((async () => { // eslint-disable-next-line no-unused-vars, no-empty for await (const chunk of read) { } - })(), /AbortError/); + })(), /AbortError/).then(common.mustCall()); setTimeout(() => controller.abort(), 0); } diff --git a/tests/node_compat/test/parallel/test-stream-uint8array.js b/tests/node_compat/test/parallel/test-stream-uint8array.js index 3ac81e6729474..aa8dbd3d821be 100644 --- a/tests/node_compat/test/parallel/test-stream-uint8array.js +++ b/tests/node_compat/test/parallel/test-stream-uint8array.js @@ -45,7 +45,7 @@ const GHI = new Uint8Array([0x47, 0x48, 0x49]); assert(!(chunk instanceof Buffer)); assert(chunk instanceof Uint8Array); assert.strictEqual(chunk, ABC); - assert.strictEqual(encoding, 'utf8'); + assert.strictEqual(encoding, undefined); cb(); }) }); diff --git a/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js b/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js index e8fbbb0face8f..d6c77c9a7002c 100644 --- a/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js +++ b/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js @@ -63,3 +63,50 @@ class MyWritable extends stream.Writable { m.write('some-text', 'utf8'); m.end(); } + +{ + assert.throws(() => { + const m = new MyWritable(null, { + defaultEncoding: 'my invalid encoding', + }); + m.end(); + }, { + code: 'ERR_UNKNOWN_ENCODING', + }); +} + +{ + const w = new MyWritable(function(isBuffer, type, enc) { + assert(!isBuffer); + assert.strictEqual(type, 'string'); + assert.strictEqual(enc, 'hex'); + }, { + defaultEncoding: 'hex', + decodeStrings: false + }); + w.write('asd'); + w.end(); +} + +{ + const w = new MyWritable(function(isBuffer, type, enc) { + assert(!isBuffer); + assert.strictEqual(type, 'string'); + assert.strictEqual(enc, 'utf8'); + }, { + defaultEncoding: null, + decodeStrings: false + }); + w.write('asd'); + w.end(); +} + +{ + const m = new MyWritable(function(isBuffer, type, enc) { + assert.strictEqual(type, 'object'); + assert.strictEqual(enc, 'utf8'); + }, { defaultEncoding: 'hex', + objectMode: true }); + m.write({ foo: 'bar' }, 'utf8'); + m.end(); +} diff --git a/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js b/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js index f4d5a399584af..ecb597f159998 100644 --- a/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js +++ b/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js @@ -43,7 +43,7 @@ const stream = require('stream'); let called = false; writable.end('asd', common.mustCall((err) => { called = true; - assert.strictEqual(err, undefined); + assert.strictEqual(err, null); })); writable.on('error', common.mustCall((err) => { diff --git a/tests/node_compat/test/parallel/test-stream2-readable-from-list.js b/tests/node_compat/test/parallel/test-stream2-readable-from-list.js deleted file mode 100644 index fea8aefda6b3b..0000000000000 --- a/tests/node_compat/test/parallel/test-stream2-readable-from-list.js +++ /dev/null @@ -1,108 +0,0 @@ -// deno-fmt-ignore-file -// deno-lint-ignore-file - -// Copyright Joyent and Node contributors. All rights reserved. MIT license. -// Taken from Node 18.12.1 -// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. - -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// Flags: --expose-internals -'use strict'; -require('../common'); -const assert = require('assert'); -const fromList = require('stream').Readable._fromList; -const BufferList = require('internal/streams/buffer_list'); -const util = require('util'); - -function bufferListFromArray(arr) { - const bl = new BufferList(); - for (let i = 0; i < arr.length; ++i) - bl.push(arr[i]); - return bl; -} - -{ - // Verify behavior with buffers - let list = [ Buffer.from('foog'), - Buffer.from('bark'), - Buffer.from('bazy'), - Buffer.from('kuel') ]; - list = bufferListFromArray(list); - - assert.strictEqual( - util.inspect([ list ], { compact: false }), - `[ - BufferList { - head: [Object], - tail: [Object], - length: 4 - } -]`); - - // Read more than the first element. - let ret = fromList(6, { buffer: list, length: 16 }); - assert.strictEqual(ret.toString(), 'foogba'); - - // Read exactly the first element. - ret = fromList(2, { buffer: list, length: 10 }); - assert.strictEqual(ret.toString(), 'rk'); - - // Read less than the first element. - ret = fromList(2, { buffer: list, length: 8 }); - assert.strictEqual(ret.toString(), 'ba'); - - // Read more than we have. - ret = fromList(100, { buffer: list, length: 6 }); - assert.strictEqual(ret.toString(), 'zykuel'); - - // all consumed. - assert.deepStrictEqual(list, new BufferList()); -} - -{ - // Verify behavior with strings - let list = [ 'foog', - 'bark', - 'bazy', - 'kuel' ]; - list = bufferListFromArray(list); - - // Read more than the first element. - let ret = fromList(6, { buffer: list, length: 16, decoder: true }); - assert.strictEqual(ret, 'foogba'); - - // Read exactly the first element. - ret = fromList(2, { buffer: list, length: 10, decoder: true }); - assert.strictEqual(ret, 'rk'); - - // Read less than the first element. - ret = fromList(2, { buffer: list, length: 8, decoder: true }); - assert.strictEqual(ret, 'ba'); - - // Read more than we have. - ret = fromList(100, { buffer: list, length: 6, decoder: true }); - assert.strictEqual(ret, 'zykuel'); - - // all consumed. - assert.deepStrictEqual(list, new BufferList()); -} diff --git a/tests/node_compat/test/parallel/test-stream2-writable.js b/tests/node_compat/test/parallel/test-stream2-writable.js index 665ff507f8ff5..4f5ba53fce5f3 100644 --- a/tests/node_compat/test/parallel/test-stream2-writable.js +++ b/tests/node_compat/test/parallel/test-stream2-writable.js @@ -201,7 +201,8 @@ for (let i = 0; i < chunks.length; i++) { { // Verify write callbacks const callbacks = chunks.map(function(chunk, i) { - return [i, function() { + return [i, function(err) { + assert.strictEqual(err, null); callbacks._called[i] = chunk; }]; }).reduce(function(set, x) { @@ -232,7 +233,9 @@ for (let i = 0; i < chunks.length; i++) { { // Verify end() callback const tw = new TestWriter(); - tw.end(common.mustCall()); + tw.end(common.mustCall(function(err) { + assert.strictEqual(err, null); + })); } const helloWorldBuffer = Buffer.from('hello world'); @@ -240,7 +243,9 @@ const helloWorldBuffer = Buffer.from('hello world'); { // Verify end() callback with chunk const tw = new TestWriter(); - tw.end(helloWorldBuffer, common.mustCall()); + tw.end(helloWorldBuffer, common.mustCall(function(err) { + assert.strictEqual(err, null); + })); } { diff --git a/tests/node_compat/test/parallel/test-url-format-whatwg.js b/tests/node_compat/test/parallel/test-url-format-whatwg.js index 9552fd7dadad7..ff8f1201cffe6 100644 --- a/tests/node_compat/test/parallel/test-url-format-whatwg.js +++ b/tests/node_compat/test/parallel/test-url-format-whatwg.js @@ -147,3 +147,8 @@ assert.strictEqual( url.format(new URL('http://user:pass@xn--0zwm56d.com:8080/path'), { unicode: true }), 'http://user:pass@测试.com:8080/path' ); + +assert.strictEqual( + url.format(new URL('tel:123')), + url.format(new URL('tel:123'), { unicode: true }) +); diff --git a/tests/node_compat/test/parallel/test-url-parse-invalid-input.js b/tests/node_compat/test/parallel/test-url-parse-invalid-input.js index 98d2d7e2d865e..7e8aa5c042153 100644 --- a/tests/node_compat/test/parallel/test-url-parse-invalid-input.js +++ b/tests/node_compat/test/parallel/test-url-parse-invalid-input.js @@ -81,3 +81,33 @@ if (common.hasIntl) { (e) => e.code === 'ERR_INVALID_URL', 'parsing http://\u00AD/bad.com/'); } + +/* + FIXME(kt3k): node -e