Skip to content

Commit 0565ed6

Browse files
kyubisationfilipesilva
authored andcommitted
feat(@angular-devkit/schematics): add UpdateBuffer2 based on magic-string
This PR adds UpdateBuffer2 which should eventually replace UpdateBuffer. UpdateBuffer2 internally uses the magic-string library. UpdateBuffer and related symbols have been marked as deprecated. Closes #21110
1 parent 8a984f7 commit 0565ed6

File tree

9 files changed

+313
-14
lines changed

9 files changed

+313
-14
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,13 +488,13 @@ export class HostSink extends SimpleSinkBase {
488488
// (undocumented)
489489
_done(): Observable<void>;
490490
// (undocumented)
491-
protected _filesToCreate: Map<Path, UpdateBuffer>;
491+
protected _filesToCreate: Map<Path, UpdateBufferBase>;
492492
// (undocumented)
493493
protected _filesToDelete: Set<Path>;
494494
// (undocumented)
495495
protected _filesToRename: Set<[Path, Path]>;
496496
// (undocumented)
497-
protected _filesToUpdate: Map<Path, UpdateBuffer>;
497+
protected _filesToUpdate: Map<Path, UpdateBufferBase>;
498498
// (undocumented)
499499
protected _force: boolean;
500500
// (undocumented)

packages/angular_devkit/schematics/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ ts_library(
4141
"//packages/angular_devkit/core",
4242
"//packages/angular_devkit/core/node", # TODO: get rid of this for 6.0
4343
"@npm//@types/node",
44+
"@npm//magic-string",
4445
"@npm//rxjs",
4546
],
4647
)

packages/angular_devkit/schematics/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"dependencies": {
1616
"@angular-devkit/core": "0.0.0",
1717
"jsonc-parser": "3.0.0",
18+
"magic-string": "0.25.7",
1819
"ora": "5.4.1",
1920
"rxjs": "6.6.7"
2021
}

packages/angular_devkit/schematics/src/sink/host.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ import {
1616
} from 'rxjs';
1717
import { concatMap, reduce } from 'rxjs/operators';
1818
import { CreateFileAction } from '../tree/action';
19-
import { UpdateBuffer } from '../utility/update-buffer';
19+
import { UpdateBufferBase } from '../utility/update-buffer';
2020
import { SimpleSinkBase } from './sink';
2121

