diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/expand_safe_reads.rs b/crates/oxc_angular_compiler/src/pipeline/phases/expand_safe_reads.rs index 9d103add1..66fadd186 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/expand_safe_reads.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/expand_safe_reads.rs @@ -174,6 +174,20 @@ fn needs_temporary_in_safe_access(expr: &IrExpression<'_>) -> bool { needs_temporary_in_safe_access(&keyed.receiver) || needs_temporary_in_safe_access(&keyed.key) } + // Binary operators need to check both operands + IrExpression::Binary(bin) => { + needs_temporary_in_safe_access(&bin.lhs) || needs_temporary_in_safe_access(&bin.rhs) + } + // Ternary needs to check all branches + IrExpression::Ternary(ternary) => { + needs_temporary_in_safe_access(&ternary.condition) + || needs_temporary_in_safe_access(&ternary.true_expr) + || needs_temporary_in_safe_access(&ternary.false_expr) + } + // Not expression needs to check operand + IrExpression::Not(not) => needs_temporary_in_safe_access(¬.expr), + // Unary operator needs to check operand + IrExpression::Unary(unary) => needs_temporary_in_safe_access(&unary.expr), // Check AST expressions for function calls IrExpression::Ast(ast_expr) => needs_temporary_in_ast_expression(ast_expr), // Parenthesized expressions need to check their inner expression diff --git a/crates/oxc_angular_compiler/tests/integration_test.rs b/crates/oxc_angular_compiler/tests/integration_test.rs index 1953cb876..d9401dcd9 100644 --- a/crates/oxc_angular_compiler/tests/integration_test.rs +++ b/crates/oxc_angular_compiler/tests/integration_test.rs @@ -1156,6 +1156,20 @@ fn test_safe_call_in_listener_inside_conditional() { insta::assert_snapshot!("safe_call_in_listener_inside_conditional", js); } +#[test] +fn test_pipe_in_binary_with_safe_property_read() { + // Pattern from EmailCommentComponent: (comment$ | async || comment)?.new_mentioned_thread_count + // When a pipe binding is inside a binary expression that is the receiver of a safe property read, + // the compiler must generate a temporary variable to avoid evaluating the pipe twice. + // TypeScript Angular compiler produces: (tmp = pipeBind(...) || fallback) == null ? null : tmp.prop + // Without the fix, OXC duplicates the pipe call in both the guard and the access expression. + let js = compile_template_to_js( + r#"