Skip to content

Commit 9582b84

Browse files
committed
fix(@angular-devkit/schematics): implement HostTree specific filtering
1 parent 61d2181 commit 9582b84

File tree

6 files changed

+160
-52
lines changed

6 files changed

+160
-52
lines changed

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import { Observable, of as observableOf } from 'rxjs';
99
import { concatMap, last, map } from 'rxjs/operators';
1010
import { FileOperator, Rule, SchematicContext, Source } from '../engine/interface';
11-
import { FilterTree, FilteredTree } from '../tree/filtered';
11+
import { SchematicsException } from '../exception/exception';
12+
import { FilteredTree } from '../tree/filtered';
13+
import { FilterHostTree, HostTree } from '../tree/host-tree';
1214
import { FileEntry, FilePredicate, MergeStrategy, Tree } from '../tree/interface';
1315
import {
1416
branch,
@@ -93,10 +95,13 @@ export function noop(): Rule {
9395

9496
export function filter(predicate: FilePredicate<boolean>): Rule {
9597
return ((tree: Tree) => {
98+
// TODO: Remove VirtualTree usage in 7.0
9699
if (tree instanceof VirtualTree) {
97100
return new FilteredTree(tree, predicate);
101+
} else if (tree instanceof HostTree) {
102+
return new FilterHostTree(tree, predicate);
98103
} else {
99-
return new FilterTree(tree, predicate);
104+
throw new SchematicsException('Tree type is not supported.');
100105
}
101106
});
102107
}

packages/angular_devkit/schematics/src/tree/filtered.ts

+1-14
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import { DelegateTree } from './delegate';
98
import { FilePredicate, Tree } from './interface';
109
import { VirtualTree } from './virtual';
1110

12-
11+
// TODO: Remove this along with VirtualTree in 7.0
1312
export class FilteredTree extends VirtualTree {
1413
constructor(tree: Tree, filter: FilePredicate<boolean> = () => true) {
1514
super();
@@ -39,15 +38,3 @@ export class FilteredTree extends VirtualTree {
3938
});
4039
}
4140
}
42-
43-
export class FilterTree extends DelegateTree {
44-
constructor(tree: Tree, filter: FilePredicate<boolean> = () => true) {
45-
super(tree.branch());
46-
47-
tree.visit(path => {
48-
if (!filter(path)) {
49-
this.delete(path);
50-
}
51-
});
52-
}
53-
}

packages/angular_devkit/schematics/src/tree/filtered_spec.ts

+1-29
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import { normalize } from '@angular-devkit/core';
9-
import { FilterTree, FilteredTree } from './filtered';
10-
import { HostTree } from './host-tree';
9+
import { FilteredTree } from './filtered';
1110
import { VirtualTree } from './virtual';
1211

1312

@@ -22,30 +21,3 @@ describe('FilteredTree', () => {
2221
expect(filtered.files.sort()).toEqual(['/file1', '/file3'].map(normalize));
2322
});
2423
});
25-
26-
describe('FilterTree', () => {
27-
it('works', () => {
28-
const tree = new HostTree();
29-
tree.create('/file1', '');
30-
tree.create('/file2', '');
31-
tree.create('/file3', '');
32-
33-
const filtered = new FilterTree(tree, p => p != '/file2');
34-
const filteredFiles: string[] = [];
35-
filtered.visit(path => filteredFiles.push(path));
36-
expect(filteredFiles.sort()).toEqual(['/file1', '/file3'].map(normalize));
37-
});
38-
39-
it('works with two filters', () => {
40-
const tree = new HostTree();
41-
tree.create('/file1', '');
42-
tree.create('/file2', '');
43-
tree.create('/file3', '');
44-
45-
const filtered = new FilterTree(tree, p => p != '/file2');
46-
const filtered2 = new FilterTree(filtered, p => p != '/file3');
47-
const filteredFiles: string[] = [];
48-
filtered2.visit(path => filteredFiles.push(path));
49-
expect(filteredFiles.sort()).toEqual(['/file1'].map(normalize));
50-
});
51-
});

