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
This commit is contained in:
Tobias Bosch 2017-10-20 09:46:41 -07:00 committed by Matias Niemelä
parent 5da96c75a2
commit 8d45fefc31
41 changed files with 1128 additions and 1837 deletions

View File

@ -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\"",

View File

@ -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';

View File

@ -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');

View File

@ -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<string> {
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();

View File

@ -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",

View File

@ -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",

View File

@ -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<string[]> {
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);
}
}

View File

@ -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>|string;
}
export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext> implements
AotCompilerHost {
private resolverCache = new Map<string, ModuleMetadata[]>();
private flatModuleIndexCache = new Map<string, boolean>();
private flatModuleIndexNames = new Set<string>();
private flatModuleIndexRedirectNames = new Set<string>();
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>|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>|string;
assumeFileExists(fileName: string): void;
}
// TODO(tbosch): remove this once G3 uses the transformer compiler!
export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
protected metadataProvider: MetadataCollector;
protected basePath: string;
private moduleFileNames = new Map<string, string|null>();
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));
}
}

View File

@ -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<string[]> {
return this.extractBundle().then(
bundle => i18nExtract(formatName, outFile, this.host, this.options, bundle));
}
extractBundle(): Promise<compiler.MessageBundle> {
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);
}
}

View File

@ -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';

View File

@ -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);
}

View File

@ -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<string>, 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<any> {
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<any> {
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+`);
}

View File

@ -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<void>;
listLazyRoutes(entryRoute?: string): LazyRoute[];
emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
emitFlags?: EmitFlags,
cancellationToken?: ts.CancellationToken,

View File

@ -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<T>(list: Array<T|T[]>): T[] {
return list.reduce((flat: any[], item: T | T[]): T[] => {
const flatItem = Array.isArray(item) ? flatten(item) : item;
return (<T[]>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;
}
}, []);
}

View File

@ -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 !;
}
}

View File

@ -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<void>;
/**
* 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.
*

View File

@ -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<CompilerHost> implements ts.CompilerHost,
export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHost, AotCompilerHost,
TypeCheckHost {
private metadataReaderCache = createMetadataReaderCache();
private flatModuleIndexCache = new Map<string, boolean>();
private flatModuleIndexNames = new Set<string>();
private flatModuleIndexRedirectNames = new Set<string>();
private rootDirs: string[];
private moduleResolutionCache: ts.ModuleResolutionCache;
private originalSourceFiles = new Map<string, ts.SourceFile|undefined>();
@ -58,6 +60,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
private generatedSourceFiles = new Map<string, GenSourceFile>();
private generatedCodeFor = new Map<string, string[]>();
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<string, LibrarySummary>()) {
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>|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();

View File

@ -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<string, ModuleMetadata[]|undefined>;
}
export function createMetadataReaderCache(): MetadataReaderCache {
const data = new Map<string, ModuleMetadata[]|undefined>();
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;
}

View File

@ -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<string, GeneratedFile>,
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

View File

@ -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}

View File

@ -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;
}
}

View File

@ -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');
});

View File

@ -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<string, string>();
export class MockLanguageServiceHost implements ts.LanguageServiceHost, CompilerHostContext {
export class MockLanguageServiceHost implements ts.LanguageServiceHost {
private options: ts.CompilerOptions;
private context: MockAotContext;
private assumedExist = new Set<string>();
@ -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}); }

View File

@ -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);
});

View File

@ -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'; }

View File

@ -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');
});
});

View File

@ -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;
}
}

View File

@ -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: '<input/>{{',
})
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'
}]);
});
});
});

View File

@ -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<StaticSymbol>(),
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) {

View File

@ -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);

View File

@ -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.

View File

@ -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};
}

View File

@ -45,7 +45,7 @@ export class StaticReflector implements CompileReflector {
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol;
private ROUTES: StaticSymbol;
ROUTES: StaticSymbol;
private ANALYZE_FOR_ENTRY_COMPONENTS: StaticSymbol;
private annotationForParentClassWithSummaryKind =
new Map<CompileSummaryKind, MetadataFactory<any>[]>();
@ -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);

View File

@ -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);

View File

@ -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';

View File

@ -260,3 +260,8 @@ function makeMetadataFactory<T>(name: string, props?: (...args: any[]) => T): Me
factory.ngMetadataName = name;
return factory;
}
export interface Route {
children?: Route[];
loadChildren?: string|Type|any;
}

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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

View File

@ -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