refactor(core): add `createMigrationCompilerHost` (#32827)

Current we need to create and override certain compiler host methods in every schematic because schematics use a virtual fs. We this change we extract this logic to a common util.

PR Close #32827
This commit is contained in:
Alan 2019-09-24 12:19:23 +02:00 committed by Alex Rickabaugh
parent 60047037a3
commit 01677b21b6
9 changed files with 52 additions and 108 deletions

View File

@ -11,10 +11,13 @@ import {dirname, relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {identifyDynamicQueryNodes, removeOptionsParameter, removeStaticFlag} from './util';
/**
* Runs the dynamic queries migration for all TypeScript projects in the current CLI workspace.
*/
@ -39,20 +42,7 @@ export default function(): Rule {
function runDynamicQueryMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run the migration for multiple tsconfig files which have intersecting
// source files, it can end up updating query definitions multiple times.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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 host = createMigrationCompilerHost(tree, parsed.options, basePath);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles().filter(

View File

@ -9,10 +9,9 @@
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
import {dirname, relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {NgModuleCollector} from './module_collector';
import {MissingInjectableTransform} from './transform';
import {UpdateRecorder} from './update_recorder';
@ -49,20 +48,9 @@ export default function(): Rule {
function runMissingInjectableMigration(
tree: Tree, tsconfigPath: string, basePath: string): string[] {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
const host = createMigrationCompilerHost(tree, parsed.options, basePath);
const failures: string[] = [];
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// Strip BOM because TypeScript respects this character and it ultimately
// results in shifted offsets since the CLI UpdateRecorder tries to
// automatically account for the BOM character.
// https://github.com/angular/angular-cli/issues/14558
return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined;
};
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const moduleCollector = new NgModuleCollector(typeChecker);

View File

@ -11,11 +11,14 @@ import {dirname, relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {COMMON_IMPORT, DOCUMENT_TOKEN_NAME, DocumentImportVisitor, ResolvedDocumentImport} from './document_import_visitor';
import {addToImport, createImport, removeFromImport} from './move-import';
/** Entry point for the V8 move-document migration. */
export default function(): Rule {
return (tree: Tree) => {
@ -40,19 +43,7 @@ export default function(): Rule {
*/
function runMoveDocumentMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run the migration for multiple tsconfig files which have intersecting
// source files, it can end up updating query definitions multiple times.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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 host = createMigrationCompilerHost(tree, parsed.options, basePath);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();

View File

@ -11,6 +11,7 @@ import {dirname, relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {HelperFunction, getHelper} from './helpers';
@ -18,6 +19,7 @@ import {migrateExpression, replaceImport} from './migration';
import {findCoreImport, findRendererReferences} from './util';
/**
* Migration that switches from `Renderer` to `Renderer2`. More information on how it works:
* https://hackmd.angular.io/UTzUZTnPRA-cSa_4mHyfYw
@ -46,17 +48,7 @@ export default function(): Rule {
function runRendererToRenderer2Migration(tree: Tree, tsconfigPath: string, basePath: string) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run the migration for multiple tsconfig files which have intersecting
// 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;
};
const host = createMigrationCompilerHost(tree, parsed.options, basePath);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();

View File

@ -14,6 +14,7 @@ import * as ts from 'typescript';
import {NgComponentTemplateVisitor} from '../../utils/ng_component_template';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {NgQueryResolveVisitor} from './angular/ng_query_visitor';
@ -117,20 +118,7 @@ function analyzeProject(
logger: logging.LoggerApi):
AnalyzedProject|null {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run the migration for multiple tsconfig files which have intersecting
// source files, it can end up updating query definitions multiple times.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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 host = createMigrationCompilerHost(tree, parsed.options, basePath);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const syntacticDiagnostics = program.getSyntacticDiagnostics();

View File

@ -13,6 +13,7 @@ import * as ts from 'typescript';
import {NgComponentTemplateVisitor} from '../../utils/ng_component_template';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {analyzeResolvedTemplate} from './analyze_template';
@ -47,18 +48,7 @@ export default function(): Rule {
function runTemplateVariableAssignmentCheck(
tree: Tree, tsconfigPath: string, basePath: string, logger: Logger) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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 host = createMigrationCompilerHost(tree, parsed.options, basePath);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const templateVisitor = new NgComponentTemplateVisitor(typeChecker);

View File

@ -9,8 +9,8 @@
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
import {dirname, relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {FALLBACK_DECORATOR, addImport, getNamedImports, getUndecoratedClassesWithDecoratedFields, hasNamedImport} from './utils';
@ -44,20 +44,7 @@ export default function(): Rule {
function runUndecoratedClassesMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run the migration for multiple tsconfig files which have intersecting
// source files, it can end up updating them multiple times.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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 host = createMigrationCompilerHost(tree, parsed.options, basePath);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();

View File

@ -9,12 +9,14 @@
import {logging} from '@angular-devkit/core';
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
import {AotCompiler} from '@angular/compiler';
import {createCompilerHost} from '@angular/compiler-cli';
import {PartialEvaluator} from '@angular/compiler-cli/src/ngtsc/partial_evaluator';
import {TypeScriptReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection';
import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {createNgcProgram} from './create_ngc_program';
import {NgDeclarationCollector} from './ng_declaration_collector';
@ -146,21 +148,8 @@ function gracefullyCreateProgram(
tree: Tree, basePath: string, tsconfigPath: string,
logger: logging.LoggerApi): {compiler: AotCompiler, program: ts.Program}|null {
try {
const {ngcProgram, host, program, compiler} = createNgcProgram((options) => {
const host = ts.createCompilerHost(options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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;
};
return host;
}, tsconfigPath);
const {ngcProgram, host, program, compiler} = createNgcProgram(
(options) => createMigrationCompilerHost(tree, options, basePath), tsconfigPath);
const syntacticDiagnostics = ngcProgram.getTsSyntacticDiagnostics();
const structuralDiagnostics = ngcProgram.getNgStructuralDiagnostics();

View File

@ -0,0 +1,29 @@
/**
* @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 {Tree} from '@angular-devkit/schematics';
import {relative} from 'path';
import * as ts from 'typescript';
export function createMigrationCompilerHost(
tree: Tree, options: ts.CompilerOptions, basePath: string): ts.CompilerHost {
const host = ts.createCompilerHost(options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run multiple migrations we might have intersecting changes and
// source files.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// 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;
};
return host;
}