Skip to content

Commit 3d76d11

Browse files
authored
feat(language-service): support global directives completion (#4989)
1 parent 31c6995 commit 3d76d11

File tree

5 files changed

+61
-108
lines changed

5 files changed

+61
-108
lines changed

packages/language-service/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twos
3131
import { parse, VueCompilerOptions } from '@vue/language-core';
3232
import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/common';
3333
import { collectExtractProps } from '@vue/typescript-plugin/lib/requests/collectExtractProps';
34-
import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from '@vue/typescript-plugin/lib/requests/componentInfos';
34+
import { getComponentDirectives, getComponentEvents, getComponentNames, getComponentProps, getElementAttrs } from '@vue/typescript-plugin/lib/requests/componentInfos';
3535
import { getImportPathForFile } from '@vue/typescript-plugin/lib/requests/getImportPathForFile';
3636
import { getPropertiesAtLocation } from '@vue/typescript-plugin/lib/requests/getPropertiesAtLocation';
3737
import type { RequestContext } from '@vue/typescript-plugin/lib/requests/types';
@@ -118,6 +118,9 @@ export function getFullLanguageServicePlugins(
118118
async getComponentEvents(...args) {
119119
return await getComponentEvents.apply(requestContext, args);
120120
},
121+
async getComponentDirectives(...args) {
122+
return await getComponentDirectives.apply(requestContext, args);
123+
},
121124
async getComponentNames(...args) {
122125
return await getComponentNames.apply(requestContext, args);
123126
},
@@ -127,9 +130,6 @@ export function getFullLanguageServicePlugins(
127130
async getElementAttrs(...args) {
128131
return await getElementAttrs.apply(requestContext, args);
129132
},
130-
async getTemplateContextProps(...args) {
131-
return await getTemplateContextProps.apply(requestContext, args);
132-
},
133133
async getQuickInfoAtPosition(fileName, position) {
134134
const languageService = context.getLanguageService();
135135
const uri = context.project.typescript!.uriConverter.asUri(fileName);

packages/language-service/lib/plugins/vue-template.ts

+12-32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Disposable, LanguageServiceContext, LanguageServicePluginInstance } from '@volar/language-service';
2-
import { VueCompilerOptions, VueVirtualCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core';
2+
import { VueCompilerOptions, VueVirtualCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges } from '@vue/language-core';
33
import { camelize, capitalize } from '@vue/shared';
44
import { getComponentSpans } from '@vue/typescript-plugin/lib/common';
55
import { create as createHtmlService } from 'volar-service-html';
@@ -491,11 +491,11 @@ export function create(
491491
attrs: string[];
492492
propsInfo: { name: string, commentMarkdown?: string; }[];
493493
events: string[];
494+
directives: string[];
494495
}>();
495496

496497
let version = 0;
497498
let components: string[] | undefined;
498-
let templateContextProps: string[] | undefined;
499499

500500
tsDocumentations.clear();
501501

@@ -562,54 +562,27 @@ export function create(
562562
const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? [];
563563
const propsInfo = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
564564
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
565+
const directives = await tsPluginClient?.getComponentDirectives(vueCode.fileName) ?? [];
565566
tagInfos.set(tag, {
566567
attrs,
567568
propsInfo: propsInfo.filter(prop =>
568569
!prop.name.startsWith('ref_')
569570
),
570571
events,
572+
directives,
571573
});
572574
version++;
573575
})());
574576
return [];
575577
}
576578

577-
const { attrs, propsInfo, events } = tagInfo;
579+
const { attrs, propsInfo, events, directives } = tagInfo;
578580
const props = propsInfo.map(prop =>
579581
hyphenateTag(prop.name).startsWith('on-vnode-')
580582
? 'onVue:' + prop.name.slice('onVnode'.length)
581583
: prop.name
582584
);
583585
const attributes: html.IAttributeData[] = [];
584-
const _tsCodegen = tsCodegen.get(vueCode._sfc);
585-
586-
if (_tsCodegen) {
587-
if (!templateContextProps) {
588-
promises.push((async () => {
589-
templateContextProps = await tsPluginClient?.getTemplateContextProps(vueCode.fileName) ?? [];
590-
version++;
591-
})());
592-
return [];
593-
}
594-
let ctxVars = [
595-
..._tsCodegen.scriptRanges.get()?.bindings.map(
596-
({ range }) => vueCode._sfc.script!.content.slice(range.start, range.end)
597-
) ?? [],
598-
..._tsCodegen.scriptSetupRanges.get()?.bindings.map(
599-
({ range }) => vueCode._sfc.scriptSetup!.content.slice(range.start, range.end)
600-
) ?? [],
601-
...templateContextProps,
602-
];
603-
ctxVars = [...new Set(ctxVars)];
604-
const dirs = ctxVars.map(hyphenateAttr).filter(v => v.startsWith('v-'));
605-
for (const dir of dirs) {
606-
attributes.push(
607-
{
608-
name: dir,
609-
}
610-
);
611-
}
612-
}
613586

614587
const propsSet = new Set(props);
615588

@@ -685,6 +658,13 @@ export function create(
685658
);
686659
}
687660

