mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(minifier): inline constant spread elements into arrays (#21095)
implements flattening logic used to inline constant spread elements into arrays fixes #17358
This commit is contained in:
1 parent
5df2eb4182
commit
f091d779a5
4 files changed
+99
-2
No files matched your search
@@ -365,7 +365,10 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
|
||||
Self::substitute_object_or_array_constructor(expr, ctx);
|
||||
}
|
||||
Expression::BooleanLiteral(_) => Self::substitute_boolean(expr, ctx),
|
||||
Expression::ArrayExpression(_) => Self::substitute_array_expression(expr, ctx),
|
||||
Expression::ArrayExpression(_) => {
|
||||
Self::try_flatten_array_expression_elements(expr, ctx);
|
||||
Self::substitute_array_expression(expr, ctx);
|
||||
}
|
||||
Expression::Identifier(_) => Self::inline_identifier_reference(expr, ctx),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1505,6 +1505,75 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
/// Flatten the spread of constant array literals inside array expressions:
|
||||
/// `[a, ...[1, 2, 3]]` -> `[a, 1, 2, 3]`
|
||||
///
|
||||
/// Elisions inside the spread source are converted to `void 0` to match iterator
|
||||
/// semantics (spreading an elision should result in `undefined`):
|
||||
/// `[a, ...[1, , 3]]` -> `[a, 1, void 0, 3]`
|
||||
/// If there are two or more elisions, we keep the spread as-is to avoid causing
|
||||
/// the code to be longer.
|
||||
pub fn try_flatten_array_expression_elements(
|
||||
expr: &mut Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let Expression::ArrayExpression(array) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (new_size, should_fold) =
|
||||
array.elements.iter().fold((0, false), |(mut new_size, mut should_fold), arg| {
|
||||
new_size += if let ArrayExpressionElement::SpreadElement(spread_el) = arg {
|
||||
if let Expression::ArrayExpression(array_expr) = &spread_el.argument
|
||||
&& array_expr
|
||||
.elements
|
||||
.iter()
|
||||
.filter(|inner_el| inner_el.is_elision())
|
||||
.count()
|
||||
< 2
|
||||
{
|
||||
should_fold = true;
|
||||
array_expr.elements.len()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
(new_size, should_fold)
|
||||
});
|
||||
if !should_fold {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_elements =
|
||||
std::mem::replace(&mut array.elements, ctx.ast.vec_with_capacity(new_size));
|
||||
let new_elements = &mut array.elements;
|
||||
|
||||
for elem in old_elements {
|
||||
if let ArrayExpressionElement::SpreadElement(mut spread_el) = elem {
|
||||
if let Expression::ArrayExpression(array_expr) = &mut spread_el.argument
|
||||
&& array_expr.elements.iter().filter(|inner_el| inner_el.is_elision()).count()
|
||||
< 2
|
||||
{
|
||||
for inner_el in array_expr.elements.drain(..) {
|
||||
if let ArrayExpressionElement::Elision(elision) = inner_el {
|
||||
new_elements.push(ctx.ast.void_0(elision.span).into());
|
||||
} else {
|
||||
new_elements.push(inner_el);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_elements.push(ArrayExpressionElement::SpreadElement(spread_el));
|
||||
}
|
||||
} else {
|
||||
new_elements.push(elem);
|
||||
}
|
||||
}
|
||||
ctx.state.changed = true;
|
||||
}
|
||||
|
||||
/// Transforms long array expression with string literals to `"str1,str2".split(',')`
|
||||
pub fn substitute_array_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// this threshold is chosen by hand by checking the minsize output
|
||||
|
||||
@@ -1007,10 +1007,12 @@ fn test_fold_array_length() {
|
||||
// Not handled yet
|
||||
fold("x = [,,1].length", "x = 3");
|
||||
|
||||
// Foldable after constant spread elements are inlined
|
||||
fold("[...[1, 2, 3]].length", "3");
|
||||
|
||||
// Cannot fold
|
||||
fold("x = [foo(), 0].length", "x = [foo(),0].length");
|
||||
fold_same("x = y.length");
|
||||
fold_same("[...[1, 2, 3]].length");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -958,3 +958,26 @@ fn test_flatten_nested_chain_expression() {
|
||||
test_same("a?.b?.c");
|
||||
test_same("(a?.b).c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flatten_array_spread_elements() {
|
||||
test("var y = [3, 4, ...[1, 2]]", "var y = [3, 4, 1, 2]");
|
||||
test("var y = [...[1, 2], 3, 4]", "var y = [1, 2, 3, 4]");
|
||||
test("var y = [...[1, 2], ...[3, 4]]", "var y = [1, 2, 3, 4]");
|
||||
test("var y = [1, ...[], 2]", "var y = [1, 2]");
|
||||
test("var y = [...[1, , 3]]", "var y = [1, void 0, 3]");
|
||||
test("var y = [...[1, ...[2, 3]]]", "var y = [1, 2, 3]");
|
||||
test("var y = [1, , ...[2, 3]]", "var y = [1, , 2, 3]");
|
||||
test_same("var y = [...x]");
|
||||
test_same("var y = [1, ...x, 2]");
|
||||
test("var x = [1, 2]; var y = [3, 4, ...x]", "var y = [3, 4, 1, 2]");
|
||||
test("var x = [1, 2]; var y = [0, ...x, ...x]", "var x = [1, 2], y = [0, ...x, ...x]");
|
||||
test("var x = [1, ...[2, 3]]; var y = [4, ...x]", "var y = [4, 1, 2, 3]");
|
||||
test("var x = [1, , 3]; var y = [0, ...x]", "var y = [0, 1, void 0, 3]");
|
||||
test("var x = []; var y = [1, ...x, 2]", "var y = [1, 2]");
|
||||
test("var x = [1, 2]; var y = [0, , ...x]", "var y = [0, , 1, 2]");
|
||||
test("var x = [1, 2]; var y = [0, ...x, 3]", "var y = [0, 1, 2, 3]");
|
||||
test("var x=[30,40];var y = [10,...[],20,...x,50];", "var y = [10,20,30,40,50]");
|
||||
test_same("var y = [0, ...[1, , , 3]]");
|
||||
test("var y = [...[1, , ,], ...[, 2], , 2];", "var y = [...[1, , , ], void 0, 2, , 2];");
|
||||
}
|
||||
Reference in new issue
Block a user