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
This commit is contained in:
Keen Yee Liau 2020-04-29 15:52:17 -07:00 committed by Alex Rickabaugh
parent b7fb92a048
commit dbd0f8e699
5 changed files with 67 additions and 3 deletions

View File

@ -6,6 +6,7 @@ ts_library(
name = "ivy", name = "ivy",
srcs = glob(["*.ts"]), srcs = glob(["*.ts"]),
deps = [ deps = [
"//packages/compiler-cli",
"@npm//typescript", "@npm//typescript",
], ],
) )

View File

@ -6,12 +6,50 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompilerOptions, createNgCompilerOptions} from '@angular/compiler-cli';
import * as ts from 'typescript/lib/tsserverlibrary'; import * as ts from 'typescript/lib/tsserverlibrary';
export class LanguageService { 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[] { getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
return []; 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());
} }

View File

@ -5,6 +5,7 @@ ts_library(
testonly = True, testonly = True,
srcs = glob(["*.ts"]), srcs = glob(["*.ts"]),
deps = [ deps = [
"//packages/language-service/ivy",
"@npm//typescript", "@npm//typescript",
], ],
) )

View File

@ -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,
}));
});
});

View File

@ -10,10 +10,10 @@ import * as ts from 'typescript/lib/tsserverlibrary';
import {LanguageService} from './language_service'; import {LanguageService} from './language_service';
export function create(info: ts.server.PluginCreateInfo): ts.LanguageService { 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 angularOnly = config?.angularOnly === true;
const ngLS = new LanguageService(tsLS); const ngLS = new LanguageService(project, tsLS);
function getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { function getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
const diagnostics: ts.Diagnostic[] = []; const diagnostics: ts.Diagnostic[] = [];