fix(compiler): various squashed fixes for the new ngc
introduce the option `allowEmptyCodegenFiles` to generate all generated files, even if they are empty. - also provides the original source files from which the file was generated in the write file callback - needed e.g. for G3 when copying over pinto mod names from the original component to all generated files use `importAs` from flat modules when writing summaries - i.e. prevents incorrect entries like @angular/common/common in the .ngsummary.json files. change interaction between ng and ts to prevent race conditions - before Angular would rely on TS to first read the file for which we generate files, and then the generated files. However, this can break easily when we reuse an old program. don’t generate files for sources that are outside of `rootDir` (see #19337)
This commit is contained in:

committed by
Victor Berchet

parent
13613d4acb
commit
a8a9660112
@ -133,6 +133,9 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||
// Whether to remove blank text nodes from compiled templates. It is `true` by default
|
||||
// in Angular 5 and will be re-visited in Angular 6.
|
||||
preserveWhitespaces?: boolean;
|
||||
|
||||
/** generate all possible generated files */
|
||||
allowEmptyCodegenFiles?: boolean;
|
||||
}
|
||||
|
||||
export interface CompilerHost extends ts.CompilerHost {
|
||||
@ -203,6 +206,12 @@ export interface TsEmitArguments {
|
||||
|
||||
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
|
||||
|
||||
export interface LibrarySummary {
|
||||
fileName: string;
|
||||
text: string;
|
||||
sourceFile?: ts.SourceFile;
|
||||
}
|
||||
|
||||
export interface Program {
|
||||
/**
|
||||
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources.
|
||||
@ -280,8 +289,9 @@ export interface Program {
|
||||
}): ts.EmitResult;
|
||||
|
||||
/**
|
||||
* Returns the .ngsummary.json files of libraries that have been compiled
|
||||
* in this program or previous programs.
|
||||
* Returns the .d.ts / .ngsummary.json / .ngfactory.d.ts files of libraries that have been emitted
|
||||
* in this program or previous programs with paths that emulate the fact that these libraries
|
||||
* have been compiled before with no outDir.
|
||||
*/
|
||||
getLibrarySummaries(): {fileName: string, content: string}[];
|
||||
getLibrarySummaries(): LibrarySummary[];
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {BaseAotCompilerHost} from '../compiler_host';
|
||||
import {TypeCheckHost} from '../diagnostics/translate_diagnostics';
|
||||
import {ModuleMetadata} from '../metadata/index';
|
||||
|
||||
import {CompilerHost, CompilerOptions} from './api';
|
||||
import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
|
||||
import {GENERATED_FILES} from './util';
|
||||
|
||||
const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/;
|
||||
@ -37,6 +37,11 @@ interface GenSourceFile {
|
||||
emitCtx: EmitterVisitorContext;
|
||||
}
|
||||
|
||||
export interface CodeGenerator {
|
||||
generateFile(genFileName: string, baseFileName?: string): GeneratedFile;
|
||||
findGeneratedFileNames(fileName: string): string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the following hosts based on an api.CompilerHost:
|
||||
* - ts.CompilerHost to be consumed by a ts.Program
|
||||
@ -48,11 +53,12 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
TypeCheckHost {
|
||||
private rootDirs: string[];
|
||||
private moduleResolutionCache: ts.ModuleResolutionCache;
|
||||
private originalSourceFiles = new Map<string, ts.SourceFile>();
|
||||
private originalSourceFiles = new Map<string, ts.SourceFile|undefined>();
|
||||
private originalFileExistsCache = new Map<string, boolean>();
|
||||
private generatedSourceFiles = new Map<string, GenSourceFile>();
|
||||
private generatedCodeFor = new Set<string>();
|
||||
private generatedCodeFor = new Map<string, string[]>();
|
||||
private emitter = new TypeScriptEmitter();
|
||||
private librarySummaries = new Map<string, LibrarySummary>();
|
||||
getCancellationToken: () => ts.CancellationToken;
|
||||
getDefaultLibLocation: () => string;
|
||||
trace: (s: string) => void;
|
||||
@ -61,10 +67,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
|
||||
constructor(
|
||||
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
|
||||
private metadataProvider: MetadataProvider,
|
||||
private codeGenerator: (fileName: string) => GeneratedFile[],
|
||||
private summariesFromPreviousCompilations: Map<string, string>) {
|
||||
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
|
||||
librarySummaries: LibrarySummary[]) {
|
||||
super(options, context);
|
||||
librarySummaries.forEach(summary => this.librarySummaries.set(summary.fileName, summary));
|
||||
this.moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
|
||||
const basePath = this.options.basePath !;
|
||||
@ -168,6 +174,11 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
'fileNameToModuleName from containingFile', containingFile, 'to importedFile',
|
||||
importedFile);
|
||||
}
|
||||
const importAs = this.getImportAs(importedFile);
|
||||
if (importAs) {
|
||||
return importAs;
|
||||
}
|
||||
|
||||
// drop extension
|
||||
importedFile = importedFile.replace(EXT, '');
|
||||
const importedFilePackagName = getPackageName(importedFile);
|
||||
@ -229,15 +240,18 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
|
||||
private getOriginalSourceFile(
|
||||
filePath: string, languageVersion?: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined): ts.SourceFile|undefined {
|
||||
let sf = this.originalSourceFiles.get(filePath);
|
||||
if (sf) {
|
||||
return sf;
|
||||
onError?: ((message: string) => void)|undefined): ts.SourceFile|null {
|
||||
// Note: we need the explicit check via `has` as we also cache results
|
||||
// that were null / undefined.
|
||||
if (this.originalSourceFiles.has(filePath)) {
|
||||
return this.originalSourceFiles.get(filePath) !;
|
||||
}
|
||||
if (!languageVersion) {
|
||||
languageVersion = this.options.target || ts.ScriptTarget.Latest;
|
||||
}
|
||||
sf = this.context.getSourceFile(filePath, languageVersion, onError);
|
||||
// Note: This can also return undefined,
|
||||
// as the TS typings are not correct!
|
||||
const sf = this.context.getSourceFile(filePath, languageVersion, onError) || null;
|
||||
this.originalSourceFiles.set(filePath, sf);
|
||||
return sf;
|
||||
}
|
||||
@ -250,9 +264,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
return this.metadataProvider.getMetadata(sf);
|
||||
}
|
||||
|
||||
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile|null {
|
||||
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile {
|
||||
if (!genFile.stmts) {
|
||||
return null;
|
||||
throw new Error(
|
||||
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
|
||||
}
|
||||
const oldGenFile = this.generatedSourceFiles.get(genFile.genFileUrl);
|
||||
if (!oldGenFile) {
|
||||
@ -271,10 +286,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
return this.addGeneratedFile(genFile, newRefs);
|
||||
}
|
||||
|
||||
private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile
|
||||
|null {
|
||||
private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile {
|
||||
if (!genFile.stmts) {
|
||||
return null;
|
||||
throw new Error(
|
||||
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
|
||||
}
|
||||
const {sourceText, context} = this.emitter.emitStatementsAndContext(
|
||||
genFile.srcFileUrl, genFile.genFileUrl, genFile.stmts, /* preamble */ '',
|
||||
@ -288,49 +303,95 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
return sf;
|
||||
}
|
||||
|
||||
private ensureCodeGeneratedFor(fileName: string): void {
|
||||
if (this.generatedCodeFor.has(fileName)) {
|
||||
return;
|
||||
shouldGenerateFile(fileName: string): {generate: boolean, baseFileName?: string} {
|
||||
// TODO(tbosch): allow generating files that are not in the rootDir
|
||||
// See https://github.com/angular/angular/issues/19337
|
||||
if (this.options.rootDir && !fileName.startsWith(this.options.rootDir)) {
|
||||
return {generate: false};
|
||||
}
|
||||
this.generatedCodeFor.add(fileName);
|
||||
const genMatch = GENERATED_FILES.exec(fileName);
|
||||
if (!genMatch) {
|
||||
return {generate: false};
|
||||
}
|
||||
const [, base, genSuffix, suffix] = genMatch;
|
||||
if (suffix !== 'ts') {
|
||||
return {generate: false};
|
||||
}
|
||||
let baseFileName: string|undefined;
|
||||
if (genSuffix.indexOf('ngstyle') >= 0) {
|
||||
// Note: ngstyle files have names like `afile.css.ngstyle.ts`
|
||||
if (!this.originalFileExists(base)) {
|
||||
return {generate: false};
|
||||
}
|
||||
} else {
|
||||
// Note: on-the-fly generated files always have a `.ts` suffix,
|
||||
// but the file from which we generated it can be a `.ts`/ `.d.ts`
|
||||
// (see options.generateCodeForLibraries).
|
||||
baseFileName = [`${base}.ts`, `${base}.d.ts`].find(
|
||||
baseFileName => this.isSourceFile(baseFileName) && this.originalFileExists(baseFileName));
|
||||
if (!baseFileName) {
|
||||
return {generate: false};
|
||||
}
|
||||
}
|
||||
return {generate: true, baseFileName};
|
||||
}
|
||||
|
||||
const baseNameFromGeneratedFile = this._getBaseNameForGeneratedSourceFile(fileName);
|
||||
if (baseNameFromGeneratedFile) {
|
||||
return this.ensureCodeGeneratedFor(baseNameFromGeneratedFile);
|
||||
}
|
||||
const sf = this.getOriginalSourceFile(fileName, this.options.target || ts.ScriptTarget.Latest);
|
||||
if (!sf) {
|
||||
return;
|
||||
}
|
||||
|
||||
const genFileNames: string[] = [];
|
||||
if (this.isSourceFile(fileName)) {
|
||||
// Note: we can't exit early here,
|
||||
// as we might need to clear out old changes to `SourceFile.referencedFiles`
|
||||
// that were created by a previous run, given an original CompilerHost
|
||||
// that caches source files.
|
||||
const genFiles = this.codeGenerator(fileName);
|
||||
genFiles.forEach(genFile => {
|
||||
const sf = this.addGeneratedFile(genFile, genFileExternalReferences(genFile));
|
||||
if (sf) {
|
||||
genFileNames.push(sf.fileName);
|
||||
}
|
||||
});
|
||||
}
|
||||
addReferencesToSourceFile(sf, genFileNames);
|
||||
shouldGenerateFilesFor(fileName: string) {
|
||||
// TODO(tbosch): allow generating files that are not in the rootDir
|
||||
// See https://github.com/angular/angular/issues/19337
|
||||
return !GENERATED_FILES.test(fileName) && this.isSourceFile(fileName) &&
|
||||
(!this.options.rootDir || pathStartsWithPrefix(this.options.rootDir, fileName));
|
||||
}
|
||||
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined): ts.SourceFile {
|
||||
this.ensureCodeGeneratedFor(fileName);
|
||||
const genFile = this.generatedSourceFiles.get(fileName);
|
||||
if (genFile) {
|
||||
return genFile.sourceFile;
|
||||
// Note: Don't exit early in this method to make sure
|
||||
// we always have up to date references on the file!
|
||||
let genFileNames: string[] = [];
|
||||
let sf = this.getGeneratedFile(fileName);
|
||||
if (!sf) {
|
||||
const summary = this.librarySummaries.get(fileName);
|
||||
if (summary) {
|
||||
if (!summary.sourceFile) {
|
||||
summary.sourceFile = ts.createSourceFile(
|
||||
fileName, summary.text, this.options.target || ts.ScriptTarget.Latest);
|
||||
}
|
||||
sf = summary.sourceFile;
|
||||
genFileNames = [];
|
||||
}
|
||||
}
|
||||
if (!sf) {
|
||||
sf = this.getOriginalSourceFile(fileName);
|
||||
const cachedGenFiles = this.generatedCodeFor.get(fileName);
|
||||
if (cachedGenFiles) {
|
||||
genFileNames = cachedGenFiles;
|
||||
} else {
|
||||
if (!this.options.noResolve && this.shouldGenerateFilesFor(fileName)) {
|
||||
genFileNames = this.codeGenerator.findGeneratedFileNames(fileName);
|
||||
}
|
||||
this.generatedCodeFor.set(fileName, genFileNames);
|
||||
}
|
||||
}
|
||||
if (sf) {
|
||||
addReferencesToSourceFile(sf, genFileNames);
|
||||
}
|
||||
// TODO(tbosch): TypeScript's typings for getSourceFile are incorrect,
|
||||
// as it can very well return undefined.
|
||||
return this.getOriginalSourceFile(fileName, languageVersion, onError) !;
|
||||
return sf !;
|
||||
}
|
||||
|
||||
private getGeneratedFile(fileName: string): ts.SourceFile|null {
|
||||
const genSrcFile = this.generatedSourceFiles.get(fileName);
|
||||
if (genSrcFile) {
|
||||
return genSrcFile.sourceFile;
|
||||
}
|
||||
const {generate, baseFileName} = this.shouldGenerateFile(fileName);
|
||||
if (generate) {
|
||||
const genFile = this.codeGenerator.generateFile(fileName, baseFileName);
|
||||
return this.addGeneratedFile(genFile, genFileExternalReferences(genFile));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private originalFileExists(fileName: string): boolean {
|
||||
@ -344,48 +405,19 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
fileName = stripNgResourceSuffix(fileName);
|
||||
if (fileName.endsWith('.ngfactory.d.ts')) {
|
||||
// Note: the factories of a previous program
|
||||
// are not reachable via the regular fileExists
|
||||
// as they might be in the outDir. So we derive their
|
||||
// fileExist information based on the .ngsummary.json file.
|
||||
if (this.summariesFromPreviousCompilations.has(summaryFileName(fileName))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Note: Don't rely on this.generatedSourceFiles here,
|
||||
// as it might not have been filled yet.
|
||||
if (this._getBaseNameForGeneratedSourceFile(fileName)) {
|
||||
if (this.librarySummaries.has(fileName) || this.generatedSourceFiles.has(fileName)) {
|
||||
return true;
|
||||
}
|
||||
return this.summariesFromPreviousCompilations.has(fileName) ||
|
||||
this.originalFileExists(fileName);
|
||||
}
|
||||
|
||||
private _getBaseNameForGeneratedSourceFile(genFileName: string): string|undefined {
|
||||
const genMatch = GENERATED_FILES.exec(genFileName);
|
||||
if (!genMatch) {
|
||||
return undefined;
|
||||
}
|
||||
const [, base, genSuffix, suffix] = genMatch;
|
||||
if (suffix !== 'ts') {
|
||||
return undefined;
|
||||
}
|
||||
if (genSuffix.indexOf('ngstyle') >= 0) {
|
||||
// Note: ngstyle files have names like `afile.css.ngstyle.ts`
|
||||
return base;
|
||||
} else {
|
||||
// Note: on-the-fly generated files always have a `.ts` suffix,
|
||||
// but the file from which we generated it can be a `.ts`/ `.d.ts`
|
||||
// (see options.generateCodeForLibraries).
|
||||
return [`${base}.ts`, `${base}.d.ts`].find(
|
||||
baseFileName => this.isSourceFile(baseFileName) && this.originalFileExists(baseFileName));
|
||||
if (this.shouldGenerateFile(fileName).generate) {
|
||||
return true;
|
||||
}
|
||||
return this.originalFileExists(fileName);
|
||||
}
|
||||
|
||||
loadSummary(filePath: string): string|null {
|
||||
if (this.summariesFromPreviousCompilations.has(filePath)) {
|
||||
return this.summariesFromPreviousCompilations.get(filePath) !;
|
||||
const summary = this.librarySummaries.get(filePath);
|
||||
if (summary) {
|
||||
return summary.text;
|
||||
}
|
||||
return super.loadSummary(filePath);
|
||||
}
|
||||
@ -393,13 +425,19 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
isSourceFile(filePath: string): boolean {
|
||||
// If we have a summary from a previous compilation,
|
||||
// treat the file never as a source file.
|
||||
if (this.summariesFromPreviousCompilations.has(summaryFileName(filePath))) {
|
||||
if (this.librarySummaries.has(filePath)) {
|
||||
return false;
|
||||
}
|
||||
return super.isSourceFile(filePath);
|
||||
}
|
||||
|
||||
readFile = (fileName: string) => this.context.readFile(fileName);
|
||||
readFile(fileName: string) {
|
||||
const summary = this.librarySummaries.get(fileName);
|
||||
if (summary) {
|
||||
return summary.text;
|
||||
}
|
||||
return this.context.readFile(fileName);
|
||||
}
|
||||
getDefaultLibFileName = (options: ts.CompilerOptions) =>
|
||||
this.context.getDefaultLibFileName(options)
|
||||
getCurrentDirectory = () => this.context.getCurrentDirectory();
|
||||
@ -451,12 +489,19 @@ function getPackageName(filePath: string): string|null {
|
||||
export function relativeToRootDirs(filePath: string, rootDirs: string[]): string {
|
||||
if (!filePath) return filePath;
|
||||
for (const dir of rootDirs || []) {
|
||||
const rel = path.relative(dir, filePath);
|
||||
if (rel.indexOf('.') != 0) return rel;
|
||||
const rel = pathStartsWithPrefix(dir, filePath);
|
||||
if (rel) {
|
||||
return rel;
|
||||
}
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function pathStartsWithPrefix(prefix: string, fullPath: string): string|null {
|
||||
const rel = path.relative(prefix, fullPath);
|
||||
return rel.startsWith('..') ? null : rel;
|
||||
}
|
||||
|
||||
function stripNodeModulesPrefix(filePath: string): string {
|
||||
return filePath.replace(/.*node_modules\//, '');
|
||||
}
|
||||
@ -473,12 +518,3 @@ function stripNgResourceSuffix(fileName: string): string {
|
||||
function addNgResourceSuffix(fileName: string): string {
|
||||
return `${fileName}.$ngresource$`;
|
||||
}
|
||||
|
||||
function summaryFileName(fileName: string): string {
|
||||
const genFileMatch = GENERATED_FILES.exec(fileName);
|
||||
if (genFileMatch) {
|
||||
const base = genFileMatch[1];
|
||||
return base + '.ngsummary.json';
|
||||
}
|
||||
return fileName.replace(EXT, '') + '.ngsummary.json';
|
||||
}
|
@ -14,8 +14,8 @@ import * as ts from 'typescript';
|
||||
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
|
||||
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from './util';
|
||||
@ -35,10 +35,10 @@ const defaultEmitCallback: TsEmitCallback =
|
||||
|
||||
class AngularCompilerProgram implements Program {
|
||||
private metadataCache: LowerMetadataCache;
|
||||
private summariesFromPreviousCompilations = new Map<string, string>();
|
||||
private oldProgramLibrarySummaries: LibrarySummary[] = [];
|
||||
// Note: This will be cleared out as soon as we create the _tsProgram
|
||||
private oldTsProgram: ts.Program|undefined;
|
||||
private _emittedGenFiles: GeneratedFile[]|undefined;
|
||||
private emittedLibrarySummaries: LibrarySummary[]|undefined;
|
||||
|
||||
// Lazily initialized fields
|
||||
private _typeCheckHost: TypeCheckHost;
|
||||
@ -59,8 +59,7 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
||||
if (oldProgram) {
|
||||
oldProgram.getLibrarySummaries().forEach(
|
||||
({content, fileName}) => this.summariesFromPreviousCompilations.set(fileName, content));
|
||||
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
|
||||
}
|
||||
|
||||
if (options.flatModuleOutFile) {
|
||||
@ -82,21 +81,12 @@ class AngularCompilerProgram implements Program {
|
||||
this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
|
||||
}
|
||||
|
||||
getLibrarySummaries(): {fileName: string, content: string}[] {
|
||||
const emittedLibSummaries: {fileName: string, content: string}[] = [];
|
||||
this.summariesFromPreviousCompilations.forEach(
|
||||
(content, fileName) => emittedLibSummaries.push({fileName, content}));
|
||||
if (this._emittedGenFiles) {
|
||||
this._emittedGenFiles.forEach(genFile => {
|
||||
if (genFile.srcFileUrl.endsWith('.d.ts') &&
|
||||
genFile.genFileUrl.endsWith('.ngsummary.json')) {
|
||||
// Note: ! is ok here as ngsummary.json files are always plain text, so genFile.source
|
||||
// is filled.
|
||||
emittedLibSummaries.push({fileName: genFile.genFileUrl, content: genFile.source !});
|
||||
}
|
||||
});
|
||||
getLibrarySummaries(): LibrarySummary[] {
|
||||
const result = [...this.oldProgramLibrarySummaries];
|
||||
if (this.emittedLibrarySummaries) {
|
||||
result.push(...this.emittedLibrarySummaries);
|
||||
}
|
||||
return emittedLibSummaries;
|
||||
return result;
|
||||
}
|
||||
|
||||
getTsProgram(): ts.Program { return this.tsProgram; }
|
||||
@ -132,8 +122,8 @@ class AngularCompilerProgram implements Program {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error('Angular structure already loaded');
|
||||
}
|
||||
const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
|
||||
return this._compiler.loadFilesAsync(analyzedFiles)
|
||||
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
|
||||
return this._compiler.loadFilesAsync(sourceFiles)
|
||||
.catch(this.catchAnalysisError.bind(this))
|
||||
.then(analyzedModules => {
|
||||
if (this._analyzedModules) {
|
||||
@ -159,7 +149,6 @@ class AngularCompilerProgram implements Program {
|
||||
const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale);
|
||||
i18nExtract(format, file, this.host, this.options, bundle);
|
||||
}
|
||||
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
|
||||
if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) ===
|
||||
0) {
|
||||
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
|
||||
@ -172,6 +161,21 @@ class AngularCompilerProgram implements Program {
|
||||
emittedFiles: [],
|
||||
};
|
||||
}
|
||||
const emittedLibrarySummaries = this.emittedLibrarySummaries = [];
|
||||
|
||||
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
|
||||
const genFileByFileName = new Map<string, GeneratedFile>();
|
||||
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
|
||||
const writeTsFile: ts.WriteFileCallback =
|
||||
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
||||
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
||||
let genFile: GeneratedFile|undefined;
|
||||
if (sourceFile) {
|
||||
outSrcMapping.push({outFileName: outFileName, sourceFile});
|
||||
genFile = genFileByFileName.get(sourceFile.fileName);
|
||||
}
|
||||
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
|
||||
};
|
||||
|
||||
// Restore the original references before we emit so TypeScript doesn't emit
|
||||
// a reference to the .d.ts file.
|
||||
@ -183,14 +187,13 @@ class AngularCompilerProgram implements Program {
|
||||
sourceFile.referencedFiles = originalReferences;
|
||||
}
|
||||
}
|
||||
|
||||
let emitResult: ts.EmitResult;
|
||||
try {
|
||||
emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
writeFile: createWriteFileCallback(genFiles, this.host, outSrcMapping),
|
||||
writeFile: writeTsFile,
|
||||
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
|
||||
customTransformers: this.calculateTransforms(genFiles, customTransformers)
|
||||
});
|
||||
@ -211,7 +214,8 @@ class AngularCompilerProgram implements Program {
|
||||
if (emitFlags & EmitFlags.Codegen) {
|
||||
genFiles.forEach(gf => {
|
||||
if (gf.source) {
|
||||
this.host.writeFile(srcToOutPath(gf.genFileUrl), gf.source, false);
|
||||
const outFileName = srcToOutPath(gf.genFileUrl);
|
||||
this.writeFile(outFileName, gf.source, false, undefined, gf);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -220,8 +224,8 @@ class AngularCompilerProgram implements Program {
|
||||
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
|
||||
const metadata = this.metadataCache.getMetadata(sf);
|
||||
const metadataText = JSON.stringify([metadata]);
|
||||
this.host.writeFile(
|
||||
srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json')), metadataText, false);
|
||||
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
|
||||
this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -310,10 +314,10 @@ class AngularCompilerProgram implements Program {
|
||||
if (this._analyzedModules) {
|
||||
return;
|
||||
}
|
||||
const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
|
||||
let analyzedModules: NgAnalyzedModules;
|
||||
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
|
||||
let analyzedModules: NgAnalyzedModules|null;
|
||||
try {
|
||||
analyzedModules = this._compiler.loadFilesSync(analyzedFiles);
|
||||
analyzedModules = this._compiler.loadFilesSync(sourceFiles);
|
||||
} catch (e) {
|
||||
analyzedModules = this.catchAnalysisError(e);
|
||||
}
|
||||
@ -322,9 +326,9 @@ class AngularCompilerProgram implements Program {
|
||||
|
||||
private _createProgramWithBasicStubs(): {
|
||||
tmpProgram: ts.Program,
|
||||
analyzedFiles: NgAnalyzedFile[],
|
||||
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter,
|
||||
rootNames: string[],
|
||||
sourceFiles: string[],
|
||||
} {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error(`Internal Error: already initalized!`);
|
||||
@ -332,19 +336,16 @@ class AngularCompilerProgram implements Program {
|
||||
// Note: This is important to not produce a memory leak!
|
||||
const oldTsProgram = this.oldTsProgram;
|
||||
this.oldTsProgram = undefined;
|
||||
const analyzedFiles: NgAnalyzedFile[] = [];
|
||||
const codegen = (fileName: string) => {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error(`Internal Error: already initalized!`);
|
||||
}
|
||||
const analyzedFile = this._compiler.analyzeFile(fileName);
|
||||
analyzedFiles.push(analyzedFile);
|
||||
const debug = fileName.endsWith('application_ref.ts');
|
||||
return this._compiler.emitBasicStubs(analyzedFile);
|
||||
|
||||
const codegen: CodeGenerator = {
|
||||
generateFile: (genFileName, baseFileName) =>
|
||||
this._compiler.emitBasicStub(genFileName, baseFileName),
|
||||
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
|
||||
};
|
||||
|
||||
const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
|
||||
this.rootNames, this.options, this.host, this.metadataCache, codegen,
|
||||
this.summariesFromPreviousCompilations);
|
||||
this.oldProgramLibrarySummaries);
|
||||
const aotOptions = getAotCompilerOptions(this.options);
|
||||
this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler;
|
||||
this._typeCheckHost = hostAdapter;
|
||||
@ -354,26 +355,41 @@ class AngularCompilerProgram implements Program {
|
||||
this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn));
|
||||
if (this.options.noResolve) {
|
||||
this.rootNames.forEach(rootName => {
|
||||
const sf =
|
||||
hostAdapter.getSourceFile(rootName, this.options.target || ts.ScriptTarget.Latest);
|
||||
sf.referencedFiles.forEach((fileRef) => {
|
||||
if (GENERATED_FILES.test(fileRef.fileName)) {
|
||||
rootNames.push(fileRef.fileName);
|
||||
}
|
||||
});
|
||||
if (hostAdapter.shouldGenerateFilesFor(rootName)) {
|
||||
rootNames.push(...this._compiler.findGeneratedFileNames(rootName));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram);
|
||||
return {tmpProgram, analyzedFiles, hostAdapter, rootNames};
|
||||
const sourceFiles: string[] = [];
|
||||
tmpProgram.getSourceFiles().forEach(sf => {
|
||||
if (hostAdapter.isSourceFile(sf.fileName)) {
|
||||
sourceFiles.push(sf.fileName);
|
||||
}
|
||||
});
|
||||
return {tmpProgram, sourceFiles, hostAdapter, rootNames};
|
||||
}
|
||||
|
||||
private _updateProgramWithTypeCheckStubs(
|
||||
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules,
|
||||
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null,
|
||||
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) {
|
||||
this._analyzedModules = analyzedModules;
|
||||
const genFiles = this._compiler.emitTypeCheckStubs(analyzedModules);
|
||||
genFiles.forEach(gf => hostAdapter.updateGeneratedFile(gf));
|
||||
this._analyzedModules = analyzedModules || emptyModules;
|
||||
if (analyzedModules) {
|
||||
tmpProgram.getSourceFiles().forEach(sf => {
|
||||
if (sf.fileName.endsWith('.ngfactory.ts')) {
|
||||
const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName);
|
||||
if (generate) {
|
||||
// Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName
|
||||
// for .ngfactory.ts files.
|
||||
const genFile = this._compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
|
||||
if (genFile) {
|
||||
hostAdapter.updateGeneratedFile(genFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram);
|
||||
// Note: the new ts program should be completely reusable by TypeScript as:
|
||||
// - we cache all the files in the hostAdapter
|
||||
@ -384,7 +400,7 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
}
|
||||
|
||||
private catchAnalysisError(e: any): NgAnalyzedModules {
|
||||
private catchAnalysisError(e: any): NgAnalyzedModules|null {
|
||||
if (isSyntaxError(e)) {
|
||||
const parserErrors = getParseErrors(e);
|
||||
if (parserErrors && parserErrors.length) {
|
||||
@ -404,7 +420,7 @@ class AngularCompilerProgram implements Program {
|
||||
code: DEFAULT_ERROR_CODE
|
||||
}];
|
||||
}
|
||||
return emptyModules;
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
@ -417,7 +433,7 @@ class AngularCompilerProgram implements Program {
|
||||
if (!(emitFlags & EmitFlags.Codegen)) {
|
||||
return {genFiles: [], genDiags: []};
|
||||
}
|
||||
const genFiles = this._emittedGenFiles = this.compiler.emitAllImpls(this.analyzedModules);
|
||||
const genFiles = this.compiler.emitAllImpls(this.analyzedModules);
|
||||
return {genFiles, genDiags: []};
|
||||
} catch (e) {
|
||||
// TODO(tbosch): check whether we can actually have syntax errors here,
|
||||
@ -441,6 +457,51 @@ class AngularCompilerProgram implements Program {
|
||||
private generateSemanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} {
|
||||
return translateDiagnostics(this.typeCheckHost, this.tsProgram.getSemanticDiagnostics());
|
||||
}
|
||||
|
||||
private writeFile(
|
||||
outFileName: string, outData: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, genFile?: GeneratedFile, sourceFiles?: ts.SourceFile[]) {
|
||||
// collect emittedLibrarySummaries
|
||||
let baseFile: ts.SourceFile|undefined;
|
||||
if (genFile) {
|
||||
baseFile = this.tsProgram.getSourceFile(genFile.srcFileUrl);
|
||||
if (baseFile) {
|
||||
if (!this.emittedLibrarySummaries) {
|
||||
this.emittedLibrarySummaries = [];
|
||||
}
|
||||
if (genFile.genFileUrl.endsWith('.ngsummary.json') && baseFile.fileName.endsWith('.d.ts')) {
|
||||
this.emittedLibrarySummaries.push({
|
||||
fileName: baseFile.fileName,
|
||||
text: baseFile.text,
|
||||
sourceFile: baseFile,
|
||||
});
|
||||
this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData});
|
||||
} else if (outFileName.endsWith('.d.ts') && baseFile.fileName.endsWith('.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,
|
||||
// but we need one that is next to the .ts file
|
||||
this.emittedLibrarySummaries.push({fileName: dtsSourceFilePath, text: outData});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filter out generated files for which we didn't generate code.
|
||||
// This can happen as the stub caclulation is not completely exact.
|
||||
// Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file
|
||||
const isGenerated = GENERATED_FILES.test(outFileName);
|
||||
if (isGenerated) {
|
||||
if (!genFile || !genFile.stmts || genFile.stmts.length === 0) {
|
||||
if (this.options.allowEmptyCodegenFiles) {
|
||||
outData = '';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baseFile) {
|
||||
sourceFiles = sourceFiles ? [...sourceFiles, baseFile] : [baseFile];
|
||||
}
|
||||
this.host.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles);
|
||||
}
|
||||
}
|
||||
|
||||
export function createProgram(
|
||||
@ -483,31 +544,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
|
||||
enableSummariesForJit: true,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
||||
rootDir: options.rootDir,
|
||||
};
|
||||
}
|
||||
|
||||
function createWriteFileCallback(
|
||||
generatedFiles: GeneratedFile[], host: ts.CompilerHost,
|
||||
outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}>) {
|
||||
const genFileByFileName = new Map<string, GeneratedFile>();
|
||||
generatedFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
|
||||
return (fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
||||
if (sourceFile) {
|
||||
outSrcMapping.push({outFileName: fileName, sourceFile});
|
||||
}
|
||||
const isGenerated = GENERATED_FILES.test(fileName);
|
||||
if (isGenerated && sourceFile) {
|
||||
// Filter out generated files for which we didn't generate code.
|
||||
// This can happen as the stub caclulation is not completely exact.
|
||||
const genFile = genFileByFileName.get(sourceFile.fileName);
|
||||
if (!genFile || !genFile.stmts || genFile.stmts.length === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
|
||||
};
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user