refactor(compiler): allow sync AOT compilation (#16832).

AOT compilation can be executed synchronously now,
if the `ReosurceLoader` returns a string directly
(and no `Promise`).
This commit is contained in:
Tobias Bosch 2017-05-17 15:39:08 -07:00 committed by Chuck Jazdzewski
parent 255d7226d1
commit 5af143e8e4
15 changed files with 577 additions and 643 deletions

View File

@ -38,7 +38,7 @@ export class CodeGenerator {
codegen(): Promise<any> { codegen(): Promise<any> {
return this.compiler return this.compiler
.compileAll(this.program.getSourceFiles().map( .compileAllAsync(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))) sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
.then(generatedModules => { .then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {

View File

@ -17,7 +17,11 @@ import {DiagnosticTemplateInfo} from '../../src/diagnostics/expression_diagnosti
import {getClassFromStaticSymbol, getClassMembers, getPipesTable, getSymbolQuery} from '../../src/diagnostics/typescript_symbols'; import {getClassFromStaticSymbol, getClassMembers, getPipesTable, getSymbolQuery} from '../../src/diagnostics/typescript_symbols';
import {Directory, MockAotContext} from '../mocks'; import {Directory, MockAotContext} from '../mocks';
const packages = path.join(__dirname, '../../../../../packages'); function calcRootPath() {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
return moduleFilename.substr(0, distIndex);
}
const realFiles = new Map<string, string>(); const realFiles = new Map<string, string>();
@ -40,7 +44,7 @@ export class MockLanguageServiceHost implements ts.LanguageServiceHost, Compiler
strictNullChecks: true, strictNullChecks: true,
baseUrl: currentDirectory, baseUrl: currentDirectory,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'], lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
paths: {'@angular/*': [packages + '/*']} paths: {'@angular/*': [calcRootPath() + '/packages/*']}
}; };
this.context = new MockAotContext(currentDirectory, files) this.context = new MockAotContext(currentDirectory, files)
} }

View File

@ -39,7 +39,7 @@ export class AotCompiler {
clearCache() { this._metadataResolver.clearCache(); } clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> { compileAllAsync(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host); const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} = const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver); analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
@ -56,6 +56,20 @@ export class AotCompiler {
}); });
} }
compileAllSync(rootFiles: string[]): GeneratedFile[] {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, true));
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return flatten(sourceModules);
}
private _compileSrcFile( private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>, srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[], directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],

View File

@ -17,5 +17,5 @@ export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryRes
/** /**
* Loads a resource (e.g. html / css) * Loads a resource (e.g. html / css)
*/ */
loadResource(path: string): Promise<string>; loadResource(path: string): Promise<string>|string;
} }

View File

