fix(compiler): recover from structural errors in watch mode (#19953)
This also changes the compiler so that we throw less often on structural changes and produce a meaningful state in the `ng.Program` in case of errors. Related to #19951 PR Close #19953
This commit is contained in:

committed by
Matias Niemelä

parent
9ce7f0e538
commit
d343bf7885
@ -191,6 +191,10 @@ export function performWatchCompilation(host: PerformWatchHost):
|
||||
};
|
||||
}
|
||||
ingoreFilesForWatch.clear();
|
||||
const oldProgram = cachedProgram;
|
||||
// We clear out the `cachedProgram` here as a
|
||||
// program can only be used as `oldProgram` 1x
|
||||
cachedProgram = undefined;
|
||||
const compileResult = performCompilation({
|
||||
rootNames: cachedOptions.rootNames,
|
||||
options: cachedOptions.options,
|
||||
|
@ -175,15 +175,17 @@ class AngularCompilerProgram implements Program {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error('Angular structure already loaded');
|
||||
}
|
||||
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
|
||||
return this.compiler.loadFilesAsync(sourceFiles)
|
||||
.catch(this.catchAnalysisError.bind(this))
|
||||
.then(analyzedModules => {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error('Angular structure loaded both synchronously and asynchronsly');
|
||||
}
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
|
||||
});
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
|
||||
return this.compiler.loadFilesAsync(sourceFiles).then(analyzedModules => {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error('Angular structure loaded both synchronously and asynchronsly');
|
||||
}
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
|
||||
});
|
||||
})
|
||||
.catch(e => this._createProgramOnError(e));
|
||||
}
|
||||
|
||||
listLazyRoutes(route?: string): LazyRoute[] {
|
||||
@ -402,14 +404,13 @@ class AngularCompilerProgram implements Program {
|
||||
if (this._analyzedModules) {
|
||||
return;
|
||||
}
|
||||
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
|
||||
let analyzedModules: NgAnalyzedModules|null;
|
||||
try {
|
||||
analyzedModules = this.compiler.loadFilesSync(sourceFiles);
|
||||
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
|
||||
const analyzedModules = this.compiler.loadFilesSync(sourceFiles);
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
|
||||
} catch (e) {
|
||||
analyzedModules = this.catchAnalysisError(e);
|
||||
this._createProgramOnError(e);
|
||||
}
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
|
||||
}
|
||||
|
||||
private _createCompiler() {
|
||||
@ -457,7 +458,7 @@ class AngularCompilerProgram implements Program {
|
||||
|
||||
let rootNames = this.rootNames;
|
||||
if (this.options.generateCodeForLibraries !== false) {
|
||||
// if we should generateCodeForLibraries, enver include
|
||||
// if we should generateCodeForLibraries, never include
|
||||
// generated files in the program as otherwise we will
|
||||
// ovewrite them and typescript will report the error
|
||||
// TS5055: Cannot write file ... because it would overwrite input file.
|
||||
@ -482,23 +483,21 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
|
||||
private _updateProgramWithTypeCheckStubs(
|
||||
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null, rootNames: string[]) {
|
||||
this._analyzedModules = analyzedModules || emptyModules;
|
||||
if (analyzedModules) {
|
||||
tmpProgram.getSourceFiles().forEach(sf => {
|
||||
if (sf.fileName.endsWith('.ngfactory.ts')) {
|
||||
const {generate, baseFileName} = this.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) {
|
||||
this.hostAdapter.updateGeneratedFile(genFile);
|
||||
}
|
||||
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules, rootNames: string[]) {
|
||||
this._analyzedModules = analyzedModules;
|
||||
tmpProgram.getSourceFiles().forEach(sf => {
|
||||
if (sf.fileName.endsWith('.ngfactory.ts')) {
|
||||
const {generate, baseFileName} = this.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) {
|
||||
this.hostAdapter.updateGeneratedFile(genFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
this._tsProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, tmpProgram);
|
||||
// Note: the new ts program should be completely reusable by TypeScript as:
|
||||
// - we cache all the files in the hostAdapter
|
||||
@ -509,7 +508,13 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
}
|
||||
|
||||
private catchAnalysisError(e: any): NgAnalyzedModules|null {
|
||||
private _createProgramOnError(e: any) {
|
||||
// Still fill the analyzedModules and the tsProgram
|
||||
// so that we don't cause other errors for users who e.g. want to emit the ngProgram.
|
||||
this._analyzedModules = emptyModules;
|
||||
this.oldTsProgram = undefined;
|
||||
this._hostAdapter.isSourceFile = () => false;
|
||||
this._tsProgram = ts.createProgram(this.rootNames, this.options, this.hostAdapter);
|
||||
if (isSyntaxError(e)) {
|
||||
const parserErrors = getParseErrors(e);
|
||||
if (parserErrors && parserErrors.length) {
|
||||
@ -533,7 +538,7 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
];
|
||||
}
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
Reference in New Issue
Block a user