Skip to content

Commit 72a17af

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.
1 parent d4948fb commit 72a17af

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

packages/compiler/src/render3/r3_ast.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -814,9 +814,8 @@ export class RecursiveVisitor implements Visitor<void> {
814814
visitAll(this, block.branches);
815815
}
816816
visitIfBlockBranch(block: IfBlockBranch): void {
817-
const blockItems = block.children;
818-
block.expressionAlias && blockItems.push(block.expressionAlias);
819-
visitAll(this, blockItems);
817+
visitAll(this, block.children);
818+
block.expressionAlias?.visit(this);
820819
}
821820
visitContent(content: Content): void {
822821
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)