Skip to content

Commit cf1f3b9

Browse files
adiyy2001dgp1130
authored andcommitted
fix(@angular-devkit/schematics): avoid stack overflow when chaining many rules
The chain rule nested one observable per rule, so subscribing to a chain of a few thousand rules exceeded the call stack and the result never settled. Each rule result is now awaited before the next rule runs. (cherry picked from commit b39a5fc)
1 parent 91bad46 commit cf1f3b9

2 files changed

Lines changed: 18 additions & 5 deletions

File tree

‎packages/angular_devkit/schematics/src/rules/base.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { Observable, concat, map, mapTo, toArray } from 'rxjs';
9+
import { concat, lastValueFrom, map, mapTo, toArray } from 'rxjs';
1010
import { FileOperator, Rule, Source } from '../engine/interface';
1111
import { SchematicsException } from '../exception/exception';
1212
import { FilterHostTree, HostTree } from '../tree/host-tree';
@@ -34,18 +34,18 @@ export function empty(): Source {
3434
*/
3535
export function chain(rules: Iterable<Rule> | AsyncIterable<Rule>): Rule {
3636
return async (initialTree, context) => {
37-
let intermediateTree: Observable<Tree> | undefined;
37+
let intermediateTree = initialTree;
3838
if (Symbol.asyncIterator in rules) {
3939
for await (const rule of rules) {
40-
intermediateTree = callRule(rule, intermediateTree ?? initialTree, context);
40+
intermediateTree = await lastValueFrom(callRule(rule, intermediateTree, context));
4141
}
4242
} else {
4343
for (const rule of rules) {
44-
intermediateTree = callRule(rule, intermediateTree ?? initialTree, context);
44+
intermediateTree = await lastValueFrom(callRule(rule, intermediateTree, context));
4545
}
4646
}
4747

48-
return () => intermediateTree;
48+
return intermediateTree;
4949
};
5050
}
5151

‎packages/angular_devkit/schematics/src/rules/base_spec.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ describe('chain', () => {
140140
expect(rulesCalled[2]).toBe(tree2);
141141
expect(result).toBe(tree3);
142142
});
143+
144+
it('works with a large number of rules', async () => {
145+
const tree0 = empty();
146+
let callCount = 0;
147+
const rules: Rule[] = Array.from({ length: 20_000 }, () => () => {
148+
callCount++;
149+
});
150+
151+
const result = await lastValueFrom(callRule(chain(rules), tree0, context));
152+
153+
expect(result).toBe(tree0);
154+
expect(callCount).toBe(20_000);
155+
});
143156
});
144157

145158
describe('apply', () => {

0 commit comments

Comments
 (0)