Skip to content

Commit

Permalink
perf: Avoid making Angular-related decisions for files not in an Angu…
Browse files Browse the repository at this point in the history
…lar project (#1259)

This commit updates our client-side short-circuit logic to provide an
additiona short circuit that avoids tokenizing typescript files when
they are not within a project that uses Angular.

fixes #1237
  • Loading branch information
atscott authored and Keen Yee Liau committed Apr 9, 2021
1 parent 4eed99b commit 154cf5e
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 31 deletions.
96 changes: 67 additions & 29 deletions client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as lsp from 'vscode-languageclient/node';

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

import {isInsideComponentDecorator, isInsideInlineTemplateRegion} from './embedded_support';
import {ProgressReporter} from './progress-reporter';
Expand All @@ -30,6 +30,9 @@ export class AngularLanguageClient implements vscode.Disposable {
private readonly outputChannel: vscode.OutputChannel;
private readonly clientOptions: lsp.LanguageClientOptions;
private readonly name = 'Angular Language Service';
private readonly virtualDocumentContents = new Map<string, string>();
/** A map that indicates whether Angular could be found in the file's project. */
private readonly fileToIsInAngularProjectMap = new Map<string, boolean>();

constructor(private readonly context: vscode.ExtensionContext) {
this.outputChannel = vscode.window.createOutputChannel(this.name);
Expand All @@ -55,36 +58,71 @@ export class AngularLanguageClient implements vscode.Disposable {
provideDefinition: async (
document: vscode.TextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: lsp.ProvideDefinitionSignature) => {
if (isInsideComponentDecorator(document, position)) {
if (await this.isInAngularProject(document) &&
isInsideComponentDecorator(document, position)) {
return next(document, position, token);
}
},
provideTypeDefinition: async (
document: vscode.TextDocument, position: vscode.Position,
token: vscode.CancellationToken, next) => {
if (isInsideInlineTemplateRegion(document, position)) {
if (await this.isInAngularProject(document) &&
isInsideInlineTemplateRegion(document, position)) {
return next(document, position, token);
}
},
provideHover: async (
document: vscode.TextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: lsp.ProvideHoverSignature) => {
if (isInsideInlineTemplateRegion(document, position)) {
return next(document, position, token);
if (!(await this.isInAngularProject(document)) ||
!isInsideInlineTemplateRegion(document, position)) {
return;
}
return next(document, position, token);
},
provideCompletionItem: async (
document: vscode.TextDocument, position: vscode.Position,
context: vscode.CompletionContext, token: vscode.CancellationToken,
next: lsp.ProvideCompletionItemsSignature) => {
if (isInsideInlineTemplateRegion(document, position)) {
return next(document, position, context, token);
// If not in inline template, do not perform request forwarding
if (!(await this.isInAngularProject(document)) ||
!isInsideInlineTemplateRegion(document, position)) {
return;
}
return next(document, position, context, token);
}
}
};
}

private async isInAngularProject(doc: vscode.TextDocument): Promise<boolean> {
if (this.client === null) {
return false;
}
const uri = doc.uri.toString();
if (this.fileToIsInAngularProjectMap.has(uri)) {
return this.fileToIsInAngularProjectMap.get(uri)!;
}

try {
const response = await this.client.sendRequest(IsInAngularProject, {
textDocument: this.client.code2ProtocolConverter.asTextDocumentIdentifier(doc),
});
this.fileToIsInAngularProjectMap.set(uri, response);
return response;
} catch {
return false;
}
}

private createVirtualHtmlDoc(document: vscode.TextDocument): vscode.Uri {
const originalUri = document.uri.toString();
const vdocUri = vscode.Uri.file(encodeURIComponent(originalUri) + '.html')
.with({scheme: 'angular-embedded-content', authority: 'html'});
this.virtualDocumentContents.set(vdocUri.toString(), document.getText());
return vdocUri;
}

/**
* Spin up the language server in a separate process and establish a connection.
*/
Expand Down Expand Up @@ -171,7 +209,8 @@ export class AngularLanguageClient implements vscode.Disposable {
}

function registerNotificationHandlers(client: lsp.LanguageClient): vscode.Disposable {
const disposable1 = client.onNotification(ProjectLoadingStart, () => {
const disposables: vscode.Disposable[] = [];
disposables.push(client.onNotification(ProjectLoadingStart, () => {
vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
Expand All @@ -181,27 +220,26 @@ function registerNotificationHandlers(client: lsp.LanguageClient): vscode.Dispos
client.onNotification(ProjectLoadingFinish, resolve);
}),
);
});
}));

const disposable2 =
client.onNotification(SuggestStrictMode, async (params: SuggestStrictModeParams) => {
const openTsConfig = 'Open tsconfig.json';
// Markdown is not generally supported in `showInformationMessage()`,
// but links are supported. See
// https://github.com/microsoft/vscode/issues/20595#issuecomment-281099832
const selection = await vscode.window.showInformationMessage(
'Some language features are not available. To access all features, enable ' +
'[strictTemplates](https://angular.io/guide/angular-compiler-options#stricttemplates) in ' +
'[angularCompilerOptions](https://angular.io/guide/angular-compiler-options).',
openTsConfig,
);
if (selection === openTsConfig) {
const document = await vscode.workspace.openTextDocument(params.configFilePath);
vscode.window.showTextDocument(document);
}
});
disposables.push(client.onNotification(SuggestStrictMode, async (params: SuggestStrictModeParams) => {
const openTsConfig = 'Open tsconfig.json';
// Markdown is not generally supported in `showInformationMessage()`,
// but links are supported. See
// https://github.com/microsoft/vscode/issues/20595#issuecomment-281099832
const selection = await vscode.window.showInformationMessage(
'Some language features are not available. To access all features, enable ' +
'[strictTemplates](https://angular.io/guide/angular-compiler-options#stricttemplates) in ' +
'[angularCompilerOptions](https://angular.io/guide/angular-compiler-options).',
openTsConfig,
);
if (selection === openTsConfig) {
const document = await vscode.workspace.openTextDocument(params.configFilePath);
vscode.window.showTextDocument(document);
}
}));

const disposable3 = client.onNotification(
disposables.push(client.onNotification(
SuggestIvyLanguageService, async (params: SuggestIvyLanguageServiceParams) => {
const config = vscode.workspace.getConfiguration();
if (config.get('angular.enable-experimental-ivy-prompt') === false) {
Expand All @@ -221,9 +259,9 @@ function registerNotificationHandlers(client: lsp.LanguageClient): vscode.Dispos
config.update(
'angular.enable-experimental-ivy-prompt', false, vscode.ConfigurationTarget.Global);
}
});
}));

return vscode.Disposable.from(disposable1, disposable2, disposable3);
return vscode.Disposable.from(...disposables);
}

function registerProgressHandlers(client: lsp.LanguageClient) {
Expand Down
8 changes: 8 additions & 0 deletions common/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ export interface GetTcbResponse {
content: string;
selections: lsp.Range[]
}

export const IsInAngularProject =
new lsp.RequestType<IsInAngularProjectParams, boolean, /* error */ void>(
'angular/isAngularCoreInOwningProject');

export interface IsInAngularProjectParams {
textDocument: lsp.TextDocumentIdentifier;
}
19 changes: 18 additions & 1 deletion integration/lsp/ivy_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {URI} from 'vscode-uri';

import {ProjectLanguageService, ProjectLanguageServiceParams, SuggestStrictMode, SuggestStrictModeParams} from '../../common/notifications';
import {NgccProgress, NgccProgressToken, NgccProgressType} from '../../common/progress';
import {GetTcbRequest} from '../../common/requests';
import {GetTcbRequest, IsInAngularProject} from '../../common/requests';

import {APP_COMPONENT, createConnection, createTracer, FOO_COMPONENT, FOO_TEMPLATE, initializeServer, openTextDocument, PROJECT_PATH, TSCONFIG} from './test_utils';

Expand Down Expand Up @@ -380,6 +380,23 @@ describe('Angular Ivy language server', () => {
expect(response).toBeDefined();
});
});

