diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 566188ae61..dd9abc8a3c 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -543,39 +543,25 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.indexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf pub(crate) fn index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.to_string(this)?; - - // TODO: Should throw TypeError if search_string is regular expression - let search_string = ctx.to_string( - args.get(0) - .expect("failed to get argument for String method"), - )?; - - let length = primitive_val.chars().count() as i32; - - // If less than 2 args specified, position is 'undefined', defaults to 0 - let position = if args.len() < 2 { - 0 - } else { - i32::from(args.get(1).expect("Could not get argument")) - }; + let this = ctx.require_object_coercible(this)?; + let string = ctx.to_string(this)?; - let start = min(max(position, 0), length); + let search_string = + ctx.to_string(&args.get(0).cloned().unwrap_or_else(Value::undefined))?; - // Here cannot use the &str method "find", because this returns the byte - // index: we need to return the char index in the JS String - // Instead, iterate over the part we're checking until the slice we're - // checking "starts with" the search string - for index in start..length { - let this_string: StdString = primitive_val.chars().skip(index as usize).collect(); - if this_string.starts_with(search_string.as_str()) { - // Explicitly return early with the index value - return Ok(Value::from(index)); + let length = string.chars().count(); + let start = args + .get(1) + .map(|position| ctx.to_integer(position)) + .transpose()? + .map_or(0, |position| position.max(0.0).min(length as f64) as usize); + + if start < length { + if let Some(position) = string.find(search_string.as_str()) { + return Ok(string[..position].chars().count().into()); } } - // Didn't find a match, so return -1 + Ok(Value::from(-1)) } diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index b73e353229..aecc203b39 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -415,3 +415,85 @@ fn trim_end() { assert_eq!(forward(&mut engine, "'Hello \n'.trimEnd()"), "\"Hello\""); assert_eq!(forward(&mut engine, "' Hello '.trimEnd()"), "\" Hello\""); } + +#[test] +fn index_of_with_no_arguments() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + assert_eq!(forward(&mut engine, "''.indexOf()"), "-1"); + assert_eq!(forward(&mut engine, "'undefined'.indexOf()"), "0"); + assert_eq!(forward(&mut engine, "'a1undefined'.indexOf()"), "2"); + assert_eq!(forward(&mut engine, "'a1undefined1a'.indexOf()"), "2"); + assert_eq!(forward(&mut engine, "'µµµundefined'.indexOf()"), "3"); + assert_eq!(forward(&mut engine, "'µµµundefinedµµµ'.indexOf()"), "3"); +} + +#[test] +fn index_of_with_string_search_string_argument() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + assert_eq!(forward(&mut engine, "''.indexOf('hello')"), "-1"); + assert_eq!( + forward(&mut engine, "'undefined'.indexOf('undefined')"), + "0" + ); + assert_eq!( + forward(&mut engine, "'a1undefined'.indexOf('undefined')"), + "2" + ); + assert_eq!( + forward(&mut engine, "'a1undefined1a'.indexOf('undefined')"), + "2" + ); + assert_eq!( + forward(&mut engine, "'µµµundefined'.indexOf('undefined')"), + "3" + ); + assert_eq!( + forward(&mut engine, "'µµµundefinedµµµ'.indexOf('undefined')"), + "3" + ); +} + +#[test] +fn index_of_with_non_string_search_string_argument() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + assert_eq!(forward(&mut engine, "''.indexOf(1)"), "-1"); + assert_eq!(forward(&mut engine, "'1'.indexOf(1)"), "0"); + assert_eq!(forward(&mut engine, "'true'.indexOf(true)"), "0"); + assert_eq!(forward(&mut engine, "'ab100ba'.indexOf(100)"), "2"); + assert_eq!(forward(&mut engine, "'µµµfalse'.indexOf(true)"), "-1"); + assert_eq!(forward(&mut engine, "'µµµ5µµµ'.indexOf(5)"), "3"); +} + +#[test] +fn index_of_with_from_index_argument() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + assert_eq!(forward(&mut engine, "''.indexOf('x', 2)"), "-1"); + assert_eq!(forward(&mut engine, "'x'.indexOf('x', 2)"), "-1"); + assert_eq!(forward(&mut engine, "'abcx'.indexOf('x', 2)"), "3"); + assert_eq!(forward(&mut engine, "'x'.indexOf('x', 2)"), "-1"); + assert_eq!(forward(&mut engine, "'µµµxµµµ'.indexOf('x', 2)"), "3"); + + assert_eq!( + forward(&mut engine, "'µµµxµµµ'.indexOf('x', 10000000)"), + "-1" + ); +} + +#[test] +fn generic_index_of() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + forward_val( + &mut engine, + "Number.prototype.indexOf = String.prototype.indexOf", + ) + .unwrap(); + + assert_eq!(forward(&mut engine, "(10).indexOf(9)"), "-1"); + assert_eq!(forward(&mut engine, "(10).indexOf(0)"), "1"); + assert_eq!(forward(&mut engine, "(10).indexOf('0')"), "1"); +}