Skip to content

Commit

Permalink
fix(language-service): Add plugin option to force strictTemplates (#4…
Browse files Browse the repository at this point in the history
…1063)

This commit adds a new configuration option, `forceStrictTemplates` to the
language service plugin to allow users to force enable `strictTemplates`.

This is needed so that the Angular extension can be used inside Google without
changing the underlying compiler options in the `ng_module` build rule.

PR Close #41063
  • Loading branch information
kyliau authored and zarend committed Mar 3, 2021
1 parent ef87953 commit 95f748c
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 12 deletions.
18 changes: 18 additions & 0 deletions packages/language-service/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@

import * as ts from 'typescript';

export interface PluginConfig {
/**
* If true, return only Angular results. Otherwise, return Angular + TypeScript
* results.
*/
angularOnly: boolean;
/**
* If true, return factory function for Ivy LS during plugin initialization.
* Otherwise return factory function for View Engine LS.
*/
ivy: boolean;
/**
* If true, enable `strictTemplates` in Angular compiler options regardless
* of its value in tsconfig.json.
*/
forceStrictTemplates?: true;
}

export type GetTcbResponse = {
/**
* The filename of the SourceFile this typecheck block belongs to.
Expand Down
26 changes: 22 additions & 4 deletions packages/language-service/ivy/language_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ import {getTargetAtPosition, TargetContext, TargetNodeKind} from './template_tar
import {findTightestNode, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './ts_utils';
import {getTemplateInfoAtPosition, isTypeScriptFile} from './utils';

interface LanguageServiceConfig {
/**
* If true, enable `strictTemplates` in Angular compiler options regardless
* of its value in tsconfig.json.
*/
forceStrictTemplates?: true;
}

export class LanguageService {
private options: CompilerOptions;
readonly compilerFactory: CompilerFactory;
Expand All @@ -35,9 +43,12 @@ export class LanguageService {
private readonly parseConfigHost: LSParseConfigHost;

constructor(
private readonly project: ts.server.Project, private readonly tsLS: ts.LanguageService) {
private readonly project: ts.server.Project,
private readonly tsLS: ts.LanguageService,
private readonly config: LanguageServiceConfig,
) {
this.parseConfigHost = new LSParseConfigHost(project.projectService.host);
this.options = parseNgCompilerOptions(project, this.parseConfigHost);
this.options = parseNgCompilerOptions(project, this.parseConfigHost, config);
logCompilerOptions(project, this.options);
this.strategy = createTypeCheckingProgramStrategy(project);
this.adapter = new LanguageServiceAdapter(project);
Expand Down Expand Up @@ -315,7 +326,7 @@ export class LanguageService {
project.getConfigFilePath(), (fileName: string, eventKind: ts.FileWatcherEventKind) => {
project.log(`Config file changed: ${fileName}`);
if (eventKind === ts.FileWatcherEventKind.Changed) {
this.options = parseNgCompilerOptions(project, this.parseConfigHost);
this.options = parseNgCompilerOptions(project, this.parseConfigHost, this.config);
logCompilerOptions(project, this.options);
}
});
Expand All @@ -329,7 +340,8 @@ function logCompilerOptions(project: ts.server.Project, options: CompilerOptions
}

function parseNgCompilerOptions(
project: ts.server.Project, host: ConfigurationHost): CompilerOptions {
project: ts.server.Project, host: ConfigurationHost,
config: LanguageServiceConfig): CompilerOptions {
if (!(project instanceof ts.server.ConfiguredProject)) {
return {};
}
Expand All @@ -350,6 +362,12 @@ function parseNgCompilerOptions(
// and only the real component declaration is used.
options.compileNonExportedClasses = false;

// If `forceStrictTemplates` is true, always enable `strictTemplates`
// regardless of its value in tsconfig.json.
if (config.forceStrictTemplates === true) {
options.strictTemplates = true;
}

return options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('definitions', () => {
beforeAll(() => {
const {project, service: _service, tsLS} = setup();
service = _service;
ngLS = new LanguageService(project, tsLS);
ngLS = new LanguageService(project, tsLS, {});
});

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('getSemanticDiagnostics', () => {
beforeAll(() => {
const {project, service: _service, tsLS} = setup();
service = _service;
ngLS = new LanguageService(project, tsLS);
ngLS = new LanguageService(project, tsLS, {});
});

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('language service adapter', () => {
const {project: _project, tsLS, service: _service, configFileFs: _configFileFs} = setup();
project = _project;
service = _service;
ngLS = new LanguageService(project, tsLS);
ngLS = new LanguageService(project, tsLS, {});
configFileFs = _configFileFs;
});

Expand Down Expand Up @@ -57,6 +57,33 @@ describe('language service adapter', () => {
strictTemplates: false,
}));
});

it('should always enable strictTemplates if forceStrictTemplates is true', () => {
const {project, tsLS, configFileFs} = setup();
const ngLS = new LanguageService(project, tsLS, {
forceStrictTemplates: true,
});

// First make sure the default for strictTemplates is true
expect(ngLS.getCompilerOptions()).toEqual(jasmine.objectContaining({
enableIvy: true, // default for ivy is true
strictTemplates: true,
strictInjectionParameters: true,
}));

// Change strictTemplates to false
configFileFs.overwriteConfigFile(TSCONFIG, {
angularCompilerOptions: {
strictTemplates: false,
}
});

// Make sure strictTemplates is still true because forceStrictTemplates
// is enabled.
expect(ngLS.getCompilerOptions()).toEqual(jasmine.objectContaining({
strictTemplates: true,
}));
});
});

describe('compiler options diagnostics', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('getExternalFiles()', () => {
// a global analysis
expect(externalFiles).toEqual([]);
// Trigger global analysis
const ngLS = new LanguageService(project, tsLS);
const ngLS = new LanguageService(project, tsLS, {});
ngLS.getSemanticDiagnostics(APP_COMPONENT);
// Now that global analysis is run, we should have all the typecheck files
externalFiles = getExternalFiles(project);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('type definitions', () => {
beforeAll(() => {
const {project, service: _service, tsLS} = setup();
service = _service;
ngLS = new LanguageService(project, tsLS);
ngLS = new LanguageService(project, tsLS, {});
});

const possibleArrayDefFiles = new Set([
Expand Down
4 changes: 2 additions & 2 deletions packages/language-service/ivy/testing/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class Project {

// The following operation forces a ts.Program to be created.
this.tsLS = tsProject.getLanguageService();
this.ngLS = new LanguageService(tsProject, this.tsLS);
this.ngLS = new LanguageService(tsProject, this.tsLS, {});
}

openFile(projectFileName: string): OpenBuffer {
Expand Down Expand Up @@ -188,4 +188,4 @@ function getClassOrError(sf: ts.SourceFile, name: string): ts.ClassDeclaration {
}
}
throw new Error(`Class ${name} not found in file: ${sf.fileName}: ${sf.text}`);
}
}
2 changes: 1 addition & 1 deletion packages/language-service/ivy/ts_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function create(info: ts.server.PluginCreateInfo): NgLanguageService {
const {project, languageService: tsLS, config} = info;
const angularOnly = config?.angularOnly === true;

const ngLS = new LanguageService(project, tsLS);
const ngLS = new LanguageService(project, tsLS, config);

function getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
const diagnostics: ts.Diagnostic[] = [];
Expand Down

0 comments on commit 95f748c

Please sign in to comment.