@ -18,7 +18,7 @@ import {ResourceLoader} from './resource_loader';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver'; import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser'; import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
import {UrlResolver} from './url_resolver'; import {UrlResolver} from './url_resolver';
import {SyncAsyncResult, isDefined, syntaxError} from './util'; import {SyncAsync, isDefined, syntaxError} from './util';
export interface PrenormalizedTemplateMetadata { export interface PrenormalizedTemplateMetadata {
ngModuleType: any; ngModuleType: any;
@ -35,7 +35,7 @@ export interface PrenormalizedTemplateMetadata {
@CompilerInjectable() @CompilerInjectable()
export class DirectiveNormalizer { export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>(); private _resourceLoaderCache = new Map<string, SyncAsync<string>>();
constructor( constructor(
private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver, private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver,
@ -53,19 +53,17 @@ export class DirectiveNormalizer {
(stylesheet) => { this._resourceLoaderCache.delete(stylesheet.moduleUrl !); }); (stylesheet) => { this._resourceLoaderCache.delete(stylesheet.moduleUrl !); });
} }
private _fetch(url: string): Promise<string> { private _fetch(url: string): SyncAsync<string> {
let result = this._resourceLoaderCache.get(url); let result = this._resourceLoaderCache.get(url);
if (!result) { if (!result) {
result = this._resourceLoader.get(url) !; result = this._resourceLoader.get(url);
this._resourceLoaderCache.set(url, result); this._resourceLoaderCache.set(url, result);
} }
return result; return result;
} }
normalizeTemplate(prenormData: PrenormalizedTemplateMetadata): normalizeTemplate(prenormData: PrenormalizedTemplateMetadata):
SyncAsyncResult<CompileTemplateMetadata> { SyncAsync<CompileTemplateMetadata> {
let normalizedTemplateSync: CompileTemplateMetadata = null !;
let normalizedTemplateAsync: Promise<CompileTemplateMetadata> = undefined !;
if (isDefined(prenormData.template)) { if (isDefined(prenormData.template)) {
if (isDefined(prenormData.templateUrl)) { if (isDefined(prenormData.templateUrl)) {
throw syntaxError( throw syntaxError(
@ -75,39 +73,33 @@ export class DirectiveNormalizer {
throw syntaxError( throw syntaxError(
`The template specified for component ${stringify(prenormData.componentType)} is not a string`); `The template specified for component ${stringify(prenormData.componentType)} is not a string`);
} }
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync !);
} else if (isDefined(prenormData.templateUrl)) { } else if (isDefined(prenormData.templateUrl)) {
if (typeof prenormData.templateUrl !== 'string') { if (typeof prenormData.templateUrl !== 'string') {
throw syntaxError( throw syntaxError(
`The templateUrl specified for component ${stringify(prenormData.componentType)} is not a string`); `The templateUrl specified for component ${stringify(prenormData.componentType)} is not a string`);
} }
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else { } else {
throw syntaxError( throw syntaxError(
`No template specified for component ${stringify(prenormData.componentType)}`); `No template specified for component ${stringify(prenormData.componentType)}`);
} }
return SyncAsync.then(
this.normalizeTemplateOnly(prenormData),
(result: CompileTemplateMetadata) => this.normalizeExternalStylesheets(result));
}
if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) { normalizeTemplateOnly(prenomData: PrenormalizedTemplateMetadata):
// sync case SyncAsync<CompileTemplateMetadata> {
return new SyncAsyncResult(normalizedTemplateSync); let template: SyncAsync<string>;
let templateUrl: string;
if (prenomData.template != null) {
template = prenomData.template;
templateUrl = prenomData.moduleUrl;
} else { } else {
// async case templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl !);
return new SyncAsyncResult( template = this._fetch(templateUrl);
null, normalizedTemplateAsync.then(
(normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)));
} }
} return SyncAsync.then(
template, (template) => this.normalizeLoadedTemplate(prenomData, template, templateUrl));
normalizeTemplateSync(prenomData: PrenormalizedTemplateMetadata): CompileTemplateMetadata {
return this.normalizeLoadedTemplate(prenomData, prenomData.template !, prenomData.moduleUrl);
}
normalizeTemplateAsync(prenomData: PrenormalizedTemplateMetadata):
Promise<CompileTemplateMetadata> {
const templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl !);
return this._fetch(templateUrl)
.then((value) => this.normalizeLoadedTemplate(prenomData, value, templateUrl));
} }
normalizeLoadedTemplate( normalizeLoadedTemplate(
@ -162,37 +154,42 @@ export class DirectiveNormalizer {
} }
normalizeExternalStylesheets(templateMeta: CompileTemplateMetadata): normalizeExternalStylesheets(templateMeta: CompileTemplateMetadata):
Promise<CompileTemplateMetadata> { SyncAsync<CompileTemplateMetadata> {
return this._loadMissingExternalStylesheets(templateMeta.styleUrls) return SyncAsync.then(
.then((externalStylesheets) => new CompileTemplateMetadata({ this._loadMissingExternalStylesheets(templateMeta.styleUrls),
encapsulation: templateMeta.encapsulation, (externalStylesheets) => new CompileTemplateMetadata({
template: templateMeta.template, encapsulation: templateMeta.encapsulation,
templateUrl: templateMeta.templateUrl, template: templateMeta.template,
styles: templateMeta.styles, templateUrl: templateMeta.templateUrl,
styleUrls: templateMeta.styleUrls, styles: templateMeta.styles,
externalStylesheets: externalStylesheets, styleUrls: templateMeta.styleUrls,
ngContentSelectors: templateMeta.ngContentSelectors, externalStylesheets: externalStylesheets,
animations: templateMeta.animations, ngContentSelectors: templateMeta.ngContentSelectors,
interpolation: templateMeta.interpolation, animations: templateMeta.animations,
isInline: templateMeta.isInline, interpolation: templateMeta.interpolation,
})); isInline: templateMeta.isInline,
}));
} }
private _loadMissingExternalStylesheets( private _loadMissingExternalStylesheets(
styleUrls: string[], styleUrls: string[],
loadedStylesheets: loadedStylesheets:
Map<string, CompileStylesheetMetadata> = new Map<string, CompileStylesheetMetadata>()): Map<string, CompileStylesheetMetadata> = new Map<string, CompileStylesheetMetadata>()):
Promise<CompileStylesheetMetadata[]> { SyncAsync<CompileStylesheetMetadata[]> {
return Promise return SyncAsync.then(
.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl)) SyncAsync.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
.map(styleUrl => this._fetch(styleUrl).then((loadedStyle) => { .map(
const stylesheet = this.normalizeStylesheet( styleUrl => SyncAsync.then(
new CompileStylesheetMetadata({styles: [loadedStyle], moduleUrl: styleUrl})); this._fetch(styleUrl),
loadedStylesheets.set(styleUrl, stylesheet); (loadedStyle) => {
return this._loadMissingExternalStylesheets( const stylesheet =
stylesheet.styleUrls, loadedStylesheets); this.normalizeStylesheet(new CompileStylesheetMetadata(
}))) {styles: [loadedStyle], moduleUrl: styleUrl}));
.then((_) => Array.from(loadedStylesheets.values())); loadedStylesheets.set(styleUrl, stylesheet);
return this._loadMissingExternalStylesheets(
stylesheet.styleUrls, loadedStylesheets);
}))),
(_) => Array.from(loadedStylesheets.values()));
} }
normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata { normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata {

View File

@ -19,7 +19,7 @@ import {jitStatements} from '../output/output_jit';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver'; import {SummaryResolver} from '../summary_resolver';
import {TemplateParser} from '../template_parser/template_parser'; import {TemplateParser} from '../template_parser/template_parser';
import {OutputContext, SyncAsyncResult} from '../util'; import {OutputContext, SyncAsync} from '../util';
import {ViewCompiler} from '../view_compiler/view_compiler'; import {ViewCompiler} from '../view_compiler/view_compiler';
@ -51,20 +51,20 @@ export class JitCompiler implements Compiler {
get injector(): Injector { return this._injector; } get injector(): Injector { return this._injector; }
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T> { compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T> {
return this._compileModuleAndComponents(moduleType, true).syncResult !; return SyncAsync.assertSync(this._compileModuleAndComponents(moduleType, true));
} }
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> { compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> {
return this._compileModuleAndComponents(moduleType, false).asyncResult !; return Promise.resolve(this._compileModuleAndComponents(moduleType, false));
} }
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> { compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
return this._compileModuleAndAllComponents(moduleType, true).syncResult !; return SyncAsync.assertSync(this._compileModuleAndAllComponents(moduleType, true));
} }
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>): compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
Promise<ModuleWithComponentFactories<T>> { Promise<ModuleWithComponentFactories<T>> {
return this._compileModuleAndAllComponents(moduleType, false).asyncResult !; return Promise.resolve(this._compileModuleAndAllComponents(moduleType, false));
} }
getNgContentSelectors(component: Type<any>): string[] { getNgContentSelectors(component: Type<any>): string[] {
@ -97,36 +97,24 @@ export class JitCompiler implements Compiler {
} }
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean): private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<NgModuleFactory<T>> { SyncAsync<NgModuleFactory<T>> {
const loadingPromise = this._loadModules(moduleType, isSync); return SyncAsync.then(this._loadModules(moduleType, isSync), () => {
const createResult = () => {
this._compileComponents(moduleType, null); this._compileComponents(moduleType, null);
return this._compileModule(moduleType); return this._compileModule(moduleType);
}; });
if (isSync) {
return new SyncAsyncResult(createResult());
} else {
return new SyncAsyncResult(null, loadingPromise.then(createResult));
}
} }
private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean): private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<ModuleWithComponentFactories<T>> { SyncAsync<ModuleWithComponentFactories<T>> {
const loadingPromise = this._loadModules(moduleType, isSync); return SyncAsync.then(this._loadModules(moduleType, isSync), () => {
const createResult = () => {
const componentFactories: ComponentFactory<any>[] = []; const componentFactories: ComponentFactory<any>[] = [];
this._compileComponents(moduleType, componentFactories); this._compileComponents(moduleType, componentFactories);
return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories); return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories);
}; });
if (isSync) {
return new SyncAsyncResult(createResult());
} else {
return new SyncAsyncResult(null, loadingPromise.then(createResult));
}
} }
private _loadModules(mainModule: any, isSync: boolean): Promise<any> { private _loadModules(mainModule: any, isSync: boolean): SyncAsync<any> {
const loadingPromises: Promise<any>[] = []; const loading: Promise<any>[] = [];
const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule) !; const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule) !;
// Note: for runtime compilation, we want to transitively compile all modules, // Note: for runtime compilation, we want to transitively compile all modules,
// so we also need to load the declared directives / pipes for all nested modules. // so we also need to load the declared directives / pipes for all nested modules.
@ -137,13 +125,13 @@ export class JitCompiler implements Compiler {
const promise = const promise =
this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync); this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync);
if (promise) { if (promise) {
loadingPromises.push(promise); loading.push(promise);
} }
}); });
this._filterJitIdentifiers(moduleMeta.declaredPipes) this._filterJitIdentifiers(moduleMeta.declaredPipes)
.forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref)); .forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref));
}); });
return Promise.all(loadingPromises); return SyncAsync.all(loading);
} }
private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> { private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵstringify as stringify} from '@angular/core'; import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵisPromise as isPromise, ɵstringify as stringify} from '@angular/core';
import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol'; import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
import {ngfactoryFilePath} from './aot/util'; import {ngfactoryFilePath} from './aot/util';
@ -24,7 +24,7 @@ import {PipeResolver} from './pipe_resolver';
import {ElementSchemaRegistry} from './schema/element_schema_registry'; import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {SummaryResolver} from './summary_resolver'; import {SummaryResolver} from './summary_resolver';
import {getUrlScheme} from './url_resolver'; import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, ValueTransformer, noUndefined, syntaxError, visitValue} from './util'; import {MODULE_SUFFIX, SyncAsync, ValueTransformer, noUndefined, syntaxError, visitValue} from './util';
export type ErrorCollector = (error: any, type?: any) => void; export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector'); export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector');
@ -170,7 +170,7 @@ export class CompileMetadataResolver {
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null; return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
} }
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): Promise<any>|null { loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
if (this._directiveCache.has(directiveType)) { if (this._directiveCache.has(directiveType)) {
return null; return null;
} }
@ -205,7 +205,7 @@ export class CompileMetadataResolver {
} }
this._directiveCache.set(directiveType, normalizedDirMeta); this._directiveCache.set(directiveType, normalizedDirMeta);
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
return normalizedDirMeta; return null;
}; };
if (metadata.isComponent) { if (metadata.isComponent) {
@ -222,16 +222,11 @@ export class CompileMetadataResolver {
animations: template.animations, animations: template.animations,
interpolation: template.interpolation interpolation: template.interpolation
}); });
if (templateMeta.syncResult) { if (isPromise(templateMeta) && isSync) {
createDirectiveMetadata(templateMeta.syncResult); this._reportError(componentStillLoadingError(directiveType), directiveType);
return null; return null;
} else {
if (isSync) {
this._reportError(componentStillLoadingError(directiveType), directiveType);
return null;
}
return templateMeta.asyncResult !.then(createDirectiveMetadata);
} }
return SyncAsync.then(templateMeta, createDirectiveMetadata);
} else { } else {
// directive // directive
createDirectiveMetadata(null); createDirectiveMetadata(null);

View File

@ -11,5 +11,5 @@
* to load templates. * to load templates.
*/ */
export class ResourceLoader { export class ResourceLoader {
get(url: string): Promise<string>|null { return null; } get(url: string): Promise<string>|string { return ''; }
} }

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵisPromise as isPromise} from '@angular/core';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
export const MODULE_SUFFIX = ''; export const MODULE_SUFFIX = '';
@ -80,19 +82,28 @@ export class ValueTransformer implements ValueVisitor {
visitOther(value: any, context: any): any { return value; } visitOther(value: any, context: any): any { return value; }
} }
export class SyncAsyncResult<T> { export type SyncAsync<T> = T | Promise<T>;
constructor(public syncResult: T|null, public asyncResult: Promise<T>|null = null) {
if (!asyncResult) { export const SyncAsync = {
this.asyncResult = Promise.resolve(syncResult); assertSync: <T>(value: SyncAsync<T>): T => {
if (isPromise(value)) {
throw new Error(`Illegal state: value cannot be a promise`);
} }
return value;
},
then: <T, R>(value: SyncAsync<T>, cb: (value: T) => R | Promise<R>| SyncAsync<R>):
SyncAsync<R> => { return isPromise(value) ? value.then(cb) : cb(value);},
all: <T>(syncAsyncValues: SyncAsync<T>[]): SyncAsync<T[]> => {
return syncAsyncValues.some(isPromise) ? Promise.all(syncAsyncValues) : syncAsyncValues as T[];
} }
} }
export function syntaxError(msg: string): Error { export function syntaxError(msg: string):
const error = Error(msg); Error {
(error as any)[ERROR_SYNTAX_ERROR] = true; const error = Error(msg);
return error; (error as any)[ERROR_SYNTAX_ERROR] = true;
} return error;
}
const ERROR_SYNTAX_ERROR = 'ngSyntaxError'; const ERROR_SYNTAX_ERROR = 'ngSyntaxError';

