diff --git a/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs b/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs index 569621a39..c9493729c 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs @@ -17,20 +17,28 @@ pub enum ValueType { } impl ValueType { + pub fn is_undefined(self) -> bool { + self == Self::Undefined + } + + pub fn is_null(self) -> bool { + self == Self::Null + } + pub fn is_string(self) -> bool { - matches!(self, Self::String) + self == Self::String } pub fn is_number(self) -> bool { - matches!(self, Self::Number) + self == Self::Number } pub fn is_bigint(self) -> bool { - matches!(self, Self::BigInt) + self == Self::BigInt } pub fn is_boolean(self) -> bool { - matches!(self, Self::Boolean) + self == Self::Boolean } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 58332ac0e..f79336b4b 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1,6 +1,6 @@ use oxc_ast::ast::*; use oxc_ecmascript::{ - constant_evaluation::{ConstantEvaluation, ValueType}, + constant_evaluation::{ConstantEvaluation, ConstantValue, ValueType}, side_effects::MayHaveSideEffects, }; use oxc_span::{GetSpan, SPAN}; @@ -53,6 +53,7 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants { } // TODO: return tryFoldGetProp(subtree); Expression::LogicalExpression(e) => Self::try_fold_logical_expression(e, ctx), + Expression::ChainExpression(e) => Self::try_fold_optional_chain(e, ctx), // TODO: tryFoldGetElem // TODO: tryFoldAssign _ => None, @@ -106,6 +107,20 @@ impl<'a, 'b> PeepholeFoldConstants { } } + fn try_fold_optional_chain( + chain_expr: &mut ChainExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + let member_expr = chain_expr.expression.as_member_expression()?; + if !member_expr.optional() { + return None; + } + let object = member_expr.object(); + let ty = ValueType::from(object); + (ty.is_null() || ty.is_undefined()) + .then(|| ctx.value_to_expr(chain_expr.span, ConstantValue::Undefined)) + } + /// Try to fold a AND / OR node. /// /// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587) @@ -1149,6 +1164,24 @@ mod test { test_same("void x()"); } + #[test] + fn test_fold_opt_chain() { + // can't fold when optional part may execute + test_same("a = x?.y"); + test_same("a = x?.()"); + + // fold args of optional call + test("x = foo() ?. (true && bar())", "x = foo() ?.(bar())"); + test("a() ?. (1 ?? b())", "a() ?. (1)"); + + // test("({a})?.a.b.c.d()?.x.y.z", "a.b.c.d()?.x.y.z"); + + test("x = undefined?.y", "x = void 0"); + test("x = null?.y", "x = void 0"); + test("x = undefined?.[foo]", "x = void 0"); + test("x = null?.[foo]", "x = void 0"); + } + #[test] fn test_fold_bitwise_op() { test("x = 1 & 1", "x = 1");