From 2dcef0d96614d7db5ff135180320685d64b12700 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 5 Mar 2025 22:31:32 +0800 Subject: [PATCH 1/7] refactor: replace named pipes with request forwarding --- extensions/vscode/src/languageClient.ts | 5 +- extensions/vscode/src/nodeClientMain.ts | 20 ++ .../language-server/lib/hybridModeProject.ts | 50 +-- packages/language-server/lib/types.ts | 1 + packages/language-server/node.ts | 47 ++- packages/language-server/tests/server.ts | 19 +- packages/language-service/index.ts | 12 +- .../lib/ideFeatures/nameCasing.ts | 8 +- .../lib/plugins/vue-autoinsert-dotvalue.ts | 2 +- .../lib/plugins/vue-document-drop.ts | 2 +- .../lib/plugins/vue-extract-file.ts | 2 +- .../lib/plugins/vue-template.ts | 6 +- .../lib/plugins/vue-twoslash-queries.ts | 2 +- packages/typescript-plugin/index.ts | 122 ++++++- packages/typescript-plugin/lib/client.ts | 62 ---- packages/typescript-plugin/lib/common.ts | 4 +- ...omponentNames.ts => getComponentsNames.ts} | 6 +- .../lib/requests/getPropertiesAtLocation.ts | 2 + .../typescript-plugin/lib/requests/index.ts | 13 + packages/typescript-plugin/lib/server.ts | 325 ------------------ packages/typescript-plugin/lib/utils.ts | 276 --------------- 21 files changed, 228 insertions(+), 758 deletions(-) delete mode 100644 packages/typescript-plugin/lib/client.ts rename packages/typescript-plugin/lib/requests/{getComponentNames.ts => getComponentsNames.ts} (86%) create mode 100644 packages/typescript-plugin/lib/requests/index.ts delete mode 100644 packages/typescript-plugin/lib/server.ts delete mode 100644 packages/typescript-plugin/lib/utils.ts diff --git a/extensions/vscode/src/languageClient.ts b/extensions/vscode/src/languageClient.ts index 97442bda5f..e207f78339 100644 --- a/extensions/vscode/src/languageClient.ts +++ b/extensions/vscode/src/languageClient.ts @@ -107,6 +107,9 @@ async function activateLc( async function getInitializationOptions(context: vscode.ExtensionContext): Promise { return { - typescript: { tsdk: (await lsp.getTsdk(context))!.tsdk }, + typescript: { + tsdk: (await lsp.getTsdk(context))!.tsdk, + serverProxy: 'tsserverRequest', + }, }; } diff --git a/extensions/vscode/src/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index 8a1fbc3337..f1438b3a12 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -86,6 +86,20 @@ export const { activate, deactivate } = defineExtension(async () => { updateProviders(client); + client.onRequest('tsserverRequest', async ([command, args]) => { + const tsserver = (globalThis as any).__TSSERVER__?.semantic; + if (!tsserver) { + return; + } + const res = await tsserver.executeImpl(command, args, { + isAsync: true, + expectsResult: true, + lowPriority: true, + requireSemantic: true, + })[0]; + return res.body; + }); + return client; } ); @@ -140,6 +154,12 @@ try { ].join('') ); + // Expose tsserver process in SingleTsServer constructor + text = text.replace( + ',this._callbacks.destroy("server errored")}))', + s => s + ',globalThis.__TSSERVER__||={},globalThis.__TSSERVER__[arguments[1]]=this' + ); + /** * VSCode < 1.87.0 */ diff --git a/packages/language-server/lib/hybridModeProject.ts b/packages/language-server/lib/hybridModeProject.ts index e9747126bd..4161ff9b80 100644 --- a/packages/language-server/lib/hybridModeProject.ts +++ b/packages/language-server/lib/hybridModeProject.ts @@ -2,14 +2,10 @@ 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 { configuredServers, getBestServer, inferredServers, onServerReady } from '@vue/typescript-plugin/lib/utils'; import { URI } from 'vscode-uri'; export function createHybridModeProject( - create: (params: { - configFileName?: string; - asFileName: (scriptId: URI) => string; - }) => ProviderResult<{ + create: () => ProviderResult<{ languagePlugins: LanguagePlugin[]; setup?(options: { language: Language; @@ -24,9 +20,6 @@ export function createHybridModeProject( const project: LanguageServerProject = { setup(_server) { server = _server; - onServerReady.push(() => { - server.languageFeatures.requestRefresh(false); - }); server.fileWatcher.onDidChangeWatchedFiles(({ changes }) => { for (const change of changes) { const changeUri = URI.parse(change.uri); @@ -36,34 +29,10 @@ export function createHybridModeProject( } } }); - const end = Date.now() + 60000; - const pipeWatcher = setInterval(() => { - for (const server of configuredServers) { - server.update(); - } - for (const server of inferredServers) { - server.update(); - } - if (Date.now() > end) { - clearInterval(pipeWatcher); - } - }, 2500); }, - async getLanguageService(uri) { - const fileName = asFileName(uri); - const namedPipeServer = await getBestServer(fileName); - if (namedPipeServer?.projectInfo?.kind === 1) { - const tsconfig = namedPipeServer.projectInfo.name; - const tsconfigUri = URI.file(tsconfig); - if (!tsconfigProjects.has(tsconfigUri)) { - tsconfigProjects.set(tsconfigUri, createLs(server, tsconfig)); - } - return await tsconfigProjects.get(tsconfigUri)!; - } - else { - simpleLs ??= createLs(server, undefined); - return await simpleLs; - } + async getLanguageService() { + simpleLs ??= createLs(server); + return await simpleLs; }, getExistingLanguageServices() { return Promise.all([ @@ -85,15 +54,8 @@ export function createHybridModeProject( return project; - function asFileName(uri: URI) { - return uri.fsPath.replace(/\\/g, '/'); - } - - async function createLs(server: LanguageServer, tsconfig: string | undefined) { - const { languagePlugins, setup } = await create({ - configFileName: tsconfig, - asFileName, - }); + async function createLs(server: LanguageServer) { + const { languagePlugins, setup } = await create(); const language = createLanguage([ { getLanguageId: uri => server.documents.get(uri)?.languageId }, ...languagePlugins, diff --git a/packages/language-server/lib/types.ts b/packages/language-server/lib/types.ts index 1b22fbfca8..8165b56928 100644 --- a/packages/language-server/lib/types.ts +++ b/packages/language-server/lib/types.ts @@ -1,6 +1,7 @@ export type VueInitializationOptions = { typescript: { tsdk: string; + serverProxy: string; }; }; diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index 6237480003..e7ed6d6cd0 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -1,7 +1,6 @@ import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node'; -import { createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core'; +import { createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core'; import { getHybridModeLanguageServicePlugins } from '@vue/language-service'; -import * as namedPipeClient from '@vue/typescript-plugin/lib/client'; import { createHybridModeProject } from './lib/hybridModeProject'; import type { VueInitializationOptions } from './lib/types'; @@ -16,20 +15,18 @@ connection.onInitialize(params => { return server.initialize( params, createHybridModeProject( - ({ asFileName, configFileName }) => { - const commandLine = configFileName - ? createParsedCommandLine(ts, ts.sys, configFileName) - : { - vueOptions: getDefaultCompilerOptions(), - options: ts.getDefaultCompilerOptions(), - }; + () => { + const commandLine = { + vueOptions: getDefaultCompilerOptions(), + options: ts.getDefaultCompilerOptions(), + }; return { languagePlugins: [ createVueLanguagePlugin( ts, commandLine.options, commandLine.vueOptions, - asFileName + uri => uri.fsPath.replace(/\\/g, '/') ), ], setup({ project }) { @@ -38,7 +35,35 @@ connection.onInitialize(params => { }; } ), - getHybridModeLanguageServicePlugins(ts, namedPipeClient) + getHybridModeLanguageServicePlugins(ts, { + collectExtractProps(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:collectExtractProps', args]); + }, + getComponentDirectives(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentDirectives', args]); + }, + getComponentEvents(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentEvents', args]); + }, + getComponentsNames(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentsNames', args]); + }, + getComponentProps(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentProps', args]); + }, + getElementAttrs(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getElementAttrs', args]); + }, + getImportPathForFile(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getImportPathForFile', args]); + }, + getPropertiesAtLocation(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getPropertiesAtLocation', args]); + }, + getQuickInfoAtPosition(...args) { + return connection.sendRequest(options.typescript.serverProxy, ['vue:getQuickInfoAtPosition', args]); + }, + }) ); }); diff --git a/packages/language-server/tests/server.ts b/packages/language-server/tests/server.ts index 8736b51adf..a84b522bed 100644 --- a/packages/language-server/tests/server.ts +++ b/packages/language-server/tests/server.ts @@ -4,6 +4,7 @@ import type { LanguageServerHandle } from '@volar/test-utils'; import { startLanguageServer } from '@volar/test-utils'; import * as path from 'node:path'; import { URI } from 'vscode-uri'; +import { VueInitializationOptions } from '../lib/types'; let serverHandle: LanguageServerHandle | undefined; let tsserver: import('@typescript/server-harness').Server; @@ -43,14 +44,23 @@ export async function getLanguageServer(): Promise<{ return null; }); }); + serverHandle.connection.onRequest('tsserverRequest', async ([command, args]) => { + const res = await tsserver.message({ + seq: seq++, + command: command, + arguments: args, + }); + return res.body; + }); await serverHandle.initialize( URI.file(testWorkspacePath).toString(), { typescript: { tsdk: path.dirname(require.resolve('typescript/lib/typescript.js')), + serverProxy: 'tsserverRequest', }, - }, + } satisfies VueInitializationOptions, { workspace: { configuration: true, @@ -74,8 +84,6 @@ export async function getLanguageServer(): Promise<{ { file: URI.parse(uri).fsPath, fileContent: content, - projectRootPath: path.resolve(testWorkspacePath, './tsconfigProject'), - plugins: ['@vue/typescript-plugin'], } ] } @@ -83,11 +91,6 @@ export async function getLanguageServer(): Promise<{ if (!res.success) { throw new Error(res.body); } - - // Wait for the named pipe server ready - // TODO: remove this when named pipe logic is removed - await new Promise(resolve => setTimeout(resolve, 2000)); - return await serverHandle!.openInMemoryDocument(uri, languageId, content); }, close: async (uri: string) => { diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index c50f8c61c7..7bbc053694 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -33,8 +33,8 @@ import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/common'; import { collectExtractProps } from '@vue/typescript-plugin/lib/requests/collectExtractProps'; import { getComponentDirectives } from '@vue/typescript-plugin/lib/requests/getComponentDirectives'; import { getComponentEvents } from '@vue/typescript-plugin/lib/requests/getComponentEvents'; -import { getComponentNames } from '@vue/typescript-plugin/lib/requests/getComponentNames'; import { getComponentProps } from '@vue/typescript-plugin/lib/requests/getComponentProps'; +import { getComponentsNames } from '@vue/typescript-plugin/lib/requests/getComponentsNames'; import { getElementAttrs } from '@vue/typescript-plugin/lib/requests/getElementAttrs'; import { getImportPathForFile } from '@vue/typescript-plugin/lib/requests/getImportPathForFile'; import { getPropertiesAtLocation } from '@vue/typescript-plugin/lib/requests/getPropertiesAtLocation'; @@ -88,7 +88,7 @@ export function getFullLanguageServicePlugins(ts: typeof import('typescript')) { } return plugins; - function getTsPluginClientForLSP(context: LanguageServiceContext): typeof import('@vue/typescript-plugin/lib/client') | undefined { + function getTsPluginClientForLSP(context: LanguageServiceContext): import('@vue/typescript-plugin/lib/requests').Requests | undefined { if (!context.project.typescript) { return; } @@ -120,8 +120,8 @@ export function getFullLanguageServicePlugins(ts: typeof import('typescript')) { async getComponentDirectives(...args) { return await getComponentDirectives.apply(requestContext, args); }, - async getComponentNames(...args) { - return await getComponentNames.apply(requestContext, args); + async getComponentsNames(...args) { + return await getComponentsNames.apply(requestContext, args); }, async getComponentProps(...args) { return await getComponentProps.apply(requestContext, args); @@ -168,7 +168,7 @@ export function getFullLanguageServicePlugins(ts: typeof import('typescript')) { export function getHybridModeLanguageServicePlugins( ts: typeof import('typescript'), - getTsPluginClient: typeof import("@vue/typescript-plugin/lib/client") + getTsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests ) { const plugins = [ createTypeScriptSyntacticPlugin(ts), @@ -184,7 +184,7 @@ export function getHybridModeLanguageServicePlugins( function getCommonLanguageServicePlugins( ts: typeof import('typescript'), - getTsPluginClient: (context: LanguageServiceContext) => typeof import('@vue/typescript-plugin/lib/client') | undefined + getTsPluginClient: (context: LanguageServiceContext) => import('@vue/typescript-plugin/lib/requests').Requests | undefined ): LanguageServicePlugin[] { return [ createTypeScriptTwoslashQueriesPlugin(ts), diff --git a/packages/language-service/lib/ideFeatures/nameCasing.ts b/packages/language-service/lib/ideFeatures/nameCasing.ts index 3ebb4d57dc..86fbaf8b6a 100644 --- a/packages/language-service/lib/ideFeatures/nameCasing.ts +++ b/packages/language-service/lib/ideFeatures/nameCasing.ts @@ -11,7 +11,7 @@ export async function convertTagName( context: LanguageServiceContext, uri: URI, casing: TagNameCasing, - tsPluginClient: typeof import('@vue/typescript-plugin/lib/client') | undefined + tsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined ) { const sourceFile = context.language.scripts.get(uri); @@ -31,7 +31,7 @@ export async function convertTagName( const document = context.documents.get(sourceFile.id, sourceFile.languageId, sourceFile.snapshot); const edits: vscode.TextEdit[] = []; - const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; + const components = await tsPluginClient?.getComponentsNames(root.fileName) ?? []; const tags = getTemplateTagsAndAttrs(root); for (const [tagName, { offsets }] of tags) { @@ -58,7 +58,7 @@ export async function convertAttrName( context: LanguageServiceContext, uri: URI, casing: AttrNameCasing, - tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client') + tsPluginClient?: import('@vue/typescript-plugin/lib/requests').Requests ) { const sourceFile = context.language.scripts.get(uri); @@ -78,7 +78,7 @@ export async function convertAttrName( const document = context.documents.get(uri, sourceFile.languageId, sourceFile.snapshot); const edits: vscode.TextEdit[] = []; - const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; + const components = await tsPluginClient?.getComponentsNames(root.fileName) ?? []; const tags = getTemplateTagsAndAttrs(root); for (const [tagName, { attrs }] of tags) { diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index 3b3b6794cf..d187fe0ebb 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -7,7 +7,7 @@ import { isTsDocument, sleep } from './utils'; export function create( ts: typeof import('typescript'), - getTsPluginClient?: (context: LanguageServiceContext) => typeof import('@vue/typescript-plugin/lib/client') | undefined + getTsPluginClient?: (context: LanguageServiceContext) => import('@vue/typescript-plugin/lib/requests').Requests | undefined ): LanguageServicePlugin { return { name: 'vue-autoinsert-dotvalue', diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index 0dcc71dbba..605eeeeef6 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -9,7 +9,7 @@ import { LanguageServiceContext, LanguageServicePlugin, TagNameCasing } from '.. export function create( ts: typeof import('typescript'), - getTsPluginClient?: (context: LanguageServiceContext) => typeof import('@vue/typescript-plugin/lib/client') | undefined + getTsPluginClient?: (context: LanguageServiceContext) => import('@vue/typescript-plugin/lib/requests').Requests | undefined ): LanguageServicePlugin { return { name: 'vue-document-drop', diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index c730c33c57..864092e71d 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -15,7 +15,7 @@ const unicodeReg = /\\u/g; export function create( ts: typeof import('typescript'), - getTsPluginClient?: (context: LanguageServiceContext) => typeof import('@vue/typescript-plugin/lib/client') | undefined + getTsPluginClient?: (context: LanguageServiceContext) => import('@vue/typescript-plugin/lib/requests').Requests | undefined ): LanguageServicePlugin { return { name: 'vue-extract-file', diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index d33fee1f99..c8def2bea3 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -39,7 +39,7 @@ let modelData: html.HTMLDataV1; export function create( mode: 'html' | 'pug', ts: typeof import('typescript'), - getTsPluginClient?: (context: LanguageServiceContext) => typeof import('@vue/typescript-plugin/lib/client') | undefined + getTsPluginClient?: (context: LanguageServiceContext) => import('@vue/typescript-plugin/lib/requests').Requests | undefined ): LanguageServicePlugin { let customData: html.IHTMLDataProvider[] = []; let extraCustomData: html.IHTMLDataProvider[] = []; @@ -236,7 +236,7 @@ export function create( // visualize missing required props const casing = await getNameCasing(context, decoded[0]); - const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; + const components = await tsPluginClient?.getComponentsNames(root.fileName) ?? []; const componentProps: Record = {}; let token: html.TokenType; let current: { @@ -507,7 +507,7 @@ export function create( provideTags: () => { if (!components) { promises.push((async () => { - components = (await tsPluginClient?.getComponentNames(vueCode.fileName) ?? []) + components = (await tsPluginClient?.getComponentsNames(vueCode.fileName) ?? []) .filter(name => name !== 'Transition' && name !== 'TransitionGroup' diff --git a/packages/language-service/lib/plugins/vue-twoslash-queries.ts b/packages/language-service/lib/plugins/vue-twoslash-queries.ts index c011f3cbfb..014660bd56 100644 --- a/packages/language-service/lib/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/lib/plugins/vue-twoslash-queries.ts @@ -6,7 +6,7 @@ import { URI } from 'vscode-uri'; const twoslashReg = //g; export function create( - getTsPluginClient?: (context: LanguageServiceContext) => typeof import('@vue/typescript-plugin/lib/client') | undefined + getTsPluginClient?: (context: LanguageServiceContext) => import('@vue/typescript-plugin/lib/requests').Requests | undefined ): LanguageServicePlugin { return { name: 'vue-twoslash-queries', diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index 26fa988a6f..9c8570bc43 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -2,10 +2,19 @@ import { createLanguageServicePlugin } from '@volar/typescript/lib/quickstart/cr import * as vue from '@vue/language-core'; import type * as ts from 'typescript'; import { proxyLanguageServiceForVue } from './lib/common'; -import { startNamedPipeServer } from './lib/server'; +import { collectExtractProps } from './lib/requests/collectExtractProps'; +import { getComponentDirectives } from './lib/requests/getComponentDirectives'; +import { getComponentEvents } from './lib/requests/getComponentEvents'; +import { getComponentProps } from './lib/requests/getComponentProps'; +import { getComponentsNames } from './lib/requests/getComponentsNames'; +import { getElementAttrs } from './lib/requests/getElementAttrs'; +import { getImportPathForFile } from './lib/requests/getImportPathForFile'; +import { getPropertiesAtLocation } from './lib/requests/getPropertiesAtLocation'; +import { getQuickInfoAtPosition } from './lib/requests/getQuickInfoAtPosition'; +import type { RequestContext } from './lib/requests/types'; const windowsPathReg = /\\/g; -const vueCompilerOptions = new WeakMap(); +const project2Language = new Map(); const plugin = createLanguageServicePlugin( (ts, info) => { const vueOptions = getVueCompilerOptions(); @@ -16,18 +25,14 @@ const plugin = createLanguageServicePlugin( id => id ); - vueCompilerOptions.set(info.project, vueOptions); + addVueCommands(); return { languagePlugins: [languagePlugin], setup: language => { + project2Language.set(info.project, [language, info.languageServiceHost, info.languageService]); + info.languageService = proxyLanguageServiceForVue(ts, language, info.languageService, vueOptions, fileName => fileName); - if ( - info.project.projectKind === ts.server.ProjectKind.Configured - || info.project.projectKind === ts.server.ProjectKind.Inferred - ) { - startNamedPipeServer(ts, info, language, info.project.projectKind); - } // #3963 const timer = setInterval(() => { @@ -48,7 +53,106 @@ const plugin = createLanguageServicePlugin( return vue.createParsedCommandLineByJson(ts, ts.sys, info.languageServiceHost.getCurrentDirectory(), {}).vueOptions; } } + + // https://github.com/JetBrains/intellij-plugins/blob/6435723ad88fa296b41144162ebe3b8513f4949b/Angular/src-js/angular-service/src/index.ts#L69 + function addVueCommands() { + const projectService = info.project.projectService; + projectService.logger.info("Vue: called handler processing " + info.project.projectKind); + + const session = info.session; + if (session == undefined) { + projectService.logger.info("Vue: there is no session in info."); + return; + } + if (session.addProtocolHandler == undefined) { + // addProtocolHandler was introduced in TS 4.4 or 4.5 in 2021, see https://github.com/microsoft/TypeScript/issues/43893 + projectService.logger.info("Vue: there is no addProtocolHandler method."); + return; + } + if ((session as any).vueCommandsAdded) { + return; + } + + (session as any).vueCommandsAdded = true; + + session.addProtocolHandler('vue:collectExtractProps', ({ arguments: args }) => { + return { + response: collectExtractProps.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getImportPathForFile', ({ arguments: args }) => { + return { + response: getImportPathForFile.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getPropertiesAtLocation', ({ arguments: args }) => { + return { + response: getPropertiesAtLocation.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getQuickInfoAtPosition', ({ arguments: args }) => { + return { + response: getQuickInfoAtPosition.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getComponentsNames', ({ arguments: args }) => { + return { + response: getComponentsNames.apply(getRequestContext(args[0]), args) ?? [], + }; + }); + session.addProtocolHandler('vue:getComponentProps', ({ arguments: args }) => { + return { + response: getComponentProps.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getComponentEvents', ({ arguments: args }) => { + return { + response: getComponentEvents.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getComponentDirectives', ({ arguments: args }) => { + return { + response: getComponentDirectives.apply(getRequestContext(args[0]), args), + }; + }); + session.addProtocolHandler('vue:getElementAttrs', ({ arguments: args }) => { + return { + response: getElementAttrs.apply(getRequestContext(args[0]), args), + }; + }); + + projectService.logger.info('Vue specific commands are successfully added.'); + } + + function getRequestContext(fileName: string): RequestContext { + for (const [project, [language, languageServiceHost, languageService]] of project2Language) { + if (project.projectKind === 1 satisfies ts.server.ProjectKind.Configured && project.containsFile(ts.server.toNormalizedPath(fileName))) { + return { + typescript: ts, + languageService: languageService, + languageServiceHost: languageServiceHost, + language: language, + isTsPlugin: true, + getFileId: (fileName: string) => fileName, + }; + } + } + for (const [project, [language, languageServiceHost, languageService]] of project2Language) { + if (project.projectKind === 0 satisfies ts.server.ProjectKind.Inferred) { + return { + typescript: ts, + languageService: languageService, + languageServiceHost: languageServiceHost, + language: language, + isTsPlugin: true, + getFileId: (fileName: string) => fileName, + }; + } + } + throw 'No RequestContext'; + } } ); export = plugin; + diff --git a/packages/typescript-plugin/lib/client.ts b/packages/typescript-plugin/lib/client.ts deleted file mode 100644 index 86ca9ce536..0000000000 --- a/packages/typescript-plugin/lib/client.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { RequestData } from './server'; -import { getBestServer } from './utils'; - -export const collectExtractProps = createRequest< - typeof import('./requests/collectExtractProps.js')['collectExtractProps'] ->('collectExtractProps'); - -export const getImportPathForFile = createRequest< - typeof import('./requests/getImportPathForFile.js')['getImportPathForFile'] ->('getImportPathForFile'); - -export const getPropertiesAtLocation = createRequest< - typeof import('./requests/getPropertiesAtLocation.js')['getPropertiesAtLocation'] ->('getPropertiesAtLocation'); - -export const getQuickInfoAtPosition = createRequest< - typeof import('./requests/getQuickInfoAtPosition.js')['getQuickInfoAtPosition'] ->('getQuickInfoAtPosition'); - -// Component Infos - -export async function getComponentProps(fileName: string, componentName: string) { - const server = await getBestServer(fileName); - if (!server) { - return; - } - return await server.getComponentProps(fileName, componentName); -} - -export const getComponentEvents = createRequest< - typeof import('./requests/getComponentEvents.js')['getComponentEvents'] ->('getComponentEvents'); - -export const getComponentDirectives = createRequest< - typeof import('./requests/getComponentDirectives.js')['getComponentDirectives'] ->('getComponentDirectives'); - -export async function getComponentNames(fileName: string) { - const server = await getBestServer(fileName); - if (!server) { - return; - } - const componentAndProps = server.componentNamesAndProps.get(fileName); - if (!componentAndProps) { - return; - } - return Object.keys(componentAndProps); -} - -export const getElementAttrs = createRequest< - typeof import('./requests/getElementAttrs.js')['getElementAttrs'] ->('getElementAttrs'); - -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/common.ts b/packages/typescript-plugin/lib/common.ts index 531692a090..dc3e7fe08b 100644 --- a/packages/typescript-plugin/lib/common.ts +++ b/packages/typescript-plugin/lib/common.ts @@ -1,7 +1,7 @@ import { forEachElementNode, hyphenateTag, Language, VueCompilerOptions, VueVirtualCode } from '@vue/language-core'; import { capitalize } from '@vue/shared'; import type * as ts from 'typescript'; -import { _getComponentNames } from './requests/getComponentNames'; +import { _getComponentsNames } from './requests/getComponentsNames'; import type { RequestContext } from './requests/types'; const windowsPathReg = /\\/g; @@ -343,7 +343,7 @@ export function getComponentSpans( ) { const { typescript: ts, languageService } = this; const result: ts.TextSpan[] = []; - const validComponentNames = _getComponentNames(ts, languageService, vueCode); + const validComponentNames = _getComponentsNames(ts, languageService, vueCode); const components = new Set([ ...validComponentNames, ...validComponentNames.map(hyphenateTag), diff --git a/packages/typescript-plugin/lib/requests/getComponentNames.ts b/packages/typescript-plugin/lib/requests/getComponentsNames.ts similarity index 86% rename from packages/typescript-plugin/lib/requests/getComponentNames.ts rename to packages/typescript-plugin/lib/requests/getComponentsNames.ts index 8cc1040c17..77867e7391 100644 --- a/packages/typescript-plugin/lib/requests/getComponentNames.ts +++ b/packages/typescript-plugin/lib/requests/getComponentsNames.ts @@ -3,7 +3,7 @@ import type * as ts from 'typescript'; import type { RequestContext } from './types'; import { getSelfComponentName, getVariableType } from './utils'; -export function getComponentNames( +export function getComponentsNames( this: RequestContext, fileName: string ) { @@ -13,10 +13,10 @@ export function getComponentNames( return; } const vueCode = volarFile.generated.root; - return _getComponentNames(ts, languageService, vueCode); + return _getComponentsNames(ts, languageService, vueCode); } -export function _getComponentNames( +export function _getComponentsNames( ts: typeof import('typescript'), tsLs: ts.LanguageService, vueCode: VueVirtualCode diff --git a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts index 834e4ebe6b..2fe6355702 100644 --- a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts +++ b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts @@ -1,3 +1,5 @@ +/// + import { isCompletionEnabled } from '@vue/language-core'; import type * as ts from 'typescript'; import type { RequestContext } from './types'; diff --git a/packages/typescript-plugin/lib/requests/index.ts b/packages/typescript-plugin/lib/requests/index.ts new file mode 100644 index 0000000000..c0967c6211 --- /dev/null +++ b/packages/typescript-plugin/lib/requests/index.ts @@ -0,0 +1,13 @@ +type ToRequest any> = (...args: Parameters) => Promise | null | undefined>; + +export type Requests = { + collectExtractProps: ToRequest; + getImportPathForFile: ToRequest; + getPropertiesAtLocation: ToRequest; + getQuickInfoAtPosition: ToRequest; + getComponentsNames: ToRequest; + getComponentProps: ToRequest; + getComponentEvents: ToRequest; + getComponentDirectives: ToRequest; + getElementAttrs: ToRequest; +}; diff --git a/packages/typescript-plugin/lib/server.ts b/packages/typescript-plugin/lib/server.ts deleted file mode 100644 index 6c188670fa..0000000000 --- a/packages/typescript-plugin/lib/server.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { FileMap, Language } from '@vue/language-core'; -import * as fs from 'node:fs'; -import * as net from 'node:net'; -import type * as ts from 'typescript'; -import { collectExtractProps } from './requests/collectExtractProps'; -import { getComponentDirectives } from './requests/getComponentDirectives'; -import { getComponentEvents } from './requests/getComponentEvents'; -import { getComponentNames } from './requests/getComponentNames'; -import { type ComponentPropInfo, getComponentProps } from './requests/getComponentProps'; -import { getElementAttrs } from './requests/getElementAttrs'; -import { getImportPathForFile } from './requests/getImportPathForFile'; -import { getPropertiesAtLocation } from './requests/getPropertiesAtLocation'; -import { getQuickInfoAtPosition } from './requests/getQuickInfoAtPosition'; -import type { RequestContext } from './requests/types'; -import { getServerPath } from './utils'; - -export type RequestType = - 'containsFile' - | 'projectInfo' - | 'collectExtractProps' - | 'getImportPathForFile' - | 'getPropertiesAtLocation' - | 'getQuickInfoAtPosition' - // Component Infos - | 'subscribeComponentProps' - | 'getComponentEvents' - | 'getComponentDirectives' - | 'getElementAttrs'; - -export type NotificationType = - 'componentNamesUpdated' - | 'componentPropsUpdated'; - -export type RequestData = [ - seq: number, - type: RequestType, - fileName: string, - ...args: any[], -]; - -export type ResponseData = [ - seq: number, - data: any, -]; - -export type NotificationData = [ - type: NotificationType, - fileName: string, - data: any, -]; - -export interface ProjectInfo { - name: string; - kind: ts.server.ProjectKind; - currentDirectory: string; -} - -export async function startNamedPipeServer( - ts: typeof import('typescript'), - info: ts.server.PluginCreateInfo, - language: Language, - projectKind: ts.server.ProjectKind.Inferred | ts.server.ProjectKind.Configured -) { - let lastProjectVersion: string | undefined; - - const requestContext: RequestContext = { - typescript: ts, - languageService: info.languageService, - languageServiceHost: info.languageServiceHost, - language: language, - isTsPlugin: true, - getFileId: (fileName: string) => fileName, - }; - const dataChunks: Buffer[] = []; - const currentData = new FileMap<[ - componentNames: string[], - Record, - ]>(false); - const allConnections = new Set(); - const pendingRequests = new Set(); - const server = net.createServer(connection => { - allConnections.add(connection); - - connection.on('end', () => { - allConnections.delete(connection); - }); - connection.on('data', buffer => { - dataChunks.push(buffer); - const text = dataChunks.toString(); - if (text.endsWith('\n\n')) { - dataChunks.length = 0; - const requests = text.split('\n\n'); - for (let json of requests) { - json = json.trim(); - if (!json) { - continue; - } - try { - onRequest(connection, JSON.parse(json)); - } catch (e) { - console.error('[Vue Named Pipe Server] JSON parse error:', e); - } - } - } - }); - connection.on('error', err => console.error('[Vue Named Pipe Server]', err.message)); - - for (const [fileName, [componentNames, componentProps]] of currentData) { - notify(connection, 'componentNamesUpdated', fileName, componentNames); - - for (const [name, props] of Object.entries(componentProps)) { - notify(connection, 'componentPropsUpdated', fileName, [name, props]); - } - } - }); - - for (let i = 0; i < 10; i++) { - const path = getServerPath(projectKind, i); - const socket = await connect(path, 100); - if (typeof socket === 'object') { - socket.end(); - } - const namedPipeOccupied = typeof socket === 'object' || socket === 'timeout'; - if (namedPipeOccupied) { - continue; - } - const success = await tryListen(server, path); - if (success) { - break; - } - } - - updateWhile(); - - async function updateWhile() { - while (true) { - await sleep(500); - const projectVersion = info.project.getProjectVersion(); - if (lastProjectVersion === projectVersion) { - continue; - } - const connections = [...allConnections].filter(c => !c.destroyed); - if (!connections.length) { - continue; - } - const token = info.languageServiceHost.getCancellationToken?.(); - const openedScriptInfos = info.project.getRootScriptInfos().filter(info => info.isScriptOpen()); - if (!openedScriptInfos.length) { - continue; - } - for (const scriptInfo of openedScriptInfos) { - await sleep(10); - if (token?.isCancellationRequested()) { - break; - } - - let data = currentData.get(scriptInfo.fileName); - if (!data) { - data = [[], {}]; - currentData.set(scriptInfo.fileName, data); - } - - const [oldComponentNames, componentProps] = data; - const newComponentNames = getComponentNames.apply(requestContext, [scriptInfo.fileName]) ?? []; - - if (JSON.stringify(oldComponentNames) !== JSON.stringify(newComponentNames)) { - data[0] = newComponentNames; - for (const connection of connections) { - notify(connection, 'componentNamesUpdated', scriptInfo.fileName, newComponentNames); - } - } - - for (const [name, props] of Object.entries(componentProps)) { - await sleep(10); - if (token?.isCancellationRequested()) { - break; - } - const newProps = getComponentProps.apply(requestContext, [scriptInfo.fileName, name]) ?? []; - if (JSON.stringify(props) !== JSON.stringify(newProps)) { - componentProps[name] = newProps; - for (const connection of connections) { - notify(connection, 'componentPropsUpdated', scriptInfo.fileName, [name, newProps]); - } - } - } - } - lastProjectVersion = projectVersion; - } - } - - function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - function notify(connection: net.Socket, type: NotificationData[0], fileName: string, data: any) { - connection.write(JSON.stringify([type, fileName, data] satisfies NotificationData) + '\n\n'); - } - - function onRequest(connection: net.Socket, [seq, requestType, ...args]: RequestData) { - if (pendingRequests.has(seq)) { - return; - } - setTimeout(() => pendingRequests.delete(seq), 500); - pendingRequests.add(seq); - - let data: any; - try { - data = handleRequest(requestType, ...args); - } catch { - data = null; - } - - connection.write(JSON.stringify([seq, data ?? null]) + '\n\n'); - } - - function handleRequest(requestType: RequestType, ...args: [fileName: string, ...any[]]) { - const fileName = args[0]; - - if (requestType === 'projectInfo') { - return { - name: info.project.getProjectName(), - kind: info.project.projectKind, - currentDirectory: info.project.getCurrentDirectory(), - } satisfies ProjectInfo; - } - else if (requestType === 'containsFile') { - return info.project.containsFile(ts.server.toNormalizedPath(fileName)); - } - else if (requestType === 'collectExtractProps') { - return collectExtractProps.apply(requestContext, args as any); - } - else if (requestType === 'getImportPathForFile') { - return getImportPathForFile.apply(requestContext, args as any); - } - else if (requestType === 'getPropertiesAtLocation') { - return getPropertiesAtLocation.apply(requestContext, args as any); - } - else if (requestType === 'getQuickInfoAtPosition') { - return getQuickInfoAtPosition.apply(requestContext, args as any); - } - else if (requestType === 'subscribeComponentProps') { - const tag = args[1]; - const props = getComponentProps.apply(requestContext, [fileName, tag]) ?? []; - let data = currentData.get(fileName); - if (!data) { - data = [[], {}]; - currentData.set(fileName, data); - } - data[1][tag] = props; - return props; - } - else if (requestType === 'getComponentEvents') { - return getComponentEvents.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); - } - - console.warn('[Vue Named Pipe Server] Unknown request:', requestType); - debugger; - return undefined; - } -} - -function connect(namedPipePath: string, timeout?: number) { - return new Promise(resolve => { - const socket = net.connect(namedPipePath); - if (timeout) { - socket.setTimeout(timeout); - } - const onConnect = () => { - cleanup(); - resolve(socket); - }; - const onError = (err: any) => { - if (err.code === 'ECONNREFUSED') { - try { - console.log('[Vue Named Pipe Client] Deleting:', namedPipePath); - fs.promises.unlink(namedPipePath); - } catch { } - } - cleanup(); - resolve('error'); - socket.end(); - }; - const onTimeout = () => { - cleanup(); - resolve('timeout'); - socket.end(); - }; - const cleanup = () => { - socket.off('connect', onConnect); - socket.off('error', onError); - socket.off('timeout', onTimeout); - }; - socket.on('connect', onConnect); - socket.on('error', onError); - socket.on('timeout', onTimeout); - }); -} - -function tryListen(server: net.Server, namedPipePath: string) { - return new Promise(resolve => { - const onSuccess = () => { - server.off('error', onError); - resolve(true); - }; - const onError = (err: any) => { - if (err.code === 'ECONNREFUSED') { - try { - console.log('[Vue Named Pipe Client] Deleting:', namedPipePath); - fs.promises.unlink(namedPipePath); - } catch { } - } - server.off('error', onError); - server.close(); - resolve(false); - }; - server.listen(namedPipePath, onSuccess); - server.on('error', onError); - }); -} diff --git a/packages/typescript-plugin/lib/utils.ts b/packages/typescript-plugin/lib/utils.ts deleted file mode 100644 index f464c0ba85..0000000000 --- a/packages/typescript-plugin/lib/utils.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { FileMap } from '@vue/language-core'; -import { camelize, capitalize } from '@vue/shared'; -import * as fs from 'node:fs'; -import * as net from 'node:net'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import type * as ts from 'typescript'; -import type { ComponentPropInfo } from './requests/getComponentProps'; -import type { NotificationData, ProjectInfo, RequestData, ResponseData } from './server'; - -export { TypeScriptProjectHost } from '@volar/typescript'; - -const { version } = require('../package.json'); -const platform = os.platform(); -const pipeDir = platform === 'win32' - ? `\\\\.\\pipe\\` - : `/tmp/`; - -export function getServerPath(kind: ts.server.ProjectKind, id: number) { - if (kind === 1 satisfies ts.server.ProjectKind.Configured) { - return `${pipeDir}vue-named-pipe-${version}-configured-${id}`; - } else { - return `${pipeDir}vue-named-pipe-${version}-inferred-${id}`; - } -} - -class NamedPipeServer { - path: string; - connecting = false; - projectInfo?: ProjectInfo; - componentNamesAndProps = new FileMap< - Record - >(false); - - constructor(kind: ts.server.ProjectKind, id: number) { - this.path = getServerPath(kind, id); - } - - containsFile(fileName: string) { - if (this.projectInfo) { - return this.sendRequest('containsFile', fileName); - } - } - - async getComponentProps(fileName: string, tag: string) { - const componentAndProps = this.componentNamesAndProps.get(fileName); - if (!componentAndProps) { - return; - } - const props = componentAndProps[tag] - ?? componentAndProps[camelize(tag)] - ?? componentAndProps[capitalize(camelize(tag))]; - if (props) { - return props; - } - return await this.sendRequest('subscribeComponentProps', fileName, tag); - } - - update() { - if (!this.connecting && !this.projectInfo) { - this.connecting = true; - this.connect(); - } - } - - connect() { - this.socket = net.connect(this.path); - this.socket.on('data', this.onData.bind(this)); - this.socket.on('connect', async () => { - const projectInfo = await this.sendRequest('projectInfo', ''); - if (projectInfo) { - console.log('TSServer project ready:', projectInfo.name); - this.projectInfo = projectInfo; - onServerReady.forEach(cb => cb()); - } else { - this.close(); - } - }); - this.socket.on('error', err => { - if ((err as any).code === 'ECONNREFUSED') { - try { - console.log('Deleteing invalid named pipe file:', this.path); - fs.promises.unlink(this.path); - } catch { } - } - this.close(); - }); - this.socket.on('timeout', () => { - this.close(); - }); - } - - close() { - this.connecting = false; - this.projectInfo = undefined; - this.socket?.end(); - } - - socket?: net.Socket; - seq = 0; - dataChunks: Buffer[] = []; - requestHandlers: Map void> = new Map(); - - onData(chunk: Buffer) { - this.dataChunks.push(chunk); - const data = Buffer.concat(this.dataChunks); - const text = data.toString(); - if (text.endsWith('\n\n')) { - this.dataChunks.length = 0; - const results = text.split('\n\n'); - for (let result of results) { - result = result.trim(); - if (!result) { - continue; - } - try { - const data: ResponseData | NotificationData = JSON.parse(result.trim()); - if (typeof data[0] === 'number') { - const [seq, res] = data; - this.requestHandlers.get(seq)?.(res); - } else { - const [type, fileName, res] = data; - this.onNotification(type, fileName, res); - } - } catch (e) { - console.error('JSON parse error:', e); - } - } - } - } - - onNotification(type: NotificationData[0], fileName: string, data: any) { - // console.log(`[${type}] ${fileName} ${JSON.stringify(data)}`); - - if (type === 'componentNamesUpdated') { - let components = this.componentNamesAndProps.get(fileName); - if (!components) { - components = {}; - this.componentNamesAndProps.set(fileName, components); - } - const newNames: string[] = data; - const newNameSet = new Set(newNames); - for (const name in components) { - if (!newNameSet.has(name)) { - delete components[name]; - } - } - for (const name of newNames) { - if (!components[name]) { - components[name] = null; - } - } - } - else if (type === 'componentPropsUpdated') { - const components = this.componentNamesAndProps.get(fileName) ?? {}; - const [name, props]: [ - name: string, - props: ComponentPropInfo[], - ] = data; - if (name in components) { - components[name] = props; - } - } - else { - console.error('Unknown notification type:', type); - debugger; - } - } - - sendRequest(requestType: RequestData[1], fileName: string, ...args: any[]) { - return new Promise(resolve => { - const seq = this.seq++; - // console.time(`[${seq}] ${requestType} ${fileName}`); - this.requestHandlers.set(seq, data => { - // console.timeEnd(`[${seq}] ${requestType} ${fileName}`); - this.requestHandlers.delete(seq); - resolve(data); - clearInterval(retryTimer); - }); - const retry = () => { - const data: RequestData = [seq, requestType, fileName, ...args]; - this.socket!.write(JSON.stringify(data) + '\n\n'); - }; - retry(); - const retryTimer = setInterval(retry, 1000); - }); - } -} - -export const configuredServers: NamedPipeServer[] = []; -export const inferredServers: NamedPipeServer[] = []; -export const onServerReady: (() => void)[] = []; - -for (let i = 0; i < 10; i++) { - configuredServers.push(new NamedPipeServer(1 satisfies ts.server.ProjectKind.Configured, i)); - inferredServers.push(new NamedPipeServer(0 satisfies ts.server.ProjectKind.Inferred, i)); -} - -export async function getBestServer(fileName: string) { - for (const server of configuredServers) { - server.update(); - } - - let servers = (await Promise.all( - configuredServers.map(async server => { - const projectInfo = server.projectInfo; - if (!projectInfo) { - return; - } - const containsFile = await server.containsFile(fileName); - if (!containsFile) { - return; - } - return server; - }) - )).filter(server => !!server); - - // Sort servers by tsconfig - servers.sort((a, b) => sortTSConfigs(fileName, a.projectInfo!.name, b.projectInfo!.name)); - - if (servers.length) { - // Return the first server - return servers[0]; - } - - for (const server of inferredServers) { - server.update(); - } - - servers = (await Promise.all( - inferredServers.map(server => { - const projectInfo = server.projectInfo; - if (!projectInfo) { - return; - } - // Check if the file is in the project's directory - if (path.relative(projectInfo.currentDirectory, fileName).startsWith('..')) { - return; - } - return server; - }) - )).filter(server => !!server); - - // Sort servers by directory - servers.sort((a, b) => - b.projectInfo!.currentDirectory.replace(/\\/g, '/').split('/').length - - a.projectInfo!.currentDirectory.replace(/\\/g, '/').split('/').length - ); - - if (servers.length) { - // Return the first server - return servers[0]; - } -} - -function sortTSConfigs(file: string, a: string, b: string) { - - const inA = isFileInDir(file, path.dirname(a)); - const inB = isFileInDir(file, path.dirname(b)); - - if (inA !== inB) { - const aWeight = inA ? 1 : 0; - const bWeight = inB ? 1 : 0; - return bWeight - aWeight; - } - - const aLength = a.split('/').length; - const bLength = b.split('/').length; - - return bLength - aLength; -} - -function isFileInDir(fileName: string, dir: string) { - const relative = path.relative(dir, fileName); - return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); -} From 375679f8207577eb14b830653b3ddd5667b214ba Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 5 Mar 2025 22:58:51 +0800 Subject: [PATCH 2/7] Update vitest snapshot --- .../language-server/tests/references.spec.ts | 225 ++++++++---------- 1 file changed, 102 insertions(+), 123 deletions(-) diff --git a/packages/language-server/tests/references.spec.ts b/packages/language-server/tests/references.spec.ts index 946a5a7ac1..caa2bc214f 100644 --- a/packages/language-server/tests/references.spec.ts +++ b/packages/language-server/tests/references.spec.ts @@ -23,46 +23,39 @@ test('Default slot', async () => { `) ).toMatchInlineSnapshot(` { - "body": { - "refs": [ - { - "end": { - "line": 3, - "offset": 10, - }, - "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", - "isDefinition": true, - "isWriteAccess": false, - "lineText": " ", - "start": { - "line": 3, - "offset": 6, - }, + "refs": [ + { + "end": { + "line": 3, + "offset": 10, }, - { - "end": { - "line": 8, - "offset": 16, - }, - "file": "\${testWorkspacePath}/tsconfigProject/foo.vue", - "isDefinition": false, - "isWriteAccess": false, - "lineText": "
", - "start": { - "line": 8, - "offset": 5, - }, + "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", + "isDefinition": true, + "isWriteAccess": false, + "lineText": " ", + "start": { + "line": 3, + "offset": 6, }, - ], - "symbolDisplayString": "(property) default?: (props: typeof __VLS_1) => any", - "symbolName": "slot", - "symbolStartOffset": 6, - }, - "command": "references", - "request_seq": 84, - "seq": 0, - "success": true, - "type": "response", + }, + { + "end": { + "line": 8, + "offset": 16, + }, + "file": "\${testWorkspacePath}/tsconfigProject/foo.vue", + "isDefinition": false, + "isWriteAccess": false, + "lineText": "
", + "start": { + "line": 8, + "offset": 5, + }, + }, + ], + "symbolDisplayString": "(property) default?: (props: typeof __VLS_1) => any", + "symbolName": "slot", + "symbolStartOffset": 6, } `); }); @@ -85,46 +78,39 @@ test('Named slot', async () => { `) ).toMatchInlineSnapshot(` { - "body": { - "refs": [ - { - "end": { - "line": 3, - "offset": 19, - }, - "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", - "isDefinition": true, - "isWriteAccess": false, - "lineText": " ", - "start": { - "line": 3, - "offset": 16, - }, + "refs": [ + { + "end": { + "line": 3, + "offset": 19, + }, + "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", + "isDefinition": true, + "isWriteAccess": false, + "lineText": " ", + "start": { + "line": 3, + "offset": 16, + }, + }, + { + "end": { + "line": 7, + "offset": 17, }, - { - "end": { - "line": 7, - "offset": 17, - }, - "file": "\${testWorkspacePath}/tsconfigProject/foo.vue", - "isDefinition": false, - "isWriteAccess": false, - "lineText": " ", - "start": { - "line": 7, - "offset": 14, - }, + "file": "\${testWorkspacePath}/tsconfigProject/foo.vue", + "isDefinition": false, + "isWriteAccess": false, + "lineText": " ", + "start": { + "line": 7, + "offset": 14, }, - ], - "symbolDisplayString": "(property) foo?: (props: typeof __VLS_1) => any", - "symbolName": "foo", - "symbolStartOffset": 16, - }, - "command": "references", - "request_seq": 89, - "seq": 0, - "success": true, - "type": "response", + }, + ], + "symbolDisplayString": "(property) foo?: (props: typeof __VLS_1) => any", + "symbolName": "foo", + "symbolStartOffset": 16, } `); }); @@ -142,54 +128,47 @@ test('v-bind shorthand', async () => { `) ).toMatchInlineSnapshot(` { - "body": { - "refs": [ - { - "contextEnd": { - "line": 3, - "offset": 18, - }, - "contextStart": { - "line": 3, - "offset": 4, - }, - "end": { - "line": 3, - "offset": 13, - }, - "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", - "isDefinition": true, - "isWriteAccess": true, - "lineText": " const foo = 1;", - "start": { - "line": 3, - "offset": 10, - }, + "refs": [ + { + "contextEnd": { + "line": 3, + "offset": 18, + }, + "contextStart": { + "line": 3, + "offset": 4, + }, + "end": { + "line": 3, + "offset": 13, + }, + "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", + "isDefinition": true, + "isWriteAccess": true, + "lineText": " const foo = 1;", + "start": { + "line": 3, + "offset": 10, + }, + }, + { + "end": { + "line": 7, + "offset": 14, }, - { - "end": { - "line": 7, - "offset": 14, - }, - "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", - "isDefinition": false, - "isWriteAccess": false, - "lineText": " ", - "start": { - "line": 7, - "offset": 11, - }, + "file": "\${testWorkspacePath}/tsconfigProject/fixture.vue", + "isDefinition": false, + "isWriteAccess": false, + "lineText": " ", + "start": { + "line": 7, + "offset": 11, }, - ], - "symbolDisplayString": "const foo: 1", - "symbolName": "foo", - "symbolStartOffset": 10, - }, - "command": "references", - "request_seq": 93, - "seq": 0, - "success": true, - "type": "response", + }, + ], + "symbolDisplayString": "const foo: 1", + "symbolName": "foo", + "symbolStartOffset": 10, } `); }); @@ -227,7 +206,7 @@ async function requestReferences(fileName: string, languageId: string, content: ref.file = '${testWorkspacePath}' + ref.file.slice(testWorkspacePath.length); } - return res!; + return res.body; } async function prepareDocument(fileName: string, languageId: string, content: string) { From dcde2b6ce887053bce6afd6d4f0b99eb9b91da67 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 5 Mar 2025 23:01:48 +0800 Subject: [PATCH 3/7] Update completions.spec.ts --- packages/language-server/tests/completions.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/language-server/tests/completions.spec.ts b/packages/language-server/tests/completions.spec.ts index e698375e59..ac17319dd9 100644 --- a/packages/language-server/tests/completions.spec.ts +++ b/packages/language-server/tests/completions.spec.ts @@ -179,9 +179,6 @@ test('HTML tags and built-in components', async () => { "component", "slot", "template", - "BaseTransition", - "Fixture", - "foo", ] `); }); From d4500985121c961c80a97524726874fe89df2cc4 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 5 Mar 2025 23:18:53 +0800 Subject: [PATCH 4/7] serverProxy -> requestForwardingCommand --- extensions/vscode/src/languageClient.ts | 2 +- extensions/vscode/src/nodeClientMain.ts | 2 +- packages/language-server/lib/types.ts | 2 +- packages/language-server/node.ts | 18 +++++++++--------- packages/language-server/tests/server.ts | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/vscode/src/languageClient.ts b/extensions/vscode/src/languageClient.ts index e207f78339..2b36026239 100644 --- a/extensions/vscode/src/languageClient.ts +++ b/extensions/vscode/src/languageClient.ts @@ -109,7 +109,7 @@ async function getInitializationOptions(context: vscode.ExtensionContext): Promi return { typescript: { tsdk: (await lsp.getTsdk(context))!.tsdk, - serverProxy: 'tsserverRequest', + requestForwardingCommand: 'forwardingTsRequest', }, }; } diff --git a/extensions/vscode/src/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index f1438b3a12..64a2b7ea0e 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -86,7 +86,7 @@ export const { activate, deactivate } = defineExtension(async () => { updateProviders(client); - client.onRequest('tsserverRequest', async ([command, args]) => { + client.onRequest('forwardingTsRequest', async ([command, args]) => { const tsserver = (globalThis as any).__TSSERVER__?.semantic; if (!tsserver) { return; diff --git a/packages/language-server/lib/types.ts b/packages/language-server/lib/types.ts index 8165b56928..ea6e37dc64 100644 --- a/packages/language-server/lib/types.ts +++ b/packages/language-server/lib/types.ts @@ -1,7 +1,7 @@ export type VueInitializationOptions = { typescript: { tsdk: string; - serverProxy: string; + requestForwardingCommand: string; }; }; diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index e7ed6d6cd0..c1f7844e82 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -37,31 +37,31 @@ connection.onInitialize(params => { ), getHybridModeLanguageServicePlugins(ts, { collectExtractProps(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:collectExtractProps', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:collectExtractProps', args]); }, getComponentDirectives(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentDirectives', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentDirectives', args]); }, getComponentEvents(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentEvents', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentEvents', args]); }, getComponentsNames(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentsNames', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentsNames', args]); }, getComponentProps(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getComponentProps', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentProps', args]); }, getElementAttrs(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getElementAttrs', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getElementAttrs', args]); }, getImportPathForFile(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getImportPathForFile', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getImportPathForFile', args]); }, getPropertiesAtLocation(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getPropertiesAtLocation', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getPropertiesAtLocation', args]); }, getQuickInfoAtPosition(...args) { - return connection.sendRequest(options.typescript.serverProxy, ['vue:getQuickInfoAtPosition', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getQuickInfoAtPosition', args]); }, }) ); diff --git a/packages/language-server/tests/server.ts b/packages/language-server/tests/server.ts index a84b522bed..b31ad558ce 100644 --- a/packages/language-server/tests/server.ts +++ b/packages/language-server/tests/server.ts @@ -44,7 +44,7 @@ export async function getLanguageServer(): Promise<{ return null; }); }); - serverHandle.connection.onRequest('tsserverRequest', async ([command, args]) => { + serverHandle.connection.onRequest('forwardingTsRequest', async ([command, args]) => { const res = await tsserver.message({ seq: seq++, command: command, @@ -58,7 +58,7 @@ export async function getLanguageServer(): Promise<{ { typescript: { tsdk: path.dirname(require.resolve('typescript/lib/typescript.js')), - serverProxy: 'tsserverRequest', + requestForwardingCommand: 'forwardingTsRequest', }, } satisfies VueInitializationOptions, { From bdd8416640cdb33ea2e86ad24570fc0c7944db39 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 5 Mar 2025 23:23:02 +0800 Subject: [PATCH 5/7] getComponentNames -> getComponentsNames --- packages/language-server/node.ts | 4 ++-- packages/language-service/index.ts | 6 +++--- packages/language-service/lib/ideFeatures/nameCasing.ts | 4 ++-- packages/language-service/lib/plugins/vue-template.ts | 4 ++-- packages/typescript-plugin/index.ts | 6 +++--- packages/typescript-plugin/lib/common.ts | 4 ++-- .../{getComponentsNames.ts => getComponentNames.ts} | 6 +++--- packages/typescript-plugin/lib/requests/index.ts | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) rename packages/typescript-plugin/lib/requests/{getComponentsNames.ts => getComponentNames.ts} (86%) diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index c1f7844e82..07ba51d9e0 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -45,8 +45,8 @@ connection.onInitialize(params => { getComponentEvents(...args) { return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentEvents', args]); }, - getComponentsNames(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentsNames', args]); + getComponentNames(...args) { + return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentNames', args]); }, getComponentProps(...args) { return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentProps', args]); diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 7bbc053694..b5e0fb9da6 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -33,8 +33,8 @@ import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/common'; import { collectExtractProps } from '@vue/typescript-plugin/lib/requests/collectExtractProps'; import { getComponentDirectives } from '@vue/typescript-plugin/lib/requests/getComponentDirectives'; import { getComponentEvents } from '@vue/typescript-plugin/lib/requests/getComponentEvents'; +import { getComponentNames } from '@vue/typescript-plugin/lib/requests/getComponentNames'; import { getComponentProps } from '@vue/typescript-plugin/lib/requests/getComponentProps'; -import { getComponentsNames } from '@vue/typescript-plugin/lib/requests/getComponentsNames'; import { getElementAttrs } from '@vue/typescript-plugin/lib/requests/getElementAttrs'; import { getImportPathForFile } from '@vue/typescript-plugin/lib/requests/getImportPathForFile'; import { getPropertiesAtLocation } from '@vue/typescript-plugin/lib/requests/getPropertiesAtLocation'; @@ -120,8 +120,8 @@ export function getFullLanguageServicePlugins(ts: typeof import('typescript')) { async getComponentDirectives(...args) { return await getComponentDirectives.apply(requestContext, args); }, - async getComponentsNames(...args) { - return await getComponentsNames.apply(requestContext, args); + async getComponentNames(...args) { + return await getComponentNames.apply(requestContext, args); }, async getComponentProps(...args) { return await getComponentProps.apply(requestContext, args); diff --git a/packages/language-service/lib/ideFeatures/nameCasing.ts b/packages/language-service/lib/ideFeatures/nameCasing.ts index 86fbaf8b6a..c0b497f2a9 100644 --- a/packages/language-service/lib/ideFeatures/nameCasing.ts +++ b/packages/language-service/lib/ideFeatures/nameCasing.ts @@ -31,7 +31,7 @@ export async function convertTagName( const document = context.documents.get(sourceFile.id, sourceFile.languageId, sourceFile.snapshot); const edits: vscode.TextEdit[] = []; - const components = await tsPluginClient?.getComponentsNames(root.fileName) ?? []; + const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; const tags = getTemplateTagsAndAttrs(root); for (const [tagName, { offsets }] of tags) { @@ -78,7 +78,7 @@ export async function convertAttrName( const document = context.documents.get(uri, sourceFile.languageId, sourceFile.snapshot); const edits: vscode.TextEdit[] = []; - const components = await tsPluginClient?.getComponentsNames(root.fileName) ?? []; + const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; const tags = getTemplateTagsAndAttrs(root); for (const [tagName, { attrs }] of tags) { diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index c8def2bea3..149887d5f5 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -236,7 +236,7 @@ export function create( // visualize missing required props const casing = await getNameCasing(context, decoded[0]); - const components = await tsPluginClient?.getComponentsNames(root.fileName) ?? []; + const components = await tsPluginClient?.getComponentNames(root.fileName) ?? []; const componentProps: Record = {}; let token: html.TokenType; let current: { @@ -507,7 +507,7 @@ export function create( provideTags: () => { if (!components) { promises.push((async () => { - components = (await tsPluginClient?.getComponentsNames(vueCode.fileName) ?? []) + components = (await tsPluginClient?.getComponentNames(vueCode.fileName) ?? []) .filter(name => name !== 'Transition' && name !== 'TransitionGroup' diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index 9c8570bc43..3aff1ec243 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -5,8 +5,8 @@ import { proxyLanguageServiceForVue } from './lib/common'; import { collectExtractProps } from './lib/requests/collectExtractProps'; import { getComponentDirectives } from './lib/requests/getComponentDirectives'; import { getComponentEvents } from './lib/requests/getComponentEvents'; +import { getComponentNames } from './lib/requests/getComponentNames'; import { getComponentProps } from './lib/requests/getComponentProps'; -import { getComponentsNames } from './lib/requests/getComponentsNames'; import { getElementAttrs } from './lib/requests/getElementAttrs'; import { getImportPathForFile } from './lib/requests/getImportPathForFile'; import { getPropertiesAtLocation } from './lib/requests/getPropertiesAtLocation'; @@ -95,9 +95,9 @@ const plugin = createLanguageServicePlugin( response: getQuickInfoAtPosition.apply(getRequestContext(args[0]), args), }; }); - session.addProtocolHandler('vue:getComponentsNames', ({ arguments: args }) => { + session.addProtocolHandler('vue:getComponentNames', ({ arguments: args }) => { return { - response: getComponentsNames.apply(getRequestContext(args[0]), args) ?? [], + response: getComponentNames.apply(getRequestContext(args[0]), args) ?? [], }; }); session.addProtocolHandler('vue:getComponentProps', ({ arguments: args }) => { diff --git a/packages/typescript-plugin/lib/common.ts b/packages/typescript-plugin/lib/common.ts index dc3e7fe08b..531692a090 100644 --- a/packages/typescript-plugin/lib/common.ts +++ b/packages/typescript-plugin/lib/common.ts @@ -1,7 +1,7 @@ import { forEachElementNode, hyphenateTag, Language, VueCompilerOptions, VueVirtualCode } from '@vue/language-core'; import { capitalize } from '@vue/shared'; import type * as ts from 'typescript'; -import { _getComponentsNames } from './requests/getComponentsNames'; +import { _getComponentNames } from './requests/getComponentNames'; import type { RequestContext } from './requests/types'; const windowsPathReg = /\\/g; @@ -343,7 +343,7 @@ export function getComponentSpans( ) { const { typescript: ts, languageService } = this; const result: ts.TextSpan[] = []; - const validComponentNames = _getComponentsNames(ts, languageService, vueCode); + const validComponentNames = _getComponentNames(ts, languageService, vueCode); const components = new Set([ ...validComponentNames, ...validComponentNames.map(hyphenateTag), diff --git a/packages/typescript-plugin/lib/requests/getComponentsNames.ts b/packages/typescript-plugin/lib/requests/getComponentNames.ts similarity index 86% rename from packages/typescript-plugin/lib/requests/getComponentsNames.ts rename to packages/typescript-plugin/lib/requests/getComponentNames.ts index 77867e7391..8cc1040c17 100644 --- a/packages/typescript-plugin/lib/requests/getComponentsNames.ts +++ b/packages/typescript-plugin/lib/requests/getComponentNames.ts @@ -3,7 +3,7 @@ import type * as ts from 'typescript'; import type { RequestContext } from './types'; import { getSelfComponentName, getVariableType } from './utils'; -export function getComponentsNames( +export function getComponentNames( this: RequestContext, fileName: string ) { @@ -13,10 +13,10 @@ export function getComponentsNames( return; } const vueCode = volarFile.generated.root; - return _getComponentsNames(ts, languageService, vueCode); + return _getComponentNames(ts, languageService, vueCode); } -export function _getComponentsNames( +export function _getComponentNames( ts: typeof import('typescript'), tsLs: ts.LanguageService, vueCode: VueVirtualCode diff --git a/packages/typescript-plugin/lib/requests/index.ts b/packages/typescript-plugin/lib/requests/index.ts index c0967c6211..687c48ce0f 100644 --- a/packages/typescript-plugin/lib/requests/index.ts +++ b/packages/typescript-plugin/lib/requests/index.ts @@ -5,7 +5,7 @@ export type Requests = { getImportPathForFile: ToRequest; getPropertiesAtLocation: ToRequest; getQuickInfoAtPosition: ToRequest; - getComponentsNames: ToRequest; + getComponentNames: ToRequest; getComponentProps: ToRequest; getComponentEvents: ToRequest; getComponentDirectives: ToRequest; From d1c8555623ff7e84587aac041ec6e0cb93a284f1 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 6 Mar 2025 16:05:38 +0800 Subject: [PATCH 6/7] Update index.ts --- packages/typescript-plugin/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts index 3aff1ec243..8994a3856f 100644 --- a/packages/typescript-plugin/index.ts +++ b/packages/typescript-plugin/index.ts @@ -155,4 +155,3 @@ const plugin = createLanguageServicePlugin( ); export = plugin; - From 780d1c698644be8ae09a53f91a288c7a60c553a0 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 6 Mar 2025 16:41:43 +0800 Subject: [PATCH 7/7] make requestForwardingCommand optional --- packages/language-server/lib/types.ts | 2 +- packages/language-server/node.ts | 30 +++++++++++++++++---------- packages/language-service/index.ts | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/language-server/lib/types.ts b/packages/language-server/lib/types.ts index ea6e37dc64..47cba24eca 100644 --- a/packages/language-server/lib/types.ts +++ b/packages/language-server/lib/types.ts @@ -1,7 +1,7 @@ export type VueInitializationOptions = { typescript: { tsdk: string; - requestForwardingCommand: string; + requestForwardingCommand?: string; }; }; diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index 07ba51d9e0..c167ca80f8 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -11,6 +11,14 @@ connection.listen(); connection.onInitialize(params => { const options: VueInitializationOptions = params.initializationOptions; + + if (!options.typescript?.tsdk) { + throw new Error('typescript.tsdk is required'); + } + if (!options.typescript?.requestForwardingCommand) { + connection.console.warn('typescript.requestForwardingCommand is required since >= 3.0 for complete TS features'); + } + const { typescript: ts } = loadTsdkByPath(options.typescript.tsdk, params.locale); return server.initialize( params, @@ -35,35 +43,35 @@ connection.onInitialize(params => { }; } ), - getHybridModeLanguageServicePlugins(ts, { + getHybridModeLanguageServicePlugins(ts, options.typescript.requestForwardingCommand ? { collectExtractProps(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:collectExtractProps', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:collectExtractProps', args]); }, getComponentDirectives(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentDirectives', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentDirectives', args]); }, getComponentEvents(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentEvents', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentEvents', args]); }, getComponentNames(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentNames', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentNames', args]); }, getComponentProps(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getComponentProps', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentProps', args]); }, getElementAttrs(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getElementAttrs', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getElementAttrs', args]); }, getImportPathForFile(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getImportPathForFile', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getImportPathForFile', args]); }, getPropertiesAtLocation(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getPropertiesAtLocation', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getPropertiesAtLocation', args]); }, getQuickInfoAtPosition(...args) { - return connection.sendRequest(options.typescript.requestForwardingCommand, ['vue:getQuickInfoAtPosition', args]); + return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getQuickInfoAtPosition', args]); }, - }) + } : undefined) ); }); diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index b5e0fb9da6..764711814f 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -168,7 +168,7 @@ export function getFullLanguageServicePlugins(ts: typeof import('typescript')) { export function getHybridModeLanguageServicePlugins( ts: typeof import('typescript'), - getTsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests + getTsPluginClient: import('@vue/typescript-plugin/lib/requests').Requests | undefined ) { const plugins = [ createTypeScriptSyntacticPlugin(ts),