diff --git a/packages/core/schematics/migrations/injectable-pipe/index.ts b/packages/core/schematics/migrations/injectable-pipe/index.ts index 5c4615bd90..51c088677b 100644 --- a/packages/core/schematics/migrations/injectable-pipe/index.ts +++ b/packages/core/schematics/migrations/injectable-pipe/index.ts @@ -47,7 +47,10 @@ function runInjectablePipeMigration(tree: Tree, tsconfigPath: string, basePath: // source files, it can end up updating query definitions multiple times. host.readFile = fileName => { const buffer = tree.read(relative(basePath, fileName)); - return buffer ? buffer.toString() : undefined; + // Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which + // which breaks the CLI UpdateRecorder. + // See: https://github.com/angular/angular/pull/30719 + return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined; }; const program = ts.createProgram(parsed.fileNames, parsed.options, host); diff --git a/packages/core/schematics/migrations/move-document/index.ts b/packages/core/schematics/migrations/move-document/index.ts index ffb2a59079..30d0cc5597 100644 --- a/packages/core/schematics/migrations/move-document/index.ts +++ b/packages/core/schematics/migrations/move-document/index.ts @@ -48,7 +48,10 @@ function runMoveDocumentMigration(tree: Tree, tsconfigPath: string, basePath: st // source files, it can end up updating query definitions multiple times. host.readFile = fileName => { const buffer = tree.read(relative(basePath, fileName)); - return buffer ? buffer.toString() : undefined; + // Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which + // which breaks the CLI UpdateRecorder. + // See: https://github.com/angular/angular/pull/30719 + return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined; }; const program = ts.createProgram(parsed.fileNames, parsed.options, host); diff --git a/packages/core/schematics/migrations/static-queries/index.ts b/packages/core/schematics/migrations/static-queries/index.ts index a12f1c06ba..207531bbeb 100644 --- a/packages/core/schematics/migrations/static-queries/index.ts +++ b/packages/core/schematics/migrations/static-queries/index.ts @@ -125,7 +125,10 @@ function analyzeProject( // source files, it can end up updating query definitions multiple times. host.readFile = fileName => { const buffer = tree.read(relative(basePath, fileName)); - return buffer ? buffer.toString() : undefined; + // Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which + // which breaks the CLI UpdateRecorder. + // See: https://github.com/angular/angular/pull/30719 + return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined; }; const program = ts.createProgram(parsed.fileNames, parsed.options, host); diff --git a/packages/core/schematics/migrations/template-var-assignment/index.ts b/packages/core/schematics/migrations/template-var-assignment/index.ts index e6b0ddef48..a7fce2081b 100644 --- a/packages/core/schematics/migrations/template-var-assignment/index.ts +++ b/packages/core/schematics/migrations/template-var-assignment/index.ts @@ -53,7 +53,10 @@ function runTemplateVariableAssignmentCheck( // program to be based on the file contents in the virtual file tree. host.readFile = fileName => { const buffer = tree.read(relative(basePath, fileName)); - return buffer ? buffer.toString() : undefined; + // Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which + // which breaks the CLI UpdateRecorder. + // See: https://github.com/angular/angular/pull/30719 + return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined; }; const program = ts.createProgram(parsed.fileNames, parsed.options, host); diff --git a/packages/core/schematics/test/injectable_pipe_migration_spec.ts b/packages/core/schematics/test/injectable_pipe_migration_spec.ts index be238ed894..a07732b4e9 100644 --- a/packages/core/schematics/test/injectable_pipe_migration_spec.ts +++ b/packages/core/schematics/test/injectable_pipe_migration_spec.ts @@ -60,7 +60,21 @@ describe('injectable pipe migration', () => { .toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/); }); - it('should add an import for Injectable to the @angular/core import declaration', async() => { + it('should add @Injectable to pipes that do not have it (BOM)', () => { + writeFile('/index.ts', `\uFEFF + import { Pipe } from '@angular/core'; + + @Pipe({ name: 'myPipe' }) + export class MyPipe { + } + `); + + runMigration(); + expect(tree.readContent('/index.ts')) + .toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/); + }); + + it('should add an import for Injectable to the @angular/core import declaration', () => { writeFile('/index.ts', ` import { Pipe } from '@angular/core'; diff --git a/packages/core/schematics/test/move_document_migration_spec.ts b/packages/core/schematics/test/move_document_migration_spec.ts index f7093e6785..066f04a122 100644 --- a/packages/core/schematics/test/move_document_migration_spec.ts +++ b/packages/core/schematics/test/move_document_migration_spec.ts @@ -60,7 +60,20 @@ describe('move-document migration', () => { expect(content).not.toContain(`import {DOCUMENT} from '@angular/platform-browser';`); }); - it('should properly apply import replacement with existing import', async() => { + it('should properly apply import replacement (BOM)', () => { + writeFile('/index.ts', `\uFEFF + import {DOCUMENT} from '@angular/platform-browser'; + `); + + runMigration(); + + const content = tree.readContent('/index.ts'); + + expect(content).toContain(`import { DOCUMENT } from "@angular/common";`); + expect(content).not.toContain(`import {DOCUMENT} from '@angular/platform-browser';`); + }); + + it('should properly apply import replacement with existing import', () => { writeFile('/index.ts', ` import {DOCUMENT} from '@angular/platform-browser'; import {someImport} from '@angular/common'; diff --git a/packages/core/schematics/test/static_queries_migration_template_spec.ts b/packages/core/schematics/test/static_queries_migration_template_spec.ts index bafd77b11f..89956ce4dc 100644 --- a/packages/core/schematics/test/static_queries_migration_template_spec.ts +++ b/packages/core/schematics/test/static_queries_migration_template_spec.ts @@ -158,6 +158,29 @@ describe('static-queries migration with template strategy', () => { .toContain(`@ViewChild('myTmpl', { static: true }) query: any;`); }); + it('should detect queries selecting ng-template as static (BOM)', async() => { + writeFile('/index.ts', `\uFEFF + import {Component, NgModule, ViewChild} from '@angular/core'; + + @Component({template: \` + + My template + + \`}) + export class MyComp { + private @ViewChild('myTmpl') query: any; + } + + @NgModule({declarations: [MyComp]}) + export class MyModule {} + `); + + await runMigration(); + + expect(tree.readContent('/index.ts')) + .toContain(`@ViewChild('myTmpl', { static: true }) query: any;`); + }); + it('should detect queries selecting component view providers through string token', async() => { writeFile('/index.ts', ` import {Component, Directive, NgModule, ViewChild} from '@angular/core'; diff --git a/packages/core/schematics/test/static_queries_migration_usage_spec.ts b/packages/core/schematics/test/static_queries_migration_usage_spec.ts index c1e065276f..02946c884d 100644 --- a/packages/core/schematics/test/static_queries_migration_usage_spec.ts +++ b/packages/core/schematics/test/static_queries_migration_usage_spec.ts @@ -127,6 +127,26 @@ describe('static-queries migration with usage strategy', () => { .toContain(`@ContentChild('test', { static: false }) query: any;`); }); + it('should not mark content queries used in "ngAfterContentInit" as static (BOM)', async() => { + writeFile('/index.ts', `\uFEFF + import {Component, ContentChild} from '@angular/core'; + + @Component({template: ''}) + export class MyComp { + @ContentChild('test') query: any; + + ngAfterContentInit() { + this.query.classList.add('test'); + } + } + `); + + await runMigration(); + + expect(tree.readContent('/index.ts')) + .toContain(`@ContentChild('test', { static: false }) query: any;`); + }); + it('should not mark content queries used in "ngAfterContentChecked" as static', async() => { writeFile('/index.ts', ` import {Component, ContentChild} from '@angular/core';