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
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||
import {GeneratedFile} from '@angular/compiler';
|
||||
import {NodeFlags} from '@angular/core/src/view/index';
|
||||
import {async} from '@angular/core/testing';
|
||||
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 {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, settings} 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';
|
||||
`;
|
||||
import {EmittingCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, compile, settings, setup, toMockFileArray} from './test_util';
|
||||
|
||||
describe('compiler (unbundled Angular)', () => {
|
||||
let angularFiles: Map<string, string>;
|
||||
|
||||
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()); });
|
||||
let angularFiles = setup();
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
|
||||
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)))
|
||||
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
|
||||
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.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> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let result: GeneratedFile[];
|
||||
let error: Error;
|
||||
resolve(compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)
|
||||
.then(
|
||||
(files) => files.find(
|
||||
genFile => genFile.srcFileUrl === componentPath &&
|
||||
genFile.genFileUrl.endsWith('.ts'))));
|
||||
});
|
||||
return compile([rootDir, angularFiles])
|
||||
.then(
|
||||
({genFiles}) => {return genFiles.find(
|
||||
genFile =>
|
||||
genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))});
|
||||
}
|
||||
|
||||
function findLineAndColumn(
|
||||
|
@ -247,7 +196,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
describe('errors', () => {
|
||||
it('should only warn if not all arguments of an @Injectable class can be resolved',
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
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');
|
||||
compile(host, aotHost, expectNoDiagnostics).then(() => {
|
||||
compile([FILES, angularFiles]).then(() => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`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(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
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! */';
|
||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
||||
.then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
compile([FILES, angularFiles], {genFilePreamble}).then(({genFiles}) => {
|
||||
const genFile =
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
describe('ComponentFactories', () => {
|
||||
it('should include inputs, outputs and ng-content selectors in the component factory',
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
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);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let generatedFiles: GeneratedFile[];
|
||||
compile(host, aotHost, expectNoDiagnostics).then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
||||
compile([FILES, angularFiles]).then(({genFiles}) => {
|
||||
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
||||
const createComponentFactoryCall =
|
||||
/ɵccf\([^)]*\)/m.exec(genFile.source) ![0].replace(/\s*/g, '');
|
||||
// selector
|
||||
|
@ -345,7 +286,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
describe('generated templates', () => {
|
||||
it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit',
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
import { NgModule, Component } from '@angular/core';
|
||||
|
@ -358,37 +299,16 @@ describe('compiler (unbundled Angular)', () => {
|
|||
`
|
||||
}
|
||||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
const genFilePreamble = '/* Hello world! */';
|
||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
||||
.then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source).not.toContain('check(');
|
||||
});
|
||||
compile([FILES, angularFiles]).then(({genFiles}) => {
|
||||
const genFile = genFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source).not.toContain('check(');
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
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(
|
||||
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
|
||||
parentClassDecorator: string,
|
||||
|
@ -396,7 +316,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
childClassDecorator: string,
|
||||
childModuleDecorator: string
|
||||
}) {
|
||||
const libInput: MockData = {
|
||||
const libInput: MockDirectory = {
|
||||
'lib': {
|
||||
'base.ts': `
|
||||
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||
|
@ -409,7 +329,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
`
|
||||
}
|
||||
};
|
||||
const appInput: MockData = {
|
||||
const appInput: MockDirectory = {
|
||||
'app': {
|
||||
'main.ts': `
|
||||
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||
|
@ -424,13 +344,14 @@ describe('compiler (unbundled Angular)', () => {
|
|||
}
|
||||
};
|
||||
|
||||
return compileWithSummaries(libInput, appInput)
|
||||
.then((generatedFiles) => generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
|
||||
return compile([libInput, angularFiles], {useSummaries: true})
|
||||
.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',
|
||||
async(() => {
|
||||
const libInput: MockData = {
|
||||
const libInput: MockDirectory = {
|
||||
'lib': {
|
||||
'base.ts': `
|
||||
export class AParam {}
|
||||
|
@ -442,7 +363,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
`
|
||||
}
|
||||
};
|
||||
const appInput: MockData = {
|
||||
const appInput: MockDirectory = {
|
||||
'app': {
|
||||
'main.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
|
@ -459,17 +380,19 @@ describe('compiler (unbundled Angular)', () => {
|
|||
}
|
||||
};
|
||||
|
||||
compileWithSummaries(libInput, appInput).then((generatedFiles) => {
|
||||
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(mainNgFactory.source)
|
||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
|
||||
});
|
||||
compile([libInput, angularFiles], {useSummaries: true})
|
||||
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||
.then(({genFiles}) => {
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
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',
|
||||
async(() => {
|
||||
const lib1Input: MockData = {
|
||||
const lib1Input: MockDirectory = {
|
||||
'lib1': {
|
||||
'base.ts': `
|
||||
export class AParam {}
|
||||
|
@ -482,7 +405,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
}
|
||||
};
|
||||
|
||||
const lib2Input: MockData = {
|
||||
const lib2Input: MockDirectory = {
|
||||
'lib2': {
|
||||
'middle.ts': `
|
||||
import {Base} from '../lib1/base';
|
||||
|
@ -492,7 +415,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
};
|
||||
|
||||
|
||||
const appInput: MockData = {
|
||||
const appInput: MockDirectory = {
|
||||
'app': {
|
||||
'main.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
|
@ -508,29 +431,11 @@ describe('compiler (unbundled Angular)', () => {
|
|||
`
|
||||
}
|
||||
};
|
||||
const lib1Host = new MockCompilerHost(['/lib1/base.ts'], lib1Input, angularFiles);
|
||||
const lib1AotHost = new MockAotCompilerHost(lib1Host);
|
||||
lib1AotHost.tsFilesOnly();
|
||||
const lib2Host = new MockCompilerHost(['/lib2/middle.ts'], lib2Input, angularFiles);
|
||||
const lib2AotHost = new MockAotCompilerHost(lib2Host);
|
||||
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');
|
||||
compile([lib1Input, angularFiles], {useSummaries: true})
|
||||
.then(({outDir}) => compile([outDir, lib2Input, angularFiles], {useSummaries: true}))
|
||||
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||
.then(({genFiles}) => {
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(mainNgFactory.source)
|
||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam_2]`);
|
||||
|
@ -660,6 +565,8 @@ describe('compiler (unbundled Angular)', () => {
|
|||
});
|
||||
|
||||
describe('compiler (bundled Angular)', () => {
|
||||
setup({compileAngular: false});
|
||||
|
||||
let angularFiles: Map<string, string>;
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -681,34 +588,19 @@ describe('compiler (bundled Angular)', () => {
|
|||
const bundleIndexName = emittingHost.effectiveName('@angular/core/bundle_index.ts');
|
||||
const emittingProgram = ts.createProgram([bundleIndexName], settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
angularFiles = emittingHost.written;
|
||||
angularFiles = emittingHost.writtenAngularFiles();
|
||||
});
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
|
||||
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)))
|
||||
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
|
||||
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
})));
|
||||
});
|
||||
|
||||
describe('Bundled library', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
let libraryFiles: Map<string, string>;
|
||||
let libraryFiles: MockDirectory;
|
||||
|
||||
beforeAll(() => {
|
||||
// Emit the library bundle
|
||||
|
@ -728,135 +620,22 @@ describe('compiler (bundled Angular)', () => {
|
|||
// Emit the sources
|
||||
const emittingProgram = ts.createProgram(['/bolder/index.ts'], settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
libraryFiles = emittingHost.written;
|
||||
const libFiles = emittingHost.written;
|
||||
|
||||
// Copy the .html file
|
||||
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(() => {
|
||||
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()); });
|
||||
it('should compile', async(() => compile([LIBRARY_USING_APP, libraryFiles, angularFiles])));
|
||||
});
|
||||
});
|
||||
|
||||
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 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 = {
|
||||
const QUICKSTART: MockDirectory = {
|
||||
quickstart: {
|
||||
app: {
|
||||
'app.component.ts': `
|
||||
|
@ -891,7 +670,7 @@ const FILES: MockData = {
|
|||
}
|
||||
};
|
||||
|
||||
const LIBRARY: MockData = {
|
||||
const LIBRARY: MockDirectory = {
|
||||
bolder: {
|
||||
'public-api.ts': `
|
||||
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: MockData = {
|
||||
const LIBRARY_USING_APP: MockDirectory = {
|
||||
'lib-user': {
|
||||
app: {
|
||||
'app.component.ts': `
|
||||
|
|
|
@ -6,19 +6,26 @@
|
|||
* 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 * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
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 = {
|
||||
[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';
|
||||
}
|
||||
|
||||
|
@ -43,12 +50,21 @@ export const settings: ts.CompilerOptions = {
|
|||
|
||||
export interface EmitterOptions {
|
||||
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 {
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
private addedFiles = new Map<string, string>();
|
||||
private writtenFiles = new Map<string, string>();
|
||||
private scriptNames: string[];
|
||||
|
@ -56,19 +72,18 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
|||
private collector = new MetadataCollector();
|
||||
|
||||
constructor(scriptNames: string[], private options: EmitterOptions) {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const 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');
|
||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||
// the source tree.
|
||||
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
||||
this.root = rootPath;
|
||||
}
|
||||
|
||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||
// the source tree.
|
||||
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
||||
|
||||
this.root = root;
|
||||
}
|
||||
public writtenAngularFiles(target = new Map<string, string>()): Map<string, string> {
|
||||
this.written.forEach((value, key) => {
|
||||
const path = `/node_modules/@angular${key.substring(angularSourcePath.length)}`;
|
||||
target.set(path, value);
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
public addScript(fileName: string, content: string) {
|
||||
|
@ -97,7 +112,7 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
|||
public effectiveName(fileName: string): string {
|
||||
const prefix = '@angular/';
|
||||
return fileName.startsWith('@angular/') ?
|
||||
path.join(this.angularSourcePath, fileName.substr(prefix.length)) :
|
||||
path.join(angularSourcePath, fileName.substr(prefix.length)) :
|
||||
fileName;
|
||||
}
|
||||
|
||||
|
@ -171,31 +186,17 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
|||
getNewLine(): string { return '\n'; }
|
||||
}
|
||||
|
||||
const MOCK_NODEMODULES_PREFIX = '/node_modules/';
|
||||
|
||||
export class MockCompilerHost implements ts.CompilerHost {
|
||||
scriptNames: string[];
|
||||
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
public overrides = new Map<string, string>();
|
||||
public writtenFiles = new Map<string, string>();
|
||||
private sourceFiles = new Map<string, ts.SourceFile>();
|
||||
private assumeExists = new Set<string>();
|
||||
private traces: string[] = [];
|
||||
|
||||
constructor(
|
||||
scriptNames: string[], private data: MockData, private angular: Map<string, string>,
|
||||
private libraries?: Map<string, string>[]) {
|
||||
constructor(scriptNames: string[], private data: MockDirectory) {
|
||||
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
|
||||
|
@ -234,22 +235,13 @@ export class MockCompilerHost implements ts.CompilerHost {
|
|||
const effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName == fileName) {
|
||||
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;
|
||||
} else {
|
||||
if (fileName.match(rxjs)) {
|
||||
let result = fs.existsSync(effectiveName);
|
||||
return result;
|
||||
}
|
||||
const result = this.angular.has(effectiveName);
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,12 +307,6 @@ export class MockCompilerHost implements ts.CompilerHost {
|
|||
let effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName === fileName) {
|
||||
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;
|
||||
} else {
|
||||
if (fileName.match(rxjs)) {
|
||||
|
@ -328,22 +314,16 @@ export class MockCompilerHost implements ts.CompilerHost {
|
|||
return fs.readFileSync(fileName, 'utf8');
|
||||
}
|
||||
}
|
||||
return this.angular.get(effectiveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEffectiveName(name: string): string {
|
||||
const node_modules = 'node_modules';
|
||||
const at_angular = '/@angular';
|
||||
const rxjs = '/rxjs';
|
||||
if (name.startsWith('/' + node_modules)) {
|
||||
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
|
||||
return path.join(
|
||||
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));
|
||||
if (nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
||||
return path.join(nodeModulesPath, name.substr(node_modules.length + 1));
|
||||
}
|
||||
}
|
||||
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;
|
||||
let names = fileName.split('/');
|
||||
if (names.length && !names[0].length) names.shift();
|
||||
let current: MockData|undefined = data;
|
||||
let current: MockFileOrDirectory|undefined = data;
|
||||
for (let name of names) {
|
||||
if (typeof current === 'string')
|
||||
return undefined;
|
||||
|
@ -454,7 +435,7 @@ function find(fileName: string, data: MockData | undefined): MockData|undefined
|
|||
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);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
|
@ -462,7 +443,204 @@ function open(fileName: string, data: MockData | undefined): string|undefined {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function directoryExists(dirname: string, data: MockData | undefined): boolean {
|
||||
function directoryExists(dirname: string, data: MockFileOrDirectory | undefined): boolean {
|
||||
let result = find(dirname, data);
|
||||
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