diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/resolve_names.rs b/crates/oxc_angular_compiler/src/pipeline/phases/resolve_names.rs index 70a8feb64..3722d461d 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/resolve_names.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/resolve_names.rs @@ -980,6 +980,18 @@ fn resolve_expression<'a>( ); } + // Parenthesized expression - resolve the inner expression + IrExpression::Parenthesized(paren) => { + resolve_expression( + paren.expr.as_mut(), + scope, + root_xref, + saved_view, + allocator, + expressions, + ); + } + // Other expression types don't need resolution _ => {} } diff --git a/crates/oxc_angular_compiler/tests/integration_test.rs b/crates/oxc_angular_compiler/tests/integration_test.rs index 88d35a968..690b98a35 100644 --- a/crates/oxc_angular_compiler/tests/integration_test.rs +++ b/crates/oxc_angular_compiler/tests/integration_test.rs @@ -3650,3 +3650,36 @@ export class TestComponent { result.code ); } + +#[test] +fn test_nested_if_listener_ctx_reference() { + // Test: nested @if where a listener in the inner @if accesses component properties. + // The listener should use nextContext() to get the component context, + // not bare `ctx` which would be the inner embedded view's context. + let js = compile_template_to_js( + r#"@if (show) { + @if (active) { + + } +}"#, + "TestComponent", + ); + insta::assert_snapshot!("nested_if_listener_ctx_reference", js); +} + +#[test] +fn test_nested_if_alias_listener_ctx_reference() { + // Test: @if with alias, nested @if where listener accesses both + // the alias from the outer @if and a method from the component. + // All context references inside the listener should use named variables (ctx_rN), + // not bare `ctx`. + let js = compile_template_to_js( + r#"@if (getItem(); as item) { + @if (item.active) { + + } +}"#, + "TestComponent", + ); + insta::assert_snapshot!("nested_if_alias_listener_ctx_reference", js); +} diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__nested_if_alias_listener_ctx_reference.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__nested_if_alias_listener_ctx_reference.snap new file mode 100644 index 000000000..908f8c057 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__nested_if_alias_listener_ctx_reference.snap @@ -0,0 +1,38 @@ +--- +source: crates/oxc_angular_compiler/tests/integration_test.rs +expression: js +--- +function TestComponent_Conditional_0_Conditional_1_Template(rf,ctx) { + if ((rf & 1)) { + const _r1 = i0.ɵɵgetCurrentView(); + i0.ɵɵtext(0,"\n "); + i0.ɵɵelementStart(1,"button",0); + i0.ɵɵlistener("click",function TestComponent_Conditional_0_Conditional_1_Template_button_click_1_listener() { + i0.ɵɵrestoreView(_r1); + const item_r2 = i0.ɵɵnextContext(); + const ctx_r2 = i0.ɵɵnextContext(); + return i0.ɵɵresetView(ctx_r2.makePrivate(!(item_r2.private && !item_r2.shareWithTeam))); + }); + i0.ɵɵtext(2,"Toggle"); + i0.ɵɵelementEnd(); + i0.ɵɵtext(3,"\n "); + } +} +function TestComponent_Conditional_0_Template(rf,ctx) { + if ((rf & 1)) { + i0.ɵɵtext(0,"\n "); + i0.ɵɵconditionalCreate(1,TestComponent_Conditional_0_Conditional_1_Template,4, + 0); + } + if ((rf & 2)) { + i0.ɵɵadvance(); + i0.ɵɵconditional((ctx.active? 1: -1)); + } +} +function TestComponent_Template(rf,ctx) { + if ((rf & 1)) { i0.ɵɵconditionalCreate(0,TestComponent_Conditional_0_Template,2,1); } + if ((rf & 2)) { + let tmp_0_0; + i0.ɵɵconditional(((tmp_0_0 = ctx.getItem())? 0: -1),tmp_0_0); + } +} diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__nested_if_listener_ctx_reference.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__nested_if_listener_ctx_reference.snap new file mode 100644 index 000000000..3094fbe89 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__nested_if_listener_ctx_reference.snap @@ -0,0 +1,35 @@ +--- +source: crates/oxc_angular_compiler/tests/integration_test.rs +expression: js +--- +function TestComponent_Conditional_0_Conditional_1_Template(rf,ctx) { + if ((rf & 1)) { + const _r1 = i0.ɵɵgetCurrentView(); + i0.ɵɵtext(0,"\n "); + i0.ɵɵelementStart(1,"button",0); + i0.ɵɵlistener("click",function TestComponent_Conditional_0_Conditional_1_Template_button_click_1_listener() { + i0.ɵɵrestoreView(_r1); + const ctx_r1 = i0.ɵɵnextContext(2); + return i0.ɵɵresetView(ctx_r1.handleClick()); + }); + i0.ɵɵtext(2,"Click"); + i0.ɵɵelementEnd(); + i0.ɵɵtext(3,"\n "); + } +} +function TestComponent_Conditional_0_Template(rf,ctx) { + if ((rf & 1)) { + i0.ɵɵtext(0,"\n "); + i0.ɵɵconditionalCreate(1,TestComponent_Conditional_0_Conditional_1_Template,4, + 0); + } + if ((rf & 2)) { + const ctx_r1 = i0.ɵɵnextContext(); + i0.ɵɵadvance(); + i0.ɵɵconditional((ctx_r1.active? 1: -1)); + } +} +function TestComponent_Template(rf,ctx) { + if ((rf & 1)) { i0.ɵɵconditionalCreate(0,TestComponent_Conditional_0_Template,2,1); } + if ((rf & 2)) { i0.ɵɵconditional((ctx.show? 0: -1)); } +}