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

feat(language-service): support global directives completion #4989

Merged
merged 14 commits into from
Dec 31, 2024
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
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
Loading