Skip to content

Commit

Permalink
feat(language-service): support global directives completion (#4989)
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX authored Dec 31, 2024
1 parent 31c6995 commit 3d76d11
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 108 deletions.
8 changes: 4 additions & 4 deletions packages/language-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twos
import { parse, VueCompilerOptions } from '@vue/language-core';
import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/common';
import { collectExtractProps } from '@vue/typescript-plugin/lib/requests/collectExtractProps';
import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from '@vue/typescript-plugin/lib/requests/componentInfos';
import { getComponentDirectives, getComponentEvents, getComponentNames, getComponentProps, getElementAttrs } from '@vue/typescript-plugin/lib/requests/componentInfos';
import { getImportPathForFile } from '@vue/typescript-plugin/lib/requests/getImportPathForFile';
import { getPropertiesAtLocation } from '@vue/typescript-plugin/lib/requests/getPropertiesAtLocation';
import type { RequestContext } from '@vue/typescript-plugin/lib/requests/types';
Expand Down Expand Up @@ -118,6 +118,9 @@ export function getFullLanguageServicePlugins(
async getComponentEvents(...args) {
return await getComponentEvents.apply(requestContext, args);
},
async getComponentDirectives(...args) {
return await getComponentDirectives.apply(requestContext, args);
},
async getComponentNames(...args) {
return await getComponentNames.apply(requestContext, args);
},
Expand All @@ -127,9 +130,6 @@ export function getFullLanguageServicePlugins(
async getElementAttrs(...args) {
return await getElementAttrs.apply(requestContext, args);
},
async getTemplateContextProps(...args) {
return await getTemplateContextProps.apply(requestContext, args);
},
async getQuickInfoAtPosition(fileName, position) {
const languageService = context.getLanguageService();
const uri = context.project.typescript!.uriConverter.asUri(fileName);
Expand Down
44 changes: 12 additions & 32 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Disposable, LanguageServiceContext, LanguageServicePluginInstance } from '@volar/language-service';
import { VueCompilerOptions, VueVirtualCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core';
import { VueCompilerOptions, VueVirtualCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges } from '@vue/language-core';
import { camelize, capitalize } from '@vue/shared';
import { getComponentSpans } from '@vue/typescript-plugin/lib/common';
import { create as createHtmlService } from 'volar-service-html';
Expand Down Expand Up @@ -491,11 +491,11 @@ export function create(
attrs: string[];
propsInfo: { name: string, commentMarkdown?: string; }[];
events: string[];
directives: string[];
}>();

let version = 0;
let components: string[] | undefined;
let templateContextProps: string[] | undefined;

tsDocumentations.clear();

Expand Down Expand Up @@ -562,54 +562,27 @@ export function create(
const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? [];
const propsInfo = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
const directives = await tsPluginClient?.getComponentDirectives(vueCode.fileName) ?? [];
tagInfos.set(tag, {
attrs,
propsInfo: propsInfo.filter(prop =>
!prop.name.startsWith('ref_')
),
events,
directives,
});
version++;
})());
return [];
}

const { attrs, propsInfo, events } = tagInfo;
const { attrs, propsInfo, events, directives } = tagInfo;
const props = propsInfo.map(prop =>
hyphenateTag(prop.name).startsWith('on-vnode-')
? 'onVue:' + prop.name.slice('onVnode'.length)
: prop.name
);
const attributes: html.IAttributeData[] = [];
const _tsCodegen = tsCodegen.get(vueCode._sfc);

if (_tsCodegen) {
if (!templateContextProps) {
promises.push((async () => {
templateContextProps = await tsPluginClient?.getTemplateContextProps(vueCode.fileName) ?? [];
version++;
})());
return [];
}
let ctxVars = [
..._tsCodegen.scriptRanges.get()?.bindings.map(
({ range }) => vueCode._sfc.script!.content.slice(range.start, range.end)
) ?? [],
..._tsCodegen.scriptSetupRanges.get()?.bindings.map(
({ range }) => vueCode._sfc.scriptSetup!.content.slice(range.start, range.end)
) ?? [],
...templateContextProps,
];
ctxVars = [...new Set(ctxVars)];
const dirs = ctxVars.map(hyphenateAttr).filter(v => v.startsWith('v-'));
for (const dir of dirs) {
attributes.push(
{
name: dir,
}
);
}
}

const propsSet = new Set(props);

Expand Down Expand Up @@ -685,6 +658,13 @@ export function create(
);
}

