From 0eabf39f62bf6ef4ff1e4e4fd72b31f7079d4e16 Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Tue, 19 Apr 2022 10:20:48 -0400
Subject: [PATCH 1/3] feat(@angular-devkit/schematics): support reading text
content directly from a Tree
The schematics `Tree` now contains an additional `readText` method that supports directly
reading the content of a file as UTF-8 text. This avoids the need to manually decode a Buffer
within a schematic when text content is needed. If a file path does not exist, an exception
will be thrown. While this differs from the semantics of `read`, it helps reduce the amount
of code needed for common schematic use cases.
---
.../angular_devkit/schematics/index.md | 4 ++
.../schematics/src/tree/delegate.ts | 3 ++
.../schematics/src/tree/host-tree.ts | 37 +++++++++++++++----
.../schematics/src/tree/host-tree_spec.ts | 23 ++++++++++++
.../schematics/src/tree/interface.ts | 11 ++++++
.../schematics/src/tree/null.ts | 3 ++
.../schematics/src/tree/scoped.ts | 3 ++
7 files changed, 76 insertions(+), 8 deletions(-)
diff --git a/goldens/public-api/angular_devkit/schematics/index.md b/goldens/public-api/angular_devkit/schematics/index.md
index 0d16c2974b54..f78444d1c0ea 100644
--- a/goldens/public-api/angular_devkit/schematics/index.md
+++ b/goldens/public-api/angular_devkit/schematics/index.md
@@ -235,6 +235,8 @@ export class DelegateTree implements Tree_2 {
// (undocumented)
read(path: string): Buffer | null;
// (undocumented)
+ readText(path: string): string;
+ // (undocumented)
rename(from: string, to: string): void;
// (undocumented)
get root(): DirEntry;
@@ -547,6 +549,8 @@ export class HostTree implements Tree_2 {
// (undocumented)
read(path: string): Buffer | null;
// (undocumented)
+ readText(path: string): string;
+ // (undocumented)
rename(from: string, to: string): void;
// (undocumented)
get root(): DirEntry;
diff --git a/packages/angular_devkit/schematics/src/tree/delegate.ts b/packages/angular_devkit/schematics/src/tree/delegate.ts
index 147997964d59..a8b8a758db79 100644
--- a/packages/angular_devkit/schematics/src/tree/delegate.ts
+++ b/packages/angular_devkit/schematics/src/tree/delegate.ts
@@ -35,6 +35,9 @@ export class DelegateTree implements Tree {
read(path: string): Buffer | null {
return this._other.read(path);
}
+ readText(path: string): string {
+ return this._other.readText(path);
+ }
exists(path: string): boolean {
return this._other.exists(path);
}
diff --git a/packages/angular_devkit/schematics/src/tree/host-tree.ts b/packages/angular_devkit/schematics/src/tree/host-tree.ts
index ba5b6b75f8fb..20eac5253dde 100644
--- a/packages/angular_devkit/schematics/src/tree/host-tree.ts
+++ b/packages/angular_devkit/schematics/src/tree/host-tree.ts
@@ -18,6 +18,7 @@ import {
} from '@angular-devkit/core';
import { EMPTY, Observable } from 'rxjs';
import { concatMap, map, mergeMap } from 'rxjs/operators';
+import { TextDecoder } from 'util';
import {
ContentHasMutatedException,
FileAlreadyExistException,
@@ -162,10 +163,10 @@ export class HostTree implements Tree {
return tree._ancestry.has(this._id);
}
if (tree instanceof DelegateTree) {
- return this.isAncestorOf(((tree as unknown) as { _other: Tree })._other);
+ return this.isAncestorOf((tree as unknown as { _other: Tree })._other);
}
if (tree instanceof ScopedTree) {
- return this.isAncestorOf(((tree as unknown) as { _base: Tree })._base);
+ return this.isAncestorOf((tree as unknown as { _base: Tree })._base);
}
return false;
@@ -206,9 +207,9 @@ export class HostTree implements Tree {
throw new MergeConflictException(path);
}
- this._record.overwrite(path, (content as {}) as virtualFs.FileBuffer).subscribe();
+ this._record.overwrite(path, content as {} as virtualFs.FileBuffer).subscribe();
} else {
- this._record.create(path, (content as {}) as virtualFs.FileBuffer).subscribe();
+ this._record.create(path, content as {} as virtualFs.FileBuffer).subscribe();
}
return;
@@ -234,7 +235,7 @@ export class HostTree implements Tree {
}
// We use write here as merge validation has already been done, and we want to let
// the CordHost do its job.
- this._record.write(path, (content as {}) as virtualFs.FileBuffer).subscribe();
+ this._record.write(path, content as {} as virtualFs.FileBuffer).subscribe();
return;
}
@@ -289,6 +290,26 @@ export class HostTree implements Tree {
return entry ? entry.content : null;
}
+
+ readText(path: string): string {
+ const data = this.read(path);
+ if (data === null) {
+ throw new FileDoesNotExistException(path);
+ }
+
+ const decoder = new TextDecoder('utf-8', { fatal: true });
+
+ try {
+ // With the `fatal` option enabled, invalid data will throw a TypeError
+ return decoder.decode(data);
+ } catch (e) {
+ if (e instanceof TypeError) {
+ throw new Error(`Failed to decode "${path}" as UTF-8 text.`);
+ }
+ throw e;
+ }
+ }
+
exists(path: string): boolean {
return this._recordSync.isFile(this._normalizePath(path));
}
@@ -337,7 +358,7 @@ export class HostTree implements Tree {
throw new FileDoesNotExistException(p);
}
const c = typeof content == 'string' ? Buffer.from(content) : content;
- this._record.overwrite(p, (c as {}) as virtualFs.FileBuffer).subscribe();
+ this._record.overwrite(p, c as {} as virtualFs.FileBuffer).subscribe();
}
beginUpdate(path: string): UpdateRecorder {
const entry = this.get(path);
@@ -371,7 +392,7 @@ export class HostTree implements Tree {
throw new FileAlreadyExistException(p);
}
const c = typeof content == 'string' ? Buffer.from(content) : content;
- this._record.create(p, (c as {}) as virtualFs.FileBuffer).subscribe();
+ this._record.create(p, c as {} as virtualFs.FileBuffer).subscribe();
}
delete(path: string): void {
this._recordSync.delete(this._normalizePath(path));
@@ -476,7 +497,7 @@ export class FilterHostTree extends HostTree {
return EMPTY;
}
- return newBackend.write(path, (content as {}) as virtualFs.FileBuffer);
+ return newBackend.write(path, content as {} as virtualFs.FileBuffer);
}),
);
};
diff --git a/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts b/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts
index 9c1a499fd8e3..d0aeb3f6e529 100644
--- a/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts
+++ b/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts
@@ -11,6 +11,29 @@ import { FilterHostTree, HostTree } from './host-tree';
import { MergeStrategy } from './interface';
describe('HostTree', () => {
+ describe('readText', () => {
+ it('returns text when reading a file that exists', () => {
+ const tree = new HostTree();
+ tree.create('/textfile1', 'abc');
+ tree.create('/textfile2', '123');
+ expect(tree.readText('/textfile1')).toEqual('abc');
+ expect(tree.readText('/textfile2')).toEqual('123');
+ });
+
+ it('throws an error when a file does not exist', () => {
+ const tree = new HostTree();
+ const path = '/textfile1';
+ expect(() => tree.readText(path)).toThrowError(`Path "${path}" does not exist.`);
+ });
+
+ it('throws an error when invalid UTF-8 characters are present', () => {
+ const tree = new HostTree();
+ const path = '/textfile1';
+ tree.create(path, Buffer.from([0xff, 0xff, 0xff, 0xff]));
+ expect(() => tree.readText(path)).toThrowError(`Failed to decode "${path}" as UTF-8 text.`);
+ });
+ });
+
describe('merge', () => {
it('should create files from each tree', () => {
const tree = new HostTree();
diff --git a/packages/angular_devkit/schematics/src/tree/interface.ts b/packages/angular_devkit/schematics/src/tree/interface.ts
index e05be51aa6a3..17436762ad2a 100644
--- a/packages/angular_devkit/schematics/src/tree/interface.ts
+++ b/packages/angular_devkit/schematics/src/tree/interface.ts
@@ -83,6 +83,17 @@ export interface Tree {
// Readonly.
read(path: string): Buffer | null;
+
+ /**
+ * Reads a file from the Tree as a UTF-8 encoded text file.
+ *
+ * @param path The path of the file to read.
+ * @returns A string containing the contents of the file.
+ * @throws {@link FileDoesNotExistException} if the file is not found.
+ * @throws An error if the file contains invalid UTF-8 characters.
+ */
+ readText(path: string): string;
+
exists(path: string): boolean;
get(path: string): FileEntry | null;
getDir(path: string): DirEntry;
diff --git a/packages/angular_devkit/schematics/src/tree/null.ts b/packages/angular_devkit/schematics/src/tree/null.ts
index 48469a3c3370..6bc774d45142 100644
--- a/packages/angular_devkit/schematics/src/tree/null.ts
+++ b/packages/angular_devkit/schematics/src/tree/null.ts
@@ -57,6 +57,9 @@ export class NullTree implements Tree {
read(_path: string) {
return null;
}
+ readText(path: string): string {
+ throw new FileDoesNotExistException(path);
+ }
get(_path: string) {
return null;
}
diff --git a/packages/angular_devkit/schematics/src/tree/scoped.ts b/packages/angular_devkit/schematics/src/tree/scoped.ts
index f77af03635a1..21639cfc30a1 100644
--- a/packages/angular_devkit/schematics/src/tree/scoped.ts
+++ b/packages/angular_devkit/schematics/src/tree/scoped.ts
@@ -113,6 +113,9 @@ export class ScopedTree implements Tree {
read(path: string): Buffer | null {
return this._base.read(this._fullPath(path));
}
+ readText(path: string): string {
+ return this._base.readText(this._fullPath(path));
+ }
exists(path: string): boolean {
return this._base.exists(this._fullPath(path));
}
From 2ef0955c68cdb57ce09189d0d2fbe41bb590aeb3 Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Tue, 19 Apr 2022 11:17:10 -0400
Subject: [PATCH 2/3] feat(@angular-devkit/schematics): support reading JSON
content directly from a Tree
The schematics `Tree` now contains an additional `readJSON` method that supports directly
reading and parsing the content of a file as UTF-8 JSON with comment support. This avoids the need to manually
decode a Buffer, parse and handle JSON comments within a schematic when JSON content is needed.
If a file path does not exist, an exception will be thrown. While this differs from the semantics of `read`,
it helps reduce the amount of code needed for common schematic use cases. JSON parse errors will also result
in an exception being thrown with a message detailing the error.
---
.../angular_devkit/schematics/index.md | 5 ++
.../schematics/testing/index.md | 1 +
.../angular_devkit/schematics/tools/index.md | 1 +
.../angular_devkit/schematics/BUILD.bazel | 1 +
.../schematics/src/tree/delegate.ts | 4 ++
.../schematics/src/tree/host-tree.ts | 18 ++++++
.../schematics/src/tree/host-tree_spec.ts | 55 +++++++++++++++++++
.../schematics/src/tree/interface.ts | 16 +++++-
.../schematics/src/tree/null.ts | 13 ++++-
.../schematics/src/tree/scoped.ts | 4 ++
10 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/goldens/public-api/angular_devkit/schematics/index.md b/goldens/public-api/angular_devkit/schematics/index.md
index f78444d1c0ea..05f844128ad5 100644
--- a/goldens/public-api/angular_devkit/schematics/index.md
+++ b/goldens/public-api/angular_devkit/schematics/index.md
@@ -7,6 +7,7 @@
///
import { BaseException } from '@angular-devkit/core';
+import { JsonValue } from '@angular-devkit/core';
import { logging } from '@angular-devkit/core';
import { Observable } from 'rxjs';
import { Path } from '@angular-devkit/core';
@@ -235,6 +236,8 @@ export class DelegateTree implements Tree_2 {
// (undocumented)
read(path: string): Buffer | null;
// (undocumented)
+ readJson(path: string): JsonValue;
+ // (undocumented)
readText(path: string): string;
// (undocumented)
rename(from: string, to: string): void;
@@ -549,6 +552,8 @@ export class HostTree implements Tree_2 {
// (undocumented)
read(path: string): Buffer | null;
// (undocumented)
+ readJson(path: string): JsonValue;
+ // (undocumented)
readText(path: string): string;
// (undocumented)
rename(from: string, to: string): void;
diff --git a/goldens/public-api/angular_devkit/schematics/testing/index.md b/goldens/public-api/angular_devkit/schematics/testing/index.md
index 9358b5a8390a..06e24643a9fe 100644
--- a/goldens/public-api/angular_devkit/schematics/testing/index.md
+++ b/goldens/public-api/angular_devkit/schematics/testing/index.md
@@ -6,6 +6,7 @@
///
+import { JsonValue } from '@angular-devkit/core';
import { logging } from '@angular-devkit/core';
import { Observable } from 'rxjs';
import { Path } from '@angular-devkit/core';
diff --git a/goldens/public-api/angular_devkit/schematics/tools/index.md b/goldens/public-api/angular_devkit/schematics/tools/index.md
index d968860a625f..b4bf5f2fa17b 100644
--- a/goldens/public-api/angular_devkit/schematics/tools/index.md
+++ b/goldens/public-api/angular_devkit/schematics/tools/index.md
@@ -8,6 +8,7 @@
import { BaseException } from '@angular-devkit/core';
import { JsonObject } from '@angular-devkit/core';
+import { JsonValue } from '@angular-devkit/core';
import { logging } from '@angular-devkit/core';
import { Observable } from 'rxjs';
import { Path } from '@angular-devkit/core';
diff --git a/packages/angular_devkit/schematics/BUILD.bazel b/packages/angular_devkit/schematics/BUILD.bazel
index 0cccea0629fc..d9907050747d 100644
--- a/packages/angular_devkit/schematics/BUILD.bazel
+++ b/packages/angular_devkit/schematics/BUILD.bazel
@@ -37,6 +37,7 @@ ts_library(
"//packages/angular_devkit/core",
"//packages/angular_devkit/core/node", # TODO: get rid of this for 6.0
"@npm//@types/node",
+ "@npm//jsonc-parser",
"@npm//magic-string",
"@npm//rxjs",
],
diff --git a/packages/angular_devkit/schematics/src/tree/delegate.ts b/packages/angular_devkit/schematics/src/tree/delegate.ts
index a8b8a758db79..e1fb75b04f04 100644
--- a/packages/angular_devkit/schematics/src/tree/delegate.ts
+++ b/packages/angular_devkit/schematics/src/tree/delegate.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
+import { JsonValue } from '@angular-devkit/core';
import { Action } from './action';
import {
DirEntry,
@@ -38,6 +39,9 @@ export class DelegateTree implements Tree {
readText(path: string): string {
return this._other.readText(path);
}
+ readJson(path: string): JsonValue {
+ return this._other.readJson(path);
+ }
exists(path: string): boolean {
return this._other.exists(path);
}
diff --git a/packages/angular_devkit/schematics/src/tree/host-tree.ts b/packages/angular_devkit/schematics/src/tree/host-tree.ts
index 20eac5253dde..edcda81b5baf 100644
--- a/packages/angular_devkit/schematics/src/tree/host-tree.ts
+++ b/packages/angular_devkit/schematics/src/tree/host-tree.ts
@@ -7,6 +7,7 @@
*/
import {
+ JsonValue,
Path,
PathFragment,
PathIsDirectoryException,
@@ -16,6 +17,7 @@ import {
normalize,
virtualFs,
} from '@angular-devkit/core';
+import { ParseError, parse as jsoncParse, printParseErrorCode } from 'jsonc-parser';
import { EMPTY, Observable } from 'rxjs';
import { concatMap, map, mergeMap } from 'rxjs/operators';
import { TextDecoder } from 'util';
@@ -310,6 +312,22 @@ export class HostTree implements Tree {
}
}
+ readJson(path: string): JsonValue {
+ const content = this.readText(path);
+ const errors: ParseError[] = [];
+ const result = jsoncParse(content, errors, { allowTrailingComma: true });
+
+ // If there is a parse error throw with the error information
+ if (errors[0]) {
+ const { error, offset } = errors[0];
+ throw new Error(
+ `Failed to parse "${path}" as JSON. ${printParseErrorCode(error)} at offset: ${offset}.`,
+ );
+ }
+
+ return result;
+ }
+
exists(path: string): boolean {
return this._recordSync.isFile(this._normalizePath(path));
}
diff --git a/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts b/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts
index d0aeb3f6e529..2687ea14e8ed 100644
--- a/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts
+++ b/packages/angular_devkit/schematics/src/tree/host-tree_spec.ts
@@ -34,6 +34,61 @@ describe('HostTree', () => {
});
});
+ describe('readJson', () => {
+ it('returns a JSON value when reading a file that exists', () => {
+ const tree = new HostTree();
+ tree.create('/textfile1', '{ "a": true, "b": "xyz" }');
+ tree.create('/textfile2', '123');
+ tree.create('/textfile3', 'null');
+ expect(tree.readJson('/textfile1')).toEqual({ a: true, b: 'xyz' });
+ expect(tree.readJson('/textfile2')).toEqual(123);
+ expect(tree.readJson('/textfile3')).toBeNull();
+ });
+
+ it('returns a JSON value when reading a file with comments', () => {
+ const tree = new HostTree();
+ tree.create(
+ '/textfile1',
+ '{ "a": true, /* inner object\nmultiline comment\n */ "b": "xyz" }',
+ );
+ tree.create('/textfile2', '123 // number value');
+ tree.create('/textfile3', 'null // null value');
+ expect(tree.readJson('/textfile1')).toEqual({ a: true, b: 'xyz' });
+ expect(tree.readJson('/textfile2')).toEqual(123);
+ expect(tree.readJson('/textfile3')).toBeNull();
+ });
+
+ it('returns a JSON value when reading a file with trailing commas', () => {
+ const tree = new HostTree();
+ tree.create('/textfile1', '{ "a": true, "b": "xyz", }');
+ tree.create('/textfile2', '[5, 4, 3, 2, 1, ]');
+ expect(tree.readJson('/textfile1')).toEqual({ a: true, b: 'xyz' });
+ expect(tree.readJson('/textfile2')).toEqual([5, 4, 3, 2, 1]);
+ });
+
+ it('throws an error when a file does not exist', () => {
+ const tree = new HostTree();
+ const path = '/textfile1';
+ expect(() => tree.readJson(path)).toThrowError(`Path "${path}" does not exist.`);
+ });
+
+ it('throws an error if the JSON is malformed', () => {
+ const tree = new HostTree();
+ const path = '/textfile1';
+ tree.create(path, '{ "a": true;;;;; "b": "xyz" }');
+ expect(() => tree.readJson(path)).toThrowError(
+ `Failed to parse "${path}" as JSON. InvalidSymbol at offset: 7.`,
+ );
+ });
+
+ it('throws an error when invalid UTF-8 characters are present', () => {
+ const tree = new HostTree();
+ const path = '/textfile1';
+ tree.create(path, Buffer.from([0xff, 0xff, 0xff, 0xff]));
+ expect(() => tree.readJson(path)).toThrowError(`Failed to decode "${path}" as UTF-8 text.`);
+ });
+ });
+
describe('merge', () => {
it('should create files from each tree', () => {
const tree = new HostTree();
diff --git a/packages/angular_devkit/schematics/src/tree/interface.ts b/packages/angular_devkit/schematics/src/tree/interface.ts
index 17436762ad2a..87792b659723 100644
--- a/packages/angular_devkit/schematics/src/tree/interface.ts
+++ b/packages/angular_devkit/schematics/src/tree/interface.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import { Path, PathFragment } from '@angular-devkit/core';
+import { JsonValue, Path, PathFragment } from '@angular-devkit/core';
import { Action } from './action';
export enum MergeStrategy {
@@ -94,6 +94,20 @@ export interface Tree {
*/
readText(path: string): string;
+ /**
+ * Reads and parses a file from the Tree as a UTF-8 encoded JSON file.
+ * Supports parsing JSON (RFC 8259) with the following extensions:
+ * * Single-line and multi-line JavaScript comments
+ * * Trailing commas within objects and arrays
+ *
+ * @param path The path of the file to read.
+ * @returns A JsonValue containing the parsed contents of the file.
+ * @throws {@link FileDoesNotExistException} if the file is not found.
+ * @throws An error if the file contains invalid UTF-8 characters.
+ * @throws An error if the file contains malformed JSON.
+ */
+ readJson(path: string): JsonValue;
+
exists(path: string): boolean;
get(path: string): FileEntry | null;
getDir(path: string): DirEntry;
diff --git a/packages/angular_devkit/schematics/src/tree/null.ts b/packages/angular_devkit/schematics/src/tree/null.ts
index 6bc774d45142..cbace2bf3211 100644
--- a/packages/angular_devkit/schematics/src/tree/null.ts
+++ b/packages/angular_devkit/schematics/src/tree/null.ts
@@ -6,7 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
-import { BaseException, Path, PathFragment, dirname, join, normalize } from '@angular-devkit/core';
+import {
+ BaseException,
+ JsonValue,
+ Path,
+ PathFragment,
+ dirname,
+ join,
+ normalize,
+} from '@angular-devkit/core';
import { FileDoesNotExistException } from '../exception/exception';
import { Action } from './action';
import { DirEntry, MergeStrategy, Tree, TreeSymbol, UpdateRecorder } from './interface';
@@ -60,6 +68,9 @@ export class NullTree implements Tree {
readText(path: string): string {
throw new FileDoesNotExistException(path);
}
+ readJson(path: string): JsonValue {
+ throw new FileDoesNotExistException(path);
+ }
get(_path: string) {
return null;
}
diff --git a/packages/angular_devkit/schematics/src/tree/scoped.ts b/packages/angular_devkit/schematics/src/tree/scoped.ts
index 21639cfc30a1..205769204439 100644
--- a/packages/angular_devkit/schematics/src/tree/scoped.ts
+++ b/packages/angular_devkit/schematics/src/tree/scoped.ts
@@ -7,6 +7,7 @@
*/
import {
+ JsonValue,
NormalizedRoot,
Path,
PathFragment,
@@ -116,6 +117,9 @@ export class ScopedTree implements Tree {
readText(path: string): string {
return this._base.readText(this._fullPath(path));
}
+ readJson(path: string): JsonValue {
+ return this._base.readJson(this._fullPath(path));
+ }
exists(path: string): boolean {
return this._base.exists(this._fullPath(path));
}
From b3c42292397b80bb5f196d0d6f48724b4105aa3e Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Tue, 19 Apr 2022 12:02:46 -0400
Subject: [PATCH 3/3] refactor(@schematics/angular): use Tree's newly
introduced `readText` and `readJSON` functionality
Code related to decoding buffers into strings and parsing content into JSON can now be removed by using the
support provided directly from the Tree instance for the executing schematic.
---
.../schematics_cli/blank/factory.ts | 25 +++++++------------
.../schematics/angular/app-shell/index.ts | 13 +++-------
.../schematics/angular/component/index.ts | 6 +----
.../schematics/angular/directive/index.ts | 12 ++-------
packages/schematics/angular/module/index.ts | 12 ++-------
packages/schematics/angular/pipe/index.ts | 12 ++-------
.../angular/service-worker/index.ts | 6 +----
.../schematics/angular/universal/index.ts | 7 +-----
.../schematics/angular/utility/json-file.ts | 7 +-----
.../angular/utility/ng-ast-utils.ts | 12 ++-------
.../schematics/angular/utility/workspace.ts | 9 ++-----
.../schematics/angular/web-worker/index.ts | 2 +-
12 files changed, 28 insertions(+), 95 deletions(-)
diff --git a/packages/angular_devkit/schematics_cli/blank/factory.ts b/packages/angular_devkit/schematics_cli/blank/factory.ts
index d8eeb16cc3d5..6585ea95b59c 100644
--- a/packages/angular_devkit/schematics_cli/blank/factory.ts
+++ b/packages/angular_devkit/schematics_cli/blank/factory.ts
@@ -28,13 +28,9 @@ function addSchematicToCollectionJson(
description: JsonObject,
): Rule {
return (tree: Tree) => {
- const collectionJsonContent = tree.read(collectionPath);
- if (!collectionJsonContent) {
- throw new Error('Invalid collection path: ' + collectionPath);
- }
+ const collectionJson = tree.readJson(collectionPath);
- const collectionJson = JSON.parse(collectionJsonContent.toString());
- if (!isJsonObject(collectionJson.schematics)) {
+ if (!isJsonObject(collectionJson) || !isJsonObject(collectionJson.schematics)) {
throw new Error('Invalid collection.json; schematics needs to be an object.');
}
@@ -55,16 +51,13 @@ export default function (options: Schema): Rule {
let collectionPath: Path | undefined;
try {
- const packageJsonContent = tree.read('/package.json');
- if (packageJsonContent) {
- const packageJson = JSON.parse(packageJsonContent.toString()) as {
- schematics: unknown;
- };
- if (typeof packageJson.schematics === 'string') {
- const p = normalize(packageJson.schematics);
- if (tree.exists(p)) {
- collectionPath = p;
- }
+ const packageJson = tree.readJson('/package.json') as {
+ schematics: unknown;
+ };
+ if (typeof packageJson.schematics === 'string') {
+ const p = normalize(packageJson.schematics);
+ if (tree.exists(p)) {
+ collectionPath = p;
}
}
} catch {}
diff --git a/packages/schematics/angular/app-shell/index.ts b/packages/schematics/angular/app-shell/index.ts
index f7caa44cabcf..0e7e966368e9 100644
--- a/packages/schematics/angular/app-shell/index.ts
+++ b/packages/schematics/angular/app-shell/index.ts
@@ -35,11 +35,7 @@ import { BrowserBuilderOptions, Builders, ServerBuilderOptions } from '../utilit
import { Schema as AppShellOptions } from './schema';
function getSourceFile(host: Tree, path: string): ts.SourceFile {
- const buffer = host.read(path);
- if (!buffer) {
- throw new SchematicsException(`Could not find ${path}.`);
- }
- const content = buffer.toString();
+ const content = host.readText(path);
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
return source;
@@ -82,10 +78,9 @@ function getComponentTemplate(host: Tree, compPath: string, tmplInfo: TemplateIn
const templateUrl = (tmplInfo.templateUrlProp.initializer as ts.StringLiteral).text;
const dir = dirname(normalize(compPath));
const templatePath = join(dir, templateUrl);
- const buffer = host.read(templatePath);
- if (buffer) {
- template = buffer.toString();
- }
+ try {
+ template = host.readText(templatePath);
+ } catch {}
}
return template;
diff --git a/packages/schematics/angular/component/index.ts b/packages/schematics/angular/component/index.ts
index 177b5eac276a..2fbbb7bcb1c0 100644
--- a/packages/schematics/angular/component/index.ts
+++ b/packages/schematics/angular/component/index.ts
@@ -32,11 +32,7 @@ import { buildDefaultPath, getWorkspace } from '../utility/workspace';
import { Schema as ComponentOptions, Style } from './schema';
function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile {
- const text = host.read(modulePath);
- if (text === null) {
- throw new SchematicsException(`File ${modulePath} does not exist.`);
- }
- const sourceText = text.toString('utf-8');
+ const sourceText = host.readText(modulePath);
return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
}
diff --git a/packages/schematics/angular/directive/index.ts b/packages/schematics/angular/directive/index.ts
index 666a181b5b48..4a4a1bdbe8b4 100644
--- a/packages/schematics/angular/directive/index.ts
+++ b/packages/schematics/angular/directive/index.ts
@@ -36,11 +36,7 @@ function addDeclarationToNgModule(options: DirectiveOptions): Rule {
}
const modulePath = options.module;
- const text = host.read(modulePath);
- if (text === null) {
- throw new SchematicsException(`File ${modulePath} does not exist.`);
- }
- const sourceText = text.toString('utf-8');
+ const sourceText = host.readText(modulePath);
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
const directivePath =
@@ -66,11 +62,7 @@ function addDeclarationToNgModule(options: DirectiveOptions): Rule {
if (options.export) {
// Need to refresh the AST because we overwrote the file in the host.
- const text = host.read(modulePath);
- if (text === null) {
- throw new SchematicsException(`File ${modulePath} does not exist.`);
- }
- const sourceText = text.toString('utf-8');
+ const sourceText = host.readText(modulePath);
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
const exportRecorder = host.beginUpdate(modulePath);
diff --git a/packages/schematics/angular/module/index.ts b/packages/schematics/angular/module/index.ts
index 4c6840011c25..6c599c418bce 100644
--- a/packages/schematics/angular/module/index.ts
+++ b/packages/schematics/angular/module/index.ts
@@ -55,11 +55,7 @@ function addDeclarationToNgModule(options: ModuleOptions): Rule {
const modulePath = options.module;
- const text = host.read(modulePath);
- if (text === null) {
- throw new SchematicsException(`File ${modulePath} does not exist.`);
- }
- const sourceText = text.toString();
+ const sourceText = host.readText(modulePath);
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
const relativePath = buildRelativeModulePath(options, modulePath);
@@ -101,12 +97,8 @@ function addRouteDeclarationToNgModule(
path = options.module;
}
- const text = host.read(path);
- if (!text) {
- throw new Error(`Couldn't find the module nor its routing module.`);
- }
+ const sourceText = host.readText(path);
- const sourceText = text.toString();
const addDeclaration = addRouteDeclarationToModule(
ts.createSourceFile(path, sourceText, ts.ScriptTarget.Latest, true),
path,
diff --git a/packages/schematics/angular/pipe/index.ts b/packages/schematics/angular/pipe/index.ts
index e18c432eabc2..e7ab1e7d8e95 100644
--- a/packages/schematics/angular/pipe/index.ts
+++ b/packages/schematics/angular/pipe/index.ts
@@ -35,11 +35,7 @@ function addDeclarationToNgModule(options: PipeOptions): Rule {
}
const modulePath = options.module;
- const text = host.read(modulePath);
- if (text === null) {
- throw new SchematicsException(`File ${modulePath} does not exist.`);
- }
- const sourceText = text.toString('utf-8');
+ const sourceText = host.readText(modulePath);
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
const pipePath =
@@ -63,11 +59,7 @@ function addDeclarationToNgModule(options: PipeOptions): Rule {
host.commitUpdate(recorder);
if (options.export) {
- const text = host.read(modulePath);
- if (text === null) {
- throw new SchematicsException(`File ${modulePath} does not exist.`);
- }
- const sourceText = text.toString('utf-8');
+ const sourceText = host.readText(modulePath);
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
const exportRecorder = host.beginUpdate(modulePath);
diff --git a/packages/schematics/angular/service-worker/index.ts b/packages/schematics/angular/service-worker/index.ts
index 774493083855..f35e47019d5f 100644
--- a/packages/schematics/angular/service-worker/index.ts
+++ b/packages/schematics/angular/service-worker/index.ts
@@ -122,11 +122,7 @@ function updateAppModule(mainPath: string): Rule {
}
function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
- const buffer = host.read(path);
- if (!buffer) {
- throw new SchematicsException(`Could not read file (${path}).`);
- }
- const content = buffer.toString();
+ const content = host.readText(path);
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
return source;
diff --git a/packages/schematics/angular/universal/index.ts b/packages/schematics/angular/universal/index.ts
index 95fe3d26e93f..c660995305bc 100644
--- a/packages/schematics/angular/universal/index.ts
+++ b/packages/schematics/angular/universal/index.ts
@@ -99,12 +99,7 @@ function updateConfigFile(options: UniversalOptions, tsConfigDirectory: Path): R
}
function findBrowserModuleImport(host: Tree, modulePath: string): ts.Node {
- const moduleBuffer = host.read(modulePath);
- if (!moduleBuffer) {
- throw new SchematicsException(`Module file (${modulePath}) not found`);
- }
- const moduleFileText = moduleBuffer.toString('utf-8');
-
+ const moduleFileText = host.readText(modulePath);
const source = ts.createSourceFile(modulePath, moduleFileText, ts.ScriptTarget.Latest, true);
const decoratorMetadata = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0];
diff --git a/packages/schematics/angular/utility/json-file.ts b/packages/schematics/angular/utility/json-file.ts
index 647199ef848d..6bb532416f2e 100644
--- a/packages/schematics/angular/utility/json-file.ts
+++ b/packages/schematics/angular/utility/json-file.ts
@@ -27,12 +27,7 @@ export class JSONFile {
content: string;
constructor(private readonly host: Tree, private readonly path: string) {
- const buffer = this.host.read(this.path);
- if (buffer) {
- this.content = buffer.toString();
- } else {
- throw new Error(`Could not read '${path}'.`);
- }
+ this.content = this.host.readText(this.path);
}
private _jsonAst: Node | undefined;
diff --git a/packages/schematics/angular/utility/ng-ast-utils.ts b/packages/schematics/angular/utility/ng-ast-utils.ts
index e55effeeea17..c0dfb8374b90 100644
--- a/packages/schematics/angular/utility/ng-ast-utils.ts
+++ b/packages/schematics/angular/utility/ng-ast-utils.ts
@@ -13,11 +13,7 @@ import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescri
import { findNode, getSourceNodes } from '../utility/ast-utils';
export function findBootstrapModuleCall(host: Tree, mainPath: string): ts.CallExpression | null {
- const mainBuffer = host.read(mainPath);
- if (!mainBuffer) {
- throw new SchematicsException(`Main file (${mainPath}) not found`);
- }
- const mainText = mainBuffer.toString('utf-8');
+ const mainText = host.readText(mainPath);
const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
const allNodes = getSourceNodes(source);
@@ -58,11 +54,7 @@ export function findBootstrapModulePath(host: Tree, mainPath: string): string {
const bootstrapModule = bootstrapCall.arguments[0];
- const mainBuffer = host.read(mainPath);
- if (!mainBuffer) {
- throw new SchematicsException(`Client application main file (${mainPath}) not found`);
- }
- const mainText = mainBuffer.toString('utf-8');
+ const mainText = host.readText(mainPath);
const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
const allNodes = getSourceNodes(source);
const bootstrapModuleRelativePath = allNodes
diff --git a/packages/schematics/angular/utility/workspace.ts b/packages/schematics/angular/utility/workspace.ts
index 07bf2c9ffc64..9354f4beab14 100644
--- a/packages/schematics/angular/utility/workspace.ts
+++ b/packages/schematics/angular/utility/workspace.ts
@@ -6,19 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
-import { json, virtualFs, workspaces } from '@angular-devkit/core';
+import { json, workspaces } from '@angular-devkit/core';
import { Rule, Tree, noop } from '@angular-devkit/schematics';
import { ProjectType } from './workspace-models';
function createHost(tree: Tree): workspaces.WorkspaceHost {
return {
async readFile(path: string): Promise {
- const data = tree.read(path);
- if (!data) {
- throw new Error('File not found.');
- }
-
- return virtualFs.fileBufferToString(data);
+ return tree.readText(path);
},
async writeFile(path: string, data: string): Promise {
return tree.overwrite(path, data);
diff --git a/packages/schematics/angular/web-worker/index.ts b/packages/schematics/angular/web-worker/index.ts
index bda1d4740887..1e0564045a75 100644
--- a/packages/schematics/angular/web-worker/index.ts
+++ b/packages/schematics/angular/web-worker/index.ts
@@ -65,7 +65,7 @@ function addSnippet(options: WebWorkerOptions): Rule {
`;
// Append the worker creation snippet.
- const originalContent = host.read(siblingModulePath);
+ const originalContent = host.readText(siblingModulePath);
host.overwrite(siblingModulePath, originalContent + '\n' + workerCreationSnippet);
return host;