packages/angular_devkit/schematics/src/tree/host-tree.ts

+67-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
normalize,
1717
virtualFs,
1818
} from '@angular-devkit/core';
19+
import { Observable, of } from 'rxjs';
20+
import { concatMap, map, mergeMap } from 'rxjs/operators';
1921
import {
2022
ContentHasMutatedException,
2123
FileAlreadyExistException,
@@ -35,10 +37,12 @@ import { LazyFileEntry } from './entry';
3537
import {
3638
DirEntry,
3739
FileEntry,
40+
FilePredicate,
3841
FileVisitor,
3942
FileVisitorCancelToken,
4043
MergeStrategy,
41-
Tree, TreeSymbol,
44+
Tree,
45+
TreeSymbol,
4246
UpdateRecorder,
4347
} from './interface';
4448
import { UpdateRecorderBase } from './recorder';
@@ -414,3 +418,65 @@ export class HostCreateTree extends HostTree {
414418
});
415419
}
416420
}
421+
422+
export class FilterHostTree extends HostTree {
423+
constructor(tree: HostTree, filter: FilePredicate<boolean> = () => true) {
424+
const newBackend = new virtualFs.SimpleMemoryHost();
425+
// cast to allow access
426+
const originalBackend = (tree as FilterHostTree)._backend;
427+
428+
const recurse: (base: Path) => Observable<void> = base => {
429+
return originalBackend.list(base)
430+
.pipe(
431+
mergeMap(x => x),
432+
map(path => join(base, path)),
433+
concatMap(path => {
434+
let isDirectory = false;
435+
originalBackend.isDirectory(path).subscribe(val => isDirectory = val);
436+
if (isDirectory) {
437+
return recurse(path);
438+
}
439+
440+
let isFile = false;
441+
originalBackend.isFile(path).subscribe(val => isFile = val);
442+
if (!isFile || !filter(path)) {
443+
return of();
444+
}
445+
446+
let content: ArrayBuffer | null = null;
447+
originalBackend.read(path).subscribe(val => content = val);
448+
if (!content) {
449+
return of();
450+
}
451+
452+
return newBackend.write(path, content as {} as virtualFs.FileBuffer);
453+
}),
454+
);
455+
};
456+
457+
recurse(normalize('/')).subscribe();
458+
459+
super(newBackend);
460+
461+
for (const action of tree.actions) {
462+
if (!filter(action.path)) {
463+
continue;
464+
}
465+
466+
switch (action.kind) {
467+
case 'c':
468+
this.create(action.path, action.content);
469+
break;
470+
case 'd':
471+
this.delete(action.path);
472+
break;
473+
case 'o':
474+
this.overwrite(action.path, action.content);
475+
break;
476+
case 'r':
477+
this.rename(action.path, action.to);
478+
break;
479+
}
480+
}
481+
}
482+
}

packages/angular_devkit/schematics/src/tree/host-tree_spec.ts

+76-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// tslint:disable:no-any
99
import { normalize, virtualFs } from '@angular-devkit/core';
1010
import { Action } from './action';
11-
import { HostTree } from './host-tree';
11+
import { FilterHostTree, HostTree } from './host-tree';
1212
import { VirtualTree } from './virtual';
1313

