Skip to content

Commit

Permalink
feat(minifier): implement folding charCodeAt string fns (oxc-projec…
Browse files Browse the repository at this point in the history
  • Loading branch information
camc314 committed Oct 24, 2024
1 parent 4348eae commit e6a5a1b
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 17 deletions.
9 changes: 5 additions & 4 deletions crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod prop_name;

// Abstract Operations
mod string_char_at;
mod string_char_code_at;
mod string_index_of;
mod string_last_index_of;
mod string_to_big_int;
Expand All @@ -27,8 +28,8 @@ pub mod side_effects;
pub use self::{
bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList,
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
string_char_at::StringCharAt, string_index_of::StringIndexOf,
string_last_index_of::StringLastIndexOf, string_to_big_int::StringToBigInt,
string_to_number::StringToNumber, to_big_int::ToBigInt, to_boolean::ToBoolean,
to_int_32::ToInt32, to_number::ToNumber, to_string::ToJsString,
string_char_at::StringCharAt, string_char_code_at::StringCharCodeAt,
string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf,
string_to_big_int::StringToBigInt, string_to_number::StringToNumber, to_big_int::ToBigInt,
to_boolean::ToBoolean, to_int_32::ToInt32, to_number::ToNumber, to_string::ToJsString,
};
32 changes: 32 additions & 0 deletions crates/oxc_ecmascript/src/string_char_code_at.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::StringCharAt;

pub trait StringCharCodeAt {
/// `String.prototype.charCodeAt ( pos )`
/// <https://tc39.es/ecma262/#sec-string.prototype.charcodeat>
fn char_code_at(&self, index: Option<f64>) -> Option<u32>;
}

impl StringCharCodeAt for &str {
fn char_code_at(&self, index: Option<f64>) -> Option<u32> {
self.char_at(index).map(|c| c as u32)
}
}

#[cfg(test)]
mod test {

#[test]
fn test_evaluate_char_code_at() {
use crate::StringCharCodeAt;

assert_eq!("abcde".char_code_at(Some(0.0)), Some(97));
assert_eq!("abcde".char_code_at(Some(1.0)), Some(98));
assert_eq!("abcde".char_code_at(Some(2.0)), Some(99));
assert_eq!("abcde".char_code_at(Some(3.0)), Some(100));
assert_eq!("abcde".char_code_at(Some(4.0)), Some(101));
assert_eq!("abcde".char_code_at(Some(5.0)), None);
assert_eq!("abcde".char_code_at(Some(-1.0)), None);
assert_eq!("abcde".char_code_at(None), Some(97));
assert_eq!("abcde".char_code_at(Some(0.0)), Some(97));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use cow_utils::CowUtils;
use oxc_ast::ast::*;
use oxc_ecmascript::{StringCharAt, StringIndexOf, StringLastIndexOf};
use oxc_ecmascript::{
constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf,
StringLastIndexOf,
};
use oxc_traverse::{Traverse, TraverseCtx};

use crate::CompressorPass;
use crate::{node_util::Ctx, CompressorPass};

/// Minimize With Known Methods
/// <https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeReplaceKnownMethods.java>
Expand Down Expand Up @@ -78,7 +81,9 @@ impl PeepholeReplaceKnownMethods {
"charAt" => {
Self::try_fold_string_char_at(call_expr.span, call_expr, string_lit, ctx)
}
"charCodeAt" => None,
"charCodeAt" => {
Self::try_fold_string_char_code_at(call_expr.span, call_expr, string_lit, ctx)
}
"replace" => None,
"replaceAll" => None,
_ => None,
Expand Down Expand Up @@ -133,6 +138,10 @@ impl PeepholeReplaceKnownMethods {
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if call_expr.arguments.len() > 1 {
return None;
}

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))
Expand All @@ -155,6 +164,35 @@ impl PeepholeReplaceKnownMethods {

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

fn try_fold_string_char_code_at<'a>(
span: Span,
call_expr: &CallExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let char_at_index = call_expr.arguments.first();
let char_at_index = if let Some(v) = char_at_index {
let val = match v {
Argument::SpreadElement(_) => None,
_ => Ctx(ctx).get_side_free_number_value(v.to_expression()),
}?;
Some(val)
} else {
None
};

// TODO: if `result` is `None`, return `NaN` instead of skipping the optimization
let result = string_lit.value.as_str().char_code_at(char_at_index)?;

#[expect(clippy::cast_lossless)]
Some(ctx.ast.expression_from_numeric_literal(ctx.ast.numeric_literal(
span,
result as f64,
result.to_string(),
NumberBase::Decimal,
)))
}
}

/// Port from: <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
Expand Down Expand Up @@ -418,12 +456,10 @@ mod test {
fold("x = 'abcde'.charAt(2)", "x = 'c'");
fold("x = 'abcde'.charAt(3)", "x = 'd'");
fold("x = 'abcde'.charAt(4)", "x = 'e'");
// 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(0, ++z)");
fold_same("x = 'abcde'.charAt(y)");
fold_same("x = 'abcde'.charAt(null)"); // or x = 'a'
fold_same("x = 'abcde'.charAt(true)"); // or x = 'b'
Expand All @@ -436,7 +472,6 @@ mod test {
}

#[test]
#[ignore]
fn test_fold_string_char_code_at() {
fold("x = 'abcde'.charCodeAt(0)", "x = 97");
fold("x = 'abcde'.charCodeAt(1)", "x = 98");
Expand All @@ -446,12 +481,12 @@ mod test {
fold_same("x = 'abcde'.charCodeAt(5)"); // or x = (0/0)
fold_same("x = 'abcde'.charCodeAt(-1)"); // or x = (0/0)
fold_same("x = 'abcde'.charCodeAt(y)");
fold_same("x = 'abcde'.charCodeAt()"); // or x = 97
fold_same("x = 'abcde'.charCodeAt(0, ++z)"); // or (++z, 97)
fold_same("x = 'abcde'.charCodeAt(null)"); // or x = 97
fold_same("x = 'abcde'.charCodeAt(true)"); // or x = 98
// fold("x = '\\ud834\udd1e'.charCodeAt(0)", "x = 55348");
// fold("x = '\\ud834\udd1e'.charCodeAt(1)", "x = 56606");
fold("x = 'abcde'.charCodeAt()", "x = 97");
fold("x = 'abcde'.charCodeAt(0, ++z)", "x = 97");
fold("x = 'abcde'.charCodeAt(null)", "x = 97");
fold("x = 'abcde'.charCodeAt(true)", "x = 98");
// fold("x = '\\ud834\udd1e'.charCodeAt(0)", "x = 55348");
// fold("x = '\\ud834\udd1e'.charCodeAt(1)", "x = 56606");

// Template strings
fold_same("x = `abcdef`.charCodeAt(0)");
Expand Down

0 comments on commit e6a5a1b

Please sign in to comment.