Skip to content

Commit 8d74f07

Browse files
committed
feat: Prompt to use the Ivy Language Service if VE is detected
In order to drive adoption of the new Ivy Language Service before it becomes the default in v12, this commit adds a prompt for users to enable it if they are using `@angular/core` v9+. Fixes #1095
1 parent 0676697 commit 8d74f07

File tree

8 files changed

+73
-6
lines changed

8 files changed

+73
-6
lines changed

client/src/client.ts

Lines changed: 21 additions & 2 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, SuggestStrictMode, SuggestStrictModeParams} from '../common/notifications';
14+
import {ProjectLoadingFinish, ProjectLoadingStart, SuggestIvyLanguageService, SuggestIvyLanguageServiceParams, SuggestStrictMode, SuggestStrictModeParams} from '../common/notifications';
1515
import {NgccProgress, NgccProgressToken, NgccProgressType} from '../common/progress';
1616

1717
import {ProgressReporter} from './progress-reporter';
@@ -136,7 +136,26 @@ function registerNotificationHandlers(
136136
}
137137
});
138138

139-
context.subscriptions.push(disposable1, disposable2);
139+
const disposable3 = client.onNotification(
140+
SuggestIvyLanguageService, async (params: SuggestIvyLanguageServiceParams) => {
141+
const config = vscode.workspace.getConfiguration();
142+
if (config.get('angular.enable-experimental-ivy-prompt') === false) {
143+
return;
144+
}
145+
146+
const enableIvy = 'Enable';
147+
const selection = await vscode.window.showInformationMessage(
148+
params.message,
149+
enableIvy,
150+
'Ignore',
151+
);
152+
if (selection === enableIvy) {
153+
config.update('angular.experimental-ivy', true);
154+
}
155+
config.update('angular.enable-experimental-ivy-prompt', false);
156+
});
157+
158+
context.subscriptions.push(disposable1, disposable2, disposable3);
140159
}
141160

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

common/notifications.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@ export interface SuggestStrictModeParams {
2626

2727
export const SuggestStrictMode =
2828
new NotificationType<SuggestStrictModeParams>('angular/suggestStrictMode');
29+
30+
export interface SuggestIvyLanguageServiceParams {
31+
message: string;
32+
}
33+
34+
export const SuggestIvyLanguageService =
35+
new NotificationType<SuggestIvyLanguageServiceParams>('angular/suggestIvyLanguageServiceMode');

integration/lsp/test_utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function createConnection(serverOptions: ServerOptions): MessageConnectio
3030
'--tsProbeLocations',
3131
PACKAGE_ROOT,
3232
'--ngProbeLocations',
33-
SERVER_PATH,
33+
[SERVER_PATH, PROJECT_PATH].join(','),
3434
];
3535
if (serverOptions.ivy) {
3636
argv.push('--experimental-ivy');

integration/lsp/viewengine_spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {MessageConnection} from 'vscode-jsonrpc';
1010
import * as lsp from 'vscode-languageserver-protocol';
11+
import {SuggestIvyLanguageService, SuggestIvyLanguageServiceParams} from '../../common/notifications';
1112
import {APP_COMPONENT, createConnection, FOO_TEMPLATE, initializeServer, openTextDocument} from './test_utils';
1213

1314
describe('Angular language server', () => {
@@ -107,6 +108,12 @@ describe('Angular language server', () => {
107108
expect(diagnostics.length).toBe(1);
108109
expect(diagnostics[0].message).toContain(`Identifier 'doesnotexist' is not defined.`);
109110
});
111+
112+
it('should prompt to enable Ivy Language Service', async () => {
113+
openTextDocument(client, APP_COMPONENT);
114+
const message = await onSuggestIvyLanguageService(client);
115+
expect(message).toContain('It is recommended that you enable the native Ivy Language Service');
116+
});
110117
});
111118

112119
describe('initialization', () => {
@@ -138,3 +145,11 @@ describe('initialization', () => {
138145
client.dispose();
139146
});
140147
});
148+
149+
function onSuggestIvyLanguageService(client: MessageConnection): Promise<string> {
150+
return new Promise(resolve => {
151+
client.onNotification(SuggestIvyLanguageService, (params: SuggestIvyLanguageServiceParams) => {
152+
resolve(params.message);
153+
});
154+
});
155+
}

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
"default": false,
5858
"description": "This is an experimental feature that enables the Ivy language service."
5959
},
60+
"angular.enable-experimental-ivy-prompt": {
61+
"type": "boolean",
62+
"default": true,
63+
"description": "Prompt to enable the Ivy language service for the workspace when an old version is in use."
64+
},
6065
"angular.trace.server": {
6166
"type": "string",
6267
"scope": "window",
@@ -159,4 +164,4 @@
159164
"type": "git",
160165
"url": "https://github.com/angular/vscode-ng-language-service"
161166
}
162-
}
167+
}

