Skip to content

Commit

Permalink
feat(minifier): implement folding charAt string fns
Browse files Browse the repository at this point in the history
  • Loading branch information
camc314 committed Oct 12, 2024
1 parent bbca743 commit 273f6f5
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 9 deletions.
4 changes: 3 additions & 1 deletion crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ mod private_bound_identifiers;
mod prop_name;

// Abstract Operations
mod string_char_at;
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,
string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf, to_int_32::ToInt32,
string_char_at::StringCharAt, string_index_of::StringIndexOf,
string_last_index_of::StringLastIndexOf, to_int_32::ToInt32,
};
34 changes: 34 additions & 0 deletions crates/oxc_ecmascript/src/string_char_at.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::ToInt32;

pub trait StringCharAt {
/// `String.prototype.charAt ( pos )`
/// <https://tc39.es/ecma262/#sec-string.prototype.charat>
fn char_at(&self, index: Option<f64>) -> Option<char>;
}

impl StringCharAt for &str {
#[expect(clippy::cast_sign_loss)]
fn char_at(&self, index: Option<f64>) -> Option<char> {
let index = index.map_or(0, |x| x.to_int_32() as isize);
if index < 0 {
None
} else {
self.chars().nth(index as usize)
}
}
}

#[cfg(test)]
mod test {

#[test]
fn test_evaluate_string_char_at() {
use crate::string_char_at::StringCharAt;
assert_eq!("test".char_at(Some(0.0)), Some('t'));
assert_eq!("test".char_at(Some(1.0)), Some('e'));
assert_eq!("test".char_at(Some(2.0)), Some('s'));
assert_eq!("test".char_at(Some(-1.0)), None);
assert_eq!("test".char_at(Some(-1.1)), None);
assert_eq!("test".char_at(Some(-1_073_741_825.0)), None);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use cow_utils::CowUtils;

use oxc_ast::ast::*;
use oxc_ecmascript::{StringIndexOf, StringLastIndexOf};
use oxc_ecmascript::{StringCharAt, StringIndexOf, StringLastIndexOf};
use oxc_traverse::{Traverse, TraverseCtx};

use crate::CompressorPass;
Expand Down Expand Up @@ -76,7 +75,9 @@ impl PeepholeReplaceKnownMethods {
// TODO: Implement the rest of the string methods
"substr" => None,
"substring" | "slice" => None,
"charAt" => None,
"charAt" => {
Self::try_fold_string_char_at(call_expr.span, call_expr, string_lit, ctx)
}
"charCodeAt" => None,
"replace" => None,
"replaceAll" => None,
Expand Down Expand Up @@ -125,6 +126,35 @@ impl PeepholeReplaceKnownMethods {
NumberBase::Decimal,
)));
}

fn try_fold_string_char_at<'a>(
span: Span,
call_expr: &CallExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let char_at_index: Option<f64> = match call_expr.arguments.first() {
Some(Argument::NumericLiteral(numeric_lit)) => Some(numeric_lit.value),
Some(Argument::UnaryExpression(unary_expr))
if unary_expr.operator == UnaryOperator::UnaryNegation =>
{
let Expression::NumericLiteral(numeric_lit) = &unary_expr.argument else {
return None;
};
Some(-(numeric_lit.value))
}
None => None,
_ => return None,
};

let result = &string_lit
.value
.as_str()
.char_at(char_at_index)
.map_or(String::new(), |v| v.to_string());

return Some(ctx.ast.expression_from_string_literal(ctx.ast.string_literal(span, result)));
}
}

/// Port from: <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
Expand Down Expand Up @@ -382,18 +412,19 @@ mod test {
}

#[test]
#[ignore]
fn test_fold_string_char_at() {
fold("x = 'abcde'.charAt(0)", "x = 'a'");
fold("x = 'abcde'.charAt(1)", "x = 'b'");
fold("x = 'abcde'.charAt(2)", "x = 'c'");
fold("x = 'abcde'.charAt(3)", "x = 'd'");
fold("x = 'abcde'.charAt(4)", "x = 'e'");
fold_same("x = 'abcde'.charAt(5)"); // or x = ''
fold_same("x = 'abcde'.charAt(-1)"); // or x = ''
// START: note, the following test cases outputs differ from Google's
fold("x = 'abcde'.charAt(5)", "x = ''");
fold("x = 'abcde'.charAt(-1)", "x = ''");
fold("x = 'abcde'.charAt()", "x = 'a'");
fold("x = 'abcde'.charAt(0, ++z)", "x = 'a'");
// END
fold_same("x = 'abcde'.charAt(y)");
fold_same("x = 'abcde'.charAt()"); // or x = 'a'
fold_same("x = 'abcde'.charAt(0, ++z)"); // or (++z, 'a')
fold_same("x = 'abcde'.charAt(null)"); // or x = 'a'
fold_same("x = 'abcde'.charAt(true)"); // or x = 'b'
// fold("x = '\\ud834\udd1e'.charAt(0)", "x = '\\ud834'");
Expand Down

0 comments on commit 273f6f5

Please sign in to comment.