661+
for (const directive of directives) {
662+
const name = hyphenateAttr(directive);
663+
attributes.push({
664+
name
665+
});
666+
}
667+
688668
const models: [boolean, string][] = [];
689669

690670
for (const prop of [...props, ...attrs]) {
+29-62
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,21 @@
11
import type { RequestData } from './server';
22
import { getBestServer } from './utils';
33

4-
export function collectExtractProps(
5-
...args: Parameters<typeof import('./requests/collectExtractProps.js')['collectExtractProps']>
6-
) {
7-
return sendRequest<ReturnType<typeof import('./requests/collectExtractProps')['collectExtractProps']>>(
8-
'collectExtractProps',
9-
...args
10-
);
11-
}
4+
export const collectExtractProps = createRequest<
5+
typeof import('./requests/collectExtractProps.js')['collectExtractProps']
6+
>('collectExtractProps');
127

13-
export async function getImportPathForFile(
14-
...args: Parameters<typeof import('./requests/getImportPathForFile.js')['getImportPathForFile']>
15-
) {
16-
return await sendRequest<ReturnType<typeof import('./requests/getImportPathForFile')['getImportPathForFile']>>(
17-
'getImportPathForFile',
18-
...args
19-
);
20-
}
8+
export const getImportPathForFile = createRequest<
9+
typeof import('./requests/getImportPathForFile.js')['getImportPathForFile']
10+
>('getImportPathForFile');
2111

22-
export async function getPropertiesAtLocation(
23-
...args: Parameters<typeof import('./requests/getPropertiesAtLocation.js')['getPropertiesAtLocation']>
24-
) {
25-
return await sendRequest<ReturnType<typeof import('./requests/getPropertiesAtLocation')['getPropertiesAtLocation']>>(
26-
'getPropertiesAtLocation',
27-
...args
28-
);
29-
}
12+
export const getPropertiesAtLocation = createRequest<
13+
typeof import('./requests/getPropertiesAtLocation.js')['getPropertiesAtLocation']
14+
>('getPropertiesAtLocation');
3015

31-
export function getQuickInfoAtPosition(
32-
...args: Parameters<typeof import('./requests/getQuickInfoAtPosition.js')['getQuickInfoAtPosition']>
33-
) {
34-
return sendRequest<ReturnType<typeof import('./requests/getQuickInfoAtPosition')['getQuickInfoAtPosition']>>(
35-
'getQuickInfoAtPosition',
36-
...args
37-
);
38-
}
16+
export const getQuickInfoAtPosition = createRequest<
17+
typeof import('./requests/getQuickInfoAtPosition.js')['getQuickInfoAtPosition']
18+
>('getQuickInfoAtPosition');
3919

4020
// Component Infos
4121

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

50-
export function getComponentEvents(
51-
...args: Parameters<typeof import('./requests/componentInfos.js')['getComponentEvents']>
52-
) {
53-
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getComponentEvents']>>(
54-
'getComponentEvents',
55-
...args
56-
);
57-
}
30+
export const getComponentEvents = createRequest<
31+
typeof import('./requests/componentInfos.js')['getComponentEvents']
32+
>('getComponentEvents');
5833

59-
export function getTemplateContextProps(
60-
...args: Parameters<typeof import('./requests/componentInfos.js')['getTemplateContextProps']>
61-
) {
62-
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getTemplateContextProps']>>(
63-
'getTemplateContextProps',
64-
...args
65-
);
66-
}
34+
export const getComponentDirectives = createRequest<
35+
typeof import('./requests/componentInfos.js')['getComponentDirectives']
36+
>('getComponentDirectives');
6737

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

