diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 3ae5751ba96..ecaf544d6cc 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -737,6 +737,47 @@ pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res Ok(new_array) } +/// Array.prototype.filter ( callback, [ thisArg ] ) +/// +/// For each element in the array the callback function is called, and a new +/// array is constructed for every value whose callback returned a truthy value +/// +pub fn filter(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { + if args.is_empty() { + return Err(to_value( + "missing argument 0 when calling function Array.prototype.filter", + )); + } + + let callback = args.get(0).cloned().unwrap_or_else(undefined); + let this_val = args.get(1).cloned().unwrap_or_else(undefined); + + let length: i32 = + from_value(this.get_field_slice("length")).expect("Could not get `length` property."); + + let new = new_array(&interpreter)?; + + let values = (0..length) + .filter_map(|idx| { + let element = this.get_field_slice(&idx.to_string()); + + let args = vec![element.clone(), to_value(idx), new.clone()]; + + let callback_result = interpreter + .call(&callback, &this_val, args) + .unwrap_or_else(|_| undefined()); + + if callback_result.is_true() { + Some(element) + } else { + None + } + }) + .collect::>(); + + construct_array(&new, &values) +} + /// Create a new `Array` object pub fn create_constructor(global: &Value) -> Value { // Create Constructor @@ -760,6 +801,7 @@ pub fn create_constructor(global: &Value) -> Value { make_builtin_fn!(map, named "map", with length 1, of array_prototype); make_builtin_fn!(fill, named "fill", with length 1, of array_prototype); make_builtin_fn!(for_each, named "forEach", with length 1, of array_prototype); + make_builtin_fn!(filter, named "filter", with length 1, of array_prototype); make_builtin_fn!(pop, named "pop", of array_prototype); make_builtin_fn!(join, named "join", with length 1, of array_prototype); make_builtin_fn!(to_string, named "toString", of array_prototype); diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index ed25220360e..97ea12e627d 100644 --- a/boa/src/builtins/array/tests.rs +++ b/boa/src/builtins/array/tests.rs @@ -675,3 +675,70 @@ fn for_each_push_value() { assert_eq!(forward(&mut engine, "a[6]"), "6"); assert_eq!(forward(&mut engine, "a[7]"), "8"); } + +#[test] +fn filter() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let js = r#" + var empty = []; + var one = ["1"]; + var many = ["1", "0", "1"]; + + var empty_filtered = empty.filter(v => v === "1"); + var one_filtered = one.filter(v => v === "1"); + var zero_filtered = one.filter(v => v === "0"); + var many_one_filtered = many.filter(v => v === "1"); + var many_zero_filtered = many.filter(v => v === "0"); + "#; + + forward(&mut engine, js); + + // assert the old arrays have not been modified + assert_eq!(forward(&mut engine, "one[0]"), String::from("1")); + assert_eq!( + forward(&mut engine, "many[2] + many[1] + many[0]"), + String::from("101") + ); + + // NB: These tests need to be rewritten once `Display` has been implemented for `Array` + // Empty + assert_eq!( + forward(&mut engine, "empty_filtered.length"), + String::from("0") + ); + + // One filtered on "1" + assert_eq!( + forward(&mut engine, "one_filtered.length"), + String::from("1") + ); + assert_eq!(forward(&mut engine, "one_filtered[0]"), String::from("1")); + + // One filtered on "0" + assert_eq!( + forward(&mut engine, "zero_filtered.length"), + String::from("0") + ); + + // Many filtered on "1" + assert_eq!( + forward(&mut engine, "many_one_filtered.length"), + String::from("2") + ); + assert_eq!( + forward(&mut engine, "many_one_filtered[0] + many_one_filtered[1]"), + String::from("11") + ); + + // Many filtered on "0" + assert_eq!( + forward(&mut engine, "many_zero_filtered.length"), + String::from("1") + ); + assert_eq!( + forward(&mut engine, "many_zero_filtered[0]"), + String::from("0") + ); +}