refactor(compiler): simplify AOT tests
This commit is contained in:
parent
21c96a5af1
commit
c946a929b7
|
@ -6,8 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
import {GeneratedFile} from '@angular/compiler';
|
||||||
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
|
||||||
import {NodeFlags} from '@angular/core/src/view/index';
|
import {NodeFlags} from '@angular/core/src/view/index';
|
||||||
import {async} from '@angular/core/testing';
|
import {async} from '@angular/core/testing';
|
||||||
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
||||||
|
@ -15,60 +14,16 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';
|
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';
|
||||||
|
|
||||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, settings} from './test_util';
|
import {EmittingCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, compile, settings, setup, toMockFileArray} from './test_util';
|
||||||
|
|
||||||
const DTS = /\.d\.ts$/;
|
|
||||||
|
|
||||||
const minCoreIndex = `
|
|
||||||
export * from './src/application_module';
|
|
||||||
export * from './src/change_detection';
|
|
||||||
export * from './src/metadata';
|
|
||||||
export * from './src/di/metadata';
|
|
||||||
export * from './src/di/injector';
|
|
||||||
export * from './src/di/injection_token';
|
|
||||||
export * from './src/linker';
|
|
||||||
export * from './src/render';
|
|
||||||
export * from './src/codegen_private_exports';
|
|
||||||
`;
|
|
||||||
|
|
||||||
describe('compiler (unbundled Angular)', () => {
|
describe('compiler (unbundled Angular)', () => {
|
||||||
let angularFiles: Map<string, string>;
|
let angularFiles = setup();
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
const emittingHost = new EmittingCompilerHost([], {emitMetadata: true});
|
|
||||||
emittingHost.addScript('@angular/core/index.ts', minCoreIndex);
|
|
||||||
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
|
||||||
emittingProgram.emit();
|
|
||||||
|
|
||||||
angularFiles = emittingHost.written;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
|
||||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
|
||||||
|
|
||||||
describe('Quickstart', () => {
|
describe('Quickstart', () => {
|
||||||
let host: MockCompilerHost;
|
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
|
||||||
let aotHost: MockAotCompilerHost;
|
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
|
|
||||||
aotHost = new MockAotCompilerHost(host);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile',
|
|
||||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
|
||||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
|
||||||
.toBeDefined();
|
|
||||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
|
||||||
.toBeDefined();
|
|
||||||
})));
|
|
||||||
|
|
||||||
it('should compile using summaries',
|
|
||||||
async(() => summaryCompile(host, aotHost).then(generatedFiles => {
|
|
||||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
|
||||||
.toBeDefined();
|
|
||||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
|
||||||
.toBeDefined();
|
.toBeDefined();
|
||||||
|
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,17 +52,11 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function compileApp(): Promise<GeneratedFile> {
|
function compileApp(): Promise<GeneratedFile> {
|
||||||
return new Promise((resolve, reject) => {
|
return compile([rootDir, angularFiles])
|
||||||
const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles);
|
.then(
|
||||||
const aotHost = new MockAotCompilerHost(host);
|
({genFiles}) => {return genFiles.find(
|
||||||
let result: GeneratedFile[];
|
genFile =>
|
||||||
let error: Error;
|
genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))});
|
||||||
resolve(compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)
|
|
||||||
.then(
|
|
||||||
(files) => files.find(
|
|
||||||
genFile => genFile.srcFileUrl === componentPath &&
|
|
||||||
genFile.genFileUrl.endsWith('.ts'))));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLineAndColumn(
|
function findLineAndColumn(
|
||||||
|
@ -247,7 +196,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
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(() => {
|
async(() => {
|
||||||
const FILES: MockData = {
|
const FILES: MockDirectory = {
|
||||||
app: {
|
app: {
|
||||||
'app.ts': `
|
'app.ts': `
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
|
@ -259,10 +208,8 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
|
||||||
const aotHost = new MockAotCompilerHost(host);
|
|
||||||
const warnSpy = spyOn(console, 'warn');
|
const warnSpy = spyOn(console, 'warn');
|
||||||
compile(host, aotHost, expectNoDiagnostics).then(() => {
|
compile([FILES, angularFiles]).then(() => {
|
||||||
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`);
|
||||||
});
|
});
|
||||||
|
@ -271,7 +218,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add the preamble to generated files', async(() => {
|
it('should add the preamble to generated files', async(() => {
|
||||||
const FILES: MockData = {
|
const FILES: MockDirectory = {
|
||||||
app: {
|
app: {
|
||||||
'app.ts': `
|
'app.ts': `
|
||||||
import { NgModule, Component } from '@angular/core';
|
import { NgModule, Component } from '@angular/core';
|
||||||
|
@ -284,22 +231,19 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
|
||||||
const aotHost = new MockAotCompilerHost(host);
|
|
||||||
const genFilePreamble = '/* Hello world! */';
|
const genFilePreamble = '/* Hello world! */';
|
||||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
compile([FILES, angularFiles], {genFilePreamble}).then(({genFiles}) => {
|
||||||
.then((generatedFiles) => {
|
const genFile =
|
||||||
const genFile = generatedFiles.find(
|
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
|
||||||
expect(genFile.source.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(() => {
|
async(() => {
|
||||||
const FILES: MockData = {
|
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';
|
||||||
|
@ -323,11 +267,8 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
compile([FILES, angularFiles]).then(({genFiles}) => {
|
||||||
const aotHost = new MockAotCompilerHost(host);
|
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
||||||
let generatedFiles: GeneratedFile[];
|
|
||||||
compile(host, aotHost, expectNoDiagnostics).then((generatedFiles) => {
|
|
||||||
const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
|
||||||
const createComponentFactoryCall =
|
const createComponentFactoryCall =
|
||||||
/ɵccf\([^)]*\)/m.exec(genFile.source) ![0].replace(/\s*/g, '');
|
/ɵccf\([^)]*\)/m.exec(genFile.source) ![0].replace(/\s*/g, '');
|
||||||
// selector
|
// selector
|
||||||
|
@ -345,7 +286,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
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(() => {
|
async(() => {
|
||||||
const FILES: MockData = {
|
const FILES: MockDirectory = {
|
||||||
app: {
|
app: {
|
||||||
'app.ts': `
|
'app.ts': `
|
||||||
import { NgModule, Component } from '@angular/core';
|
import { NgModule, Component } from '@angular/core';
|
||||||
|
@ -358,37 +299,16 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
compile([FILES, angularFiles]).then(({genFiles}) => {
|
||||||
const aotHost = new MockAotCompilerHost(host);
|
const genFile = genFiles.find(
|
||||||
const genFilePreamble = '/* Hello world! */';
|
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
expect(genFile.source).not.toContain('check(');
|
||||||
.then((generatedFiles) => {
|
});
|
||||||
const genFile = generatedFiles.find(
|
|
||||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
|
||||||
expect(genFile.source).not.toContain('check(');
|
|
||||||
});
|
|
||||||
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inheritance with summaries', () => {
|
describe('inheritance with summaries', () => {
|
||||||
function compileWithSummaries(
|
|
||||||
libInput: MockData, appInput: MockData): Promise<GeneratedFile[]> {
|
|
||||||
const libHost = new MockCompilerHost(['/lib/base.ts'], libInput, angularFiles);
|
|
||||||
const libAotHost = new MockAotCompilerHost(libHost);
|
|
||||||
libAotHost.tsFilesOnly();
|
|
||||||
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
|
|
||||||
const appAotHost = new MockAotCompilerHost(appHost);
|
|
||||||
appAotHost.tsFilesOnly();
|
|
||||||
return compile(libHost, libAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
|
|
||||||
.then(() => {
|
|
||||||
libHost.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
|
|
||||||
libHost.overrides.forEach((value, key) => appHost.override(key, value));
|
|
||||||
|
|
||||||
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileParentAndChild(
|
function compileParentAndChild(
|
||||||
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
|
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
|
||||||
parentClassDecorator: string,
|
parentClassDecorator: string,
|
||||||
|
@ -396,7 +316,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
childClassDecorator: string,
|
childClassDecorator: string,
|
||||||
childModuleDecorator: string
|
childModuleDecorator: string
|
||||||
}) {
|
}) {
|
||||||
const libInput: MockData = {
|
const libInput: MockDirectory = {
|
||||||
'lib': {
|
'lib': {
|
||||||
'base.ts': `
|
'base.ts': `
|
||||||
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||||
|
@ -409,7 +329,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const appInput: MockData = {
|
const appInput: MockDirectory = {
|
||||||
'app': {
|
'app': {
|
||||||
'main.ts': `
|
'main.ts': `
|
||||||
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||||
|
@ -424,13 +344,14 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return compileWithSummaries(libInput, appInput)
|
return compile([libInput, angularFiles], {useSummaries: true})
|
||||||
.then((generatedFiles) => generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
|
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||||
|
.then(({genFiles}) => 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(() => {
|
async(() => {
|
||||||
const libInput: MockData = {
|
const libInput: MockDirectory = {
|
||||||
'lib': {
|
'lib': {
|
||||||
'base.ts': `
|
'base.ts': `
|
||||||
export class AParam {}
|
export class AParam {}
|
||||||
|
@ -442,7 +363,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const appInput: MockData = {
|
const appInput: MockDirectory = {
|
||||||
'app': {
|
'app': {
|
||||||
'main.ts': `
|
'main.ts': `
|
||||||
import {NgModule, Component} from '@angular/core';
|
import {NgModule, Component} from '@angular/core';
|
||||||
|
@ -459,17 +380,19 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
compileWithSummaries(libInput, appInput).then((generatedFiles) => {
|
compile([libInput, angularFiles], {useSummaries: true})
|
||||||
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
.then(({genFiles}) => {
|
||||||
expect(mainNgFactory.source)
|
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
|
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||||
});
|
expect(mainNgFactory.source)
|
||||||
|
.toContain(`${flags},(null as any),0,import1.Extends,[import2.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(() => {
|
async(() => {
|
||||||
const lib1Input: MockData = {
|
const lib1Input: MockDirectory = {
|
||||||
'lib1': {
|
'lib1': {
|
||||||
'base.ts': `
|
'base.ts': `
|
||||||
export class AParam {}
|
export class AParam {}
|
||||||
|
@ -482,7 +405,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const lib2Input: MockData = {
|
const lib2Input: MockDirectory = {
|
||||||
'lib2': {
|
'lib2': {
|
||||||
'middle.ts': `
|
'middle.ts': `
|
||||||
import {Base} from '../lib1/base';
|
import {Base} from '../lib1/base';
|
||||||
|
@ -492,7 +415,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const appInput: MockData = {
|
const appInput: MockDirectory = {
|
||||||
'app': {
|
'app': {
|
||||||
'main.ts': `
|
'main.ts': `
|
||||||
import {NgModule, Component} from '@angular/core';
|
import {NgModule, Component} from '@angular/core';
|
||||||
|
@ -508,29 +431,11 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const lib1Host = new MockCompilerHost(['/lib1/base.ts'], lib1Input, angularFiles);
|
compile([lib1Input, angularFiles], {useSummaries: true})
|
||||||
const lib1AotHost = new MockAotCompilerHost(lib1Host);
|
.then(({outDir}) => compile([outDir, lib2Input, angularFiles], {useSummaries: true}))
|
||||||
lib1AotHost.tsFilesOnly();
|
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||||
const lib2Host = new MockCompilerHost(['/lib2/middle.ts'], lib2Input, angularFiles);
|
.then(({genFiles}) => {
|
||||||
const lib2AotHost = new MockAotCompilerHost(lib2Host);
|
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||||
lib2AotHost.tsFilesOnly();
|
|
||||||
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
|
|
||||||
const appAotHost = new MockAotCompilerHost(appHost);
|
|
||||||
appAotHost.tsFilesOnly();
|
|
||||||
compile(lib1Host, lib1AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
|
|
||||||
.then(() => {
|
|
||||||
lib1Host.writtenFiles.forEach((value, key) => lib2Host.writeFile(key, value, false));
|
|
||||||
lib1Host.overrides.forEach((value, key) => lib2Host.override(key, value));
|
|
||||||
return compile(
|
|
||||||
lib2Host, lib2AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
lib2Host.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
|
|
||||||
lib2Host.overrides.forEach((value, key) => appHost.override(key, value));
|
|
||||||
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
|
||||||
})
|
|
||||||
.then((generatedFiles) => {
|
|
||||||
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
|
||||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||||
expect(mainNgFactory.source)
|
expect(mainNgFactory.source)
|
||||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam_2]`);
|
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam_2]`);
|
||||||
|
@ -660,6 +565,8 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compiler (bundled Angular)', () => {
|
describe('compiler (bundled Angular)', () => {
|
||||||
|
setup({compileAngular: false});
|
||||||
|
|
||||||
let angularFiles: Map<string, string>;
|
let angularFiles: Map<string, string>;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -681,34 +588,19 @@ describe('compiler (bundled Angular)', () => {
|
||||||
const bundleIndexName = emittingHost.effectiveName('@angular/core/bundle_index.ts');
|
const bundleIndexName = emittingHost.effectiveName('@angular/core/bundle_index.ts');
|
||||||
const emittingProgram = ts.createProgram([bundleIndexName], settings, emittingHost);
|
const emittingProgram = ts.createProgram([bundleIndexName], settings, emittingHost);
|
||||||
emittingProgram.emit();
|
emittingProgram.emit();
|
||||||
angularFiles = emittingHost.written;
|
angularFiles = emittingHost.writtenAngularFiles();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Quickstart', () => {
|
describe('Quickstart', () => {
|
||||||
let host: MockCompilerHost;
|
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
|
||||||
let aotHost: MockAotCompilerHost;
|
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
|
|
||||||
aotHost = new MockAotCompilerHost(host);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
|
||||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
|
||||||
|
|
||||||
it('should compile',
|
|
||||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
|
||||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
|
||||||
.toBeDefined();
|
|
||||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
|
||||||
.toBeDefined();
|
.toBeDefined();
|
||||||
|
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Bundled library', () => {
|
describe('Bundled library', () => {
|
||||||
let host: MockCompilerHost;
|
let libraryFiles: MockDirectory;
|
||||||
let aotHost: MockAotCompilerHost;
|
|
||||||
let libraryFiles: Map<string, string>;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// Emit the library bundle
|
// Emit the library bundle
|
||||||
|
@ -728,135 +620,22 @@ describe('compiler (bundled Angular)', () => {
|
||||||
// Emit the sources
|
// Emit the sources
|
||||||
const emittingProgram = ts.createProgram(['/bolder/index.ts'], settings, emittingHost);
|
const emittingProgram = ts.createProgram(['/bolder/index.ts'], settings, emittingHost);
|
||||||
emittingProgram.emit();
|
emittingProgram.emit();
|
||||||
libraryFiles = emittingHost.written;
|
const libFiles = emittingHost.written;
|
||||||
|
|
||||||
// Copy the .html file
|
// Copy the .html file
|
||||||
const htmlFileName = '/bolder/src/bolder.component.html';
|
const htmlFileName = '/bolder/src/bolder.component.html';
|
||||||
libraryFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
|
libFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
|
||||||
|
|
||||||
|
libraryFiles = arrayToMockDir(toMockFileArray(libFiles).map(
|
||||||
|
({fileName, content}) => ({fileName: `/node_modules${fileName}`, content})));
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
it('should compile', async(() => compile([LIBRARY_USING_APP, libraryFiles, angularFiles])));
|
||||||
host = new MockCompilerHost(
|
|
||||||
LIBRARY_USING_APP_MODULE, LIBRARY_USING_APP, angularFiles, [libraryFiles]);
|
|
||||||
aotHost = new MockAotCompilerHost(host);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile',
|
|
||||||
async(() => compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)));
|
|
||||||
|
|
||||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
|
||||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function expectNoDiagnostics(program: ts.Program) {
|
|
||||||
function fileInfo(diagnostic: ts.Diagnostic): string {
|
|
||||||
if (diagnostic.file) {
|
|
||||||
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
|
const QUICKSTART: MockDirectory = {
|
||||||
|
|
||||||
function lineNoOf(offset: number, text: string): number {
|
|
||||||
let result = 1;
|
|
||||||
for (let i = 0; i < offset; i++) {
|
|
||||||
if (text[i] == '\n') result++;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lineInfo(diagnostic: ts.Diagnostic): string {
|
|
||||||
if (diagnostic.file) {
|
|
||||||
const start = diagnostic.start;
|
|
||||||
let end = diagnostic.start + diagnostic.length;
|
|
||||||
const source = diagnostic.file.text;
|
|
||||||
let lineStart = start;
|
|
||||||
let lineEnd = end;
|
|
||||||
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
|
|
||||||
if (lineStart < start) lineStart++;
|
|
||||||
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
|
|
||||||
let line = source.substring(lineStart, lineEnd);
|
|
||||||
const lineIndex = line.indexOf('/n');
|
|
||||||
if (lineIndex > 0) {
|
|
||||||
line = line.substr(0, lineIndex);
|
|
||||||
end = start + lineIndex;
|
|
||||||
}
|
|
||||||
const lineNo = lineNoOf(start, source) + ': ';
|
|
||||||
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
|
|
||||||
chars(end - start, '^');
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
|
||||||
if (diagnostics && diagnostics.length) {
|
|
||||||
throw new Error(
|
|
||||||
'Errors from TypeScript:\n' +
|
|
||||||
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expectNoDiagnostics(program.getOptionsDiagnostics());
|
|
||||||
expectNoDiagnostics(program.getSyntacticDiagnostics());
|
|
||||||
expectNoDiagnostics(program.getSemanticDiagnostics());
|
|
||||||
}
|
|
||||||
|
|
||||||
function expectNoDiagnosticsAndEmit(program: ts.Program) {
|
|
||||||
expectNoDiagnostics(program);
|
|
||||||
program.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDTS(fileName: string): boolean {
|
|
||||||
return /\.d\.ts$/.test(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSource(fileName: string): boolean {
|
|
||||||
return /\.ts$/.test(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFactory(fileName: string): boolean {
|
|
||||||
return /\.ngfactory\./.test(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function summaryCompile(
|
|
||||||
host: MockCompilerHost, aotHost: MockAotCompilerHost,
|
|
||||||
preCompile?: (program: ts.Program) => void) {
|
|
||||||
// First compile the program to generate the summary files.
|
|
||||||
return compile(host, aotHost).then(generatedFiles => {
|
|
||||||
// Remove generated files that were not generated from a DTS file
|
|
||||||
host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl));
|
|
||||||
|
|
||||||
// Next compile the program shrowding metadata and only treating .ts files as source.
|
|
||||||
aotHost.hideMetadata();
|
|
||||||
aotHost.tsFilesOnly();
|
|
||||||
|
|
||||||
return compile(host, aotHost);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function compile(
|
|
||||||
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
|
|
||||||
postCompile: (program: ts.Program) => void = expectNoDiagnostics,
|
|
||||||
options: AotCompilerOptions = {}): Promise<GeneratedFile[]> {
|
|
||||||
const scripts = host.scriptNames.slice(0);
|
|
||||||
const program = ts.createProgram(scripts, settings, host);
|
|
||||||
if (preCompile) preCompile(program);
|
|
||||||
const {compiler, reflector} = createAotCompiler(aotHost, options);
|
|
||||||
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName))
|
|
||||||
.then(generatedFiles => {
|
|
||||||
generatedFiles.forEach(
|
|
||||||
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
|
|
||||||
host.override(file.genFileUrl, file.source));
|
|
||||||
const scripts = host.scriptNames.slice(0);
|
|
||||||
const newProgram = ts.createProgram(scripts, settings, host);
|
|
||||||
if (postCompile) postCompile(newProgram);
|
|
||||||
return generatedFiles;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const QUICKSTART = ['/quickstart/app/app.module.ts'];
|
|
||||||
const FILES: MockData = {
|
|
||||||
quickstart: {
|
quickstart: {
|
||||||
app: {
|
app: {
|
||||||
'app.component.ts': `
|
'app.component.ts': `
|
||||||
|
@ -891,7 +670,7 @@ const FILES: MockData = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIBRARY: MockData = {
|
const LIBRARY: MockDirectory = {
|
||||||
bolder: {
|
bolder: {
|
||||||
'public-api.ts': `
|
'public-api.ts': `
|
||||||
export * from './src/bolder.component';
|
export * from './src/bolder.component';
|
||||||
|
@ -927,7 +706,7 @@ const LIBRARY: MockData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIBRARY_USING_APP_MODULE = ['/lib-user/app/app.module.ts'];
|
const LIBRARY_USING_APP_MODULE = ['/lib-user/app/app.module.ts'];
|
||||||
const LIBRARY_USING_APP: MockData = {
|
const LIBRARY_USING_APP: MockDirectory = {
|
||||||
'lib-user': {
|
'lib-user': {
|
||||||
app: {
|
app: {
|
||||||
'app.component.ts': `
|
'app.component.ts': `
|
||||||
|
|
|
@ -6,19 +6,26 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompilerHost} from '@angular/compiler';
|
import {AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||||
|
import {ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||||
import {MetadataBundlerHost, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
import {MetadataBundlerHost, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
export type MockData = string | MockDirectory;
|
let nodeModulesPath: string;
|
||||||
|
let angularSourcePath: string;
|
||||||
|
let rootPath: string;
|
||||||
|
|
||||||
|
calcPathsOnDisc();
|
||||||
|
|
||||||
|
export type MockFileOrDirectory = string | MockDirectory;
|
||||||
|
|
||||||
export type MockDirectory = {
|
export type MockDirectory = {
|
||||||
[name: string]: MockData | undefined;
|
[name: string]: MockFileOrDirectory | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isDirectory(data: MockData | undefined): data is MockDirectory {
|
export function isDirectory(data: MockFileOrDirectory | undefined): data is MockDirectory {
|
||||||
return typeof data !== 'string';
|
return typeof data !== 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,12 +50,21 @@ export const settings: ts.CompilerOptions = {
|
||||||
|
|
||||||
export interface EmitterOptions {
|
export interface EmitterOptions {
|
||||||
emitMetadata: boolean;
|
emitMetadata: boolean;
|
||||||
mockData?: MockData;
|
mockData?: MockDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calcPathsOnDisc() {
|
||||||
|
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||||
|
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||||
|
if (distIndex >= 0) {
|
||||||
|
rootPath = moduleFilename.substr(0, distIndex);
|
||||||
|
nodeModulesPath = path.join(rootPath, 'node_modules');
|
||||||
|
angularSourcePath = path.join(rootPath, 'packages');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class EmittingCompilerHost implements ts.CompilerHost {
|
export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
private angularSourcePath: string|undefined;
|
|
||||||
private nodeModulesPath: string|undefined;
|
|
||||||
private addedFiles = new Map<string, string>();
|
private addedFiles = new Map<string, string>();
|
||||||
private writtenFiles = new Map<string, string>();
|
private writtenFiles = new Map<string, string>();
|
||||||
private scriptNames: string[];
|
private scriptNames: string[];
|
||||||
|
@ -56,19 +72,18 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
private collector = new MetadataCollector();
|
private collector = new MetadataCollector();
|
||||||
|
|
||||||
constructor(scriptNames: string[], private options: EmitterOptions) {
|
constructor(scriptNames: string[], private options: EmitterOptions) {
|
||||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
// the source tree.
|
||||||
if (distIndex >= 0) {
|
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
||||||
const root = moduleFilename.substr(0, distIndex);
|
this.root = rootPath;
|
||||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
}
|
||||||
this.angularSourcePath = path.join(root, 'packages');
|
|
||||||
|
|
||||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
public writtenAngularFiles(target = new Map<string, string>()): Map<string, string> {
|
||||||
// the source tree.
|
this.written.forEach((value, key) => {
|
||||||
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
const path = `/node_modules/@angular${key.substring(angularSourcePath.length)}`;
|
||||||
|
target.set(path, value);
|
||||||
this.root = root;
|
});
|
||||||
}
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addScript(fileName: string, content: string) {
|
public addScript(fileName: string, content: string) {
|
||||||
|
@ -97,7 +112,7 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
public effectiveName(fileName: string): string {
|
public effectiveName(fileName: string): string {
|
||||||
const prefix = '@angular/';
|
const prefix = '@angular/';
|
||||||
return fileName.startsWith('@angular/') ?
|
return fileName.startsWith('@angular/') ?
|
||||||
path.join(this.angularSourcePath, fileName.substr(prefix.length)) :
|
path.join(angularSourcePath, fileName.substr(prefix.length)) :
|
||||||
fileName;
|
fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,31 +186,17 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
getNewLine(): string { return '\n'; }
|
getNewLine(): string { return '\n'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
const MOCK_NODEMODULES_PREFIX = '/node_modules/';
|
|
||||||
|
|
||||||
export class MockCompilerHost implements ts.CompilerHost {
|
export class MockCompilerHost implements ts.CompilerHost {
|
||||||
scriptNames: string[];
|
scriptNames: string[];
|
||||||
|
|
||||||
private angularSourcePath: string|undefined;
|
|
||||||
private nodeModulesPath: string|undefined;
|
|
||||||
public overrides = new Map<string, string>();
|
public overrides = new Map<string, string>();
|
||||||
public writtenFiles = new Map<string, string>();
|
public writtenFiles = new Map<string, string>();
|
||||||
private sourceFiles = new Map<string, ts.SourceFile>();
|
private sourceFiles = new Map<string, ts.SourceFile>();
|
||||||
private assumeExists = new Set<string>();
|
private assumeExists = new Set<string>();
|
||||||
private traces: string[] = [];
|
private traces: string[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(scriptNames: string[], private data: MockDirectory) {
|
||||||
scriptNames: string[], private data: MockData, private angular: Map<string, string>,
|
|
||||||
private libraries?: Map<string, string>[]) {
|
|
||||||
this.scriptNames = scriptNames.slice(0);
|
this.scriptNames = scriptNames.slice(0);
|
||||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
|
||||||
let angularIndex = moduleFilename.indexOf('@angular');
|
|
||||||
let distIndex = moduleFilename.indexOf('/dist/all');
|
|
||||||
if (distIndex >= 0) {
|
|
||||||
const root = moduleFilename.substr(0, distIndex);
|
|
||||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
|
||||||
this.angularSourcePath = path.join(root, 'packages');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test API
|
// Test API
|
||||||
|
@ -234,22 +235,13 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||||
const effectiveName = this.getEffectiveName(fileName);
|
const effectiveName = this.getEffectiveName(fileName);
|
||||||
if (effectiveName == fileName) {
|
if (effectiveName == fileName) {
|
||||||
let result = open(fileName, this.data) != null;
|
let result = open(fileName, this.data) != null;
|
||||||
if (!result && fileName.startsWith(MOCK_NODEMODULES_PREFIX)) {
|
|
||||||
const libraryPath = fileName.substr(MOCK_NODEMODULES_PREFIX.length - 1);
|
|
||||||
for (const library of this.libraries !) {
|
|
||||||
if (library.has(libraryPath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
if (fileName.match(rxjs)) {
|
if (fileName.match(rxjs)) {
|
||||||
let result = fs.existsSync(effectiveName);
|
let result = fs.existsSync(effectiveName);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const result = this.angular.has(effectiveName);
|
return false;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,12 +307,6 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||||
let effectiveName = this.getEffectiveName(fileName);
|
let effectiveName = this.getEffectiveName(fileName);
|
||||||
if (effectiveName === fileName) {
|
if (effectiveName === fileName) {
|
||||||
const result = open(fileName, this.data);
|
const result = open(fileName, this.data);
|
||||||
if (!result && fileName.startsWith(MOCK_NODEMODULES_PREFIX)) {
|
|
||||||
const libraryPath = fileName.substr(MOCK_NODEMODULES_PREFIX.length - 1);
|
|
||||||
for (const library of this.libraries !) {
|
|
||||||
if (library.has(libraryPath)) return library.get(libraryPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
if (fileName.match(rxjs)) {
|
if (fileName.match(rxjs)) {
|
||||||
|
@ -328,22 +314,16 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||||
return fs.readFileSync(fileName, 'utf8');
|
return fs.readFileSync(fileName, 'utf8');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.angular.get(effectiveName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEffectiveName(name: string): string {
|
private getEffectiveName(name: string): string {
|
||||||
const node_modules = 'node_modules';
|
const node_modules = 'node_modules';
|
||||||
const at_angular = '/@angular';
|
|
||||||
const rxjs = '/rxjs';
|
const rxjs = '/rxjs';
|
||||||
if (name.startsWith('/' + node_modules)) {
|
if (name.startsWith('/' + node_modules)) {
|
||||||
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
|
if (nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
||||||
return path.join(
|
return path.join(nodeModulesPath, name.substr(node_modules.length + 1));
|
||||||
this.angularSourcePath, name.substr(node_modules.length + at_angular.length + 1));
|
|
||||||
}
|
|
||||||
if (this.nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
|
||||||
return path.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
|
@ -439,11 +419,12 @@ export class MockMetadataBundlerHost implements MetadataBundlerHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function find(fileName: string, data: MockData | undefined): MockData|undefined {
|
function find(fileName: string, data: MockFileOrDirectory | undefined): MockFileOrDirectory|
|
||||||
|
undefined {
|
||||||
if (!data) return undefined;
|
if (!data) return undefined;
|
||||||
let names = fileName.split('/');
|
let names = fileName.split('/');
|
||||||
if (names.length && !names[0].length) names.shift();
|
if (names.length && !names[0].length) names.shift();
|
||||||
let current: MockData|undefined = data;
|
let current: MockFileOrDirectory|undefined = data;
|
||||||
for (let name of names) {
|
for (let name of names) {
|
||||||
if (typeof current === 'string')
|
if (typeof current === 'string')
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -454,7 +435,7 @@ function find(fileName: string, data: MockData | undefined): MockData|undefined
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(fileName: string, data: MockData | undefined): string|undefined {
|
function open(fileName: string, data: MockFileOrDirectory | undefined): string|undefined {
|
||||||
let result = find(fileName, data);
|
let result = find(fileName, data);
|
||||||
if (typeof result === 'string') {
|
if (typeof result === 'string') {
|
||||||
return result;
|
return result;
|
||||||
|
@ -462,7 +443,204 @@ function open(fileName: string, data: MockData | undefined): string|undefined {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function directoryExists(dirname: string, data: MockData | undefined): boolean {
|
function directoryExists(dirname: string, data: MockFileOrDirectory | undefined): boolean {
|
||||||
let result = find(dirname, data);
|
let result = find(dirname, data);
|
||||||
return !!result && typeof result !== 'string';
|
return !!result && typeof result !== 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MockFileArray = {
|
||||||
|
fileName: string,
|
||||||
|
content: string
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export type MockData = MockDirectory | Map<string, string>| (MockDirectory | Map<string, string>)[];
|
||||||
|
|
||||||
|
export function toMockFileArray(data: MockData, target: MockFileArray = []): MockFileArray {
|
||||||
|
if (data instanceof Map) {
|
||||||
|
mapToMockFileArray(data, target);
|
||||||
|
} else if (Array.isArray(data)) {
|
||||||
|
data.forEach(entry => toMockFileArray(entry, target));
|
||||||
|
} else {
|
||||||
|
mockDirToFileArray(data, '', target);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockDirToFileArray(dir: MockDirectory, path: string, target: MockFileArray) {
|
||||||
|
Object.keys(dir).forEach((localFileName) => {
|
||||||
|
const value = dir[localFileName] !;
|
||||||
|
const fileName = `${path}/${localFileName}`;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
target.push({fileName, content: value});
|
||||||
|
} else {
|
||||||
|
mockDirToFileArray(value, fileName, target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapToMockFileArray(files: Map<string, string>, target: MockFileArray) {
|
||||||
|
files.forEach((content, fileName) => { target.push({fileName, content}); });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function arrayToMockMap(arr: MockFileArray): Map<string, string> {
|
||||||
|
const map = new Map<string, string>();
|
||||||
|
arr.forEach(({fileName, content}) => { map.set(fileName, content); });
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function arrayToMockDir(arr: MockFileArray): MockDirectory {
|
||||||
|
const rootDir: MockDirectory = {};
|
||||||
|
arr.forEach(({fileName, content}) => {
|
||||||
|
let pathParts = fileName.split('/');
|
||||||
|
// trim trailing slash
|
||||||
|
let startIndex = pathParts[0] ? 0 : 1;
|
||||||
|
// get/create the directory
|
||||||
|
let currentDir = rootDir;
|
||||||
|
for (let i = startIndex; i < pathParts.length - 1; i++) {
|
||||||
|
const pathPart = pathParts[i];
|
||||||
|
let localDir = <MockDirectory>currentDir[pathPart];
|
||||||
|
if (!localDir) {
|
||||||
|
currentDir[pathPart] = localDir = {};
|
||||||
|
}
|
||||||
|
currentDir = localDir;
|
||||||
|
}
|
||||||
|
// write the file
|
||||||
|
currentDir[pathParts[pathParts.length - 1]] = content;
|
||||||
|
});
|
||||||
|
return rootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minCoreIndex = `
|
||||||
|
export * from './src/application_module';
|
||||||
|
export * from './src/change_detection';
|
||||||
|
export * from './src/metadata';
|
||||||
|
export * from './src/di/metadata';
|
||||||
|
export * from './src/di/injector';
|
||||||
|
export * from './src/di/injection_token';
|
||||||
|
export * from './src/linker';
|
||||||
|
export * from './src/render';
|
||||||
|
export * from './src/codegen_private_exports';
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function setup(options: {compileAngular: boolean} = {
|
||||||
|
compileAngular: true
|
||||||
|
}) {
|
||||||
|
let angularFiles = new Map<string, string>();
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
if (options.compileAngular) {
|
||||||
|
const emittingHost = new EmittingCompilerHost([], {emitMetadata: true});
|
||||||
|
emittingHost.addScript('@angular/core/index.ts', minCoreIndex);
|
||||||
|
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||||
|
emittingProgram.emit();
|
||||||
|
emittingHost.writtenAngularFiles(angularFiles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||||
|
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||||
|
|
||||||
|
return angularFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expectNoDiagnostics(program: ts.Program) {
|
||||||
|
function fileInfo(diagnostic: ts.Diagnostic): string {
|
||||||
|
if (diagnostic.file) {
|
||||||
|
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
|
||||||
|
|
||||||
|
function lineNoOf(offset: number, text: string): number {
|
||||||
|
let result = 1;
|
||||||
|
for (let i = 0; i < offset; i++) {
|
||||||
|
if (text[i] == '\n') result++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lineInfo(diagnostic: ts.Diagnostic): string {
|
||||||
|
if (diagnostic.file) {
|
||||||
|
const start = diagnostic.start;
|
||||||
|
let end = diagnostic.start + diagnostic.length;
|
||||||
|
const source = diagnostic.file.text;
|
||||||
|
let lineStart = start;
|
||||||
|
let lineEnd = end;
|
||||||
|
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
|
||||||
|
if (lineStart < start) lineStart++;
|
||||||
|
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
|
||||||
|
let line = source.substring(lineStart, lineEnd);
|
||||||
|
const lineIndex = line.indexOf('/n');
|
||||||
|
if (lineIndex > 0) {
|
||||||
|
line = line.substr(0, lineIndex);
|
||||||
|
end = start + lineIndex;
|
||||||
|
}
|
||||||
|
const lineNo = lineNoOf(start, source) + ': ';
|
||||||
|
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
|
||||||
|
chars(end - start, '^');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||||
|
if (diagnostics && diagnostics.length) {
|
||||||
|
throw new Error(
|
||||||
|
'Errors from TypeScript:\n' +
|
||||||
|
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectNoDiagnostics(program.getOptionsDiagnostics());
|
||||||
|
expectNoDiagnostics(program.getSyntacticDiagnostics());
|
||||||
|
expectNoDiagnostics(program.getSemanticDiagnostics());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSource(fileName: string): boolean {
|
||||||
|
return !/\.d\.ts$/.test(fileName) && /\.ts$/.test(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compile(rootDirs: MockData, options: {
|
||||||
|
emit?: boolean,
|
||||||
|
useSummaries?: boolean,
|
||||||
|
preCompile?: (program: ts.Program) => void,
|
||||||
|
postCompile?: (program: ts.Program) => void,
|
||||||
|
}& AotCompilerOptions = {}): Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> {
|
||||||
|
// Make sure we always return errors via the promise...
|
||||||
|
return Promise.resolve(null).then(() => {
|
||||||
|
// when using summaries, always emit so the next step can use the results.
|
||||||
|
const emit = options.emit || options.useSummaries;
|
||||||
|
const preCompile = options.preCompile || expectNoDiagnostics;
|
||||||
|
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 aotHost = new MockAotCompilerHost(host);
|
||||||
|
if (options.useSummaries) {
|
||||||
|
aotHost.hideMetadata();
|
||||||
|
aotHost.tsFilesOnly();
|
||||||
|
}
|
||||||
|
const scripts = host.scriptNames.slice(0);
|
||||||
|
const program = ts.createProgram(scripts, settings, 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 => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
|
||||||
|
host.override(file.genFileUrl, file.source));
|
||||||
|
const scripts = host.scriptNames.slice(0);
|
||||||
|
const newProgram = ts.createProgram(scripts, settings, 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};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue