Skip to content

Commit 99c4b37

Browse files
kyliauKeen Yee Liau
authored andcommitted
feat: Show prompt to enable strictTemplates
This commit adds a prompt to encourage users turn on `strictTemplates` so that they can get the most out of Angular language service.
1 parent 9a6107e commit 99c4b37

File tree

6 files changed

+120
-9
lines changed

6 files changed

+120
-9
lines changed

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
{
33
"version": "0.2.0",
44
"configurations": [
5+
{
6+
"name": "Integration test: Attach to server",
7+
"port": 9330,
8+
"request": "attach",
9+
"skipFiles": [
10+
"<node_internals>/**"
11+
],
12+
"outFiles": ["${workspaceRoot}/dist/integration/lsp/*.js"],
13+
"type": "node"
14+
},
515
{
616
"type": "extensionHost",
717
"request": "launch",

client/src/client.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as path from 'path';
1111
import * as vscode from 'vscode';
1212
import * as lsp from 'vscode-languageclient/node';
1313

14-
import {ProjectLoadingFinish, ProjectLoadingStart} from '../common/notifications';
14+
import {ProjectLoadingFinish, ProjectLoadingStart, SuggestStrictMode, SuggestStrictModeParams} from '../common/notifications';
1515
import {NgccProgress, NgccProgressToken, NgccProgressType} from '../common/progress';
1616

1717
import {ProgressReporter} from './progress-reporter';
@@ -76,7 +76,7 @@ export class AngularLanguageClient implements vscode.Disposable {
7676
await this.client.onReady();
7777
// Must wait for the client to be ready before registering notification
7878
// handlers.
79-
registerNotificationHandlers(this.client);
79+
registerNotificationHandlers(this.client, this.context);
8080
registerProgressHandlers(this.client, this.context);
8181
}
8282

@@ -104,8 +104,9 @@ export class AngularLanguageClient implements vscode.Disposable {
104104
}
105105
}
106106

107-
function registerNotificationHandlers(client: lsp.LanguageClient) {
108-
client.onNotification(ProjectLoadingStart, () => {
107+
function registerNotificationHandlers(
108+
client: lsp.LanguageClient, context: vscode.ExtensionContext) {
109+
const disposable1 = client.onNotification(ProjectLoadingStart, () => {
109110
vscode.window.withProgress(
110111
{
111112
location: vscode.ProgressLocation.Window,
@@ -116,6 +117,26 @@ function registerNotificationHandlers(client: lsp.LanguageClient) {
116117
}),
117118
);
118119
});
120+
121+
const disposable2 =
122+
client.onNotification(SuggestStrictMode, async (params: SuggestStrictModeParams) => {
123+
const openTsConfig = 'Open tsconfig.json';
124+
// Markdown is not generally supported in `showInformationMessage()`,
125+
// but links are supported. See
126+
// https://github.com/microsoft/vscode/issues/20595#issuecomment-281099832
127+
const selection = await vscode.window.showInformationMessage(
128+
'Some language features are not available. To access all features, enable ' +
129+
'[strictTemplates](https://angular.io/guide/angular-compiler-options#stricttemplates) in ' +
130+
'[angularCompilerOptions](https://angular.io/guide/angular-compiler-options).',
131+
openTsConfig,
132+
);
133+
if (selection === openTsConfig) {
134+
const document = await vscode.workspace.openTextDocument(params.configFilePath);
135+
vscode.window.showTextDocument(document);
136+
}
137+
});
138+
139+
context.subscriptions.push(disposable1, disposable2);
119140
}
120141

121142
function registerProgressHandlers(client: lsp.LanguageClient, context: vscode.ExtensionContext) {

common/notifications.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ export interface ProjectLanguageServiceParams {
1818

1919
export const ProjectLanguageService =
2020
new NotificationType<ProjectLanguageServiceParams>('angular/projectLanguageService');
21+
22+
export interface SuggestStrictModeParams {
23+
configFilePath: string;
24+
message: string;
25+
}
26+
27+
export const SuggestStrictMode =
28+
new NotificationType<SuggestStrictModeParams>('angular/suggestStrictMode');

integration/lsp/ivy_spec.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import * as fs from 'fs';
910
import {MessageConnection} from 'vscode-jsonrpc';
1011
import * as lsp from 'vscode-languageserver-protocol';
1112
import {URI} from 'vscode-uri';
12-
import {ProjectLanguageService, ProjectLanguageServiceParams} from '../../common/notifications';
13+
14+
import {ProjectLanguageService, ProjectLanguageServiceParams, SuggestStrictMode, SuggestStrictModeParams} from '../../common/notifications';
1315
import {NgccProgress, NgccProgressToken, NgccProgressType} from '../../common/progress';
1416

15-
import {APP_COMPONENT, createConnection, FOO_COMPONENT, FOO_TEMPLATE, initializeServer, openTextDocument} from './test_utils';
17+
import {APP_COMPONENT, createConnection, createTracer, FOO_COMPONENT, FOO_TEMPLATE, initializeServer, openTextDocument, TSCONFIG} from './test_utils';
1618

1719
describe('Angular Ivy language server', () => {
1820
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; /* 10 seconds */
@@ -23,6 +25,10 @@ describe('Angular Ivy language server', () => {
2325
client = createConnection({
2426
ivy: true,
2527
});
28+
// If debugging, set to
29+
// - lsp.Trace.Messages to inspect request/response/notification, or
30+
// - lsp.Trace.Verbose to inspect payload
31+
client.trace(lsp.Trace.Off, createTracer());
2632
client.listen();
2733
await initializeServer(client);
2834
});
@@ -259,6 +265,27 @@ describe('Angular Ivy language server', () => {
259265
});
260266
});
261267
});
268+
269+
describe('compiler options', () => {
270+
const originalConfig = fs.readFileSync(TSCONFIG, 'utf-8');
271+
272+
afterEach(() => {
273+
// TODO(kyliau): Use an in-memory FS harness for the server
274+
fs.writeFileSync(TSCONFIG, originalConfig);
275+
});
276+
277+
it('should suggest strict mode', async () => {
278+
const config = JSON.parse(originalConfig);
279+
config.angularCompilerOptions.strictTemplates = false;
280+
fs.writeFileSync(TSCONFIG, JSON.stringify(config, null, 2));
281+
282+
openTextDocument(client, APP_COMPONENT);
283+
const languageServiceEnabled = await waitForNgcc(client);
284+
expect(languageServiceEnabled).toBeTrue();
285+
const configFilePath = await onSuggestStrictMode(client);
286+
expect(configFilePath.endsWith('integration/project/tsconfig.json')).toBeTrue();
287+
});
288+
});
262289
});
263290

264291
function onNgccProgress(client: MessageConnection): Promise<string> {
@@ -271,6 +298,14 @@ function onNgccProgress(client: MessageConnection): Promise<string> {
271298
});
272299
}
273300

301+
function onSuggestStrictMode(client: MessageConnection): Promise<string> {
302+
return new Promise(resolve => {
303+
client.onNotification(SuggestStrictMode, (params: SuggestStrictModeParams) => {
304+
resolve(params.configFilePath);
305+
});
306+
});
307+
}
308+
274309
function onLanguageServiceStateNotification(client: MessageConnection): Promise<boolean> {
275310
return new Promise(resolve => {
276311
client.onNotification(ProjectLanguageService, (params: ProjectLanguageServiceParams) => {

integration/lsp/test_utils.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const PROJECT_PATH = `${PACKAGE_ROOT}/integration/project`;
1818
export const APP_COMPONENT = `${PROJECT_PATH}/app/app.component.ts`;
1919
export const FOO_TEMPLATE = `${PROJECT_PATH}/app/foo.component.html`;
2020
export const FOO_COMPONENT = `${PROJECT_PATH}/app/foo.component.ts`;
21+
export const TSCONFIG = `${PROJECT_PATH}/tsconfig.json`;
2122

2223
export interface ServerOptions {
2324
ivy: boolean;
@@ -40,7 +41,7 @@ export function createConnection(serverOptions: ServerOptions): MessageConnectio
4041
TSC_NONPOLLING_WATCHER: 'true',
4142
},
4243
// uncomment to debug server process
43-
// execArgv: ['--inspect-brk=9229']
44+
// execArgv: ['--inspect-brk=9330']
4445
});
4546

4647
const connection = createMessageConnection(
@@ -87,3 +88,22 @@ export function openTextDocument(client: MessageConnection, filePath: string) {
8788
},
8889
});
8990
}
91+
92+
export function createTracer(): lsp.Tracer {
93+
return {
94+
log(messageOrDataObject: string|any, data?: string) {
95+
if (typeof messageOrDataObject === 'string') {
96+
const message = messageOrDataObject;
97+
console.log(`[Trace - ${(new Date().toLocaleTimeString())}] ${message}`);
98+
if (data) {
99+
console.log(data);
100+
}
101+
} else {
102+
const dataObject = messageOrDataObject;
103+
console.log(
104+
`[Trace - ${(new Date().toLocaleTimeString())}] ` +
105+
JSON.stringify(dataObject, null, 2));
106+
}
107+
},
108+
};
109+
}

server/src/session.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as ts from 'typescript/lib/tsserverlibrary';
1010
import * as lsp from 'vscode-languageserver/node';
1111

1212
import {ServerOptions} from '../common/initialize';
13-
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart} from '../common/notifications';
13+
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart, SuggestStrictMode} from '../common/notifications';
1414
import {NgccProgressToken, NgccProgressType} from '../common/progress';
1515

1616
import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
@@ -182,7 +182,7 @@ export class Session {
182182
// project as dirty to force update the graph.
183183
project.markAsDirty();
184184
this.info(`Enabling Ivy language service for ${project.projectName}.`);
185-
185+
this.handleCompilerOptionsDiagnostics(project);
186186
// Send diagnostics since we skipped this step when opening the file
187187
// (because language service was disabled while waiting for ngcc).
188188
// First, make sure the Angular project is complete.
@@ -203,6 +203,23 @@ export class Session {
203203
project.getLanguageService().getSemanticDiagnostics(fileName);
204204
}
205205

206+
private handleCompilerOptionsDiagnostics(project: ts.server.Project) {
207+
if (!isConfiguredProject(project)) {
208+
return;
209+
}
210+
211+
const diags = project.getLanguageService().getCompilerOptionsDiagnostics();
212+
const suggestStrictModeDiag = diags.find(d => d.code === -9910001);
213+
214+
if (suggestStrictModeDiag) {
215+
const configFilePath: string = project.getConfigFilePath();
216+
this.connection.sendNotification(SuggestStrictMode, {
217+
configFilePath,
218+
message: suggestStrictModeDiag.messageText,
219+
});
220+
}
221+
}
222+
206223
/**
207224
* An event handler that gets invoked whenever the program changes and
208225
* TS ProjectService sends `ProjectUpdatedInBackgroundEvent`. This particular

0 commit comments

Comments
 (0)