From bbca7436890f6ef971807b7079f1472bc3e5a8df Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sat, 12 Oct 2024 09:40:43 +0000 Subject: [PATCH] refactor(minifier): move string methods to `oxc_ecmascript` (#6472) --- crates/oxc_ecmascript/Cargo.toml | 2 +- crates/oxc_ecmascript/src/lib.rs | 8 +- crates/oxc_ecmascript/src/string_index_of.rs | 50 +++++ .../src/string_last_index_of.rs | 40 ++++ ...d.rs => peephole_replace_known_methods.rs} | 23 +-- .../string_methods.rs | 190 ------------------ 6 files changed, 106 insertions(+), 207 deletions(-) create mode 100644 crates/oxc_ecmascript/src/string_index_of.rs create mode 100644 crates/oxc_ecmascript/src/string_last_index_of.rs rename crates/oxc_minifier/src/ast_passes/{peephole_replace_known_methods/mod.rs => peephole_replace_known_methods.rs} (98%) delete mode 100644 crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs diff --git a/crates/oxc_ecmascript/Cargo.toml b/crates/oxc_ecmascript/Cargo.toml index 47ae1587abb44..f9330c7b67261 100644 --- a/crates/oxc_ecmascript/Cargo.toml +++ b/crates/oxc_ecmascript/Cargo.toml @@ -17,7 +17,7 @@ description.workspace = true workspace = true [lib] -test = false +test = true doctest = false [dependencies] diff --git a/crates/oxc_ecmascript/src/lib.rs b/crates/oxc_ecmascript/src/lib.rs index fb6d01599e00e..86fb5aed24f48 100644 --- a/crates/oxc_ecmascript/src/lib.rs +++ b/crates/oxc_ecmascript/src/lib.rs @@ -1,12 +1,18 @@ //! Methods defined in the [ECMAScript Language Specification](https://tc39.es/ecma262). +// [Syntax-Directed Operations](https://tc39.es/ecma262/#sec-syntax-directed-operations) mod bound_names; mod is_simple_parameter_list; mod private_bound_identifiers; mod prop_name; + +// Abstract Operations +mod string_index_of; +mod string_last_index_of; mod to_int_32; pub use self::{ bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList, - private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName, to_int_32::ToInt32, + private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName, + string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf, to_int_32::ToInt32, }; diff --git a/crates/oxc_ecmascript/src/string_index_of.rs b/crates/oxc_ecmascript/src/string_index_of.rs new file mode 100644 index 0000000000000..86f81ad2218e4 --- /dev/null +++ b/crates/oxc_ecmascript/src/string_index_of.rs @@ -0,0 +1,50 @@ +use crate::ToInt32; + +pub trait StringIndexOf { + /// `String.prototype.indexOf ( searchString [ , position ] )` + /// + fn index_of(&self, search_value: Option<&str>, from_index: Option) -> isize; +} + +impl StringIndexOf for &str { + #[expect(clippy::cast_possible_wrap, clippy::cast_sign_loss)] + fn index_of(&self, search_value: Option<&str>, from_index: Option) -> isize { + let from_index = from_index.map_or(0, |x| x.to_int_32().max(0)) as usize; + let Some(search_value) = search_value else { + return -1; + }; + let result = self.chars().skip(from_index).collect::().find(search_value); + result.map(|index| index + from_index).map_or(-1, |index| index as isize) + } +} + +#[cfg(test)] +mod test { + #[test] + fn test_string_index_of() { + use super::StringIndexOf; + + assert_eq!("test test test".index_of(Some("t"), Some(0.0)), 0); + assert_eq!("test test test".index_of(Some("t"), Some(1.0)), 3); + assert_eq!("test test test".index_of(Some("t"), Some(4.0)), 5); + assert_eq!("test test test".index_of(Some("t"), Some(4.1)), 5); + assert_eq!("test test test".index_of(Some("t"), Some(0.0)), 0); + assert_eq!("test test test".index_of(Some("t"), Some(-1.0)), 0); + assert_eq!("test test test".index_of(Some("t"), Some(-1.0)), 0); + assert_eq!("test test test".index_of(Some("t"), Some(-1.1)), 0); + assert_eq!("test test test".index_of(Some("t"), Some(-1_073_741_825.0)), 0); + assert_eq!("test test test".index_of(Some("e"), Some(0.0)), 1); + assert_eq!("test test test".index_of(Some("s"), Some(0.0)), 2); + assert_eq!("test test test".index_of(Some("test"), Some(4.0)), 5); + assert_eq!("test test test".index_of(Some("test"), Some(5.0)), 5); + assert_eq!("test test test".index_of(Some("test"), Some(6.0)), 10); + assert_eq!("test test test".index_of(Some("test"), Some(0.0)), 0); + assert_eq!("test test test".index_of(Some("test"), Some(-1.0)), 0); + assert_eq!("test test test".index_of(Some("not found"), Some(-1.0)), -1); + assert_eq!("test test test".index_of(Some("test"), Some(-1.0)), 0); + assert_eq!("test test test".index_of(Some("test"), Some(-1_073_741_825.0)), 0); + assert_eq!("test test test".index_of(Some("test"), Some(0.0)), 0); + assert_eq!("test test test".index_of(Some("notpresent"), Some(0.0)), -1); + assert_eq!("test test test".index_of(None, Some(0.0)), -1); + } +} diff --git a/crates/oxc_ecmascript/src/string_last_index_of.rs b/crates/oxc_ecmascript/src/string_last_index_of.rs new file mode 100644 index 0000000000000..889f48f71bf1e --- /dev/null +++ b/crates/oxc_ecmascript/src/string_last_index_of.rs @@ -0,0 +1,40 @@ +use crate::ToInt32; + +pub trait StringLastIndexOf { + /// `String.prototype.lastIndexOf ( searchString [ , position ] )` + /// + fn last_index_of(&self, search_value: Option<&str>, from_index: Option) -> isize; +} + +impl StringLastIndexOf for &str { + #[expect(clippy::cast_possible_wrap, clippy::cast_sign_loss)] + fn last_index_of(&self, search_value: Option<&str>, from_index: Option) -> isize { + let Some(search_value) = search_value else { return -1 }; + let from_index = + from_index.map_or(usize::MAX, |x| x.to_int_32().max(0) as usize + search_value.len()); + self.chars() + .take(from_index) + .collect::() + .rfind(search_value) + .map_or(-1, |index| index as isize) + } +} + +#[cfg(test)] +mod test { + #[test] + fn test_prototype_last_index_of() { + use super::StringLastIndexOf; + assert_eq!("test test test".last_index_of(Some("test"), Some(15.0)), 10); + assert_eq!("test test test".last_index_of(Some("test"), Some(14.0)), 10); + assert_eq!("test test test".last_index_of(Some("test"), Some(10.0)), 10); + assert_eq!("test test test".last_index_of(Some("test"), Some(9.0)), 5); + assert_eq!("test test test".last_index_of(Some("test"), Some(6.0)), 5); + assert_eq!("test test test".last_index_of(Some("test"), Some(5.0)), 5); + assert_eq!("test test test".last_index_of(Some("test"), Some(4.0)), 0); + assert_eq!("test test test".last_index_of(Some("test"), Some(0.0)), 0); + assert_eq!("test test test".last_index_of(Some("notpresent"), Some(0.0)), -1); + assert_eq!("test test test".last_index_of(None, Some(1.0)), -1); + assert_eq!("abcdef".last_index_of(Some("b"), None), 1); + } +} diff --git a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/mod.rs b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs similarity index 98% rename from crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/mod.rs rename to crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs index 6134a5628eef6..286f8a22d1071 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs @@ -1,9 +1,8 @@ use cow_utils::CowUtils; + use oxc_ast::ast::*; +use oxc_ecmascript::{StringIndexOf, StringLastIndexOf}; use oxc_traverse::{Traverse, TraverseCtx}; -use string_methods::StringUtils; - -mod string_methods; use crate::CompressorPass; @@ -110,18 +109,12 @@ impl PeepholeReplaceKnownMethods { _ => return None, }; - let result = if member.property.name.as_str() == "indexOf" { - StringUtils::evaluate_string_index_of( - &string_lit.value, - search_value, - search_start_index, - ) - } else { - StringUtils::evaluate_string_last_index_of( - &string_lit.value, - search_value, - search_start_index, - ) + let result = match member.property.name.as_str() { + "indexOf" => string_lit.value.as_str().index_of(search_value, search_start_index), + "lastIndexOf" => { + string_lit.value.as_str().last_index_of(search_value, search_start_index) + } + _ => unreachable!(), }; #[expect(clippy::cast_precision_loss)] diff --git a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs deleted file mode 100644 index e72230e333bfd..0000000000000 --- a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods/string_methods.rs +++ /dev/null @@ -1,190 +0,0 @@ -use oxc_ecmascript::ToInt32; - -pub(super) struct StringUtils; - -impl StringUtils { - #[expect(clippy::cast_possible_wrap, clippy::cast_sign_loss)] - pub(super) fn evaluate_string_index_of( - string: &str, - search_value: Option<&str>, - from_index: Option, - ) -> isize { - let from_index = from_index.map_or(0, |x| x.to_int_32().max(0)) as usize; - let Some(search_value) = search_value else { - return -1; - }; - let result = string.chars().skip(from_index).collect::().find(search_value); - result.map(|index| index + from_index).map_or(-1, |index| index as isize) - } - - #[expect(clippy::cast_possible_wrap, clippy::cast_sign_loss)] - pub(super) fn evaluate_string_last_index_of( - string: &str, - search_value: Option<&str>, - from_index: Option, - ) -> isize { - let Some(search_value) = search_value else { return -1 }; - - let from_index = - from_index.map_or(usize::MAX, |x| x.to_int_32().max(0) as usize + search_value.len()); - - string - .chars() - .take(from_index) - .collect::() - .rfind(search_value) - .map_or(-1, |index| index as isize) - } -} - -#[cfg(test)] -mod test { - #[test] - fn test_evaluate_string_index_of() { - use super::StringUtils; - - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(0.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(1.0)), - 3 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(4.0)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(4.1)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(0.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(-1.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(-1.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("t"), Some(-1.1)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of( - "test test test", - Some("t"), - Some(-1_073_741_825.0) - ), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("e"), Some(0.0)), - 1 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("s"), Some(0.0)), - 2 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(4.0)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(5.0)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(6.0)), - 10 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(0.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(-1.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("not found"), Some(-1.0)), - -1 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(-1.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of( - "test test test", - Some("test"), - Some(-1_073_741_825.0) - ), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("test"), Some(0.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_index_of("test test test", Some("notpresent"), Some(0.0)), - -1 - ); - assert_eq!(StringUtils::evaluate_string_index_of("test test test", None, Some(0.0)), -1); - } - - #[test] - fn test_evaluate_string_last_index_of() { - use super::StringUtils; - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(15.0)), - 10 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(14.0)), - 10 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(10.0)), - 10 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(9.0)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(6.0)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(5.0)), - 5 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(4.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", Some("test"), Some(0.0)), - 0 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of( - "test test test", - Some("notpresent"), - Some(0.0) - ), - -1 - ); - assert_eq!( - StringUtils::evaluate_string_last_index_of("test test test", None, Some(1.0)), - -1 - ); - assert_eq!(StringUtils::evaluate_string_last_index_of("abcdef", Some("b"), None), 1); - } -}