fix(language-service): Recover from error in analyzing Ng Modules (#37108)

In place of failing to return analyzed Ng Modules when the analyzer
fails, return the previously-analyzed Ng Modules (which may be empty)
and log an error.

Closes https://github.com/angular/vscode-ng-language-service/issues/777

PR Close #37108
This commit is contained in:
Ayaz Hafiz 2020-05-14 08:35:38 -07:00 committed by atscott
parent 34827559b9
commit c4f4675ebf
3 changed files with 55 additions and 2 deletions

View File

@ -175,8 +175,15 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
}
};
const programFiles = this.program.getSourceFiles().map(sf => sf.fileName);
this.analyzedModules =
analyzeNgModules(programFiles, analyzeHost, this.staticSymbolResolver, this.resolver);
try {
this.analyzedModules =
analyzeNgModules(programFiles, analyzeHost, this.staticSymbolResolver, this.resolver);
} catch (e) {
// Analyzing modules may throw; in that case, reuse the old modules.
this.error(`Analyzing NgModules failed. ${e}`);
return this.analyzedModules;
}
// update template references and fileToComponent
const urlResolver = createOfflineCompileUrlResolver();

View File

@ -101,6 +101,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
private readonly overrideDirectory = new Set<string>();
private readonly existsCache = new Map<string, boolean>();
private readonly fileCache = new Map<string, string|undefined>();
errors: string[] = [];
constructor(
private readonly scriptNames: string[],
@ -398,6 +399,10 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
}
throw new Error(`Failed to find marker '${selector}' in ${fileName}`);
}
error(msg: string) {
this.errors.push(msg);
}
}
const locationMarker = /\~\{(\w+(-\w+)*)\}/g;

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as ngc from '@angular/compiler';
import * as ts from 'typescript';
import {TypeScriptServiceHost} from '../src/typescript_host';
@ -216,4 +217,44 @@ describe('TypeScriptServiceHost', () => {
// But the content should be exactly the same
expect(newModules).toEqual(oldModules);
});
it('should recover from error in analyzing ng modules', () => {
// First create a TypescriptHost with empty script names
const tsLSHost = new MockTypescriptHost([]);
const tsLS = ts.createLanguageService(tsLSHost);
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
const oldModules = ngLSHost.getAnalyzedModules();
expect(oldModules.ngModules).toEqual([]);
// Now add a script, this would change the program
let fileName = '/app/main.ts';
let content = `
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
@NgModule({
entryComponents: [CommonModule],
})
export class AppModule {}
`;
tsLSHost.addScript(fileName, content);
// If analyzing modules throws, the old modules should be returned.
let newModules = ngLSHost.getAnalyzedModules();
expect(newModules.ngModules).toEqual([]);
expect(tsLSHost.errors).toEqual([
'Analyzing NgModules failed. Error: CommonModule cannot be used as an entry component.'
]);
content = `
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
@NgModule({})
export class AppModule {}
`;
tsLSHost.override(fileName, content);
// Check that analyzing modules successfully still works.
newModules = ngLSHost.getAnalyzedModules();
expect(newModules.ngModules.length).toBeGreaterThan(0);
});
});