Revert "perf(compiler): skip type check and emit in bazel in some cases. (#19646)"

This reverts commit a22121d65d.
This commit is contained in:
Chuck Jazdzewski 2017-10-12 10:26:53 -07:00
parent 055b802713
commit 94a925a1b0
21 changed files with 297 additions and 596 deletions

View File

@ -7,7 +7,6 @@ ts_library(
name = "ngc_lib", name = "ngc_lib",
srcs = [ srcs = [
"index.ts", "index.ts",
"emit_cache.ts",
"extract_i18n.ts", "extract_i18n.ts",
], ],
deps = [ deps = [

View File

@ -1,117 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ng from '@angular/compiler-cli';
import {CompilerHost, debug, fixUmdModuleDeclarations} from '@bazel/typescript';
import * as tsickle from 'tsickle';
import * as ts from 'typescript';
interface EmitCacheEntry {
emitResult: tsickle.EmitResult;
writtenFiles: Array<{fileName: string, content: string, sourceFiles?: ts.SourceFile[]}>;
generatedFile: ng.GeneratedFile;
}
interface SourceFileWithEmitCache extends ts.SourceFile {
emitCache: Map<string, EmitCacheEntry>;
}
function getCache(sf: ts.SourceFile, genFileName?: string): EmitCacheEntry|undefined {
const emitCache = (sf as SourceFileWithEmitCache).emitCache;
return emitCache ? emitCache.get(genFileName || sf.fileName) : undefined;
}
function setCache(sf: ts.SourceFile, entry: EmitCacheEntry) {
let emitCache = (sf as SourceFileWithEmitCache).emitCache;
if (!emitCache) {
emitCache = new Map();
(sf as SourceFileWithEmitCache).emitCache = emitCache;
}
emitCache.set(entry.generatedFile ? entry.generatedFile.genFileName : sf.fileName, entry);
}
export function getCachedGeneratedFile(sf: ts.SourceFile, genFileName: string): ng.GeneratedFile|
undefined {
const cacheEntry = getCache(sf, genFileName);
return cacheEntry ? cacheEntry.generatedFile : undefined;
}
export function emitWithCache(
program: ng.Program, inputsChanged: boolean, targetFileNames: string[],
compilerOpts: ng.CompilerOptions, host: CompilerHost): tsickle.EmitResult {
const emitCallback: ng.EmitCallback = ({
targetSourceFiles,
writeFile,
cancellationToken,
emitOnlyDtsFiles,
customTransformers = {}
}) => {
if (!targetSourceFiles) {
// Note: we know that we always have targetSourceFiles
// as we called `ng.Program.emit` with `targetFileNames`.
throw new Error('Unexpected state: no targetSourceFiles!');
}
let cacheHits = 0;
const mergedEmitResult = tsickle.mergeEmitResults(targetSourceFiles.map(targetSourceFile => {
const targetGeneratedFile = program.getGeneratedFile(targetSourceFile.fileName);
const cacheSf = targetGeneratedFile ?
program.getTsProgram().getSourceFile(targetGeneratedFile.srcFileName) :
targetSourceFile;
const cacheEntry = getCache(cacheSf, targetGeneratedFile && targetGeneratedFile.genFileName);
if (cacheEntry) {
let useEmitCache = false;
if (targetGeneratedFile && !program.hasChanged(targetSourceFile.fileName)) {
// we emitted a GeneratedFile with the same content as before -> use the cache
useEmitCache = true;
} else if (!inputsChanged && !targetGeneratedFile) {
// this is an input and no inputs have changed -> use the cache
useEmitCache = true;
}
if (useEmitCache) {
cacheHits++;
cacheEntry.writtenFiles.forEach(
({fileName, content, sourceFiles}) => writeFile(
fileName, content, /*writeByteOrderMark*/ false, /*onError*/ undefined,
sourceFiles));
return cacheEntry.emitResult;
}
}
const writtenFiles:
Array<{fileName: string, content: string, sourceFiles?: ts.SourceFile[]}> = [];
const recordingWriteFile =
(fileName: string, content: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
writtenFiles.push({fileName, content, sourceFiles});
writeFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
};
const emitResult = tsickle.emitWithTsickle(
program.getTsProgram(), host, host, compilerOpts, targetSourceFile, recordingWriteFile,
cancellationToken, emitOnlyDtsFiles, {
beforeTs: customTransformers.before,
afterTs: [
...(customTransformers.after || []),
fixUmdModuleDeclarations((sf: ts.SourceFile) => host.amdModuleName(sf)),
],
});
setCache(cacheSf, {
emitResult,
writtenFiles,
generatedFile: targetGeneratedFile,
});
return emitResult;
}));
debug(`Emitted ${targetSourceFiles.length} files with ${cacheHits} cache hits`);
return mergedEmitResult;
};
return program
.emit({
targetFileNames,
emitCallback,
emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Codegen
}) as tsickle.EmitResult;
}

View File

@ -11,6 +11,6 @@
// Entry point // Entry point
if (require.main === module) { if (require.main === module) {
const args = process.argv.slice(2); const args = process.argv.slice(2);
console.error('>>> not yet implemented!'); console.error('>>> now yet implemented!');
process.exitCode = 1; process.exitCode = 1;
} }

View File

@ -8,14 +8,12 @@
// TODO(tbosch): figure out why we need this as it breaks node code within ngc-wrapped // TODO(tbosch): figure out why we need this as it breaks node code within ngc-wrapped
/// <reference types="node" /> /// <reference types="node" />
import * as ng from '@angular/compiler-cli'; import * as ng from '@angular/compiler-cli';
import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript'; import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, fixUmdModuleDeclarations, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as tsickle from 'tsickle'; import * as tsickle from 'tsickle';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {emitWithCache, getCachedGeneratedFile} from './emit_cache';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const NGC_GEN_FILES = /^(.*?)\.(ngfactory|ngsummary|ngstyle|shim\.ngstyle)(.*)$/; const NGC_GEN_FILES = /^(.*?)\.(ngfactory|ngsummary|ngstyle|shim\.ngstyle)(.*)$/;
// FIXME: we should be able to add the assets to the tsconfig so FileLoader // FIXME: we should be able to add the assets to the tsconfig so FileLoader
@ -76,32 +74,23 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
} }
export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, compilerOpts, export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, compilerOpts,
tsHost, bazelOpts, files, inputs, expectedOuts, tsHost, bazelOpts, files, inputs, expectedOuts, gatherDiagnostics}: {
gatherDiagnostics = defaultGatherDiagnostics}: {
allowNonHermeticReads: boolean, allowNonHermeticReads: boolean,
allDepsCompiledWithBazel?: boolean, allDepsCompiledWithBazel?: boolean,
compilerOpts: ng.CompilerOptions, compilerOpts: ng.CompilerOptions,
tsHost: ts.CompilerHost, inputs?: {[path: string]: string}, tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
bazelOpts: BazelOptions, bazelOpts: BazelOptions,
files: string[], files: string[],
expectedOuts: string[], expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics
gatherDiagnostics?: (program: ng.Program, inputsToCheck: ts.SourceFile[],
genFilesToCheck: ng.GeneratedFile[]) => ng.Diagnostics
}): {diagnostics: ng.Diagnostics, program: ng.Program} { }): {diagnostics: ng.Diagnostics, program: ng.Program} {
let fileLoader: FileLoader; let fileLoader: FileLoader;
const oldFiles = new Map<string, ts.SourceFile>();
if (inputs) { if (inputs) {
fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads); fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads);
// Resolve the inputs to absolute paths to match TypeScript internals // Resolve the inputs to absolute paths to match TypeScript internals
const resolvedInputs: {[path: string]: string} = {}; const resolvedInputs: {[path: string]: string} = {};
for (const key of Object.keys(inputs)) { for (const key of Object.keys(inputs)) {
const resolvedKey = path.resolve(key); resolvedInputs[path.resolve(key)] = inputs[key];
resolvedInputs[resolvedKey] = inputs[key];
const cachedSf = fileCache.getCache(resolvedKey);
if (cachedSf) {
oldFiles.set(resolvedKey, cachedSf);
}
} }
fileCache.updateCache(resolvedInputs); fileCache.updateCache(resolvedInputs);
} else { } else {
@ -189,56 +178,40 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
path.resolve(bazelBin, fileName) + '.d.ts'; path.resolve(bazelBin, fileName) + '.d.ts';
} }
const oldProgram = { const emitCallback: ng.TsEmitCallback = ({
getSourceFile: (fileName: string) => { return oldFiles.get(fileName); }, program,
getGeneratedFile: (srcFileName: string, genFileName: string) => { targetSourceFile,
const sf = oldFiles.get(srcFileName); writeFile,
return sf ? getCachedGeneratedFile(sf, genFileName) : undefined; cancellationToken,
}, emitOnlyDtsFiles,
}; customTransformers = {},
const program = }) =>
ng.createProgram({rootNames: files, host: ngHost, options: compilerOpts, oldProgram}); tsickle.emitWithTsickle(
let inputsChanged = files.some(fileName => program.hasChanged(fileName)); program, bazelHost, bazelHost, compilerOpts, targetSourceFile, writeFile,
cancellationToken, emitOnlyDtsFiles, {
beforeTs: customTransformers.before,
afterTs: [
...(customTransformers.after || []),
fixUmdModuleDeclarations((sf: ts.SourceFile) => bazelHost.amdModuleName(sf)),
],
});
let genFilesToCheck: ng.GeneratedFile[]; if (!gatherDiagnostics) {
let inputsToCheck: ts.SourceFile[]; gatherDiagnostics = (program) =>
if (inputsChanged) { gatherDiagnosticsForInputsOnly(compilerOpts, bazelOpts, program);
// if an input file changed, we need to type check all
// of our compilation sources as well as all generated files.
inputsToCheck = bazelOpts.compilationTargetSrc.map(
fileName => program.getTsProgram().getSourceFile(fileName));
genFilesToCheck = program.getGeneratedFiles().filter(gf => gf.genFileName.endsWith('.ts'));
} else {
// if no input file changed, only type check the changed generated files
// as these don't influence each other nor the type check of the input files.
inputsToCheck = [];
genFilesToCheck = program.getGeneratedFiles().filter(
gf => program.hasChanged(gf.genFileName) && gf.genFileName.endsWith('.ts'));
}
debug(
`TypeChecking ${inputsToCheck ? inputsToCheck.length : 'all'} inputs and ${genFilesToCheck ? genFilesToCheck.length : 'all'} generated files`);
const diagnostics = [...gatherDiagnostics(program !, inputsToCheck, genFilesToCheck)];
let emitResult: tsickle.EmitResult|undefined;
if (!diagnostics.length) {
const targetFileNames = [...bazelOpts.compilationTargetSrc];
for (const genFile of program.getGeneratedFiles()) {
if (genFile.genFileName.endsWith('.ts')) {
targetFileNames.push(genFile.genFileName);
}
}
emitResult = emitWithCache(program, inputsChanged, targetFileNames, compilerOpts, bazelHost);
diagnostics.push(...emitResult.diagnostics);
} }
const {diagnostics, emitResult, program} = ng.performCompilation(
{rootNames: files, options: compilerOpts, host: ngHost, emitCallback, gatherDiagnostics});
const tsickleEmitResult = emitResult as tsickle.EmitResult;
let externs = '/** @externs */\n'; let externs = '/** @externs */\n';
if (diagnostics.length) { if (diagnostics.length) {
console.error(ng.formatDiagnostics(compilerOpts, diagnostics)); console.error(ng.formatDiagnostics(compilerOpts, diagnostics));
} else if (emitResult) { } else {
if (bazelOpts.tsickleGenerateExterns) { if (bazelOpts.tsickleGenerateExterns) {
externs += tsickle.getGeneratedExterns(emitResult.externs); externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs);
} }
if (bazelOpts.manifest) { if (bazelOpts.manifest) {
const manifest = constructManifest(emitResult.modulesManifest, bazelHost); const manifest = constructManifest(tsickleEmitResult.modulesManifest, bazelHost);
fs.writeFileSync(bazelOpts.manifest, manifest); fs.writeFileSync(bazelOpts.manifest, manifest);
} }
} }
@ -257,9 +230,14 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
return {program, diagnostics}; return {program, diagnostics};
} }
function defaultGatherDiagnostics( function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolean {
ngProgram: ng.Program, inputsToCheck: ts.SourceFile[], return !NGC_GEN_FILES.test(sf.fileName) &&
genFilesToCheck: ng.GeneratedFile[]): (ng.Diagnostic | ts.Diagnostic)[] { (bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
}
function gatherDiagnosticsForInputsOnly(
options: ng.CompilerOptions, bazelOpts: BazelOptions,
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {
const tsProgram = ngProgram.getTsProgram(); const tsProgram = ngProgram.getTsProgram();
const diagnostics: (ng.Diagnostic | ts.Diagnostic)[] = []; const diagnostics: (ng.Diagnostic | ts.Diagnostic)[] = [];
// These checks mirror ts.getPreEmitDiagnostics, with the important // These checks mirror ts.getPreEmitDiagnostics, with the important
@ -267,7 +245,7 @@ function defaultGatherDiagnostics(
// program.getDeclarationDiagnostics() it somehow corrupts the emit. // program.getDeclarationDiagnostics() it somehow corrupts the emit.
diagnostics.push(...tsProgram.getOptionsDiagnostics()); diagnostics.push(...tsProgram.getOptionsDiagnostics());
diagnostics.push(...tsProgram.getGlobalDiagnostics()); diagnostics.push(...tsProgram.getGlobalDiagnostics());
for (const sf of inputsToCheck) { for (const sf of tsProgram.getSourceFiles().filter(f => isCompilationTarget(bazelOpts, f))) {
// Note: We only get the diagnostics for individual files // Note: We only get the diagnostics for individual files
// to e.g. not check libraries. // to e.g. not check libraries.
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf)); diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
@ -277,9 +255,7 @@ function defaultGatherDiagnostics(
// only gather the angular diagnostics if we have no diagnostics // only gather the angular diagnostics if we have no diagnostics
// in any other files. // in any other files.
diagnostics.push(...ngProgram.getNgStructuralDiagnostics()); diagnostics.push(...ngProgram.getNgStructuralDiagnostics());
for (const genFile of genFilesToCheck) { diagnostics.push(...ngProgram.getNgSemanticDiagnostics());
diagnostics.push(...ngProgram.getNgSemanticDiagnostics(genFile));
}
} }
return diagnostics; return diagnostics;
} }

View File

@ -58,8 +58,8 @@ export class CodeGenerator {
private emit(analyzedModules: compiler.NgAnalyzedModules) { private emit(analyzedModules: compiler.NgAnalyzedModules) {
const generatedModules = this.compiler.emitAllImpls(analyzedModules); const generatedModules = this.compiler.emitAllImpls(analyzedModules);
return generatedModules.map(generatedModule => { return generatedModules.map(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.srcFileName); const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileName); const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE); const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE);
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]); this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
return emitPath; return emitPath;

View File

@ -39,7 +39,7 @@ export function main(
return reportErrorsAndExit(options, compileDiags, consoleError); return reportErrorsAndExit(options, compileDiags, consoleError);
} }
function createEmitCallback(options: api.CompilerOptions): api.EmitCallback|undefined { function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined {
const transformDecorators = options.annotationsAs !== 'decorators'; const transformDecorators = options.annotationsAs !== 'decorators';
const transformTypesToClosure = options.annotateForClosureCompiler; const transformTypesToClosure = options.annotateForClosureCompiler;
if (!transformDecorators && !transformTypesToClosure) { if (!transformDecorators && !transformTypesToClosure) {
@ -58,30 +58,20 @@ function createEmitCallback(options: api.CompilerOptions): api.EmitCallback|unde
return ({ return ({
program, program,
targetSourceFiles, targetSourceFile,
writeFile, writeFile,
cancellationToken, cancellationToken,
emitOnlyDtsFiles, emitOnlyDtsFiles,
customTransformers = {}, customTransformers = {},
host, host,
options options
}) => { }) =>
if (targetSourceFiles) { tsickle.emitWithTsickle(
return tsickle.mergeEmitResults(targetSourceFiles.map( program, tsickleHost, host, options, targetSourceFile, writeFile,
targetSourceFile => tsickle.emitWithTsickle( cancellationToken, emitOnlyDtsFiles, {
program, tsickleHost, host, options, targetSourceFile, writeFile, cancellationToken, beforeTs: customTransformers.before,
emitOnlyDtsFiles, { afterTs: customTransformers.after,
beforeTs: customTransformers.before, });
afterTs: customTransformers.after,
})));
}
return tsickle.emitWithTsickle(
program, tsickleHost, host, options, /*targetSourceFile*/ undefined, writeFile,
cancellationToken, emitOnlyDtsFiles, {
beforeTs: customTransformers.before,
afterTs: customTransformers.after,
});
};
} }
export interface NgcParsedConfiguration extends ParsedConfiguration { watch?: boolean; } export interface NgcParsedConfiguration extends ParsedConfiguration { watch?: boolean; }

View File

@ -103,7 +103,8 @@ export interface Program {
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[]; getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[];
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken): getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
ts.Diagnostic[]; ts.Diagnostic[];
getNgSemanticDiagnostics(genFile?: any, cancellationToken?: ts.CancellationToken): Diagnostic[]; getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
Diagnostic[];
loadNgStructureAsync(): Promise<void>; loadNgStructureAsync(): Promise<void>;
emit({emitFlags, cancellationToken, customTransformers, emitCallback}: { emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
emitFlags?: EmitFlags, emitFlags?: EmitFlags,

View File

@ -144,7 +144,7 @@ export function performCompilation({rootNames, options, host, oldProgram, emitCa
options: api.CompilerOptions, options: api.CompilerOptions,
host?: api.CompilerHost, host?: api.CompilerHost,
oldProgram?: api.Program, oldProgram?: api.Program,
emitCallback?: api.EmitCallback, emitCallback?: api.TsEmitCallback,
gatherDiagnostics?: (program: api.Program) => Diagnostics, gatherDiagnostics?: (program: api.Program) => Diagnostics,
customTransformers?: api.CustomTransformers, customTransformers?: api.CustomTransformers,
emitFlags?: api.EmitFlags emitFlags?: api.EmitFlags

View File

@ -40,7 +40,7 @@ export interface PerformWatchHost {
reportDiagnostics(diagnostics: Diagnostics): void; reportDiagnostics(diagnostics: Diagnostics): void;
readConfiguration(): ParsedConfiguration; readConfiguration(): ParsedConfiguration;
createCompilerHost(options: api.CompilerOptions): api.CompilerHost; createCompilerHost(options: api.CompilerOptions): api.CompilerHost;
createEmitCallback(options: api.CompilerOptions): api.EmitCallback|undefined; createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined;
onFileChange( onFileChange(
options: api.CompilerOptions, listener: (event: FileChangeEvent, fileName: string) => void, options: api.CompilerOptions, listener: (event: FileChangeEvent, fileName: string) => void,
ready: () => void): {close: () => void}; ready: () => void): {close: () => void};
@ -51,7 +51,7 @@ export interface PerformWatchHost {
export function createPerformWatchHost( export function createPerformWatchHost(
configFileName: string, reportDiagnostics: (diagnostics: Diagnostics) => void, configFileName: string, reportDiagnostics: (diagnostics: Diagnostics) => void,
existingOptions?: ts.CompilerOptions, createEmitCallback?: (options: api.CompilerOptions) => existingOptions?: ts.CompilerOptions, createEmitCallback?: (options: api.CompilerOptions) =>
api.EmitCallback | undefined): PerformWatchHost { api.TsEmitCallback | undefined): PerformWatchHost {
return { return {
reportDiagnostics: reportDiagnostics, reportDiagnostics: reportDiagnostics,
createCompilerHost: options => createCompilerHost({options}), createCompilerHost: options => createCompilerHost({options}),

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {GeneratedFile as GeneratedFileImpl, ParseSourceSpan} from '@angular/compiler'; import {GeneratedFile, ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
export const DEFAULT_ERROR_CODE = 100; export const DEFAULT_ERROR_CODE = 100;
@ -207,32 +207,18 @@ export interface CustomTransformers {
afterTs?: ts.TransformerFactory<ts.SourceFile>[]; afterTs?: ts.TransformerFactory<ts.SourceFile>[];
} }
export interface EmitArguments { export interface TsEmitArguments {
program: ts.Program; program: ts.Program;
host: CompilerHost; host: CompilerHost;
options: CompilerOptions; options: CompilerOptions;
targetSourceFiles?: ts.SourceFile[]; targetSourceFile?: ts.SourceFile;
writeFile?: ts.WriteFileCallback; writeFile?: ts.WriteFileCallback;
cancellationToken?: ts.CancellationToken; cancellationToken?: ts.CancellationToken;
emitOnlyDtsFiles?: boolean; emitOnlyDtsFiles?: boolean;
customTransformers?: ts.CustomTransformers; customTransformers?: ts.CustomTransformers;
} }
export interface EmitCallback { (args: EmitArguments): ts.EmitResult; } export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
/**
* Represents a generated file that has not yet been emitted.
*/
export interface GeneratedFile {
/**
* The file name of the sourceFile from which this file was generated.
*/
srcFileName: string;
/**
* The file name of the generated but not yet emitted file.
*/
genFileName: string;
}
/** /**
* @internal * @internal
@ -296,7 +282,7 @@ export interface Program {
* *
* Angular structural information is required to produce these diagnostics. * Angular structural information is required to produce these diagnostics.
*/ */
getNgSemanticDiagnostics(genFile?: GeneratedFile, cancellationToken?: ts.CancellationToken): getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
Diagnostic[]; Diagnostic[];
/** /**
@ -307,33 +293,16 @@ export interface Program {
*/ */
loadNgStructureAsync(): Promise<void>; loadNgStructureAsync(): Promise<void>;
/**
* Retrieves the GeneratedFile with the given fileName.
*/
getGeneratedFile(genFileName: string): GeneratedFile|undefined;
/**
* Retriesves all GeneratedFiles.
*/
getGeneratedFiles(): GeneratedFile[];
/**
* Calculates whether the given file has changed since the `oldProgram` that was passed
* to `createProgram`.
*/
hasChanged(fileName: string): boolean;
/** /**
* Emit the files requested by emitFlags implied by the program. * Emit the files requested by emitFlags implied by the program.
* *
* Angular structural information is required to emit files. * Angular structural information is required to emit files.
*/ */
emit({targetFileNames, emitFlags, cancellationToken, customTransformers, emitCallback}?: { emit({emitFlags, cancellationToken, customTransformers, emitCallback}?: {
targetFileNames?: string[],
emitFlags?: EmitFlags, emitFlags?: EmitFlags,
cancellationToken?: ts.CancellationToken, cancellationToken?: ts.CancellationToken,
customTransformers?: CustomTransformers, customTransformers?: CustomTransformers,
emitCallback?: EmitCallback emitCallback?: TsEmitCallback
}): ts.EmitResult; }): ts.EmitResult;
/** /**
@ -348,25 +317,10 @@ export interface Program {
/** /**
* @internal * @internal
*/ */
getEmittedGeneratedFiles(): Map<string, GeneratedFileImpl>; getEmittedGeneratedFiles(): Map<string, GeneratedFile>;
/** /**
* @internal * @internal
*/ */
getEmittedSourceFiles(): Map<string, ts.SourceFile>; getEmittedSourceFiles(): Map<string, ts.SourceFile>;
} }
export type OldProgram = Program | CachedFiles;
export interface CachedFiles {
getSourceFile(fileName: string): ts.SourceFile|undefined;
getGeneratedFile(srcFileName: string, genFileName: string): GeneratedFile|undefined;
}
export interface CreateProgram {
({rootNames, options, host, oldProgram}: {
rootNames: string[],
options: CompilerOptions,
host: CompilerHost, oldProgram?: OldProgram
}): Program;
}

View File

@ -15,10 +15,11 @@ import {TypeCheckHost} from '../diagnostics/translate_diagnostics';
import {ModuleMetadata} from '../metadata/index'; import {ModuleMetadata} from '../metadata/index';
import {CompilerHost, CompilerOptions, LibrarySummary} from './api'; import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
import {EXT, GENERATED_FILES} from './util'; import {GENERATED_FILES} from './util';
const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/; const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/;
const DTS = /\.d\.ts$/; const DTS = /\.d\.ts$/;
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export function createCompilerHost( export function createCompilerHost(
{options, tsHost = ts.createCompilerHost(options, true)}: {options, tsHost = ts.createCompilerHost(options, true)}:
@ -261,12 +262,11 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile { updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile {
if (!genFile.stmts) { if (!genFile.stmts) {
throw new Error( throw new Error(
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileName}`); `Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
} }
const oldGenFile = this.generatedSourceFiles.get(genFile.genFileName); const oldGenFile = this.generatedSourceFiles.get(genFile.genFileUrl);
if (!oldGenFile) { if (!oldGenFile) {
throw new Error( throw new Error(`Illegal State: previous GeneratedFile not found for ${genFile.genFileUrl}.`);
`Illegal State: previous GeneratedFile not found for ${genFile.genFileName}.`);
} }
const newRefs = genFileExternalReferences(genFile); const newRefs = genFileExternalReferences(genFile);
const oldRefs = oldGenFile.externalReferences; const oldRefs = oldGenFile.externalReferences;
@ -276,7 +276,7 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
} }
if (!refsAreEqual) { if (!refsAreEqual) {
throw new Error( throw new Error(
`Illegal State: external references changed in ${genFile.genFileName}.\nOld: ${Array.from(oldRefs)}.\nNew: ${Array.from(newRefs)}`); `Illegal State: external references changed in ${genFile.genFileUrl}.\nOld: ${Array.from(oldRefs)}.\nNew: ${Array.from(newRefs)}`);
} }
return this.addGeneratedFile(genFile, newRefs); return this.addGeneratedFile(genFile, newRefs);
} }
@ -284,14 +284,14 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile { private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile {
if (!genFile.stmts) { if (!genFile.stmts) {
throw new Error( throw new Error(
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileName}`); `Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
} }
const {sourceText, context} = this.emitter.emitStatementsAndContext( const {sourceText, context} = this.emitter.emitStatementsAndContext(
genFile.genFileName, genFile.stmts, /* preamble */ '', genFile.genFileUrl, genFile.stmts, /* preamble */ '',
/* emitSourceMaps */ false); /* emitSourceMaps */ false);
const sf = ts.createSourceFile( const sf = ts.createSourceFile(
genFile.genFileName, sourceText, this.options.target || ts.ScriptTarget.Latest); genFile.genFileUrl, sourceText, this.options.target || ts.ScriptTarget.Latest);
this.generatedSourceFiles.set(genFile.genFileName, { this.generatedSourceFiles.set(genFile.genFileUrl, {
sourceFile: sf, sourceFile: sf,
emitCtx: context, externalReferences, emitCtx: context, externalReferences,
}); });

View File

@ -12,4 +12,3 @@ import {CompilerHost, CompilerOptions, Program} from './api';
export {createCompilerHost} from './compiler_host'; export {createCompilerHost} from './compiler_host';
export {createProgram} from './program'; export {createProgram} from './program';
export {isGeneratedFile} from './util';

View File

@ -9,7 +9,6 @@
import {GeneratedFile} from '@angular/compiler'; import {GeneratedFile} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Program} from './api';
import {TypeScriptNodeEmitter} from './node_emitter'; import {TypeScriptNodeEmitter} from './node_emitter';
import {GENERATED_FILES} from './util'; import {GENERATED_FILES} from './util';
@ -20,12 +19,12 @@ const PREAMBLE = `/**
* tslint:disable * tslint:disable
*/`; */`;
export function getAngularEmitterTransformFactory(program: Program): () => export function getAngularEmitterTransformFactory(generatedFiles: Map<string, GeneratedFile>): () =>
(sourceFile: ts.SourceFile) => ts.SourceFile { (sourceFile: ts.SourceFile) => ts.SourceFile {
return function() { return function() {
const emitter = new TypeScriptNodeEmitter(); const emitter = new TypeScriptNodeEmitter();
return function(sourceFile: ts.SourceFile): ts.SourceFile { return function(sourceFile: ts.SourceFile): ts.SourceFile {
const g = program.getGeneratedFile(sourceFile.fileName) as GeneratedFile; const g = generatedFiles.get(sourceFile.fileName);
if (g && g.stmts) { if (g && g.stmts) {
const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, PREAMBLE); const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, PREAMBLE);
return newSourceFile; return newSourceFile;

View File

@ -14,12 +14,11 @@ import * as ts from 'typescript';
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics'; import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index'; import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
import {CachedFiles, CompilerHost, CompilerOptions, CreateProgram, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitArguments, EmitCallback, EmitFlags, LibrarySummary, OldProgram, Program, SOURCE} from './api'; import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host'; import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform'; import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {EXT, GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util'; import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util';
/** /**
* Maximum number of files that are emitable via calling ts.Program.emit * Maximum number of files that are emitable via calling ts.Program.emit
@ -33,34 +32,17 @@ const emptyModules: NgAnalyzedModules = {
files: [] files: []
}; };
const defaultEmitCallback: EmitCallback = const defaultEmitCallback: TsEmitCallback =
({program, targetSourceFiles, writeFile, cancellationToken, emitOnlyDtsFiles, ({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles,
customTransformers}) => { customTransformers}) =>
if (targetSourceFiles) { program.emit(
const diagnostics: ts.Diagnostic[] = []; targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
let emitSkipped = false;
const emittedFiles: string[] = [];
for (const targetSourceFile of targetSourceFiles) {
const er = program.emit(
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
diagnostics.push(...er.diagnostics);
emitSkipped = emitSkipped || er.emitSkipped;
emittedFiles.push(...er.emittedFiles);
}
return {diagnostics, emitSkipped, emittedFiles};
} else {
return program.emit(
/*targetSourceFile*/ undefined, writeFile, cancellationToken, emitOnlyDtsFiles,
customTransformers);
}
};
class AngularCompilerProgram implements Program { class AngularCompilerProgram implements Program {
private metadataCache: LowerMetadataCache; private metadataCache: LowerMetadataCache;
private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined; private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined;
private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined; private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined;
private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|undefined; private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|undefined;
private oldProgramCachedFiles: CachedFiles|undefined;
// Note: This will be cleared out as soon as we create the _tsProgram // Note: This will be cleared out as soon as we create the _tsProgram
private oldTsProgram: ts.Program|undefined; private oldTsProgram: ts.Program|undefined;
private emittedLibrarySummaries: LibrarySummary[]|undefined; private emittedLibrarySummaries: LibrarySummary[]|undefined;
@ -74,33 +56,20 @@ class AngularCompilerProgram implements Program {
private _analyzedModules: NgAnalyzedModules|undefined; private _analyzedModules: NgAnalyzedModules|undefined;
private _structuralDiagnostics: Diagnostic[]|undefined; private _structuralDiagnostics: Diagnostic[]|undefined;
private _programWithStubs: ts.Program|undefined; private _programWithStubs: ts.Program|undefined;
private _generatedFilesMap: Map<string, GeneratedFile>|undefined;
private _generatedFiles: GeneratedFile[]|undefined;
private _changedFileNames = new Map<string, boolean>();
private _optionsDiagnostics: Diagnostic[] = []; private _optionsDiagnostics: Diagnostic[] = [];
constructor( constructor(
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost, private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
oldProgram?: OldProgram) { private oldProgram?: Program) {
const [major, minor] = ts.version.split('.'); const [major, minor] = ts.version.split('.');
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 4)) { if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 4)) {
throw new Error('The Angular Compiler requires TypeScript >= 2.4.'); throw new Error('The Angular Compiler requires TypeScript >= 2.4.');
} }
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
if (oldProgram) { if (oldProgram) {
if ((oldProgram as Program).getTsProgram) { this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
const oldNgProgram = oldProgram as Program; this.oldProgramEmittedGeneratedFiles = oldProgram.getEmittedGeneratedFiles();
this.oldTsProgram = oldNgProgram.getTsProgram(); this.oldProgramEmittedSourceFiles = oldProgram.getEmittedSourceFiles();
this.oldProgramLibrarySummaries = oldNgProgram.getLibrarySummaries();
this.oldProgramEmittedGeneratedFiles = oldNgProgram.getEmittedGeneratedFiles();
this.oldProgramEmittedSourceFiles = oldNgProgram.getEmittedSourceFiles();
this.oldProgramCachedFiles = {
getSourceFile: (fileName: string) => this.oldProgramEmittedSourceFiles !.get(fileName),
getGeneratedFile: (srcFileName: string, genFileName: string) =>
this.oldProgramEmittedGeneratedFiles !.get(genFileName),
};
} else {
this.oldProgramCachedFiles = oldProgram as CachedFiles;
}
} }
if (options.flatModuleOutFile) { if (options.flatModuleOutFile) {
@ -141,9 +110,7 @@ class AngularCompilerProgram implements Program {
(genFile, fileName) => result.set(fileName, genFile)); (genFile, fileName) => result.set(fileName, genFile));
} }
if (this.emittedGeneratedFiles) { if (this.emittedGeneratedFiles) {
for (const genFile of this.emittedGeneratedFiles) { this.emittedGeneratedFiles.forEach((genFile) => result.set(genFile.genFileUrl, genFile));
result.set(genFile.genFileName, genFile);
}
} }
return result; return result;
} }
@ -154,9 +121,7 @@ class AngularCompilerProgram implements Program {
this.oldProgramEmittedSourceFiles.forEach((sf, fileName) => result.set(fileName, sf)); this.oldProgramEmittedSourceFiles.forEach((sf, fileName) => result.set(fileName, sf));
} }
if (this.emittedSourceFiles) { if (this.emittedSourceFiles) {
for (const sf of this.emittedSourceFiles) { this.emittedSourceFiles.forEach((sf) => result.set(sf.fileName, sf));
result.set(sf.fileName, sf);
}
} }
return result; return result;
} }
@ -186,27 +151,22 @@ class AngularCompilerProgram implements Program {
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken); return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
} }
let diags: ts.Diagnostic[] = []; let diags: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) { this.tsProgram.getSourceFiles().forEach(sf => {
if (!GENERATED_FILES.test(sf.fileName)) { if (!GENERATED_FILES.test(sf.fileName)) {
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken)); diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
} }
} });
return diags; return diags;
} }
getNgSemanticDiagnostics(genFile?: GeneratedFile, cancellationToken?: ts.CancellationToken): getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
Diagnostic[] { Diagnostic[] {
let diags: ts.Diagnostic[] = []; let diags: ts.Diagnostic[] = [];
if (genFile) { this.tsProgram.getSourceFiles().forEach(sf => {
diags.push(...this.tsProgram.getSemanticDiagnostics( if (GENERATED_FILES.test(sf.fileName) && !sf.isDeclarationFile) {
this.tsProgram.getSourceFile(genFile.genFileName), cancellationToken)); diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
} else {
for (const sf of this.tsProgram.getSourceFiles()) {
if (GENERATED_FILES.test(sf.fileName) && !sf.isDeclarationFile) {
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
}
} }
} });
const {ng} = translateDiagnostics(this.typeCheckHost, diags); const {ng} = translateDiagnostics(this.typeCheckHost, diags);
return ng; return ng;
} }
@ -227,42 +187,13 @@ class AngularCompilerProgram implements Program {
}); });
} }
getGeneratedFile(genFileName: string): GeneratedFile|undefined {
return this.generatedFilesMap.get(genFileName);
}
getGeneratedFiles(): GeneratedFile[] { return this.generatedFiles; }
hasChanged(fileName: string) {
if (!this.oldProgramCachedFiles) {
return true;
}
let changed = this._changedFileNames.get(fileName);
if (typeof changed === 'boolean') {
return changed;
}
const genFile = this.getGeneratedFile(fileName);
if (genFile) {
const oldGenFile = this.oldProgramCachedFiles !.getGeneratedFile(
genFile.srcFileName, genFile.genFileName) as GeneratedFile;
changed = !oldGenFile || !genFile.isEquivalent(oldGenFile);
} else {
const sf = this.tsProgram.getSourceFile(fileName);
const oldSf = this.oldProgramCachedFiles !.getSourceFile(fileName);
changed = !oldSf || sf !== oldSf;
}
this._changedFileNames.set(fileName, changed);
return changed;
}
emit( emit(
{targetFileNames, emitFlags = EmitFlags.Default, cancellationToken, customTransformers, {emitFlags = EmitFlags.Default, cancellationToken, customTransformers,
emitCallback = defaultEmitCallback}: { emitCallback = defaultEmitCallback}: {
targetFileNames?: string[],
emitFlags?: EmitFlags, emitFlags?: EmitFlags,
cancellationToken?: ts.CancellationToken, cancellationToken?: ts.CancellationToken,
customTransformers?: CustomTransformers, customTransformers?: CustomTransformers,
emitCallback?: EmitCallback emitCallback?: TsEmitCallback
} = {}): ts.EmitResult { } = {}): ts.EmitResult {
const emitStart = Date.now(); const emitStart = Date.now();
if (emitFlags & EmitFlags.I18nBundle) { if (emitFlags & EmitFlags.I18nBundle) {
@ -272,48 +203,88 @@ class AngularCompilerProgram implements Program {
const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale); const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale);
i18nExtract(format, file, this.host, this.options, bundle); i18nExtract(format, file, this.host, this.options, bundle);
} }
const emitSkippedResult = {emitSkipped: true, diagnostics: [], emittedFiles: []};
if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) === if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) ===
0) { 0) {
return emitSkippedResult; return {emitSkipped: true, diagnostics: [], emittedFiles: []};
} }
const {srcFilesToEmit, jsonFilesToEmit, emitAllFiles} = this.calcFilesToEmit(targetFileNames); let {genFiles, genDiags} = this.generateFilesForEmit(emitFlags);
if (genDiags.length) {
return {
diagnostics: genDiags,
emitSkipped: true,
emittedFiles: [],
};
}
this.emittedGeneratedFiles = genFiles;
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = []; const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
const genFileByFileName = new Map<string, GeneratedFile>();
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
this.emittedLibrarySummaries = [];
const emittedSourceFiles = [] as ts.SourceFile[];
const writeTsFile: ts.WriteFileCallback = const writeTsFile: ts.WriteFileCallback =
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => { (outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null; const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
let genFile: GeneratedFile|undefined; let genFile: GeneratedFile|undefined;
if (sourceFile) { if (sourceFile) {
outSrcMapping.push({outFileName: outFileName, sourceFile}); outSrcMapping.push({outFileName: outFileName, sourceFile});
if (emitFlags & EmitFlags.Codegen) { genFile = genFileByFileName.get(sourceFile.fileName);
genFile = this.getGeneratedFile(sourceFile.fileName); if (!sourceFile.isDeclarationFile && !GENERATED_FILES.test(sourceFile.fileName)) {
// Note: sourceFile is the transformed sourcefile, not the original one!
emittedSourceFiles.push(this.tsProgram.getSourceFile(sourceFile.fileName));
} }
} }
this.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles, genFile); this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
}; };
const tsCustomTansformers = this.calculateTransforms(customTransformers); const tsCustomTansformers = this.calculateTransforms(genFileByFileName, customTransformers);
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
// Restore the original references before we emit so TypeScript doesn't emit // Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file. // a reference to the .d.ts file.
const augmentedReferences = new Map<ts.SourceFile, ts.FileReference[]>(); const augmentedReferences = new Map<ts.SourceFile, ts.FileReference[]>();
for (const sourceFile of srcFilesToEmit) { for (const sourceFile of this.tsProgram.getSourceFiles()) {
const originalReferences = getOriginalReferences(sourceFile); const originalReferences = getOriginalReferences(sourceFile);
if (originalReferences) { if (originalReferences) {
augmentedReferences.set(sourceFile, sourceFile.referencedFiles); augmentedReferences.set(sourceFile, sourceFile.referencedFiles);
sourceFile.referencedFiles = originalReferences; sourceFile.referencedFiles = originalReferences;
} }
} }
const genTsFiles: GeneratedFile[] = [];
const genJsonFiles: GeneratedFile[] = [];
genFiles.forEach(gf => {
if (gf.stmts) {
genTsFiles.push(gf);
}
if (gf.source) {
genJsonFiles.push(gf);
}
});
let emitResult: ts.EmitResult; let emitResult: ts.EmitResult;
let emittedUserTsCount: number;
try { try {
emitResult = emitCallback({ const sourceFilesToEmit = this.getSourceFilesForEmit();
program: this.tsProgram, if (sourceFilesToEmit &&
host: this.host, (sourceFilesToEmit.length + genTsFiles.length) < MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT) {
options: this.options, const fileNamesToEmit =
writeFile: writeTsFile, emitOnlyDtsFiles, [...sourceFilesToEmit.map(sf => sf.fileName), ...genTsFiles.map(gf => gf.genFileUrl)];
customTransformers: tsCustomTansformers, emitResult = mergeEmitResults(
targetSourceFiles: emitAllFiles ? undefined : srcFilesToEmit, fileNamesToEmit.map((fileName) => emitResult = emitCallback({
}); program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTansformers,
targetSourceFile: this.tsProgram.getSourceFile(fileName),
})));
emittedUserTsCount = sourceFilesToEmit.length;
} else {
emitResult = emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTansformers
});
emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length;
}
} finally { } finally {
// Restore the references back to the augmented value to ensure that the // Restore the references back to the augmented value to ensure that the
// checks that TypeScript makes for project structure reuse will succeed. // checks that TypeScript makes for project structure reuse will succeed.
@ -321,6 +292,7 @@ class AngularCompilerProgram implements Program {
sourceFile.referencedFiles = references; sourceFile.referencedFiles = references;
} }
} }
this.emittedSourceFiles = emittedSourceFiles;
if (!outSrcMapping.length) { if (!outSrcMapping.length) {
// if no files were emitted by TypeScript, also don't emit .json files // if no files were emitted by TypeScript, also don't emit .json files
@ -337,99 +309,35 @@ class AngularCompilerProgram implements Program {
const srcToOutPath = const srcToOutPath =
createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName); createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName);
if (emitFlags & EmitFlags.Codegen) { if (emitFlags & EmitFlags.Codegen) {
this.emitNgSummaryJsonFiles(jsonFilesToEmit, srcToOutPath); genJsonFiles.forEach(gf => {
const outFileName = srcToOutPath(gf.genFileUrl);
this.writeFile(outFileName, gf.source !, false, undefined, gf);
});
} }
let metadataJsonCount = 0;
if (emitFlags & EmitFlags.Metadata) { if (emitFlags & EmitFlags.Metadata) {
this.emitMetadataJsonFiles(srcFilesToEmit, srcToOutPath); this.tsProgram.getSourceFiles().forEach(sf => {
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
metadataJsonCount++;
const metadata = this.metadataCache.getMetadata(sf);
const metadataText = JSON.stringify([metadata]);
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
}
});
} }
const emitEnd = Date.now(); const emitEnd = Date.now();
if (this.options.diagnostics) { if (this.options.diagnostics) {
const genTsFileCount = srcFilesToEmit.filter(sf => GENERATED_FILES.test(sf.fileName)).length;
let genJsonCount = jsonFilesToEmit.length;
if (emitFlags & EmitFlags.Metadata) {
genJsonCount += genTsFileCount;
}
emitResult.diagnostics.push(createMessageDiagnostic([ emitResult.diagnostics.push(createMessageDiagnostic([
`Emitted in ${emitEnd - emitStart}ms`, `Emitted in ${emitEnd - emitStart}ms`,
`- ${srcFilesToEmit.length - genTsFileCount} user ts files`, `- ${emittedUserTsCount} user ts files`,
`- ${genTsFileCount} generated ts files`, `- ${genTsFiles.length} generated ts files`,
`- ${genJsonCount} generated json files`, `- ${genJsonFiles.length + metadataJsonCount} generated json files`,
].join('\n'))); ].join('\n')));
} }
return emitResult; return emitResult;
} }
private emitNgSummaryJsonFiles(
jsonFilesToEmit: GeneratedFile[], srcToOutPath: (srcFileName: string) => string) {
for (const gf of jsonFilesToEmit) {
const outFileName = srcToOutPath(gf.genFileName);
this.writeFile(outFileName, gf.source !, false, undefined, undefined, gf);
}
}
private emitMetadataJsonFiles(
srcFilesToEmit: ts.SourceFile[], srcToOutPath: (srcFileName: string) => string) {
for (const sf of srcFilesToEmit) {
if (!GENERATED_FILES.test(sf.fileName)) {
const metadata = this.metadataCache.getMetadata(sf);
const metadataText = JSON.stringify([metadata]);
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
this.writeFile(outFileName, metadataText, false);
}
}
}
private calcFilesToEmit(targetFileNames?: string[]):
{srcFilesToEmit: ts.SourceFile[]; jsonFilesToEmit: GeneratedFile[]; emitAllFiles: boolean;} {
let srcFilesToEmit: ts.SourceFile[];
let jsonFilesToEmit: GeneratedFile[];
let emitAllFiles: boolean;
if (targetFileNames) {
emitAllFiles = false;
srcFilesToEmit = [];
jsonFilesToEmit = [];
for (const fileName of targetFileNames) {
const sf = this.tsProgram.getSourceFile(fileName);
if (!sf || sf.isDeclarationFile) continue;
srcFilesToEmit.push(sf);
if (!GENERATED_FILES.test(sf.fileName)) {
// find the .ngsummary.json file and mark it for emit as well
const ngSummaryFileName = sf.fileName.replace(EXT, '') + '.ngsummary.json';
const ngSummaryGenFile = this.getGeneratedFile(ngSummaryFileName);
if (ngSummaryGenFile) {
jsonFilesToEmit.push(ngSummaryGenFile);
}
}
}
} else {
const changedFiles: ts.SourceFile[] = [];
let useChangedFiles = !!this.oldProgramCachedFiles;
if (useChangedFiles) {
for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile) continue;
if (this.hasChanged(sf.fileName)) {
changedFiles.push(sf);
}
if (changedFiles.length > MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT) {
useChangedFiles = false;
break;
}
}
}
if (useChangedFiles) {
emitAllFiles = false;
srcFilesToEmit = changedFiles;
jsonFilesToEmit = this.getGeneratedFiles().filter(
genFile => !!genFile.source && this.hasChanged(genFile.genFileName));
} else {
emitAllFiles = true;
srcFilesToEmit = this.tsProgram.getSourceFiles().filter(sf => !sf.isDeclarationFile);
jsonFilesToEmit = this.getGeneratedFiles().filter(genFile => !!genFile.source);
}
}
return {srcFilesToEmit, jsonFilesToEmit, emitAllFiles};
}
// Private members // Private members
private get compiler(): AotCompiler { private get compiler(): AotCompiler {
if (!this._compiler) { if (!this._compiler) {
@ -466,26 +374,14 @@ class AngularCompilerProgram implements Program {
return this._typeCheckHost !; return this._typeCheckHost !;
} }
private get generatedFilesMap(): Map<string, GeneratedFile> { private calculateTransforms(
if (!this._generatedFilesMap) { genFiles: Map<string, GeneratedFile>,
this.createGenerateFiles(); customTransformers?: CustomTransformers): ts.CustomTransformers {
}
return this._generatedFilesMap !;
}
private get generatedFiles(): GeneratedFile[] {
if (!this._generatedFilesMap) {
this.createGenerateFiles();
}
return this._generatedFiles !;
}
private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers {
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = []; const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
if (!this.options.disableExpressionLowering) { if (!this.options.disableExpressionLowering) {
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache)); beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache));
} }
beforeTs.push(getAngularEmitterTransformFactory(this)); beforeTs.push(getAngularEmitterTransformFactory(genFiles));
if (customTransformers && customTransformers.beforeTs) { if (customTransformers && customTransformers.beforeTs) {
beforeTs.push(...customTransformers.beforeTs); beforeTs.push(...customTransformers.beforeTs);
} }
@ -537,20 +433,20 @@ class AngularCompilerProgram implements Program {
let rootNames = let rootNames =
this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn)); this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn));
if (this.options.noResolve) { if (this.options.noResolve) {
for (const rootName of this.rootNames) { this.rootNames.forEach(rootName => {
if (hostAdapter.shouldGenerateFilesFor(rootName)) { if (hostAdapter.shouldGenerateFilesFor(rootName)) {
rootNames.push(...this._compiler.findGeneratedFileNames(rootName)); rootNames.push(...this._compiler.findGeneratedFileNames(rootName));
} }
} });
} }
const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram); const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram);
const sourceFiles: string[] = []; const sourceFiles: string[] = [];
for (const sf of tmpProgram.getSourceFiles()) { tmpProgram.getSourceFiles().forEach(sf => {
if (hostAdapter.isSourceFile(sf.fileName)) { if (hostAdapter.isSourceFile(sf.fileName)) {
sourceFiles.push(sf.fileName); sourceFiles.push(sf.fileName);
} }
} });
return {tmpProgram, sourceFiles, hostAdapter, rootNames}; return {tmpProgram, sourceFiles, hostAdapter, rootNames};
} }
@ -559,7 +455,7 @@ class AngularCompilerProgram implements Program {
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) { hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) {
this._analyzedModules = analyzedModules || emptyModules; this._analyzedModules = analyzedModules || emptyModules;
if (analyzedModules) { if (analyzedModules) {
for (const sf of tmpProgram.getSourceFiles()) { tmpProgram.getSourceFiles().forEach(sf => {
if (sf.fileName.endsWith('.ngfactory.ts')) { if (sf.fileName.endsWith('.ngfactory.ts')) {
const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName); const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName);
if (generate) { if (generate) {
@ -571,7 +467,7 @@ class AngularCompilerProgram implements Program {
} }
} }
} }
} });
} }
this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram); this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram);
// Note: the new ts program should be completely reusable by TypeScript as: // Note: the new ts program should be completely reusable by TypeScript as:
@ -608,64 +504,95 @@ class AngularCompilerProgram implements Program {
throw e; throw e;
} }
private createGenerateFiles() { // Note: this returns a ts.Diagnostic so that we
this._generatedFiles = this.compiler.emitAllImpls(this.analyzedModules); // can return errors in a ts.EmitResult
this._generatedFilesMap = new Map<string, GeneratedFile>(); private generateFilesForEmit(emitFlags: EmitFlags):
for (const genFile of this._generatedFiles) { {genFiles: GeneratedFile[], genDiags: ts.Diagnostic[]} {
this._generatedFilesMap !.set(genFile.genFileName, genFile); try {
if (!(emitFlags & EmitFlags.Codegen)) {
return {genFiles: [], genDiags: []};
}
let genFiles = this.compiler.emitAllImpls(this.analyzedModules);
if (this.oldProgramEmittedGeneratedFiles) {
const oldProgramEmittedGeneratedFiles = this.oldProgramEmittedGeneratedFiles;
genFiles = genFiles.filter(genFile => {
const oldGenFile = oldProgramEmittedGeneratedFiles.get(genFile.genFileUrl);
return !oldGenFile || !genFile.isEquivalent(oldGenFile);
});
}
return {genFiles, genDiags: []};
} catch (e) {
// TODO(tbosch): check whether we can actually have syntax errors here,
// as we already parsed the metadata and templates before to create the type check block.
if (isSyntaxError(e)) {
const genDiags: ts.Diagnostic[] = [{
file: undefined,
start: undefined,
length: undefined,
messageText: e.message,
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}];
return {genFiles: [], genDiags};
}
throw e;
} }
} }
/**
* Returns undefined if all files should be emitted.
*/
private getSourceFilesForEmit(): ts.SourceFile[]|undefined {
// TODO(tbosch): if one of the files contains a `const enum`
// always emit all files -> return undefined!
let sourceFilesToEmit: ts.SourceFile[]|undefined;
if (this.oldProgramEmittedSourceFiles) {
sourceFilesToEmit = this.tsProgram.getSourceFiles().filter(sf => {
const oldFile = this.oldProgramEmittedSourceFiles !.get(sf.fileName);
return !sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName) && sf !== oldFile;
});
}
return sourceFilesToEmit;
}
private writeFile( private writeFile(
outFileName: string, outData: string, writeByteOrderMark: boolean, outFileName: string, outData: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[], genFile?: GeneratedFile) { onError?: (message: string) => void, genFile?: GeneratedFile, sourceFiles?: ts.SourceFile[]) {
const isGenerated = GENERATED_FILES.test(outFileName);
if (!isGenerated && sourceFiles && sourceFiles.length === 1) {
const sf = sourceFiles[0];
if (!this.emittedSourceFiles) {
this.emittedSourceFiles = [];
}
// Note: sourceFile is the transformed sourcefile, not the original one!
this.emittedSourceFiles.push(this.tsProgram.getSourceFile(sf.fileName));
}
// collect emittedLibrarySummaries // collect emittedLibrarySummaries
let baseFile: ts.SourceFile|undefined; let baseFile: ts.SourceFile|undefined;
if (genFile) { if (genFile) {
baseFile = this.tsProgram.getSourceFile(genFile.srcFileName); baseFile = this.tsProgram.getSourceFile(genFile.srcFileUrl);
if (baseFile) { if (baseFile) {
if (!this.emittedLibrarySummaries) { if (!this.emittedLibrarySummaries) {
this.emittedLibrarySummaries = []; this.emittedLibrarySummaries = [];
} }
if (genFile.genFileName.endsWith('.ngsummary.json') && if (genFile.genFileUrl.endsWith('.ngsummary.json') && baseFile.fileName.endsWith('.d.ts')) {
baseFile.fileName.endsWith('.d.ts')) {
this.emittedLibrarySummaries.push({ this.emittedLibrarySummaries.push({
fileName: baseFile.fileName, fileName: baseFile.fileName,
text: baseFile.text, text: baseFile.text,
sourceFile: baseFile, sourceFile: baseFile,
}); });
this.emittedLibrarySummaries.push({fileName: genFile.genFileName, text: outData}); this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData});
if (!this.options.declaration) { if (!this.options.declaration) {
// If we don't emit declarations, still record an empty .ngfactory.d.ts file, // If we don't emit declarations, still record an empty .ngfactory.d.ts file,
// as we might need it lateron for resolving module names from summaries. // as we might need it lateron for resolving module names from summaries.
const ngFactoryDts = genFile.genFileName.substring(0, genFile.genFileName.length - 15) + const ngFactoryDts =
'.ngfactory.d.ts'; genFile.genFileUrl.substring(0, genFile.genFileUrl.length - 15) + '.ngfactory.d.ts';
this.emittedLibrarySummaries.push({fileName: ngFactoryDts, text: ''}); this.emittedLibrarySummaries.push({fileName: ngFactoryDts, text: ''});
} }
} else if (outFileName.endsWith('.d.ts') && baseFile.fileName.endsWith('.d.ts')) { } else if (outFileName.endsWith('.d.ts') && baseFile.fileName.endsWith('.d.ts')) {
const dtsSourceFilePath = genFile.genFileName.replace(/\.ts$/, '.d.ts'); const dtsSourceFilePath = genFile.genFileUrl.replace(/\.ts$/, '.d.ts');
// Note: Don't use sourceFiles here as the created .d.ts has a path in the outDir, // Note: Don't use sourceFiles here as the created .d.ts has a path in the outDir,
// but we need one that is next to the .ts file // but we need one that is next to the .ts file
this.emittedLibrarySummaries.push({fileName: dtsSourceFilePath, text: outData}); this.emittedLibrarySummaries.push({fileName: dtsSourceFilePath, text: outData});
} }
} }
if (!this.emittedGeneratedFiles) {
this.emittedGeneratedFiles = [];
}
this.emittedGeneratedFiles.push(genFile);
} }
// Filter out generated files for which we didn't generate code. // Filter out generated files for which we didn't generate code.
// This can happen as the stub calculation is not completely exact. // This can happen as the stub caclulation is not completely exact.
// Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file // Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file
const isGenerated = GENERATED_FILES.test(outFileName);
if (isGenerated) { if (isGenerated) {
if (!genFile || !genFile.stmts || genFile.stmts.length === 0) { if (!genFile || !genFile.stmts || genFile.stmts.length === 0) {
if (this.options.allowEmptyCodegenFiles) { if (this.options.allowEmptyCodegenFiles) {
@ -682,13 +609,12 @@ class AngularCompilerProgram implements Program {
} }
} }
export const createProgram: CreateProgram = ({rootNames, options, host, oldProgram}: { export function createProgram(
rootNames: string[], {rootNames, options, host, oldProgram}:
options: CompilerOptions, {rootNames: string[], options: CompilerOptions, host: CompilerHost, oldProgram?: Program}):
host: CompilerHost, oldProgram?: OldProgram Program {
}) => {
return new AngularCompilerProgram(rootNames, options, host, oldProgram); return new AngularCompilerProgram(rootNames, options, host, oldProgram);
}; }
// Compute the AotCompiler options // Compute the AotCompiler options
function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions { function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
@ -842,3 +768,15 @@ export function i18nGetExtension(formatName: string): string {
throw new Error(`Unsupported format "${formatName}"`); throw new Error(`Unsupported format "${formatName}"`);
} }
function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
const diagnostics: ts.Diagnostic[] = [];
let emitSkipped = false;
const emittedFiles: string[] = [];
for (const er of emitResults) {
diagnostics.push(...er.diagnostics);
emitSkipped = emitSkipped || er.emitSkipped;
emittedFiles.push(...er.emittedFiles);
}
return {diagnostics, emitSkipped, emittedFiles};
}

View File

@ -11,7 +11,6 @@ import * as ts from 'typescript';
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api'; import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api';
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/; export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
export const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2} export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
@ -30,7 +29,3 @@ export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diag
source: SOURCE, source: SOURCE,
}; };
} }
export function isGeneratedFile(fileName: string): boolean {
return GENERATED_FILES.test(fileName);
}

View File

@ -58,8 +58,7 @@ describe('ng program', () => {
function compile( function compile(
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[], oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[],
host?: CompilerHost, host?: CompilerHost): {program: ng.Program, emitResult: ts.EmitResult} {
targetFileNames?: string[]): {program: ng.Program, emitResult: ts.EmitResult} {
const options = testSupport.createCompilerOptions(overrideOptions); const options = testSupport.createCompilerOptions(overrideOptions);
if (!rootNames) { if (!rootNames) {
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')]; rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
@ -74,7 +73,7 @@ describe('ng program', () => {
oldProgram, oldProgram,
}); });
expectNoDiagnosticsInProgram(options, program); expectNoDiagnosticsInProgram(options, program);
const emitResult = program.emit({targetFileNames}); const emitResult = program.emit();
return {emitResult, program}; return {emitResult, program};
} }
@ -476,38 +475,6 @@ describe('ng program', () => {
testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json'); testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json');
}); });
it('should only emit the given files', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
'src/main2.ts': createModuleAndCompSource('main2'),
'src/index.ts': `
export * from './main';
export * from './main2';
`
});
compile(undefined, undefined, undefined, undefined, [
path.resolve(testSupport.basePath, 'src/index.ts'),
path.resolve(testSupport.basePath, 'src/index.ngfactory.ts'),
path.resolve(testSupport.basePath, 'src/main.ts'),
path.resolve(testSupport.basePath, 'src/main.ngfactory.ts')
]);
testSupport.shouldExist('built/src/main.js');
testSupport.shouldExist('built/src/main.d.ts');
testSupport.shouldExist('built/src/main.ngfactory.js');
testSupport.shouldExist('built/src/main.ngfactory.d.ts');
testSupport.shouldExist('built/src/main.ngsummary.json');
testSupport.shouldNotExist('built/src/main2.js');
testSupport.shouldNotExist('built/src/main2.d.ts');
testSupport.shouldNotExist('built/src/main2.ngfactory.js');
testSupport.shouldNotExist('built/src/main2.ngfactory.d.ts');
testSupport.shouldNotExist('built/src/main2.ngsummary.json');
testSupport.shouldNotExist('built/node_modules/lib/index.js');
testSupport.shouldNotExist('built/node_modules/lib/index.d.ts');
testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.js');
testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.d.ts');
testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json');
});
describe('createSrcToOutPathMapper', () => { describe('createSrcToOutPathMapper', () => {
it('should return identity mapping if no outDir is present', () => { it('should return identity mapping if no outDir is present', () => {
const mapper = createSrcToOutPathMapper(undefined, undefined, undefined); const mapper = createSrcToOutPathMapper(undefined, undefined, undefined);

View File

@ -14,7 +14,7 @@ export class GeneratedFile {
public stmts: Statement[]|null; public stmts: Statement[]|null;
constructor( constructor(
public srcFileName: string, public genFileName: string, sourceOrStmts: string|Statement[]) { public srcFileUrl: string, public genFileUrl: string, sourceOrStmts: string|Statement[]) {
if (typeof sourceOrStmts === 'string') { if (typeof sourceOrStmts === 'string') {
this.source = sourceOrStmts; this.source = sourceOrStmts;
this.stmts = null; this.stmts = null;
@ -25,7 +25,7 @@ export class GeneratedFile {
} }
isEquivalent(other: GeneratedFile): boolean { isEquivalent(other: GeneratedFile): boolean {
if (this.genFileName !== other.genFileName) { if (this.genFileUrl !== other.genFileUrl) {
return false; return false;
} }
if (this.source) { if (this.source) {
@ -42,7 +42,7 @@ export class GeneratedFile {
export function toTypeScript(file: GeneratedFile, preamble: string = ''): string { export function toTypeScript(file: GeneratedFile, preamble: string = ''): string {
if (!file.stmts) { if (!file.stmts) {
throw new Error(`Illegal state: No stmts present on GeneratedFile ${file.genFileName}`); throw new Error(`Illegal state: No stmts present on GeneratedFile ${file.genFileUrl}`);
} }
return new TypeScriptEmitter().emitStatements(file.genFileName, file.stmts, preamble); return new TypeScriptEmitter().emitStatements(file.genFileUrl, file.stmts, preamble);
} }

View File

@ -22,8 +22,8 @@ describe('compiler (unbundled Angular)', () => {
describe('Quickstart', () => { describe('Quickstart', () => {
it('should compile', () => { it('should compile', () => {
const {genFiles} = compile([QUICKSTART, angularFiles]); const {genFiles} = compile([QUICKSTART, angularFiles]);
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
}); });
}); });
@ -54,7 +54,7 @@ describe('compiler (unbundled Angular)', () => {
function compileApp(): GeneratedFile { function compileApp(): GeneratedFile {
const {genFiles} = compile([rootDir, angularFiles]); const {genFiles} = compile([rootDir, angularFiles]);
return genFiles.find( return genFiles.find(
genFile => genFile.srcFileName === componentPath && genFile.genFileName.endsWith('.ts')); genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'));
} }
function findLineAndColumn( function findLineAndColumn(
@ -127,7 +127,7 @@ describe('compiler (unbundled Angular)', () => {
const genFile = compileApp(); const genFile = compileApp();
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !; const sourceMap = extractSourceMap(genSource) !;
expect(sourceMap.file).toEqual(genFile.genFileName); expect(sourceMap.file).toEqual(genFile.genFileUrl);
// Note: the generated file also contains code that is not mapped to // Note: the generated file also contains code that is not mapped to
// the template (e.g. import statements, ...) // the template (e.g. import statements, ...)
@ -322,7 +322,7 @@ describe('compiler (unbundled Angular)', () => {
const genFilePreamble = '/* Hello world! */'; const genFilePreamble = '/* Hello world! */';
const {genFiles} = compile([FILES, angularFiles]); const {genFiles} = compile([FILES, angularFiles]);
const genFile = const genFile =
genFiles.find(gf => gf.srcFileName === '/app/app.ts' && gf.genFileName.endsWith('.ts')); genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile, genFilePreamble); const genSource = toTypeScript(genFile, genFilePreamble);
expect(genSource.startsWith(genFilePreamble)).toBe(true); expect(genSource.startsWith(genFilePreamble)).toBe(true);
}); });
@ -407,7 +407,7 @@ describe('compiler (unbundled Angular)', () => {
}; };
const result = compile([FILES, angularFiles]); const result = compile([FILES, angularFiles]);
const appModuleFactory = const appModuleFactory =
result.genFiles.find(f => /my-component\.ngfactory/.test(f.genFileName)); result.genFiles.find(f => /my-component\.ngfactory/.test(f.genFileUrl));
expect(appModuleFactory).toBeDefined(); expect(appModuleFactory).toBeDefined();
if (appModuleFactory) { if (appModuleFactory) {
expect(toTypeScript(appModuleFactory)).toContain('MyComponentNgFactory'); expect(toTypeScript(appModuleFactory)).toContain('MyComponentNgFactory');
@ -441,7 +441,7 @@ describe('compiler (unbundled Angular)', () => {
} }
}; };
const {genFiles} = compile([FILES, angularFiles]); const {genFiles} = compile([FILES, angularFiles]);
const genFile = genFiles.find(genFile => genFile.srcFileName === '/app/app.ts'); const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, ''); const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
// selector // selector
@ -472,7 +472,7 @@ describe('compiler (unbundled Angular)', () => {
}; };
const {genFiles} = compile([FILES, angularFiles]); const {genFiles} = compile([FILES, angularFiles]);
const genFile = const genFile =
genFiles.find(gf => gf.srcFileName === '/app/app.ts' && gf.genFileName.endsWith('.ts')); genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).not.toContain('check('); expect(genSource).not.toContain('check(');
@ -510,7 +510,7 @@ describe('compiler (unbundled Angular)', () => {
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} = const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true}); compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileName === '/app/main.ngfactory.ts'); const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
expect(toTypeScript(appNgFactory)).not.toContain('AType'); expect(toTypeScript(appNgFactory)).not.toContain('AType');
}); });
}); });
@ -555,7 +555,7 @@ describe('compiler (unbundled Angular)', () => {
compile([libInput, getAngularSummaryFiles()], {useSummaries: true}); compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} = const {genFiles} =
compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
return genFiles.find(gf => gf.srcFileName === '/app/main.ts'); return genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
} }
it('should inherit ctor and lifecycle hooks from classes in other compilation units', () => { it('should inherit ctor and lifecycle hooks from classes in other compilation units', () => {
@ -592,7 +592,7 @@ describe('compiler (unbundled Angular)', () => {
compile([libInput, getAngularSummaryFiles()], {useSummaries: true}); compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} = const {genFiles} =
compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileName === '/app/main.ts'); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory)) expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`); .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
@ -645,7 +645,7 @@ describe('compiler (unbundled Angular)', () => {
compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true}); compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} = const {genFiles} =
compile([lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); compile([lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileName === '/app/main.ts'); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory)) expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`); .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`);
@ -803,8 +803,8 @@ describe('compiler (bundled Angular)', () => {
describe('Quickstart', () => { describe('Quickstart', () => {
it('should compile', () => { it('should compile', () => {
const {genFiles} = compile([QUICKSTART, angularFiles]); const {genFiles} = compile([QUICKSTART, angularFiles]);
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
}); });
}); });

View File

@ -41,7 +41,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -70,7 +70,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -99,7 +99,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -125,7 +125,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -164,7 +164,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
@ -198,7 +198,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
@ -225,7 +225,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch( expect(genSource).toMatch(
@ -247,7 +247,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch( expect(genSource).toMatch(
@ -294,9 +294,9 @@ describe('aot summaries for jit', () => {
}; };
const {outDir: lib3In, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true}); const {outDir: lib3In, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true});
const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileName === '/lib2/module.ngsummary.ts'); const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts');
const lib2ReexportNgSummary = const lib2ReexportNgSummary =
lib2Gen.find(f => f.genFileName === '/lib2/reexport.ngsummary.ts'); lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts');
// ngsummaries should add reexports for imported NgModules from a direct dependency // ngsummaries should add reexports for imported NgModules from a direct dependency
expect(toTypeScript(lib2ModuleNgSummary)) expect(toTypeScript(lib2ModuleNgSummary))
@ -325,9 +325,9 @@ describe('aot summaries for jit', () => {
}; };
const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles; const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles;
const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileName === '/lib3/module.ngsummary.ts'); const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts');
const lib3ReexportNgSummary = const lib3ReexportNgSummary =
lib3Gen.find(f => f.genFileName === '/lib3/reexport.ngsummary.ts'); lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts');
// ngsummary.ts files should use the reexported values from direct and deep deps // ngsummary.ts files should use the reexported values from direct and deep deps
const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary); const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary);

View File

@ -27,6 +27,6 @@ describe('regressions', () => {
const {genFiles} = compile( const {genFiles} = compile(
[rootDir, angularFiles], {postCompile: expectNoDiagnostics}, [rootDir, angularFiles], {postCompile: expectNoDiagnostics},
{noUnusedLocals: true, noUnusedParameters: true}); {noUnusedLocals: true, noUnusedParameters: true});
expect(genFiles.find((f) => f.genFileName === '/app/app.module.ngfactory.ts')).toBeTruthy(); expect(genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts')).toBeTruthy();
}); });
}); });

View File

@ -658,10 +658,10 @@ export function compile(
const genFiles = compiler.emitAllImpls(analyzedModules); const genFiles = compiler.emitAllImpls(analyzedModules);
genFiles.forEach((file) => { genFiles.forEach((file) => {
const source = file.source || toTypeScript(file); const source = file.source || toTypeScript(file);
if (isSource(file.genFileName)) { if (isSource(file.genFileUrl)) {
host.addScript(file.genFileName, source); host.addScript(file.genFileUrl, source);
} else { } else {
host.override(file.genFileName, source); host.override(file.genFileUrl, source);
} }
}); });
const newProgram = ts.createProgram(host.scriptNames.slice(0), tsSettings, host); const newProgram = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
@ -671,7 +671,7 @@ export function compile(
} }
let outDir: MockDirectory = {}; let outDir: MockDirectory = {};
if (emit) { if (emit) {
const dtsFilesWithGenFiles = new Set<string>(genFiles.map(gf => gf.srcFileName).filter(isDts)); const dtsFilesWithGenFiles = new Set<string>(genFiles.map(gf => gf.srcFileUrl).filter(isDts));
outDir = outDir =
arrayToMockDir(toMockFileArray([host.writtenFiles, host.overrides]) arrayToMockDir(toMockFileArray([host.writtenFiles, host.overrides])
.filter((entry) => !isSource(entry.fileName)) .filter((entry) => !isSource(entry.fileName))