Skip to content

feat: Prompt to use the Ivy Language Service if VE is detected #1099

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as path from 'path';
import * as vscode from 'vscode';
import * as lsp from 'vscode-languageclient/node';

import {ProjectLoadingFinish, ProjectLoadingStart, SuggestStrictMode, SuggestStrictModeParams} from '../common/notifications';
import {ProjectLoadingFinish, ProjectLoadingStart, SuggestIvyLanguageService, SuggestIvyLanguageServiceParams, SuggestStrictMode, SuggestStrictModeParams} from '../common/notifications';
import {NgccProgress, NgccProgressToken, NgccProgressType} from '../common/progress';

import {ProgressReporter} from './progress-reporter';
Expand Down Expand Up @@ -136,7 +136,29 @@ function registerNotificationHandlers(
}
});

context.subscriptions.push(disposable1, disposable2);
const disposable3 = client.onNotification(
SuggestIvyLanguageService, async (params: SuggestIvyLanguageServiceParams) => {
const config = vscode.workspace.getConfiguration();
if (config.get('angular.enable-experimental-ivy-prompt') === false) {
return;
}

const enableIvy = 'Enable';
const doNotPromptAgain = 'Do not show this again';
const selection = await vscode.window.showInformationMessage(
params.message,
enableIvy,
doNotPromptAgain,
);
if (selection === enableIvy) {
config.update('angular.experimental-ivy', true, vscode.ConfigurationTarget.Global);
} else if (selection === doNotPromptAgain) {
config.update(
'angular.enable-experimental-ivy-prompt', false, vscode.ConfigurationTarget.Global);
}
});

context.subscriptions.push(disposable1, disposable2, disposable3);
}

function registerProgressHandlers(client: lsp.LanguageClient, context: vscode.ExtensionContext) {
Expand Down
7 changes: 7 additions & 0 deletions common/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ export interface SuggestStrictModeParams {

export const SuggestStrictMode =
new NotificationType<SuggestStrictModeParams>('angular/suggestStrictMode');

export interface SuggestIvyLanguageServiceParams {
message: string;
}

export const SuggestIvyLanguageService =
new NotificationType<SuggestIvyLanguageServiceParams>('angular/suggestIvyLanguageServiceMode');
2 changes: 1 addition & 1 deletion integration/lsp/test_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function createConnection(serverOptions: ServerOptions): MessageConnectio
'--tsProbeLocations',
PACKAGE_ROOT,
'--ngProbeLocations',
SERVER_PATH,
[SERVER_PATH, PROJECT_PATH].join(','),
];
if (serverOptions.ivy) {
argv.push('--experimental-ivy');
Expand Down
15 changes: 15 additions & 0 deletions integration/lsp/viewengine_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

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

describe('Angular language server', () => {
Expand Down Expand Up @@ -107,6 +108,12 @@ describe('Angular language server', () => {
expect(diagnostics.length).toBe(1);
expect(diagnostics[0].message).toContain(`Identifier 'doesnotexist' is not defined.`);
});

it('should prompt to enable Ivy Language Service', async () => {
openTextDocument(client, APP_COMPONENT);
const message = await onSuggestIvyLanguageService(client);
expect(message).toContain('Would you like to enable the new Ivy-native language service');
});
});

describe('initialization', () => {
Expand Down Expand Up @@ -138,3 +145,11 @@ describe('initialization', () => {
client.dispose();
});
});

function onSuggestIvyLanguageService(client: MessageConnection): Promise<string> {
return new Promise(resolve => {
client.onNotification(SuggestIvyLanguageService, (params: SuggestIvyLanguageServiceParams) => {
resolve(params.message);
});
});
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
"default": false,
"description": "This is an experimental feature that enables the Ivy language service."
},
"angular.enable-experimental-ivy-prompt": {
"type": "boolean",
"default": true,
"description": "Prompt to enable the Ivy language service for the workspace when View Engine is in use."
},
"angular.trace.server": {
"type": "string",
"scope": "window",
Expand Down Expand Up @@ -159,4 +164,4 @@
"type": "git",
"url": "https://github.com/angular/vscode-ng-language-service"
}
}
}
2 changes: 1 addition & 1 deletion server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const session = new Session({
host,
logger,
ngPlugin: ng.name,
ngProbeLocation: ng.resolvedPath,
resolvedNgLsPath: ng.resolvedPath,
ivy: options.ivy,
logToConsole: options.logToConsole,
});
Expand Down
41 changes: 31 additions & 10 deletions server/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ import * as ts from 'typescript/lib/tsserverlibrary';
import * as lsp from 'vscode-languageserver/node';

