Skip to content
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

perf(typescript-plugin): use named pipe servers more efficiently #5070

Merged
merged 17 commits into from
Dec 20, 2024
16 changes: 10 additions & 6 deletions packages/language-server/lib/hybridModeProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Language, LanguagePlugin, LanguageServer, LanguageServerProject, P
import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
import { createLanguage } from '@vue/language-core';
import { createLanguageService, createUriMap, LanguageService } from '@vue/language-service';
import { getReadyNamedPipePaths, onSomePipeReadyCallbacks, searchNamedPipeServerForFile } from '@vue/typescript-plugin/lib/utils';
import { configuredServers, getBestServer, inferredServers, onServerReady } from '@vue/typescript-plugin/lib/utils';
import { URI } from 'vscode-uri';

export function createHybridModeProject(
Expand All @@ -24,7 +24,7 @@ export function createHybridModeProject(
const project: LanguageServerProject = {
setup(_server) {
server = _server;
onSomePipeReadyCallbacks.push(() => {
onServerReady.push(() => {
server.languageFeatures.requestRefresh(false);
});
server.fileWatcher.onDidChangeWatchedFiles(({ changes }) => {
Expand All @@ -38,16 +38,20 @@ export function createHybridModeProject(
});
const end = Date.now() + 60000;
const pipeWatcher = setInterval(() => {
getReadyNamedPipePaths();
for (const server of configuredServers) {
server.update();
}
for (const server of inferredServers) {
server.update();
}
if (Date.now() > end) {
clearInterval(pipeWatcher);
}
}, 1000);
}, 2500);
},
async getLanguageService(uri) {
const fileName = asFileName(uri);
const namedPipeServer = (await searchNamedPipeServerForFile(fileName));
namedPipeServer?.socket.end();
const namedPipeServer = await getBestServer(fileName);
if (namedPipeServer?.projectInfo?.kind === 1) {
const tsconfig = namedPipeServer.projectInfo.name;
const tsconfigUri = URI.file(tsconfig);
Expand Down
8 changes: 5 additions & 3 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ export function create(
? tagName
: components.find(component => component === tagName || hyphenateTag(component) === tagName);
if (checkTag) {
componentProps[checkTag] ??= (await tsPluginClient?.getComponentProps(code.fileName, checkTag, true) ?? []).map(prop => prop.name);
componentProps[checkTag] ??= (await tsPluginClient?.getComponentProps(code.fileName, checkTag) ?? [])
.filter(prop => prop.required)
.map(prop => prop.name);
current = {
unburnedRequiredProps: [...componentProps[checkTag]],
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
Expand Down Expand Up @@ -469,7 +471,7 @@ export function create(
const promises: Promise<void>[] = [];
const tagInfos = new Map<string, {
attrs: string[];
propsInfo: { name: string, commentMarkdown: string; }[];
propsInfo: { name: string, commentMarkdown?: string; }[];
events: string[];
}>();

Expand Down Expand Up @@ -1010,7 +1012,7 @@ function parseLabel(label: string) {
return {
name,
leadingSlash
}
};
}

function generateItemKey(type: InternalItemId, tag: string, prop: string) {
Expand Down
103 changes: 53 additions & 50 deletions packages/typescript-plugin/lib/client.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,99 @@
import type { Request } from './server';
import { searchNamedPipeServerForFile, sendRequestWorker } from './utils';
import type { RequestData } from './server';
import { getBestServer } from './utils';

export function collectExtractProps(
...args: Parameters<typeof import('./requests/collectExtractProps.js')['collectExtractProps']>
) {
return sendRequest<ReturnType<typeof import('./requests/collectExtractProps')['collectExtractProps']>>({
type: 'collectExtractProps',
args,
});
return sendRequest<ReturnType<typeof import('./requests/collectExtractProps')['collectExtractProps']>>(
'collectExtractProps',
...args
);
}

export async function getImportPathForFile(
...args: Parameters<typeof import('./requests/getImportPathForFile.js')['getImportPathForFile']>
) {
return await sendRequest<ReturnType<typeof import('./requests/getImportPathForFile')['getImportPathForFile']>>({
type: 'getImportPathForFile',
args,
});
return await sendRequest<ReturnType<typeof import('./requests/getImportPathForFile')['getImportPathForFile']>>(
'getImportPathForFile',
...args
);
}

export async function getPropertiesAtLocation(
...args: Parameters<typeof import('./requests/getPropertiesAtLocation.js')['getPropertiesAtLocation']>
) {
return await sendRequest<ReturnType<typeof import('./requests/getPropertiesAtLocation')['getPropertiesAtLocation']>>({
type: 'getPropertiesAtLocation',
args,
});
return await sendRequest<ReturnType<typeof import('./requests/getPropertiesAtLocation')['getPropertiesAtLocation']>>(
'getPropertiesAtLocation',
...args
);
}

export function getQuickInfoAtPosition(
...args: Parameters<typeof import('./requests/getQuickInfoAtPosition.js')['getQuickInfoAtPosition']>
) {
return sendRequest<ReturnType<typeof import('./requests/getQuickInfoAtPosition')['getQuickInfoAtPosition']>>({
type: 'getQuickInfoAtPosition',
args,
});
return sendRequest<ReturnType<typeof import('./requests/getQuickInfoAtPosition')['getQuickInfoAtPosition']>>(
'getQuickInfoAtPosition',
...args
);
}

// Component Infos

export function getComponentProps(
...args: Parameters<typeof import('./requests/componentInfos.js')['getComponentProps']>
) {
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getComponentProps']>>({
type: 'getComponentProps',
args,
});
export async function getComponentProps(fileName: string, componentName: string) {
const server = await getBestServer(fileName);
if (!server) {
return;
}
const componentAndProps = await server.getAllComponentAndProps(fileName);
if (!componentAndProps) {
return;
}
return componentAndProps[componentName];
}

export function getComponentEvents(
...args: Parameters<typeof import('./requests/componentInfos.js')['getComponentEvents']>
) {
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getComponentEvents']>>({
type: 'getComponentEvents',
args,
});
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getComponentEvents']>>(
'getComponentEvents',
...args
);
}

export function getTemplateContextProps(
...args: Parameters<typeof import('./requests/componentInfos.js')['getTemplateContextProps']>
) {
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getTemplateContextProps']>>({
type: 'getTemplateContextProps',
args,
});
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getTemplateContextProps']>>(
'getTemplateContextProps',
...args
);
}

export function getComponentNames(
...args: Parameters<typeof import('./requests/componentInfos.js')['getComponentNames']>
) {
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getComponentNames']>>({
type: 'getComponentNames',
args,
});
export async function getComponentNames(fileName: string) {
const server = await getBestServer(fileName);
if (!server) {
return;
}
const componentAndProps = await server.getAllComponentAndProps(fileName);
if (!componentAndProps) {
return;
}
return Object.keys(componentAndProps);
}

export function getElementAttrs(
...args: Parameters<typeof import('./requests/componentInfos.js')['getElementAttrs']>
) {
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getElementAttrs']>>({
type: 'getElementAttrs',
args,
});
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getElementAttrs']>>(
'getElementAttrs',
...args
);
}

