Skip to content

Commit e9e7c33

Browse files
kyliauzarend
authored andcommitted
fix(language-service): Add plugin option to force strictTemplates (#41062)
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 #41062
1 parent 21f0dee commit e9e7c33

10 files changed

+67
-17
lines changed

packages/language-service/api.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import * as ts from 'typescript';
1616

17-
export interface NgLanguageServiceConfig {
17+
export interface PluginConfig {
1818
/**
1919
* If true, return only Angular results. Otherwise, return Angular + TypeScript
2020
* results.
@@ -25,6 +25,11 @@ export interface NgLanguageServiceConfig {
2525
* Otherwise return factory function for View Engine LS.
2626
*/
2727
ivy: boolean;
28+
/**
29+
* If true, enable `strictTemplates` in Angular compiler options regardless
30+
* of its value in tsconfig.json.
31+
*/
32+
forceStrictTemplates?: true;
2833
}
2934

3035
export type GetTcbResponse = {

packages/language-service/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,29 @@
77
*/
88

99
import * as ts from 'typescript/lib/tsserverlibrary';
10-
import {NgLanguageService, NgLanguageServiceConfig} from './api';
10+
import {NgLanguageService, PluginConfig} from './api';
1111

1212
export * from './api';
1313

1414
interface PluginModule extends ts.server.PluginModule {
1515
create(createInfo: ts.server.PluginCreateInfo): NgLanguageService;
16-
onConfigurationChanged?(config: NgLanguageServiceConfig): void;
16+
onConfigurationChanged?(config: PluginConfig): void;
1717
}
1818

1919
const factory: ts.server.PluginModuleFactory = (tsModule): PluginModule => {
2020
let plugin: PluginModule;
2121

2222
return {
2323
create(info: ts.server.PluginCreateInfo): NgLanguageService {
24-
const config: NgLanguageServiceConfig = info.config;
24+
const config: PluginConfig = info.config;
2525
const bundleName = config.ivy ? 'ivy.js' : 'language-service.js';
2626
plugin = require(`./bundles/${bundleName}`)(tsModule);
2727
return plugin.create(info);
2828
},
2929
getExternalFiles(project: ts.server.Project): string[] {
3030
return plugin?.getExternalFiles?.(project) ?? [];
3131
},
32-
onConfigurationChanged(config: NgLanguageServiceConfig): void {
32+
onConfigurationChanged(config: PluginConfig): void {
3333
plugin?.onConfigurationChanged?.(config);
3434
},
3535
};

packages/language-service/ivy/language_service.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ import {getTargetAtPosition, TargetContext, TargetNodeKind} from './template_tar
2929
import {findTightestNode, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './ts_utils';
3030
import {getTemplateInfoAtPosition, isTypeScriptFile} from './utils';
3131

32+
interface LanguageServiceConfig {
33+
/**
34+
* If true, enable `strictTemplates` in Angular compiler options regardless
35+
* of its value in tsconfig.json.
36+
*/
37+
forceStrictTemplates?: true;
38+
}
39+
3240
export class LanguageService {
3341
private options: CompilerOptions;
3442
readonly compilerFactory: CompilerFactory;
@@ -37,9 +45,12 @@ export class LanguageService {
3745
private readonly parseConfigHost: LSParseConfigHost;
3846

3947
constructor(
40-
private readonly project: ts.server.Project, private readonly tsLS: ts.LanguageService) {
48+
private readonly project: ts.server.Project,
49+
private readonly tsLS: ts.LanguageService,
50+
private readonly config: LanguageServiceConfig,
51+
) {
4152
this.parseConfigHost = new LSParseConfigHost(project.projectService.host);
42-
this.options = parseNgCompilerOptions(project, this.parseConfigHost);
53+
this.options = parseNgCompilerOptions(project, this.parseConfigHost, config);
4354
logCompilerOptions(project, this.options);
4455
this.strategy = createTypeCheckingProgramStrategy(project);
4556
this.adapter = new LanguageServiceAdapter(project);
@@ -340,7 +351,7 @@ export class LanguageService {
340351
project.getConfigFilePath(), (fileName: string, eventKind: ts.FileWatcherEventKind) => {
341352
project.log(`Config file changed: ${fileName}`);
342353
if (eventKind === ts.FileWatcherEventKind.Changed) {
343-
this.options = parseNgCompilerOptions(project, this.parseConfigHost);
354+
this.options = parseNgCompilerOptions(project, this.parseConfigHost, this.config);
344355
logCompilerOptions(project, this.options);
345356
}
346357
});
@@ -354,7 +365,8 @@ function logCompilerOptions(project: ts.server.Project, options: CompilerOptions
354365
}
355366

356367
function parseNgCompilerOptions(
357-
project: ts.server.Project, host: ConfigurationHost): CompilerOptions {
368+
project: ts.server.Project, host: ConfigurationHost,
369+
config: LanguageServiceConfig): CompilerOptions {
358370
if (!(project instanceof ts.server.ConfiguredProject)) {
359371
return {};
360372
}
@@ -375,6 +387,12 @@ function parseNgCompilerOptions(
375387
// and only the real component declaration is used.
376388
options.compileNonExportedClasses = false;
377389

390+
// If `forceStrictTemplates` is true, always enable `strictTemplates`
391+
// regardless of its value in tsconfig.json.
392+
if (config.forceStrictTemplates === true) {
393+
options.strictTemplates = true;
394+
}
395+
378396
return options;
379397
}
380398

packages/language-service/ivy/test/legacy/definitions_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('definitions', () => {
1818
beforeAll(() => {
1919
const {project, service: _service, tsLS} = setup();
2020
service = _service;
21-
ngLS = new LanguageService(project, tsLS);
21+
ngLS = new LanguageService(project, tsLS, {});
2222
});
2323

2424
beforeEach(() => {

packages/language-service/ivy/test/legacy/daignostic_spec.ts packages/language-service/ivy/test/legacy/diagnostic_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('getSemanticDiagnostics', () => {
1717
beforeAll(() => {
1818
const {project, service: _service, tsLS} = setup();
1919
service = _service;
20-
ngLS = new LanguageService(project, tsLS);
20+
ngLS = new LanguageService(project, tsLS, {});
2121
});
2222

2323
beforeEach(() => {

packages/language-service/ivy/test/legacy/language_service_spec.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('language service adapter', () => {
2323
const {project: _project, tsLS, service: _service, configFileFs: _configFileFs} = setup();
2424
project = _project;
2525
service = _service;
26-
ngLS = new LanguageService(project, tsLS);
26+
ngLS = new LanguageService(project, tsLS, {});
2727
configFileFs = _configFileFs;
2828
});
2929

@@ -57,6 +57,33 @@ describe('language service adapter', () => {
5757
strictTemplates: false,
5858
}));
5959
});
60+
61+
it('should always enable strictTemplates if forceStrictTemplates is true', () => {
62+
const {project, tsLS, configFileFs} = setup();
63+
const ngLS = new LanguageService(project, tsLS, {
64+
forceStrictTemplates: true,
65+
});
66+
67+
// First make sure the default for strictTemplates is true
68+
expect(ngLS.getCompilerOptions()).toEqual(jasmine.objectContaining({
69+
enableIvy: true, // default for ivy is true
70+
strictTemplates: true,
71+
strictInjectionParameters: true,
72+
}));
73+
74+
// Change strictTemplates to false
75+
configFileFs.overwriteConfigFile(TSCONFIG, {
76+
angularCompilerOptions: {
77+
strictTemplates: false,
78+
}
79+
});
80+
81+
// Make sure strictTemplates is still true because forceStrictTemplates
82+
// is enabled.
83+
expect(ngLS.getCompilerOptions()).toEqual(jasmine.objectContaining({
84+
strictTemplates: true,
85+
}));
86+
});
6087
});
6188

6289
describe('compiler options diagnostics', () => {

packages/language-service/ivy/test/legacy/ts_plugin_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('getExternalFiles()', () => {
1919
// a global analysis
2020
expect(externalFiles).toEqual([]);
2121
// Trigger global analysis
22-
const ngLS = new LanguageService(project, tsLS);
22+
const ngLS = new LanguageService(project, tsLS, {});
2323
ngLS.getSemanticDiagnostics(APP_COMPONENT);
2424
// Now that global analysis is run, we should have all the typecheck files
2525
externalFiles = getExternalFiles(project);

packages/language-service/ivy/test/legacy/type_definitions_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('type definitions', () => {
1818
beforeAll(() => {
1919
const {project, service: _service, tsLS} = setup();
2020
service = _service;
21-
ngLS = new LanguageService(project, tsLS);
21+
ngLS = new LanguageService(project, tsLS, {});
2222
});
2323

2424
const possibleArrayDefFiles = new Set([

packages/language-service/ivy/testing/src/project.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class Project {
9292

9393
// The following operation forces a ts.Program to be created.
9494
this.tsLS = tsProject.getLanguageService();
95-
this.ngLS = new LanguageService(tsProject, this.tsLS);
95+
this.ngLS = new LanguageService(tsProject, this.tsLS, {});
9696
}
9797

9898
openFile(projectFileName: string): OpenBuffer {
@@ -188,4 +188,4 @@ function getClassOrError(sf: ts.SourceFile, name: string): ts.ClassDeclaration {
188188
}
189189
}
190190
throw new Error(`Class ${name} not found in file: ${sf.fileName}: ${sf.text}`);
191-
}
191+
}

packages/language-service/ivy/ts_plugin.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function create(info: ts.server.PluginCreateInfo): NgLanguageService {
1616
const {project, languageService: tsLS, config} = info;
1717
const angularOnly = config?.angularOnly === true;
1818

19-
const ngLS = new LanguageService(project, tsLS);
19+
const ngLS = new LanguageService(project, tsLS, config);
2020

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

0 commit comments

Comments
 (0)