Skip to content

Commit f4183dc

Browse files
refactor(language-service): dependency injection typescript plugin (#3944)
1 parent 3efa52d commit f4183dc

File tree

10 files changed

+73
-44
lines changed

10 files changed

+73
-44
lines changed

packages/language-server/node.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ParsedCommandLine, VueCompilerOptions, createParsedCommandLine, createV
44
import { ServiceEnvironment, convertAttrName, convertTagName, createVueServicePlugins, detect } from '@vue/language-service';
55
import { DetectNameCasingRequest, GetConvertAttrCasingEditsRequest, GetConvertTagCasingEditsRequest, ParseSFCRequest } from './lib/protocol';
66
import type { VueInitializationOptions } from './lib/types';
7+
import * as tsPluginClient from '@vue/typescript-plugin/lib/client';
78

89
export const connection: Connection = createConnection();
910

@@ -35,7 +36,7 @@ connection.onInitialize(async params => {
3536
{
3637
watchFileExtensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'json', ...vueFileExtensions],
3738
getServicePlugins() {
38-
return createVueServicePlugins(tsdk.typescript, env => envToVueOptions.get(env)!);
39+
return createVueServicePlugins(tsdk.typescript, env => envToVueOptions.get(env)!, tsPluginClient);
3940
},
4041
async getLanguagePlugins(serviceEnv, projectContext) {
4142
const [commandLine, vueOptions] = await parseCommandLine();
@@ -103,21 +104,21 @@ connection.onRequest(ParseSFCRequest.type, params => {
103104
connection.onRequest(DetectNameCasingRequest.type, async params => {
104105
const languageService = await getService(params.textDocument.uri);
105106
if (languageService) {
106-
return await detect(languageService.context, params.textDocument.uri);
107+
return await detect(languageService.context, params.textDocument.uri, tsPluginClient);
107108
}
108109
});
109110

110111
connection.onRequest(GetConvertTagCasingEditsRequest.type, async params => {
111112
const languageService = await getService(params.textDocument.uri);
112113
if (languageService) {
113-
return await convertTagName(languageService.context, params.textDocument.uri, params.casing);
114+
return await convertTagName(languageService.context, params.textDocument.uri, params.casing, tsPluginClient);
114115
}
115116
});
116117

117118
connection.onRequest(GetConvertAttrCasingEditsRequest.type, async params => {
118119
const languageService = await getService(params.textDocument.uri);
119120
if (languageService) {
120-
return await convertAttrName(languageService.context, params.textDocument.uri, params.casing);
121+
return await convertAttrName(languageService.context, params.textDocument.uri, params.casing, tsPluginClient);
121122
}
122123
});
123124

packages/language-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@volar/language-server": "~2.1.0",
2020
"@vue/language-core": "2.0.2",
2121
"@vue/language-service": "2.0.2",
22+
"@vue/typescript-plugin": "2.0.2",
2223
"vscode-languageserver-protocol": "^3.17.5"
2324
}
2425
}

packages/language-service/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,26 @@ import { create as createVueVisualizeHiddenCallbackParamServicePlugin } from './
2828
export function createVueServicePlugins(
2929
ts: typeof import('typescript'),
3030
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
31+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
3132
): ServicePlugin[] {
3233
return [
3334
createTypeScriptServicePlugin(ts),
3435
createTypeScriptTwoslashQueriesServicePlugin(),
3536
createCssServicePlugin(),
3637
createPugFormatServicePlugin(),
3738
createJsonServicePlugin(),
38-
createVueTemplateServicePlugin('html', ts, getVueOptions),
39-
createVueTemplateServicePlugin('pug', ts, getVueOptions),
39+
createVueTemplateServicePlugin('html', ts, getVueOptions, tsPluginClient),
40+
createVueTemplateServicePlugin('pug', ts, getVueOptions, tsPluginClient),
4041
createVueSfcServicePlugin(),
41-
createVueTwoslashQueriesServicePlugin(ts),
42+
createVueTwoslashQueriesServicePlugin(ts, tsPluginClient),
4243
createVueReferencesCodeLensServicePlugin(),
4344
createVueDocumentDropServicePlugin(ts),
44-
createVueAutoDotValueServicePlugin(ts),
45+
createVueAutoDotValueServicePlugin(ts, tsPluginClient),
4546
createVueAutoWrapParenthesesServicePlugin(ts),
4647
createVueAutoAddSpaceServicePlugin(),
4748
createVueVisualizeHiddenCallbackParamServicePlugin(),
4849
createVueDirectiveCommentsServicePlugin(),
49-
createVueExtractFileServicePlugin(ts),
50+
createVueExtractFileServicePlugin(ts, tsPluginClient),
5051
createVueToggleVBindServicePlugin(ts),
5152
createEmmetServicePlugin(),
5253
];

packages/language-service/lib/ideFeatures/nameCasing.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import type { ServiceContext, VirtualCode } from '@volar/language-service';
22
import type { CompilerDOM } from '@vue/language-core';
33
import * as vue from '@vue/language-core';
44
import { VueGeneratedCode, hyphenateAttr, hyphenateTag } from '@vue/language-core';
5-
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
65
import { computed } from 'computeds';
76
import type * as vscode from 'vscode-languageserver-protocol';
87
import { AttrNameCasing, TagNameCasing } from '../types';
98

10-
export async function convertTagName(context: ServiceContext, uri: string, casing: TagNameCasing) {
9+
export async function convertTagName(
10+
context: ServiceContext,
11+
uri: string,
12+
casing: TagNameCasing,
13+
tsPluginClient: typeof import('@vue/typescript-plugin/lib/client'),
14+
) {
1115

1216
const sourceFile = context.language.files.get(uri);
1317
if (!sourceFile)
@@ -24,7 +28,7 @@ export async function convertTagName(context: ServiceContext, uri: string, casin
2428
const template = desc.template;
2529
const document = context.documents.get(sourceFile.id, sourceFile.languageId, sourceFile.snapshot);
2630
const edits: vscode.TextEdit[] = [];
27-
const components = await namedPipeClient.getComponentNames(rootCode.fileName) ?? [];
31+
const components = await tsPluginClient.getComponentNames(rootCode.fileName) ?? [];
2832
const tags = getTemplateTagsAndAttrs(rootCode);
2933

3034
for (const [tagName, { offsets }] of tags) {
@@ -47,7 +51,12 @@ export async function convertTagName(context: ServiceContext, uri: string, casin
4751
return edits;
4852
}
4953

50-
export async function convertAttrName(context: ServiceContext, uri: string, casing: AttrNameCasing) {
54+
export async function convertAttrName(
55+
context: ServiceContext,
56+
uri: string,
57+
casing: AttrNameCasing,
58+
tsPluginClient: typeof import('@vue/typescript-plugin/lib/client'),
59+
) {
5160

5261
const sourceFile = context.language.files.get(uri);
5362
if (!sourceFile)
@@ -64,13 +73,13 @@ export async function convertAttrName(context: ServiceContext, uri: string, casi
6473
const template = desc.template;
6574
const document = context.documents.get(uri, sourceFile.languageId, sourceFile.snapshot);
6675
const edits: vscode.TextEdit[] = [];
67-
const components = await namedPipeClient.getComponentNames(rootCode.fileName) ?? [];
76+
const components = await tsPluginClient.getComponentNames(rootCode.fileName) ?? [];
6877
const tags = getTemplateTagsAndAttrs(rootCode);
6978

7079
for (const [tagName, { attrs }] of tags) {
7180
const componentName = components.find(component => component === tagName || hyphenateTag(component) === tagName);
7281
if (componentName) {
73-
const props = await namedPipeClient.getComponentProps(rootCode.fileName, componentName) ?? [];
82+
const props = await tsPluginClient.getComponentProps(rootCode.fileName, componentName) ?? [];
7483
for (const [attrName, { offsets }] of attrs) {
7584
const propName = props.find(prop => prop === attrName || hyphenateAttr(prop) === attrName);
7685
if (propName) {
@@ -93,9 +102,13 @@ export async function convertAttrName(context: ServiceContext, uri: string, casi
93102
return edits;
94103
}
95104

96-
export async function getNameCasing(context: ServiceContext, uri: string) {
105+
export async function getNameCasing(
106+
context: ServiceContext,
107+
uri: string,
108+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
109+
) {
97110

98-
const detected = await detect(context, uri);
111+
const detected = await detect(context, uri, tsPluginClient);
99112
const [attr, tag] = await Promise.all([
100113
context.env.getConfiguration?.<'autoKebab' | 'autoCamel' | 'kebab' | 'camel'>('vue.complete.casing.props', uri),
101114
context.env.getConfiguration?.<'autoKebab' | 'autoPascal' | 'kebab' | 'pascal'>('vue.complete.casing.tags', uri),
@@ -109,7 +122,11 @@ export async function getNameCasing(context: ServiceContext, uri: string) {
109122
};
110123
}
111124

112-
export async function detect(context: ServiceContext, uri: string): Promise<{
125+
export async function detect(
126+
context: ServiceContext,
127+
uri: string,
128+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
129+
): Promise<{
113130
tag: TagNameCasing[],
114131
attr: AttrNameCasing[],
115132
}> {
@@ -153,7 +170,7 @@ export async function detect(context: ServiceContext, uri: string): Promise<{
153170
}
154171
async function getTagNameCase(file: VueGeneratedCode): Promise<TagNameCasing[]> {
155172

156-
const components = await namedPipeClient.getComponentNames(file.fileName) ?? [];
173+
const components = await tsPluginClient?.getComponentNames(file.fileName) ?? [];
157174
const tagNames = getTemplateTagsAndAttrs(file);
158175
const result: TagNameCasing[] = [];
159176

packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ServicePlugin, ServicePluginInstance } from '@volar/language-service';
22
import { hyphenateAttr } from '@vue/language-core';
3-
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
43
import type * as ts from 'typescript';
54
import type * as vscode from 'vscode-languageserver-protocol';
65
import type { TextDocument } from 'vscode-languageserver-textdocument';
@@ -16,7 +15,10 @@ function getAst(ts: typeof import('typescript'), fileName: string, snapshot: ts.
1615
return ast;
1716
}
1817

19-
export function create(ts: typeof import('typescript')): ServicePlugin {
18+
export function create(
19+
ts: typeof import('typescript'),
20+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
21+
): ServicePlugin {
2022
return {
2123
name: 'vue-autoinsert-dotvalue',
2224
create(context): ServicePluginInstance {
@@ -75,7 +77,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
7577
if (isBlacklistNode(ts, ast, document.offsetAt(position), false))
7678
return;
7779

78-
const props = await namedPipeClient.getPropertiesAtLocation(fileName, sourceCodeOffset) ?? [];
80+
const props = await tsPluginClient?.getPropertiesAtLocation(fileName, sourceCodeOffset) ?? [];
7981
if (props.some(prop => prop === 'value')) {
8082
return '${1:.value}';
8183
}

packages/language-service/lib/plugins/vue-extract-file.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { CreateFile, ServicePlugin, TextDocumentEdit, TextEdit } from '@volar/language-service';
22
import type { ExpressionNode, TemplateChildNode } from '@vue/compiler-dom';
33
import { Sfc, VueGeneratedCode, scriptRanges } from '@vue/language-core';
4-
import { collectExtractProps } from '@vue/typescript-plugin/lib/client';
54
import type * as ts from 'typescript';
65
import type * as vscode from 'vscode-languageserver-protocol';
76

@@ -13,7 +12,10 @@ interface ActionData {
1312

1413
const unicodeReg = /\\u/g;
1514

16-
export function create(ts: typeof import('typescript')): ServicePlugin {
15+
export function create(
16+
ts: typeof import('typescript'),
17+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
18+
): ServicePlugin {
1719
return {
1820
name: 'vue-extract-file',
1921
create(context) {
@@ -73,7 +75,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
7375
if (!templateCodeRange)
7476
return codeAction;
7577

76-
const toExtract = await collectExtractProps(sourceFile.generated.code.fileName, templateCodeRange) ?? [];
78+
const toExtract = await tsPluginClient?.collectExtractProps(sourceFile.generated.code.fileName, templateCodeRange) ?? [];
7779
if (!toExtract)
7880
return codeAction;
7981

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Disposable, ServiceEnvironment, ServicePluginInstance } from '@volar/language-service';
22
import { VueGeneratedCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core';
33
import { camelize, capitalize } from '@vue/shared';
4-
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
54
import { create as createHtmlService } from 'volar-service-html';
65
import { create as createPugService } from 'volar-service-pug';
76
import * as html from 'vscode-html-languageservice';
@@ -18,6 +17,7 @@ export function create(
1817
mode: 'html' | 'pug',
1918
ts: typeof import('typescript'),
2019
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
20+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
2121
): ServicePlugin {
2222

2323
let customData: html.IHTMLDataProvider[] = [];
@@ -141,8 +141,8 @@ export function create(
141141
if (code instanceof VueGeneratedCode && scanner) {
142142

143143
// visualize missing required props
144-
const casing = await getNameCasing(context, map.sourceDocument.uri);
145-
const components = await namedPipeClient.getComponentNames(code.fileName) ?? [];
144+
const casing = await getNameCasing(context, map.sourceDocument.uri, tsPluginClient);
145+
const components = await tsPluginClient?.getComponentNames(code.fileName) ?? [];
146146
const componentProps: Record<string, string[]> = {};
147147
let token: html.TokenType;
148148
let current: {
@@ -159,7 +159,7 @@ export function create(
159159
: components.find(component => component === tagName || hyphenateTag(component) === tagName);
160160
const checkTag = tagName.indexOf('.') >= 0 ? tagName : component;
161161
if (checkTag) {
162-
componentProps[checkTag] ??= await namedPipeClient.getComponentProps(code.fileName, checkTag, true) ?? [];
162+
componentProps[checkTag] ??= await tsPluginClient?.getComponentProps(code.fileName, checkTag, true) ?? [];
163163
current = {
164164
unburnedRequiredProps: [...componentProps[checkTag]],
165165
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
@@ -307,7 +307,7 @@ export function create(
307307

308308
async function provideHtmlData(sourceDocumentUri: string, vueCode: VueGeneratedCode) {
309309

310-
const casing = await getNameCasing(context, sourceDocumentUri);
310+
const casing = await getNameCasing(context, sourceDocumentUri, tsPluginClient);
311311

312312
if (builtInData.tags) {
313313
for (const tag of builtInData.tags) {
@@ -345,7 +345,7 @@ export function create(
345345
provideTags: () => {
346346
if (!components) {
347347
promises.push((async () => {
348-
components = (await namedPipeClient.getComponentNames(vueCode.fileName) ?? [])
348+
components = (await tsPluginClient?.getComponentNames(vueCode.fileName) ?? [])
349349
.filter(name =>
350350
name !== 'Transition'
351351
&& name !== 'TransitionGroup'
@@ -391,16 +391,16 @@ export function create(
391391
},
392392
provideAttributes: (tag) => {
393393

394-
namedPipeClient.getTemplateContextProps;
394+
tsPluginClient?.getTemplateContextProps;
395395

396396
let failed = false;
397397

398398
let tagInfo = tagInfos.get(tag);
399399
if (!tagInfo) {
400400
promises.push((async () => {
401-
const attrs = await namedPipeClient.getElementAttrs(vueCode.fileName, tag) ?? [];
402-
const props = await namedPipeClient.getComponentProps(vueCode.fileName, tag) ?? [];
403-
const events = await namedPipeClient.getComponentEvents(vueCode.fileName, tag) ?? [];
401+
const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? [];
402+
const props = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
403+
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
404404
tagInfos.set(tag, {
405405
attrs,
406406
props,
@@ -423,7 +423,7 @@ export function create(
423423
if (_tsCodegen) {
424424
if (!templateContextProps) {
425425
promises.push((async () => {
426-
templateContextProps = await namedPipeClient.getTemplateContextProps(vueCode.fileName) ?? [];
426+
templateContextProps = await tsPluginClient?.getTemplateContextProps(vueCode.fileName) ?? [];
427427
version++;
428428
})());
429429
return [];
@@ -556,7 +556,7 @@ export function create(
556556

557557
const replacement = getReplacement(completionList, sourceDocument);
558558
const componentNames = new Set(
559-
(await namedPipeClient.getComponentNames(code.fileName) ?? [])
559+
(await tsPluginClient?.getComponentNames(code.fileName) ?? [])
560560
.map(hyphenateTag)
561561
);
562562

packages/language-service/lib/plugins/vue-twoslash-queries.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type { ServicePlugin, ServicePluginInstance } from '@volar/language-service';
22
import * as vue from '@vue/language-core';
33
import type * as vscode from 'vscode-languageserver-protocol';
4-
import { getQuickInfoAtPosition } from '@vue/typescript-plugin/lib/client';
54

65
const twoslashReg = /<!--\s*\^\?\s*-->/g;
76

8-
export function create(ts: typeof import('typescript')): ServicePlugin {
7+
export function create(
8+
ts: typeof import('typescript'),
9+
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
10+
): ServicePlugin {
911
return {
1012
name: 'vue-twoslash-queries',
1113
create(context): ServicePluginInstance {
@@ -31,7 +33,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
3133
for (const [pointerPosition, hoverOffset] of hoverOffsets) {
3234
for (const [_1, [_2, map]] of context.language.files.getMaps(virtualCode)) {
3335
for (const [sourceOffset] of map.getSourceOffsets(hoverOffset)) {
34-
const quickInfo = await getQuickInfoAtPosition(sourceFile.generated.code.fileName, sourceOffset);
36+
const quickInfo = await tsPluginClient?.getQuickInfoAtPosition(sourceFile.generated.code.fileName, sourceOffset);
3537
if (quickInfo) {
3638
inlayHints.push({
3739
position: { line: pointerPosition.line, character: pointerPosition.character + 2 },

packages/language-service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
"@vue/compiler-dom": "^3.4.0",
2323
"@vue/language-core": "2.0.2",
2424
"@vue/shared": "^3.4.0",
25-
"@vue/typescript-plugin": "2.0.2",
2625
"computeds": "^0.0.1",
2726
"path-browserify": "^1.0.1",
2827
"volar-service-css": "0.0.31",
@@ -40,6 +39,7 @@
4039
"@types/node": "latest",
4140
"@types/path-browserify": "latest",
4241
"@volar/kit": "~2.1.0",
42+
"@vue/typescript-plugin": "2.0.2",
4343
"vscode-languageserver-protocol": "^3.17.5",
4444
"vscode-uri": "^3.0.8"
4545
}

pnpm-lock.yaml

Lines changed: 6 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)