From dbd0f8e6990204b8f0cc44347a45fb6cca3bced8 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Wed, 29 Apr 2020 15:52:17 -0700 Subject: [PATCH] feat(language-service): [ivy] Parse Angular compiler options (#36922) Parse Angular compiler options in Angular language service. In View Engine, only TypeScript compiler options are read, Angular compiler options are not. With Ivy, there could be different modes of compilation, most notably how strict the templates should be checked. This commit makes the behavior of language service consistent with the Ivy compiler. PR Close #36922 --- packages/language-service/ivy/BUILD.bazel | 1 + .../language-service/ivy/language_service.ts | 40 ++++++++++++++++++- .../language-service/ivy/test/BUILD.bazel | 1 + .../ivy/test/language_service_spec.ts | 24 +++++++++++ packages/language-service/ivy/ts_plugin.ts | 4 +- 5 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 packages/language-service/ivy/test/language_service_spec.ts diff --git a/packages/language-service/ivy/BUILD.bazel b/packages/language-service/ivy/BUILD.bazel index 849916eb81..1849b649f7 100644 --- a/packages/language-service/ivy/BUILD.bazel +++ b/packages/language-service/ivy/BUILD.bazel @@ -6,6 +6,7 @@ ts_library( name = "ivy", srcs = glob(["*.ts"]), deps = [ + "//packages/compiler-cli", "@npm//typescript", ], ) diff --git a/packages/language-service/ivy/language_service.ts b/packages/language-service/ivy/language_service.ts index 3c51444754..b9d258352a 100644 --- a/packages/language-service/ivy/language_service.ts +++ b/packages/language-service/ivy/language_service.ts @@ -6,12 +6,50 @@ * found in the LICENSE file at https://angular.io/license */ +import {CompilerOptions, createNgCompilerOptions} from '@angular/compiler-cli'; import * as ts from 'typescript/lib/tsserverlibrary'; export class LanguageService { - constructor(private readonly tsLS: ts.LanguageService) {} + private options: CompilerOptions; + + constructor(project: ts.server.Project, private readonly tsLS: ts.LanguageService) { + this.options = parseNgCompilerOptions(project); + this.watchConfigFile(project); + } getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { return []; } + + private watchConfigFile(project: ts.server.Project) { + // TODO: Check the case when the project is disposed. An InferredProject + // could be disposed when a tsconfig.json is added to the workspace, + // in which case it becomes a ConfiguredProject (or vice-versa). + // We need to make sure that the FileWatcher is closed. + if (!(project instanceof ts.server.ConfiguredProject)) { + return; + } + const {host} = project.projectService; + host.watchFile( + project.getConfigFilePath(), (fileName: string, eventKind: ts.FileWatcherEventKind) => { + project.log(`Config file changed: ${fileName}`); + if (eventKind === ts.FileWatcherEventKind.Changed) { + this.options = parseNgCompilerOptions(project); + } + }); + } +} + +export function parseNgCompilerOptions(project: ts.server.Project): CompilerOptions { + let config = {}; + if (project instanceof ts.server.ConfiguredProject) { + const configPath = project.getConfigFilePath(); + const result = ts.readConfigFile(configPath, path => project.readFile(path)); + if (result.error) { + project.error(ts.flattenDiagnosticMessageText(result.error.messageText, '\n')); + } + config = result.config || config; + } + const basePath = project.getCurrentDirectory(); + return createNgCompilerOptions(basePath, config, project.getCompilationSettings()); } diff --git a/packages/language-service/ivy/test/BUILD.bazel b/packages/language-service/ivy/test/BUILD.bazel index 2c70a5e70c..938725080a 100644 --- a/packages/language-service/ivy/test/BUILD.bazel +++ b/packages/language-service/ivy/test/BUILD.bazel @@ -5,6 +5,7 @@ ts_library( testonly = True, srcs = glob(["*.ts"]), deps = [ + "//packages/language-service/ivy", "@npm//typescript", ], ) diff --git a/packages/language-service/ivy/test/language_service_spec.ts b/packages/language-service/ivy/test/language_service_spec.ts new file mode 100644 index 0000000000..0363f1a504 --- /dev/null +++ b/packages/language-service/ivy/test/language_service_spec.ts @@ -0,0 +1,24 @@ +/** + * @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 {parseNgCompilerOptions} from '../language_service'; + +import {setup} from './mock_host'; + +const {project} = setup(); + +describe('parseNgCompilerOptions', () => { + it('should read angularCompilerOptions in tsconfig.json', () => { + const options = parseNgCompilerOptions(project); + expect(options).toEqual(jasmine.objectContaining({ + enableIvy: true, // default for ivy is true + fullTemplateTypeCheck: true, + strictInjectionParameters: true, + })); + }); +}); diff --git a/packages/language-service/ivy/ts_plugin.ts b/packages/language-service/ivy/ts_plugin.ts index 46ba8a49f9..053adbb2cf 100644 --- a/packages/language-service/ivy/ts_plugin.ts +++ b/packages/language-service/ivy/ts_plugin.ts @@ -10,10 +10,10 @@ import * as ts from 'typescript/lib/tsserverlibrary'; import {LanguageService} from './language_service'; export function create(info: ts.server.PluginCreateInfo): ts.LanguageService { - const {languageService: tsLS, config} = info; + const {project, languageService: tsLS, config} = info; const angularOnly = config?.angularOnly === true; - const ngLS = new LanguageService(tsLS); + const ngLS = new LanguageService(project, tsLS); function getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { const diagnostics: ts.Diagnostic[] = [];