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:
parent
5da96c75a2
commit
8d45fefc31
|
@ -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\"",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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+`);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}, []);
|
||||
}
|
|
@ -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 !;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
|
||||
|
|
|
@ -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}); }
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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'; }
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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};
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue