Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ impl MayHaveSideEffects for Expression<'_> {
| Expression::ArrowFunctionExpression(_)
| Expression::FunctionExpression(_)
| Expression::Super(_) => false,
Expression::TemplateLiteral(template) => {
template.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
Expression::TemplateLiteral(e) => e.may_have_side_effects(is_global_reference),
Expression::UnaryExpression(e) => e.may_have_side_effects(is_global_reference),
Expression::LogicalExpression(e) => e.may_have_side_effects(is_global_reference),
Expression::ParenthesizedExpression(e) => {
Expand Down Expand Up @@ -84,6 +82,17 @@ impl MayHaveSideEffects for IdentifierReference<'_> {
}
}

impl MayHaveSideEffects for TemplateLiteral<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
self.expressions.iter().any(|e| {
// ToString is called for each expression.
// If the expression is a Symbol or ToPrimitive returns a Symbol, an error is thrown.
// ToPrimitive returns the value as-is for non-Object values, so we can use it instead of ToString here.
e.to_primitive(is_global_reference).is_symbol() != Some(false)
})
}
}

impl MayHaveSideEffects for UnaryExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self.operator {
Expand Down Expand Up @@ -294,9 +303,7 @@ impl MayHaveSideEffects for ArrayExpressionElement<'_> {
ArrayExpressionElement::SpreadElement(e) => match &e.argument {
Expression::ArrayExpression(arr) => arr.may_have_side_effects(is_global_reference),
Expression::StringLiteral(_) => false,
Expression::TemplateLiteral(t) => {
t.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
Expression::TemplateLiteral(t) => t.may_have_side_effects(is_global_reference),
_ => true,
},
match_expression!(ArrayExpressionElement) => {
Expand All @@ -314,9 +321,7 @@ impl MayHaveSideEffects for ObjectPropertyKind<'_> {
ObjectPropertyKind::SpreadProperty(e) => match &e.argument {
Expression::ArrayExpression(arr) => arr.may_have_side_effects(is_global_reference),
Expression::StringLiteral(_) => false,
Expression::TemplateLiteral(t) => {
t.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
Expression::TemplateLiteral(t) => t.may_have_side_effects(is_global_reference),
_ => true,
},
}
Expand Down Expand Up @@ -485,9 +490,7 @@ impl MayHaveSideEffects for Argument<'_> {
Argument::SpreadElement(e) => match &e.argument {
Expression::ArrayExpression(arr) => arr.may_have_side_effects(is_global_reference),
Expression::StringLiteral(_) => false,
Expression::TemplateLiteral(t) => {
t.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
Expression::TemplateLiteral(t) => t.may_have_side_effects(is_global_reference),
_ => true,
},
match_expression!(Argument) => {
Expand Down
40 changes: 34 additions & 6 deletions crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ fn closure_compiler_tests() {
test("a ?? b", false);
// test("'1' + navigator.userAgent", false);
test("`template`", false);
test("`template${name}`", false);
test("`${name}template`", false);
test("`template${name}`", true);
test("`${name}template`", true);
test("`${naming()}template`", true);
test("templateFunction`template`", true);
test("st = `${name}template`", true);
Expand Down Expand Up @@ -143,7 +143,7 @@ fn closure_compiler_tests() {
test("[...[i++]]", true);
test("[...'string']", false);
test("[...`templatelit`]", false);
test("[...`templatelit ${safe}`]", false);
test("[...`templatelit ${safe}`]", true);
test("[...`templatelit ${unsafe()}`]", true);
test("[...f()]", true);
test("[...5]", true);
Expand All @@ -156,7 +156,7 @@ fn closure_compiler_tests() {
test("Math.sin(...[i++])", true);
// test("Math.sin(...'string')", false);
// test("Math.sin(...`templatelit`)", false);
// test("Math.sin(...`templatelit ${safe}`)", false);
// test("Math.sin(...`templatelit ${safe}`)", true);
test("Math.sin(...`templatelit ${unsafe()}`)", true);
test("Math.sin(...f())", true);
test("Math.sin(...5)", true);
Expand All @@ -169,7 +169,7 @@ fn closure_compiler_tests() {
test("new Object(...[i++])", true);
// test("new Object(...'string')", false);
// test("new Object(...`templatelit`)", false);
// test("new Object(...`templatelit ${safe}`)", false);
// test("new Object(...`templatelit ${safe}`)", true);
test("new Object(...`templatelit ${unsafe()}`)", true);
test("new Object(...f())", true);
test("new Object(...5)", true);
Expand Down Expand Up @@ -342,6 +342,20 @@ fn test_simple_expressions() {
test("(() => {})", false);
}

#[test]
fn test_template_literal() {
test("``", false);
test("`a`", false);
test("`${1}`", false);
test("`${[]}`", false);
test("`${Symbol()}`", true);
test("`${{ toString() { console.log('sideeffect') } }}`", true);
test("`${{ valueOf() { console.log('sideeffect') } }}`", true);
test("`${{ [s]() { console.log('sideeffect') } }}`", true); // assuming s is Symbol.toPrimitive
test("`${a}`", true); // a maybe a symbol
test("`${a()}`", true);
}

#[test]
fn test_unary_expressions() {
test("delete 'foo'", true);
Expand Down Expand Up @@ -553,6 +567,8 @@ fn test_object_expression() {
test("({...[...a]})", true);
test("({...'foo'})", false);
test("({...`foo`})", false);
test("({...`foo${1}`})", false);
test("({...`foo${foo}`})", true);
test("({...`foo${foo()}`})", true);
test("({...foo()})", true);
}
Expand All @@ -568,6 +584,8 @@ fn test_array_expression() {
test("[...[...a]]", true);
test("[...'foo']", false);
test("[...`foo`]", false);
test("[...`foo${1}`]", false);
test("[...`foo${foo}`]", true);
test("[...`foo${foo()}`]", true);
test("[...foo()]", true);
}
Expand Down Expand Up @@ -628,6 +646,10 @@ fn test_call_like_expressions() {
test("/* #__PURE__ */ foo(...[1])", false);
test("/* #__PURE__ */ foo(...[bar()])", true);
test("/* #__PURE__ */ foo(...bar)", true);
test("/* #__PURE__ */ foo(...`foo`)", false);
test("/* #__PURE__ */ foo(...`${1}`)", false);
test("/* #__PURE__ */ foo(...`${bar}`)", true);
test("/* #__PURE__ */ foo(...`${bar()}`)", true);
test("/* #__PURE__ */ (() => { foo() })()", false);

test("new Foo()", true);
Expand All @@ -638,6 +660,10 @@ fn test_call_like_expressions() {
test("/* #__PURE__ */ new Foo(...[1])", false);
test("/* #__PURE__ */ new Foo(...[bar()])", true);
test("/* #__PURE__ */ new Foo(...bar)", true);
test("/* #__PURE__ */ new Foo(...`foo`)", false);
test("/* #__PURE__ */ new Foo(...`${1}`)", false);
test("/* #__PURE__ */ new Foo(...`${bar}`)", true);
test("/* #__PURE__ */ new Foo(...`${bar()}`)", true);
test("/* #__PURE__ */ new class { constructor() { foo() } }()", false);
}

Expand All @@ -658,7 +684,9 @@ fn test_object_with_to_primitive_related_properties_overridden() {
test("+{ ...[] }", false);
test("+{ ...'foo' }", false);
test("+{ ...`foo` }", false);
test("+{ ...`foo${foo}` }", false);
test("+{ ...`foo${1}` }", false);
test("+{ ...`foo${foo}` }", true);
test("+{ ...`foo${foo()}` }", true);
test("+{ ...{ toString() { return Symbol() } } }", true);
test("+{ ...{ valueOf() { return Symbol() } } }", true);
test("+{ ...{ [Symbol.toPrimitive]() { return Symbol() } } }", true);
Expand Down
Loading