diff --git a/.cargo/config.toml b/.cargo/config.toml index 61b55184a..d5d222bd5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,5 +3,5 @@ # The following aliases simplify linting the entire workspace neon-check = " check --all --all-targets --features napi-experimental,futures,external-buffers" neon-clippy = "clippy --all --all-targets --features napi-experimental,futures,external-buffers -- -A clippy::missing_safety_doc" -neon-test = " test --all --features=doc-comment,napi-experimental,futures,external-buffers" +neon-test = " test --all --features=doc-dependencies,doc-comment,napi-experimental,futures,external-buffers" neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures,external-buffers -- --cfg docsrs" diff --git a/Cargo.lock b/Cargo.lock index d1a0c964e..6a14b6aa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "easy-cast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2953e4bb4103fd7db41b83431f681a583e2c18e529beb4ca6c679a0dad16c8d" + [[package]] name = "either" version = "1.8.0" @@ -227,6 +233,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "linkify" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96dd5884008358112bc66093362197c7248ece00d46624e2cf71e50029f8cff5" +dependencies = [ + "memchr", +] + [[package]] name = "log" version = "0.4.17" @@ -258,8 +273,10 @@ dependencies = [ "anyhow", "aquamarine", "doc-comment", + "easy-cast", "getrandom", "libloading", + "linkify", "neon-macros", "nodejs-sys", "once_cell", diff --git a/crates/neon/Cargo.toml b/crates/neon/Cargo.toml index bf7de921c..ff2d98ea0 100644 --- a/crates/neon/Cargo.toml +++ b/crates/neon/Cargo.toml @@ -15,6 +15,7 @@ semver = "1" psd = "0.3.1" # used for a doc example anyhow = "1.0.58" # used for a doc example widestring = "1.0.2" # used for a doc example +linkify = "0.9.0" # used for a doc example [target.'cfg(not(target = "windows"))'.dev-dependencies] # Avoid `clang` as a dependency on windows @@ -28,6 +29,7 @@ smallvec = "1.4.2" once_cell = "1.10.0" neon-macros = { version = "=1.0.0-alpha.2", path = "../neon-macros" } aquamarine = { version = "0.1.11", optional = true } +easy-cast = { version = "0.5.1", optional = true } doc-comment = { version = "0.3.3", optional = true } [dependencies.tokio] @@ -80,7 +82,7 @@ task-api = [] proc-macros = [] # Enables the optional dependencies that are only used for generating the API docs. -doc-dependencies = ["doc-comment", "aquamarine"] +doc-dependencies = ["doc-comment", "aquamarine", "easy-cast"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/neon/src/types_impl/boxed.rs b/crates/neon/src/types_impl/boxed.rs index fd3949b26..5abf15884 100644 --- a/crates/neon/src/types_impl/boxed.rs +++ b/crates/neon/src/types_impl/boxed.rs @@ -29,7 +29,7 @@ mod private { } } -/// A smart pointer for Rust data managed by the JavaScript engine. +/// A JavaScript smart pointer object that owns Rust data. /// /// The type `JsBox` provides shared ownership of a value of type `T`, /// allocated in the heap. The data is owned by the JavaScript engine and the @@ -281,8 +281,11 @@ impl Deref for JsBox { } } -/// Finalize is executed on the main JavaScript thread and executed immediately -/// before garbage collection. +/// A trait for finalizing values owned by the main JavaScript thread. +/// +/// [`Finalize::finalize`] is executed on the main JavaScript thread +/// immediately before garbage collection. +/// /// Values contained by a `JsBox` must implement `Finalize`. /// /// ## Examples diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index edb7b0038..026731dad 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -25,7 +25,9 @@ macro_rules! doc_comment { {$comment:expr, $decl:item} => { $decl }; } -/// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. +/// The type of Node +/// [`Buffer`](https://nodejs.org/api/buffer.html) +/// objects. /// /// # Example /// @@ -50,6 +52,8 @@ pub struct JsBuffer(raw::Local); impl JsBuffer { /// Constructs a new `Buffer` object, safely zero-filled. + /// + /// **See also:** [`Context::buffer`] pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { let result = unsafe { sys::buffer::new(cx.env().to_raw(), len) }; @@ -206,7 +210,9 @@ impl TypedArray for JsBuffer { } } -/// The standard JS [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) type. +/// The type of JavaScript +/// [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) +/// objects. /// /// # Example /// @@ -231,6 +237,8 @@ pub struct JsArrayBuffer(raw::Local); impl JsArrayBuffer { /// Constructs a new `JsArrayBuffer` object, safely zero-filled. + /// + /// **See also:** [`Context::array_buffer`] pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { let result = unsafe { sys::arraybuffer::new(cx.env().to_raw(), len) }; @@ -420,7 +428,7 @@ pub trait Binary: private::Sealed + Copy { const TYPE_TAG: TypedArrayType; } -/// The family of JS [typed array][typed-arrays] types. +/// The family of JavaScript [typed array][typed-arrays] types. /// /// ## Typed Arrays /// @@ -814,11 +822,11 @@ macro_rules! impl_typed_array { doc_comment! { concat!( - "The standard JS [`", + "The type of JavaScript [`", stringify!($typ), "`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/", stringify!($typ), - ") type. + ") objects. # Example diff --git a/crates/neon/src/types_impl/date.rs b/crates/neon/src/types_impl/date.rs index 0f55ab98e..5a396c30f 100644 --- a/crates/neon/src/types_impl/date.rs +++ b/crates/neon/src/types_impl/date.rs @@ -13,7 +13,39 @@ use crate::{ sys::{self, raw}, }; -/// A JavaScript Date object +/// The type of JavaScript +/// [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) +/// objects. +/// +/// # Example +/// +/// The following shows an example of converting Rust +/// [`SystemTime`](std::time::SystemTime) timestamps to JavaScript `Date` objects. +/// +/// ``` +/// # use neon::prelude::*; +/// use easy_cast::Cast; // for safe numeric conversions +/// use neon::types::JsDate; +/// use std::{error::Error, fs::File, time::SystemTime}; +/// +/// /// Return the "modified" timestamp for the file at the given path. +/// fn last_modified(path: &str) -> Result> { +/// Ok(File::open(&path)? +/// .metadata()? +/// .modified()? +/// .duration_since(SystemTime::UNIX_EPOCH)? +/// .as_millis() +/// .try_cast()?) +/// } +/// +/// fn modified(mut cx: FunctionContext) -> JsResult { +/// let path: Handle = cx.argument(0)?; +/// +/// last_modified(&path.value(&mut cx)) +/// .and_then(|n| Ok(cx.date(n)?)) +/// .or_else(|err| cx.throw_error(err.to_string())) +/// } +/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] #[derive(Debug)] #[repr(transparent)] @@ -39,7 +71,7 @@ impl Managed for JsDate { } } -/// The Error struct for a Date +/// An error produced when constructing a date with an invalid value. #[derive(Debug)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] pub struct DateError(DateErrorKind); @@ -62,7 +94,11 @@ impl Error for DateError {} #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] pub enum DateErrorKind { + /// Produced for an initialization value greater than + /// [`JsDate::MAX_VALUE`](JsDate::MAX_VALUE). Overflow, + /// Produced for an initialization value lesser than + /// [`JsDate::MIN_VALUE`](JsDate::MIN_VALUE). Underflow, } @@ -83,13 +119,16 @@ impl<'a, T: Value> ResultExt> for Result, DateError> } impl JsDate { - /// The smallest possible Date value, defined by ECMAScript. See + /// The smallest possible `Date` value, + /// [defined by ECMAScript](https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.3.3). pub const MIN_VALUE: f64 = -8.64e15; - /// The largest possible Date value, defined by ECMAScript. See + /// The largest possible `Date` value, + /// [defined by ECMAScript](https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.3.2). pub const MAX_VALUE: f64 = 8.64e15; - /// Creates a new Date. It errors when `value` is outside the range of valid JavaScript Date values. When `value` - /// is `NaN`, the operation will succeed but with an invalid Date + /// Creates a new `Date`. It errors when `value` is outside the range of valid JavaScript + /// `Date` values. When `value` is `NaN`, the operation will succeed but with an + /// invalid `Date`. pub fn new<'a, C: Context<'a>, T: Into>( cx: &mut C, value: T, @@ -108,22 +147,22 @@ impl JsDate { Ok(date) } - /// Creates a new Date with lossy conversion for out of bounds Date values. Out of bounds - /// values will be treated as NaN + /// Creates a new `Date` with lossy conversion for out of bounds `Date` values. + /// Out of bounds values will be treated as `NaN`. pub fn new_lossy<'a, C: Context<'a>, V: Into>(cx: &mut C, value: V) -> Handle<'a, JsDate> { let env = cx.env().to_raw(); let local = unsafe { sys::date::new_date(env, value.into()) }; Handle::new_internal(JsDate(local)) } - /// Gets the Date's value. An invalid Date will return `std::f64::NaN` + /// Gets the `Date`'s value. An invalid `Date` will return [`std::f64::NAN`]. pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> f64 { let env = cx.env().to_raw(); unsafe { sys::date::value(env, self.to_raw()) } } - /// Checks if the Date's value is valid. A Date is valid if its value is between - /// `JsDate::MIN_VALUE` and `JsDate::MAX_VALUE` or if it is `NaN` + /// Checks if the `Date`'s value is valid. A `Date` is valid if its value is + /// between [`JsDate::MIN_VALUE`] and [`JsDate::MAX_VALUE`] or if it is `NaN`. pub fn is_valid<'a, C: Context<'a>>(&self, cx: &mut C) -> bool { let value = self.value(cx); (JsDate::MIN_VALUE..=JsDate::MAX_VALUE).contains(&value) diff --git a/crates/neon/src/types_impl/error.rs b/crates/neon/src/types_impl/error.rs index d4e7a002d..b192a30af 100644 --- a/crates/neon/src/types_impl/error.rs +++ b/crates/neon/src/types_impl/error.rs @@ -11,7 +11,30 @@ use crate::{ types::{build, private::ValueInternal, utf8::Utf8, Value}, }; -/// A JS `Error` object. +/// The type of JavaScript +/// [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) +/// objects. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// // Create a type error: +/// let err = cx.type_error("expected a number, found a string")?; +/// +/// // Add some custom diagnostic properties to the error: +/// let expected = cx.string("number"); +/// err.set(&mut cx, "expected", expected)?; +/// +/// let found = cx.string("string"); +/// err.set(&mut cx, "found", found)?; +/// +/// // Throw the error: +/// cx.throw(err)?; +/// # Ok(cx.undefined()) +/// # } +/// ``` #[repr(transparent)] #[derive(Debug)] pub struct JsError(raw::Local); @@ -50,6 +73,8 @@ impl Object for JsError {} impl JsError { /// Creates a direct instance of the [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) class. + /// + /// **See also:** [`Context::error`] pub fn error<'a, C: Context<'a>, S: AsRef>( cx: &mut C, msg: S, @@ -62,6 +87,8 @@ impl JsError { } /// Creates an instance of the [`TypeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError) class. + /// + /// **See also:** [`Context::type_error`] pub fn type_error<'a, C: Context<'a>, S: AsRef>( cx: &mut C, msg: S, @@ -74,6 +101,8 @@ impl JsError { } /// Creates an instance of the [`RangeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError) class. + /// + /// **See also:** [`Context::range_error`] pub fn range_error<'a, C: Context<'a>, S: AsRef>( cx: &mut C, msg: S, diff --git a/crates/neon/src/types_impl/mod.rs b/crates/neon/src/types_impl/mod.rs index 21a30ce61..161eceba6 100644 --- a/crates/neon/src/types_impl/mod.rs +++ b/crates/neon/src/types_impl/mod.rs @@ -92,7 +92,42 @@ pub trait Value: private::ValueInternal { } } -/// A JavaScript value of any type. +/// The type of any JavaScript value, i.e., the root of all types. +/// +/// The `JsValue` type is a catch-all type that sits at the top of the +/// [JavaScript type hierarchy](./index.html#the-javascript-type-hierarchy). +/// All JavaScript values can be safely and statically +/// [upcast](crate::handle::Handle::upcast) to `JsValue`; by contrast, a +/// [downcast](crate::handle::Handle::downcast) of a `JsValue` to another type +/// requires a runtime check. +/// (For TypeScript programmers, this can be thought of as similar to TypeScript's +/// [`unknown`](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown) +/// type.) +/// +/// The `JsValue` type can be useful for generic, dynamic, or otherwise +/// hard-to-express API signatures, such as overloaded types: +/// +/// ``` +/// # use neon::prelude::*; +/// // Takes a string and adds the specified padding to the left. +/// // If the padding is a string, it's added as-is. +/// // If the padding is a number, then that number of spaces is added. +/// fn pad_left(mut cx: FunctionContext) -> JsResult { +/// let string: Handle = cx.argument(0)?; +/// let padding: Handle = cx.argument(1)?; +/// +/// let padding: String = if let Ok(str) = padding.downcast::(&mut cx) { +/// str.value(&mut cx) +/// } else if let Ok(num) = padding.downcast::(&mut cx) { +/// " ".repeat(num.value(&mut cx) as usize) +/// } else { +/// return cx.throw_type_error("expected string or number"); +/// }; +/// +/// let new_value = padding + &string.value(&mut cx); +/// Ok(cx.string(&new_value)) +/// } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsValue(raw::Local); @@ -133,12 +168,37 @@ impl JsValue { } } -/// The JavaScript `undefined` value. +/// The type of JavaScript +/// [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) +/// primitives. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// // Extract the console object: +/// let console: Handle = cx.global().get(&mut cx, "console")?; +/// +/// // The undefined value: +/// let undefined = cx.undefined(); +/// +/// // Call console.log(undefined): +/// console.call_method_with(&mut cx, "log")?.arg(undefined).exec(&mut cx)?; +/// # Ok(undefined) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsUndefined(raw::Local); impl JsUndefined { + /// Creates an `undefined` value. + /// + /// Although this method can be called many times, all `undefined` + /// values are indistinguishable. + /// + /// **See also:** [`Context::undefined`] pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Handle<'a, JsUndefined> { JsUndefined::new_internal(cx.env()) } @@ -182,12 +242,34 @@ impl private::ValueInternal for JsUndefined { } } -/// The JavaScript `null` value. +/// The type of JavaScript +/// [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) +/// primitives. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// cx.global() +/// .get::(&mut cx, "console")? +/// .call_method_with(&mut cx, "log")? +/// .arg(cx.null()) +/// .exec(&mut cx)?; +/// # Ok(cx.null()) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsNull(raw::Local); impl JsNull { + /// Creates a `null` value. + /// + /// Although this method can be called many times, all `null` + /// values are indistinguishable. + /// + /// **See also:** [`Context::null`] pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Handle<'a, JsNull> { JsNull::new_internal(cx.env()) } @@ -231,12 +313,36 @@ impl private::ValueInternal for JsNull { } } -/// A JavaScript boolean primitive value. +/// The type of JavaScript +/// [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) +/// primitives. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// // Extract the console.log function: +/// let console: Handle = cx.global().get(&mut cx, "console")?; +/// let log: Handle = console.get(&mut cx, "log")?; +/// +/// // The two Boolean values: +/// let t = cx.boolean(true); +/// let f = cx.boolean(false); +/// +/// // Call console.log(true, false): +/// log.call_with(&cx).arg(t).arg(f).exec(&mut cx)?; +/// # Ok(cx.undefined()) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsBoolean(raw::Local); impl JsBoolean { + /// Creates a Boolean value with value `b`. + /// + /// **See also:** [`Context::boolean`] pub fn new<'a, C: Context<'a>>(cx: &mut C, b: bool) -> Handle<'a, JsBoolean> { JsBoolean::new_internal(cx.env(), b) } @@ -249,6 +355,7 @@ impl JsBoolean { } } + /// Returns the value of this Boolean as a Rust `bool`. pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> bool { let env = cx.env().to_raw(); unsafe { sys::primitive::boolean_value(env, self.to_raw()) } @@ -285,12 +392,32 @@ impl private::ValueInternal for JsBoolean { } } -/// A JavaScript string primitive value. +/// The type of JavaScript +/// [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) +/// primitives. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// // Extract the console.log function: +/// let console: Handle = cx.global().get(&mut cx, "console")?; +/// let log: Handle = console.get(&mut cx, "log")?; +/// +/// // Create a string: +/// let s = cx.string("hello 🥹"); +/// +/// // Call console.log(s): +/// log.call_with(&cx).arg(s).exec(&mut cx)?; +/// # Ok(cx.undefined()) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsString(raw::Local); -/// An error produced when constructing a string that exceeds the JS engine's maximum string size. +/// An error produced when constructing a string that exceeds the limits of the runtime. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] pub struct StringOverflow(usize); @@ -343,26 +470,25 @@ impl private::ValueInternal for JsString { } impl JsString { - /// Return the byte size of this string when converted to a Rust [`String`] with - /// [`JsString::value`]. + /// Returns the size of the UTF-8 representation of this string, + /// measured in 8-bit code units. + /// + /// Equivalent to `self.value(cx).len()` (but more efficient). /// /// # Example /// - /// A function that verifies the length of the passed JavaScript string. The string is assumed - /// to be `hello 🥹` here, which encodes as 10 bytes in UTF-8: + /// The string `"hello 🥹"` encodes as 10 bytes in UTF-8: /// - /// - 6 bytes for `hello ` (including the space). - /// - 4 bytes for the emoji `🥹`. + /// - 6 bytes for `"hello "` (including the space). + /// - 4 bytes for the emoji `"🥹"`. /// /// ```rust /// # use neon::prelude::*; - /// fn string_len(mut cx: FunctionContext) -> JsResult { - /// let len = cx.argument::(0)?.size(&mut cx); - /// // assuming the function is called with the JS string `hello 🥹`. - /// assert_eq!(10, len); - /// - /// Ok(cx.undefined()) - /// } + /// # fn string_len(mut cx: FunctionContext) -> JsResult { + /// let str = cx.string("hello 🥹"); + /// assert_eq!(10, str.size(&mut cx)); + /// # Ok(cx.undefined()) + /// # } /// ``` pub fn size<'a, C: Context<'a>>(&self, cx: &mut C) -> usize { let env = cx.env().to_raw(); @@ -370,25 +496,25 @@ impl JsString { unsafe { sys::string::utf8_len(env, self.to_raw()) } } - /// Return the size of this string encoded as UTF-16 with [`JsString::to_utf16`]. + /// Returns the size of the UTF-16 representation of this string, + /// measured in 16-bit code units. + /// + /// Equivalent to `self.to_utf16(cx).len()` (but more efficient). /// /// # Example /// - /// A function that verifies the length of the passed JavaScript string. The string is assumed - /// to be `hello 🥹` here, which encodes as 8 `u16`s in UTF-16: + /// The string `"hello 🥹"` encodes as 8 code units in UTF-16: /// - /// - 6 `u16`s for `hello ` (including the space). - /// - 2 `u16`s for the emoji `🥹`. + /// - 6 `u16`s for `"hello "` (including the space). + /// - 2 `u16`s for the emoji `"🥹"`. /// /// ```rust /// # use neon::prelude::*; - /// fn string_len_utf16(mut cx: FunctionContext) -> JsResult { - /// let len = cx.argument::(0)?.size_utf16(&mut cx); - /// // assuming the function is called with the JS string `hello 🥹`. - /// assert_eq!(8, len); - /// - /// Ok(cx.undefined()) - /// } + /// # fn string_len_utf16(mut cx: FunctionContext) -> JsResult { + /// let str = cx.string("hello 🥹"); + /// assert_eq!(8, str.size_utf16(&mut cx)); + /// # Ok(cx.undefined()) + /// # } /// ``` pub fn size_utf16<'a, C: Context<'a>>(&self, cx: &mut C) -> usize { let env = cx.env().to_raw(); @@ -396,17 +522,18 @@ impl JsString { unsafe { sys::string::utf16_len(env, self.to_raw()) } } - /// Convert the JavaScript string into a Rust [`String`]. + /// Convert this JavaScript string into a Rust [`String`]. /// /// # Example /// - /// A function that expects a single JavaScript string as argument and prints it out. + /// This example function expects a single JavaScript string as argument + /// and prints it out. /// /// ```rust /// # use neon::prelude::*; /// fn print_string(mut cx: FunctionContext) -> JsResult { /// let s = cx.argument::(0)?.value(&mut cx); - /// println!("JavaScript string content: {}", s); + /// println!("JavaScript string contents: {}", s); /// /// Ok(cx.undefined()) /// } @@ -423,16 +550,15 @@ impl JsString { } } - /// Convert the JavaScript String into a UTF-16 encoded [`Vec`]. + /// Convert this JavaScript string into a [`Vec`] encoded as UTF-16. /// - /// The returned vector is guaranteed to be valid UTF-16. Therefore, any external crate that - /// handles UTF-16 encoded strings, can assume the content to be valid and skip eventual - /// validation steps. + /// The returned vector is guaranteed to be valid UTF-16, so libraries that handle + /// UTF-16-encoded strings can assume the content to be valid. /// /// # Example /// - /// A function that expects a single JavaScript string as argument and prints it out as a raw - /// vector of `u16`s. + /// This example function expects a single JavaScript string as argument and prints it out + /// as a raw vector of `u16`s. /// /// ```rust /// # use neon::prelude::*; @@ -444,17 +570,21 @@ impl JsString { /// } /// ``` /// - /// Again a function that expects a single JavaScript string as argument, but utilizes the - /// [`widestring`](https://crates.io/crates/widestring) crate to handle the raw [`Vec`] as - /// a typical string. + /// This next example function also expects a single JavaScript string as argument and converts + /// to a [`Vec`], but utilizes the [`widestring`](https://crates.io/crates/widestring) + /// crate to handle the vector as a typical string. /// /// ```rust /// # use neon::prelude::*; + /// use widestring::Utf16String; + /// /// fn print_with_widestring(mut cx: FunctionContext) -> JsResult { /// let s = cx.argument::(0)?.to_utf16(&mut cx); - /// // The returned vector is guaranteed to be valid UTF-16. - /// // Therefore, we can skip the validation step. - /// let s = unsafe { widestring::Utf16String::from_vec_unchecked(s) }; + /// + /// // The returned vector is guaranteed to be valid UTF-16, so we can + /// // safely skip the validation step. + /// let s = unsafe { Utf16String::from_vec_unchecked(s) }; + /// /// println!("JavaScript string as UTF-16: {}", s); /// /// Ok(cx.undefined()) @@ -472,10 +602,49 @@ impl JsString { } } + /// Creates a new `JsString` value from a Rust string by copying its contents. + /// + /// This method panics if the string is longer than the maximum string size allowed + /// by the JavaScript engine. + /// + /// # Example + /// + /// ``` + /// # use neon::prelude::*; + /// # fn string_new(mut cx: FunctionContext) -> JsResult { + /// let str = JsString::new(&mut cx, "hello 🥹"); + /// assert_eq!(10, str.size(&mut cx)); + /// # Ok(cx.undefined()) + /// # } + /// ``` + /// + /// **See also:** [`Context::string`] pub fn new<'a, C: Context<'a>, S: AsRef>(cx: &mut C, val: S) -> Handle<'a, JsString> { JsString::try_new(cx, val).unwrap() } + /// Tries to create a new `JsString` value from a Rust string by copying its contents. + /// + /// Returns `Err(StringOverflow)` if the string is longer than the maximum string size + /// allowed by the JavaScript engine. + /// + /// # Example + /// + /// This example tries to construct a JavaScript string from a Rust string of + /// unknown length, and on overflow generates an alternate truncated string with + /// a suffix (`"[…]"`) to indicate the truncation. + /// + /// ``` + /// # use neon::prelude::*; + /// # fn string_try_new(mut cx: FunctionContext) -> JsResult { + /// # static str: &'static str = "hello 🥹"; + /// let s = match JsString::try_new(&mut cx, str) { + /// Ok(s) => s, + /// Err(_) => cx.string(format!("{}[…]", &str[0..32])), + /// }; + /// # Ok(s) + /// # } + /// ``` pub fn try_new<'a, C: Context<'a>, S: AsRef>(cx: &mut C, val: S) -> StringResult<'a> { let val = val.as_ref(); match JsString::new_internal(cx.env(), val) { @@ -502,12 +671,35 @@ impl JsString { } } -/// A JavaScript number value. +/// The type of JavaScript +/// [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) +/// primitives. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// // Extract the console.log function: +/// let console: Handle = cx.global().get(&mut cx, "console")?; +/// let log: Handle = console.get(&mut cx, "log")?; +/// +/// // Create a number: +/// let n = cx.number(17.0); +/// +/// // Call console.log(n): +/// log.call_with(&cx).arg(n).exec(&mut cx)?; +/// # Ok(cx.undefined()) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsNumber(raw::Local); impl JsNumber { + /// Creates a new number with value `x`. + /// + /// **See also:** [`Context::number`] pub fn new<'a, C: Context<'a>, T: Into>(cx: &mut C, x: T) -> Handle<'a, JsNumber> { JsNumber::new_internal(cx.env(), x.into()) } @@ -520,6 +712,7 @@ impl JsNumber { } } + /// Returns the value of this number as a Rust `f64`. pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> f64 { let env = cx.env().to_raw(); unsafe { sys::primitive::number_value(env, self.to_raw()) } @@ -556,7 +749,33 @@ impl private::ValueInternal for JsNumber { } } -/// A JavaScript object. +/// The type of JavaScript +/// [objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#objects), +/// i.e., the root of all object types. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn test(mut cx: FunctionContext) -> JsResult { +/// // Extract the console.log function: +/// let console: Handle = cx.global().get(&mut cx, "console")?; +/// let log: Handle = console.get(&mut cx, "log")?; +/// +/// // Create an object: +/// let obj = cx.empty_object(); +/// +/// let name = cx.string("Neon"); +/// obj.set(&mut cx, "name", name)?; +/// +/// let url = cx.string("https://neon-bindings.com"); +/// obj.set(&mut cx, "url", url)?; +/// +/// // Call console.log(obj): +/// log.call_with(&cx).arg(obj).exec(&mut cx)?; +/// # Ok(cx.undefined()) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsObject(raw::Local); @@ -594,6 +813,9 @@ impl private::ValueInternal for JsObject { impl Object for JsObject {} impl JsObject { + /// Creates a new empty object. + /// + /// **See also:** [`Context::empty_object`] pub fn new<'a, C: Context<'a>>(c: &mut C) -> Handle<'a, JsObject> { JsObject::new_internal(c.env()) } @@ -611,13 +833,48 @@ impl JsObject { } } -/// A JavaScript array object, i.e. a value for which `Array.isArray` +/// The type of JavaScript +/// [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +/// objects. +/// +/// An array is any JavaScript value for which +/// [`Array.isArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) /// would return `true`. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// // Create a new empty array: +/// let a: Handle = cx.empty_array(); +/// +/// // Create some new values to push onto the array: +/// let n = cx.number(17); +/// let s = cx.string("hello"); +/// +/// // Push the elements onto the array: +/// a.set(&mut cx, 0, n)?; +/// a.set(&mut cx, 1, s)?; +/// # Ok(a) +/// # } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsArray(raw::Local); impl JsArray { + /// Constructs a new empty array of length `len`, equivalent to the JavaScript + /// expression `new Array(len)`. + /// + /// Note that for non-zero `len`, this creates a + /// [sparse array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays), + /// which can sometimes have surprising behavior. To ensure that a new array + /// is and remains dense (i.e., not sparse), consider creating an empty array + /// with `JsArray::new(cx, 0)` or `cx.empty_array()` and only appending + /// elements to the end of the array. + /// + /// **See also:** [`Context::empty_array`] pub fn new<'a, C: Context<'a>>(cx: &mut C, len: u32) -> Handle<'a, JsArray> { JsArray::new_internal(cx.env(), len) } @@ -630,6 +887,11 @@ impl JsArray { } } + /// Copies the array contents into a new [`Vec`] by iterating through all indices + /// from 0 to `self.len()`. + /// + /// The length is dynamically checked on each iteration in case the array is modified + /// during the computation. pub fn to_vec<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult>> { let mut result = Vec::with_capacity(self.len_inner(cx.env()) as usize); let mut i = 0; @@ -649,10 +911,14 @@ impl JsArray { } #[allow(clippy::len_without_is_empty)] + /// Returns the length of the array, equivalent to the JavaScript expression + /// [`this.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length). pub fn len<'a, C: Context<'a>>(&self, cx: &mut C) -> u32 { self.len_inner(cx.env()) } + /// Indicates whether the array is empty, equivalent to + /// `self.len() == 0`. pub fn is_empty<'a, C: Context<'a>>(&self, cx: &mut C) -> bool { self.len(cx) == 0 } @@ -690,7 +956,9 @@ impl private::ValueInternal for JsArray { impl Object for JsArray {} -/// A JavaScript function object. +/// The type of JavaScript +/// [`Function`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) +/// objects. #[derive(Debug)] #[repr(transparent)] /// @@ -802,6 +1070,7 @@ impl JsFunction { } #[cfg(feature = "napi-5")] + /// Returns a new `JsFunction` implemented by `f`. pub fn new<'a, C, F, V>(cx: &mut C, f: F) -> JsResult<'a, JsFunction> where C: Context<'a>, @@ -853,6 +1122,9 @@ impl JsFunction { } impl JsFunction { + /// Calls this function. + /// + /// **See also:** [`JsFunction::call_with`]. pub fn call<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, @@ -870,6 +1142,9 @@ impl JsFunction { }) } + /// Calls this function for side effect, discarding its result. + /// + /// **See also:** [`JsFunction::call_with`]. pub fn exec<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, @@ -884,6 +1159,9 @@ impl JsFunction { Ok(()) } + /// Calls this function as a constructor. + /// + /// **See also:** [`JsFunction::construct_with`]. pub fn construct<'a, 'b, C: Context<'a>, AS>(&self, cx: &mut C, args: AS) -> JsResult<'a, CL> where AS: AsRef<[Handle<'b, JsValue>]>, diff --git a/crates/neon/src/types_impl/promise.rs b/crates/neon/src/types_impl/promise.rs index 314c62bfb..90abb0ff6 100644 --- a/crates/neon/src/types_impl/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -49,9 +49,91 @@ const BOUNDARY: FailureBoundary = FailureBoundary { feature = "promise-api", deprecated = "`promise-api` feature has no impact and may be removed" )] -/// The JavaScript [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) value. +/// The type of JavaScript +/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +/// objects. /// -/// [`JsPromise`] may be constructed with [`Context::promise`]. +/// [`JsPromise`] instances may be constructed with [`Context::promise`], which +/// produces both a promise and a [`Deferred`], which can be used to control +/// the behavior of the promise. A `Deferred` struct is similar to the `resolve` +/// and `reject` functions produced by JavaScript's standard +/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) +/// constructor: +/// +/// ```javascript +/// let deferred; +/// let promise = new Promise((resolve, reject) => { +/// deferred = { resolve, reject }; +/// }); +/// ``` +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// fn resolve_promise(mut cx: FunctionContext) -> JsResult { +/// let (deferred, promise) = cx.promise(); +/// let msg = cx.string("Hello, World!"); +/// +/// deferred.resolve(&mut cx, msg); +/// +/// Ok(promise) +/// } +/// ``` +/// +/// # Example: Asynchronous task +/// +/// This example uses the [linkify](https://crates.io/crates/linkify) crate in an +/// asynchronous task, i.e. a +/// [Node worker pool](https://nodejs.org/en/docs/guides/dont-block-the-event-loop/) +/// thread, to find all the links in a text string. +/// +/// Alternate implementations might use a custom Rust thread or thread pool to avoid +/// blocking the worker pool; for more information, see the [`JsFuture`] example. +/// +/// ``` +/// # use neon::prelude::*; +/// use linkify::{LinkFinder, LinkKind}; +/// use easy_cast::Cast; // for safe numerical conversions +/// +/// fn linkify(mut cx: FunctionContext) -> JsResult { +/// let text = cx.argument::(0)?.value(&mut cx); +/// +/// let promise = cx +/// .task(move || { +/// let (indices, kinds): (Vec<_>, Vec<_>) = LinkFinder::new() +/// // The spans() method fully partitions the text +/// // into a sequence of contiguous spans, some of which +/// // are plain text and some of which are links. +/// .spans(&text) +/// .map(|span| { +/// // The first span starts at 0 and the rest start +/// // at their preceding span's end index. +/// let end: u32 = span.end().cast(); +/// +/// let kind: u8 = match span.kind() { +/// Some(LinkKind::Url) => 1, +/// Some(LinkKind::Email) => 2, +/// _ => 0, +/// }; +/// +/// (end, kind) +/// }) +/// .unzip(); +/// (indices, kinds) +/// }) +/// .promise(|mut cx, (indices, kinds)| { +/// let indices = JsUint32Array::from_slice(&mut cx, &indices)?; +/// let kinds = JsUint8Array::from_slice(&mut cx, &kinds)?; +/// let result = cx.empty_object(); +/// result.set(&mut cx, "indices", indices)?; +/// result.set(&mut cx, "kinds", kinds)?; +/// Ok(result) +/// }); +/// +/// Ok(promise) +/// } +/// ``` pub struct JsPromise(raw::Local); impl JsPromise { @@ -190,7 +272,7 @@ impl Value for JsPromise {} impl Object for JsPromise {} -/// [`Deferred`] is a handle that can be used to resolve or reject a [`JsPromise`] +/// A controller struct that can be used to resolve or reject a [`JsPromise`]. /// /// It is recommended to settle a [`Deferred`] with [`Deferred::settle_with`] to ensure /// exceptions are caught. @@ -198,6 +280,10 @@ impl Object for JsPromise {} /// On Node-API versions less than 6, dropping a [`Deferred`] without settling will /// cause a panic. On Node-API 6+, the associated [`JsPromise`] will be automatically /// rejected. +/// +/// # Examples +/// +/// See [`JsPromise`], [`JsFuture`]. pub struct Deferred { internal: Option, #[cfg(feature = "napi-6")] @@ -348,10 +434,86 @@ impl Drop for Deferred { #[cfg(all(feature = "napi-5", feature = "futures"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))] -/// A [`Future`](std::future::Future) created from a [`JsPromise`]. +/// A type of JavaScript +/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +/// object that acts as a [`Future`](std::future::Future). +/// +/// Unlike typical `Future` implementations, `JsFuture`s are eagerly executed +/// because they are backed by a `Promise`. +/// +/// # Example +/// +/// This example uses a `JsFuture` to take asynchronous binary data and perform +/// potentially expensive computations on that data in a Rust thread. +/// +/// The example uses a [Tokio](https://tokio.rs) thread pool (allocated and +/// stored on demand with a [`OnceCell`](https://crates.io/crates/once_cell)) +/// to run the computations. +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// use once_cell::sync::OnceCell; +/// use tokio::runtime::Runtime; +/// +/// // Lazily allocate a Tokio runtime to use as the thread pool. +/// fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { +/// static RUNTIME: OnceCell = OnceCell::new(); +/// +/// RUNTIME +/// .get_or_try_init(Runtime::new) +/// .or_else(|err| cx.throw_error(&err.to_string())) +/// } +/// +/// // async_compute: Promise -> Promise +/// // +/// // Takes a promise that produces a typed array and returns a promise that: +/// // - awaits the typed array from the original promise; +/// // - computes a value from the contents of the array in a background thread; and +/// // - resolves once the computation is completed +/// pub fn async_compute(mut cx: FunctionContext) -> JsResult { +/// let nums: Handle = cx.argument(0)?; +/// +/// // Convert the JS Promise to a Rust Future for use in a compute thread. +/// let nums = nums.to_future(&mut cx, |mut cx, result| { +/// // Get the promise's result value (or throw if it was rejected). +/// let value = result.or_throw(&mut cx)?; +/// +/// // Downcast the result value to a Float64Array. +/// let array: Handle = value.downcast_or_throw(&mut cx)?; +/// +/// // Convert the typed array to a Rust vector. +/// let vec = array.as_slice(&cx).to_vec(); +/// Ok(vec) +/// })?; +/// +/// // Construct a result promise which will be fulfilled when the computation completes. +/// let (deferred, promise) = cx.promise(); +/// let channel = cx.channel(); +/// let runtime = runtime(&mut cx)?; +/// +/// // Perform the computation in a background thread using the Tokio thread pool. +/// runtime.spawn(async move { +/// // Await the JsFuture, which yields Result, JoinError>. +/// let result = match nums.await { +/// // Perform the computation. In this example, we just calculate the sum +/// // of all values in the array; more involved examples might be running +/// // compression or decompression algorithms, encoding or decoding media +/// // codecs, image filters or other media transformations, etc. +/// Ok(nums) => Ok(nums.into_iter().sum::()), +/// Err(err) => Err(err) +/// }; +/// +/// // Resolve the result promise with the result of the computation. +/// deferred.settle_with(&channel, |mut cx| { +/// let result = result.or_throw(&mut cx)?; +/// Ok(cx.number(result)) +/// }); +/// }); /// -/// Unlike typical `Future`, `JsFuture` are eagerly executed because they -/// are backed by a `Promise`. +/// Ok(promise) +/// } +/// ``` pub struct JsFuture { // `Err` is always `Throw`, but `Throw` cannot be sent across threads rx: oneshot::Receiver>,