for (const directive of directives) {
const name = hyphenateAttr(directive);
attributes.push({
name
});
}

const models: [boolean, string][] = [];

for (const prop of [...props, ...attrs]) {
Expand Down
91 changes: 29 additions & 62 deletions packages/typescript-plugin/lib/client.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,21 @@
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']>>(
'collectExtractProps',
...args
);
}
export const collectExtractProps = createRequest<
typeof import('./requests/collectExtractProps.js')['collectExtractProps']
>('collectExtractProps');

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

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

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

// Component Infos

Expand All @@ -47,23 +27,13 @@ export async function getComponentProps(fileName: string, componentName: string)
return await server.getComponentProps(fileName, componentName);
}

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

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

export async function getComponentNames(fileName: string) {
const server = await getBestServer(fileName);
Expand All @@ -77,19 +47,16 @@ export async function getComponentNames(fileName: string) {
return Object.keys(componentAndProps);
}

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

async function sendRequest<T>(requestType: RequestData[1], fileName: string, ...rest: any[]) {
const server = await getBestServer(fileName);
if (!server) {
return;
}
return server.sendRequest<T>(requestType, fileName, ...rest);
function createRequest<T extends (...args: any) => any>(requestType: RequestData[1]) {
return async function (...[fileName, ...rest]: Parameters<T>) {
const server = await getBestServer(fileName);
if (!server) {
return;
}
return server.sendRequest<ReturnType<T>>(requestType, fileName, ...rest);
};
}
18 changes: 12 additions & 6 deletions packages/typescript-plugin/lib/requests/componentInfos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function getComponentEvents(
return [...result];
}

export function getTemplateContextProps(
export function getComponentDirectives(
this: RequestContext,
fileName: string
) {
Expand All @@ -132,11 +132,15 @@ export function getTemplateContextProps(
return;
}
const vueCode = volarFile.generated.root;
const directives = getVariableType(ts, languageService, vueCode, '__VLS_directives');
if (!directives) {
return [];
}

return getVariableType(ts, languageService, vueCode, '__VLS_ctx')
?.type
?.getProperties()
.map(c => c.name);
return directives.type.getProperties()
.map(({ name }) => name)
.filter(name => name.startsWith('v') && name.length >= 2 && name[1] === name[1].toUpperCase())
.filter(name => !['vBind', 'vIf', 'vOn', 'VOnce', 'vShow', 'VSlot'].includes(name));
}

export function getComponentNames(
Expand Down Expand Up @@ -184,7 +188,9 @@ export function getElementAttrs(

if (tsSourceFile = program.getSourceFile(fileName)) {

const typeNode = tsSourceFile.statements.find((node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) && node.name.getText() === '__VLS_IntrinsicElementsCompletion');
const typeNode = tsSourceFile.statements
.filter(ts.isTypeAliasDeclaration)
.find(node => node.name.getText() === '__VLS_IntrinsicElementsCompletion');
const checker = program.getTypeChecker();

if (checker && typeNode) {
Expand Down
8 changes: 4 additions & 4 deletions packages/typescript-plugin/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as fs from 'node:fs';
import * as net from 'node:net';
import type * as ts from 'typescript';
import { collectExtractProps } from './requests/collectExtractProps';
import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from './requests/componentInfos';
import { getComponentDirectives, getComponentEvents, getComponentNames, getComponentProps, getElementAttrs } from './requests/componentInfos';
import { getImportPathForFile } from './requests/getImportPathForFile';
import { getPropertiesAtLocation } from './requests/getPropertiesAtLocation';
import { getQuickInfoAtPosition } from './requests/getQuickInfoAtPosition';
Expand All @@ -20,7 +20,7 @@ export type RequestType =
// Component Infos
| 'subscribeComponentProps'
| 'getComponentEvents'
| 'getTemplateContextProps'
| 'getComponentDirectives'
| 'getElementAttrs';

export type NotificationType =
Expand Down Expand Up @@ -252,8 +252,8 @@ export async function startNamedPipeServer(
else if (requestType === 'getComponentEvents') {
return getComponentEvents.apply(requestContext, args as any);
}
else if (requestType === 'getTemplateContextProps') {
return getTemplateContextProps.apply(requestContext, args as any);
else if (requestType === 'getComponentDirectives') {
return getComponentDirectives.apply(requestContext, args as any);
}
else if (requestType === 'getElementAttrs') {
return getElementAttrs.apply(requestContext, args as any);
Expand Down

0 comments on commit 3d76d11

Please sign in to comment.