Skip to content

Commit

Permalink
feat(minifier): constant fold String.fromCharCode (#8248)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen authored Jan 4, 2025
1 parent 0845162 commit 65f46f5
Showing 1 changed file with 69 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cow_utils::CowUtils;
use oxc_ast::ast::*;
use oxc_ecmascript::{
constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf,
StringLastIndexOf, StringSubstring,
StringLastIndexOf, StringSubstring, ToInt32,
};
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};

Expand All @@ -30,20 +30,20 @@ impl<'a> Traverse<'a> for PeepholeReplaceKnownMethods {
}
}

impl PeepholeReplaceKnownMethods {
impl<'a> PeepholeReplaceKnownMethods {
pub fn new() -> Self {
Self { changed: false }
}

fn try_fold_known_string_methods<'a>(
fn try_fold_known_string_methods(
&mut self,
node: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::CallExpression(ce) = node else { return };
let Expression::StaticMemberExpression(member) = &ce.callee else { return };
if let Expression::StringLiteral(s) = &member.object {
let replacement = match member.property.name.as_str() {
let replacement = match &member.object {
Expression::StringLiteral(s) => match member.property.name.as_str() {
"toLowerCase" | "toUpperCase" | "trim" => {
let value = match member.property.name.as_str() {
"toLowerCase" => s.value.cow_to_lowercase(),
Expand All @@ -59,16 +59,24 @@ impl PeepholeReplaceKnownMethods {
"charCodeAt" => Self::try_fold_string_char_code_at(ce, s, ctx),
"replace" | "replaceAll" => Self::try_fold_string_replace(ce, member, s, ctx),
_ => None,
};

if let Some(replacement) = replacement {
self.changed = true;
*node = replacement;
},
Expression::Identifier(ident)
if ident.name == "String"
&& member.property.name == "fromCharCode"
&& Ctx(ctx).is_global_reference(ident) =>
{
Self::try_fold_string_from_char_code(ce, ctx)
}
_ => None,
};

if let Some(replacement) = replacement {
self.changed = true;
*node = replacement;
}
}

fn try_fold_string_index_of<'a>(
fn try_fold_string_index_of(
call_expr: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
string_lit: &StringLiteral<'a>,
Expand Down Expand Up @@ -96,7 +104,7 @@ impl PeepholeReplaceKnownMethods {
Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal))
}

fn try_fold_string_substring_or_slice<'a>(
fn try_fold_string_substring_or_slice(
call_expr: &CallExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
Expand Down Expand Up @@ -131,7 +139,7 @@ impl PeepholeReplaceKnownMethods {
))
}

fn try_fold_string_char_at<'a>(
fn try_fold_string_char_at(
call_expr: &CallExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
Expand Down Expand Up @@ -163,7 +171,7 @@ impl PeepholeReplaceKnownMethods {
Some(ctx.ast.expression_string_literal(span, result, None))
}

fn try_fold_string_char_code_at<'a>(
fn try_fold_string_char_code_at(
call_expr: &CallExpression<'a>,
string_lit: &StringLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
Expand All @@ -179,7 +187,7 @@ impl PeepholeReplaceKnownMethods {
Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal))
}

fn try_fold_string_replace<'a>(
fn try_fold_string_replace(
call_expr: &CallExpression<'a>,
member: &StaticMemberExpression<'a>,
string_lit: &StringLiteral<'a>,
Expand Down Expand Up @@ -217,6 +225,31 @@ impl PeepholeReplaceKnownMethods {
};
Some(ctx.ast.expression_string_literal(span, result, None))
}

fn try_fold_string_from_char_code(
ce: &CallExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.iter().any(|arg| !matches!(arg, Argument::NumericLiteral(_))) {
return None;
}
let mut s = String::with_capacity(args.len());
for arg in args {
let Argument::NumericLiteral(lit) = arg else { unreachable!() };
if lit.value.is_nan() || lit.value.is_infinite() {
return None;
}
let v = lit.value.to_int_32();
if v >= 65535 {
return None;
}
let Ok(v) = u32::try_from(v) else { return None };
let Ok(c) = char::try_from(v) else { return None };
s.push(c);
}
Some(ctx.ast.expression_string_literal(ce.span, s, None))
}
}

/// Port from: <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
Expand Down Expand Up @@ -1058,4 +1091,25 @@ mod test {
let right = "function f(/** string */ a) {".to_string() + expected + "}";
test(left.as_str(), right.as_str());
}

#[test]
fn test_fold_string_from_char_code() {
test("String.fromCharCode()", "''");
test("String.fromCharCode(0)", "'\\0'");
test("String.fromCharCode(120)", "'x'");
test("String.fromCharCode(120, 121)", "'xy'");
test_same("String.fromCharCode(55358, 56768)");
test("String.fromCharCode(0x10000)", "String.fromCharCode(65536)");
test("String.fromCharCode(0x10078, 0x10079)", "String.fromCharCode(0x10078, 0x10079)");
test("String.fromCharCode(0x1_0000_FFFF)", "String.fromCharCode(4295032831)");
test_same("String.fromCharCode(NaN)");
test_same("String.fromCharCode(-Infinity)");
test_same("String.fromCharCode(Infinity)");
test_same("String.fromCharCode(null)");
test_same("String.fromCharCode(undefined)");
test_same("String.fromCharCode('123')");
test_same("String.fromCharCode(x)");
test_same("String.fromCharCode('x')");
test_same("String.fromCharCode('0.5')");
}
}

0 comments on commit 65f46f5

Please sign in to comment.