diff --git a/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs b/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs index 0edb7327cc4d9..673d43303f976 100644 --- a/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs +++ b/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs @@ -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) => { @@ -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 { @@ -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) => { @@ -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, }, } @@ -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) => { diff --git a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs index 602f4227935d1..5e6cd45c0be79 100644 --- a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs +++ b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs @@ -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); @@ -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); @@ -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); @@ -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); @@ -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); @@ -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); } @@ -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); } @@ -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); @@ -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); } @@ -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);