Skip to content

Commit 526cdb2

Browse files
clydindgp1130
authored andcommitted
feat(@angular-devkit/schematics): allow chain rule to accept iterables of rules
Previously, the `chain` base rule only accepted an `Array` of schematics rules. In addition to still allowing an `Array`, `chain` now can accept either an `Iterable<Rule>` or `AsyncIterable<Rule>`. This provides support for sync and async generator functions with the `chain` rule.
1 parent 4325995 commit 526cdb2

File tree

3 files changed

+65
-6
lines changed

3 files changed

+65
-6
lines changed

goldens/public-api/angular_devkit/schematics/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export function callRule(rule: Rule, input: Tree_2 | Observable<Tree_2>, context
145145
export function callSource(source: Source, context: SchematicContext): Observable<Tree_2>;
146146

147147
// @public
148-
export function chain(rules: Rule[]): Rule;
148+
export function chain(rules: Iterable<Rule> | AsyncIterable<Rule>): Rule;
149149

150150
// @public (undocumented)
151151
export class CircularCollectionException extends BaseException {

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,14 @@ export function empty(): Source {
3333
/**
3434
* Chain multiple rules into a single rule.
3535
*/
36-
export function chain(rules: Rule[]): Rule {
37-
return (tree, context) => {
38-
return rules.reduce<Tree | Observable<Tree>>((acc, curr) => callRule(curr, acc, context), tree);
36+
export function chain(rules: Iterable<Rule> | AsyncIterable<Rule>): Rule {
37+
return async (initialTree, context) => {
38+
let intermediateTree: Observable<Tree> | undefined;
39+
for await (const rule of rules) {
40+
intermediateTree = callRule(rule, intermediateTree ?? initialTree, context);
41+
}
42+
43+
return () => intermediateTree;
3944
};
4045
}
4146

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

+56-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import { apply, applyToSubtree, chain } from './base';
1717
import { callRule, callSource } from './call';
1818
import { move } from './move';
1919

20-
const context: SchematicContext = ({
20+
const context: SchematicContext = {
2121
engine: null,
2222
debug: false,
2323
strategy: MergeStrategy.Default,
24-
} as {}) as SchematicContext;
24+
} as {} as SchematicContext;
2525

2626
describe('chain', () => {
2727
it('works with simple rules', (done) => {
@@ -48,6 +48,60 @@ describe('chain', () => {
4848
.then(done, done.fail);
4949
});
5050

51+
it('works with a sync generator of rules', async () => {
52+
const rulesCalled: Tree[] = [];
53+
54+
const tree0 = empty();
55+
const tree1 = empty();
56+
const tree2 = empty();
57+
const tree3 = empty();
58+
59+
const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1);
60+
const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2);
61+
const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3);
62+
63+
function* generateRules() {
64+
yield rule0;
65+
yield rule1;
66+
yield rule2;
67+
}
68+
69+
const result = await callRule(chain(generateRules()), tree0, context).toPromise();
70+
71+
expect(result).not.toBe(tree0);
72+
expect(rulesCalled[0]).toBe(tree0);
73+
expect(rulesCalled[1]).toBe(tree1);
74+
expect(rulesCalled[2]).toBe(tree2);
75+
expect(result).toBe(tree3);
76+
});
77+
78+
it('works with an async generator of rules', async () => {
79+
const rulesCalled: Tree[] = [];
80+
81+
const tree0 = empty();
82+
const tree1 = empty();
83+
const tree2 = empty();
84+
const tree3 = empty();
85+
86+
const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1);
87+
const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2);
88+
const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3);
89+
90+
async function* generateRules() {
91+
yield rule0;
92+
yield rule1;
93+
yield rule2;
94+
}
95+
96+
const result = await callRule(chain(generateRules()), tree0, context).toPromise();
97+
98+
expect(result).not.toBe(tree0);
99+
expect(rulesCalled[0]).toBe(tree0);
100+
expect(rulesCalled[1]).toBe(tree1);
101+
expect(rulesCalled[2]).toBe(tree2);
102+
expect(result).toBe(tree3);
103+
});
104+
51105
it('works with observable rules', (done) => {
52106
const rulesCalled: Tree[] = [];
53107

0 commit comments

Comments
 (0)