From abfa8eb8f002c4d9d020b97ab792be18d5632bc6 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 4 Feb 2026 14:21:51 +0800 Subject: [PATCH] fix: i18n property bindings should use Bindings marker, not I18n marker in consts Angular's compiler never produces I18n AttributeMarker (6) in consts arrays. The previous override in attribute_extraction converted i18n property bindings to BindingKind::I18n, creating duplicate consts entries that shifted all subsequent const indices. This fixes 8 mismatched files (~396 numeric diffs). Co-Authored-By: Claude Opus 4.5 --- .../pipeline/phases/attribute_extraction.rs | 24 ++++------ .../tests/integration_test.rs | 47 ++++++++++++------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/attribute_extraction.rs b/crates/oxc_angular_compiler/src/pipeline/phases/attribute_extraction.rs index 4acab4527..79a697958 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/attribute_extraction.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/attribute_extraction.rs @@ -187,22 +187,14 @@ fn process_view_attributes<'a>( continue; } - // Determine the extracted binding kind. - // Ported from Angular's attribute_extraction.ts lines 32-39: - // if (op.i18nMessage !== null && op.templateKind === null) { - // bindingKind = ir.BindingKind.I18n; - // } else if (op.isStructuralTemplateAttribute) { - // bindingKind = ir.BindingKind.Template; - // } else { - // bindingKind = ir.BindingKind.Property; - // } - let binding_kind = if prop_op.i18n_message.is_some() - && prop_op.binding_kind != BindingKind::Template - { - BindingKind::I18n - } else { - prop_op.binding_kind - }; + // Use the binding kind from the property op directly. + // Angular's attribute_extraction.ts has a condition: + // if (op.i18nMessage !== null && op.templateKind === null) + // that sets bindingKind to I18n, but empirically the Angular + // compiler never produces I18n AttributeMarker (6) in consts + // arrays. The templateKind guard (which OXC's PropertyOp lacks) + // prevents it from triggering in practice. + let binding_kind = prop_op.binding_kind; // Properties also generate extracted attributes for directive matching // Note: Property ops are NOT removed - they still need runtime updates diff --git a/crates/oxc_angular_compiler/tests/integration_test.rs b/crates/oxc_angular_compiler/tests/integration_test.rs index 7b80079b7..72324171e 100644 --- a/crates/oxc_angular_compiler/tests/integration_test.rs +++ b/crates/oxc_angular_compiler/tests/integration_test.rs @@ -3743,13 +3743,14 @@ fn test_let_declaration_with_multiple_context_refs_variable_naming() { // Const reference index: i18n property binding extraction // ============================================================================ -/// Tests that property bindings with i18n markers are extracted as BindingKind::I18n -/// in the consts array. Angular's attribute_extraction.ts checks `op.i18nMessage !== null` -/// on Property ops and converts them to BindingKind.I18n. Without this, the const entry -/// would be `[3, "heading"]` (Bindings marker) instead of `[6, "heading"]` (I18n marker), -/// causing const index mismatches. -#[test] -fn test_i18n_property_binding_extracted_as_i18n_kind() { +/// Tests that property bindings with i18n markers are extracted as BindingKind::Property +/// in the consts array. Angular's attribute_extraction.ts has a condition +/// `op.i18nMessage !== null && op.templateKind === null` that would produce I18n kind, +/// but empirically Angular never produces I18n marker (6) in consts arrays across all +/// tested components. The i18n metadata is handled by the i18n pipeline separately. +/// The property binding should use Bindings marker (3) for directive matching. +#[test] +fn test_i18n_property_binding_extracted_as_property_kind() { let allocator = Allocator::default(); let source = r#" import { Component } from '@angular/core'; @@ -3772,20 +3773,26 @@ export class TestComponent { None, ); - // The consts array should contain [6,"heading"] (AttributeMarker.I18n = 6) - // not [3,"heading"] (AttributeMarker.Bindings = 3) + // The consts array should contain [3,"heading"] (AttributeMarker.Bindings = 3) + // Angular never produces [6,"heading"] (AttributeMarker.I18n = 6) in consts arrays. + assert!( + result.code.contains(r#"3,"heading""#), + "Property binding with i18n marker should produce Bindings AttributeMarker (3), not I18n (6). Output:\n{}", + result.code + ); assert!( - result.code.contains(r#"6,"heading""#), - "Property binding with i18n marker should produce I18n AttributeMarker (6), not Bindings (3). Output:\n{}", + !result.code.contains(r#"6,"heading""#), + "Property binding with i18n marker should NOT produce I18n AttributeMarker (6). Output:\n{}", result.code ); } /// Tests that interpolated attributes with i18n markers (e.g., heading="{{ name }}" i18n-heading) -/// are extracted as BindingKind::I18n in the consts array. +/// are extracted as BindingKind::Property (Bindings marker 3), not I18n marker 6. +/// Angular's compiler never produces I18n AttributeMarker (6) in consts arrays. /// This matches the real-world pattern in ClickUp's old-join-team component. #[test] -fn test_i18n_interpolated_attribute_extracted_as_i18n_kind() { +fn test_i18n_interpolated_attribute_extracted_as_property_kind() { let allocator = Allocator::default(); let source = r#" import { Component } from '@angular/core'; @@ -3808,11 +3815,17 @@ export class TestComponent { None, ); - // The consts array should contain [6,"heading"] (AttributeMarker.I18n = 6) - // not [3,"heading"] (AttributeMarker.Bindings = 3) + // The consts array should contain [3,"heading"] (AttributeMarker.Bindings = 3) + // not [6,"heading"] (AttributeMarker.I18n = 6) + // Angular's compiler never produces I18n marker in consts arrays. + assert!( + result.code.contains(r#"3,"heading""#), + "Interpolated attribute with i18n marker should produce Bindings AttributeMarker (3), not I18n (6). Output:\n{}", + result.code + ); assert!( - result.code.contains(r#"6,"heading""#), - "Interpolated attribute with i18n marker should produce I18n AttributeMarker (6), not Bindings (3). Output:\n{}", + !result.code.contains(r#"6,"heading""#), + "Interpolated attribute with i18n marker should NOT produce I18n AttributeMarker (6). Output:\n{}", result.code ); }