diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index ee87813dfa52b..ca9760b525e05 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -983,6 +983,21 @@ impl<'a> AssignmentTargetMaybeDefault<'a> { _ => None, } } + + /// Returns mut identifier bound by this assignment target. + pub fn identifier_mut(&mut self) -> Option<&mut IdentifierReference<'a>> { + match self { + AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(id) => Some(id), + Self::AssignmentTargetWithDefault(target) => { + if let AssignmentTarget::AssignmentTargetIdentifier(id) = &mut target.binding { + Some(id) + } else { + None + } + } + _ => None, + } + } } impl Statement<'_> { diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index b1aa1e209d0b4..7003556d311ad 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -50,7 +50,9 @@ impl<'a> oxc_ecmascript::is_global_reference::IsGlobalReference<'a> for Ctx<'a, self.scoping() .get_reference(reference_id) .symbol_id() - .and_then(|symbol_id| self.state.symbol_values.get_constant_value(symbol_id)) + .and_then(|symbol_id| self.state.symbol_values.get_symbol_value(symbol_id)) + .filter(|sv| sv.write_references_count == 0) + .and_then(|sv| sv.initialized_constant.as_ref()) .cloned() } } @@ -164,7 +166,23 @@ impl<'a> Ctx<'a, '_> { false } - pub fn init_value(&mut self, symbol_id: SymbolId, constant: ConstantValue<'a>) { + pub fn init_value(&mut self, symbol_id: SymbolId, constant: Option>) { + let mut exported = false; + if self.scoping.current_scope_id() == self.scoping().root_scope_id() { + for ancestor in self.ancestors() { + if ancestor.is_export_named_declaration() + || ancestor.is_export_all_declaration() + || ancestor.is_export_default_declaration() + { + exported = true; + } + } + } + + let for_statement_init = self.ancestors().nth(1).is_some_and(|ancestor| { + ancestor.is_parent_of_for_statement_init() || ancestor.is_parent_of_for_statement_left() + }); + let mut read_references_count = 0; let mut write_references_count = 0; for r in self.scoping().get_resolved_references(symbol_id) { @@ -177,8 +195,14 @@ impl<'a> Ctx<'a, '_> { } let scope_id = self.scoping.current_scope_id(); - let symbol_value = - SymbolValue { constant, read_references_count, write_references_count, scope_id }; + let symbol_value = SymbolValue { + initialized_constant: constant, + exported, + for_statement_init, + read_references_count, + write_references_count, + scope_id, + }; self.state.symbol_values.init_value(symbol_id, symbol_value); } diff --git a/crates/oxc_minifier/src/peephole/inline.rs b/crates/oxc_minifier/src/peephole/inline.rs index 0f35c9ae555c6..71731546cf4c0 100644 --- a/crates/oxc_minifier/src/peephole/inline.rs +++ b/crates/oxc_minifier/src/peephole/inline.rs @@ -1,5 +1,5 @@ use oxc_ast::ast::*; -use oxc_ecmascript::constant_evaluation::ConstantEvaluation; +use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, ConstantValue}; use oxc_span::GetSpan; use crate::ctx::Ctx; @@ -10,11 +10,12 @@ impl<'a> PeepholeOptimizations { pub fn init_symbol_value(&self, decl: &VariableDeclarator<'a>, ctx: &mut Ctx<'a, '_>) { let BindingPatternKind::BindingIdentifier(ident) = &decl.id.kind else { return }; let Some(symbol_id) = ident.symbol_id.get() else { return }; - // Skip if not `const` variable. - if !ctx.scoping().symbol_flags(symbol_id).is_const_variable() { + // Skip for `var` declarations, due to TDZ problems. + if decl.kind.is_var() { return; } - let Some(value) = decl.init.evaluate_value(ctx) else { return }; + let value = + decl.init.as_ref().map_or(Some(ConstantValue::Undefined), |e| e.evaluate_value(ctx)); ctx.init_value(symbol_id, value); } @@ -33,7 +34,11 @@ impl<'a> PeepholeOptimizations { if symbol_value.write_references_count > 0 { return; } - *expr = ctx.value_to_expr(expr.span(), symbol_value.constant.clone()); + if symbol_value.for_statement_init { + return; + } + let Some(cv) = &symbol_value.initialized_constant else { return }; + *expr = ctx.value_to_expr(expr.span(), cv.clone()); ctx.state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index 44e8964b6d1b1..2c624985fbfc1 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -526,8 +526,9 @@ mod test { // In the following test case, we can't remove the duplicate "alert(x);" lines since each "x" // refers to a different variable. // We only try removing duplicate statements if the AST is normalized and names are unique. - test_same( + test( "if (Math.random() < 0.5) { let x = 3; alert(x); } else { let x = 5; alert(x); }", + "if (Math.random() < 0.5) { let x = 3; alert(3); } else { let x = 5; alert(5); }", ); } diff --git a/crates/oxc_minifier/src/peephole/normalize.rs b/crates/oxc_minifier/src/peephole/normalize.rs index 7e87a3e7a88ed..1a13fe7442749 100644 --- a/crates/oxc_minifier/src/peephole/normalize.rs +++ b/crates/oxc_minifier/src/peephole/normalize.rs @@ -454,7 +454,8 @@ mod test { #[test] fn fold_number_nan() { test("foo(Number.NaN)", "foo(NaN)"); - test_same("let Number; foo(Number.NaN)"); + test_same("var Number; foo(Number.NaN)"); + test_same("let Number; foo((void 0).NaN)"); } #[test] diff --git a/crates/oxc_minifier/src/peephole/remove_unused_variable_declaration.rs b/crates/oxc_minifier/src/peephole/remove_unused_variable_declaration.rs index 109886bbde214..2c3ace496d767 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_variable_declaration.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_variable_declaration.rs @@ -1,3 +1,4 @@ +use oxc_allocator::TakeIn; use oxc_ast::ast::*; use crate::{CompressOptionsUnused, ctx::Ctx}; @@ -88,37 +89,47 @@ impl<'a> PeepholeOptimizations { pub fn remove_unused_assignment_expression( &self, - _e: &mut Expression<'a>, - _ctx: &mut Ctx<'a, '_>, + e: &mut Expression<'a>, + ctx: &mut Ctx<'a, '_>, ) -> bool { - // let Expression::AssignmentExpression(assign_expr) = e else { return false }; - // if matches!( - // ctx.state.options.unused, - // CompressOptionsUnused::Keep | CompressOptionsUnused::KeepAssign - // ) { - // return false; - // } - // let Some(SimpleAssignmentTarget::AssignmentTargetIdentifier(ident)) = - // assign_expr.left.as_simple_assignment_target() - // else { - // return false; - // }; - // if Self::keep_top_level_var_in_script_mode(ctx) { - // return false; - // } - // let Some(reference_id) = ident.reference_id.get() else { return false }; - // let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id() else { - // return false; - // }; - // // Keep error for assigning to `const foo = 1; foo = 2`. - // if ctx.scoping().symbol_flags(symbol_id).is_const_variable() { - // return false; - // } - // if !ctx.scoping().get_resolved_references(symbol_id).all(|r| !r.flags().is_read()) { - // return false; - // } - // *e = assign_expr.right.take_in(ctx.ast); - // state.changed = true; + let Expression::AssignmentExpression(assign_expr) = e else { return false }; + if matches!( + ctx.state.options.unused, + CompressOptionsUnused::Keep | CompressOptionsUnused::KeepAssign + ) { + return false; + } + let Some(SimpleAssignmentTarget::AssignmentTargetIdentifier(ident)) = + assign_expr.left.as_simple_assignment_target() + else { + return false; + }; + if Self::keep_top_level_var_in_script_mode(ctx) { + return false; + } + let Some(reference_id) = ident.reference_id.get() else { return false }; + let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id() else { + return false; + }; + // Keep error for assigning to `const foo = 1; foo = 2`. + if ctx.scoping().symbol_flags(symbol_id).is_const_variable() { + return false; + } + let Some(symbol_value) = ctx.state.symbol_values.get_symbol_value(symbol_id) else { + return false; + }; + // Cannot remove assignment to live bindings: `export let foo; foo = 1;`. + if symbol_value.exported { + return false; + } + if symbol_value.read_references_count > 0 { + return false; + } + if symbol_value.for_statement_init { + return false; + } + *e = assign_expr.right.take_in(ctx.ast); + ctx.state.changed = true; false } @@ -180,23 +191,39 @@ mod test { } #[test] - #[ignore] fn remove_unused_assignment_expression() { let options = CompressOptions::smallest(); - test_options("var x = 1; x = 2;", "", &options); - test_options("var x = 1; x = 2;", "", &options); - test_options("var x = 1; x = foo();", "foo()", &options); - test_same_options("export let foo; foo = 0;", &options); + // Vars are not handled yet due to TDZ. + test_same_options("var x = 1; x = 2;", &options); + test_same_options("var x = 1; x = foo();", &options); + test_same_options("export var foo; foo = 0;", &options); test_same_options("var x = 1; x = 2, foo(x)", &options); test_same_options("function foo() { return t = x(); } foo();", &options); + test_same_options("function foo() { var t; return t = x(); } foo();", &options); + test_same_options("function foo(t) { return t = x(); } foo();", &options); + + test_options("let x = 1; x = 2;", "", &options); + test_options("let x = 1; x = foo();", "foo()", &options); + test_same_options("export let foo; foo = 0;", &options); + test_same_options("let x = 1; x = 2, foo(x)", &options); + test_same_options("function foo() { return t = x(); } foo();", &options); test_options( - "function foo() { var t; return t = x(); } foo();", - "function foo() { return x(); } foo();", + "function foo() { let t; return t = x(); } foo();", + "function foo() { return x() } foo()", &options, ); - test_options( - "function foo(t) { return t = x(); } foo();", - "function foo(t) { return x(); } foo();", + test_same_options("function foo(t) { return t = x(); } foo();", &options); + + test_same_options("for(let i;;) foo(i)", &options); + test_same_options("for(let i in []) foo(i)", &options); + + test_options("var a; ({ a: a } = {})", "var a; ({ a } = {})", &options); + test_options("var a; b = ({ a: a })", "var a; b = ({ a })", &options); + + test_options("let foo = {}; foo = 1", "", &options); + + test_same_options( + "let bracketed = !1; for(;;) bracketed = !bracketed, log(bracketed)", &options, ); @@ -204,9 +231,8 @@ mod test { let source_type = SourceType::cjs(); test_same_options_source_type("var x = 1; x = 2;", source_type, &options); test_same_options_source_type("var x = 1; x = 2, foo(x)", source_type, &options); - test_options_source_type( - "function foo() { var x = 1; x = 2; bar() } foo()", - "function foo() { bar() } foo()", + test_same_options_source_type( + "function foo() { var x = 1; x = 2, bar() } foo()", source_type, &options, ); diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index f16bfc8959a13..097b6e3ade63b 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -40,7 +40,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_assignment_target_property_property( &self, prop: &mut AssignmentTargetPropertyProperty<'a>, - ctx: &mut Ctx<'a, '_>, ) { self.try_compress_property_key(&mut prop.name, &mut prop.computed, ctx); @@ -49,7 +48,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_assignment_target_property( &self, prop: &mut AssignmentTargetProperty<'a>, - ctx: &mut Ctx<'a, '_>, ) { self.try_compress_assignment_target_property(prop, ctx); @@ -58,7 +56,6 @@ impl<'a> PeepholeOptimizations { pub fn try_compress_assignment_target_property( &self, prop: &mut AssignmentTargetProperty<'a>, - ctx: &mut Ctx<'a, '_>, ) { // `a: a` -> `a` @@ -66,16 +63,13 @@ impl<'a> PeepholeOptimizations { prop { let Some(prop_name) = assign_target_prop_prop.name.static_name() else { return }; - let Some(binding_identifier) = assign_target_prop_prop.binding.identifier() else { + let Some(ident) = assign_target_prop_prop.binding.identifier_mut() else { return; }; - if prop_name == binding_identifier.name { + if prop_name == ident.name { *prop = ctx.ast.assignment_target_property_assignment_target_property_identifier( - assign_target_prop_prop.span, - ctx.ast.identifier_reference( - assign_target_prop_prop.span, - binding_identifier.name, - ), + ident.span, + ident.take_in(ctx.ast), None, ); ctx.state.changed = true; @@ -86,7 +80,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_binding_property( &self, prop: &mut BindingProperty<'a>, - ctx: &mut Ctx<'a, '_>, ) { self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); @@ -95,7 +88,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_method_definition( &self, prop: &mut MethodDefinition<'a>, - ctx: &mut Ctx<'a, '_>, ) { let property_key_parent: ClassPropertyKeyParent = prop.into(); @@ -110,7 +102,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_property_definition( &self, prop: &mut PropertyDefinition<'a>, - ctx: &mut Ctx<'a, '_>, ) { let property_key_parent: ClassPropertyKeyParent = prop.into(); @@ -125,7 +116,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_accessor_property( &self, prop: &mut AccessorProperty<'a>, - ctx: &mut Ctx<'a, '_>, ) { let property_key_parent: ClassPropertyKeyParent = prop.into(); @@ -140,7 +130,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_return_statement( &self, stmt: &mut ReturnStatement<'a>, - ctx: &mut Ctx<'a, '_>, ) { self.compress_return_statement(stmt, ctx); @@ -149,7 +138,6 @@ impl<'a> PeepholeOptimizations { pub fn substitute_variable_declaration( &self, decl: &mut VariableDeclaration<'a>, - ctx: &mut Ctx<'a, '_>, ) { for declarator in &mut decl.declarations { @@ -218,7 +206,6 @@ impl<'a> PeepholeOptimizations { fn try_compress_arrow_expression( &self, arrow_expr: &mut ArrowFunctionExpression<'a>, - ctx: &mut Ctx<'a, '_>, ) { if !arrow_expr.expression @@ -603,7 +590,6 @@ impl<'a> PeepholeOptimizations { fn compress_variable_declarator( &self, decl: &mut VariableDeclarator<'a>, - ctx: &mut Ctx<'a, '_>, ) { // Destructuring Pattern has error throwing side effect. @@ -859,7 +845,6 @@ impl<'a> PeepholeOptimizations { fn try_compress_chain_call_expression( &self, chain_expr: &mut ChainExpression<'a>, - ctx: &mut Ctx<'a, '_>, ) { if let ChainElement::CallExpression(call_expr) = &mut chain_expr.expression { @@ -890,7 +875,6 @@ impl<'a> PeepholeOptimizations { &self, key: &mut PropertyKey<'a>, computed: &mut bool, - ctx: &mut Ctx<'a, '_>, ) { if let PropertyKey::NumericLiteral(_) = key { diff --git a/crates/oxc_minifier/src/symbol_value.rs b/crates/oxc_minifier/src/symbol_value.rs index e530a50a6985b..04e367a689330 100644 --- a/crates/oxc_minifier/src/symbol_value.rs +++ b/crates/oxc_minifier/src/symbol_value.rs @@ -5,8 +5,15 @@ use oxc_syntax::{scope::ScopeId, symbol::SymbolId}; #[derive(Debug)] pub struct SymbolValue<'a> { - /// Constant value evaluated from expressions. - pub constant: ConstantValue<'a>, + /// Initialized constant value evaluated from expressions. + /// `None` when the value is not a constant evaluated value. + pub initialized_constant: Option>, + + /// Symbol is exported. + pub exported: bool, + + /// Inside for statement initializer. + pub for_statement_init: bool, pub read_references_count: u32, pub write_references_count: u32, @@ -29,10 +36,6 @@ impl<'a> SymbolValues<'a> { self.values.insert(symbol_id, symbol_value); } - pub fn get_constant_value(&self, symbol_id: SymbolId) -> Option<&ConstantValue<'a>> { - self.values.get(&symbol_id).map(|v| &v.constant) - } - pub fn get_symbol_value(&self, symbol_id: SymbolId) -> Option<&SymbolValue<'a>> { self.values.get(&symbol_id) } diff --git a/crates/oxc_minifier/tests/peephole/collapse_variable_declarations.rs b/crates/oxc_minifier/tests/peephole/collapse_variable_declarations.rs index 71c2831717d4f..457dae2123ae2 100644 --- a/crates/oxc_minifier/tests/peephole/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/tests/peephole/collapse_variable_declarations.rs @@ -105,30 +105,30 @@ mod join_vars { fn test_redeclaration_let_in_function() { test( "function f() { let x = 1; let y = 2; let z = 3; x + y + z; }", - "function f() { let x = 1, y = 2, z = 3; x + y + z; } ", + "function f() { let x = 1, y = 2, z = 3; } ", ); // recognize local scope version of x test( "var x = 1; function f() { let x = 1; let y = 2; x + y; }", - "var x = 1; function f() { let x = 1, y = 2; x + y } ", + "var x = 1; function f() { let x = 1, y = 2; } ", ); // do not redeclare function parameters // incompatible with strict mode - test_same("function f(x) { let y = 3; x = 4, x + y; }"); + test( + "function f(x) { let y = 3; x = 4, x + y; }", + "function f(x) { let y = 3; x = 4, x + 3 }", + ); } #[test] fn test_arrow_function() { - test( - "(() => { let x = 1; let y = 2; x + y; })()", - "(() => { let x = 1, y = 2; x + y; })()", - ); + test("(() => { let x = 1; let y = 2; x + y; })()", "(() => { let x = 1, y = 2; })()"); // do not redeclare function parameters // incompatible with strict mode - test_same("((x) => { x = 4; let y = 2; x + y; })()"); + test("((x) => { x = 4; let y = 2; x + y; })()", "((x) => { x = 4; let y = 2; x + 2; })()"); } #[test] diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index ccb3ea873ff16..30fe7d5b0652f 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -561,23 +561,59 @@ fn js_parser_test() { test("a = b === c ? true : false", "a = b === c;"); test("a = b !== c ? true : false", "a = b !== c;"); test("a ? b(c) : b(d)", "a ? b(c) : b(d);"); - test("let a; a ? b(c) : b(d)", "let a; a ? b(c) : b(d);"); - test("let a, b; a ? b(c) : b(d)", "let a, b;b(a ? c : d);"); - test("let a, b; a ? b(c, 0) : b(d)", "let a, b; a ? b(c, 0) : b(d);"); - test("let a, b; a ? b(c) : b(d, 0)", "let a, b; a ? b(c) : b(d, 0);"); - test("let a, b; a ? b(c, 0) : b(d, 1)", "let a, b; a ? b(c, 0) : b(d, 1);"); - test("let a, b; a ? b(c, 0) : b(d, 0)", "let a, b;b(a ? c : d, 0);"); - test("let a, b; a ? b(...c) : b(d)", "let a, b; a ? b(...c) : b(d);"); - test("let a, b; a ? b(c) : b(...d)", "let a, b; a ? b(c) : b(...d);"); - test("let a, b; a ? b(...c) : b(...d)", "let a, b;b(...a ? c : d);"); - test("let a, b; a ? b(a) : b(c)", "let a, b;b(a || c);"); - test("let a, b; a ? b(c) : b(a)", "let a, b;b(a && c);"); - test("let a, b; a ? b(...a) : b(...c)", "let a, b;b(...a || c);"); - test("let a, b; a ? b(...c) : b(...a)", "let a, b;b(...a && c);"); - test("let a; a.x ? b(c) : b(d)", "let a;a.x ? b(c) : b(d);"); - test("let a, b; a.x ? b(c) : b(d)", "let a, b; a.x ? b(c) : b(d);"); - test("let a, b; a ? b.y(c) : b.y(d)", "let a, b; a ? b.y(c) : b.y(d);"); - test("let a, b; a.x ? b.y(c) : b.y(d)", "let a, b; a.x ? b.y(c) : b.y(d);"); + test("let a = foo(); a ? b(c) : b(d)", "let a = foo(); a ? b(c) : b(d);"); + test("let a = foo(), b = bar(); a ? b(c) : b(d)", "let a = foo(), b = bar();b(a ? c : d);"); + test( + "let a = foo(), b = bar(); a ? b(c, 0) : b(d)", + "let a = foo(), b = bar(); a ? b(c, 0) : b(d);", + ); + test( + "let a = foo(), b = bar(); a ? b(c) : b(d, 0)", + "let a = foo(), b = bar(); a ? b(c) : b(d, 0);", + ); + test( + "let a = foo(), b = bar(); a ? b(c, 0) : b(d, 1)", + "let a = foo(), b = bar(); a ? b(c, 0) : b(d, 1);", + ); + test( + "let a = foo(), b = bar(); a ? b(c, 0) : b(d, 0)", + "let a = foo(), b = bar();b(a ? c : d, 0);", + ); + test( + "let a = foo(), b = bar(); a ? b(...c) : b(d)", + "let a = foo(), b = bar(); a ? b(...c) : b(d);", + ); + test( + "let a = foo(), b = bar(); a ? b(c) : b(...d)", + "let a = foo(), b = bar(); a ? b(c) : b(...d);", + ); + test( + "let a = foo(), b = bar(); a ? b(...c) : b(...d)", + "let a = foo(), b = bar();b(...a ? c : d);", + ); + test("let a = foo(), b = bar(); a ? b(a) : b(c)", "let a = foo(), b = bar();b(a || c);"); + test("let a = foo(), b = bar(); a ? b(c) : b(a)", "let a = foo(), b = bar();b(a && c);"); + test( + "let a = foo(), b = bar(); a ? b(...a) : b(...c)", + "let a = foo(), b = bar();b(...a || c);", + ); + test( + "let a = foo(), b = bar(); a ? b(...c) : b(...a)", + "let a = foo(), b = bar();b(...a && c);", + ); + test("let a = foo(); a.x ? b(c) : b(d)", "let a = foo(); a.x ? b(c) : b(d);"); + test( + "let a = foo(), b = bar(); a.x ? b(c) : b(d)", + "let a = foo(), b = bar(); a.x ? b(c) : b(d);", + ); + test( + "let a = foo(), b = bar(); a ? b.y(c) : b.y(d)", + "let a = foo(), b = bar(); a ? b.y(c) : b.y(d);", + ); + test( + "let a = foo(), b = bar(); a.x ? b.y(c) : b.y(d)", + "let a = foo(), b = bar(); a.x ? b.y(c) : b.y(d);", + ); test("a ? b : c ? b : d", "a || c ? b : d;"); test("a ? b ? c : d : d", "a && b ? c : d;"); test("a ? c : (b, c)", "a || b, c;"); @@ -592,24 +628,24 @@ fn js_parser_test() { test("a ? c : b || c", "a ? c : b || c;"); test("a = b == null ? c : b", "a = b == null ? c : b;"); test("a = b != null ? b : c", "a = b == null ? c : b;"); - test("let b; a = b == null ? c : b", "let b; a = b ?? c;"); - test("let b; a = b != null ? b : c", "let b; a = b ?? c;"); - test("let b; a = b == null ? b : c", "let b; a = b == null ? b : c;"); - test("let b; a = b != null ? c : b", "let b; a = b == null ? b : c;"); - test("let b; a = null == b ? c : b", "let b; a = b ?? c;"); - test("let b; a = null != b ? b : c", "let b; a = b ?? c;"); - test("let b; a = null == b ? b : c", "let b; a = b == null ? b : c;"); - test("let b; a = null != b ? c : b", "let b; a = b == null ? b : c;"); - test("let b; a = b.x == null ? c : b.x", "let b; a = b.x == null ? c : b.x;"); - test("let b; a = b.x != null ? b.x : c", "let b; a = b.x == null ? c : b.x;"); - test("let b; a = null == b.x ? c : b.x", "let b; a = b.x == null ? c : b.x;"); - test("let b; a = null != b.x ? b.x : c", "let b; a = b.x == null ? c : b.x;"); - test("let b; a = b === null ? c : b", "let b; a = b === null ? c : b;"); - test("let b; a = b !== null ? b : c", "let b; a = b === null ? c : b;"); - test("let b; a = null === b ? c : b", "let b; a = b === null ? c : b;"); - test("let b; a = null !== b ? b : c", "let b; a = b === null ? c : b;"); - test("let b; a = null === b || b === undefined ? c : b", "let b; a = b ?? c;"); - test("let b; a = b !== undefined && b !== null ? b : c", "let b; a = b ?? c;"); + test("let b = foo(); a = b == null ? c : b", "let b = foo(); a = b ?? c;"); + test("let b = foo(); a = b != null ? b : c", "let b = foo(); a = b ?? c;"); + test("let b = foo(); a = b == null ? b : c", "let b = foo(); a = b == null ? b : c;"); + test("let b = foo(); a = b != null ? c : b", "let b = foo(); a = b == null ? b : c;"); + test("let b = foo(); a = null == b ? c : b", "let b = foo(); a = b ?? c;"); + test("let b = foo(); a = null != b ? b : c", "let b = foo(); a = b ?? c;"); + test("let b = foo(); a = null == b ? b : c", "let b = foo(); a = b == null ? b : c;"); + test("let b = foo(); a = null != b ? c : b", "let b = foo(); a = b == null ? b : c;"); + test("let b = foo(); a = b.x == null ? c : b.x", "let b = foo(); a = b.x == null ? c : b.x;"); + test("let b = foo(); a = b.x != null ? b.x : c", "let b = foo(); a = b.x == null ? c : b.x;"); + test("let b = foo(); a = null == b.x ? c : b.x", "let b = foo(); a = b.x == null ? c : b.x;"); + test("let b = foo(); a = null != b.x ? b.x : c", "let b = foo(); a = b.x == null ? c : b.x;"); + test("let b = foo(); a = b === null ? c : b", "let b = foo(); a = b === null ? c : b;"); + test("let b = foo(); a = b !== null ? b : c", "let b = foo(); a = b === null ? c : b;"); + test("let b = foo(); a = null === b ? c : b", "let b = foo(); a = b === null ? c : b;"); + test("let b = foo(); a = null !== b ? b : c", "let b = foo(); a = b === null ? c : b;"); + test("let b = foo(); a = null === b || b === undefined ? c : b", "let b = foo(); a = b ?? c;"); + test("let b = foo(); a = b !== undefined && b !== null ? b : c", "let b = foo(); a = b ?? c;"); test("a(b ? 0 : 0)", "a((b, 0));"); test("a(b ? +0 : -0)", "a(b ? 0 : -0);"); test("a(b ? +0 : 0)", "a((b, 0));"); @@ -649,8 +685,8 @@ fn js_parser_test() { test("if (a) if (b) if (c) d", "a && b && c && d;"); test("if (!a) if (!b) if (!c) d", "a || b || c || d;"); test( - "function _() { let a, b, c; return a != null ? a : b != null ? b : c }", - "function _() { let a, b, c;return a ?? b ?? c; }", + "function _() { let a = foo(), b = bar(), c = baz(); return a != null ? a : b != null ? b : c }", + "function _() { let a = foo(), b = bar(), c = baz(); return a ?? b ?? c; }", ); test( "function _() { if (a) return c; if (b) return d; }", @@ -789,96 +825,96 @@ fn js_parser_test() { "function _() { if (a) { x: if (b) break x;} else return c; }", ); test( - "function _() { let a; return a != null ? a.b : undefined }", - "function _ () { let a;return a?.b; }", + "function _() { let a = foo(); return a != null ? a.b : undefined }", + "function _() { let a = foo(); return a?.b; }", ); test( - "function _() { let a; return a != null ? a[b] : undefined }", - "function _ () { let a;return a?.[b]; }", + "function _() { let a = foo(); return a != null ? a[b] : undefined }", + "function _() { let a = foo(); return a?.[b]; }", ); test( - "function _() { let a; return a != null ? a(b) : undefined }", - "function _ () { let a;return a?.(b); }", + "function _() { let a = foo(); return a != null ? a(b) : undefined }", + "function _() { let a = foo(); return a?.(b); }", ); test( - "function _() { let a; return a == null ? undefined : a.b }", - "function _ () { let a;return a?.b; }", + "function _() { let a = foo(); return a == null ? undefined : a.b }", + "function _() { let a = foo(); return a?.b; }", ); test( - "function _() { let a; return a == null ? undefined : a[b] }", - "function _ () { let a;return a?.[b]; }", + "function _() { let a = foo(); return a == null ? undefined : a[b] }", + "function _() { let a = foo(); return a?.[b]; }", ); test( - "function _() { let a; return a == null ? undefined : a(b) }", - "function _ () { let a;return a?.(b); }", + "function _() { let a = foo(); return a == null ? undefined : a(b) }", + "function _() { let a = foo(); return a?.(b); }", ); test( - "function _() { let a; return null != a ? a.b : undefined }", - "function _ () { let a;return a?.b; }", + "function _() { let a = foo(); return null != a ? a.b : undefined }", + "function _() { let a = foo(); return a?.b; }", ); test( - "function _() { let a; return null != a ? a[b] : undefined }", - "function _ () { let a;return a?.[b]; }", + "function _() { let a = foo(); return null != a ? a[b] : undefined }", + "function _() { let a = foo(); return a?.[b]; }", ); test( - "function _() { let a; return null != a ? a(b) : undefined }", - "function _ () { let a;return a?.(b); }", + "function _() { let a = foo(); return null != a ? a(b) : undefined }", + "function _() { let a = foo(); return a?.(b); }", ); test( - "function _() { let a; return null == a ? undefined : a.b }", - "function _ () { let a;return a?.b; }", + "function _() { let a = foo(); return null == a ? undefined : a.b }", + "function _() { let a = foo(); return a?.b; }", ); test( - "function _() { let a; return null == a ? undefined : a[b] }", - "function _ () { let a;return a?.[b]; }", + "function _() { let a = foo(); return null == a ? undefined : a[b] }", + "function _() { let a = foo(); return a?.[b]; }", ); test( - "function _() { let a; return null == a ? undefined : a(b) }", - "function _ () { let a;return a?.(b); }", + "function _() { let a = foo(); return null == a ? undefined : a(b) }", + "function _() { let a = foo(); return a?.(b); }", ); test( "function _() { return a != null ? a.b : undefined }", - "function _ () { return a == null ? void 0 : a.b; }", + "function _() { return a == null ? void 0 : a.b; }", ); test( - "function _() { let a; return a != null ? a.b : null }", - "function _ () { let a;return a == null ? null : a.b; }", + "function _() { let a = foo(); return a != null ? a.b : null }", + "function _() { let a = foo(); return a == null ? null : a.b; }", ); test( - "function _() { let a; return a != null ? b.a : undefined }", - "function _ () { let a;return a == null ? void 0 : b.a; }", + "function _() { let a = foo(); return a != null ? b.a : undefined }", + "function _() { let a = foo(); return a == null ? void 0 : b.a; }", ); test( - "function _() { let a; return a != 0 ? a.b : undefined }", - "function _ () { let a;return a == 0 ? void 0 : a.b; }", + "function _() { let a = foo(); return a != 0 ? a.b : undefined }", + "function _() { let a = foo(); return a == 0 ? void 0 : a.b; }", ); test( - "function _() { let a; return a !== null ? a.b : undefined }", - "function _ () { let a;return a === null ? void 0 : a.b; }", + "function _() { let a = foo(); return a !== null ? a.b : undefined }", + "function _() { let a = foo(); return a === null ? void 0 : a.b; }", ); test( - "function _() { let a; return a != undefined ? a.b : undefined }", - "function _ () { let a;return a?.b; }", + "function _() { let a = foo(); return a != undefined ? a.b : undefined }", + "function _() { let a = foo(); return a?.b; }", ); test( - "function _() { let a; return a != null ? a?.b : undefined }", - "function _ () { let a;return a?.b; }", + "function _() { let a = foo(); return a != null ? a?.b : undefined }", + "function _() { let a = foo(); return a?.b; }", ); test( - "function _() { let a; return a != null ? a.b.c[d](e) : undefined }", - "function _ () { let a;return a?.b.c[d](e); }", + "function _() { let a = foo(); return a != null ? a.b.c[d](e) : undefined }", + "function _() { let a = foo(); return a?.b.c[d](e); }", ); test( - "function _() { let a; return a != null ? a?.b.c[d](e) : undefined }", - "function _ () { let a;return a?.b.c[d](e); }", + "function _() { let a = foo(); return a != null ? a?.b.c[d](e) : undefined }", + "function _() { let a = foo(); return a?.b.c[d](e); }", ); test( - "function _() { let a; return a != null ? a.b.c?.[d](e) : undefined }", - "function _ () { let a;return a?.b.c?.[d](e); }", + "function _() { let a = foo(); return a != null ? a.b.c?.[d](e) : undefined }", + "function _() { let a = foo(); return a?.b.c?.[d](e); }", ); test( - "function _() { let a; return a != null ? a?.b.c?.[d](e) : undefined }", - "function _ () { let a;return a?.b.c?.[d](e); }", + "function _() { let a = foo(); return a != null ? a?.b.c?.[d](e) : undefined }", + "function _() { let a = foo(); return a?.b.c?.[d](e); }", ); }