diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index 55787d0a48..948971f817 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -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(); diff --git a/packages/language-service/test/test_utils.ts b/packages/language-service/test/test_utils.ts index 611e66a2f5..ee3c3d7eee 100644 --- a/packages/language-service/test/test_utils.ts +++ b/packages/language-service/test/test_utils.ts @@ -101,6 +101,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { private readonly overrideDirectory = new Set(); private readonly existsCache = new Map(); private readonly fileCache = new Map(); + 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; diff --git a/packages/language-service/test/typescript_host_spec.ts b/packages/language-service/test/typescript_host_spec.ts index 9f60660a7b..4719150fdb 100644 --- a/packages/language-service/test/typescript_host_spec.ts +++ b/packages/language-service/test/typescript_host_spec.ts @@ -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); + }); });