View File

@ -8,7 +8,6 @@
import {GeneratedFile, toTypeScript} from '@angular/compiler'; import {GeneratedFile, toTypeScript} from '@angular/compiler';
import {NodeFlags} from '@angular/core/src/view/index'; import {NodeFlags} from '@angular/core/src/view/index';
import {async} from '@angular/core/testing';
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped'; import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -20,11 +19,11 @@ describe('compiler (unbundled Angular)', () => {
let angularFiles = setup(); let angularFiles = setup();
describe('Quickstart', () => { describe('Quickstart', () => {
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => { it('should compile', () => {
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) const {genFiles} = compile([QUICKSTART, angularFiles]);
.toBeDefined(); expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined(); expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
}))); });
}); });
describe('aot source mapping', () => { describe('aot source mapping', () => {
@ -51,12 +50,10 @@ describe('compiler (unbundled Angular)', () => {
rootDir = {'app': appDir}; rootDir = {'app': appDir};
}); });
function compileApp(): Promise<GeneratedFile> { function compileApp(): GeneratedFile {
return compile([rootDir, angularFiles]) const {genFiles} = compile([rootDir, angularFiles]);
.then( return genFiles.find(
({genFiles}) => {return genFiles.find( genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'));
genFile =>
genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))});
} }
function findLineAndColumn( function findLineAndColumn(
@ -106,102 +103,95 @@ describe('compiler (unbundled Angular)', () => {
function declareTests({ngUrl, templateDecorator}: function declareTests({ngUrl, templateDecorator}:
{ngUrl: string, templateDecorator: (template: string) => string}) { {ngUrl: string, templateDecorator: (template: string) => string}) {
it('should use the right source url in html parse errors', async(() => { it('should use the right source url in html parse errors', () => {
appDir['app.component.ts'] = appDir['app.component.ts'] = createComponentSource(templateDecorator('<div>\n </error>'));
createComponentSource(templateDecorator('<div>\n </error>'));
expectPromiseToThrow( expect(() => compileApp())
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`)); .toThrowError(new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`));
})); });
it('should use the right source url in template parse errors', async(() => { it('should use the right source url in template parse errors', () => {
appDir['app.component.ts'] = createComponentSource( appDir['app.component.ts'] =
templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')); createComponentSource(templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>'));
expectPromiseToThrow( expect(() => compileApp())
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`)); .toThrowError(new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`));
})); });
it('should create a sourceMap for the template', async(() => { it('should create a sourceMap for the template', () => {
const template = 'Hello World!'; const template = 'Hello World!';
appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => { const genFile = compileApp();
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !; const sourceMap = extractSourceMap(genSource) !;
expect(sourceMap.file).toEqual(genFile.genFileUrl); expect(sourceMap.file).toEqual(genFile.genFileUrl);
// the generated file contains code that is not mapped to // the generated file contains code that is not mapped to
// the template but rather to the original source file (e.g. import statements, ...) // the template but rather to the original source file (e.g. import statements, ...)
const templateIndex = sourceMap.sources.indexOf(ngUrl); const templateIndex = sourceMap.sources.indexOf(ngUrl);
expect(sourceMap.sourcesContent[templateIndex]).toEqual(template); expect(sourceMap.sourcesContent[templateIndex]).toEqual(template);
// for the mapping to the original source file we don't store the source code // for the mapping to the original source file we don't store the source code
// as we want to keep whatever TypeScript / ... produced for them. // as we want to keep whatever TypeScript / ... produced for them.
const sourceIndex = sourceMap.sources.indexOf(ngComponentPath); const sourceIndex = sourceMap.sources.indexOf(ngComponentPath);
expect(sourceMap.sourcesContent[sourceIndex]).toBe(' '); expect(sourceMap.sourcesContent[sourceIndex]).toBe(' ');
}); });
}));
it('should map elements correctly to the source', async(() => { it('should map elements correctly to the source', () => {
const template = '<div>\n <span></span></div>'; const template = '<div>\n <span></span></div>';
appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => { const genFile = compileApp();
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !; const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `'span'`))) expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `'span'`)))
.toEqual({line: 2, column: 3, source: ngUrl}); .toEqual({line: 2, column: 3, source: ngUrl});
}); });
}));
it('should map bindings correctly to the source', async(() => { it('should map bindings correctly to the source', () => {
const template = `<div>\n <span [title]="someMethod()"></span></div>`; const template = `<div>\n <span [title]="someMethod()"></span></div>`;
appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => { const genFile = compileApp();
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !; const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`))) expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
.toEqual({line: 2, column: 9, source: ngUrl}); .toEqual({line: 2, column: 9, source: ngUrl});
}); });
}));
it('should map events correctly to the source', async(() => { it('should map events correctly to the source', () => {
const template = `<div>\n <span (click)="someMethod()"></span></div>`; const template = `<div>\n <span (click)="someMethod()"></span></div>`;
appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => { const genFile = compileApp();
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !; const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`))) expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
.toEqual({line: 2, column: 9, source: ngUrl}); .toEqual({line: 2, column: 9, source: ngUrl});
}); });
}));
it('should map non template parts to the source file', async(() => { it('should map non template parts to the source file', () => {
appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!')); appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!'));
compileApp().then((genFile) => { const genFile = compileApp();
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !; const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, {line: 1, column: 0})) expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
.toEqual({line: 1, column: 0, source: ngComponentPath}); .toEqual({line: 1, column: 0, source: ngComponentPath});
}); });
}));
} }
}); });
describe('errors', () => { describe('errors', () => {
it('should only warn if not all arguments of an @Injectable class can be resolved', it('should only warn if not all arguments of an @Injectable class can be resolved', () => {
async(() => { const FILES: MockDirectory = {
const FILES: MockDirectory = { app: {
app: { 'app.ts': `
'app.ts': `
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
@Injectable() @Injectable()
@ -209,20 +199,19 @@ describe('compiler (unbundled Angular)', () => {
constructor(a: boolean) {} constructor(a: boolean) {}
} }
` `
} }
}; };
const warnSpy = spyOn(console, 'warn'); const warnSpy = spyOn(console, 'warn');
compile([FILES, angularFiles]).then(() => { compile([FILES, angularFiles]);
expect(warnSpy).toHaveBeenCalledWith( expect(warnSpy).toHaveBeenCalledWith(
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`); `Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
});
})); });
it('should be able to supress a null access', async(() => { it('should be able to supress a null access', () => {
const FILES: MockDirectory = { const FILES: MockDirectory = {
app: { app: {
'app.ts': ` 'app.ts': `
import {Component, NgModule} from '@angular/core'; import {Component, NgModule} from '@angular/core';
interface Person { name: string; } interface Person { name: string; }
@ -240,16 +229,16 @@ describe('compiler (unbundled Angular)', () => {
}) })
export class MyModule {} export class MyModule {}
` `
} }
}; };
compile([FILES, angularFiles], {postCompile: expectNoDiagnostics}); compile([FILES, angularFiles], {postCompile: expectNoDiagnostics});
})); });
}); });
it('should add the preamble to generated files', async(() => { it('should add the preamble to generated files', () => {
const FILES: MockDirectory = { const FILES: MockDirectory = {
app: { app: {
'app.ts': ` 'app.ts': `
import { NgModule, Component } from '@angular/core'; import { NgModule, Component } from '@angular/core';
@Component({ template: '' }) @Component({ template: '' })
@ -258,24 +247,21 @@ describe('compiler (unbundled Angular)', () => {
@NgModule({ declarations: [ AppComponent ] }) @NgModule({ declarations: [ AppComponent ] })
export class AppModule { } export class AppModule { }
` `
} }
}; };
const genFilePreamble = '/* Hello world! */'; const genFilePreamble = '/* Hello world! */';
compile([FILES, angularFiles]).then(({genFiles}) => { const {genFiles} = compile([FILES, angularFiles]);
const genFile = const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile, genFilePreamble); const genSource = toTypeScript(genFile, genFilePreamble);
expect(genSource.startsWith(genFilePreamble)).toBe(true); expect(genSource.startsWith(genFilePreamble)).toBe(true);
}); });
}));
describe('ComponentFactories', () => { describe('ComponentFactories', () => {
it('should include inputs, outputs and ng-content selectors in the component factory', it('should include inputs, outputs and ng-content selectors in the component factory', () => {
async(() => { const FILES: MockDirectory = {
const FILES: MockDirectory = { app: {
app: { 'app.ts': `
'app.ts': `
import {Component, NgModule, Input, Output} from '@angular/core'; import {Component, NgModule, Input, Output} from '@angular/core';
@Component({ @Component({
@ -295,31 +281,28 @@ describe('compiler (unbundled Angular)', () => {
}) })
export class MyModule {} export class MyModule {}
` `
} }
}; };
compile([FILES, angularFiles]).then(({genFiles}) => { const {genFiles} = compile([FILES, angularFiles]);
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts'); const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const createComponentFactoryCall = const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
/ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, ''); // selector
// selector expect(createComponentFactoryCall).toContain('my-comp');
expect(createComponentFactoryCall).toContain('my-comp'); // inputs
// inputs expect(createComponentFactoryCall).toContain(`{aInputProp:'aInputName'}`);
expect(createComponentFactoryCall).toContain(`{aInputProp:'aInputName'}`); // outputs
// outputs expect(createComponentFactoryCall).toContain(`{aOutputProp:'aOutputName'}`);
expect(createComponentFactoryCall).toContain(`{aOutputProp:'aOutputName'}`); // ngContentSelectors
// ngContentSelectors expect(createComponentFactoryCall).toContain(`['*','child']`);
expect(createComponentFactoryCall).toContain(`['*','child']`); });
});
}));
}); });
describe('generated templates', () => { describe('generated templates', () => {
it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit', it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit', () => {
async(() => { const FILES: MockDirectory = {
const FILES: MockDirectory = { app: {
app: { 'app.ts': `
'app.ts': `
import { NgModule, Component } from '@angular/core'; import { NgModule, Component } from '@angular/core';
@Component({ template: '' }) @Component({ template: '' })
@ -328,16 +311,15 @@ describe('compiler (unbundled Angular)', () => {
@NgModule({ declarations: [ AppComponent ] }) @NgModule({ declarations: [ AppComponent ] })
export class AppModule { } export class AppModule { }
` `
} }
}; };
compile([FILES, angularFiles]).then(({genFiles}) => { const {genFiles} = compile([FILES, angularFiles]);
const genFile = genFiles.find( const genFile =
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).not.toContain('check('); expect(genSource).not.toContain('check(');
});
})); });
}); });
describe('inheritance with summaries', () => { describe('inheritance with summaries', () => {
@ -376,16 +358,15 @@ describe('compiler (unbundled Angular)', () => {
} }
}; };
return compile([libInput, angularFiles], {useSummaries: true}) const {outDir: libOutDir} = compile([libInput, angularFiles], {useSummaries: true});
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true})) const {genFiles} = compile([libOutDir, appInput, angularFiles], {useSummaries: true});
.then(({genFiles}) => genFiles.find(gf => gf.srcFileUrl === '/app/main.ts')); return genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
} }
it('should inherit ctor and lifecycle hooks from classes in other compilation units', it('should inherit ctor and lifecycle hooks from classes in other compilation units', () => {
async(() => { const libInput: MockDirectory = {
const libInput: MockDirectory = { 'lib': {
'lib': { 'base.ts': `
'base.ts': `
export class AParam {} export class AParam {}
export class Base { export class Base {
@ -393,11 +374,11 @@ describe('compiler (unbundled Angular)', () => {
ngOnDestroy() {} ngOnDestroy() {}
} }
` `
} }
}; };
const appInput: MockDirectory = { const appInput: MockDirectory = {
'app': { 'app': {
'main.ts': ` 'main.ts': `
import {NgModule, Component} from '@angular/core'; import {NgModule, Component} from '@angular/core';
import {Base} from '../lib/base'; import {Base} from '../lib/base';
@ -409,21 +390,19 @@ describe('compiler (unbundled Angular)', () => {
}) })
export class MyModule {} export class MyModule {}
` `
} }
}; };
compile([libInput, angularFiles], {useSummaries: true}) const {outDir: libOutDir} = compile([libInput, angularFiles], {useSummaries: true});
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true})) const {genFiles} = compile([libOutDir, appInput, angularFiles], {useSummaries: true});
.then(({genFiles}) => { const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; expect(toTypeScript(mainNgFactory))
expect(toTypeScript(mainNgFactory)) .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`); });
});
}));
it('should inherit ctor and lifecycle hooks from classes in other compilation units over 2 levels', it('should inherit ctor and lifecycle hooks from classes in other compilation units over 2 levels',
async(() => { () => {
const lib1Input: MockDirectory = { const lib1Input: MockDirectory = {
'lib1': { 'lib1': {
'base.ts': ` 'base.ts': `
@ -463,135 +442,134 @@ describe('compiler (unbundled Angular)', () => {
` `
} }
}; };
compile([lib1Input, angularFiles], {useSummaries: true}) const {outDir: lib1OutDir} = compile([lib1Input, angularFiles], {useSummaries: true});
.then(({outDir}) => compile([outDir, lib2Input, angularFiles], {useSummaries: true})) const {outDir: lib2OutDir} =
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true})) compile([lib1OutDir, lib2Input, angularFiles], {useSummaries: true});
.then(({genFiles}) => { const {genFiles} = compile([lib2OutDir, appInput, angularFiles], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory)) expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`); .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`);
}); });
}));
describe('Injectable', () => { describe('Injectable', () => {
it('should allow to inherit', async(() => { it('should allow to inherit', () => {
compileParentAndChild({ const mainNgFactory = compileParentAndChild({
parentClassDecorator: '@Injectable()', parentClassDecorator: '@Injectable()',
parentModuleDecorator: '@NgModule({providers: [Base]})', parentModuleDecorator: '@NgModule({providers: [Base]})',
childClassDecorator: '@Injectable()', childClassDecorator: '@Injectable()',
childModuleDecorator: '@NgModule({providers: [Extends]})', childModuleDecorator: '@NgModule({providers: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); }); });
})); expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => { it('should error if the child class has no matching decorator', () => {
compileParentAndChild({ expect(() => compileParentAndChild({
parentClassDecorator: '@Injectable()', parentClassDecorator: '@Injectable()',
parentModuleDecorator: '@NgModule({providers: [Base]})', parentModuleDecorator: '@NgModule({providers: [Base]})',
childClassDecorator: '', childClassDecorator: '',
childModuleDecorator: '@NgModule({providers: [Extends]})', childModuleDecorator: '@NgModule({providers: [Extends]})',
}).then(fail, (e) => { }))
expect(e.message).toContain( .toThrowError(
'Class Extends in /app/main.ts extends from a Injectable in another compilation unit without duplicating the decorator. ' + 'Class Extends in /app/main.ts extends from a Injectable in another compilation unit without duplicating the decorator. ' +
'Please add a Injectable or Pipe or Directive or Component or NgModule decorator to the class.'); 'Please add a Injectable or Pipe or Directive or Component or NgModule decorator to the class.');
}); });
}));
}); });
describe('Component', () => { describe('Component', () => {
it('should allow to inherit', async(() => { it('should allow to inherit', () => {
compileParentAndChild({ const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@Component({template: ''})`, parentClassDecorator: `@Component({template: ''})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})', parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Component({template: ''})`, childClassDecorator: `@Component({template: ''})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})', childModuleDecorator: '@NgModule({declarations: [Extends]})'
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); }); });
})); expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => { it('should error if the child class has no matching decorator', () => {
compileParentAndChild({ expect(() => compileParentAndChild({
parentClassDecorator: `@Component({template: ''})`, parentClassDecorator: `@Component({template: ''})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})', parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '', childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})', childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then(fail, (e) => { }))
expect(e.message).toContain( .toThrowError(
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' + 'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
'Please add a Directive or Component decorator to the class.'); 'Please add a Directive or Component decorator to the class.');
}); });
}));
}); });
describe('Directive', () => { describe('Directive', () => {
it('should allow to inherit', async(() => { it('should allow to inherit', () => {
compileParentAndChild({ const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@Directive({selector: '[someDir]'})`, parentClassDecorator: `@Directive({selector: '[someDir]'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})', parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Directive({selector: '[someDir]'})`, childClassDecorator: `@Directive({selector: '[someDir]'})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})', childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); }); });
})); expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => { it('should error if the child class has no matching decorator', () => {
compileParentAndChild({ expect(() => compileParentAndChild({
parentClassDecorator: `@Directive({selector: '[someDir]'})`, parentClassDecorator: `@Directive({selector: '[someDir]'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})', parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '', childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})', childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then(fail, (e) => { }))
expect(e.message).toContain( .toThrowError(
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' + 'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
'Please add a Directive or Component decorator to the class.'); 'Please add a Directive or Component decorator to the class.');
}); });
}));
}); });
describe('Pipe', () => { describe('Pipe', () => {
it('should allow to inherit', async(() => { it('should allow to inherit', () => {
compileParentAndChild({ const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@Pipe({name: 'somePipe'})`, parentClassDecorator: `@Pipe({name: 'somePipe'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})', parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Pipe({name: 'somePipe'})`, childClassDecorator: `@Pipe({name: 'somePipe'})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})', childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); }); });
})); expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => { it('should error if the child class has no matching decorator', () => {
compileParentAndChild({ expect(() => compileParentAndChild({
parentClassDecorator: `@Pipe({name: 'somePipe'})`, parentClassDecorator: `@Pipe({name: 'somePipe'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})', parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '', childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})', childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then(fail, (e) => { }))
expect(e.message).toContain( .toThrowError(
'Class Extends in /app/main.ts extends from a Pipe in another compilation unit without duplicating the decorator. ' + 'Class Extends in /app/main.ts extends from a Pipe in another compilation unit without duplicating the decorator. ' +
'Please add a Pipe decorator to the class.'); 'Please add a Pipe decorator to the class.');
}); });
}));
}); });
describe('NgModule', () => { describe('NgModule', () => {
it('should allow to inherit', async(() => { it('should allow to inherit', () => {
compileParentAndChild({ const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@NgModule()`, parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '', parentModuleDecorator: '',
childClassDecorator: `@NgModule()`, childClassDecorator: `@NgModule()`,
childModuleDecorator: '', childModuleDecorator: '',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); }); });
})); expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => { it('should error if the child class has no matching decorator', () => {
compileParentAndChild({ expect(() => compileParentAndChild({
parentClassDecorator: `@NgModule()`, parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '', parentModuleDecorator: '',
childClassDecorator: '', childClassDecorator: '',
childModuleDecorator: '', childModuleDecorator: '',
}).then(fail, (e) => { }))
expect(e.message).toContain( .toThrowError(
'Class Extends in /app/main.ts extends from a NgModule in another compilation unit without duplicating the decorator. ' + 'Class Extends in /app/main.ts extends from a NgModule in another compilation unit without duplicating the decorator. ' +
'Please add a NgModule decorator to the class.'); 'Please add a NgModule decorator to the class.');
}); });
}));
}); });
}); });
}); });
@ -624,11 +602,11 @@ describe('compiler (bundled Angular)', () => {
}); });
describe('Quickstart', () => { describe('Quickstart', () => {
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => { it('should compile', () => {
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) const {genFiles} = compile([QUICKSTART, angularFiles]);
.toBeDefined(); expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined(); expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
}))); });
}); });
describe('Bundled library', () => { describe('Bundled library', () => {
@ -662,7 +640,7 @@ describe('compiler (bundled Angular)', () => {
({fileName, content}) => ({fileName: `/node_modules${fileName}`, content}))); ({fileName, content}) => ({fileName: `/node_modules${fileName}`, content})));
}); });
it('should compile', async(() => compile([LIBRARY_USING_APP, libraryFiles, angularFiles]))); it('should compile', () => compile([LIBRARY_USING_APP, libraryFiles, angularFiles]));
}); });
}); });
@ -767,8 +745,3 @@ const LIBRARY_USING_APP: MockDirectory = {
} }
} }
}; };
function expectPromiseToThrow(p: Promise<any>, msg: RegExp) {
p.then(
() => { throw new Error('Expected to throw'); }, (e) => { expect(e.message).toMatch(msg); });
}

View File

@ -7,7 +7,6 @@
*/ */
import {AotCompiler, AotCompilerHost, AotCompilerOptions, CompileSummaryKind, GeneratedFile, createAotCompiler, toTypeScript} from '@angular/compiler'; import {AotCompiler, AotCompilerHost, AotCompilerOptions, CompileSummaryKind, GeneratedFile, createAotCompiler, toTypeScript} from '@angular/compiler';
import {fakeAsync, tick} from '@angular/core/testing';
import {MockDirectory, compile, setup} from './test_util'; import {MockDirectory, compile, setup} from './test_util';
@ -16,19 +15,12 @@ describe('aot summaries for jit', () => {
function compileApp(rootDir: MockDirectory, options: {useSummaries?: boolean} = {}): function compileApp(rootDir: MockDirectory, options: {useSummaries?: boolean} = {}):
{genFiles: GeneratedFile[], outDir: MockDirectory} { {genFiles: GeneratedFile[], outDir: MockDirectory} {
let result: {genFiles: GeneratedFile[], outDir: MockDirectory} = null !; return compile([rootDir, angularFiles], options);
let error: Error|null = null;
compile([rootDir, angularFiles], options).then((r) => result = r, (e) => error = e);
tick();
if (error) {
throw error;
}
return result;
} }
it('should create @Injectable summaries', fakeAsync(() => { it('should create @Injectable summaries', () => {
const appDir = { const appDir = {
'app.module.ts': ` 'app.module.ts': `
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
export class Dep {} export class Dep {}
@ -38,23 +30,23 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {} constructor(d: Dep) {}
} }
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain('export function MyServiceNgSummary()');
expect(genSource).toContain('export function MyServiceNgSummary()'); // Note: CompileSummaryKind.Injectable = 3
// Note: CompileSummaryKind.Injectable = 3 expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i0.MyService/);
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i0.MyService/); expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}'); });
}));
it('should create @Pipe summaries', fakeAsync(() => { it('should create @Pipe summaries', () => {
const appDir = { const appDir = {
'app.module.ts': ` 'app.module.ts': `
import { Pipe, NgModule } from '@angular/core'; import { Pipe, NgModule } from '@angular/core';
export class Dep {} export class Dep {}
@ -67,23 +59,23 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyPipe]}) @NgModule({declarations: [MyPipe]})
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain('export function MyPipeNgSummary()');
expect(genSource).toContain('export function MyPipeNgSummary()'); // Note: CompileSummaryKind.Pipe = 1
// Note: CompileSummaryKind.Pipe = 1 expect(genSource).toMatch(/summaryKind:0,\s*type:\{\s*reference:i0.MyPipe/);
expect(genSource).toMatch(/summaryKind:0,\s*type:\{\s*reference:i0.MyPipe/); expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}'); });
}));
it('should create @Directive summaries', fakeAsync(() => { it('should create @Directive summaries', () => {
const appDir = { const appDir = {
'app.module.ts': ` 'app.module.ts': `
import { Directive, NgModule } from '@angular/core'; import { Directive, NgModule } from '@angular/core';
export class Dep {} export class Dep {}
@ -96,23 +88,23 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyDirective]}) @NgModule({declarations: [MyDirective]})
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain('export function MyDirectiveNgSummary()');
expect(genSource).toContain('export function MyDirectiveNgSummary()'); // Note: CompileSummaryKind.Directive = 1
// Note: CompileSummaryKind.Directive = 1 expect(genSource).toMatch(/summaryKind:1,\s*type:\{\s*reference:i0.MyDirective/);
expect(genSource).toMatch(/summaryKind:1,\s*type:\{\s*reference:i0.MyDirective/); expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}'); });
}));
it('should create @NgModule summaries', fakeAsync(() => { it('should create @NgModule summaries', () => {
const appDir = { const appDir = {
'app.module.ts': ` 'app.module.ts': `
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
export class Dep {} export class Dep {}
@ -122,23 +114,23 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {} constructor(d: Dep) {}
} }
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain('export function MyModuleNgSummary()');
expect(genSource).toContain('export function MyModuleNgSummary()'); // Note: CompileSummaryKind.NgModule = 2
// Note: CompileSummaryKind.NgModule = 2 expect(genSource).toMatch(/summaryKind:2,\s*type:\{\s*reference:i0.MyModule/);
expect(genSource).toMatch(/summaryKind:2,\s*type:\{\s*reference:i0.MyModule/); expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}'); });
}));
it('should embed useClass provider summaries in @Directive summaries', fakeAsync(() => { it('should embed useClass provider summaries in @Directive summaries', () => {
const appDir = { const appDir = {
'app.service.ts': ` 'app.service.ts': `
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
export class Dep {} export class Dep {}
@ -148,7 +140,7 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {} constructor(d: Dep) {}
} }
`, `,
'app.module.ts': ` 'app.module.ts': `
import { Directive, NgModule } from '@angular/core'; import { Directive, NgModule } from '@angular/core';
import { MyService } from './app.service'; import { MyService } from './app.service';
@ -161,22 +153,22 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyDirective]}) @NgModule({declarations: [MyDirective]})
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); // Note: CompileSummaryKind.Injectable = 3
// Note: CompileSummaryKind.Injectable = 3 expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/);
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/); expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}');
expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}'); });
}));
it('should embed useClass provider summaries into @NgModule summaries', fakeAsync(() => { it('should embed useClass provider summaries into @NgModule summaries', () => {
const appDir = { const appDir = {
'app.service.ts': ` 'app.service.ts': `
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
export class Dep {} export class Dep {}
@ -186,7 +178,7 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {} constructor(d: Dep) {}
} }
`, `,
'app.module.ts': ` 'app.module.ts': `
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MyService } from './app.service'; import { MyService } from './app.service';
@ -195,23 +187,22 @@ describe('aot summaries for jit', () => {
}) })
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); // Note: CompileSummaryKind.Injectable = 3
// Note: CompileSummaryKind.Injectable = 3 expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/);
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/); expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}');
expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}'); });
}));
it('should reference declared @Directive and @Pipe summaries in @NgModule summaries', it('should reference declared @Directive and @Pipe summaries in @NgModule summaries', () => {
fakeAsync(() => { const appDir = {
const appDir = { 'app.module.ts': `
'app.module.ts': `
import { Directive, Pipe, NgModule } from '@angular/core'; import { Directive, Pipe, NgModule } from '@angular/core';
@Directive({selector: '[myDir]'}) @Directive({selector: '[myDir]'})
@ -223,20 +214,20 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyDirective, MyPipe]}) @NgModule({declarations: [MyDirective, MyPipe]})
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toMatch(
expect(genSource).toMatch( /export function MyModuleNgSummary()[^;]*,\s*MyDirectiveNgSummary,\s*MyPipeNgSummary\s*\]\s*;/);
/export function MyModuleNgSummary()[^;]*,\s*MyDirectiveNgSummary,\s*MyPipeNgSummary\s*\]\s*;/); });
}));
it('should reference imported @NgModule summaries in @NgModule summaries', fakeAsync(() => { it('should reference imported @NgModule summaries in @NgModule summaries', () => {
const appDir = { const appDir = {
'app.module.ts': ` 'app.module.ts': `
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
@NgModule() @NgModule()
@ -245,20 +236,20 @@ describe('aot summaries for jit', () => {
@NgModule({imports: [MyImportedModule]}) @NgModule({imports: [MyImportedModule]})
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile); expect(genSource).toMatch(
expect(genSource).toMatch( /export function MyModuleNgSummary()[^;]*,\s*MyImportedModuleNgSummary\s*\]\s*;/);
/export function MyModuleNgSummary()[^;]*,\s*MyImportedModuleNgSummary\s*\]\s*;/); });
}));
it('should create and use reexports for imported NgModules ' + it('should create and use reexports for imported NgModules ' +
'accross compilation units', 'accross compilation units',
fakeAsync(() => { () => {
const lib1In = { const lib1In = {
'lib1': { 'lib1': {
'module.ts': ` 'module.ts': `
@ -348,5 +339,5 @@ describe('aot summaries for jit', () => {
expect(toTypeScript(lib3ReexportNgSummary)) expect(toTypeScript(lib3ReexportNgSummary))
.toContain( .toContain(
`export {ReexportModule_2NgSummary as ReexportModule_3NgSummary} from '/lib2/reexport.ngsummary'`); `export {ReexportModule_2NgSummary as ReexportModule_3NgSummary} from '/lib2/reexport.ngsummary'`);
})); });
}); });

View File

@ -5,16 +5,15 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {async} from '@angular/core/testing';
import {MockDirectory, compile, expectNoDiagnostics, setup} from './test_util'; import {MockDirectory, compile, expectNoDiagnostics, setup} from './test_util';
describe('regressions', () => { describe('regressions', () => {
let angularFiles = setup(); let angularFiles = setup();
it('should compile components with empty templates', async(() => { it('should compile components with empty templates', () => {
const appDir = { const appDir = {
'app.module.ts': ` 'app.module.ts': `
import { Component, NgModule } from '@angular/core'; import { Component, NgModule } from '@angular/core';
@Component({template: ''}) @Component({template: ''})
@ -23,14 +22,11 @@ describe('regressions', () => {
@NgModule({declarations: [EmptyComp]}) @NgModule({declarations: [EmptyComp]})
export class MyModule {} export class MyModule {}
` `
}; };
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics}, { const {genFiles} = compile(
noUnusedLocals: true, [rootDir, angularFiles], {postCompile: expectNoDiagnostics},
noUnusedParameters: true {noUnusedLocals: true, noUnusedParameters: true});
}).then((result) => { expect(genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts')).toBeTruthy();
expect(result.genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts')) });
.toBeTruthy();
});
}));
}); });

View File

@ -399,11 +399,11 @@ export class MockAotCompilerHost implements AotCompilerHost {
return importedFile.replace(EXT, ''); return importedFile.replace(EXT, '');
} }
loadResource(path: string): Promise<string> { loadResource(path: string): string {
if (this.tsHost.fileExists(path)) { if (this.tsHost.fileExists(path)) {
return Promise.resolve(this.tsHost.readFile(path)); return this.tsHost.readFile(path);
} else { } else {
return Promise.reject(new Error(`Resource ${path} not found.`)) throw new Error(`Resource ${path} not found.`);
} }
} }
} }
@ -604,47 +604,43 @@ export function compile(
preCompile?: (program: ts.Program) => void, preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void, postCompile?: (program: ts.Program) => void,
}& AotCompilerOptions = {}, }& AotCompilerOptions = {},
tsOptions: ts.CompilerOptions = {}): tsOptions: ts.CompilerOptions = {}): {genFiles: GeneratedFile[], outDir: MockDirectory} {
Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> { // when using summaries, always emit so the next step can use the results.
// Make sure we always return errors via the promise... const emit = options.emit || options.useSummaries;
return Promise.resolve(null).then(() => { const preCompile = options.preCompile || expectNoDiagnostics;
// when using summaries, always emit so the next step can use the results. const postCompile = options.postCompile || expectNoDiagnostics;
const emit = options.emit || options.useSummaries; const rootDirArr = toMockFileArray(rootDirs);
const preCompile = options.preCompile || expectNoDiagnostics; const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
const postCompile = options.postCompile || expectNoDiagnostics;
const rootDirArr = toMockFileArray(rootDirs);
const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr)); const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
const aotHost = new MockAotCompilerHost(host); const aotHost = new MockAotCompilerHost(host);
if (options.useSummaries) { if (options.useSummaries) {
aotHost.hideMetadata(); aotHost.hideMetadata();
aotHost.tsFilesOnly(); aotHost.tsFilesOnly();
}
const tsSettings = {...settings, ...tsOptions};
const program = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
const genFiles = compiler.compileAllSync(program.getSourceFiles().map(sf => sf.fileName));
genFiles.forEach((file) => {
const source = file.source || toTypeScript(file);
if (isSource(file.genFileUrl)) {
host.addScript(file.genFileUrl, source);
} else {
host.override(file.genFileUrl, source);
} }
const tsSettings = {...settings, ...tsOptions};
const scripts = host.scriptNames.slice(0);
const program = ts.createProgram(scripts, tsSettings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)).then(genFiles => {
genFiles.forEach((file) => {
const source = file.source || toTypeScript(file);
isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, source) :
host.override(file.genFileUrl, source);
});
const scripts = host.scriptNames.slice(0);
const newProgram = ts.createProgram(scripts, tsSettings, host);
if (postCompile) postCompile(newProgram);
if (emit) {
newProgram.emit();
}
let outDir: MockDirectory = {};
if (emit) {
outDir = arrayToMockDir(toMockFileArray([
host.writtenFiles, host.overrides
]).filter((entry) => !isSource(entry.fileName)));
}
return {genFiles, outDir};
});
}); });
const newProgram = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
if (postCompile) postCompile(newProgram);
if (emit) {
newProgram.emit();
}
let outDir: MockDirectory = {};
if (emit) {
outDir = arrayToMockDir(toMockFileArray([
host.writtenFiles, host.overrides
]).filter((entry) => !isSource(entry.fileName)));
}
return {genFiles, outDir};
} }

View File

@ -16,7 +16,7 @@ import {ViewEncapsulation} from '@angular/core/src/metadata/view';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal'; import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
import {SyncAsyncResult, noUndefined} from '../src/util'; import {noUndefined} from '../src/util';
import {SpyResourceLoader} from './spies'; import {SpyResourceLoader} from './spies';
@ -46,7 +46,7 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
}); });
} }
function normalizeTemplateAsync(normalizer: DirectiveNormalizer, o: { function normalizeTemplateOnly(normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null; ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null; templateUrl?: string | null;
styles?: string[]; styles?: string[];
@ -55,30 +55,7 @@ function normalizeTemplateAsync(normalizer: DirectiveNormalizer, o: {
encapsulation?: ViewEncapsulation | null; encapsulation?: ViewEncapsulation | null;
animations?: CompileAnimationEntryMetadata[]; animations?: CompileAnimationEntryMetadata[];
}) { }) {
return normalizer.normalizeTemplateAsync({ return normalizer.normalizeTemplateOnly({
ngModuleType: noUndefined(o.ngModuleType),
componentType: noUndefined(o.componentType),
moduleUrl: noUndefined(o.moduleUrl),
template: noUndefined(o.template),
templateUrl: noUndefined(o.templateUrl),
styles: noUndefined(o.styles),
styleUrls: noUndefined(o.styleUrls),
interpolation: noUndefined(o.interpolation),
encapsulation: noUndefined(o.encapsulation),
animations: noUndefined(o.animations)
});
}
function normalizeTemplateSync(normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null;
styles?: string[];
styleUrls?: string[];
interpolation?: [string, string] | null;
encapsulation?: ViewEncapsulation | null;
animations?: CompileAnimationEntryMetadata[];
}): CompileTemplateMetadata {
return normalizer.normalizeTemplateSync({
ngModuleType: noUndefined(o.ngModuleType), ngModuleType: noUndefined(o.ngModuleType),
componentType: noUndefined(o.componentType), componentType: noUndefined(o.componentType),
moduleUrl: noUndefined(o.moduleUrl), moduleUrl: noUndefined(o.moduleUrl),
@ -194,10 +171,10 @@ export function main() {
})); }));
}); });
describe('normalizeTemplateSync', () => { describe('normalizeTemplateOnly sync', () => {
it('should store the template', it('should store the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, { const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -214,7 +191,7 @@ export function main() {
it('should resolve styles on the annotation against the moduleUrl', it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, { const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -229,7 +206,7 @@ export function main() {
it('should resolve styles in the template against the moduleUrl', it('should resolve styles in the template against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, { const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -244,7 +221,7 @@ export function main() {
it('should use ViewEncapsulation.Emulated by default', it('should use ViewEncapsulation.Emulated by default',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, { const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -262,7 +239,7 @@ export function main() {
[CompilerConfig, DirectiveNormalizer], [CompilerConfig, DirectiveNormalizer],
(config: CompilerConfig, normalizer: DirectiveNormalizer) => { (config: CompilerConfig, normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None; config.defaultEncapsulation = ViewEncapsulation.None;
const template = normalizeTemplateSync(normalizer, { const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -284,7 +261,7 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, (async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => { resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/sometplurl.html', 'a'); resourceLoader.expect('package:some/module/sometplurl.html', 'a');
normalizeTemplateAsync(normalizer, { (<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -293,7 +270,7 @@ export function main() {
templateUrl: 'sometplurl.html', templateUrl: 'sometplurl.html',
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
}).then((template: CompileTemplateMetadata) => { })).then((template) => {
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html'); expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
expect(template.isInline).toBe(false); expect(template.isInline).toBe(false);
@ -308,7 +285,7 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, (async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => { resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/tpl/sometplurl.html', ''); resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
normalizeTemplateAsync(normalizer, { (<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -317,7 +294,7 @@ export function main() {
templateUrl: 'tpl/sometplurl.html', templateUrl: 'tpl/sometplurl.html',
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
}).then((template: CompileTemplateMetadata) => { })).then((template) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done(); async.done();
}); });
@ -331,7 +308,7 @@ export function main() {
resourceLoader: MockResourceLoader) => { resourceLoader: MockResourceLoader) => {
resourceLoader.expect( resourceLoader.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>'); 'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
normalizeTemplateAsync(normalizer, { (<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null, ngModuleType: null,
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
@ -340,7 +317,7 @@ export function main() {
templateUrl: 'tpl/sometplurl.html', templateUrl: 'tpl/sometplurl.html',
styles: [], styles: [],
styleUrls: [] styleUrls: []
}).then((template: CompileTemplateMetadata) => { })).then((template) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']); expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done(); async.done();
}); });
@ -362,13 +339,13 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, (async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: SpyResourceLoader) => { resourceLoader: SpyResourceLoader) => {
programResourceLoaderSpy(resourceLoader, {'package:some/module/test.css': 'a'}); programResourceLoaderSpy(resourceLoader, {'package:some/module/test.css': 'a'});
normalizer (<Promise<CompileTemplateMetadata>>normalizer.normalizeExternalStylesheets(
.normalizeExternalStylesheets(compileTemplateMetadata({ compileTemplateMetadata({
template: '', template: '',
templateUrl: '', templateUrl: '',
styleUrls: ['package:some/module/test.css'] styleUrls: ['package:some/module/test.css']
})) })))
.then((template: CompileTemplateMetadata) => { .then((template) => {
expect(template.externalStylesheets.length).toBe(1); expect(template.externalStylesheets.length).toBe(1);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({ expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css', moduleUrl: 'package:some/module/test.css',
@ -388,13 +365,13 @@ export function main() {
'package:some/module/test.css': 'a@import "test2.css"', 'package:some/module/test.css': 'a@import "test2.css"',
'package:some/module/test2.css': 'b' 'package:some/module/test2.css': 'b'
}); });
normalizer (<Promise<CompileTemplateMetadata>>normalizer.normalizeExternalStylesheets(
.normalizeExternalStylesheets(compileTemplateMetadata({ compileTemplateMetadata({
template: '', template: '',
templateUrl: '', templateUrl: '',
styleUrls: ['package:some/module/test.css'] styleUrls: ['package:some/module/test.css']
})) })))
.then((template: CompileTemplateMetadata) => { .then((template) => {
expect(template.externalStylesheets.length).toBe(2); expect(template.externalStylesheets.length).toBe(2);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({ expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css', moduleUrl: 'package:some/module/test.css',
@ -426,8 +403,8 @@ export function main() {
}; };
Promise Promise
.all([ .all([
normalizeTemplateAsync(normalizer, prenormMeta), normalizeTemplateOnly(normalizer, prenormMeta),
normalizeTemplateAsync(normalizer, prenormMeta) normalizeTemplateOnly(normalizer, prenormMeta)
]) ])
.then((templates: CompileTemplateMetadata[]) => { .then((templates: CompileTemplateMetadata[]) => {
expect(templates[0].template).toEqual('a'); expect(templates[0].template).toEqual('a');

View File

@ -7,18 +7,10 @@
*/ */
import {fakeAsync} from '@angular/core/testing/src/fake_async'; import {fakeAsync} from '@angular/core/testing/src/fake_async';
import {SyncAsyncResult, escapeRegExp, splitAtColon, utf8Encode} from '../src/util'; import {SyncAsync, escapeRegExp, splitAtColon, utf8Encode} from '../src/util';
export function main() { export function main() {
describe('util', () => { describe('util', () => {
describe('SyncAsyncResult', () => {
it('async value should default to Promise.resolve(syncValue)', fakeAsync(() => {
const syncValue = {};
const sar = new SyncAsyncResult(syncValue);
sar.asyncResult !.then((v: any) => expect(v).toBe(syncValue));
}));
});
describe('splitAtColon', () => { describe('splitAtColon', () => {
it('should split when a single ":" is present', () => { it('should split when a single ":" is present', () => {
expect(splitAtColon('a:b', [])).toEqual(['a', 'b']); expect(splitAtColon('a:b', [])).toEqual(['a', 'b']);