From 25a6da244c3f2f1583fadf2b1a590c5c5bbd7de6 Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Thu, 1 Dec 2016 13:24:51 -0800 Subject: [PATCH] refactor(compiler-cli): refactor compiler host parameters (#13147) --- modules/@angular/compiler-cli/index.ts | 2 +- modules/@angular/compiler-cli/src/codegen.ts | 8 +-- .../compiler-cli/src/compiler_host.ts | 58 +++++++++++++------ .../@angular/compiler-cli/src/extractor.ts | 8 ++- .../src/path_mapped_compiler_host.ts | 8 +-- .../compiler-cli/test/aot_host_spec.ts | 7 +-- 6 files changed, 56 insertions(+), 35 deletions(-) diff --git a/modules/@angular/compiler-cli/index.ts b/modules/@angular/compiler-cli/index.ts index 89d6383800..b9cd406d96 100644 --- a/modules/@angular/compiler-cli/index.ts +++ b/modules/@angular/compiler-cli/index.ts @@ -7,7 +7,7 @@ */ export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler'; export {CodeGenerator} from './src/codegen'; -export {CompilerHost, CompilerHostContext, NodeCompilerHostContext} from './src/compiler_host'; +export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host'; export {Extractor} from './src/extractor'; export * from '@angular/tsc-wrapped'; diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 8b327f6e53..6c143e7356 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -17,7 +17,7 @@ import {readFileSync} from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; -import {CompilerHost, CompilerHostContext} from './compiler_host'; +import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; import {PathMappedCompilerHost} from './path_mapped_compiler_host'; import {Console} from './private_import_core'; @@ -82,9 +82,9 @@ export class CodeGenerator { ngCompilerHost?: CompilerHost): CodeGenerator { if (!ngCompilerHost) { const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; - ngCompilerHost = usePathMapping ? - new PathMappedCompilerHost(program, tsCompilerHost, options, compilerHostContext) : - new CompilerHost(program, tsCompilerHost, options, compilerHostContext); + const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost); + ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) : + new CompilerHost(program, options, context); } const transFile = cliOptions.i18nFile; const locale = cliOptions.locale; diff --git a/modules/@angular/compiler-cli/src/compiler_host.ts b/modules/@angular/compiler-cli/src/compiler_host.ts index 50ebaab6cf..b20d8aadb5 100644 --- a/modules/@angular/compiler-cli/src/compiler_host.ts +++ b/modules/@angular/compiler-cli/src/compiler_host.ts @@ -17,30 +17,25 @@ const DTS = /\.d\.ts$/; const NODE_MODULES = '/node_modules/'; const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/; -export interface CompilerHostContext { - fileExists(fileName: string): boolean; - directoryExists(directoryName: string): boolean; - readFile(fileName: string): string; +export interface CompilerHostContext extends ts.ModuleResolutionHost { readResource(fileName: string): Promise; assumeFileExists(fileName: string): void; } export class CompilerHost implements AotCompilerHost { protected metadataCollector = new MetadataCollector(); - protected context: CompilerHostContext; private isGenDirChildOfRootDir: boolean; protected basePath: string; private genDir: string; private resolverCache = new Map(); constructor( - protected program: ts.Program, protected compilerHost: ts.CompilerHost, - protected options: AngularCompilerOptions, context?: CompilerHostContext) { + protected program: ts.Program, protected options: AngularCompilerOptions, + protected context: CompilerHostContext) { // normalize the path so that it never ends with '/'. this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/'); this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/'); - this.context = context || new NodeCompilerHostContext(compilerHost); const genPath: string = path.relative(this.basePath, this.genDir); this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); } @@ -81,7 +76,7 @@ export class CompilerHost implements AotCompilerHost { fileNameToModuleName(importedFile: string, containingFile: string): string { // If a file does not yet exist (because we compile it later), we still need to // assume it exists it so that the `resolve` method works! - if (!this.compilerHost.fileExists(importedFile)) { + if (!this.context.fileExists(importedFile)) { this.context.assumeFileExists(importedFile); } @@ -181,8 +176,8 @@ export class CompilerHost implements AotCompilerHost { const metadatas = metadataOrMetadatas ? (Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) : []; - const v1Metadata = metadatas.find(m => m['version'] === 1); - let v2Metadata = metadatas.find(m => m['version'] === 2); + const v1Metadata = metadatas.find((m: any) => m['version'] === 1); + let v2Metadata = metadatas.find((m: any) => m['version'] === 2); if (!v2Metadata && v1Metadata) { // patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file // as the only difference between the versions is whether all exports are contained in @@ -216,15 +211,44 @@ export class CompilerHost implements AotCompilerHost { loadResource(filePath: string): Promise { return this.context.readResource(filePath); } } -export class NodeCompilerHostContext implements CompilerHostContext { - constructor(private host: ts.CompilerHost) {} +export class CompilerHostContextAdapter { + protected assumedExists: {[fileName: string]: boolean} = {}; - private assumedExists: {[fileName: string]: boolean} = {}; + assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; } +} + +export class ModuleResolutionHostAdapter extends CompilerHostContextAdapter implements + CompilerHostContext { + public directoryExists: ((directoryName: string) => boolean)|undefined; + + constructor(private host: ts.ModuleResolutionHost) { + super(); + if (host.directoryExists) { + this.directoryExists = (directoryName: string) => host.directoryExists(directoryName); + } + } fileExists(fileName: string): boolean { return this.assumedExists[fileName] || this.host.fileExists(fileName); } + readFile(fileName: string): string { return this.host.readFile(fileName); } + + readResource(s: string) { + if (!this.host.fileExists(s)) { + // TODO: We should really have a test for error cases like this! + throw new Error(`Compilation failed. Resource file not found: ${s}`); + } + return Promise.resolve(this.host.readFile(s)); + } +} + +export class NodeCompilerHostContext extends CompilerHostContextAdapter implements + CompilerHostContext { + fileExists(fileName: string): boolean { + return this.assumedExists[fileName] || fs.existsSync(fileName); + } + directoryExists(directoryName: string): boolean { try { return fs.statSync(directoryName).isDirectory(); @@ -236,12 +260,10 @@ export class NodeCompilerHostContext implements CompilerHostContext { readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); } readResource(s: string) { - if (!this.host.fileExists(s)) { + if (!this.fileExists(s)) { // TODO: We should really have a test for error cases like this! throw new Error(`Compilation failed. Resource file not found: ${s}`); } - return Promise.resolve(this.host.readFile(s)); + return Promise.resolve(this.readFile(s)); } - - assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; } } diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index bcf7cbecdd..439ce01d50 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -18,7 +18,7 @@ import * as tsc from '@angular/tsc-wrapped'; import * as ts from 'typescript'; import {excludeFilePattern} from './codegen'; -import {CompilerHost} from './compiler_host'; +import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host'; export class Extractor { constructor( @@ -32,8 +32,10 @@ export class Extractor { static create( options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, - tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor { - if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options); + moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor { + if (!ngCompilerHost) + ngCompilerHost = + new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost)); const {extractor: ngExtractor} = compiler.Extractor.create( ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)}); return new Extractor(ngExtractor, ngCompilerHost, program); diff --git a/modules/@angular/compiler-cli/src/path_mapped_compiler_host.ts b/modules/@angular/compiler-cli/src/path_mapped_compiler_host.ts index 2a1af90ea4..6dcbf8cee5 100644 --- a/modules/@angular/compiler-cli/src/path_mapped_compiler_host.ts +++ b/modules/@angular/compiler-cli/src/path_mapped_compiler_host.ts @@ -25,10 +25,8 @@ const DTS = /\.d\.ts$/; * loader what to do. */ export class PathMappedCompilerHost extends CompilerHost { - constructor( - program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions, - context?: CompilerHostContext) { - super(program, compilerHost, options, context); + constructor(program: ts.Program, options: AngularCompilerOptions, context: CompilerHostContext) { + super(program, options, context); } getCanonicalFileName(fileName: string): string { @@ -119,7 +117,7 @@ export class PathMappedCompilerHost extends CompilerHost { getMetadataFor(filePath: string): ModuleMetadata[] { for (const root of this.options.rootDirs || []) { const rootedPath = path.join(root, filePath); - if (!this.compilerHost.fileExists(rootedPath)) { + if (!this.context.fileExists(rootedPath)) { // If the file doesn't exists then we cannot return metadata for the file. // This will occur if the user refernced a declared module for which no file // exists for the module (i.e. jQuery or angularjs). diff --git a/modules/@angular/compiler-cli/test/aot_host_spec.ts b/modules/@angular/compiler-cli/test/aot_host_spec.ts index 0a1f6d3e60..c577d2b9ff 100644 --- a/modules/@angular/compiler-cli/test/aot_host_spec.ts +++ b/modules/@angular/compiler-cli/test/aot_host_spec.ts @@ -14,14 +14,13 @@ import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks'; describe('CompilerHost', () => { let context: MockAotContext; - let host: ts.CompilerHost; let program: ts.Program; let hostNestedGenDir: CompilerHost; let hostSiblingGenDir: CompilerHost; beforeEach(() => { context = new MockAotContext('/tmp/src', clone(FILES)); - host = new MockCompilerHost(context); + const host = new MockCompilerHost(context); program = ts.createProgram( ['main.ts'], { module: ts.ModuleKind.CommonJS, @@ -33,7 +32,7 @@ describe('CompilerHost', () => { throw new Error('Expected no errors'); } hostNestedGenDir = new CompilerHost( - program, host, { + program, { genDir: '/tmp/project/src/gen/', basePath: '/tmp/project/src', skipMetadataEmit: false, @@ -43,7 +42,7 @@ describe('CompilerHost', () => { }, context); hostSiblingGenDir = new CompilerHost( - program, host, { + program, { genDir: '/tmp/project/gen', basePath: '/tmp/project/src/', skipMetadataEmit: false,