diff --git a/goldens/public-api/angular_devkit/schematics/index.md b/goldens/public-api/angular_devkit/schematics/index.md index 5ef8ce4c5cd5..2d9f6a616bb1 100644 --- a/goldens/public-api/angular_devkit/schematics/index.md +++ b/goldens/public-api/angular_devkit/schematics/index.md @@ -145,7 +145,7 @@ export function callRule(rule: Rule, input: Tree_2 | Observable, context export function callSource(source: Source, context: SchematicContext): Observable; // @public -export function chain(rules: Rule[]): Rule; +export function chain(rules: Iterable | AsyncIterable): Rule; // @public (undocumented) export class CircularCollectionException extends BaseException { diff --git a/packages/angular_devkit/schematics/src/rules/base.ts b/packages/angular_devkit/schematics/src/rules/base.ts index a52dda34f6ee..739be3940cbf 100644 --- a/packages/angular_devkit/schematics/src/rules/base.ts +++ b/packages/angular_devkit/schematics/src/rules/base.ts @@ -33,9 +33,14 @@ export function empty(): Source { /** * Chain multiple rules into a single rule. */ -export function chain(rules: Rule[]): Rule { - return (tree, context) => { - return rules.reduce>((acc, curr) => callRule(curr, acc, context), tree); +export function chain(rules: Iterable | AsyncIterable): Rule { + return async (initialTree, context) => { + let intermediateTree: Observable | undefined; + for await (const rule of rules) { + intermediateTree = callRule(rule, intermediateTree ?? initialTree, context); + } + + return () => intermediateTree; }; } diff --git a/packages/angular_devkit/schematics/src/rules/base_spec.ts b/packages/angular_devkit/schematics/src/rules/base_spec.ts index 8adf8c503851..9a687cb6c71e 100644 --- a/packages/angular_devkit/schematics/src/rules/base_spec.ts +++ b/packages/angular_devkit/schematics/src/rules/base_spec.ts @@ -17,11 +17,11 @@ import { apply, applyToSubtree, chain } from './base'; import { callRule, callSource } from './call'; import { move } from './move'; -const context: SchematicContext = ({ +const context: SchematicContext = { engine: null, debug: false, strategy: MergeStrategy.Default, -} as {}) as SchematicContext; +} as {} as SchematicContext; describe('chain', () => { it('works with simple rules', (done) => { @@ -48,6 +48,60 @@ describe('chain', () => { .then(done, done.fail); }); + it('works with a sync generator of rules', async () => { + const rulesCalled: Tree[] = []; + + const tree0 = empty(); + const tree1 = empty(); + const tree2 = empty(); + const tree3 = empty(); + + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); + + function* generateRules() { + yield rule0; + yield rule1; + yield rule2; + } + + const result = await callRule(chain(generateRules()), tree0, context).toPromise(); + + expect(result).not.toBe(tree0); + expect(rulesCalled[0]).toBe(tree0); + expect(rulesCalled[1]).toBe(tree1); + expect(rulesCalled[2]).toBe(tree2); + expect(result).toBe(tree3); + }); + + it('works with an async generator of rules', async () => { + const rulesCalled: Tree[] = []; + + const tree0 = empty(); + const tree1 = empty(); + const tree2 = empty(); + const tree3 = empty(); + + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); + + async function* generateRules() { + yield rule0; + yield rule1; + yield rule2; + } + + const result = await callRule(chain(generateRules()), tree0, context).toPromise(); + + expect(result).not.toBe(tree0); + expect(rulesCalled[0]).toBe(tree0); + expect(rulesCalled[1]).toBe(tree1); + expect(rulesCalled[2]).toBe(tree2); + expect(result).toBe(tree3); + }); + it('works with observable rules', (done) => { const rulesCalled: Tree[] = [];