it('detects an Angular project', async () => {
openTextDocument(client, FOO_TEMPLATE);
await waitForNgcc(client);
const templateResponse = await client.sendRequest(IsInAngularProject, {
textDocument: {
uri: `file://${FOO_TEMPLATE}`,
}
});
expect(templateResponse).toBe(true);
const componentResponse = await client.sendRequest(IsInAngularProject, {
textDocument: {
uri: `file://${FOO_COMPONENT}`,
}
});
expect(componentResponse).toBe(true);
})
});

function onNgccProgress(client: MessageConnection): Promise<string> {
Expand Down
23 changes: 22 additions & 1 deletion server/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as lsp from 'vscode-languageserver/node';
import {ServerOptions} from '../common/initialize';
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart, SuggestIvyLanguageService, SuggestStrictMode} from '../common/notifications';
import {NgccProgressToken, NgccProgressType} from '../common/progress';
import {GetTcbParams, GetTcbRequest, GetTcbResponse} from '../common/requests';
import {GetTcbParams, GetTcbRequest, GetTcbResponse, IsInAngularProject, IsInAngularProjectParams} from '../common/requests';

import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
Expand Down Expand Up @@ -164,6 +164,27 @@ export class Session {
conn.onCompletion(p => this.onCompletion(p));
conn.onCompletionResolve(p => this.onCompletionResolve(p));
conn.onRequest(GetTcbRequest, p => this.onGetTcb(p));
conn.onRequest(IsInAngularProject, p => this.isInAngularProject(p));
}

private isInAngularProject(params: IsInAngularProjectParams): boolean {
const filePath = uriToFilePath(params.textDocument.uri);
if (!filePath) {
return false;
}
const scriptInfo = this.projectService.getScriptInfo(filePath);
if (!scriptInfo) {
return false;
}
const project = this.projectService.getDefaultProjectForFile(
scriptInfo.fileName,
false // ensureProject
);
if (!project) {
return false;
}
const angularCore = project.getFileNames().find(isAngularCore);
return angularCore !== undefined;
}

private onGetTcb(params: GetTcbParams): GetTcbResponse|undefined {
Expand Down

0 comments on commit 154cf5e

Please sign in to comment.