diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 237444e3d5..73b086d39d 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -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'; @@ -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); }, @@ -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); diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index f9dab9e8aa..f1d26d468b 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -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'; @@ -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(); @@ -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); @@ -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]) { diff --git a/packages/typescript-plugin/lib/client.ts b/packages/typescript-plugin/lib/client.ts index fea8bef22d..4aab0b6ef4 100644 --- a/packages/typescript-plugin/lib/client.ts +++ b/packages/typescript-plugin/lib/client.ts @@ -1,41 +1,21 @@ import type { RequestData } from './server'; import { getBestServer } from './utils'; -export function collectExtractProps( - ...args: Parameters -) { - return sendRequest>( - 'collectExtractProps', - ...args - ); -} +export const collectExtractProps = createRequest< + typeof import('./requests/collectExtractProps.js')['collectExtractProps'] +>('collectExtractProps'); -export async function getImportPathForFile( - ...args: Parameters -) { - return await sendRequest>( - 'getImportPathForFile', - ...args - ); -} +export const getImportPathForFile = createRequest< + typeof import('./requests/getImportPathForFile.js')['getImportPathForFile'] +>('getImportPathForFile'); -export async function getPropertiesAtLocation( - ...args: Parameters -) { - return await sendRequest>( - 'getPropertiesAtLocation', - ...args - ); -} +export const getPropertiesAtLocation = createRequest< + typeof import('./requests/getPropertiesAtLocation.js')['getPropertiesAtLocation'] +>('getPropertiesAtLocation'); -export function getQuickInfoAtPosition( - ...args: Parameters -) { - return sendRequest>( - 'getQuickInfoAtPosition', - ...args - ); -} +export const getQuickInfoAtPosition = createRequest< + typeof import('./requests/getQuickInfoAtPosition.js')['getQuickInfoAtPosition'] +>('getQuickInfoAtPosition'); // Component Infos @@ -47,23 +27,13 @@ export async function getComponentProps(fileName: string, componentName: string) return await server.getComponentProps(fileName, componentName); } -export function getComponentEvents( - ...args: Parameters -) { - return sendRequest>( - 'getComponentEvents', - ...args - ); -} +export const getComponentEvents = createRequest< + typeof import('./requests/componentInfos.js')['getComponentEvents'] +>('getComponentEvents'); -export function getTemplateContextProps( - ...args: Parameters -) { - return sendRequest>( - 'getTemplateContextProps', - ...args - ); -} +export const getComponentDirectives = createRequest< + typeof import('./requests/componentInfos.js')['getComponentDirectives'] +>('getComponentDirectives'); export async function getComponentNames(fileName: string) { const server = await getBestServer(fileName); @@ -77,19 +47,16 @@ export async function getComponentNames(fileName: string) { return Object.keys(componentAndProps); } -export function getElementAttrs( - ...args: Parameters -) { - return sendRequest>( - 'getElementAttrs', - ...args - ); -} +export const getElementAttrs = createRequest< + typeof import('./requests/componentInfos.js')['getElementAttrs'] +>('getElementAttrs'); -async function sendRequest(requestType: RequestData[1], fileName: string, ...rest: any[]) { - const server = await getBestServer(fileName); - if (!server) { - return; - } - return server.sendRequest(requestType, fileName, ...rest); +function createRequest any>(requestType: RequestData[1]) { + return async function (...[fileName, ...rest]: Parameters) { + const server = await getBestServer(fileName); + if (!server) { + return; + } + return server.sendRequest>(requestType, fileName, ...rest); + }; } diff --git a/packages/typescript-plugin/lib/requests/componentInfos.ts b/packages/typescript-plugin/lib/requests/componentInfos.ts index b3920c23f2..1df0adcd5b 100644 --- a/packages/typescript-plugin/lib/requests/componentInfos.ts +++ b/packages/typescript-plugin/lib/requests/componentInfos.ts @@ -122,7 +122,7 @@ export function getComponentEvents( return [...result]; } -export function getTemplateContextProps( +export function getComponentDirectives( this: RequestContext, fileName: string ) { @@ -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( @@ -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) { diff --git a/packages/typescript-plugin/lib/server.ts b/packages/typescript-plugin/lib/server.ts index 752cca0b0f..753d76d1a0 100644 --- a/packages/typescript-plugin/lib/server.ts +++ b/packages/typescript-plugin/lib/server.ts @@ -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'; @@ -20,7 +20,7 @@ export type RequestType = // Component Infos | 'subscribeComponentProps' | 'getComponentEvents' - | 'getTemplateContextProps' + | 'getComponentDirectives' | 'getElementAttrs'; export type NotificationType = @@ -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);