From 8d45fefc313aebd0db7b320a1d324c2d4bebd268 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 20 Oct 2017 09:46:41 -0700 Subject: [PATCH] refactor(compiler): remove old ngtools api and add listLazyRoutes to new api (#19836) Usages of `NgTools_InternalApi_NG_2` from `@angular/compiler-cli` will now throw an error. Adds `listLazyRoutes` to `@angular/compiler-cli/ngtools2.ts` for getting the lazy routes of a `ng.Program`. PR Close #19836 --- aio/package.json | 4 +- packages/compiler-cli/index.ts | 8 +- .../integrationtest/test/test_ngtools_api.ts | 151 ------ .../integrationtest/test/test_summaries.ts | 126 ----- .../integrationtest/tsconfig-build.json | 1 - .../integrationtest/tsconfig-xi18n.json | 1 - packages/compiler-cli/src/codegen.ts | 117 ----- packages/compiler-cli/src/compiler_host.ts | 494 ------------------ packages/compiler-cli/src/extractor.ts | 64 --- .../compiler-cli/src/language_services.ts | 3 +- packages/compiler-cli/src/main.ts | 17 +- packages/compiler-cli/src/ngtools_api.ts | 108 ++-- packages/compiler-cli/src/ngtools_api2.ts | 7 + packages/compiler-cli/src/ngtools_impl.ts | 222 -------- .../src/path_mapped_compiler_host.ts | 142 ----- packages/compiler-cli/src/transformers/api.ts | 14 + .../src/transformers/compiler_host.ts | 129 ++++- .../src/transformers/metadata_reader.ts | 128 +++++ .../compiler-cli/src/transformers/program.ts | 108 ++-- .../compiler-cli/src/transformers/util.ts | 1 + packages/compiler-cli/test/aot_host_spec.ts | 310 ----------- .../expression_diagnostics_spec.ts | 6 +- .../compiler-cli/test/diagnostics/mocks.ts | 7 +- .../test/diagnostics/symbol_query_spec.ts | 5 +- packages/compiler-cli/test/mocks.ts | 3 +- .../compiler-cli/test/ngtools_api_spec.ts | 90 ++++ .../test/transformers/metadata_reader_spec.ts | 177 +++++++ .../test/transformers/program_spec.ts | 285 +++++++++- packages/compiler/src/aot/compiler.ts | 50 +- packages/compiler/src/aot/compiler_factory.ts | 11 +- packages/compiler/src/aot/compiler_host.ts | 7 + packages/compiler/src/aot/lazy_routes.ts | 62 +++ packages/compiler/src/aot/static_reflector.ts | 7 +- .../src/aot/static_symbol_resolver.ts | 21 +- packages/compiler/src/compiler.ts | 1 + packages/compiler/src/core.ts | 5 + .../test/aot/static_symbol_resolver_spec.ts | 4 - packages/compiler/test/aot/test_util.ts | 2 +- .../language-service/src/reflector_host.ts | 63 ++- scripts/ci/env.sh | 2 +- scripts/ci/offline_compiler_test.sh | 2 - 41 files changed, 1128 insertions(+), 1837 deletions(-) delete mode 100644 packages/compiler-cli/integrationtest/test/test_summaries.ts delete mode 100644 packages/compiler-cli/src/codegen.ts delete mode 100644 packages/compiler-cli/src/compiler_host.ts delete mode 100644 packages/compiler-cli/src/extractor.ts delete mode 100644 packages/compiler-cli/src/ngtools_impl.ts delete mode 100644 packages/compiler-cli/src/path_mapped_compiler_host.ts create mode 100644 packages/compiler-cli/src/transformers/metadata_reader.ts delete mode 100644 packages/compiler-cli/test/aot_host_spec.ts create mode 100644 packages/compiler-cli/test/ngtools_api_spec.ts create mode 100644 packages/compiler-cli/test/transformers/metadata_reader_spec.ts create mode 100644 packages/compiler/src/aot/lazy_routes.ts diff --git a/aio/package.json b/aio/package.json index eb9cfdf5c6..c78c4b92a7 100644 --- a/aio/package.json +++ b/aio/package.json @@ -7,7 +7,7 @@ "license": "MIT", "scripts": { "aio-use-local": "node tools/ng-packages-installer overwrite . --debug --ignore-packages @angular/service-worker", - "aio-use-npm": "node tools/ng-packages-installer restore .", + "aio-use-npm": "node tools/ng-packages-installer restore . && yarn upgrade @angular/cli@1.3.0", "aio-check-local": "node tools/ng-packages-installer check .", "ng": "yarn check-env && ng", "start": "yarn check-env && ng serve", @@ -23,7 +23,7 @@ "setup": "yarn aio-use-npm && yarn example-use-npm", "postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn generate-plunkers && yarn generate-zips && yarn docs", "presetup-local": "yarn presetup", - "setup-local": "yarn aio-use-local && yarn example-use-local", + "setup-local": "yarn aio-use-local && yarn upgrade @angular/cli@1.5.0-rc.2 && yarn example-use-local", "postsetup-local": "yarn postsetup", "pretest-pwa-score-localhost": "yarn build", "test-pwa-score-localhost": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score http://localhost:4200 90\"", diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts index 1409d067f7..a4c9e99f77 100644 --- a/packages/compiler-cli/index.ts +++ b/packages/compiler-cli/index.ts @@ -6,13 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler'; -export {CodeGenerator} from './src/codegen'; -export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host'; export {DiagnosticTemplateInfo, getExpressionScope, getTemplateExpressionDiagnostics} from './src/diagnostics/expression_diagnostics'; export {AstType, ExpressionDiagnosticsContext} from './src/diagnostics/expression_type'; export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './src/diagnostics/symbols'; export {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './src/diagnostics/typescript_symbols'; -export {Extractor} from './src/extractor'; export {VERSION} from './src/version'; export * from './src/metadata'; @@ -21,8 +18,7 @@ export * from './src/transformers/entry_points'; export * from './src/perform_compile'; -// TODO(tbosch): remove this once everyone is on transformers +// TODO(tbosch): remove this once cli 1.5 is fully released, +// and usages in G3 are changed to `CompilerOptions`. export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'; - -// TODO(hansl): moving to Angular 4 need to update this API. export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'; diff --git a/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts b/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts index e08fe91c43..0c7a2f9880 100644 --- a/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts +++ b/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts @@ -15,8 +15,6 @@ import * as ts from 'typescript'; import * as assert from 'assert'; import {__NGTOOLS_PRIVATE_API_2, readConfiguration} from '@angular/compiler-cli'; -const glob = require('glob'); - /* tslint:disable:no-console */ /** * Main method. @@ -27,9 +25,6 @@ function main() { console.log(`testing ngtools API...`); Promise.resolve() - .then(() => codeGenTest()) - .then(() => codeGenTest(true)) - .then(() => i18nTest()) .then(() => lazyRoutesTest()) .then(() => { console.log('All done!'); @@ -42,152 +37,6 @@ function main() { }); } -function codeGenTest(forceError = false) { - const basePath = path.join(__dirname, '../ngtools_src'); - const srcPath = path.join(__dirname, '../src'); - const project = path.join(basePath, 'tsconfig-build.json'); - const readResources: string[] = []; - const wroteFiles: string[] = []; - - const config = readConfiguration(project); - const delegateHost = ts.createCompilerHost(config.options, true); - const host: ts.CompilerHost = Object.assign( - {}, delegateHost, - {writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }}); - const program = ts.createProgram(config.rootNames, config.options, host); - - config.options.basePath = basePath; - - console.log(`>>> running codegen for ${project}`); - if (forceError) { - console.log(`>>> asserting that missingTranslation param with error value throws`); - } - return __NGTOOLS_PRIVATE_API_2 - .codeGen({ - basePath, - compilerOptions: config.options, program, host, - - angularCompilerOptions: config.options, - - // i18n options. - i18nFormat: 'xlf', - i18nFile: path.join(srcPath, 'messages.fi.xlf'), - locale: 'fi', - missingTranslation: forceError ? 'error' : 'ignore', - - readResource: (fileName: string) => { - readResources.push(fileName); - if (!host.fileExists(fileName)) { - throw new Error(`Compilation failed. Resource file not found: ${fileName}`); - } - return Promise.resolve(host.readFile(fileName)); - } - }) - .then(() => { - console.log(`>>> codegen done, asserting read and wrote files`); - - // Assert for each file that it has been read and each `ts` has a written file associated. - const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true}); - - allFiles.forEach((fileName: string) => { - // Skip tsconfig. - if (fileName.match(/tsconfig-build.json$/)) { - return; - } - - // Assert that file was read. - if (fileName.match(/\.module\.ts$/)) { - const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts'); - assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`); - } else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) { - assert( - readResources.indexOf(fileName) != -1, - `Expected resource "${fileName}" to be read.`); - } - }); - - console.log(`done, no errors.`); - }) - .catch((e: Error) => { - if (forceError) { - assert( - e.message.match(`Missing translation for message`), - `Expected error message for missing translations`); - console.log(`done, error catched`); - } else { - console.error(e.stack); - console.error('Compilation failed'); - throw e; - } - }); -} - -function i18nTest() { - const basePath = path.join(__dirname, '../ngtools_src'); - const project = path.join(basePath, 'tsconfig-build.json'); - const readResources: string[] = []; - const wroteFiles: string[] = []; - - const config = readConfiguration(project); - const delegateHost = ts.createCompilerHost(config.options, true); - const host: ts.CompilerHost = Object.assign( - {}, delegateHost, - {writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }}); - const program = ts.createProgram(config.rootNames, config.options, host); - - config.options.basePath = basePath; - - console.log(`>>> running i18n extraction for ${project}`); - return __NGTOOLS_PRIVATE_API_2 - .extractI18n({ - basePath, - compilerOptions: config.options, program, host, - angularCompilerOptions: config.options, - i18nFormat: 'xlf', - locale: undefined, - outFile: undefined, - readResource: (fileName: string) => { - readResources.push(fileName); - if (!host.fileExists(fileName)) { - throw new Error(`Compilation failed. Resource file not found: ${fileName}`); - } - return Promise.resolve(host.readFile(fileName)); - }, - }) - .then(() => { - console.log(`>>> i18n extraction done, asserting read and wrote files`); - - const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true}); - - assert(wroteFiles.length == 1, `Expected a single message bundle file.`); - - assert( - wroteFiles[0].endsWith('/ngtools_src/messages.xlf'), - `Expected the bundle file to be "message.xlf".`); - - allFiles.forEach((fileName: string) => { - // Skip tsconfig. - if (fileName.match(/tsconfig-build.json$/)) { - return; - } - - // Assert that file was read. - if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) { - assert( - readResources.indexOf(fileName) != -1, - `Expected resource "${fileName}" to be read.`); - } - }); - - console.log(`done, no errors.`); - }) - .catch((e: Error) => { - console.error(e.stack); - console.error('Extraction failed'); - throw e; - }); -} - function lazyRoutesTest() { const basePath = path.join(__dirname, '../ngtools_src'); const project = path.join(basePath, 'tsconfig-build.json'); diff --git a/packages/compiler-cli/integrationtest/test/test_summaries.ts b/packages/compiler-cli/integrationtest/test/test_summaries.ts deleted file mode 100644 index 5cd278ad8e..0000000000 --- a/packages/compiler-cli/integrationtest/test/test_summaries.ts +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env node -/** - * @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 - */ - -// Must be imported first, because Angular decorators throw on load. -import 'reflect-metadata'; - -import * as path from 'path'; -import * as ts from 'typescript'; -import * as assert from 'assert'; -import {CompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, readConfiguration} from '@angular/compiler-cli'; - -/* tslint:disable:no-console */ -/** - * Main method. - * Standalone program that executes the real codegen and tests that - * ngsummary.json files are used for libraries. - */ -function main() { - console.log(`testing usage of ngsummary.json files in libraries...`); - const basePath = path.resolve(__dirname, '..'); - const project = path.resolve(basePath, 'tsconfig-build.json'); - const readFiles: string[] = []; - const writtenFiles: {fileName: string, content: string}[] = []; - - class AssertingHostContext extends NodeCompilerHostContext { - readFile(fileName: string): string { - if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName) && - !/package\.json$/.test(fileName)) { - // Only allow to read summaries and package.json files from node_modules - // TODO (mhevery): Fix this. TypeScript.d.ts does not allow returning null. - return null !; - } - readFiles.push(path.relative(basePath, fileName)); - return super.readFile(fileName); - } - readResource(s: string): Promise { - readFiles.push(path.relative(basePath, s)); - return super.readResource(s); - } - } - - const config = readConfiguration(project); - config.options.basePath = basePath; - // This flag tells ngc do not recompile libraries. - config.options.generateCodeForLibraries = false; - - console.log(`>>> running codegen for ${project}`); - codegen( - config, - (host) => { - host.writeFile = (fileName: string, content: string) => { - fileName = path.relative(basePath, fileName); - writtenFiles.push({fileName, content}); - }; - return new AssertingHostContext(); - }) - .then((exitCode: any) => { - console.log(`>>> codegen done, asserting read files`); - assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/); - assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/); - assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/); - assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/); - - assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/); - assertSomeFileMatch(readFiles, /^src\/.*\.html$/); - assertSomeFileMatch(readFiles, /^src\/.*\.css$/); - - console.log(`>>> asserting written files`); - assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/); - - console.log(`done, no errors.`); - process.exit(exitCode); - }) - .catch((e: any) => { - console.error(e.stack); - console.error('Compilation failed'); - process.exit(1); - }); -} - -/** - * Simple adaption of main to just run codegen with a CompilerHostContext - */ -function codegen( - config: {options: CompilerOptions, rootNames: string[]}, - hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) { - const host = ts.createCompilerHost(config.options, true); - - // HACK: patch the realpath to solve symlink issue here: - // https://github.com/Microsoft/TypeScript/issues/9552 - // todo(misko): remove once facade symlinks are removed - host.realpath = (path) => path; - - const program = ts.createProgram(config.rootNames, config.options, host); - - return CodeGenerator.create(config.options, { - } as any, program, host, hostContextFactory(host)).codegen(); -} - -function assertSomeFileMatch(fileNames: string[], pattern: RegExp) { - assert( - fileNames.some(fileName => pattern.test(fileName)), - `Expected some read files match ${pattern}`); -} - -function assertNoFileMatch(fileNames: string[], pattern: RegExp) { - const matches = fileNames.filter(fileName => pattern.test(fileName)); - assert( - matches.length === 0, - `Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`); -} - -function assertWrittenFile( - files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) { - assert( - files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)), - `Expected some written files for ${filePattern} and content ${contentPattern}`); -} - -main(); diff --git a/packages/compiler-cli/integrationtest/tsconfig-build.json b/packages/compiler-cli/integrationtest/tsconfig-build.json index f967c96ccc..8badeac4d1 100644 --- a/packages/compiler-cli/integrationtest/tsconfig-build.json +++ b/packages/compiler-cli/integrationtest/tsconfig-build.json @@ -31,7 +31,6 @@ "src/bootstrap", "test/all_spec", "test/test_ngtools_api", - "test/test_summaries", "benchmarks/src/tree/ng2/index_aot.ts", "benchmarks/src/tree/ng2_switch/index_aot.ts", "benchmarks/src/largetable/ng2/index_aot.ts", diff --git a/packages/compiler-cli/integrationtest/tsconfig-xi18n.json b/packages/compiler-cli/integrationtest/tsconfig-xi18n.json index b99e0f28b5..039931f12c 100644 --- a/packages/compiler-cli/integrationtest/tsconfig-xi18n.json +++ b/packages/compiler-cli/integrationtest/tsconfig-xi18n.json @@ -26,7 +26,6 @@ "src/bootstrap", "test/all_spec", "test/test_ngtools_api", - "test/test_summaries", "benchmarks/src/tree/ng2/index_aot.ts", "benchmarks/src/tree/ng2_switch/index_aot.ts", "benchmarks/src/largetable/ng2/index_aot.ts", diff --git a/packages/compiler-cli/src/codegen.ts b/packages/compiler-cli/src/codegen.ts deleted file mode 100644 index 5b00a9b412..0000000000 --- a/packages/compiler-cli/src/codegen.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @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 - */ - -/** - * Transform template html and css into executable code. - * Intended to be used in a build step. - */ -import * as compiler from '@angular/compiler'; -import {readFileSync} from 'fs'; -import * as ts from 'typescript'; - -import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; -import {PathMappedCompilerHost} from './path_mapped_compiler_host'; -import {CompilerOptions} from './transformers/api'; - -const GENERATED_META_FILES = /\.json$/; - -const PREAMBLE = `/** - * @fileoverview This file is generated by the Angular template compiler. - * Do not edit. - * @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride} - */ - /* tslint:disable */ - -`; - -export interface CodeGeneratorI18nOptions { - i18nFormat: string|null; - i18nFile: string|null; - locale: string|null; - missingTranslation: string|null; -} - -// TODO(tbosch): remove this once G3 uses the transformer compiler! -export class CodeGenerator { - constructor( - private options: CompilerOptions, private program: ts.Program, public host: ts.CompilerHost, - private compiler: compiler.AotCompiler, private ngCompilerHost: CompilerHost) {} - - codegen(): Promise { - return this.compiler - .analyzeModulesAsync(this.program.getSourceFiles().map( - sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))) - .then(analyzedModules => this.emit(analyzedModules)); - } - - codegenSync(): string[] { - const analyzed = this.compiler.analyzeModulesSync(this.program.getSourceFiles().map( - sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))); - return this.emit(analyzed); - } - - private emit(analyzedModules: compiler.NgAnalyzedModules) { - const generatedModules = this.compiler.emitAllImpls(analyzedModules); - return generatedModules.map(generatedModule => { - const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl); - const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl); - const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE); - this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]); - return emitPath; - }); - } - - static create( - options: CompilerOptions, i18nOptions: CodeGeneratorI18nOptions, program: ts.Program, - tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext, - ngCompilerHost?: CompilerHost): CodeGenerator { - if (!ngCompilerHost) { - const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; - const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost); - ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) : - new CompilerHost(program, options, context); - } - let transContent: string = ''; - if (i18nOptions.i18nFile) { - if (!i18nOptions.locale) { - throw new Error( - `The translation file (${i18nOptions.i18nFile}) locale must be provided. Use the --locale option.`); - } - transContent = readFileSync(i18nOptions.i18nFile, 'utf8'); - } - let missingTranslation = compiler.core.MissingTranslationStrategy.Warning; - if (i18nOptions.missingTranslation) { - switch (i18nOptions.missingTranslation) { - case 'error': - missingTranslation = compiler.core.MissingTranslationStrategy.Error; - break; - case 'warning': - missingTranslation = compiler.core.MissingTranslationStrategy.Warning; - break; - case 'ignore': - missingTranslation = compiler.core.MissingTranslationStrategy.Ignore; - break; - default: - throw new Error( - `Unknown option for missingTranslation (${i18nOptions.missingTranslation}). Use either error, warning or ignore.`); - } - } - if (!transContent) { - missingTranslation = compiler.core.MissingTranslationStrategy.Ignore; - } - const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, { - translations: transContent, - i18nFormat: i18nOptions.i18nFormat || undefined, - locale: i18nOptions.locale || undefined, missingTranslation, - enableLegacyTemplate: options.enableLegacyTemplate === true, - enableSummariesForJit: options.enableSummariesForJit !== false, - preserveWhitespaces: options.preserveWhitespaces, - }); - return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost); - } -} diff --git a/packages/compiler-cli/src/compiler_host.ts b/packages/compiler-cli/src/compiler_host.ts deleted file mode 100644 index 2a0399b2d7..0000000000 --- a/packages/compiler-cli/src/compiler_host.ts +++ /dev/null @@ -1,494 +0,0 @@ -/** - * @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 {AotCompilerHost, StaticSymbol, UrlResolver, createOfflineCompileUrlResolver, syntaxError} from '@angular/compiler'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as ts from 'typescript'; - -import {CollectorOptions, METADATA_VERSION, MetadataCollector, ModuleMetadata} from './metadata/index'; -import {CompilerOptions} from './transformers/api'; - -const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; -const DTS = /\.d\.ts$/; -const NODE_MODULES = '/node_modules/'; -const IS_GENERATED = /\.(ngfactory|ngstyle|ngsummary)$/; -const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/; -const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/; -const SHALLOW_IMPORT = /^((\w|-)+|(@(\w|-)+(\/(\w|-)+)+))$/; - -export interface BaseAotCompilerHostContext extends ts.ModuleResolutionHost { - readResource?(fileName: string): Promise|string; -} - -export abstract class BaseAotCompilerHost implements - AotCompilerHost { - private resolverCache = new Map(); - private flatModuleIndexCache = new Map(); - private flatModuleIndexNames = new Set(); - private flatModuleIndexRedirectNames = new Set(); - - constructor(protected options: CompilerOptions, protected context: C) {} - - abstract moduleNameToFileName(m: string, containingFile: string): string|null; - - abstract resourceNameToFileName(m: string, containingFile: string): string|null; - - abstract fileNameToModuleName(importedFile: string, containingFile: string): string; - - abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string; - - abstract fromSummaryFileName(fileName: string, referringLibFileName: string): string; - - abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined; - - getMetadataFor(filePath: string): ModuleMetadata[]|undefined { - if (!this.context.fileExists(filePath)) { - // If the file doesn't exists then we cannot return metadata for the file. - // This will occur if the user referenced a declared module for which no file - // exists for the module (i.e. jQuery or angularjs). - return; - } - - if (DTS.test(filePath)) { - let metadatas = this.readMetadata(filePath); - if (!metadatas) { - // If there is a .d.ts file but no metadata file we need to produce a - // metadata from the .d.ts file as metadata files capture reexports - // (starting with v3). - metadatas = [this.upgradeMetadataWithDtsData( - {'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)]; - } - return metadatas; - } - - // Attention: don't cache this, so that e.g. the LanguageService - // can read in changes from source files in the metadata! - const metadata = this.getMetadataForSourceFile(filePath); - return metadata ? [metadata] : []; - } - - protected readMetadata(dtsFilePath: string): ModuleMetadata[]|undefined { - let metadatas = this.resolverCache.get(dtsFilePath); - if (metadatas) { - return metadatas; - } - const metadataPath = dtsFilePath.replace(DTS, '.metadata.json'); - if (!this.context.fileExists(metadataPath)) { - return undefined; - } - try { - const metadataOrMetadatas = JSON.parse(this.context.readFile(metadataPath)); - const metadatas: ModuleMetadata[] = metadataOrMetadatas ? - (Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) : - []; - if (metadatas.length) { - let maxMetadata = metadatas.reduce((p, c) => p.version > c.version ? p : c); - if (maxMetadata.version < METADATA_VERSION) { - metadatas.push(this.upgradeMetadataWithDtsData(maxMetadata, dtsFilePath)); - } - } - this.resolverCache.set(dtsFilePath, metadatas); - return metadatas; - } catch (e) { - console.error(`Failed to read JSON file ${metadataPath}`); - throw e; - } - } - - private upgradeMetadataWithDtsData(oldMetadata: ModuleMetadata, dtsFilePath: string): - ModuleMetadata { - // patch v1 to v3 by adding exports and the `extends` clause. - // patch v3 to v4 by adding `interface` symbols for TypeAlias - let newMetadata: ModuleMetadata = { - '__symbolic': 'module', - 'version': METADATA_VERSION, - 'metadata': {...oldMetadata.metadata}, - }; - if (oldMetadata.exports) { - newMetadata.exports = oldMetadata.exports; - } - if (oldMetadata.importAs) { - newMetadata.importAs = oldMetadata.importAs; - } - if (oldMetadata.origins) { - newMetadata.origins = oldMetadata.origins; - } - const dtsMetadata = this.getMetadataForSourceFile(dtsFilePath); - if (dtsMetadata) { - for (let prop in dtsMetadata.metadata) { - if (!newMetadata.metadata[prop]) { - newMetadata.metadata[prop] = dtsMetadata.metadata[prop]; - } - } - - // Only copy exports from exports from metadata prior to version 3. - // Starting with version 3 the collector began collecting exports and - // this should be redundant. Also, with bundler will rewrite the exports - // which will hoist the exports from modules referenced indirectly causing - // the imports to be different than the .d.ts files and using the .d.ts file - // exports would cause the StaticSymbolResolver to redirect symbols to the - // incorrect location. - if ((!oldMetadata.version || oldMetadata.version < 3) && dtsMetadata.exports) { - newMetadata.exports = dtsMetadata.exports; - } - } - return newMetadata; - } - - loadResource(filePath: string): Promise|string { - if (this.context.readResource) return this.context.readResource(filePath); - if (!this.context.fileExists(filePath)) { - throw syntaxError(`Error: Resource file not found: ${filePath}`); - } - return this.context.readFile(filePath); - } - - loadSummary(filePath: string): string|null { - if (this.context.fileExists(filePath)) { - return this.context.readFile(filePath); - } - return null; - } - - isSourceFile(filePath: string): boolean { - const excludeRegex = - this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; - if (excludeRegex.test(filePath)) { - return false; - } - if (DTS.test(filePath)) { - // Check for a bundle index. - if (this.hasBundleIndex(filePath)) { - const normalFilePath = path.normalize(filePath); - return this.flatModuleIndexNames.has(normalFilePath) || - this.flatModuleIndexRedirectNames.has(normalFilePath); - } - } - return true; - } - - private hasBundleIndex(filePath: string): boolean { - const checkBundleIndex = (directory: string): boolean => { - let result = this.flatModuleIndexCache.get(directory); - if (result == null) { - if (path.basename(directory) == 'node_module') { - // Don't look outside the node_modules this package is installed in. - result = false; - } else { - // A bundle index exists if the typings .d.ts file has a metadata.json that has an - // importAs. - try { - const packageFile = path.join(directory, 'package.json'); - if (this.context.fileExists(packageFile)) { - // Once we see a package.json file, assume false until it we find the bundle index. - result = false; - const packageContent: any = JSON.parse(this.context.readFile(packageFile)); - if (packageContent.typings) { - const typings = path.normalize(path.join(directory, packageContent.typings)); - if (DTS.test(typings)) { - const metadataFile = typings.replace(DTS, '.metadata.json'); - if (this.context.fileExists(metadataFile)) { - const metadata = JSON.parse(this.context.readFile(metadataFile)); - if (metadata.flatModuleIndexRedirect) { - this.flatModuleIndexRedirectNames.add(typings); - // Note: don't set result = true, - // as this would mark this folder - // as having a bundleIndex too early without - // filling the bundleIndexNames. - } else if (metadata.importAs) { - this.flatModuleIndexNames.add(typings); - result = true; - } - } - } - } - } else { - const parent = path.dirname(directory); - if (parent != directory) { - // Try the parent directory. - result = checkBundleIndex(parent); - } else { - result = false; - } - } - } catch (e) { - // If we encounter any errors assume we this isn't a bundle index. - result = false; - } - } - this.flatModuleIndexCache.set(directory, result); - } - return result; - }; - - return checkBundleIndex(path.dirname(filePath)); - } -} - -export interface CompilerHostContext extends ts.ModuleResolutionHost { - readResource?(fileName: string): Promise|string; - assumeFileExists(fileName: string): void; -} - -// TODO(tbosch): remove this once G3 uses the transformer compiler! -export class CompilerHost extends BaseAotCompilerHost { - protected metadataProvider: MetadataCollector; - protected basePath: string; - private moduleFileNames = new Map(); - private isGenDirChildOfRootDir: boolean; - private genDir: string; - protected resolveModuleNameHost: CompilerHostContext; - private urlResolver: UrlResolver; - - constructor( - protected program: ts.Program, options: CompilerOptions, context: CompilerHostContext, - collectorOptions?: CollectorOptions) { - super(options, context); - this.metadataProvider = new MetadataCollector(collectorOptions); - // 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, '/'); - - const genPath: string = path.relative(this.basePath, this.genDir); - this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); - - this.resolveModuleNameHost = Object.create(this.context); - - // When calling ts.resolveModuleName, - // additional allow checks for .d.ts files to be done based on - // checks for .ngsummary.json files, - // so that our codegen depends on fewer inputs and requires to be called - // less often. - // This is needed as we use ts.resolveModuleName in reflector_host - // and it should be able to resolve summary file names. - this.resolveModuleNameHost.fileExists = (fileName: string): boolean => { - if (this.context.fileExists(fileName)) { - return true; - } - if (DTS.test(fileName)) { - const base = fileName.substring(0, fileName.length - 5); - return this.context.fileExists(base + '.ngsummary.json'); - } - return false; - }; - this.urlResolver = createOfflineCompileUrlResolver(); - } - - protected getSourceFile(filePath: string): ts.SourceFile { - let sf = this.program.getSourceFile(filePath); - if (!sf) { - if (this.context.fileExists(filePath)) { - const sourceText = this.context.readFile(filePath); - sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true); - } else { - throw new Error(`Source file ${filePath} not present in program.`); - } - } - return sf; - } - - getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined { - return this.metadataProvider.getMetadata(this.getSourceFile(filePath)); - } - - toSummaryFileName(fileName: string, referringSrcFileName: string): string { - return fileName.replace(EXT, '') + '.d.ts'; - } - - fromSummaryFileName(fileName: string, referringLibFileName: string): string { return fileName; } - - calculateEmitPath(filePath: string): string { - // Write codegen in a directory structure matching the sources. - let root = this.options.basePath !; - for (const eachRootDir of this.options.rootDirs || []) { - if (this.options.trace) { - console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`); - } - if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) { - root = eachRootDir; - } - } - - // transplant the codegen path to be inside the `genDir` - let relativePath: string = path.relative(root, filePath); - while (relativePath.startsWith('..' + path.sep)) { - // Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything - // into `genDir`. - relativePath = relativePath.substr(3); - } - - return path.join(this.options.genDir !, relativePath); - } - - // We use absolute paths on disk as canonical. - getCanonicalFileName(fileName: string): string { return fileName; } - - moduleNameToFileName(m: string, containingFile: string): string|null { - const key = m + ':' + (containingFile || ''); - let result: string|null = this.moduleFileNames.get(key) || null; - if (!result) { - if (!containingFile || !containingFile.length) { - if (m.indexOf('.') === 0) { - throw new Error('Resolution of relative paths requires a containing file.'); - } - // Any containing file gives the same result for absolute imports - containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts')); - } - m = m.replace(EXT, ''); - const resolved = - ts.resolveModuleName( - m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost) - .resolvedModule; - result = resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; - this.moduleFileNames.set(key, result); - } - return result; - } - - /** - * We want a moduleId that will appear in import statements in the generated code. - * These need to be in a form that system.js can load, so absolute file paths don't work. - * - * The `containingFile` is always in the `genDir`, where as the `importedFile` can be in - * `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or - * existing file. - * - * | genDir | node_module | rootDir - * --------------+----------+-------------+---------- - * generated | relative | relative | n/a - * existing file | n/a | absolute | relative(*) - * - * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. - */ - 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 (importedFile !== containingFile && !this.context.fileExists(importedFile)) { - this.context.assumeFileExists(importedFile); - } - - containingFile = this.rewriteGenDirPath(containingFile); - const containingDir = path.dirname(containingFile); - // drop extension - importedFile = importedFile.replace(EXT, ''); - - const nodeModulesIndex = importedFile.indexOf(NODE_MODULES); - const importModule = nodeModulesIndex === -1 ? - null : - importedFile.substring(nodeModulesIndex + NODE_MODULES.length); - const isGeneratedFile = IS_GENERATED.test(importedFile); - - if (isGeneratedFile) { - // rewrite to genDir path - if (importModule) { - // it is generated, therefore we do a relative path to the factory - return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule); - } else { - // assume that import is also in `genDir` - importedFile = this.rewriteGenDirPath(importedFile); - return this.dotRelative(containingDir, importedFile); - } - } else { - // user code import - if (importModule) { - return importModule; - } else { - if (!this.isGenDirChildOfRootDir) { - // assume that they are on top of each other. - importedFile = importedFile.replace(this.basePath, this.genDir); - } - if (SHALLOW_IMPORT.test(importedFile)) { - return importedFile; - } - return this.dotRelative(containingDir, importedFile); - } - } - } - - resourceNameToFileName(m: string, containingFile: string): string { - return this.urlResolver.resolve(containingFile, m); - } - - /** - * Moves the path into `genDir` folder while preserving the `node_modules` directory. - */ - private rewriteGenDirPath(filepath: string) { - const nodeModulesIndex = filepath.indexOf(NODE_MODULES); - if (nodeModulesIndex !== -1) { - // If we are in node_modules, transplant them into `genDir`. - return path.join(this.genDir, filepath.substring(nodeModulesIndex)); - } else { - // pretend that containing file is on top of the `genDir` to normalize the paths. - // we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later. - return filepath.replace(this.basePath, this.genDir); - } - } - - private dotRelative(from: string, to: string): string { - const rPath: string = path.relative(from, to).replace(/\\/g, '/'); - return rPath.startsWith('.') ? rPath : './' + rPath; - } -} - -export class CompilerHostContextAdapter { - protected 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(); - } catch (e) { - return false; - } - } - - readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); } - - readResource(s: string) { - 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.readFile(s)); - } -} diff --git a/packages/compiler-cli/src/extractor.ts b/packages/compiler-cli/src/extractor.ts deleted file mode 100644 index 4a55f3b07f..0000000000 --- a/packages/compiler-cli/src/extractor.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @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 - */ - - -/** - * Extract i18n messages from source code - */ -// Must be imported first, because Angular decorators throw on load. -import 'reflect-metadata'; - -import * as compiler from '@angular/compiler'; -import * as path from 'path'; -import * as ts from 'typescript'; - -import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; -import {PathMappedCompilerHost} from './path_mapped_compiler_host'; -import {CompilerOptions} from './transformers/api'; -import {i18nExtract, i18nGetExtension, i18nSerialize} from './transformers/program'; - -export class Extractor { - constructor( - private options: CompilerOptions, private ngExtractor: compiler.Extractor, - public host: ts.CompilerHost, private ngCompilerHost: CompilerHost, - private program: ts.Program) {} - - extract(formatName: string, outFile: string|null): Promise { - return this.extractBundle().then( - bundle => i18nExtract(formatName, outFile, this.host, this.options, bundle)); - } - - extractBundle(): Promise { - const files = this.program.getSourceFiles().map( - sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)); - - return this.ngExtractor.extract(files); - } - - serialize(bundle: compiler.MessageBundle, formatName: string): string { - return i18nSerialize(bundle, formatName, this.options); - } - - getExtension(formatName: string): string { return i18nGetExtension(formatName); } - - static create( - options: CompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost, - locale?: string|null, compilerHostContext?: CompilerHostContext, - ngCompilerHost?: CompilerHost): Extractor { - if (!ngCompilerHost) { - const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; - const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost); - ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) : - new CompilerHost(program, options, context); - } - - const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost, locale || null); - - return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program); - } -} diff --git a/packages/compiler-cli/src/language_services.ts b/packages/compiler-cli/src/language_services.ts index 877a0f8283..4f04e048c4 100644 --- a/packages/compiler-cli/src/language_services.ts +++ b/packages/compiler-cli/src/language_services.ts @@ -14,9 +14,10 @@ Angular modules and Typescript as this will indirectly add a dependency to the language service. */ -export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './compiler_host'; export {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics'; export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type'; export {BuiltinType, DeclarationKind, Definition, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './diagnostics/symbols'; export {getClassFromStaticSymbol, getClassMembers, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './diagnostics/typescript_symbols'; +export {MetadataCollector, ModuleMetadata} from './metadata'; export {CompilerOptions} from './transformers/api'; +export {MetadataReaderCache, MetadataReaderHost, createMetadataReaderCache, readMetadata} from './transformers/metadata_reader'; diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index d373cb8c89..7dd8ac90aa 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -28,15 +28,15 @@ export function main( let {project, rootNames, options, errors: configErrors, watch, emitFlags} = config || readNgcCommandLineAndConfiguration(args); if (configErrors.length) { - return reportErrorsAndExit(configErrors, consoleError); + return reportErrorsAndExit(configErrors, /*options*/ undefined, consoleError); } if (watch) { const result = watchMode(project, options, consoleError); - return reportErrorsAndExit(result.firstCompileResult, consoleError); + return reportErrorsAndExit(result.firstCompileResult, options, consoleError); } const {diagnostics: compileDiags} = performCompilation( {rootNames, options, emitFlags, emitCallback: createEmitCallback(options)}); - return reportErrorsAndExit(compileDiags, consoleError); + return reportErrorsAndExit(compileDiags, options, consoleError); } function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined { @@ -128,10 +128,17 @@ export function readCommandLineAndConfiguration( } function reportErrorsAndExit( - allDiagnostics: Diagnostics, consoleError: (s: string) => void = console.error): number { + allDiagnostics: Diagnostics, options?: api.CompilerOptions, + consoleError: (s: string) => void = console.error): number { const errorsAndWarnings = filterErrorsAndWarnings(allDiagnostics); if (errorsAndWarnings.length) { - consoleError(formatDiagnostics(errorsAndWarnings)); + let currentDir = options ? options.basePath : undefined; + const formatHost: ts.FormatDiagnosticsHost = { + getCurrentDirectory: () => currentDir || ts.sys.getCurrentDirectory(), + getCanonicalFileName: fileName => fileName, + getNewLine: () => ts.sys.newLine + }; + consoleError(formatDiagnostics(errorsAndWarnings, formatHost)); } return exitCodeFromResult(allDiagnostics); } diff --git a/packages/compiler-cli/src/ngtools_api.ts b/packages/compiler-cli/src/ngtools_api.ts index 5e5e771c5e..2ef6dc0374 100644 --- a/packages/compiler-cli/src/ngtools_api.ts +++ b/packages/compiler-cli/src/ngtools_api.ts @@ -19,15 +19,11 @@ ********************************************************************* */ -import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler'; import * as ts from 'typescript'; -import {CodeGenerator} from './codegen'; -import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; -import {Extractor} from './extractor'; -import {listLazyRoutesOfModule} from './ngtools_impl'; -import {PathMappedCompilerHost} from './path_mapped_compiler_host'; -import {CompilerOptions} from './transformers/api'; +import {CompilerHost, CompilerOptions, LazyRoute} from './transformers/api'; +import {getOriginalReferences} from './transformers/compiler_host'; +import {createProgram} from './transformers/entry_points'; export interface NgTools_InternalApi_NG2_CodeGen_Options { basePath: string; @@ -72,47 +68,16 @@ export interface NgTools_InternalApi_NG2_ExtractI18n_Options { outFile?: string; } -/** - * A ModuleResolutionHostAdapter that overrides the readResource() method with the one - * passed in the interface. - */ -class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter { - constructor( - private _readResource: (path: string) => Promise, host: ts.ModuleResolutionHost) { - super(host); - } - - readResource(path: string) { return this._readResource(path); } -} - /** * @internal + * @deprecatd Use ngtools_api2 instead! */ export class NgTools_InternalApi_NG_2 { /** * @internal */ static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise { - const hostContext: CompilerHostContext = - new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host); - - const i18nOptions = { - i18nFormat: options.i18nFormat !, - i18nFile: options.i18nFile !, - locale: options.locale !, - missingTranslation: options.missingTranslation !, - }; - const ngOptions = options.angularCompilerOptions; - if (ngOptions.enableSummariesForJit === undefined) { - // default to false - ngOptions.enableSummariesForJit = false; - } - - // Create the Code Generator. - const codeGenerator = - CodeGenerator.create(ngOptions, i18nOptions, options.program, options.host, hostContext); - - return codeGenerator.codegen(); + throw throwNotSupportedError(); } /** @@ -120,42 +85,49 @@ export class NgTools_InternalApi_NG_2 { */ static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options): NgTools_InternalApi_NG_2_LazyRouteMap { - const angularCompilerOptions = options.angularCompilerOptions; - const program = options.program; + // TODO(tbosch): Also throwNotSupportedError once Angular CLI 1.5.1 ships, + // as we only needed this to support Angular CLI 1.5.0 rc.* + const ngProgram = createProgram({ + rootNames: options.program.getRootFileNames(), + options: options.angularCompilerOptions, + host: options.host + }); + const lazyRoutes = ngProgram.listLazyRoutes(options.entryModule); - const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host); - const usePathMapping = - !!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0; - const ngCompilerHost: AotCompilerHost = usePathMapping ? - new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) : - new CompilerHost(program, angularCompilerOptions, moduleResolutionHost); + // reset the referencedFiles that the ng.Program added to the SourceFiles + // as the host might be caching the source files! + for (const sourceFile of options.program.getSourceFiles()) { + const originalReferences = getOriginalReferences(sourceFile); + if (originalReferences) { + sourceFile.referencedFiles = originalReferences; + } + } - const symbolCache = new StaticSymbolCache(); - const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache); - const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver); - const staticReflector = new StaticReflector(summaryResolver, symbolResolver); - const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector); + const result: NgTools_InternalApi_NG_2_LazyRouteMap = {}; + lazyRoutes.forEach(lazyRoute => { + const route = lazyRoute.route; + const referencedFilePath = lazyRoute.referencedModule.filePath; + if (result[route] && result[route] != referencedFilePath) { + throw new Error( + `Duplicated path in loadChildren detected: "${route}" is used in 2 loadChildren, ` + + `but they point to different modules "(${result[route]} and ` + + `"${referencedFilePath}"). Webpack cannot distinguish on context and would fail to ` + + 'load the proper one.'); + } + result[route] = referencedFilePath; + }); - return Object.keys(routeMap).reduce( - (acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => { - acc[route] = routeMap[route].absoluteFilePath; - return acc; - }, - {}); + return result; } /** * @internal */ static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise { - const hostContext: CompilerHostContext = - new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host); - - // Create the i18n extractor. - const locale = options.locale || null; - const extractor = Extractor.create( - options.angularCompilerOptions, options.program, options.host, locale, hostContext); - - return extractor.extract(options.i18nFormat !, options.outFile || null); + throw throwNotSupportedError(); } } + +function throwNotSupportedError() { + throw new Error(`Please update @angular/cli. Angular 5+ requires at least Angular CLI 1.5+`); +} diff --git a/packages/compiler-cli/src/ngtools_api2.ts b/packages/compiler-cli/src/ngtools_api2.ts index 4542da69bb..0342638585 100644 --- a/packages/compiler-cli/src/ngtools_api2.ts +++ b/packages/compiler-cli/src/ngtools_api2.ts @@ -100,6 +100,12 @@ export interface TsEmitArguments { export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; } +export interface LazyRoute { + module: {name: string, filePath: string}; + route: string; + referencedModule: {name: string, filePath: string}; +} + export interface Program { getTsProgram(): ts.Program; getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[]; @@ -112,6 +118,7 @@ export interface Program { getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): Diagnostic[]; loadNgStructureAsync(): Promise; + listLazyRoutes(entryRoute?: string): LazyRoute[]; emit({emitFlags, cancellationToken, customTransformers, emitCallback}: { emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken, diff --git a/packages/compiler-cli/src/ngtools_impl.ts b/packages/compiler-cli/src/ngtools_impl.ts deleted file mode 100644 index 0827f83024..0000000000 --- a/packages/compiler-cli/src/ngtools_impl.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * @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 - */ - -/** - * This is a private API for the ngtools toolkit. - * - * This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by - * something else. - */ - -/** - ********************************************************************* - * Changes to this file need to be approved by the Angular CLI team. * - ********************************************************************* - */ - -import {AotCompilerHost, StaticReflector, StaticSymbol, core} from '@angular/compiler'; - - -// We cannot depend directly to @angular/router. -type Route = any; -const ROUTER_MODULE_PATH = '@angular/router'; -const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES'; - - -// LazyRoute information between the extractors. -export interface LazyRoute { - routeDef: RouteDef; - absoluteFilePath: string; -} -export type LazyRouteMap = { - [route: string]: LazyRoute -}; - -// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by -// the user, and this is a helper class to extract information from it. -export class RouteDef { - private constructor(public readonly path: string, public readonly className: string|null = null) { - } - - toString() { - return (this.className === null || this.className == 'default') ? - this.path : - `${this.path}#${this.className}`; - } - - static fromString(entry: string): RouteDef { - const split = entry.split('#'); - return new RouteDef(split[0], split[1] || null); - } -} - - -export function listLazyRoutesOfModule( - entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap { - const entryRouteDef = RouteDef.fromString(entryModule); - const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host); - const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`; - const className = entryRouteDef.className !; - - // List loadChildren of this single module. - const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile); - const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME); - const lazyRoutes: LazyRoute[] = - _extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES); - - const allLazyRoutes = lazyRoutes.reduce( - function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute): - LazyRouteMap { - const route: string = lazyRoute.routeDef.toString(); - _assertRoute(allRoutes, lazyRoute); - allRoutes[route] = lazyRoute; - - // StaticReflector does not support discovering annotations like `NgModule` on default - // exports - // Which means: if a default export NgModule was lazy-loaded, we can discover it, but, - // we cannot parse its routes to see if they have loadChildren or not. - if (!lazyRoute.routeDef.className) { - return allRoutes; - } - - const lazyModuleSymbol = reflector.findDeclaration( - lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default'); - - const subRoutes = - _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES); - - return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes); - }, - {}); - - return allLazyRoutes; -} - - -/** - * Try to resolve a module, and returns its absolute path. - * @private - */ -function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) { - const result = host.moduleNameToFileName(modulePath, containingFile); - if (!result) { - throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`); - } - return result; -} - - -/** - * Throw an exception if a route is in a route map, but does not point to the same module. - */ -function _assertRoute(map: LazyRouteMap, route: LazyRoute) { - const r = route.routeDef.toString(); - if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) { - throw new Error( - `Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` + - `but they point to different modules "(${map[r].absoluteFilePath} and ` + - `"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` + - 'load the proper one.'); - } -} - -export function flatten(list: Array): T[] { - return list.reduce((flat: any[], item: T | T[]): T[] => { - const flatItem = Array.isArray(item) ? flatten(item) : item; - return (flat).concat(flatItem); - }, []); -} - -/** - * Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this - * module and all statically referred modules. - * @private - */ -function _extractLazyRoutesFromStaticModule( - staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost, - ROUTES: StaticSymbol): LazyRoute[] { - const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector); - const imports = flatten(moduleMetadata.imports || []); - const allRoutes: any = imports.filter(i => 'providers' in i).reduce((mem: Route[], m: any) => { - return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES)); - }, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES)); - - const lazyRoutes: LazyRoute[] = - _collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => { - const routeDef = RouteDef.fromString(route); - const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host); - acc.push({routeDef, absoluteFilePath}); - return acc; - }, []); - - const importedSymbols = - (imports as any[]) - .filter(i => i instanceof StaticSymbol || i.ngModule instanceof StaticSymbol) - .map(i => { - if (i instanceof StaticSymbol) return i; - return i.ngModule; - }) as StaticSymbol[]; - - return importedSymbols - .reduce( - (acc: LazyRoute[], i: StaticSymbol) => { - return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES)); - }, - []) - .concat(lazyRoutes); -} - - -/** - * Get the NgModule Metadata of a symbol. - */ -function _getNgModuleMetadata( - staticSymbol: StaticSymbol, reflector: StaticReflector): core.NgModule { - const ngModules = - reflector.annotations(staticSymbol).filter((s: any) => core.createNgModule.isTypeOf(s)); - if (ngModules.length === 0) { - throw new Error(`${staticSymbol.name} is not an NgModule`); - } - return ngModules[0]; -} - - -/** - * Return the routes from the provider list. - * @private - */ -function _collectRoutes( - providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] { - return providers.reduce((routeList: Route[], p: any) => { - if (p.provide === ROUTES) { - return routeList.concat(p.useValue); - } else if (Array.isArray(p)) { - return routeList.concat(_collectRoutes(p, reflector, ROUTES)); - } else { - return routeList; - } - }, []); -} - - -/** - * Return the loadChildren values of a list of Route. - */ -function _collectLoadChildren(routes: Route[]): string[] { - return routes.reduce((m, r) => { - if (r.loadChildren && typeof r.loadChildren === 'string') { - return m.concat(r.loadChildren); - } else if (Array.isArray(r)) { - return m.concat(_collectLoadChildren(r)); - } else if (r.children) { - return m.concat(_collectLoadChildren(r.children)); - } else { - return m; - } - }, []); -} diff --git a/packages/compiler-cli/src/path_mapped_compiler_host.ts b/packages/compiler-cli/src/path_mapped_compiler_host.ts deleted file mode 100644 index 1b82c11b19..0000000000 --- a/packages/compiler-cli/src/path_mapped_compiler_host.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @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 {StaticSymbol} from '@angular/compiler'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as ts from 'typescript'; - -import {CompilerHost, CompilerHostContext} from './compiler_host'; -import {ModuleMetadata} from './metadata/index'; -import {CompilerOptions} from './transformers/api'; - -const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; -const DTS = /\.d\.ts$/; - -/** - * This version of the AotCompilerHost expects that the program will be compiled - * and executed with a "path mapped" directory structure, where generated files - * are in a parallel tree with the sources, and imported using a `./` relative - * import. This requires using TS `rootDirs` option and also teaching the module - * loader what to do. - */ -export class PathMappedCompilerHost extends CompilerHost { - constructor(program: ts.Program, options: CompilerOptions, context: CompilerHostContext) { - super(program, options, context); - } - - getCanonicalFileName(fileName: string): string { - if (!fileName) return fileName; - // NB: the rootDirs should have been sorted longest-first - for (const dir of this.options.rootDirs || []) { - if (fileName.indexOf(dir) === 0) { - fileName = fileName.substring(dir.length); - } - } - return fileName; - } - - moduleNameToFileName(m: string, containingFile: string): string|null { - if (!containingFile || !containingFile.length) { - if (m.indexOf('.') === 0) { - throw new Error('Resolution of relative paths requires a containing file.'); - } - // Any containing file gives the same result for absolute imports - containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts')); - } - for (const root of this.options.rootDirs || ['']) { - const rootedContainingFile = path.join(root, containingFile); - const resolved = - ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule; - if (resolved) { - if (this.options.traceResolution) { - console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName); - } - return this.getCanonicalFileName(resolved.resolvedFileName); - } - } - return null; - } - - /** - * We want a moduleId that will appear in import statements in the generated code. - * These need to be in a form that system.js can load, so absolute file paths don't work. - * Relativize the paths by checking candidate prefixes of the absolute path, to see if - * they are resolvable by the moduleResolution strategy from the CompilerHost. - */ - fileNameToModuleName(importedFile: string, containingFile: string): string { - if (this.options.traceResolution) { - console.error( - 'getImportPath from containingFile', containingFile, 'to importedFile', importedFile); - } - - // If a file does not yet exist (because we compile it later), we still need to - // assume it exists so that the `resolve` method works! - if (!this.context.fileExists(importedFile)) { - if (this.options.rootDirs && this.options.rootDirs.length > 0) { - this.context.assumeFileExists(path.join(this.options.rootDirs[0], importedFile)); - } else { - this.context.assumeFileExists(importedFile); - } - } - - const resolvable = (candidate: string) => { - const resolved = this.moduleNameToFileName(candidate, importedFile); - return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, ''); - }; - - const importModuleName = importedFile.replace(EXT, ''); - const parts = importModuleName.split(path.sep).filter(p => !!p); - let foundRelativeImport: string = undefined !; - for (let index = parts.length - 1; index >= 0; index--) { - let candidate = parts.slice(index, parts.length).join(path.sep); - if (resolvable(candidate)) { - return candidate; - } - candidate = '.' + path.sep + candidate; - if (resolvable(candidate)) { - foundRelativeImport = candidate; - } - } - - if (foundRelativeImport) return foundRelativeImport; - - // Try a relative import - const candidate = path.relative(path.dirname(containingFile), importModuleName); - if (resolvable(candidate)) { - return candidate; - } - - throw new Error( - `Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`); - } - - getMetadataFor(filePath: string): ModuleMetadata[] { - for (const root of this.options.rootDirs || []) { - const rootedPath = path.join(root, filePath); - 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). - continue; - } - if (DTS.test(rootedPath)) { - const metadatas = this.readMetadata(rootedPath); - if (metadatas) { - return metadatas; - } - } else { - // Attention: don't cache this, so that e.g. the LanguageService - // can read in changes from source files in the metadata! - const metadata = this.getMetadataForSourceFile(rootedPath); - return metadata ? [metadata] : []; - } - } - return null !; - } -} diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index 2bdd727dd0..befe24da20 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -229,6 +229,12 @@ export interface LibrarySummary { sourceFile?: ts.SourceFile; } +export interface LazyRoute { + route: string; + module: {name: string, filePath: string}; + referencedModule: {name: string, filePath: string}; +} + export interface Program { /** * Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources. @@ -293,6 +299,14 @@ export interface Program { */ loadNgStructureAsync(): Promise; + /** + * Returns the lazy routes in the program. + * @param entryRoute A reference to an NgModule like `someModule#name`. If given, + * will recursively analyze routes starting from this symbol only. + * Otherwise will list all routes for all NgModules in the program/ + */ + listLazyRoutes(entryRoute?: string): LazyRoute[]; + /** * Emit the files requested by emitFlags implied by the program. * diff --git a/packages/compiler-cli/src/transformers/compiler_host.ts b/packages/compiler-cli/src/transformers/compiler_host.ts index 0b0a34b945..a190cb551b 100644 --- a/packages/compiler-cli/src/transformers/compiler_host.ts +++ b/packages/compiler-cli/src/transformers/compiler_host.ts @@ -6,19 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {EmitterVisitorContext, ExternalReference, GeneratedFile, ParseSourceSpan, TypeScriptEmitter, collectExternalReferences, syntaxError} from '@angular/compiler'; +import {AotCompilerHost, EmitterVisitorContext, ExternalReference, GeneratedFile, ParseSourceSpan, TypeScriptEmitter, collectExternalReferences, syntaxError} from '@angular/compiler'; import * as path from 'path'; import * as ts from 'typescript'; -import {BaseAotCompilerHost} from '../compiler_host'; import {TypeCheckHost} from '../diagnostics/translate_diagnostics'; -import {ModuleMetadata} from '../metadata/index'; +import {METADATA_VERSION, ModuleMetadata} from '../metadata/index'; import {CompilerHost, CompilerOptions, LibrarySummary} from './api'; -import {GENERATED_FILES} from './util'; +import {MetadataReaderHost, createMetadataReaderCache, readMetadata} from './metadata_reader'; +import {DTS, GENERATED_FILES} from './util'; const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/; -const DTS = /\.d\.ts$/; const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; export function createCompilerHost( @@ -48,9 +47,12 @@ export interface CodeGenerator { * - AotCompilerHost for @angular/compiler * - TypeCheckHost for mapping ts errors to ng errors (via translateDiagnostics) */ -export class TsCompilerAotCompilerTypeCheckHostAdapter extends - BaseAotCompilerHost implements ts.CompilerHost, +export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHost, AotCompilerHost, TypeCheckHost { + private metadataReaderCache = createMetadataReaderCache(); + private flatModuleIndexCache = new Map(); + private flatModuleIndexNames = new Set(); + private flatModuleIndexRedirectNames = new Set(); private rootDirs: string[]; private moduleResolutionCache: ts.ModuleResolutionCache; private originalSourceFiles = new Map(); @@ -58,6 +60,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends private generatedSourceFiles = new Map(); private generatedCodeFor = new Map(); private emitter = new TypeScriptEmitter(); + private metadataReaderHost: MetadataReaderHost; + getCancellationToken: () => ts.CancellationToken; getDefaultLibLocation: () => string; trace: (s: string) => void; @@ -65,10 +69,9 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends directoryExists?: (directoryName: string) => boolean; constructor( - private rootFiles: string[], options: CompilerOptions, context: CompilerHost, + private rootFiles: string[], private options: CompilerOptions, private context: CompilerHost, private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator, private librarySummaries = new Map()) { - super(options, context); this.moduleResolutionCache = ts.createModuleResolutionCache( this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context)); const basePath = this.options.basePath !; @@ -103,6 +106,15 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends if (context.fromSummaryFileName) { this.fromSummaryFileName = context.fromSummaryFileName.bind(context); } + this.metadataReaderHost = { + cacheMetadata: () => true, + getSourceFileMetadata: (filePath) => { + const sf = this.getOriginalSourceFile(filePath); + return sf ? this.metadataProvider.getMetadata(sf) : undefined; + }, + fileExists: (filePath) => this.originalFileExists(filePath), + readFile: (filePath) => this.context.readFile(filePath), + }; } private resolveModuleName(moduleName: string, containingFile: string): ts.ResolvedModule @@ -251,14 +263,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends return sf; } - getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined { - const sf = this.getOriginalSourceFile(filePath); - if (!sf) { - return undefined; - } - return this.metadataProvider.getMetadata(sf); - } - updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile { if (!genFile.stmts) { throw new Error( @@ -415,7 +419,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends if (summary) { return summary.text; } - return super.loadSummary(filePath); + if (this.originalFileExists(filePath)) { + return this.context.readFile(filePath); + } + return null; } isSourceFile(filePath: string): boolean { @@ -424,7 +431,21 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends if (this.librarySummaries.has(filePath)) { return false; } - return super.isSourceFile(filePath); + if (GENERATED_FILES.test(filePath)) { + return false; + } + if (this.options.generateCodeForLibraries === false && DTS.test(filePath)) { + return false; + } + if (DTS.test(filePath)) { + // Check for a bundle index. + if (this.hasBundleIndex(filePath)) { + const normalFilePath = path.normalize(filePath); + return this.flatModuleIndexNames.has(normalFilePath) || + this.flatModuleIndexRedirectNames.has(normalFilePath); + } + } + return true; } readFile(fileName: string) { @@ -434,6 +455,76 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends } return this.context.readFile(fileName); } + + getMetadataFor(filePath: string): ModuleMetadata[]|undefined { + return readMetadata(filePath, this.metadataReaderHost, this.metadataReaderCache); + } + + loadResource(filePath: string): Promise|string { + if (this.context.readResource) return this.context.readResource(filePath); + if (!this.originalFileExists(filePath)) { + throw syntaxError(`Error: Resource file not found: ${filePath}`); + } + return this.context.readFile(filePath); + } + + private hasBundleIndex(filePath: string): boolean { + const checkBundleIndex = (directory: string): boolean => { + let result = this.flatModuleIndexCache.get(directory); + if (result == null) { + if (path.basename(directory) == 'node_module') { + // Don't look outside the node_modules this package is installed in. + result = false; + } else { + // A bundle index exists if the typings .d.ts file has a metadata.json that has an + // importAs. + try { + const packageFile = path.join(directory, 'package.json'); + if (this.originalFileExists(packageFile)) { + // Once we see a package.json file, assume false until it we find the bundle index. + result = false; + const packageContent: any = JSON.parse(this.context.readFile(packageFile)); + if (packageContent.typings) { + const typings = path.normalize(path.join(directory, packageContent.typings)); + if (DTS.test(typings)) { + const metadataFile = typings.replace(DTS, '.metadata.json'); + if (this.originalFileExists(metadataFile)) { + const metadata = JSON.parse(this.context.readFile(metadataFile)); + if (metadata.flatModuleIndexRedirect) { + this.flatModuleIndexRedirectNames.add(typings); + // Note: don't set result = true, + // as this would mark this folder + // as having a bundleIndex too early without + // filling the bundleIndexNames. + } else if (metadata.importAs) { + this.flatModuleIndexNames.add(typings); + result = true; + } + } + } + } + } else { + const parent = path.dirname(directory); + if (parent != directory) { + // Try the parent directory. + result = checkBundleIndex(parent); + } else { + result = false; + } + } + } catch (e) { + // If we encounter any errors assume we this isn't a bundle index. + result = false; + } + } + this.flatModuleIndexCache.set(directory, result); + } + return result; + }; + + return checkBundleIndex(path.dirname(filePath)); + } + getDefaultLibFileName = (options: ts.CompilerOptions) => this.context.getDefaultLibFileName(options) getCurrentDirectory = () => this.context.getCurrentDirectory(); diff --git a/packages/compiler-cli/src/transformers/metadata_reader.ts b/packages/compiler-cli/src/transformers/metadata_reader.ts new file mode 100644 index 0000000000..1d0264c259 --- /dev/null +++ b/packages/compiler-cli/src/transformers/metadata_reader.ts @@ -0,0 +1,128 @@ +/** + * @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 * as ts from 'typescript'; + +import {METADATA_VERSION, ModuleMetadata} from '../metadata'; + +import {DTS} from './util'; + +export interface MetadataReaderHost { + getSourceFileMetadata(filePath: string): ModuleMetadata|undefined; + cacheMetadata?(fileName: string): boolean; + fileExists(filePath: string): boolean; + readFile(filePath: string): string; +} + +export interface MetadataReaderCache { + /** + * @internal + */ + data: Map; +} + +export function createMetadataReaderCache(): MetadataReaderCache { + const data = new Map(); + return {data}; +} + +export function readMetadata( + filePath: string, host: MetadataReaderHost, cache?: MetadataReaderCache): ModuleMetadata[]| + undefined { + let metadatas = cache && cache.data.get(filePath); + if (metadatas) { + return metadatas; + } + if (host.fileExists(filePath)) { + // If the file doesn't exists then we cannot return metadata for the file. + // This will occur if the user referenced a declared module for which no file + // exists for the module (i.e. jQuery or angularjs). + if (DTS.test(filePath)) { + metadatas = readMetadataFile(host, filePath); + if (!metadatas) { + // If there is a .d.ts file but no metadata file we need to produce a + // metadata from the .d.ts file as metadata files capture reexports + // (starting with v3). + metadatas = [upgradeMetadataWithDtsData( + host, {'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)]; + } + } else { + const metadata = host.getSourceFileMetadata(filePath); + metadatas = metadata ? [metadata] : []; + } + } + if (cache && (!host.cacheMetadata || host.cacheMetadata(filePath))) { + cache.data.set(filePath, metadatas); + } + return metadatas; +} + + +function readMetadataFile(host: MetadataReaderHost, dtsFilePath: string): ModuleMetadata[]| + undefined { + const metadataPath = dtsFilePath.replace(DTS, '.metadata.json'); + if (!host.fileExists(metadataPath)) { + return undefined; + } + try { + const metadataOrMetadatas = JSON.parse(host.readFile(metadataPath)); + const metadatas: ModuleMetadata[] = metadataOrMetadatas ? + (Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) : + []; + if (metadatas.length) { + let maxMetadata = metadatas.reduce((p, c) => p.version > c.version ? p : c); + if (maxMetadata.version < METADATA_VERSION) { + metadatas.push(upgradeMetadataWithDtsData(host, maxMetadata, dtsFilePath)); + } + } + return metadatas; + } catch (e) { + console.error(`Failed to read JSON file ${metadataPath}`); + throw e; + } +} + +function upgradeMetadataWithDtsData( + host: MetadataReaderHost, oldMetadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata { + // patch v1 to v3 by adding exports and the `extends` clause. + // patch v3 to v4 by adding `interface` symbols for TypeAlias + let newMetadata: ModuleMetadata = { + '__symbolic': 'module', + 'version': METADATA_VERSION, + 'metadata': {...oldMetadata.metadata}, + }; + if (oldMetadata.exports) { + newMetadata.exports = oldMetadata.exports; + } + if (oldMetadata.importAs) { + newMetadata.importAs = oldMetadata.importAs; + } + if (oldMetadata.origins) { + newMetadata.origins = oldMetadata.origins; + } + const dtsMetadata = host.getSourceFileMetadata(dtsFilePath); + if (dtsMetadata) { + for (let prop in dtsMetadata.metadata) { + if (!newMetadata.metadata[prop]) { + newMetadata.metadata[prop] = dtsMetadata.metadata[prop]; + } + } + + // Only copy exports from exports from metadata prior to version 3. + // Starting with version 3 the collector began collecting exports and + // this should be redundant. Also, with bundler will rewrite the exports + // which will hoist the exports from modules referenced indirectly causing + // the imports to be different than the .d.ts files and using the .d.ts file + // exports would cause the StaticSymbolResolver to redirect symbols to the + // incorrect location. + if ((!oldMetadata.version || oldMetadata.version < 3) && dtsMetadata.exports) { + newMetadata.exports = dtsMetadata.exports; + } + } + return newMetadata; +} diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index a8d2303ded..7ebef35d5a 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -14,12 +14,13 @@ import * as ts from 'typescript'; import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics'; import {ModuleMetadata, createBundleIndexHost} from '../metadata/index'; -import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api'; +import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api'; import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host'; import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; import {getAngularEmitterTransformFactory} from './node_emitter_transform'; import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util'; + /** * Maximum number of files that are emitable via calling ts.Program.emit * passing individual targetSourceFiles. @@ -50,8 +51,8 @@ class AngularCompilerProgram implements Program { private emittedSourceFiles: ts.SourceFile[]|undefined; // Lazily initialized fields - private _typeCheckHost: TypeCheckHost; private _compiler: AotCompiler; + private _hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter; private _tsProgram: ts.Program; private _analyzedModules: NgAnalyzedModules|undefined; private _structuralDiagnostics: Diagnostic[]|undefined; @@ -167,7 +168,7 @@ class AngularCompilerProgram implements Program { diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken)); } }); - const {ng} = translateDiagnostics(this.typeCheckHost, diags); + const {ng} = translateDiagnostics(this.hostAdapter, diags); return ng; } @@ -175,18 +176,23 @@ class AngularCompilerProgram implements Program { if (this._analyzedModules) { throw new Error('Angular structure already loaded'); } - const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs(); - return this._compiler.loadFilesAsync(sourceFiles) + const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs(); + return this.compiler.loadFilesAsync(sourceFiles) .catch(this.catchAnalysisError.bind(this)) .then(analyzedModules => { if (this._analyzedModules) { throw new Error('Angular structure loaded both synchronously and asynchronsly'); } - this._updateProgramWithTypeCheckStubs( - tmpProgram, analyzedModules, hostAdapter, rootNames); + this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames); }); } + listLazyRoutes(route?: string): LazyRoute[] { + // Note: Don't analyzedModules if a route is given + // to be fast enough. + return this.compiler.listLazyRoutes(route, route ? undefined : this.analyzedModules); + } + emit( {emitFlags = EmitFlags.Default, cancellationToken, customTransformers, emitCallback = defaultEmitCallback}: { @@ -341,11 +347,18 @@ class AngularCompilerProgram implements Program { // Private members private get compiler(): AotCompiler { if (!this._compiler) { - this.initSync(); + this._createCompiler(); } return this._compiler !; } + private get hostAdapter(): TsCompilerAotCompilerTypeCheckHostAdapter { + if (!this._hostAdapter) { + this._createCompiler(); + } + return this._hostAdapter !; + } + private get analyzedModules(): NgAnalyzedModules { if (!this._analyzedModules) { this.initSync(); @@ -367,13 +380,6 @@ class AngularCompilerProgram implements Program { return this._tsProgram !; } - private get typeCheckHost(): TypeCheckHost { - if (!this._typeCheckHost) { - this.initSync(); - } - return this._typeCheckHost !; - } - private calculateTransforms( genFiles: Map, customTransformers?: CustomTransformers): ts.CustomTransformers { @@ -393,19 +399,41 @@ class AngularCompilerProgram implements Program { if (this._analyzedModules) { return; } - const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs(); + const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs(); let analyzedModules: NgAnalyzedModules|null; try { - analyzedModules = this._compiler.loadFilesSync(sourceFiles); + analyzedModules = this.compiler.loadFilesSync(sourceFiles); } catch (e) { analyzedModules = this.catchAnalysisError(e); } - this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, hostAdapter, rootNames); + this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames); + } + + private _createCompiler() { + const codegen: CodeGenerator = { + generateFile: (genFileName, baseFileName) => + this._compiler.emitBasicStub(genFileName, baseFileName), + findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName), + }; + + this._hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter( + this.rootNames, this.options, this.host, this.metadataCache, codegen, + this.oldProgramLibrarySummaries); + const aotOptions = getAotCompilerOptions(this.options); + this._structuralDiagnostics = []; + const errorCollector = (err: any) => { + this._structuralDiagnostics !.push({ + messageText: err.toString(), + category: ts.DiagnosticCategory.Error, + source: SOURCE, + code: DEFAULT_ERROR_CODE + }); + }; + this._compiler = createAotCompiler(this._hostAdapter, aotOptions, errorCollector).compiler; } private _createProgramWithBasicStubs(): { tmpProgram: ts.Program, - hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[], sourceFiles: string[], } { @@ -418,58 +446,56 @@ class AngularCompilerProgram implements Program { const codegen: CodeGenerator = { generateFile: (genFileName, baseFileName) => - this._compiler.emitBasicStub(genFileName, baseFileName), - findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName), + this.compiler.emitBasicStub(genFileName, baseFileName), + findGeneratedFileNames: (fileName) => this.compiler.findGeneratedFileNames(fileName), }; - const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter( - this.rootNames, this.options, this.host, this.metadataCache, codegen, - this.oldProgramLibrarySummaries); - const aotOptions = getAotCompilerOptions(this.options); - this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler; - this._typeCheckHost = hostAdapter; - this._structuralDiagnostics = []; - let rootNames = - this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn)); + let rootNames = this.rootNames; + if (this.options.generateCodeForLibraries !== false) { + // if we should generateCodeForLibraries, enver include + // generated files in the program as otherwise we will + // ovewrite them and typescript will report the error + // TS5055: Cannot write file ... because it would overwrite input file. + rootNames = this.rootNames.filter(fn => !GENERATED_FILES.test(fn)); + } if (this.options.noResolve) { this.rootNames.forEach(rootName => { - if (hostAdapter.shouldGenerateFilesFor(rootName)) { - rootNames.push(...this._compiler.findGeneratedFileNames(rootName)); + if (this.hostAdapter.shouldGenerateFilesFor(rootName)) { + rootNames.push(...this.compiler.findGeneratedFileNames(rootName)); } }); } - const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram); + const tmpProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, oldTsProgram); const sourceFiles: string[] = []; tmpProgram.getSourceFiles().forEach(sf => { - if (hostAdapter.isSourceFile(sf.fileName)) { + if (this.hostAdapter.isSourceFile(sf.fileName)) { sourceFiles.push(sf.fileName); } }); - return {tmpProgram, sourceFiles, hostAdapter, rootNames}; + return {tmpProgram, sourceFiles, rootNames}; } private _updateProgramWithTypeCheckStubs( - tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null, - hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) { + tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null, rootNames: string[]) { this._analyzedModules = analyzedModules || emptyModules; if (analyzedModules) { tmpProgram.getSourceFiles().forEach(sf => { if (sf.fileName.endsWith('.ngfactory.ts')) { - const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName); + const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName); if (generate) { // Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName // for .ngfactory.ts files. - const genFile = this._compiler.emitTypeCheckStub(sf.fileName, baseFileName !); + const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName !); if (genFile) { - hostAdapter.updateGeneratedFile(genFile); + this.hostAdapter.updateGeneratedFile(genFile); } } } }); } - this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram); + this._tsProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, tmpProgram); // Note: the new ts program should be completely reusable by TypeScript as: // - we cache all the files in the hostAdapter // - new new stubs use the exactly same imports/exports as the old once (we assert that in diff --git a/packages/compiler-cli/src/transformers/util.ts b/packages/compiler-cli/src/transformers/util.ts index ec774fcde3..45aafdfbae 100644 --- a/packages/compiler-cli/src/transformers/util.ts +++ b/packages/compiler-cli/src/transformers/util.ts @@ -11,6 +11,7 @@ import * as ts from 'typescript'; import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api'; export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/; +export const DTS = /\.d\.ts$/; export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2} diff --git a/packages/compiler-cli/test/aot_host_spec.ts b/packages/compiler-cli/test/aot_host_spec.ts deleted file mode 100644 index 9f9b5e3bba..0000000000 --- a/packages/compiler-cli/test/aot_host_spec.ts +++ /dev/null @@ -1,310 +0,0 @@ -/** - * @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 {METADATA_VERSION, ModuleMetadata} from '@angular/compiler-cli'; -import * as ts from 'typescript'; - -import {CompilerHost} from '../src/compiler_host'; - -import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks'; - -describe('CompilerHost', () => { - let context: MockAotContext; - let program: ts.Program; - let hostNestedGenDir: CompilerHost; - let hostSiblingGenDir: CompilerHost; - - beforeEach(() => { - context = new MockAotContext('/tmp/src', clone(FILES)); - const host = new MockCompilerHost(context); - program = ts.createProgram( - ['main.ts'], { - module: ts.ModuleKind.CommonJS, - }, - host); - // Force a typecheck - const errors = program.getSemanticDiagnostics(); - if (errors && errors.length) { - throw new Error('Expected no errors'); - } - hostNestedGenDir = new CompilerHost( - program, { - genDir: '/tmp/project/src/gen/', - basePath: '/tmp/project/src', - skipMetadataEmit: false, - strictMetadataEmit: false, - skipTemplateCodegen: false, - trace: false - }, - context); - hostSiblingGenDir = new CompilerHost( - program, { - genDir: '/tmp/project/gen', - basePath: '/tmp/project/src/', - skipMetadataEmit: false, - strictMetadataEmit: false, - skipTemplateCodegen: false, - trace: false - }, - context); - }); - - describe('nestedGenDir', () => { - it('should import node_module from factory', () => { - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/node_modules/@angular/core.d.ts', - '/tmp/project/src/gen/my.ngfactory.ts', )) - .toEqual('@angular/core'); - }); - - it('should import factory from factory', () => { - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('./my.other.ngfactory'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.css.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts')) - .toEqual('../my.other.css.ngstyle'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('./a/my.other.shim.ngstyle'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts')) - .toEqual('../my.other.sass.ngstyle'); - }); - - it('should import application from factory', () => { - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('../my.other'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts')) - .toEqual('../../my.other'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('../a/my.other'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('../a/my.other.css'); - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('../a/my.other.css.shim'); - }); - }); - - describe('siblingGenDir', () => { - it('should import node_module from factory', () => { - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/node_modules/@angular/core.d.ts', - '/tmp/project/src/gen/my.ngfactory.ts')) - .toEqual('@angular/core'); - }); - - it('should import factory from factory', () => { - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('./my.other.ngfactory'); - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts')) - .toEqual('../my.other.css'); - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('./a/my.other.css.shim'); - }); - - it('should import application from factory', () => { - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('./my.other'); - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts')) - .toEqual('../my.other'); - expect(hostSiblingGenDir.fileNameToModuleName( - '/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts')) - .toEqual('./a/my.other'); - }); - }); - - it('should be able to produce an import from main @angular/core', () => { - expect(hostNestedGenDir.fileNameToModuleName( - '/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts')) - .toEqual('@angular/core'); - }); - - it('should be able to produce an import to a shallow import', () => { - expect(hostNestedGenDir.fileNameToModuleName('@angular/core', '/tmp/project/src/main.ts')) - .toEqual('@angular/core'); - expect(hostNestedGenDir.fileNameToModuleName( - '@angular/upgrade/static', '/tmp/project/src/main.ts')) - .toEqual('@angular/upgrade/static'); - expect(hostNestedGenDir.fileNameToModuleName('myLibrary', '/tmp/project/src/main.ts')) - .toEqual('myLibrary'); - expect(hostNestedGenDir.fileNameToModuleName('lib23-43', '/tmp/project/src/main.ts')) - .toEqual('lib23-43'); - }); - - it('should be able to produce an import from main to a sub-directory', () => { - expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils'); - }); - - it('should be able to produce an import from to a peer file', () => { - expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts')) - .toEqual('./collections'); - }); - - it('should be able to produce an import from to a sibling directory', () => { - expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts')) - .toEqual('../lib/utils'); - }); - - it('should be able to read a metadata file', () => { - expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([ - {__symbolic: 'module', version: METADATA_VERSION, metadata: {foo: {__symbolic: 'class'}}} - ]); - }); - - it('should be able to read metadata from an otherwise unused .d.ts file ', () => { - expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([ - dummyMetadata - ]); - }); - - it('should be able to read empty metadata ', () => { - expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]); - }); - - it('should return undefined for missing modules', () => { - expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined(); - }); - - it(`should add missing v${METADATA_VERSION} metadata from v1 metadata and .d.ts files`, () => { - expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([ - {__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, { - __symbolic: 'module', - version: METADATA_VERSION, - metadata: { - foo: {__symbolic: 'class'}, - aType: {__symbolic: 'interface'}, - Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}}, - BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}, - ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'}, - }, - exports: [{from: './lib/utils2', export: ['Export']}], - } - ]); - }); - - it(`should upgrade a missing metadata file into v${METADATA_VERSION}`, () => { - expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1_empty.d.ts')).toEqual([{ - __symbolic: 'module', - version: METADATA_VERSION, - metadata: {}, - exports: [{from: './lib/utils'}] - }]); - }); - - it(`should upgrade v3 metadata into v${METADATA_VERSION}`, () => { - expect(hostNestedGenDir.getMetadataFor('metadata_versions/v3.d.ts')).toEqual([ - {__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}, { - __symbolic: 'module', - version: METADATA_VERSION, - metadata: { - foo: {__symbolic: 'class'}, - aType: {__symbolic: 'interface'}, - Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}}, - BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}, - ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'}, - } - // Note: exports is missing because it was elided in the original. - } - ]); - }); -}); - -const dummyModule = 'export let foo: any[];'; -const dummyMetadata: ModuleMetadata = { - __symbolic: 'module', - version: METADATA_VERSION, - metadata: - {foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}} -}; -const FILES: Entry = { - 'tmp': { - 'src': { - 'main.ts': ` - import * as c from '@angular/core'; - import * as r from '@angular/router'; - import * as u from './lib/utils'; - import * as cs from './lib/collections'; - import * as u2 from './lib2/utils2'; - `, - 'lib': { - 'utils.ts': dummyModule, - 'collections.ts': dummyModule, - }, - 'lib2': {'utils2.ts': dummyModule}, - 'node_modules': { - '@angular': { - 'core.d.ts': dummyModule, - 'core.metadata.json': - `{"__symbolic":"module", "version": ${METADATA_VERSION}, "metadata": {"foo": {"__symbolic": "class"}}}`, - 'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}}, - 'unused.d.ts': dummyModule, - 'empty.d.ts': 'export declare var a: string;', - 'empty.metadata.json': '[]', - } - }, - 'metadata_versions': { - 'v1.d.ts': ` - import {ReExport} from './lib/utils2'; - export {ReExport}; - - export {Export} from './lib/utils2'; - - export type aType = number; - - export declare class Bar { - ngOnInit() {} - } - export declare class BarChild extends Bar {} - `, - 'v1.metadata.json': - `{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`, - 'v1_empty.d.ts': ` - export * from './lib/utils'; - `, - 'v3.d.ts': ` - import {ReExport} from './lib/utils2'; - export {ReExport}; - - export {Export} from './lib/utils2'; - - export type aType = number; - - export declare class Bar { - ngOnInit() {} - } - export declare class BarChild extends Bar {} - `, - 'v3.metadata.json': - `{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`, - } - } - } -}; - -function clone(entry: Entry): Entry { - if (typeof entry === 'string') { - return entry; - } else { - const result: Directory = {}; - for (const name in entry) { - result[name] = clone(entry[name]); - } - return result; - } -} diff --git a/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts b/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts index 6d95a5b9df..bffd7f3db8 100644 --- a/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts +++ b/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts @@ -8,6 +8,7 @@ import {StaticSymbol} from '@angular/compiler'; import {CompilerHost} from '@angular/compiler-cli'; +import {ReflectorHost} from '@angular/language-service/src/reflector_host'; import * as ts from 'typescript'; import {getExpressionDiagnostics, getTemplateExpressionDiagnostics} from '../../src/diagnostics/expression_diagnostics'; @@ -21,7 +22,6 @@ describe('expression diagnostics', () => { let host: MockLanguageServiceHost; let service: ts.LanguageService; let context: DiagnosticContext; - let aotHost: CompilerHost; let type: StaticSymbol; beforeAll(() => { @@ -33,8 +33,8 @@ describe('expression diagnostics', () => { const options: CompilerOptions = Object.create(host.getCompilationSettings()); options.genDir = '/dist'; options.basePath = '/src'; - aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true}); - context = new DiagnosticContext(service, program, checker, aotHost); + const symbolResolverHost = new ReflectorHost(() => program, host, options); + context = new DiagnosticContext(service, program, checker, symbolResolverHost); type = context.getStaticSymbol('app/app.component.ts', 'AppComponent'); }); diff --git a/packages/compiler-cli/test/diagnostics/mocks.ts b/packages/compiler-cli/test/diagnostics/mocks.ts index 1f7965b845..13cbbce3d0 100644 --- a/packages/compiler-cli/test/diagnostics/mocks.ts +++ b/packages/compiler-cli/test/diagnostics/mocks.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompilerHost, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, InterpolationConfig, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, SummaryResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver} from '@angular/compiler'; +import {AotCompilerHost, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, InterpolationConfig, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, SummaryResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver} from '@angular/compiler'; import {ViewEncapsulation, ɵConsole as Console} from '@angular/core'; -import {CompilerHostContext} from 'compiler-cli'; import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; @@ -25,7 +24,7 @@ function calcRootPath() { const realFiles = new Map(); -export class MockLanguageServiceHost implements ts.LanguageServiceHost, CompilerHostContext { +export class MockLanguageServiceHost implements ts.LanguageServiceHost { private options: ts.CompilerOptions; private context: MockAotContext; private assumedExist = new Set(); @@ -122,7 +121,7 @@ export class DiagnosticContext { constructor( public service: ts.LanguageService, public program: ts.Program, - public checker: ts.TypeChecker, public host: AotCompilerHost) {} + public checker: ts.TypeChecker, public host: StaticSymbolResolverHost) {} private collectError(e: any, path?: string) { this._errors.push({e, path}); } diff --git a/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts b/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts index 624b810b30..7073f8958d 100644 --- a/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts +++ b/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts @@ -9,6 +9,7 @@ import {StaticSymbol} from '@angular/compiler'; import {CompilerHost} from '@angular/compiler-cli'; import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util'; +import {ReflectorHost} from '@angular/language-service/src/reflector_host'; import * as ts from 'typescript'; import {Symbol, SymbolQuery, SymbolTable} from '../../src/diagnostics/symbols'; @@ -44,8 +45,8 @@ describe('symbol query', () => { const options: CompilerOptions = Object.create(host.getCompilationSettings()); options.genDir = '/dist'; options.basePath = '/quickstart'; - const aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true}); - context = new DiagnosticContext(service, program, checker, aotHost); + const symbolResolverHost = new ReflectorHost(() => program, host, options); + context = new DiagnosticContext(service, program, checker, symbolResolverHost); query = getSymbolQuery(program, checker, sourceFile, emptyPipes); }); diff --git a/packages/compiler-cli/test/mocks.ts b/packages/compiler-cli/test/mocks.ts index 88503a67ae..9c867bd94f 100644 --- a/packages/compiler-cli/test/mocks.ts +++ b/packages/compiler-cli/test/mocks.ts @@ -6,14 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host'; import * as ts from 'typescript'; export type Entry = string | Directory; export interface Directory { [name: string]: Entry; } -export class MockAotContext implements CompilerHostContext { +export class MockAotContext { constructor(public currentDirectory: string, private files: Entry) {} fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; } diff --git a/packages/compiler-cli/test/ngtools_api_spec.ts b/packages/compiler-cli/test/ngtools_api_spec.ts new file mode 100644 index 0000000000..b20d9e7054 --- /dev/null +++ b/packages/compiler-cli/test/ngtools_api_spec.ts @@ -0,0 +1,90 @@ +/** + * @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 * as path from 'path'; +import * as ts from 'typescript'; + +import {NgTools_InternalApi_NG_2} from '../src/ngtools_api'; + +import {TestSupport, setup} from './test_support'; + +describe('ngtools_api (deprecated)', () => { + let testSupport: TestSupport; + + beforeEach(() => { testSupport = setup(); }); + + function createProgram(rootNames: string[]) { + const options = testSupport.createCompilerOptions(); + const host = ts.createCompilerHost(options, true); + const program = + ts.createProgram(rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host); + return {program, host, options}; + } + + function writeSomeRoutes() { + testSupport.writeFiles({ + 'src/main.ts': ` + import {NgModule, Component} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + // Component with metadata errors. + @Component(() => {if (1==1) return null as any;}) + export class ErrorComp2 {} + + @NgModule({ + declarations: [ErrorComp2, NonExistentComp], + imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])] + }) + export class MainModule {} + `, + 'src/child.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @NgModule({ + imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])] + }) + export class ChildModule {} + `, + 'src/child2.ts': ` + import {NgModule} from '@angular/core'; + + @NgModule() + export class ChildModule2 {} + `, + }); + } + + it('should list lazy routes recursively', () => { + writeSomeRoutes(); + const {program, host, options} = createProgram(['src/main.ts']); + const routes = NgTools_InternalApi_NG_2.listLazyRoutes({ + program, + host, + angularCompilerOptions: options, + entryModule: 'src/main#MainModule', + }); + expect(routes).toEqual({ + './child#ChildModule': path.resolve(testSupport.basePath, 'src/child.ts'), + './child2#ChildModule2': path.resolve(testSupport.basePath, 'src/child2.ts'), + }); + }); + + it('should allow to emit the program after analyzing routes', () => { + writeSomeRoutes(); + const {program, host, options} = createProgram(['src/main.ts']); + NgTools_InternalApi_NG_2.listLazyRoutes({ + program, + host, + angularCompilerOptions: options, + entryModule: 'src/main#MainModule', + }); + program.emit(); + testSupport.shouldExist('built/src/main.js'); + }); +}); diff --git a/packages/compiler-cli/test/transformers/metadata_reader_spec.ts b/packages/compiler-cli/test/transformers/metadata_reader_spec.ts new file mode 100644 index 0000000000..306c215483 --- /dev/null +++ b/packages/compiler-cli/test/transformers/metadata_reader_spec.ts @@ -0,0 +1,177 @@ +/** + * @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 * as ts from 'typescript'; + +import {METADATA_VERSION, MetadataCollector, ModuleMetadata} from '../../src/metadata'; +import {MetadataReaderHost, readMetadata} from '../../src/transformers/metadata_reader'; +import {Directory, Entry, MockAotContext} from '../mocks'; + +describe('metadata reader', () => { + let host: MetadataReaderHost; + + beforeEach(() => { + const context = new MockAotContext('/tmp/src', clone(FILES)); + const metadataCollector = new MetadataCollector(); + host = { + fileExists: (fileName) => context.fileExists(fileName), + readFile: (fileName) => context.readFile(fileName), + getSourceFileMetadata: (fileName) => { + const sourceText = context.readFile(fileName); + return sourceText != null ? + metadataCollector.getMetadata( + ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest)) : + undefined; + }, + }; + }); + + + it('should be able to read a metadata file', () => { + expect(readMetadata('node_modules/@angular/core.d.ts', host)).toEqual([ + {__symbolic: 'module', version: METADATA_VERSION, metadata: {foo: {__symbolic: 'class'}}} + ]); + }); + + it('should be able to read metadata from an otherwise unused .d.ts file ', () => { + expect(readMetadata('node_modules/@angular/unused.d.ts', host)).toEqual([dummyMetadata]); + }); + + it('should be able to read empty metadata ', + () => { expect(readMetadata('node_modules/@angular/empty.d.ts', host)).toEqual([]); }); + + it('should return undefined for missing modules', + () => { expect(readMetadata('node_modules/@angular/missing.d.ts', host)).toBeUndefined(); }); + + it(`should add missing v${METADATA_VERSION} metadata from v1 metadata and .d.ts files`, () => { + expect(readMetadata('metadata_versions/v1.d.ts', host)).toEqual([ + {__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, { + __symbolic: 'module', + version: METADATA_VERSION, + metadata: { + foo: {__symbolic: 'class'}, + aType: {__symbolic: 'interface'}, + Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}}, + BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}, + ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'}, + }, + exports: [{from: './lib/utils2', export: ['Export']}], + } + ]); + }); + + it(`should upgrade a missing metadata file into v${METADATA_VERSION}`, () => { + expect(readMetadata('metadata_versions/v1_empty.d.ts', host)).toEqual([{ + __symbolic: 'module', + version: METADATA_VERSION, + metadata: {}, + exports: [{from: './lib/utils'}] + }]); + }); + + it(`should upgrade v3 metadata into v${METADATA_VERSION}`, () => { + expect(readMetadata('metadata_versions/v3.d.ts', host)).toEqual([ + {__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}, { + __symbolic: 'module', + version: METADATA_VERSION, + metadata: { + foo: {__symbolic: 'class'}, + aType: {__symbolic: 'interface'}, + Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}}, + BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}, + ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'}, + } + // Note: exports is missing because it was elided in the original. + } + ]); + }); +}); + +const dummyModule = 'export let foo: any[];'; +const dummyMetadata: ModuleMetadata = { + __symbolic: 'module', + version: METADATA_VERSION, + metadata: + {foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}} +}; +const FILES: Entry = { + 'tmp': { + 'src': { + 'main.ts': ` + import * as c from '@angular/core'; + import * as r from '@angular/router'; + import * as u from './lib/utils'; + import * as cs from './lib/collections'; + import * as u2 from './lib2/utils2'; + `, + 'lib': { + 'utils.ts': dummyModule, + 'collections.ts': dummyModule, + }, + 'lib2': {'utils2.ts': dummyModule}, + 'node_modules': { + '@angular': { + 'core.d.ts': dummyModule, + 'core.metadata.json': + `{"__symbolic":"module", "version": ${METADATA_VERSION}, "metadata": {"foo": {"__symbolic": "class"}}}`, + 'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}}, + 'unused.d.ts': dummyModule, + 'empty.d.ts': 'export declare var a: string;', + 'empty.metadata.json': '[]', + } + }, + 'metadata_versions': { + 'v1.d.ts': ` + import {ReExport} from './lib/utils2'; + export {ReExport}; + + export {Export} from './lib/utils2'; + + export type aType = number; + + export declare class Bar { + ngOnInit() {} + } + export declare class BarChild extends Bar {} + `, + 'v1.metadata.json': + `{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`, + 'v1_empty.d.ts': ` + export * from './lib/utils'; + `, + 'v3.d.ts': ` + import {ReExport} from './lib/utils2'; + export {ReExport}; + + export {Export} from './lib/utils2'; + + export type aType = number; + + export declare class Bar { + ngOnInit() {} + } + export declare class BarChild extends Bar {} + `, + 'v3.metadata.json': + `{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`, + } + } + } +}; + +function clone(entry: Entry): Entry { + if (typeof entry === 'string') { + return entry; + } else { + const result: Directory = {}; + for (const name in entry) { + result[name] = clone(entry[name]); + } + return result; + } +} diff --git a/packages/compiler-cli/test/transformers/program_spec.ts b/packages/compiler-cli/test/transformers/program_spec.ts index 100d08fa66..aea2c328dd 100644 --- a/packages/compiler-cli/test/transformers/program_spec.ts +++ b/packages/compiler-cli/test/transformers/program_spec.ts @@ -11,7 +11,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; -import {CompilerHost} from '../../src/transformers/api'; +import {CompilerHost, LazyRoute} from '../../src/transformers/api'; import {createSrcToOutPathMapper} from '../../src/transformers/program'; import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from '../../src/transformers/util'; import {TestSupport, expectNoDiagnosticsInProgram, setup} from '../test_support'; @@ -508,4 +508,287 @@ describe('ng program', () => { expect(mapper('c:\\tmp\\b\\y.js')).toBe('c:\\tmp\\out\\b\\y.js'); }); }); + + describe('listLazyRoutes', () => { + function writeSomeRoutes() { + testSupport.writeFiles({ + 'src/main.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @NgModule({ + imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])] + }) + export class MainModule {} + `, + 'src/child.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @NgModule({ + imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])] + }) + export class ChildModule {} + `, + 'src/child2.ts': ` + import {NgModule} from '@angular/core'; + + @NgModule() + export class ChildModule2 {} + `, + }); + } + + function createProgram(rootNames: string[]) { + const options = testSupport.createCompilerOptions(); + const host = ng.createCompilerHost({options}); + const program = ng.createProgram( + {rootNames: rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host}); + return {program, options}; + } + + function normalizeRoutes(lazyRoutes: LazyRoute[]) { + return lazyRoutes.map( + r => ({ + route: r.route, + module: {name: r.module.name, filePath: r.module.filePath}, + referencedModule: + {name: r.referencedModule.name, filePath: r.referencedModule.filePath}, + })); + } + + it('should list all lazyRoutes', () => { + writeSomeRoutes(); + const {program, options} = createProgram(['src/main.ts', 'src/child.ts', 'src/child2.ts']); + expectNoDiagnosticsInProgram(options, program); + expect(normalizeRoutes(program.listLazyRoutes())).toEqual([ + { + module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')}, + referencedModule: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + route: './child#ChildModule' + }, + { + module: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + referencedModule: + {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')}, + route: './child2#ChildModule2' + }, + ]); + }); + + it('should list lazyRoutes given an entryRoute recursively', () => { + writeSomeRoutes(); + const {program, options} = createProgram(['src/main.ts']); + expectNoDiagnosticsInProgram(options, program); + expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([ + { + module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')}, + referencedModule: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + route: './child#ChildModule' + }, + { + module: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + referencedModule: + {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')}, + route: './child2#ChildModule2' + }, + ]); + + expect(normalizeRoutes(program.listLazyRoutes('src/child#ChildModule'))).toEqual([ + { + module: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + referencedModule: + {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')}, + route: './child2#ChildModule2' + }, + ]); + }); + + it('should list lazyRoutes pointing to a default export', () => { + testSupport.writeFiles({ + 'src/main.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @NgModule({ + imports: [RouterModule.forRoot([{loadChildren: './child'}])] + }) + export class MainModule {} + `, + 'src/child.ts': ` + import {NgModule} from '@angular/core'; + + @NgModule() + export default class ChildModule {} + `, + }); + const {program, options} = createProgram(['src/main.ts']); + expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([ + { + module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')}, + referencedModule: + {name: undefined, filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + route: './child' + }, + ]); + }); + + it('should list lazyRoutes from imported modules', () => { + testSupport.writeFiles({ + 'src/main.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + import {NestedMainModule} from './nested/main'; + + @NgModule({ + imports: [ + RouterModule.forRoot([{loadChildren: './child#ChildModule'}]), + NestedMainModule, + ] + }) + export class MainModule {} + `, + 'src/child.ts': ` + import {NgModule} from '@angular/core'; + + @NgModule() + export class ChildModule {} + `, + 'src/nested/main.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @NgModule({ + imports: [RouterModule.forChild([{loadChildren: './child#NestedChildModule'}])] + }) + export class NestedMainModule {} + `, + 'src/nested/child.ts': ` + import {NgModule} from '@angular/core'; + + @NgModule() + export class NestedChildModule {} + `, + }); + const {program, options} = createProgram(['src/main.ts']); + expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([ + { + module: { + name: 'NestedMainModule', + filePath: path.resolve(testSupport.basePath, 'src/nested/main.ts') + }, + referencedModule: { + name: 'NestedChildModule', + filePath: path.resolve(testSupport.basePath, 'src/nested/child.ts') + }, + route: './child#NestedChildModule' + }, + { + module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')}, + referencedModule: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + route: './child#ChildModule' + }, + ]); + }); + + it('should dedupe lazyRoutes given an entryRoute', () => { + writeSomeRoutes(); + testSupport.writeFiles({ + 'src/index.ts': ` + import {NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @NgModule({ + imports: [ + RouterModule.forRoot([{loadChildren: './main#MainModule'}]), + RouterModule.forRoot([{loadChildren: './child#ChildModule'}]), + ] + }) + export class MainModule {} + `, + }); + const {program, options} = createProgram(['src/index.ts']); + expectNoDiagnosticsInProgram(options, program); + expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([ + { + module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')}, + referencedModule: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + route: './child#ChildModule' + }, + { + module: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + referencedModule: + {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')}, + route: './child2#ChildModule2' + }, + ]); + }); + + it('should list lazyRoutes given an entryRoute even with static errors', () => { + testSupport.writeFiles({ + 'src/main.ts': ` + import {NgModule, Component} from '@angular/core'; + import {RouterModule} from '@angular/router'; + + @Component({ + selector: 'url-comp', + // Non existent external template + templateUrl: 'non-existent.html', + }) + export class ErrorComp {} + + @Component({ + selector: 'err-comp', + // Error in template + template: '{{', + }) + export class ErrorComp2 {} + + // Component with metadata errors. + @Component(() => {if (1==1) return null as any;}) + export class ErrorComp3 {} + + // Unused component + @Component({ + selector: 'unused-comp', + template: '' + }) + export class UnusedComp {} + + @NgModule({ + declarations: [ErrorComp, ErrorComp2, ErrorComp3, NonExistentComp], + imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])] + }) + export class MainModule {} + + @NgModule({ + // Component used in 2 NgModules + declarations: [ErrorComp], + }) + export class Mod2 {} + `, + 'src/child.ts': ` + import {NgModule} from '@angular/core'; + + @NgModule() + export class ChildModule {} + `, + }); + const program = createProgram(['src/main.ts']).program; + expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([{ + module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')}, + referencedModule: + {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')}, + route: './child#ChildModule' + }]); + }); + }); }); diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 7a8c5cf20b..3e2eda7ef5 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl} from '../compile_metadata'; +import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata'; import {CompilerConfig} from '../config'; import {ViewEncapsulation} from '../core'; import {MessageBundle} from '../i18n/message_bundle'; @@ -29,6 +29,7 @@ import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; import {AotCompilerHost} from './compiler_host'; import {AotCompilerOptions} from './compiler_options'; import {GeneratedFile} from './generated_file'; +import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes'; import {StaticReflector} from './static_reflector'; import {StaticSymbol} from './static_symbol'; import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; @@ -500,13 +501,13 @@ export class AotCompiler { } const arity = this._symbolResolver.getTypeArity(symbol) || 0; const {filePath, name, members} = this._symbolResolver.getImportAs(symbol) || symbol; - const importModule = this._symbolResolver.fileNameToModuleName(filePath, genFilePath); + const importModule = this._fileNameToModuleName(filePath, genFilePath); // It should be good enough to compare filePath to genFilePath and if they are equal // there is a self reference. However, ngfactory files generate to .ts but their // symbols have .d.ts so a simple compare is insufficient. They should be canonical // and is tracked by #17705. - const selfReference = this._symbolResolver.fileNameToModuleName(genFilePath, genFilePath); + const selfReference = this._fileNameToModuleName(genFilePath, genFilePath); const moduleName = importModule === selfReference ? null : importModule; // If we are in a type expression that refers to a generic type then supply @@ -527,6 +528,12 @@ export class AotCompiler { return {statements: [], genFilePath, importExpr}; } + private _fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { + return this._summaryResolver.getKnownModuleName(importedFilePath) || + this._symbolResolver.getKnownModuleName(importedFilePath) || + this._host.fileNameToModuleName(importedFilePath, containingFilePath); + } + private _codegenStyles( srcFileUrl: string, compMeta: CompileDirectiveMetadata, stylesheetMetadata: CompileStylesheetMetadata, isShimmed: boolean, @@ -542,6 +549,43 @@ export class AotCompiler { private _codegenSourceModule(srcFileUrl: string, ctx: OutputContext): GeneratedFile { return new GeneratedFile(srcFileUrl, ctx.genFilePath, ctx.statements); } + + listLazyRoutes(entryRoute?: string, analyzedModules?: NgAnalyzedModules): LazyRoute[] { + const self = this; + if (entryRoute) { + const symbol = parseLazyRoute(entryRoute, this._reflector).referencedModule; + return visitLazyRoute(symbol); + } else if (analyzedModules) { + const allLazyRoutes: LazyRoute[] = []; + for (const ngModule of analyzedModules.ngModules) { + const lazyRoutes = listLazyRoutes(ngModule, this._reflector); + for (const lazyRoute of lazyRoutes) { + allLazyRoutes.push(lazyRoute); + } + } + return allLazyRoutes; + } else { + throw new Error(`Either route or analyzedModules has to be specified!`); + } + + function visitLazyRoute( + symbol: StaticSymbol, seenRoutes = new Set(), + allLazyRoutes: LazyRoute[] = []): LazyRoute[] { + // Support pointing to default exports, but stop recursing there, + // as the StaticReflector does not yet support default exports. + if (seenRoutes.has(symbol) || !symbol.name) { + return allLazyRoutes; + } + seenRoutes.add(symbol); + const lazyRoutes = listLazyRoutes( + self._metadataResolver.getNgModuleMetadata(symbol, true) !, self._reflector); + for (const lazyRoute of lazyRoutes) { + allLazyRoutes.push(lazyRoute); + visitLazyRoute(lazyRoute.referencedModule, seenRoutes, allLazyRoutes); + } + return allLazyRoutes; + } + } } function _createEmptyStub(outputCtx: OutputContext) { diff --git a/packages/compiler/src/aot/compiler_factory.ts b/packages/compiler/src/aot/compiler_factory.ts index 776688a975..2bfd4b91df 100644 --- a/packages/compiler/src/aot/compiler_factory.ts +++ b/packages/compiler/src/aot/compiler_factory.ts @@ -52,15 +52,18 @@ export function createAotUrlResolver(host: { /** * Creates a new AotCompiler based on options and a host. */ -export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions): - {compiler: AotCompiler, reflector: StaticReflector} { +export function createAotCompiler( + compilerHost: AotCompilerHost, options: AotCompilerOptions, + errorCollector: (error: any, type?: any) => + void): {compiler: AotCompiler, reflector: StaticReflector} { let translations: string = options.translations || ''; const urlResolver = createAotUrlResolver(compilerHost); const symbolCache = new StaticSymbolCache(); const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache); const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver); - const staticReflector = new StaticReflector(summaryResolver, symbolResolver); + const staticReflector = + new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector); const htmlParser = new I18NHtmlParser( new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console); const config = new CompilerConfig({ @@ -80,7 +83,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom const resolver = new CompileMetadataResolver( config, htmlParser, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, - elementSchemaRegistry, normalizer, console, symbolCache, staticReflector); + elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector); // TODO(vicb): do not pass options.i18nFormat here const viewCompiler = new ViewCompiler(staticReflector); const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector); diff --git a/packages/compiler/src/aot/compiler_host.ts b/packages/compiler/src/aot/compiler_host.ts index 00bbf1e45b..9cf72642e9 100644 --- a/packages/compiler/src/aot/compiler_host.ts +++ b/packages/compiler/src/aot/compiler_host.ts @@ -14,6 +14,13 @@ import {AotSummaryResolverHost} from './summary_resolver'; * services and from underlying file systems. */ export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost { + /** + * Converts a file path to a module name that can be used as an `import. + * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`. + * + * See ImportResolver. + */ + fileNameToModuleName(importedFilePath: string, containingFilePath: string): string; /** * Converts a path that refers to a resource into an absolute filePath * that can be later on used for loading the resource via `loadResource. diff --git a/packages/compiler/src/aot/lazy_routes.ts b/packages/compiler/src/aot/lazy_routes.ts new file mode 100644 index 0000000000..ab32b34d1c --- /dev/null +++ b/packages/compiler/src/aot/lazy_routes.ts @@ -0,0 +1,62 @@ +/** + * @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 {CompileNgModuleMetadata, tokenReference} from '../compile_metadata'; +import {Route} from '../core'; +import {CompileMetadataResolver} from '../metadata_resolver'; + +import {AotCompilerHost} from './compiler_host'; +import {StaticReflector} from './static_reflector'; +import {StaticSymbol} from './static_symbol'; + +export interface LazyRoute { + module: StaticSymbol; + route: string; + referencedModule: StaticSymbol; +} + +export function listLazyRoutes( + moduleMeta: CompileNgModuleMetadata, reflector: StaticReflector): LazyRoute[] { + const allLazyRoutes: LazyRoute[] = []; + for (const {provider, module} of moduleMeta.transitiveModule.providers) { + if (tokenReference(provider.token) === reflector.ROUTES) { + const loadChildren = _collectLoadChildren(provider.useValue); + for (const route of loadChildren) { + allLazyRoutes.push(parseLazyRoute(route, reflector, module.reference)); + } + } + } + return allLazyRoutes; +} + +function _collectLoadChildren(routes: string | Route | Route[], target: string[] = []): string[] { + if (typeof routes === 'string') { + target.push(routes); + } else if (Array.isArray(routes)) { + for (const route of routes) { + _collectLoadChildren(route, target); + } + } else if (routes.loadChildren) { + _collectLoadChildren(routes.loadChildren, target); + } else if (routes.children) { + _collectLoadChildren(routes.children, target); + } + return target; +} + +export function parseLazyRoute( + route: string, reflector: StaticReflector, module?: StaticSymbol): LazyRoute { + const [routePath, routeName] = route.split('#'); + const referencedModule = reflector.resolveExternalReference( + { + moduleName: routePath, + name: routeName, + }, + module ? module.filePath : undefined); + return {route: route, module: module || referencedModule, referencedModule}; +} diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts index 3c460c84db..1dd5603166 100644 --- a/packages/compiler/src/aot/static_reflector.ts +++ b/packages/compiler/src/aot/static_reflector.ts @@ -45,7 +45,7 @@ export class StaticReflector implements CompileReflector { private conversionMap = new Map any>(); private injectionToken: StaticSymbol; private opaqueToken: StaticSymbol; - private ROUTES: StaticSymbol; + ROUTES: StaticSymbol; private ANALYZE_FOR_ENTRY_COMPONENTS: StaticSymbol; private annotationForParentClassWithSummaryKind = new Map[]>(); @@ -76,8 +76,9 @@ export class StaticReflector implements CompileReflector { return this.symbolResolver.getResourcePath(staticSymbol); } - resolveExternalReference(ref: o.ExternalReference): StaticSymbol { - const refSymbol = this.symbolResolver.getSymbolByModule(ref.moduleName !, ref.name !); + resolveExternalReference(ref: o.ExternalReference, containingFile?: string): StaticSymbol { + const refSymbol = + this.symbolResolver.getSymbolByModule(ref.moduleName !, ref.name !, containingFile); const declarationSymbol = this.findSymbolDeclaration(refSymbol); this.symbolResolver.recordModuleNameForFileName(refSymbol.filePath, ref.moduleName !); this.symbolResolver.recordImportAs(declarationSymbol, refSymbol); diff --git a/packages/compiler/src/aot/static_symbol_resolver.ts b/packages/compiler/src/aot/static_symbol_resolver.ts index 39979ece99..cb4241d5e8 100644 --- a/packages/compiler/src/aot/static_symbol_resolver.ts +++ b/packages/compiler/src/aot/static_symbol_resolver.ts @@ -39,13 +39,6 @@ export interface StaticSymbolResolverHost { * `path/to/containingFile.ts` containing `import {...} from 'module-name'`. */ moduleNameToFileName(moduleName: string, containingFile?: string): string|null; - /** - * Converts a file path to a module name that can be used as an `import. - * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`. - * - * See ImportResolver. - */ - fileNameToModuleName(importedFilePath: string, containingFilePath: string): string; } const SUPPORTED_SCHEMA_VERSION = 4; @@ -160,15 +153,6 @@ export class StaticSymbolResolver { return (resolvedSymbol && resolvedSymbol.metadata && resolvedSymbol.metadata.arity) || null; } - /** - * Converts a file path to a module name that can be used as an `import`. - */ - fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { - return this.summaryResolver.getKnownModuleName(importedFilePath) || - this.knownFileNameToModuleNames.get(importedFilePath) || - this.host.fileNameToModuleName(importedFilePath, containingFilePath); - } - getKnownModuleName(filePath: string): string|null { return this.knownFileNameToModuleNames.get(filePath) || null; } @@ -498,9 +482,8 @@ export class StaticSymbolResolver { const filePath = this.resolveModule(module, containingFile); if (!filePath) { this.reportError( - new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ { - containingFile - } `: ''}`)); + new Error(`Could not resolve module ${module}${containingFile ? ' relative to ' + + containingFile : ''}`)); return this.getStaticSymbol(`ERROR:${module}`, symbolName); } return this.getStaticSymbol(filePath, symbolName); diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 15c8a43aec..445605a6e1 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -39,6 +39,7 @@ export * from './aot/static_reflector'; export * from './aot/static_symbol'; export * from './aot/static_symbol_resolver'; export * from './aot/summary_resolver'; +export {LazyRoute} from './aot/lazy_routes'; export * from './ast_path'; export * from './summary_resolver'; export {Identifiers} from './identifiers'; diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index 71af0afaab..57fdca02d5 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -260,3 +260,8 @@ function makeMetadataFactory(name: string, props?: (...args: any[]) => T): Me factory.ngMetadataName = name; return factory; } + +export interface Route { + children?: Route[]; + loadChildren?: string|Type|any; +} diff --git a/packages/compiler/test/aot/static_symbol_resolver_spec.ts b/packages/compiler/test/aot/static_symbol_resolver_spec.ts index d9ab7cd6b9..1a6aa31eb6 100644 --- a/packages/compiler/test/aot/static_symbol_resolver_spec.ts +++ b/packages/compiler/test/aot/static_symbol_resolver_spec.ts @@ -465,10 +465,6 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost { return '/tmp/' + modulePath + '.d.ts'; } - fileNameToModuleName(filePath: string, containingFile: string) { - return filePath.replace(/(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/, ''); - } - getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); } private _getMetadataFor(filePath: string): any { diff --git a/packages/compiler/test/aot/test_util.ts b/packages/compiler/test/aot/test_util.ts index 9910e82a17..4fa62fa501 100644 --- a/packages/compiler/test/aot/test_util.ts +++ b/packages/compiler/test/aot/test_util.ts @@ -652,7 +652,7 @@ export function compile( const tsSettings = {...settings, ...tsOptions}; const program = ts.createProgram(host.scriptNames.slice(0), tsSettings, host); preCompile(program); - const {compiler, reflector} = createAotCompiler(aotHost, options); + const {compiler, reflector} = createAotCompiler(aotHost, options, (err) => { throw err; }); const analyzedModules = compiler.analyzeModulesSync(program.getSourceFiles().map(sf => sf.fileName)); const genFiles = compiler.emitAllImpls(analyzedModules); diff --git a/packages/language-service/src/reflector_host.ts b/packages/language-service/src/reflector_host.ts index c78787b845..99511fad06 100644 --- a/packages/language-service/src/reflector_host.ts +++ b/packages/language-service/src/reflector_host.ts @@ -6,12 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompilerHost} from '@angular/compiler'; -import {CompilerHost, CompilerOptions, ModuleResolutionHostAdapter} from '@angular/compiler-cli/src/language_services'; +import {StaticSymbolResolverHost} from '@angular/compiler'; +import {CompilerOptions, MetadataCollector, MetadataReaderCache, MetadataReaderHost, createMetadataReaderCache, readMetadata} from '@angular/compiler-cli/src/language_services'; +import * as path from 'path'; import * as ts from 'typescript'; -class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost { - constructor(private host: ts.LanguageServiceHost) { +class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost, MetadataReaderHost { + // Note: verboseInvalidExpressions is important so that + // the collector will collect errors instead of throwing + private metadataCollector = new MetadataCollector({verboseInvalidExpression: true}); + + constructor(private host: ts.LanguageServiceHost, private getProgram: () => ts.Program) { if (host.directoryExists) this.directoryExists = directoryName => this.host.directoryExists !(directoryName); } @@ -29,24 +34,46 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost { } directoryExists: (directoryName: string) => boolean; + + getSourceFileMetadata(fileName: string) { + const sf = this.getProgram().getSourceFile(fileName); + return sf ? this.metadataCollector.getMetadata(sf) : undefined; + } + + cacheMetadata(fileName: string) { + // Don't cache the metadata for .ts files as they might change in the editor! + return fileName.endsWith('.d.ts'); + } } -// This reflector host's purpose is to first set verboseInvalidExpressions to true so the -// reflector will collect errors instead of throwing, and second to all deferring the creation -// of the program until it is actually needed. -export class ReflectorHost extends CompilerHost { +export class ReflectorHost implements StaticSymbolResolverHost { + private moduleResolutionCache: ts.ModuleResolutionCache; + private hostAdapter: ReflectorModuleModuleResolutionHost; + private metadataReaderCache = createMetadataReaderCache(); + constructor( - private getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost, - options: CompilerOptions) { - super( - // The ancestor value for program is overridden below so passing null here is safe. - /* program */ null !, options, - new ModuleResolutionHostAdapter(new ReflectorModuleModuleResolutionHost(serviceHost)), - {verboseInvalidExpression: true}); + getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost, + private options: CompilerOptions) { + this.hostAdapter = new ReflectorModuleModuleResolutionHost(serviceHost, getProgram); + this.moduleResolutionCache = + ts.createModuleResolutionCache(serviceHost.getCurrentDirectory(), (s) => s); } - protected get program() { return this.getProgram(); } - protected set program(value: ts.Program) { - // Discard the result set by ancestor constructor + getMetadataFor(modulePath: string): {[key: string]: any}[]|undefined { + return readMetadata(modulePath, this.hostAdapter, this.metadataReaderCache); + } + + moduleNameToFileName(moduleName: string, containingFile?: string): string|null { + if (!containingFile) { + if (moduleName.indexOf('.') === 0) { + throw new Error('Resolution of relative paths requires a containing file.'); + } + // Any containing file gives the same result for absolute imports + containingFile = path.join(this.options.basePath !, 'index.ts'); + } + const resolved = + ts.resolveModuleName(moduleName, containingFile, this.options, this.hostAdapter) + .resolvedModule; + return resolved ? resolved.resolvedFileName : null; } } diff --git a/scripts/ci/env.sh b/scripts/ci/env.sh index 2fadcf21d3..b2ad2649a0 100755 --- a/scripts/ci/env.sh +++ b/scripts/ci/env.sh @@ -39,7 +39,7 @@ setEnvVar YARN_VERSION 1.0.2 setEnvVar CHROMIUM_VERSION 499098 # Chrome 62 linux stable, see https://www.chromium.org/developers/calendar setEnvVar BAZEL_VERSION 0.5.4 setEnvVar SAUCE_CONNECT_VERSION 4.4.9 -setEnvVar ANGULAR_CLI_VERSION 1.4.0-rc.2 +setEnvVar ANGULAR_CLI_VERSION 1.5.0-rc.2 setEnvVar PROJECT_ROOT $(cd ${thisDir}/../..; pwd) if [[ ${TRAVIS:-} ]]; then diff --git a/scripts/ci/offline_compiler_test.sh b/scripts/ci/offline_compiler_test.sh index d3ebc6f1c1..74832f5eac 100755 --- a/scripts/ci/offline_compiler_test.sh +++ b/scripts/ci/offline_compiler_test.sh @@ -60,8 +60,6 @@ cp -v package.json $TMP ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf2 --outFile=messages.xliff2.xlf ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xmb --outFile=custom_file.xmb - # Removed until #15219 is fixed - # node test/test_summaries.js node test/test_ngtools_api.js ./node_modules/.bin/jasmine init