mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(minifier): implement folding substring string fns (#6869)
This commit is contained in:
parent
b075982eaa
commit
fccf82e4df
3 changed files with 95 additions and 6 deletions
|
|
@ -11,6 +11,7 @@ mod string_char_at;
|
||||||
mod string_char_code_at;
|
mod string_char_code_at;
|
||||||
mod string_index_of;
|
mod string_index_of;
|
||||||
mod string_last_index_of;
|
mod string_last_index_of;
|
||||||
|
mod string_substring;
|
||||||
mod string_to_big_int;
|
mod string_to_big_int;
|
||||||
mod string_to_number;
|
mod string_to_number;
|
||||||
mod to_big_int;
|
mod to_big_int;
|
||||||
|
|
@ -30,6 +31,7 @@ pub use self::{
|
||||||
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
|
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
|
||||||
string_char_at::StringCharAt, string_char_code_at::StringCharCodeAt,
|
string_char_at::StringCharAt, string_char_code_at::StringCharCodeAt,
|
||||||
string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf,
|
string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf,
|
||||||
string_to_big_int::StringToBigInt, string_to_number::StringToNumber, to_big_int::ToBigInt,
|
string_substring::StringSubstring, string_to_big_int::StringToBigInt,
|
||||||
to_boolean::ToBoolean, to_int_32::ToInt32, to_number::ToNumber, to_string::ToJsString,
|
string_to_number::StringToNumber, to_big_int::ToBigInt, to_boolean::ToBoolean,
|
||||||
|
to_int_32::ToInt32, to_number::ToNumber, to_string::ToJsString,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
37
crates/oxc_ecmascript/src/string_substring.rs
Normal file
37
crates/oxc_ecmascript/src/string_substring.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use crate::ToInt32;
|
||||||
|
|
||||||
|
pub trait StringSubstring {
|
||||||
|
/// `String.prototype.substring ( start , end ] )`
|
||||||
|
/// <https://tc39.es/ecma262/#sec-string.prototype.substring>
|
||||||
|
fn substring(&self, start: Option<f64>, end: Option<f64>) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringSubstring for &str {
|
||||||
|
#[expect(clippy::cast_sign_loss)]
|
||||||
|
fn substring(&self, start: Option<f64>, end: Option<f64>) -> String {
|
||||||
|
let start = start.map_or(0, |x| x.to_int_32().max(0) as usize);
|
||||||
|
let end = end.map_or(usize::MAX, |x| x.to_int_32().max(0) as usize);
|
||||||
|
let start = start.min(self.len());
|
||||||
|
let end = end.min(self.len());
|
||||||
|
if start > end {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
self.chars().skip(start).take(end - start).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_prototype_last_index_of() {
|
||||||
|
use super::StringSubstring;
|
||||||
|
assert_eq!("foo".substring(Some(1.0), None), "oo");
|
||||||
|
assert_eq!("foo".substring(Some(1.0), Some(2.0)), "o");
|
||||||
|
assert_eq!("foo".substring(Some(1.0), Some(1.0)), "");
|
||||||
|
assert_eq!("foo".substring(Some(1.0), Some(0.0)), "");
|
||||||
|
assert_eq!("foo".substring(Some(0.0), Some(0.0)), "");
|
||||||
|
assert_eq!("foo".substring(Some(0.0), Some(1.0)), "f");
|
||||||
|
assert_eq!("abc".substring(Some(0.0), Some(2.0)), "ab");
|
||||||
|
assert_eq!("abcde".substring(Some(0.0), Some(2.0)), "ab");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use cow_utils::CowUtils;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_ecmascript::{
|
use oxc_ecmascript::{
|
||||||
constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf,
|
constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf,
|
||||||
StringLastIndexOf,
|
StringLastIndexOf, StringSubstring,
|
||||||
};
|
};
|
||||||
use oxc_traverse::{Traverse, TraverseCtx};
|
use oxc_traverse::{Traverse, TraverseCtx};
|
||||||
|
|
||||||
|
|
@ -76,7 +76,12 @@ impl PeepholeReplaceKnownMethods {
|
||||||
ctx,
|
ctx,
|
||||||
),
|
),
|
||||||
// TODO: Implement the rest of the string methods
|
// TODO: Implement the rest of the string methods
|
||||||
"substring" | "slice" => None,
|
"substring" | "slice" => Self::try_fold_string_substring_or_slice(
|
||||||
|
call_expr.span,
|
||||||
|
call_expr,
|
||||||
|
string_lit,
|
||||||
|
ctx,
|
||||||
|
),
|
||||||
"charAt" => {
|
"charAt" => {
|
||||||
Self::try_fold_string_char_at(call_expr.span, call_expr, string_lit, ctx)
|
Self::try_fold_string_char_at(call_expr.span, call_expr, string_lit, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -131,6 +136,53 @@ impl PeepholeReplaceKnownMethods {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_fold_string_substring_or_slice<'a>(
|
||||||
|
span: Span,
|
||||||
|
call_expr: &CallExpression<'a>,
|
||||||
|
string_lit: &StringLiteral<'a>,
|
||||||
|
ctx: &mut TraverseCtx<'a>,
|
||||||
|
) -> Option<Expression<'a>> {
|
||||||
|
if call_expr.arguments.len() > 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_idx = if let Some(v) = call_expr.arguments.first() {
|
||||||
|
let val = match v {
|
||||||
|
Argument::SpreadElement(_) => None,
|
||||||
|
_ => Ctx(ctx).get_side_free_number_value(v.to_expression()),
|
||||||
|
}?;
|
||||||
|
Some(val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let end_idx = if let Some(v) = call_expr.arguments.get(1) {
|
||||||
|
let val = match v {
|
||||||
|
Argument::SpreadElement(_) => None,
|
||||||
|
_ => Ctx(ctx).get_side_free_number_value(v.to_expression()),
|
||||||
|
}?;
|
||||||
|
Some(val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::cast_precision_loss)]
|
||||||
|
if start_idx.is_some_and(|start| start > string_lit.value.len() as f64 || start < 0.0)
|
||||||
|
|| end_idx.is_some_and(|end| end > string_lit.value.len() as f64 || end < 0.0)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(start), Some(end)) = (start_idx, end_idx) {
|
||||||
|
if start > end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Some(ctx.ast.expression_from_string_literal(
|
||||||
|
ctx.ast.string_literal(span, string_lit.value.as_str().substring(start_idx, end_idx)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn try_fold_string_char_at<'a>(
|
fn try_fold_string_char_at<'a>(
|
||||||
span: Span,
|
span: Span,
|
||||||
call_expr: &CallExpression<'a>,
|
call_expr: &CallExpression<'a>,
|
||||||
|
|
@ -393,7 +445,6 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_fold_string_substring() {
|
fn test_fold_string_substring() {
|
||||||
fold("x = 'abcde'.substring(0,2)", "x = 'ab'");
|
fold("x = 'abcde'.substring(0,2)", "x = 'ab'");
|
||||||
fold("x = 'abcde'.substring(1,2)", "x = 'b'");
|
fold("x = 'abcde'.substring(1,2)", "x = 'b'");
|
||||||
|
|
@ -412,7 +463,6 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_fold_string_slice() {
|
fn test_fold_string_slice() {
|
||||||
fold("x = 'abcde'.slice(0,2)", "x = 'ab'");
|
fold("x = 'abcde'.slice(0,2)", "x = 'ab'");
|
||||||
fold("x = 'abcde'.slice(1,2)", "x = 'b'");
|
fold("x = 'abcde'.slice(1,2)", "x = 'b'");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue