diff --git a/packages/angular_devkit/schematics/src/tree/recorder.ts b/packages/angular_devkit/schematics/src/tree/recorder.ts index 40829dd46e..da59b6dbea 100644 --- a/packages/angular_devkit/schematics/src/tree/recorder.ts +++ b/packages/angular_devkit/schematics/src/tree/recorder.ts @@ -21,6 +21,23 @@ export class UpdateRecorderBase implements UpdateRecorder { this._path = entry.path; } + static createFromFileEntry(entry: FileEntry): UpdateRecorderBase { + const c0 = entry.content.readUInt8(0, true); + const c1 = entry.content.readUInt8(1, true); + const c2 = entry.content.readUInt8(2, true); + + // Check if we're BOM. + if (c0 == 0xEF && c1 == 0xBB && c2 == 0xBF) { + return new UpdateRecorderBom(entry); + } else if (c0 === 0xFF && c1 == 0xFE) { + return new UpdateRecorderBom(entry, 2); + } else if (c0 === 0xFE && c1 == 0xFF) { + return new UpdateRecorderBom(entry, 2); + } + + return new UpdateRecorderBase(entry); + } + get path() { return this._path; } // These just record changes. @@ -50,3 +67,22 @@ export class UpdateRecorderBase implements UpdateRecorder { return this._content.generate(); } } + + +export class UpdateRecorderBom extends UpdateRecorderBase { + constructor(entry: FileEntry, private _delta = 3) { + super(entry); + } + + insertLeft(index: number, content: Buffer | string) { + return super.insertLeft(index + this._delta, content); + } + + insertRight(index: number, content: Buffer | string) { + return super.insertRight(index + this._delta, content); + } + + remove(index: number, length: number) { + return super.remove(index + this._delta, length); + } +} diff --git a/packages/angular_devkit/schematics/src/tree/recorder_spec.ts b/packages/angular_devkit/schematics/src/tree/recorder_spec.ts new file mode 100644 index 0000000000..d35f561fa9 --- /dev/null +++ b/packages/angular_devkit/schematics/src/tree/recorder_spec.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { normalize } from '@angular-devkit/core'; +import { SimpleFileEntry } from './entry'; +import { UpdateRecorderBase, UpdateRecorderBom } from './recorder'; + +describe('UpdateRecorderBase', () => { + it('works for simple files', () => { + const buffer = new Buffer('Hello World'); + const entry = new SimpleFileEntry(normalize('/some/path'), buffer); + + const recorder = new UpdateRecorderBase(entry); + recorder.insertLeft(5, ' beautiful'); + const result = recorder.apply(buffer); + expect(result.toString()).toBe('Hello beautiful World'); + }); + + it('works for simple files (2)', () => { + const buffer = new Buffer('Hello World'); + const entry = new SimpleFileEntry(normalize('/some/path'), buffer); + + const recorder = new UpdateRecorderBase(entry); + recorder.insertRight(5, ' beautiful'); + const result = recorder.apply(buffer); + expect(result.toString()).toBe('Hello beautiful World'); + }); + + it('can create the proper recorder', () => { + const e = new SimpleFileEntry(normalize('/some/path'), new Buffer('hello')); + expect(UpdateRecorderBase.createFromFileEntry(e) instanceof UpdateRecorderBase).toBe(true); + expect(UpdateRecorderBase.createFromFileEntry(e) instanceof UpdateRecorderBom).toBe(false); + }); + + it('can create the proper recorder (bom)', () => { + const eBom = new SimpleFileEntry(normalize('/some/path'), new Buffer('\uFEFFhello')); + expect(UpdateRecorderBase.createFromFileEntry(eBom) instanceof UpdateRecorderBase).toBe(true); + expect(UpdateRecorderBase.createFromFileEntry(eBom) instanceof UpdateRecorderBom).toBe(true); + }); + + it('supports empty files', () => { + const e = new SimpleFileEntry(normalize('/some/path'), new Buffer('')); + expect(UpdateRecorderBase.createFromFileEntry(e) instanceof UpdateRecorderBase).toBe(true); + }); + + it('supports empty files (bom)', () => { + const eBom = new SimpleFileEntry(normalize('/some/path'), new Buffer('\uFEFF')); + expect(UpdateRecorderBase.createFromFileEntry(eBom) instanceof UpdateRecorderBase).toBe(true); + }); +}); + +describe('UpdateRecorderBom', () => { + it('works for simple files', () => { + const buffer = new Buffer('\uFEFFHello World'); + const entry = new SimpleFileEntry(normalize('/some/path'), buffer); + + const recorder = new UpdateRecorderBom(entry); + recorder.insertLeft(5, ' beautiful'); + const result = recorder.apply(buffer); + expect(result.toString()).toBe('\uFEFFHello beautiful World'); + }); +}); diff --git a/packages/angular_devkit/schematics/src/tree/virtual.ts b/packages/angular_devkit/schematics/src/tree/virtual.ts index 24d58f7916..7928958fe5 100644 --- a/packages/angular_devkit/schematics/src/tree/virtual.ts +++ b/packages/angular_devkit/schematics/src/tree/virtual.ts @@ -186,7 +186,7 @@ export class VirtualTree implements Tree { throw new FileDoesNotExistException(path); } - return new UpdateRecorderBase(entry); + return UpdateRecorderBase.createFromFileEntry(entry); } commitUpdate(record: UpdateRecorder) {