80-
export function getElementAttrs(
81-
...args: Parameters<typeof import('./requests/componentInfos.js')['getElementAttrs']>
82-
) {
83-
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getElementAttrs']>>(
84-
'getElementAttrs',
85-
...args
86-
);
87-
}
50+
export const getElementAttrs = createRequest<
51+
typeof import('./requests/componentInfos.js')['getElementAttrs']
52+
>('getElementAttrs');
8853

89-
async function sendRequest<T>(requestType: RequestData[1], fileName: string, ...rest: any[]) {
90-
const server = await getBestServer(fileName);
91-
if (!server) {
92-
return;
93-
}
94-
return server.sendRequest<T>(requestType, fileName, ...rest);
54+
function createRequest<T extends (...args: any) => any>(requestType: RequestData[1]) {
55+
return async function (...[fileName, ...rest]: Parameters<T>) {
56+
const server = await getBestServer(fileName);
57+
if (!server) {
58+
return;
59+
}
60+
return server.sendRequest<ReturnType<T>>(requestType, fileName, ...rest);
61+
};
9562
}

packages/typescript-plugin/lib/requests/componentInfos.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function getComponentEvents(
122122
return [...result];
123123
}
124124

125-
export function getTemplateContextProps(
125+
export function getComponentDirectives(
126126
this: RequestContext,
127127
fileName: string
128128
) {
@@ -132,11 +132,15 @@ export function getTemplateContextProps(
132132
return;
133133
}
134134
const vueCode = volarFile.generated.root;
135+
const directives = getVariableType(ts, languageService, vueCode, '__VLS_directives');
136+
if (!directives) {
137+
return [];
138+
}
135139

136-
return getVariableType(ts, languageService, vueCode, '__VLS_ctx')
137-
?.type
138-
?.getProperties()
139-
.map(c => c.name);
140+
return directives.type.getProperties()
141+
.map(({ name }) => name)
142+
.filter(name => name.startsWith('v') && name.length >= 2 && name[1] === name[1].toUpperCase())
143+
.filter(name => !['vBind', 'vIf', 'vOn', 'VOnce', 'vShow', 'VSlot'].includes(name));
140144
}
141145

142146
export function getComponentNames(
@@ -184,7 +188,9 @@ export function getElementAttrs(
184188

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

187-
const typeNode = tsSourceFile.statements.find((node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) && node.name.getText() === '__VLS_IntrinsicElementsCompletion');
191+
const typeNode = tsSourceFile.statements
192+
.filter(ts.isTypeAliasDeclaration)
193+
.find(node => node.name.getText() === '__VLS_IntrinsicElementsCompletion');
188194
const checker = program.getTypeChecker();
189195

190196
if (checker && typeNode) {

packages/typescript-plugin/lib/server.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
33
import * as net from 'node:net';
44
import type * as ts from 'typescript';
55
import { collectExtractProps } from './requests/collectExtractProps';
6-
import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from './requests/componentInfos';
6+
import { getComponentDirectives, getComponentEvents, getComponentNames, getComponentProps, getElementAttrs } from './requests/componentInfos';
77
import { getImportPathForFile } from './requests/getImportPathForFile';
88
import { getPropertiesAtLocation } from './requests/getPropertiesAtLocation';
99
import { getQuickInfoAtPosition } from './requests/getQuickInfoAtPosition';
@@ -20,7 +20,7 @@ export type RequestType =
2020
// Component Infos
2121
| 'subscribeComponentProps'
2222
| 'getComponentEvents'
23-
| 'getTemplateContextProps'
23+
| 'getComponentDirectives'
2424
| 'getElementAttrs';
2525

2626
export type NotificationType =
@@ -252,8 +252,8 @@ export async function startNamedPipeServer(
252252
else if (requestType === 'getComponentEvents') {
253253
return getComponentEvents.apply(requestContext, args as any);
254254
}
255-
else if (requestType === 'getTemplateContextProps') {
256-
return getTemplateContextProps.apply(requestContext, args as any);
255+
else if (requestType === 'getComponentDirectives') {
256+
return getComponentDirectives.apply(requestContext, args as any);
257257
}
258258
else if (requestType === 'getElementAttrs') {
259259
return getElementAttrs.apply(requestContext, args as any);

0 commit comments

Comments
 (0)