2222
export class HostSink extends SimpleSinkBase {
2323
protected _filesToDelete = new Set<Path>();
2424
protected _filesToRename = new Set<[Path, Path]>();
25-
protected _filesToCreate = new Map<Path, UpdateBuffer>();
26-
protected _filesToUpdate = new Map<Path, UpdateBuffer>();
25+
protected _filesToCreate = new Map<Path, UpdateBufferBase>();
26+
protected _filesToUpdate = new Map<Path, UpdateBufferBase>();
2727

2828
constructor(protected _host: virtualFs.Host, protected _force = false) {
2929
super();
@@ -55,12 +55,12 @@ export class HostSink extends SimpleSinkBase {
5555
}
5656

5757
protected _overwriteFile(path: Path, content: Buffer): Observable<void> {
58-
this._filesToUpdate.set(path, new UpdateBuffer(content));
58+
this._filesToUpdate.set(path, UpdateBufferBase.create(content));
5959

6060
return EMPTY;
6161
}
6262
protected _createFile(path: Path, content: Buffer): Observable<void> {
63-
this._filesToCreate.set(path, new UpdateBuffer(content));
63+
this._filesToCreate.set(path, UpdateBufferBase.create(content));
6464

6565
return EMPTY;
6666
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@
77
*/
88

99
import { ContentHasMutatedException } from '../exception/exception';
10-
import { UpdateBuffer } from '../utility/update-buffer';
10+
import { UpdateBufferBase } from '../utility/update-buffer';
1111
import { FileEntry, UpdateRecorder } from './interface';
1212

1313
export class UpdateRecorderBase implements UpdateRecorder {
1414
protected _path: string;
1515
protected _original: Buffer;
16-
protected _content: UpdateBuffer;
16+
protected _content: UpdateBufferBase;
1717

1818
constructor(entry: FileEntry) {
1919
this._original = Buffer.from(entry.content);
20-
this._content = new UpdateBuffer(entry.content);
20+
this._content = UpdateBufferBase.create(entry.content);
2121
this._path = entry.path;
2222
}
2323

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { normalize } from '@angular-devkit/core';
10+
import { UpdateBuffer2, UpdateBufferBase } from '../utility/update-buffer';
1011
import { SimpleFileEntry } from './entry';
1112
import { UpdateRecorderBase, UpdateRecorderBom } from './recorder';
1213

@@ -31,6 +32,23 @@ describe('UpdateRecorderBase', () => {
3132
expect(result.toString()).toBe('Hello beautiful World');
3233
});
3334

35+
it('works with multiple adjacent inserts', () => {
36+
const buffer = Buffer.from('Hello beautiful World');
37+
const entry = new SimpleFileEntry(normalize('/some/path'), buffer);
38+
39+
// TODO: Remove once UpdateBufferBase.create defaults to UpdateBuffer2
40+
spyOn(UpdateBufferBase, 'create').and.callFake(
41+
(originalContent) => new UpdateBuffer2(originalContent),
42+
);
43+
44+
const recorder = new UpdateRecorderBase(entry);
45+
recorder.remove(6, 9);
46+
recorder.insertRight(6, 'amazing');
47+
recorder.insertRight(15, ' and fantastic');
48+
const result = recorder.apply(buffer);
49+
expect(result.toString()).toBe('Hello amazing and fantastic World');
50+
});
51+
3452
it('can create the proper recorder', () => {
3553
const e = new SimpleFileEntry(normalize('/some/path'), Buffer.from('hello'));
3654
expect(UpdateRecorderBase.createFromFileEntry(e) instanceof UpdateRecorderBase).toBe(true);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
function isEnabled(variable: string): boolean {
10+
return variable === '1' || variable.toLowerCase() === 'true';
11+
}
12+
13+
function isPresent(variable: string | undefined): variable is string {
14+
return typeof variable === 'string' && variable !== '';
15+
}
16+
17+
// Use UpdateBuffer2, which uses magic-string internally.
18+
// TODO: Switch this for the next major release to use UpdateBuffer2 by default.
19+
const updateBufferV2 = process.env['NG_UPDATE_BUFFER_V2'];
20+
export const updateBufferV2Enabled = isPresent(updateBufferV2) && isEnabled(updateBufferV2);

packages/angular_devkit/schematics/src/utility/update-buffer.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
*/
88

99
import { BaseException } from '@angular-devkit/core';
10+
import MagicString from 'magic-string';
11+
import { updateBufferV2Enabled } from './environment-options';
1012
import { LinkedList } from './linked-list';
1113

1214
export class IndexOutOfBoundException extends BaseException {
1315
constructor(index: number, min: number, max = Infinity) {
1416
super(`Index ${index} outside of range [${min}, ${max}].`);
1517
}
1618
}
19+
/** @deprecated Since v13.0 */
1720
export class ContentCannotBeRemovedException extends BaseException {
1821
constructor() {
1922
super(`User tried to remove content that was marked essential.`);
@@ -26,6 +29,7 @@ export class ContentCannotBeRemovedException extends BaseException {
2629
* it means the content itself was deleted.
2730
*
2831
* @see UpdateBuffer
32+
* @deprecated Since v13.0
2933
*/
3034
export class Chunk {
3135
private _content: Buffer | null;
@@ -176,6 +180,37 @@ export class Chunk {
176180
}
177181
}
178182

183+
/**
184+
* Base class for an update buffer implementation that allows buffers to be inserted to the _right
185+
* or _left, or deleted, while keeping indices to the original buffer.
186+
*/
187+
export abstract class UpdateBufferBase {
188+
constructor(protected _originalContent: Buffer) {}
189+
abstract get length(): number;
190+
abstract get original(): Buffer;
191+
abstract toString(encoding?: string): string;
192+
abstract generate(): Buffer;
193+
abstract insertLeft(index: number, content: Buffer, assert?: boolean): void;
194+
abstract insertRight(index: number, content: Buffer, assert?: boolean): void;
195+
abstract remove(index: number, length: number): void;
196+
197+
/**
198+
* Creates an UpdateBufferBase instance. Depending on the NG_UPDATE_BUFFER_V2
199+
* environment variable, will either create an UpdateBuffer or an UpdateBuffer2
200+
* instance.
201+
*
202+
* See: https://github.com/angular/angular-cli/issues/21110
203+
*
204+
* @param originalContent The original content of the update buffer instance.
205+
* @returns An UpdateBufferBase instance.
206+
*/
207+
static create(originalContent: Buffer): UpdateBufferBase {
208+
return updateBufferV2Enabled
209+
? new UpdateBuffer2(originalContent)
210+
: new UpdateBuffer(originalContent);
211+
}
212+
}
213+
179214
/**
180215
* An utility class that allows buffers to be inserted to the _right or _left, or deleted, while
181216
* keeping indices to the original buffer.
@@ -185,12 +220,15 @@ export class Chunk {
185220
*
186221
* Since the Node Buffer structure is non-destructive when slicing, we try to use slicing to create
187222
* new chunks, and always keep chunks pointing to the original content.
223+
*
224+
* @deprecated Since v13.0
188225
*/
189-
export class UpdateBuffer {
226+
export class UpdateBuffer extends UpdateBufferBase {
190227
protected _linkedList: LinkedList<Chunk>;
191228

192-
constructor(protected _originalContent: Buffer) {
193-
this._linkedList = new LinkedList(new Chunk(0, _originalContent.length, _originalContent));
229+
constructor(originalContent: Buffer) {
230+
super(originalContent);
231+
this._linkedList = new LinkedList(new Chunk(0, originalContent.length, originalContent));
194232
}
195233

196234
protected _assertIndex(index: number) {
@@ -274,3 +312,47 @@ export class UpdateBuffer {
274312
}
275313
}
276314
}
315+
316+
/**
317+
* An utility class that allows buffers to be inserted to the _right or _left, or deleted, while
318+
* keeping indices to the original buffer.
319+
*/
320+
export class UpdateBuffer2 extends UpdateBufferBase {
321+
protected _mutatableContent: MagicString = new MagicString(this._originalContent.toString());
322+
323+
protected _assertIndex(index: number) {
324+
if (index < 0 || index > this._originalContent.length) {
325+
throw new IndexOutOfBoundException(index, 0, this._originalContent.length);
326+
}
327+
}
328+
329+
get length(): number {
330+
return this._mutatableContent.length();
331+
}
332+
get original(): Buffer {
333+
return this._originalContent;
334+
}
335+
336+
toString(): string {
337+
return this._mutatableContent.toString();
338+
}
339+
340+
generate(): Buffer {
341+
return Buffer.from(this.toString());
342+
}
343+
344+
insertLeft(index: number, content: Buffer): void {
345+
this._assertIndex(index);
346+
this._mutatableContent.appendLeft(index, content.toString());
347+
}
348+
349+
insertRight(index: number, content: Buffer): void {
350+
this._assertIndex(index);
351+
this._mutatableContent.appendRight(index, content.toString());
352+
}
353+
354+
remove(index: number, length: number) {
355+
this._assertIndex(index);
356+
this._mutatableContent.remove(index, index + length);
357+
}
358+
}

0 commit comments

Comments
 (0)