use oxc_str::JSStr;
pub trait StringIndexOf {
fn index_of(&self, search_value: Option<JSStr<'_>>, from_index: Option<f64>) -> isize;
}
impl StringIndexOf for &str {
fn index_of(&self, search_value: Option<JSStr<'_>>, from_index: Option<f64>) -> isize {
JSStr::from(*self).index_of(search_value, from_index)
}
}
impl StringIndexOf for JSStr<'_> {
#[expect(clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn index_of(&self, search_value: Option<JSStr<'_>>, from_index: Option<f64>) -> isize {
let search = search_value.unwrap_or(JSStr::from("undefined"));
let start = from_index
.map_or(0.0, |position| if position.is_nan() { 0.0 } else { position.trunc() })
.max(0.0) as usize;
if search.is_empty() {
return self.len_utf16().min(start) as isize;
}
if let Some(search) = search.as_str() {
return crate::string_search::index_of_utf8(*self, search, start)
.map_or(-1, |index| index as isize);
}
crate::string_search::index_of_code_units(*self, search, start)
.map_or(-1, |index| index as isize)
}
}
#[cfg(test)]
mod test {
use oxc_str::JSStr;
#[test]
fn test_string_index_of() {
use super::StringIndexOf;
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(0.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(1.0)), 3);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(4.0)), 5);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(4.1)), 5);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(0.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(-1.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(-1.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(-1.1)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("t")), Some(-1_073_741_825.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("e")), Some(0.0)), 1);
assert_eq!("test test test".index_of(Some(JSStr::from("s")), Some(0.0)), 2);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(4.0)), 5);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(5.0)), 5);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(6.0)), 10);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(0.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(-1.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("not found")), Some(-1.0)), -1);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(-1.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(-1_073_741_825.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("test")), Some(0.0)), 0);
assert_eq!("test test test".index_of(Some(JSStr::from("notpresent")), Some(0.0)), -1);
assert_eq!("undefined".index_of(None, Some(0.0)), 0);
assert_eq!("test test test".index_of(None, Some(0.0)), -1);
assert_eq!("éx".index_of(Some(JSStr::from("x")), None), 1);
assert_eq!("😀x".index_of(Some(JSStr::from("x")), Some(1.0)), 2);
assert_eq!("ä½ å¥½b".index_of(Some(JSStr::from("b")), None), 2);
assert_eq!("abc".index_of(Some(JSStr::from("a")), Some(4_294_967_296.0)), -1);
assert_eq!("abc".index_of(Some(JSStr::from("")), Some(5.0)), 3);
}
#[test]
fn test_string_index_of_with_lone_surrogates() {
use oxc_allocator::Allocator;
use oxc_str::JSStrBuilder;
use super::StringIndexOf;
let allocator = Allocator::new();
let js_str = |units: &[u16]| {
let mut builder = JSStrBuilder::new_in(&&allocator);
builder.push_utf16(units);
builder.into_js_str()
};
let value = js_str(&[0x61, 0xD800, 0x62]);
assert_eq!(value.index_of(Some(JSStr::from("a")), None), 0);
assert_eq!(value.index_of(Some(JSStr::from("b")), None), 2);
assert_eq!(value.index_of(Some(JSStr::from("b")), Some(2.0)), 2);
assert_eq!(value.index_of(Some(JSStr::from("b")), Some(3.0)), -1);
assert_eq!(value.index_of(Some(JSStr::from("c")), None), -1);
assert_eq!(value.index_of(Some(JSStr::from("")), None), 0);
assert_eq!(value.index_of(Some(JSStr::from("")), Some(2.0)), 2);
assert_eq!(value.index_of(Some(JSStr::from("")), Some(5.0)), 3);
assert_eq!(value.index_of(None, None), -1);
let value = js_str(&[0x75, 0x6E, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x65, 0x64, 0xD800]);
assert_eq!(value.index_of(None, None), 0);
let value = js_str(&[0x78, 0xD83D, 0x79, 0xDE00, 0x7A]);
assert_eq!(value.index_of(Some(JSStr::from("\u{1F600}")), None), -1);
let value = js_str(&[0x61, 0xD83D, 0xDE00, 0xD800, 0x62]);
assert_eq!(value.index_of(Some(JSStr::from("\u{1F600}")), None), 1);
assert_eq!(value.index_of(Some(JSStr::from("b")), None), 4);
assert_eq!(value.index_of(Some(JSStr::from("b")), Some(2.0)), 4);
assert_eq!(value.index_of(Some(JSStr::from("\u{1F600}")), Some(2.0)), -1);
let value = js_str(&[0x61, 0xD800, 0x61]);
assert_eq!(value.index_of(Some(JSStr::from("a")), Some(3e9)), -1);
assert_eq!(value.index_of(Some(JSStr::from("a")), Some(f64::NAN)), 0);
let value = js_str(&[0xD83D, 0xDE00]);
assert_eq!(value.as_str(), Some("\u{1F600}"));
assert_eq!(value.index_of(Some(JSStr::from("\u{1F600}")), None), 0);
let value =
js_str(&[0xE9, 0xD800, 0x61, 0x62, 0x61, 0x62, 0x61, 0xD83D, 0xDE00, 0x61, 0x62]);
assert_eq!(value.index_of(Some(JSStr::from("aba")), Some(3.0)), 4);
assert_eq!(value.index_of(Some(JSStr::from("b")), Some(8.0)), 10);
assert_eq!(value.index_of(Some(JSStr::from("\u{1F600}")), Some(8.0)), -1);
let lead = js_str(&[0xD83D]);
let trail = js_str(&[0xDE00]);
assert_eq!("a\u{1F600}b".index_of(Some(lead), None), 1);
assert_eq!("a\u{1F600}b".index_of(Some(trail), None), 2);
assert_eq!("a\u{1F600}b".index_of(Some(lead), Some(2.0)), -1);
let value = js_str(&[0x61, 0xD800, 0xD83D, 0xDE00]);
assert_eq!(value.index_of(Some(lead), None), 2);
assert_eq!(value.index_of(Some(js_str(&[0xD800])), None), 1);
assert_eq!(value.index_of(Some(js_str(&[0xD83D, 0xDE00])), None), 2);
}
}