diff --git a/crates/swc/tests/exec/issues-8xxx/8119/.swcrc b/crates/swc/tests/exec/issues-8xxx/8119/.swcrc new file mode 100644 index 000000000000..f48b5d4bf0a8 --- /dev/null +++ b/crates/swc/tests/exec/issues-8xxx/8119/.swcrc @@ -0,0 +1,65 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": false + }, + "target": "es2022", + "loose": false, + "minify": { + "compress": { + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true, + "passes": 2 + }, + "mangle": false + } + }, + "module": { + "type": "es6" + }, + "minify": false, + "isModule": true +} \ No newline at end of file diff --git a/crates/swc/tests/exec/issues-8xxx/8119/1/exec.js b/crates/swc/tests/exec/issues-8xxx/8119/1/exec.js new file mode 100644 index 000000000000..812e99cbd788 --- /dev/null +++ b/crates/swc/tests/exec/issues-8xxx/8119/1/exec.js @@ -0,0 +1,24 @@ +const myArr = []; +// function with side effect +function foo(arr) { + arr.push('foo'); + return 'foo'; +} +let a; + +if (Math.random() > 1.00000) { + a = true; +} + +// the function call below should always run +// regardless of whether `a` is `undefined` +let b = foo(myArr); + +// const seems to keep this line here instead of +// moving it behind the logitcal nullish assignment +// const b = foo(myArr); + +a ??= b; + +console.log(a); +console.log(myArr); \ No newline at end of file diff --git a/crates/swc/tests/exec/issues-8xxx/8119/2/exec.js b/crates/swc/tests/exec/issues-8xxx/8119/2/exec.js new file mode 100644 index 000000000000..8351227178b1 --- /dev/null +++ b/crates/swc/tests/exec/issues-8xxx/8119/2/exec.js @@ -0,0 +1,24 @@ +const myArr = []; +// function with side effect +function foo(arr) { + arr.push('foo'); + return 'foo'; +} +let a; + +if (Math.random() > -0.1) { + a = true; +} + +// the function call below should always run +// regardless of whether `a` is `undefined` +let b = foo(myArr); + +// const seems to keep this line here instead of +// moving it behind the logitcal nullish assignment +// const b = foo(myArr); + +a ??= b; + +console.log(a); +console.log(myArr); \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8119/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8119/input/.swcrc new file mode 100644 index 000000000000..f48b5d4bf0a8 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8119/input/.swcrc @@ -0,0 +1,65 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": false + }, + "target": "es2022", + "loose": false, + "minify": { + "compress": { + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true, + "passes": 2 + }, + "mangle": false + } + }, + "module": { + "type": "es6" + }, + "minify": false, + "isModule": true +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8119/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8119/input/1.js new file mode 100644 index 000000000000..b7d762a3c371 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8119/input/1.js @@ -0,0 +1,24 @@ +const myArr = []; +// function with side effect +function foo(arr) { + arr.push('foo'); + return 'foo'; +} +let a; + +if (Math.random() > 0.5) { + a = true; +} + +// the function call below should always run +// regardless of whether `a` is `undefined` +let b = foo(myArr); + +// const seems to keep this line here instead of +// moving it behind the logitcal nullish assignment +// const b = foo(myArr); + +a ??= b; + +console.log(a); +console.log(myArr); \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8119/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8119/output/1.js new file mode 100644 index 000000000000..e452f1440480 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8119/output/1.js @@ -0,0 +1,10 @@ +let a; +const myArr = []; +Math.random() > 0.5 && (a = !0); +// the function call below should always run +// regardless of whether `a` is `undefined` +let b = (myArr.push('foo'), 'foo'); +console.log(// const seems to keep this line here instead of +// moving it behind the logitcal nullish assignment +// const b = foo(myArr); +a ??= b), console.log(myArr); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs index 4f7a6e2f1c25..771d4a59c015 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs @@ -2489,6 +2489,10 @@ impl Optimizer<'_> { /// 1, arr[i]` // fn should_not_check_rhs_of_assign(&self, a: &Mergable, b: &mut AssignExpr) -> Result { + if b.op.may_short_circuit() { + return Ok(true); + } + if let Some(a_id) = a.id() { match a { Mergable::Expr(Expr::Assign(AssignExpr { op: op!("="), .. })) => {} diff --git a/crates/swc_ecma_minifier/tests/exec.rs b/crates/swc_ecma_minifier/tests/exec.rs index a02d73b4c2b8..a29b30015970 100644 --- a/crates/swc_ecma_minifier/tests/exec.rs +++ b/crates/swc_ecma_minifier/tests/exec.rs @@ -10947,3 +10947,167 @@ fn issue_7274() { "#, ); } + +#[test] +fn issue_8119_1() { + run_exec_test( + r#" + const myArr = []; + // function with side effect + function foo(arr) { + arr.push('foo'); + return 'foo'; + } + let a; + + if (Math.random() > 1.1) { + a = true; + } + + // the function call below should always run + // regardless of whether `a` is `undefined` + let b = foo(myArr); + + // const seems to keep this line here instead of + // moving it behind the logitcal nullish assignment + // const b = foo(myArr); + + a ??= b; + + console.log(a); + console.log(myArr); + "#, + r#" + { + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true, + "passes": 2 + } + "#, + false, + ); +} + +#[test] +fn issue_8119_2() { + run_exec_test( + r#" + const myArr = []; + // function with side effect + function foo(arr) { + arr.push('foo'); + return 'foo'; + } + let a; + + if (Math.random() > -0.1) { + a = true; + } + + // the function call below should always run + // regardless of whether `a` is `undefined` + let b = foo(myArr); + + // const seems to keep this line here instead of + // moving it behind the logitcal nullish assignment + // const b = foo(myArr); + + a ??= b; + + console.log(a); + console.log(myArr); + "#, + r#" + { + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true, + "passes": 2 + } + "#, + false, + ); +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8119/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/8119/config.json new file mode 100644 index 000000000000..ed751935a870 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/8119/config.json @@ -0,0 +1,47 @@ +{ + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true, + "passes": 2 +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8119/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8119/input.js new file mode 100644 index 000000000000..b7d762a3c371 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/8119/input.js @@ -0,0 +1,24 @@ +const myArr = []; +// function with side effect +function foo(arr) { + arr.push('foo'); + return 'foo'; +} +let a; + +if (Math.random() > 0.5) { + a = true; +} + +// the function call below should always run +// regardless of whether `a` is `undefined` +let b = foo(myArr); + +// const seems to keep this line here instead of +// moving it behind the logitcal nullish assignment +// const b = foo(myArr); + +a ??= b; + +console.log(a); +console.log(myArr); \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8119/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8119/output.js new file mode 100644 index 000000000000..6ab32f78e582 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/8119/output.js @@ -0,0 +1,10 @@ +let a; +const myArr = []; +function foo(arr) { + return arr.push('foo'), 'foo'; +} +Math.random() > 0.5 && (a = !0); +let b = foo(myArr); +a ??= b; +console.log(a); +console.log(myArr); diff --git a/crates/swc_ecma_minifier/tests/passing.txt b/crates/swc_ecma_minifier/tests/passing.txt index f4248cf15af6..88fbcc357ef7 100644 --- a/crates/swc_ecma_minifier/tests/passing.txt +++ b/crates/swc_ecma_minifier/tests/passing.txt @@ -744,6 +744,9 @@ hoist_props/issue_3071_2/input.js hoist_props/issue_3071_2_toplevel/input.js hoist_props/issue_3071_3/input.js hoist_props/single_use/input.js +hoist_props/toplevel_const/input.js +hoist_props/toplevel_let/input.js +hoist_props/toplevel_var/input.js hoist_vars/issue_2295/input.js hoist_vars/regression_toplevel_args/input.js hoist_vars/sequences/input.js diff --git a/crates/swc_ecma_minifier/tests/postponed.txt b/crates/swc_ecma_minifier/tests/postponed.txt index f1e504b6f771..b990fc50abfd 100644 --- a/crates/swc_ecma_minifier/tests/postponed.txt +++ b/crates/swc_ecma_minifier/tests/postponed.txt @@ -40,9 +40,6 @@ hoist_props/name_collision_1/input.js hoist_props/name_collision_2/input.js hoist_props/name_collision_3/input.js hoist_props/new_this/input.js -hoist_props/toplevel_const/input.js -hoist_props/toplevel_let/input.js -hoist_props/toplevel_var/input.js hoist_props/undefined_key/input.js ie8/do_screw_try_catch_undefined/input.js ie8/dont_screw_try_catch/input.js