import {ServerOptions} from '../common/initialize';
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart, SuggestStrictMode} from '../common/notifications';
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart, SuggestIvyLanguageService, SuggestStrictMode} from '../common/notifications';
import {NgccProgressToken, NgccProgressType} from '../common/progress';

import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
import {resolveAndRunNgcc} from './ngcc';
import {ServerHost} from './server_host';
import {filePathToUri, isConfiguredProject, lspPositionToTsPosition, lspRangeToTsPositions, tsTextSpanToLspRange, uriToFilePath} from './utils';
import {resolve, Version} from './version_provider';

export interface SessionOptions {
host: ServerHost;
logger: ts.server.Logger;
ngPlugin: string;
ngProbeLocation: string;
resolvedNgLsPath: string;
ivy: boolean;
logToConsole: boolean;
}
Expand All @@ -44,6 +45,7 @@ export class Session {
private readonly connection: lsp.Connection;
private readonly projectService: ts.server.ProjectService;
private readonly logger: ts.server.Logger;
private readonly angularCoreVersionMap = new WeakMap<ts.server.Project, Version>();
private readonly ivy: boolean;
private readonly configuredProjToExternalProj = new Map<string, string>();
private readonly logToConsole: boolean;
Expand Down Expand Up @@ -83,7 +85,7 @@ export class Session {
suppressDiagnosticEvents: true,
eventHandler: (e) => this.handleProjectServiceEvent(e),
globalPlugins: [options.ngPlugin],
pluginProbeLocations: [options.ngProbeLocation],
pluginProbeLocations: [options.resolvedNgLsPath],
allowLocalPluginLoads: false, // do not load plugins from tsconfig.json
});

Expand Down Expand Up @@ -836,7 +838,8 @@ export class Session {
return;
}

if (!this.checkIsAngularProject(project)) {
const coreDts = this.checkIsAngularProject(project);
if (coreDts === undefined) {
return;
}

Expand All @@ -850,22 +853,40 @@ export class Session {
} else {
// Immediately enable Legacy/ViewEngine language service
this.info(`Enabling VE language service for ${projectName}.`);
this.promptToEnableIvyIfAvailable(project, coreDts);
}
}

private promptToEnableIvyIfAvailable(
project: ts.server.Project, coreDts: ts.server.NormalizedPath) {
let angularCoreVersion = this.angularCoreVersionMap.get(project);
if (angularCoreVersion === undefined) {
angularCoreVersion = resolve('@angular/core', coreDts)?.version;
}

if (angularCoreVersion !== undefined && !this.ivy && angularCoreVersion.major >= 9) {
this.connection.sendNotification(SuggestIvyLanguageService, {
message:
'Would you like to enable the new Ivy-native language service to get the latest features and bug fixes?',
});
}
}

/**
* Determine if the specified `project` is Angular, and disable the language
* service if not.
*
* @returns The `ts.server.NormalizedPath` to the `@angular/core/core.d.ts` file.
*/
private checkIsAngularProject(project: ts.server.Project): boolean {
private checkIsAngularProject(project: ts.server.Project): ts.server.NormalizedPath|undefined {
const {projectName} = project;
const NG_CORE = '@angular/core/core.d.ts';

const isAngularProject = project.hasRoots() && !project.isNonTsProject() &&
project.getFileNames().some(f => f.endsWith(NG_CORE));
const ngCoreDts = project.getFileNames().find(f => f.endsWith(NG_CORE));
const isAngularProject =
project.hasRoots() && !project.isNonTsProject() && ngCoreDts !== undefined;

if (isAngularProject) {
return true;
return ngCoreDts;
}

project.disableLanguageService();
Expand All @@ -879,7 +900,7 @@ export class Session {
`Please check your tsconfig.json to make sure 'node_modules' directory is not excluded.`);
}

return false;
return undefined;
}
}

Expand Down
2 changes: 1 addition & 1 deletion server/src/version_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface NodeModule {
version: Version;
}

function resolve(packageName: string, location: string, rootPackage?: string): NodeModule|
export function resolve(packageName: string, location: string, rootPackage?: string): NodeModule|
undefined {
rootPackage = rootPackage || packageName;
try {
Expand Down