server/src/server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {generateHelpMessage, parseCommandLine} from './cmdline_utils';
1010
import {createLogger} from './logger';
1111
import {ServerHost} from './server_host';
1212
import {Session} from './session';
13-
import {resolveNgLangSvc, resolveTsServer} from './version_provider';
13+
import {resolveAngularCore, resolveNgLangSvc, resolveTsServer} from './version_provider';
1414

1515
// Parse command line arguments
1616
const options = parseCommandLine(process.argv);
@@ -28,6 +28,7 @@ const logger = createLogger({
2828

2929
const ts = resolveTsServer(options.tsProbeLocations);
3030
const ng = resolveNgLangSvc(options.ngProbeLocations, options.ivy);
31+
const angularCore = resolveAngularCore(options.ngProbeLocations);
3132

3233
// ServerHost provides native OS functionality
3334
const host = new ServerHost();
@@ -40,6 +41,7 @@ const session = new Session({
4041
ngProbeLocation: ng.resolvedPath,
4142
ivy: options.ivy,
4243
logToConsole: options.logToConsole,
44+
ngVersion: angularCore.version,
4345
});
4446

4547
// Log initialization info

server/src/session.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@ 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, SuggestStrictMode} from '../common/notifications';
13+
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart, SuggestIvyLanguageService, SuggestStrictMode} from '../common/notifications';
1414
import {NgccProgressToken, NgccProgressType} from '../common/progress';
1515

1616
import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
1717
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
1818
import {resolveAndRunNgcc} from './ngcc';
1919
import {ServerHost} from './server_host';
2020
import {filePathToUri, isConfiguredProject, lspPositionToTsPosition, lspRangeToTsPositions, tsTextSpanToLspRange, uriToFilePath} from './utils';
21+
import {Version} from './version_provider';
2122

2223
export interface SessionOptions {
2324
host: ServerHost;
2425
logger: ts.server.Logger;
2526
ngPlugin: string;
2627
ngProbeLocation: string;
28+
ngVersion: Version;
2729
ivy: boolean;
2830
logToConsole: boolean;
2931
}
@@ -44,6 +46,7 @@ export class Session {
4446
private readonly connection: lsp.Connection;
4547
private readonly projectService: ts.server.ProjectService;
4648
private readonly logger: ts.server.Logger;
49+
private readonly angularCoreVersion: Version;
4750
private readonly ivy: boolean;
4851
private readonly configuredProjToExternalProj = new Map<string, string>();
4952
private readonly logToConsole: boolean;
@@ -54,6 +57,7 @@ export class Session {
5457
this.logger = options.logger;
5558
this.ivy = options.ivy;
5659
this.logToConsole = options.logToConsole;
60+
this.angularCoreVersion = options.ngVersion;
5761
// Create a connection for the server. The connection uses Node's IPC as a transport.
5862
this.connection = lsp.createConnection();
5963
this.addProtocolHandlers(this.connection);
@@ -830,6 +834,12 @@ export class Session {
830834
} else {
831835
// Immediately enable Legacy/ViewEngine language service
832836
this.info(`Enabling VE language service for ${projectName}.`);
837+
if (!this.ivy && this.angularCoreVersion.major >= 9) {
838+
this.connection.sendNotification(SuggestIvyLanguageService, {
839+
message: 'An old version of the Angular Language Service is enabled.' +
840+
'It is recommended that you enable the native Ivy Language Service to get the latest features and bug fixes.',
841+
});
842+
}
833843
}
834844
}
835845

server/src/version_provider.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ export function resolveNgLangSvc(probeLocations: string[], ivy: boolean): NodeMo
8989
return resolveWithMinVersion(packageName, MIN_NG_VERSION, probeLocations, nglangsvc);
9090
}
9191

92+
/**
93+
* Resolve `@angular/core` from the given locations.
94+
* @param probeLocations
95+
*/
96+
export function resolveAngularCore(probeLocations: string[]): NodeModule {
97+
return resolveWithMinVersion(
98+
'@angular/core/bundles/core.umd', '0.0', probeLocations, '@angular/core');
99+
}
100+
92101
/**
93102
* Converts the specified string `a` to non-negative integer.
94103
* Returns -1 if the result is NaN.

0 commit comments

Comments
 (0)