1414
describe('HostTree', () => {
@@ -36,3 +36,78 @@ describe('HostTree', () => {
3636
] as any);
3737
});
3838
});
39+
40+
describe('FilterHostTree', () => {
41+
it('works', () => {
42+
const tree = new HostTree();
43+
tree.create('/file1', '');
44+
tree.create('/file2', '');
45+
tree.create('/file3', '');
46+
47+
const filtered = new FilterHostTree(tree, p => p != '/file2');
48+
const filteredFiles: string[] = [];
49+
filtered.visit(path => filteredFiles.push(path));
50+
expect(filteredFiles.sort()).toEqual(['/file1', '/file3'].map(normalize));
51+
expect(filtered.actions.length).toEqual(2);
52+
});
53+
54+
it('works with two filters', () => {
55+
const tree = new HostTree();
56+
tree.create('/file1', '');
57+
tree.create('/file2', '');
58+
tree.create('/file3', '');
59+
60+
const filtered = new FilterHostTree(tree, p => p != '/file2');
61+
const filtered2 = new FilterHostTree(filtered, p => p != '/file3');
62+
const filteredFiles: string[] = [];
63+
filtered2.visit(path => filteredFiles.push(path));
64+
expect(filteredFiles.sort()).toEqual(['/file1'].map(normalize));
65+
expect(filtered2.actions.map(a => a.kind)).toEqual(['c']);
66+
});
67+
68+
it('works with underlying files', () => {
69+
const fs = new virtualFs.test.TestHost({
70+
'/file1': '',
71+
});
72+
const tree = new HostTree(fs);
73+
tree.create('/file2', '');
74+
tree.create('/file3', '');
75+
76+
const filtered = new FilterHostTree(tree, p => p != '/file2');
77+
const filtered2 = new FilterHostTree(filtered, p => p != '/file3');
78+
const filteredFiles: string[] = [];
79+
filtered2.visit(path => filteredFiles.push(path));
80+
expect(filteredFiles.sort()).toEqual(['/file1'].map(normalize));
81+
expect(filtered2.actions.map(a => a.kind)).toEqual([]);
82+
});
83+
84+
it('works with created paths and files', () => {
85+
const tree = new HostTree();
86+
tree.create('/dir1/file1', '');
87+
tree.create('/dir2/file2', '');
88+
tree.create('/file3', '');
89+
90+
const filtered = new FilterHostTree(tree, p => p != '/dir2/file2');
91+
const filtered2 = new FilterHostTree(filtered, p => p != '/file3');
92+
const filteredFiles: string[] = [];
93+
filtered2.visit(path => filteredFiles.push(path));
94+
expect(filteredFiles.sort()).toEqual(['/dir1/file1'].map(normalize));
95+
expect(filtered2.actions.map(a => a.kind)).toEqual(['c']);
96+
});
97+
98+
it('works with underlying paths and files', () => {
99+
const fs = new virtualFs.test.TestHost({
100+
'/dir1/file1': '',
101+
'/dir2/file2': '',
102+
});
103+
const tree = new HostTree(fs);
104+
tree.create('/file3', '');
105+
106+
const filtered = new FilterHostTree(tree, p => p != '/dir2/file2');
107+
const filtered2 = new FilterHostTree(filtered, p => p != '/file3');
108+
const filteredFiles: string[] = [];
109+
filtered2.visit(path => filteredFiles.push(path));
110+
expect(filteredFiles.sort()).toEqual(['/dir1/file1'].map(normalize));
111+
expect(filtered2.actions.map(a => a.kind)).toEqual([]);
112+
});
113+
});

packages/angular_devkit/schematics/src/tree/static.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import { FilterTree, FilteredTree } from './filtered';
9-
import { HostTree } from './host-tree';
8+
import { SchematicsException } from '../exception/exception';
9+
import { FilteredTree } from './filtered';
10+
import { FilterHostTree, HostTree } from './host-tree';
1011
import { FilePredicate, MergeStrategy, Tree } from './interface';
1112
import { VirtualTree } from './virtual';
1213

@@ -42,11 +43,13 @@ export function partition(tree: Tree, predicate: FilePredicate<boolean>): [Tree,
4243
new FilteredTree(tree, predicate),
4344
new FilteredTree(tree, (path, entry) => !predicate(path, entry)),
4445
];
45-
} else {
46+
} else if (tree instanceof HostTree) {
4647
return [
47-
new FilterTree(tree, predicate),
48-
new FilterTree(tree, (path, entry) => !predicate(path, entry)),
48+
new FilterHostTree(tree, predicate),
49+
new FilterHostTree(tree, (path, entry) => !predicate(path, entry)),
4950
];
51+
} else {
52+
throw new SchematicsException('Tree type is not supported.');
5053
}
5154
}
5255

0 commit comments

Comments
 (0)