From 22b1a0b14cee70c382d4658f10cf0725250373c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4ni?= Date: Thu, 23 Aug 2018 01:21:31 +0200 Subject: [PATCH] fix(@angular-devkit/schematics): fix `generate` mangling files containing wide characters Executing a command like `ng generate component my-component` can sometimes lead to mangled Angular module files when inserting the component into `declaration` and adding the import. This happens if the file contains characters that are wider than one byte e.g. a copyright sign or an umlaut. Today it is expected to be able to use two byte long characters in code. The `UpdateBuffer` class operates using Buffer objects which use byte arrays internally. Using text node positions provided by the TypeScript library, these will not match up. This change looks up the textual position inside the Buffer and uses the correct index. Closes #7851, #7950 --- .../schematics/src/tree/recorder.ts | 6 +++--- .../schematics/src/utility/update-buffer.ts | 20 +++++++++++++++---- .../src/utility/update-buffer_spec.ts | 10 ++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/angular_devkit/schematics/src/tree/recorder.ts b/packages/angular_devkit/schematics/src/tree/recorder.ts index be8e485e6f8b..8b18b8182e55 100644 --- a/packages/angular_devkit/schematics/src/tree/recorder.ts +++ b/packages/angular_devkit/schematics/src/tree/recorder.ts @@ -30,9 +30,9 @@ export class UpdateRecorderBase implements UpdateRecorder { if (c0 == 0xEF && c1 == 0xBB && c2 == 0xBF) { return new UpdateRecorderBom(entry); } else if (c0 === 0xFF && c1 == 0xFE) { - return new UpdateRecorderBom(entry, 2); + return new UpdateRecorderBom(entry); } else if (c0 === 0xFE && c1 == 0xFF) { - return new UpdateRecorderBom(entry, 2); + return new UpdateRecorderBom(entry); } return new UpdateRecorderBase(entry); @@ -70,7 +70,7 @@ export class UpdateRecorderBase implements UpdateRecorder { export class UpdateRecorderBom extends UpdateRecorderBase { - constructor(entry: FileEntry, private _delta = 3) { + constructor(entry: FileEntry, private _delta = 1) { super(entry); } diff --git a/packages/angular_devkit/schematics/src/utility/update-buffer.ts b/packages/angular_devkit/schematics/src/utility/update-buffer.ts index 44a9f620a914..165305ddabae 100644 --- a/packages/angular_devkit/schematics/src/utility/update-buffer.ts +++ b/packages/angular_devkit/schematics/src/utility/update-buffer.ts @@ -201,19 +201,31 @@ export class UpdateBuffer { } protected _slice(start: number): [Chunk, Chunk] { - this._assertIndex(start); + // If start is longer than the content, use start, otherwise determine exact position in string. + const index = start >= this._originalContent.length ? start : this._getTextPosition(start); + + this._assertIndex(index); // Find the chunk by going through the list. - const h = this._linkedList.find(chunk => start <= chunk.end); + const h = this._linkedList.find(chunk => index <= chunk.end); if (!h) { throw Error('Chunk cannot be found.'); } - if (start == h.end && h.next !== null) { + if (index == h.end && h.next !== null) { return [h, h.next]; } - return [h, h.slice(start)]; + return [h, h.slice(index)]; + } + + /** + * Gets the position in the content based on the position in the string. + * Some characters might be wider than one byte, thus we have to determine the position using + * string functions. + */ + protected _getTextPosition(index: number): number { + return Buffer.from(this._originalContent.toString().substring(0, index)).length; } get length(): number { diff --git a/packages/angular_devkit/schematics/src/utility/update-buffer_spec.ts b/packages/angular_devkit/schematics/src/utility/update-buffer_spec.ts index f6e7cc2aa271..64e650feeb29 100644 --- a/packages/angular_devkit/schematics/src/utility/update-buffer_spec.ts +++ b/packages/angular_devkit/schematics/src/utility/update-buffer_spec.ts @@ -58,6 +58,16 @@ describe('UpdateBuffer', () => { mb.insertLeft(6, Buffer.from('Awesome ')); expect(mb.toString()).toBe('Hello Great Awesome Beautiful World'); }); + + it('works with special characters', () => { + const mb = new UpdateBuffer(Buffer.from('Ülaut')); + + mb.insertLeft(1, Buffer.from('m')); + expect(mb.toString()).toBe('Ümlaut'); + + mb.insertLeft(0, Buffer.from('Hello ')); + expect(mb.toString()).toBe('Hello Ümlaut'); + }); }); describe('delete', () => {