Skip to content

Commit 1df1697

Browse files
committed
fix(compiler): prevent mutation of children array in RecursiveVisitor
RecursiveVisitor.visitIfBlockBranch was permanently mutating the children array by pushing the expressionAlias into it. This change clones the array before pushing to avoid this side effect. (cherry picked from commit 72a17af)
1 parent 05476ea commit 1df1697

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

packages/compiler/src/render3/r3_ast.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -803,9 +803,8 @@ export class RecursiveVisitor implements Visitor<void> {
803803
visitAll(this, block.branches);
804804
}
805805
visitIfBlockBranch(block: IfBlockBranch): void {
806-
const blockItems = block.children;
807-
block.expressionAlias && blockItems.push(block.expressionAlias);
808-
visitAll(this, blockItems);
806+
visitAll(this, block.children);
807+
block.expressionAlias?.visit(this);
809808
}
810809
visitContent(content: Content): void {
811810
visitAll(this, content.children);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import * as t from '../../src/render3/r3_ast';
10+
import {parseR3 as parse} from './view/util';
11+
12+
describe('RecursiveVisitor', () => {
13+
it('should not mutate IfBlockBranch children when visiting', () => {
14+
// Template with an @if block that has an alias.
15+
// The alias is key because `visitIfBlockBranch` previously pushed the alias
16+
// to the children array, causing mutation if the array wasn't cloned.
17+
const template = '@if (val; as alias) { <div></div> }';
18+
const result = parse(template);
19+
20+
// Structure:
21+
// result.nodes[0] is the IfBlock
22+
// IfBlock.branches[0] is the IfBlockBranch
23+
const ifBlock = result.nodes[0] as t.IfBlock;
24+
expect(ifBlock instanceof t.IfBlock).toBe(true);
25+
26+
const branch = ifBlock.branches[0];
27+
expect(branch instanceof t.IfBlockBranch).toBe(true);
28+
29+
// Verify initial state
30+
// Children should contain only the Element (<div></div>)
31+
expect(branch.children.length).toBe(1);
32+
expect(branch.children[0] instanceof t.Element).toBe(true);
33+
const element = branch.children[0] as t.Element;
34+
expect(element.name).toBe('div');
35+
36+
// Alias should exist
37+
expect(branch.expressionAlias).not.toBeNull();
38+
const alias = branch.expressionAlias!;
39+
40+
const visitor = new t.RecursiveVisitor();
41+
42+
// Visit the branch
43+
visitor.visitIfBlockBranch(branch);
44+
45+
// Verify post-visit state
46+
// The children array should STILL only contain the Element.
47+
// It should NOT contain the alias variable.
48+
expect(branch.children.length).toBe(1);
49+
expect(branch.children[0]).toBe(element);
50+
expect(branch.children).not.toContain(alias);
51+
});
52+
});

0 commit comments

Comments
 (0)