From 26fee7fd120b9990b3a1f9c08a4a458d1bb0370e Mon Sep 17 00:00:00 2001 From: joshyrobot Date: Tue, 1 Mar 2022 14:01:59 -0700 Subject: [PATCH] Update `js-sys` with new features (#2824) * js-sys: add `String#matchAll` * js-sys: add `String#replaceAll` * js-sys: add `Promise#allSettled` * js-sys: add `Promise#any` * js-sys: add `.at()` for indexed collections * js-sys: add `Object.hasOwn()` * js-sys: add `Error` options and `cause` --- crates/js-sys/src/lib.rs | 89 +++++++++++++++++++++++ crates/js-sys/tests/wasm/Error.rs | 26 +++++++ crates/js-sys/tests/wasm/JsString.rs | 99 ++++++++++++++++++++++++++ crates/js-sys/tests/wasm/Object.rs | 7 ++ crates/js-sys/tests/wasm/TypedArray.rs | 12 ++++ 5 files changed, 233 insertions(+) diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index ed69237e9e1..ba300b684ca 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -306,6 +306,11 @@ extern "C" { #[wasm_bindgen(constructor)] pub fn new_with_length(len: u32) -> Array; + /// Retrieves the element at the index, counting from the end if negative + /// (returns `undefined` if the index is out of range). + #[wasm_bindgen(method)] + pub fn at(this: &Array, index: i32) -> JsValue; + /// Retrieves the element at the index (returns `undefined` if the index is out of range). #[wasm_bindgen(method, structural, indexing_getter)] pub fn get(this: &Array, index: u32) -> JsValue; @@ -1483,6 +1488,17 @@ extern "C" { /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) #[wasm_bindgen(constructor)] pub fn new(message: &str) -> Error; + #[wasm_bindgen(constructor)] + pub fn new_with_options(message: &str, options: &Object) -> Error; + + /// The cause property is the underlying cause of the error. + /// Usually this is used to add context to re-thrown errors. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#differentiate_between_similar_errors) + #[wasm_bindgen(method, getter, structural)] + pub fn cause(this: &Error) -> JsValue; + #[wasm_bindgen(method, setter, structural)] + pub fn set_cause(this: &Error, cause: &JsValue); /// The message property is a human-readable description of the error. /// @@ -3125,6 +3141,14 @@ extern "C" { #[wasm_bindgen(method, js_name = hasOwnProperty)] pub fn has_own_property(this: &Object, property: &JsValue) -> bool; + /// The `Object.hasOwn()` method returns a boolean indicating whether the + /// object passed in has the specified property as its own property (as + /// opposed to inheriting it). + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn) + #[wasm_bindgen(static_method_of = Object, js_name = hasOwn)] + pub fn has_own(instance: &Object, property: &JsValue) -> bool; + /// The `Object.is()` method determines whether two values are the same value. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) @@ -4304,6 +4328,14 @@ extern "C" { #[wasm_bindgen(method, getter, structural)] pub fn length(this: &JsString) -> u32; + /// The 'at()' method returns a new string consisting of the single UTF-16 + /// code unit located at the specified offset into the string, counting from + /// the end if it's negative. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/at) + #[wasm_bindgen(method, js_class = "String")] + pub fn at(this: &JsString, index: i32) -> Option; + /// The String object's `charAt()` method returns a new string consisting of /// the single UTF-16 code unit located at the specified offset into the /// string. @@ -4461,6 +4493,12 @@ extern "C" { #[wasm_bindgen(method, js_class = "String", js_name = match)] pub fn match_(this: &JsString, pattern: &RegExp) -> Option; + /// The `match_all()` method is similar to `match()`, but gives an iterator of `exec()` arrays, which preserve capture groups. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll) + #[wasm_bindgen(method, js_class = "String", js_name = matchAll)] + pub fn match_all(this: &JsString, pattern: &RegExp) -> Iterator; + /// The `normalize()` method returns the Unicode Normalization Form /// of a given string (if the value isn't a string, it will be converted to one first). /// @@ -4522,6 +4560,36 @@ extern "C" { replacement: &Function, ) -> JsString; + /// The `replace_all()` method returns a new string with all matches of a pattern + /// replaced by a replacement. The pattern can be a string or a global RegExp, and + /// the replacement can be a string or a function to be called for each match. + /// + /// Note: The original string will remain unchanged. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll) + #[wasm_bindgen(method, js_class = "String", js_name = replaceAll)] + pub fn replace_all(this: &JsString, pattern: &str, replacement: &str) -> JsString; + + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll) + #[wasm_bindgen(method, js_class = "String", js_name = replaceAll)] + pub fn replace_all_with_function( + this: &JsString, + pattern: &str, + replacement: &Function, + ) -> JsString; + + #[wasm_bindgen(method, js_class = "String", js_name = replaceAll)] + pub fn replace_all_by_pattern(this: &JsString, pattern: &RegExp, replacement: &str) + -> JsString; + + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll) + #[wasm_bindgen(method, js_class = "String", js_name = replaceAll)] + pub fn replace_all_by_pattern_with_function( + this: &JsString, + pattern: &RegExp, + replacement: &Function, + ) -> JsString; + /// The `search()` method executes a search for a match between /// a regular expression and this String object. /// @@ -5323,6 +5391,23 @@ extern "C" { #[wasm_bindgen(static_method_of = Promise)] pub fn all(obj: &JsValue) -> Promise; + /// The `Promise.allSettled(iterable)` method returns a single `Promise` that + /// resolves when all of the promises in the iterable argument have either + /// fulfilled or rejected or when the iterable argument contains no promises. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) + #[wasm_bindgen(static_method_of = Promise, js_name = allSettled)] + pub fn all_settled(obj: &JsValue) -> Promise; + + /// The `Promise.any(iterable)` method returns a single `Promise` that + /// resolves when any of the promises in the iterable argument have resolved + /// or when the iterable argument contains no promises. It rejects with an + /// `AggregateError` if all promises in the iterable rejected. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) + #[wasm_bindgen(static_method_of = Promise)] + pub fn any(obj: &JsValue) -> Promise; + /// The `Promise.race(iterable)` method returns a promise that resolves or /// rejects as soon as one of the promises in the iterable resolves or /// rejects, with the value or reason from that promise. @@ -5573,6 +5658,10 @@ macro_rules! arrays { #[wasm_bindgen(method)] pub fn set(this: &$name, src: &JsValue, offset: u32); + /// Gets the value at `idx`, counting from the end if negative. + #[wasm_bindgen(method)] + pub fn at(this: &$name, idx: i32) -> Option<$ty>; + /// Gets the value at `idx`, equivalent to the javascript `my_var = arr[idx]`. #[wasm_bindgen(method, structural, indexing_getter)] pub fn get_index(this: &$name, idx: u32) -> $ty; diff --git a/crates/js-sys/tests/wasm/Error.rs b/crates/js-sys/tests/wasm/Error.rs index b6414bf75d7..efe6dfcaf55 100644 --- a/crates/js-sys/tests/wasm/Error.rs +++ b/crates/js-sys/tests/wasm/Error.rs @@ -9,6 +9,32 @@ fn new() { assert_eq!(JsValue::from(error.message()), "some message"); } +#[wasm_bindgen_test] +fn new_with_cause() { + let options = Object::new(); + Reflect::set( + options.as_ref(), + &JsValue::from("cause"), + &JsValue::from("some cause"), + ) + .unwrap(); + let error = Error::new_with_options("some message", &options); + assert_eq!(error.cause(), "some cause"); +} + +#[wasm_bindgen_test] +fn empty_cause() { + let error = Error::new("test"); + assert_eq!(error.cause(), JsValue::UNDEFINED); +} + +#[wasm_bindgen_test] +fn set_cause() { + let error = Error::new("test"); + error.set_cause(&JsValue::from("different")); + assert_eq!(error.cause(), "different"); +} + #[wasm_bindgen_test] fn set_message() { let error = Error::new("test"); diff --git a/crates/js-sys/tests/wasm/JsString.rs b/crates/js-sys/tests/wasm/JsString.rs index 862a8a2ad4d..33cd53bce9f 100644 --- a/crates/js-sys/tests/wasm/JsString.rs +++ b/crates/js-sys/tests/wasm/JsString.rs @@ -188,6 +188,13 @@ fn match_() { assert_eq!(Reflect::get(obj.as_ref(), &"0".into()).unwrap(), "T"); assert_eq!(Reflect::get(obj.as_ref(), &"1".into()).unwrap(), "I"); + let re = RegExp::new("[A-Z]([a-z]*)", "g"); + let result = JsString::from(s).match_(&re); + let obj = result.unwrap(); + + assert_eq!(Reflect::get(obj.as_ref(), &"0".into()).unwrap(), "The"); + assert_eq!(Reflect::get(obj.as_ref(), &"1".into()).unwrap(), "It"); + let result = JsString::from("foo").match_(&re); assert!(result.is_none()); @@ -209,6 +216,66 @@ fn match_() { assert_eq!(Reflect::get(obj.as_ref(), &"input".into()).unwrap(), s); } +#[wasm_bindgen_test] +fn match_all() { + let s = "The quick brown fox jumped over the lazy dog. It barked."; + let re = RegExp::new("[A-Z]([a-z]*)", "g"); + let result: Vec<_> = JsString::from(s) + .match_all(&re) + .into_iter() + .collect::>() + .unwrap(); + + let obj = &result[0]; + assert_eq!(Reflect::get(obj.as_ref(), &"0".into()).unwrap(), "The"); + assert_eq!(Reflect::get(obj.as_ref(), &"1".into()).unwrap(), "he"); + + let obj = &result[1]; + assert_eq!(Reflect::get(obj.as_ref(), &"0".into()).unwrap(), "It"); + assert_eq!(Reflect::get(obj.as_ref(), &"1".into()).unwrap(), "t"); + + let result: Vec<_> = JsString::from("foo") + .match_all(&re) + .into_iter() + .collect::>() + .unwrap(); + assert_eq!(result.len(), 0); + + let s = "For more information, see Chapter 3.4.5.1. Also see Chapter 3.1.4"; + let re = RegExp::new("see (chapter \\d+(\\.\\d)*)", "gi"); + let result: Vec<_> = JsString::from(s) + .match_all(&re) + .into_iter() + .collect::>() + .unwrap(); + + let obj = &result[0]; + assert_eq!( + Reflect::get(obj.as_ref(), &"0".into()).unwrap(), + "see Chapter 3.4.5.1" + ); + assert_eq!( + Reflect::get(obj.as_ref(), &"1".into()).unwrap(), + "Chapter 3.4.5.1" + ); + assert_eq!(Reflect::get(obj.as_ref(), &"2".into()).unwrap(), ".1"); + assert_eq!(Reflect::get(obj.as_ref(), &"index".into()).unwrap(), 22); + assert_eq!(Reflect::get(obj.as_ref(), &"input".into()).unwrap(), s); + + let obj = &result[1]; + assert_eq!( + Reflect::get(obj.as_ref(), &"0".into()).unwrap(), + "see Chapter 3.1.4" + ); + assert_eq!( + Reflect::get(obj.as_ref(), &"1".into()).unwrap(), + "Chapter 3.1.4" + ); + assert_eq!(Reflect::get(obj.as_ref(), &"2".into()).unwrap(), ".4"); + assert_eq!(Reflect::get(obj.as_ref(), &"index".into()).unwrap(), 48); + assert_eq!(Reflect::get(obj.as_ref(), &"input".into()).unwrap(), s); +} + #[wasm_bindgen_test] fn normalize() { let js = JsString::from("\u{1E9B}\u{0323}"); @@ -290,6 +357,38 @@ fn replace() { assert_eq!(result, "border-top"); } +#[wasm_bindgen_test] +fn replace_all() { + let js = JsString::from( + "The quick brown fox jumped over the lazy dog. If the dog reacted, was it really lazy?", + ); + let result = js.replace_all("dog", "ferret"); + + assert_eq!( + result, + "The quick brown fox jumped over the lazy ferret. If the ferret reacted, was it really lazy?" + ); + + let js = JsString::from("borderTopTest"); + let result = js.replace_all_with_function("T", &get_replacer_function()); + + assert_eq!(result, "border-top-test"); + + let js = JsString::from( + "The quick brown fox jumped over the lazy dog. If the dog reacted, was it really lazy?", + ); + let re = RegExp::new("dog", "g"); + let result = js.replace_all_by_pattern(&re, "ferret"); + + assert_eq!(result, "The quick brown fox jumped over the lazy ferret. If the ferret reacted, was it really lazy?"); + + let js = JsString::from("borderTopTest"); + let re = RegExp::new("[A-Z]", "g"); + let result = js.replace_all_by_pattern_with_function(&re, &get_replacer_function()); + + assert_eq!(result, "border-top-test"); +} + #[wasm_bindgen_test] fn search() { let js = JsString::from( diff --git a/crates/js-sys/tests/wasm/Object.rs b/crates/js-sys/tests/wasm/Object.rs index bc7c9b09087..4f1ade9727c 100644 --- a/crates/js-sys/tests/wasm/Object.rs +++ b/crates/js-sys/tests/wasm/Object.rs @@ -191,6 +191,13 @@ fn has_own_property() { assert!(map_with_symbol_key().has_own_property(&symbol_key())); } +#[wasm_bindgen_test] +fn has_own() { + assert!(Object::has_own(&foo_42(), &"foo".into())); + assert!(!Object::has_own(&foo_42(), &"bar".into())); + assert!(Object::has_own(&map_with_symbol_key(), &symbol_key())); +} + #[wasm_bindgen_test] fn to_string() { assert_eq!(Object::new().to_string(), "[object Object]"); diff --git a/crates/js-sys/tests/wasm/TypedArray.rs b/crates/js-sys/tests/wasm/TypedArray.rs index 1c2af800fcb..ef06a4c440b 100644 --- a/crates/js-sys/tests/wasm/TypedArray.rs +++ b/crates/js-sys/tests/wasm/TypedArray.rs @@ -89,6 +89,18 @@ fn new_fill() { each!(test_fill); } +macro_rules! test_at { + ($arr:ident) => {{ + let arr = $arr::new(&2.into()); + arr.set_index(1, 1 as _); + assert_eq!(arr.at(-1).unwrap() as f64, 1 as f64); + }}; +} +#[wasm_bindgen_test] +fn new_at() { + each!(test_at); +} + macro_rules! test_get_set { ($arr:ident) => {{ let arr = $arr::new(&1.into());