From 7241177ff0dbddba474af7af7537eefb6c528223 Mon Sep 17 00:00:00 2001 From: Benjamin Dobler Date: Mon, 23 Mar 2026 22:08:35 +0100 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20preserve=20trailing=20empty=20suffix?= =?UTF-8?q?=20in=20=C9=B5=C9=B5textInterpolateN=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ɵɵtextInterpolate1/2/...N always require all positional string args including the trailing suffix. When the suffix was an empty string, it was incorrectly dropped, causing `undefined` to be concatenated at runtime (e.g. `#1undefined` instead of `#1`). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../oxc_angular_compiler/src/pipeline/phases/reify/mod.rs | 6 ++++-- crates/oxc_angular_compiler/tests/integration_test.rs | 6 +++--- .../integration_test__for_listener_with_index.snap | 2 +- .../integration_test__for_with_context_variables.snap | 2 +- .../integration_test__multiple_interpolations.snap | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs b/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs index 7cb143a9e..6c50a7e4e 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs @@ -874,13 +874,15 @@ fn reify_update_op<'a>( } } UpdateOp::InterpolateText(interp) => { - // Handle multiple interpolations like "{{a}} and {{b}}" + // Handle multiple interpolations like "{{a}} and {{b}}" or "#{{a}}" + // has_extra_args must be true to preserve trailing empty strings — + // ɵɵtextInterpolateN always requires all positional string args. let (args, expr_count) = reify_interpolation( allocator, &interp.interpolation, expressions, root_xref, - false, + true, ); Some(create_text_interpolate_stmt_with_args(allocator, args, expr_count)) } diff --git a/crates/oxc_angular_compiler/tests/integration_test.rs b/crates/oxc_angular_compiler/tests/integration_test.rs index 490d5c8e8..1ebc200c1 100644 --- a/crates/oxc_angular_compiler/tests/integration_test.rs +++ b/crates/oxc_angular_compiler/tests/integration_test.rs @@ -171,7 +171,7 @@ fn test_html_entity_between_interpolations() { // Should produce: textInterpolate2("", ctx.a, "×", ctx.b) // Note: × (multiplication sign) = U+00D7, emitted as raw UTF-8 assert!( - js.contains("textInterpolate2(\"\",ctx.a,\"\u{00D7}\",ctx.b)"), + js.contains("textInterpolate2(\"\",ctx.a,\"\u{00D7}\",ctx.b,\"\")"), "Expected textInterpolate2 with raw times character. Got:\n{js}" ); } @@ -183,7 +183,7 @@ fn test_html_entity_at_start_of_interpolation() { // Should produce: textInterpolate1("×", ctx.a) // Note: × (multiplication sign) = U+00D7, emitted as raw UTF-8 assert!( - js.contains("textInterpolate1(\"\u{00D7}\",ctx.a)") + js.contains("textInterpolate1(\"\u{00D7}\",ctx.a,\"\")") || js.contains("textInterpolate(\"\u{00D7}\",ctx.a)"), "Expected textInterpolate with raw times character at start. Got:\n{js}" ); @@ -197,7 +197,7 @@ fn test_multiple_html_entities_between_interpolations() { // Should produce: textInterpolate2("", ctx.a, "\u{00A0}×\u{00A0}", ctx.b) // Note:   = U+00A0, × = U+00D7, both emitted as raw UTF-8 assert!( - js.contains("textInterpolate2(\"\",ctx.a,\"\u{00A0}\u{00D7}\u{00A0}\",ctx.b)"), + js.contains("textInterpolate2(\"\",ctx.a,\"\u{00A0}\u{00D7}\u{00A0}\",ctx.b,\"\")"), "Expected textInterpolate2 with raw Unicode entities. Got:\n{js}" ); } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_listener_with_index.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_listener_with_index.snap index 82fd90a24..dadd063a8 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_listener_with_index.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_listener_with_index.snap @@ -20,7 +20,7 @@ function TestComponent_For_1_Template(rf,ctx) { if ((rf & 2)) { const ɵ$index_1_r2 = ctx.$index; i0.ɵɵadvance(2); - i0.ɵɵtextInterpolate1("Remove #",ɵ$index_1_r2); + i0.ɵɵtextInterpolate1("Remove #",ɵ$index_1_r2,""); } } function TestComponent_Template(rf,ctx) { diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_with_context_variables.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_with_context_variables.snap index fde2cd8ee..c61ea9413 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_with_context_variables.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__for_with_context_variables.snap @@ -18,7 +18,7 @@ function TestComponent_For_1_Template(rf,ctx) { i0.ɵɵadvance(); i0.ɵɵclassProp("first",(ɵ$index_1_r2 === 0))("last",(ɵ$index_1_r2 === (ɵ$count_1_r3 - 1))); i0.ɵɵadvance(); - i0.ɵɵtextInterpolate2("",ɵ$index_1_r2,": ",item_r1.name); + i0.ɵɵtextInterpolate2("",ɵ$index_1_r2,": ",item_r1.name,""); } } function TestComponent_Template(rf,ctx) { diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__multiple_interpolations.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__multiple_interpolations.snap index 6cb634764..d82f3f63b 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__multiple_interpolations.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__multiple_interpolations.snap @@ -10,6 +10,6 @@ function TestComponent_Template(rf,ctx) { } if ((rf & 2)) { i0.ɵɵadvance(); - i0.ɵɵtextInterpolate2("",ctx.first," and ",ctx.second); + i0.ɵɵtextInterpolate2("",ctx.first," and ",ctx.second,""); } } From 73157f5c7a2061ee83ab93cedbec8672c5d87ab8 Mon Sep 17 00:00:00 2001 From: Benjamin Dobler Date: Tue, 24 Mar 2026 07:01:34 +0100 Subject: [PATCH 2/2] style: apply cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/pipeline/phases/reify/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs b/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs index 6c50a7e4e..d448662d7 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/reify/mod.rs @@ -877,13 +877,8 @@ fn reify_update_op<'a>( // Handle multiple interpolations like "{{a}} and {{b}}" or "#{{a}}" // has_extra_args must be true to preserve trailing empty strings — // ɵɵtextInterpolateN always requires all positional string args. - let (args, expr_count) = reify_interpolation( - allocator, - &interp.interpolation, - expressions, - root_xref, - true, - ); + let (args, expr_count) = + reify_interpolation(allocator, &interp.interpolation, expressions, root_xref, true); Some(create_text_interpolate_stmt_with_args(allocator, args, expr_count)) } UpdateOp::Binding(binding) => {