@@ -99,6 +99,34 @@ pub struct TransformOptions {
9999 pub collect_comment_nodes : bool ,
100100}
101101
102+ /// Inserts or updates a var entry in an ordered Vec, preserving first-insertion order.
103+ /// This matches JS object semantics where reassigning an existing key keeps its position.
104+ fn ordered_insert_var < ' a > (
105+ vec : & mut Vec < ' a , ( Atom < ' a > , R3BoundText < ' a > ) > ,
106+ key : Atom < ' a > ,
107+ value : R3BoundText < ' a > ,
108+ ) {
109+ if let Some ( existing) = vec. iter_mut ( ) . find ( |( k, _) | * k == key) {
110+ existing. 1 = value;
111+ } else {
112+ vec. push ( ( key, value) ) ;
113+ }
114+ }
115+
116+ /// Inserts or updates a placeholder entry in an ordered Vec, preserving first-insertion order.
117+ /// This matches JS object semantics where reassigning an existing key keeps its position.
118+ fn ordered_insert_placeholder < ' a > (
119+ vec : & mut Vec < ' a , ( Atom < ' a > , R3IcuPlaceholder < ' a > ) > ,
120+ key : Atom < ' a > ,
121+ value : R3IcuPlaceholder < ' a > ,
122+ ) {
123+ if let Some ( existing) = vec. iter_mut ( ) . find ( |( k, _) | * k == key) {
124+ existing. 1 = value;
125+ } else {
126+ vec. push ( ( key, value) ) ;
127+ }
128+ }
129+
102130/// Transforms HTML AST to R3 AST.
103131pub struct HtmlToR3Transform < ' a > {
104132 allocator : & ' a Allocator ,
@@ -1250,7 +1278,7 @@ impl<'a> HtmlToR3Transform<'a> {
12501278 } ;
12511279
12521280 // Create variable for the switch value (using VAR_* placeholder name)
1253- let mut vars = HashMap :: new_in ( self . allocator ) ;
1281+ let mut vars = Vec :: new_in ( self . allocator ) ;
12541282 let switch_value_str = expansion. switch_value . as_str ( ) ;
12551283 let switch_value_span = expansion. switch_value_span ;
12561284
@@ -1262,7 +1290,7 @@ impl<'a> HtmlToR3Transform<'a> {
12621290 // This matches Angular's visitExpansion behavior where nested ICUs are visited first,
12631291 // and their VAR_* placeholders are added before the outer ICU's VAR_*.
12641292 // Ported from Angular's i18n_parser.ts:137-159
1265- let mut placeholders = HashMap :: new_in ( self . allocator ) ;
1293+ let mut placeholders = Vec :: new_in ( self . allocator ) ;
12661294 for case in expansion. cases . iter ( ) {
12671295 self . extract_placeholders_from_nodes ( & case. expansion , & mut placeholders, & mut vars) ;
12681296 }
@@ -1271,7 +1299,8 @@ impl<'a> HtmlToR3Transform<'a> {
12711299 // This ensures the correct order: nested ICU vars first, then outer ICU var.
12721300 // Use the unique VAR_* placeholder name as the key, matching Angular's behavior.
12731301 // The expression_placeholder was already generated above with getUniquePlaceholder.
1274- vars. insert (
1302+ ordered_insert_var (
1303+ & mut vars,
12751304 expression_placeholder. clone ( ) ,
12761305 R3BoundText { value : parse_result. ast , source_span : switch_value_span, i18n : None } ,
12771306 ) ;
@@ -1290,8 +1319,8 @@ impl<'a> HtmlToR3Transform<'a> {
12901319 fn extract_placeholders_from_nodes (
12911320 & mut self ,
12921321 nodes : & [ HtmlNode < ' a > ] ,
1293- placeholders : & mut HashMap < ' a , Atom < ' a > , R3IcuPlaceholder < ' a > > ,
1294- vars : & mut HashMap < ' a , Atom < ' a > , R3BoundText < ' a > > ,
1322+ placeholders : & mut Vec < ' a , ( Atom < ' a > , R3IcuPlaceholder < ' a > ) > ,
1323+ vars : & mut Vec < ' a , ( Atom < ' a > , R3BoundText < ' a > ) > ,
12951324 ) {
12961325 for node in nodes {
12971326 match node {
@@ -1335,10 +1364,11 @@ impl<'a> HtmlToR3Transform<'a> {
13351364 // Use the unique VAR_* placeholder name as the key, not the raw switch value.
13361365 // This is critical: when multiple nested ICUs have the same switch value
13371366 // (e.g., same pipe expression), they MUST have separate entries in the vars
1338- // HashMap . Angular uses unique placeholder names (VAR_SELECT, VAR_SELECT_1,
1367+ // collection . Angular uses unique placeholder names (VAR_SELECT, VAR_SELECT_1,
13391368 // VAR_SELECT_2) to ensure each nested ICU creates its own TextOp with its
13401369 // own pipe slot allocation.
1341- vars. insert (
1370+ ordered_insert_var (
1371+ vars,
13421372 var_placeholder_name,
13431373 R3BoundText {
13441374 value : parse_result. ast ,
@@ -1357,7 +1387,7 @@ impl<'a> HtmlToR3Transform<'a> {
13571387 & mut self ,
13581388 text : & str ,
13591389 base_span : Span ,
1360- placeholders : & mut HashMap < ' a , Atom < ' a > , R3IcuPlaceholder < ' a > > ,
1390+ placeholders : & mut Vec < ' a , ( Atom < ' a > , R3IcuPlaceholder < ' a > ) > ,
13611391 ) {
13621392 // Use default Angular interpolation markers
13631393 let start_marker = "{{" ;
@@ -1395,7 +1425,7 @@ impl<'a> HtmlToR3Transform<'a> {
13951425 source_span : interp_span,
13961426 i18n : None ,
13971427 } ;
1398- placeholders . insert ( placeholder_key, R3IcuPlaceholder :: BoundText ( bound_text) ) ;
1428+ ordered_insert_placeholder ( placeholders , placeholder_key, R3IcuPlaceholder :: BoundText ( bound_text) ) ;
13991429
14001430 pos = abs_end;
14011431 } else {
0 commit comments