Alex Rickabaugh eb999300d9 test(ivy): run compiler compliance tests without rebuilding core,common (#25248)
Previously the compiler compliance tests ran and built test code with
real dependencies on @angular/core and @angular/common. This meant that
any changes to the compiler would result in long rebuild processes
for tests to rerun.

This change removes those dependencies and causes test code to be built
against the fake_core stub of @angular/core that the ngtsc tests use.
This change also removes the dependency on @angular/common entirely, as
locality means it's possible to reference *ngIf without needing to link
to an implementation.

PR Close #25248
2018-08-03 13:08:51 -07:00

848 lines
28 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler, toTypeScript} from '@angular/compiler';
import {MetadataBundlerHost} from '@angular/compiler-cli/src/metadata/bundler';
import {MetadataCollector} from '@angular/compiler-cli/src/metadata/collector';
import {ModuleMetadata} from '@angular/compiler-cli/src/metadata/index';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
export interface MetadataProvider { getMetadata(source: ts.SourceFile): ModuleMetadata|undefined; }
let nodeModulesPath: string;
let angularSourcePath: string;
let rootPath: string;
calcPathsOnDisc();
export type MockFileOrDirectory = string | MockDirectory;
export type MockDirectory = {
[name: string]: MockFileOrDirectory | undefined;
};
export function isDirectory(data: MockFileOrDirectory | undefined): data is MockDirectory {
return typeof data !== 'string';
}
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
const rxjs = /\/rxjs\//;
const tsxfile = /\.tsx$/;
export const settings: ts.CompilerOptions = {
target: ts.ScriptTarget.ES5,
declaration: true,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
removeComments: false,
noImplicitAny: false,
skipLibCheck: true,
strictNullChecks: true,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
types: []
};
export interface EmitterOptions {
emitMetadata: boolean;
mockData?: MockDirectory;
context?: Map<string, string>;
}
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 addedFiles = new Map<string, string>();
private writtenFiles = new Map<string, string>();
private scriptNames: string[];
private root = '/';
private collector = new MetadataCollector();
private cachedAddedDirectories: Set<string>|undefined;
constructor(scriptNames: string[], private options: EmitterOptions) {
// 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 || this.root;
if (options.context) {
this.addedFiles = mergeMaps(options.context);
}
}
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) {
const scriptName = this.effectiveName(fileName);
this.addedFiles.set(scriptName, content);
this.cachedAddedDirectories = undefined;
this.scriptNames.push(scriptName);
}
public override(fileName: string, content: string) {
const scriptName = this.effectiveName(fileName);
this.addedFiles.set(scriptName, content);
this.cachedAddedDirectories = undefined;
}
public addFiles(map: Map<string, string>) {
for (const [name, content] of Array.from(map.entries())) {
this.addedFiles.set(name, content);
}
}
public addWrittenFile(fileName: string, content: string) {
this.writtenFiles.set(this.effectiveName(fileName), content);
}
public getWrittenFiles(): {name: string, content: string}[] {
return Array.from(this.writtenFiles).map(f => ({name: f[0], content: f[1]}));
}
public get scripts(): string[] { return this.scriptNames; }
public get written(): Map<string, string> { return this.writtenFiles; }
public effectiveName(fileName: string): string {
const prefix = '@angular/';
return angularSourcePath && fileName.startsWith(prefix) ?
path.join(angularSourcePath, fileName.substr(prefix.length)) :
fileName;
}
// ts.ModuleResolutionHost
fileExists(fileName: string): boolean {
return this.addedFiles.has(fileName) || open(fileName, this.options.mockData) != null ||
fs.existsSync(fileName);
}
readFile(fileName: string): string {
const result = this.addedFiles.get(fileName) || open(fileName, this.options.mockData);
if (result) return result;
let basename = path.basename(fileName);
if (/^lib.*\.d\.ts$/.test(basename)) {
let libPath = ts.getDefaultLibFilePath(settings);
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
}
return fs.readFileSync(fileName, 'utf8');
}
directoryExists(directoryName: string): boolean {
return directoryExists(directoryName, this.options.mockData) ||
this.getAddedDirectories().has(directoryName) ||
(fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory());
}
getCurrentDirectory(): string { return this.root; }
getDirectories(dir: string): string[] {
const result = open(dir, this.options.mockData);
if (result && typeof result !== 'string') {
return Object.keys(result);
}
return fs.readdirSync(dir).filter(p => {
const name = path.join(dir, p);
const stat = fs.statSync(name);
return stat && stat.isDirectory();
});
}
// ts.CompilerHost
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void): ts.SourceFile {
const content = this.readFile(fileName);
if (content) {
return ts.createSourceFile(fileName, content, languageVersion, /* setParentNodes */ true);
}
throw new Error(`File not found '${fileName}'.`);
}
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ReadonlyArray<ts.SourceFile>) => {
this.addWrittenFile(fileName, data);
if (this.options.emitMetadata && sourceFiles && sourceFiles.length && DTS.test(fileName)) {
const metadataFilePath = fileName.replace(DTS, '.metadata.json');
const metadata = this.collector.getMetadata(sourceFiles[0]);
if (metadata) {
this.addWrittenFile(metadataFilePath, JSON.stringify(metadata));
}
}
}
getCanonicalFileName(fileName: string): string {
return fileName;
}
useCaseSensitiveFileNames(): boolean { return false; }
getNewLine(): string { return '\n'; }
private getAddedDirectories(): Set<string> {
let result = this.cachedAddedDirectories;
if (!result) {
const newCache = new Set<string>();
const addFile = (fileName: string) => {
const directory = fileName.substr(0, fileName.lastIndexOf('/'));
if (!newCache.has(directory)) {
newCache.add(directory);
addFile(directory);
}
};
Array.from(this.addedFiles.keys()).forEach(addFile);
this.cachedAddedDirectories = result = newCache;
}
return result;
}
}
export class MockCompilerHost implements ts.CompilerHost {
scriptNames: string[];
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: MockDirectory) {
this.scriptNames = scriptNames.slice(0);
}
// Test API
override(fileName: string, content: string) {
if (content) {
this.overrides.set(fileName, content);
} else {
this.overrides.delete(fileName);
}
this.sourceFiles.delete(fileName);
}
addScript(fileName: string, content: string) {
this.overrides.set(fileName, content);
this.scriptNames.push(fileName);
this.sourceFiles.delete(fileName);
}
assumeFileExists(fileName: string) { this.assumeExists.add(fileName); }
remove(files: string[]) {
// Remove the files from the list of scripts.
const fileSet = new Set(files);
this.scriptNames = this.scriptNames.filter(f => fileSet.has(f));
// Remove files from written files
files.forEach(f => this.writtenFiles.delete(f));
}
// ts.ModuleResolutionHost
fileExists(fileName: string): boolean {
if (this.overrides.has(fileName) || this.writtenFiles.has(fileName) ||
this.assumeExists.has(fileName)) {
return true;
}
const effectiveName = this.getEffectiveName(fileName);
if (effectiveName == fileName) {
return open(fileName, this.data) != null;
}
if (fileName.match(rxjs)) {
return fs.existsSync(effectiveName);
}
return false;
}
readFile(fileName: string): string { return this.getFileContent(fileName) !; }
trace(s: string): void { this.traces.push(s); }
getCurrentDirectory(): string { return '/'; }
getDirectories(dir: string): string[] {
const effectiveName = this.getEffectiveName(dir);
if (effectiveName === dir) {
const data = find(dir, this.data);
if (isDirectory(data)) {
return Object.keys(data).filter(k => isDirectory(data[k]));
}
}
return [];
}
// ts.CompilerHost
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void): ts.SourceFile {
let result = this.sourceFiles.get(fileName);
if (!result) {
const content = this.getFileContent(fileName);
if (content) {
result = ts.createSourceFile(fileName, content, languageVersion);
this.sourceFiles.set(fileName, result);
}
}
return result !;
}
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean) => {
this.writtenFiles.set(fileName, data);
this.sourceFiles.delete(fileName);
}
getCanonicalFileName(fileName: string): string {
return fileName;
}
useCaseSensitiveFileNames(): boolean { return false; }
getNewLine(): string { return '\n'; }
// Private methods
private getFileContent(fileName: string): string|undefined {
if (this.overrides.has(fileName)) {
return this.overrides.get(fileName);
}
if (this.writtenFiles.has(fileName)) {
return this.writtenFiles.get(fileName);
}
let basename = path.basename(fileName);
if (/^lib.*\.d\.ts$/.test(basename)) {
let libPath = ts.getDefaultLibFilePath(settings);
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
}
let effectiveName = this.getEffectiveName(fileName);
if (effectiveName === fileName) {
return open(fileName, this.data);
}
if (fileName.match(rxjs) && fs.existsSync(fileName)) {
return fs.readFileSync(fileName, 'utf8');
}
}
private getEffectiveName(name: string): string {
const node_modules = 'node_modules';
const rxjs = '/rxjs';
if (name.startsWith('/' + node_modules)) {
if (nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
return path.join(nodeModulesPath, name.substr(node_modules.length + 1));
}
}
return name;
}
}
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
export class MockAotCompilerHost implements AotCompilerHost {
private metadataVisible: boolean = true;
private dtsAreSource: boolean = true;
private resolveModuleNameHost: ts.ModuleResolutionHost;
constructor(
private tsHost: MockCompilerHost,
private metadataProvider: MetadataProvider = new MetadataCollector()) {
this.resolveModuleNameHost = Object.create(tsHost);
this.resolveModuleNameHost.fileExists = (fileName) => {
fileName = stripNgResourceSuffix(fileName);
return tsHost.fileExists(fileName);
};
}
hideMetadata() { this.metadataVisible = false; }
tsFilesOnly() { this.dtsAreSource = false; }
// StaticSymbolResolverHost
getMetadataFor(modulePath: string): {[key: string]: any}[]|undefined {
if (!this.tsHost.fileExists(modulePath)) {
return undefined;
}
if (DTS.test(modulePath)) {
if (this.metadataVisible) {
const metadataPath = modulePath.replace(DTS, '.metadata.json');
if (this.tsHost.fileExists(metadataPath)) {
let result = JSON.parse(this.tsHost.readFile(metadataPath));
return Array.isArray(result) ? result : [result];
}
}
} else {
const sf = this.tsHost.getSourceFile(modulePath, ts.ScriptTarget.Latest);
const metadata = this.metadataProvider.getMetadata(sf);
return metadata ? [metadata] : [];
}
return undefined;
}
moduleNameToFileName(moduleName: string, containingFile: string): string|null {
if (!containingFile || !containingFile.length) {
if (moduleName.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = path.join('/', 'index.ts');
}
moduleName = moduleName.replace(EXT, '');
const resolved = ts.resolveModuleName(
moduleName, containingFile.replace(/\\/g, '/'),
{baseDir: '/', genDir: '/'}, this.resolveModuleNameHost)
.resolvedModule;
return resolved ? resolved.resolvedFileName : null;
}
getOutputName(filePath: string) { return filePath; }
resourceNameToFileName(resourceName: string, containingFile: string) {
// Note: we convert package paths into relative paths to be compatible with the the
// previous implementation of UrlResolver.
if (resourceName && resourceName.charAt(0) !== '.' && !path.isAbsolute(resourceName)) {
resourceName = `./${resourceName}`;
}
const filePathWithNgResource =
this.moduleNameToFileName(addNgResourceSuffix(resourceName), containingFile);
return filePathWithNgResource ? stripNgResourceSuffix(filePathWithNgResource) : null;
}
// AotSummaryResolverHost
loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); }
isSourceFile(sourceFilePath: string): boolean {
return !GENERATED_FILES.test(sourceFilePath) &&
(this.dtsAreSource || !DTS.test(sourceFilePath));
}
toSummaryFileName(filePath: string): string { return filePath.replace(EXT, '') + '.d.ts'; }
fromSummaryFileName(filePath: string): string { return filePath; }
// AotCompilerHost
fileNameToModuleName(importedFile: string, containingFile: string): string {
return importedFile.replace(EXT, '');
}
loadResource(path: string): string {
if (this.tsHost.fileExists(path)) {
return this.tsHost.readFile(path);
} else {
throw new Error(`Resource ${path} not found.`);
}
}
}
export class MockMetadataBundlerHost implements MetadataBundlerHost {
private collector = new MetadataCollector();
constructor(private host: ts.CompilerHost) {}
getMetadataFor(moduleName: string): ModuleMetadata|undefined {
const source = this.host.getSourceFile(moduleName + '.ts', ts.ScriptTarget.Latest);
return source && this.collector.getMetadata(source);
}
}
function find(fileName: string, data: MockFileOrDirectory | undefined): MockFileOrDirectory|
undefined {
if (!data) return undefined;
const names = fileName.split('/');
if (names.length && !names[0].length) names.shift();
let current: MockFileOrDirectory|undefined = data;
for (const name of names) {
if (typeof current !== 'object') {
return undefined;
}
current = current[name];
}
return current;
}
function open(fileName: string, data: MockFileOrDirectory | undefined): string|undefined {
let result = find(fileName, data);
if (typeof result === 'string') {
return result;
}
return undefined;
}
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/injectable';
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';
`;
function readBazelWrittenFilesFrom(
bazelPackageRoot: string, packageName: string, map: Map<string, string>,
skip: (name: string, fullName: string) => boolean = () => false) {
function processDirectory(dir: string, dest: string) {
const entries = fs.readdirSync(dir);
for (const name of entries) {
const fullName = path.join(dir, name);
const destName = path.join(dest, name);
const stat = fs.statSync(fullName);
if (!skip(name, fullName)) {
if (stat.isDirectory()) {
processDirectory(fullName, destName);
} else {
const content = fs.readFileSync(fullName, 'utf8');
map.set(destName, content);
}
}
}
}
try {
processDirectory(bazelPackageRoot, path.join('/node_modules/@angular', packageName));
} catch (e) {
console.error(
`Consider adding //packages/${packageName} as a data dependency in the BUILD.bazel rule for the failing test`);
throw e;
}
}
export function isInBazel(): boolean {
return process.env.TEST_SRCDIR != null;
}
export function setup(options: {
compileAngular: boolean,
compileFakeCore?: boolean,
compileAnimations: boolean, compileCommon?: boolean
} = {
compileAngular: true,
compileAnimations: true,
compileCommon: false,
compileFakeCore: false,
}) {
let angularFiles = new Map<string, string>();
beforeAll(() => {
const sources = process.env.TEST_SRCDIR;
if (sources) {
// If running under bazel then we get the compiled version of the files from the bazel package
// output.
const bundles = new Set([
'bundles', 'esm2015', 'esm5', 'testing', 'testing.d.ts', 'testing.metadata.json', 'browser',
'browser.d.ts'
]);
const skipDirs = (name: string) => bundles.has(name);
if (options.compileAngular) {
// If this fails please add //packages/core:npm_package as a test data dependency.
readBazelWrittenFilesFrom(
path.join(sources, 'angular/packages/core/npm_package'), 'core', angularFiles,
skipDirs);
}
if (options.compileFakeCore) {
readBazelWrittenFilesFrom(
path.join(sources, 'angular/packages/compiler-cli/test/ngtsc/fake_core/npm_package'),
'core', angularFiles, skipDirs);
}
if (options.compileAnimations) {
// If this fails please add //packages/animations:npm_package as a test data dependency.
readBazelWrittenFilesFrom(
path.join(sources, 'angular/packages/animations/npm_package'), 'animations',
angularFiles, skipDirs);
}
if (options.compileCommon) {
// If this fails please add //packages/common:npm_package as a test data dependency.
readBazelWrittenFilesFrom(
path.join(sources, 'angular/packages/common/npm_package'), 'common', angularFiles,
skipDirs);
}
return;
}
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);
}
if (options.compileCommon) {
const emittingHost =
new EmittingCompilerHost(['@angular/common/index.ts'], {emitMetadata: true});
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
emittingProgram.emit();
emittingHost.writtenAngularFiles(angularFiles);
}
if (options.compileAnimations) {
const emittingHost =
new EmittingCompilerHost(['@angular/animations/index.ts'], {emitMetadata: true});
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
emittingProgram.emit();
emittingHost.writtenAngularFiles(angularFiles);
}
});
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: ReadonlyArray<ts.Diagnostic>) {
if (diagnostics && diagnostics.length) {
throw new Error(
'Errors from TypeScript:\n' +
diagnostics
.map(
d =>
`${fileInfo(d)}${ts.flattenDiagnosticMessageText(d.messageText, '\n')}${lineInfo(d)}`)
.join(' \n'));
}
}
expectNoDiagnostics(program.getOptionsDiagnostics());
expectNoDiagnostics(program.getSyntacticDiagnostics());
expectNoDiagnostics(program.getSemanticDiagnostics());
}
export function isSource(fileName: string): boolean {
return !isDts(fileName) && /\.ts$/.test(fileName);
}
function isDts(fileName: string): boolean {
return /\.d.ts$/.test(fileName);
}
function isSourceOrDts(fileName: string): boolean {
return /\.ts$/.test(fileName);
}
export function compile(
rootDirs: MockData, options: {
emit?: boolean,
useSummaries?: boolean,
preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void,
}& AotCompilerOptions = {},
tsOptions: ts.CompilerOptions = {}): {genFiles: GeneratedFile[], outDir: MockDirectory} {
// when using summaries, always emit so the next step can use the results.
const emit = options.emit || options.useSummaries;
const preCompile = options.preCompile || (() => {});
const postCompile = options.postCompile || expectNoDiagnostics;
const rootDirArr = toMockFileArray(rootDirs);
const scriptNames = rootDirArr.map(entry => entry.fileName)
.filter(options.useSummaries ? isSource : isSourceOrDts);
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
const aotHost = new MockAotCompilerHost(host);
if (options.useSummaries) {
aotHost.hideMetadata();
aotHost.tsFilesOnly();
}
const tsSettings = {...settings, ...tsOptions};
const program = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options, (err) => { throw err; });
const analyzedModules =
compiler.analyzeModulesSync(program.getSourceFiles().map(sf => sf.fileName));
const genFiles = compiler.emitAllImpls(analyzedModules);
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 newProgram = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
postCompile(newProgram);
if (emit) {
newProgram.emit();
}
let outDir: MockDirectory = {};
if (emit) {
const dtsFilesWithGenFiles = new Set<string>(genFiles.map(gf => gf.srcFileUrl).filter(isDts));
outDir =
arrayToMockDir(toMockFileArray([host.writtenFiles, host.overrides])
.filter((entry) => !isSource(entry.fileName))
.concat(rootDirArr.filter(e => dtsFilesWithGenFiles.has(e.fileName))));
}
return {genFiles, outDir};
}
function stripNgResourceSuffix(fileName: string): string {
return fileName.replace(/\.\$ngresource\$.*/, '');
}
function addNgResourceSuffix(fileName: string): string {
return `${fileName}.$ngresource$`;
}
function extractFileNames(directory: MockDirectory): string[] {
const result: string[] = [];
const scan = (directory: MockDirectory, prefix: string) => {
for (let name of Object.getOwnPropertyNames(directory)) {
const entry = directory[name];
const fileName = `${prefix}/${name}`;
if (typeof entry === 'string') {
result.push(fileName);
} else if (entry) {
scan(entry, fileName);
}
}
};
scan(directory, '');
return result;
}
export function emitLibrary(
context: Map<string, string>, mockData: MockDirectory,
scriptFiles?: string[]): Map<string, string> {
const emittingHost = new EmittingCompilerHost(
scriptFiles || extractFileNames(mockData), {emitMetadata: true, mockData, context});
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
expectNoDiagnostics(emittingProgram);
emittingProgram.emit();
return emittingHost.written;
}
export function mergeMaps<K, V>(...maps: Map<K, V>[]): Map<K, V> {
const result = new Map<K, V>();
for (const map of maps) {
for (const [key, value] of Array.from(map.entries())) {
result.set(key, value);
}
}
return result;
}