async function sendRequest<T>(request: Request) {
const server = (await searchNamedPipeServerForFile(request.args[0]));
async function sendRequest<T>(requestType: RequestData[1], fileName: string, ...rest: any[]) {
const server = await getBestServer(fileName);
if (!server) {
console.warn('[Vue Named Pipe Client] No server found for', request.args[0]);
return;
}
const res = await sendRequestWorker<T>(request, server.socket);
server.socket.end();
return res;
return server.request<T>(requestType, fileName, ...rest);
}
27 changes: 14 additions & 13 deletions packages/typescript-plugin/lib/requests/componentInfos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import type { RequestContext } from './types';
export function getComponentProps(
this: RequestContext,
fileName: string,
tag: string,
requiredOnly = false
tag: string
) {
const { typescript: ts, language, languageService, getFileId } = this;
const volarFile = language.scripts.get(getFileId(fileName));
Expand Down Expand Up @@ -47,20 +46,23 @@ export function getComponentProps(
}
}

const result = new Map<string, { name: string, commentMarkdown: string; }>();
const result = new Map<string, {
name: string;
required?: true;
commentMarkdown?: string;
}>();

for (const sig of componentType.getCallSignatures()) {
const propParam = sig.parameters[0];
if (propParam) {
const propsType = checker.getTypeOfSymbolAtLocation(propParam, components.node);
const props = propsType.getProperties();
for (const prop of props) {
if (!requiredOnly || !(prop.flags & ts.SymbolFlags.Optional)) {
const name = prop.name;
const commentMarkdown = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());
const name = prop.name;
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
const commentMarkdown = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags()) || undefined;

result.set(name, { name, commentMarkdown });
}
result.set(name, { name, required, commentMarkdown });
}
}
}
Expand All @@ -75,12 +77,11 @@ export function getComponentProps(
if (prop.flags & ts.SymbolFlags.Method) { // #2443
continue;
}
if (!requiredOnly || !(prop.flags & ts.SymbolFlags.Optional)) {
const name = prop.name;
const commentMarkdown = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());
const name = prop.name;
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
const commentMarkdown = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags()) || undefined;

result.set(name, { name, commentMarkdown });
}
result.set(name, { name, required, commentMarkdown });
}
}
}
Expand Down
Loading
Loading