Skip to content

Commit

Permalink
refactor(language-service): dependency injection typescript plugin (v…
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk authored and so1ve committed Mar 4, 2024
1 parent 3ae8dda commit 23102ca
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 41 deletions.
9 changes: 5 additions & 4 deletions packages/language-server/node.ts
Expand Up @@ -4,6 +4,7 @@ import { ParsedCommandLine, VueCompilerOptions, createParsedCommandLine, createV
import { ServiceEnvironment, convertAttrName, convertTagName, createVueServicePlugins, detect } from '@vue/language-service';
import { DetectNameCasingRequest, GetConvertAttrCasingEditsRequest, GetConvertTagCasingEditsRequest, ParseSFCRequest } from './lib/protocol';
import type { VueInitializationOptions } from './lib/types';
import * as tsPluginClient from '@vue/typescript-plugin/lib/client';

export const connection: Connection = createConnection();

Expand Down Expand Up @@ -35,7 +36,7 @@ connection.onInitialize(async params => {
{
watchFileExtensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'json', ...vueFileExtensions],
getServicePlugins() {
return createVueServicePlugins(tsdk.typescript, env => envToVueOptions.get(env)!);
return createVueServicePlugins(tsdk.typescript, env => envToVueOptions.get(env)!, tsPluginClient);
},
async getLanguagePlugins(serviceEnv, projectContext) {
const [commandLine, vueOptions] = await parseCommandLine();
Expand Down Expand Up @@ -103,21 +104,21 @@ connection.onRequest(ParseSFCRequest.type, params => {
connection.onRequest(DetectNameCasingRequest.type, async params => {
const languageService = await getService(params.textDocument.uri);
if (languageService) {
return await detect(languageService.context, params.textDocument.uri);
return await detect(languageService.context, params.textDocument.uri, tsPluginClient);
}
});

connection.onRequest(GetConvertTagCasingEditsRequest.type, async params => {
const languageService = await getService(params.textDocument.uri);
if (languageService) {
return await convertTagName(languageService.context, params.textDocument.uri, params.casing);
return await convertTagName(languageService.context, params.textDocument.uri, params.casing, tsPluginClient);
}
});

connection.onRequest(GetConvertAttrCasingEditsRequest.type, async params => {
const languageService = await getService(params.textDocument.uri);
if (languageService) {
return await convertAttrName(languageService.context, params.textDocument.uri, params.casing);
return await convertAttrName(languageService.context, params.textDocument.uri, params.casing, tsPluginClient);
}
});

Expand Down
1 change: 1 addition & 0 deletions packages/language-server/package.json
Expand Up @@ -19,6 +19,7 @@
"@volar/language-server": "~2.1.0",
"@vue/language-core": "2.0.2",
"@vue/language-service": "2.0.2",
"@vue/typescript-plugin": "2.0.2",
"vscode-languageserver-protocol": "^3.17.5"
}
}
11 changes: 6 additions & 5 deletions packages/language-service/index.ts
Expand Up @@ -28,25 +28,26 @@ import { create as createVueVisualizeHiddenCallbackParamServicePlugin } from './
export function createVueServicePlugins(
ts: typeof import('typescript'),
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
): ServicePlugin[] {
return [
createTypeScriptServicePlugin(ts),
createTypeScriptTwoslashQueriesServicePlugin(),
createCssServicePlugin(),
createPugFormatServicePlugin(),
createJsonServicePlugin(),
createVueTemplateServicePlugin('html', ts, getVueOptions),
createVueTemplateServicePlugin('pug', ts, getVueOptions),
createVueTemplateServicePlugin('html', ts, getVueOptions, tsPluginClient),
createVueTemplateServicePlugin('pug', ts, getVueOptions, tsPluginClient),
createVueSfcServicePlugin(),
createVueTwoslashQueriesServicePlugin(ts),
createVueTwoslashQueriesServicePlugin(ts, tsPluginClient),
createVueReferencesCodeLensServicePlugin(),
createVueDocumentDropServicePlugin(ts),
createVueAutoDotValueServicePlugin(ts),
createVueAutoDotValueServicePlugin(ts, tsPluginClient),
createVueAutoWrapParenthesesServicePlugin(ts),
createVueAutoAddSpaceServicePlugin(),
createVueVisualizeHiddenCallbackParamServicePlugin(),
createVueDirectiveCommentsServicePlugin(),
createVueExtractFileServicePlugin(ts),
createVueExtractFileServicePlugin(ts, tsPluginClient),
createVueToggleVBindServicePlugin(ts),
createEmmetServicePlugin(),
];
Expand Down
37 changes: 27 additions & 10 deletions packages/language-service/lib/ideFeatures/nameCasing.ts
Expand Up @@ -2,12 +2,16 @@ import type { ServiceContext, VirtualCode } from '@volar/language-service';
import type { CompilerDOM } from '@vue/language-core';
import * as vue from '@vue/language-core';
import { VueGeneratedCode, hyphenateAttr, hyphenateTag } from '@vue/language-core';
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
import { computed } from 'computeds';
import type * as vscode from 'vscode-languageserver-protocol';
import { AttrNameCasing, TagNameCasing } from '../types';

export async function convertTagName(context: ServiceContext, uri: string, casing: TagNameCasing) {
export async function convertTagName(
context: ServiceContext,
uri: string,
casing: TagNameCasing,
tsPluginClient: typeof import('@vue/typescript-plugin/lib/client'),
) {

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

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

export async function convertAttrName(context: ServiceContext, uri: string, casing: AttrNameCasing) {
export async function convertAttrName(
context: ServiceContext,
uri: string,
casing: AttrNameCasing,
tsPluginClient: typeof import('@vue/typescript-plugin/lib/client'),
) {

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

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

export async function getNameCasing(context: ServiceContext, uri: string) {
export async function getNameCasing(
context: ServiceContext,
uri: string,
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
) {

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

export async function detect(context: ServiceContext, uri: string): Promise<{
export async function detect(
context: ServiceContext,
uri: string,
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
): Promise<{
tag: TagNameCasing[],
attr: AttrNameCasing[],
}> {
Expand Down Expand Up @@ -153,7 +170,7 @@ export async function detect(context: ServiceContext, uri: string): Promise<{
}
async function getTagNameCase(file: VueGeneratedCode): Promise<TagNameCasing[]> {

const components = await namedPipeClient.getComponentNames(file.fileName) ?? [];
const components = await tsPluginClient?.getComponentNames(file.fileName) ?? [];
const tagNames = getTemplateTagsAndAttrs(file);
const result: TagNameCasing[] = [];

Expand Down
@@ -1,6 +1,5 @@
import type { ServicePlugin, ServicePluginInstance } from '@volar/language-service';
import { hyphenateAttr } from '@vue/language-core';
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
import type * as ts from 'typescript';
import type * as vscode from 'vscode-languageserver-protocol';
import type { TextDocument } from 'vscode-languageserver-textdocument';
Expand All @@ -16,7 +15,10 @@ function getAst(ts: typeof import('typescript'), fileName: string, snapshot: ts.
return ast;
}

export function create(ts: typeof import('typescript')): ServicePlugin {
export function create(
ts: typeof import('typescript'),
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
): ServicePlugin {
return {
name: 'vue-autoinsert-dotvalue',
create(context): ServicePluginInstance {
Expand Down Expand Up @@ -75,7 +77,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
if (isBlacklistNode(ts, ast, document.offsetAt(position), false))
return;

const props = await namedPipeClient.getPropertiesAtLocation(fileName, sourceCodeOffset) ?? [];
const props = await tsPluginClient?.getPropertiesAtLocation(fileName, sourceCodeOffset) ?? [];
if (props.some(prop => prop === 'value')) {
return '${1:.value}';
}
Expand Down
8 changes: 5 additions & 3 deletions packages/language-service/lib/plugins/vue-extract-file.ts
@@ -1,7 +1,6 @@
import type { CreateFile, ServicePlugin, TextDocumentEdit, TextEdit } from '@volar/language-service';
import type { ExpressionNode, TemplateChildNode } from '@vue/compiler-dom';
import { Sfc, VueGeneratedCode, scriptRanges } from '@vue/language-core';
import { collectExtractProps } from '@vue/typescript-plugin/lib/client';
import type * as ts from 'typescript';
import type * as vscode from 'vscode-languageserver-protocol';

Expand All @@ -13,7 +12,10 @@ interface ActionData {

const unicodeReg = /\\u/g;

export function create(ts: typeof import('typescript')): ServicePlugin {
export function create(
ts: typeof import('typescript'),
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
): ServicePlugin {
return {
name: 'vue-extract-file',
create(context) {
Expand Down Expand Up @@ -73,7 +75,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
if (!templateCodeRange)
return codeAction;

const toExtract = await collectExtractProps(sourceFile.generated.code.fileName, templateCodeRange) ?? [];
const toExtract = await tsPluginClient?.collectExtractProps(sourceFile.generated.code.fileName, templateCodeRange) ?? [];
if (!toExtract)
return codeAction;

Expand Down
24 changes: 12 additions & 12 deletions packages/language-service/lib/plugins/vue-template.ts
@@ -1,7 +1,6 @@
import type { Disposable, ServiceEnvironment, ServicePluginInstance } from '@volar/language-service';
import { VueGeneratedCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core';
import { camelize, capitalize } from '@vue/shared';
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
import { create as createHtmlService } from 'volar-service-html';
import { create as createPugService } from 'volar-service-pug';
import * as html from 'vscode-html-languageservice';
Expand All @@ -18,6 +17,7 @@ export function create(
mode: 'html' | 'pug',
ts: typeof import('typescript'),
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
): ServicePlugin {

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

// visualize missing required props
const casing = await getNameCasing(context, map.sourceDocument.uri);
const components = await namedPipeClient.getComponentNames(code.fileName) ?? [];
const casing = await getNameCasing(context, map.sourceDocument.uri, tsPluginClient);
const components = await tsPluginClient?.getComponentNames(code.fileName) ?? [];
const componentProps: Record<string, string[]> = {};
let token: html.TokenType;
let current: {
Expand All @@ -159,7 +159,7 @@ export function create(
: components.find(component => component === tagName || hyphenateTag(component) === tagName);
const checkTag = tagName.indexOf('.') >= 0 ? tagName : component;
if (checkTag) {
componentProps[checkTag] ??= await namedPipeClient.getComponentProps(code.fileName, checkTag, true) ?? [];
componentProps[checkTag] ??= await tsPluginClient?.getComponentProps(code.fileName, checkTag, true) ?? [];
current = {
unburnedRequiredProps: [...componentProps[checkTag]],
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
Expand Down Expand Up @@ -307,7 +307,7 @@ export function create(

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

const casing = await getNameCasing(context, sourceDocumentUri);
const casing = await getNameCasing(context, sourceDocumentUri, tsPluginClient);

if (builtInData.tags) {
for (const tag of builtInData.tags) {
Expand Down Expand Up @@ -345,7 +345,7 @@ export function create(
provideTags: () => {
if (!components) {
promises.push((async () => {
components = (await namedPipeClient.getComponentNames(vueCode.fileName) ?? [])
components = (await tsPluginClient?.getComponentNames(vueCode.fileName) ?? [])
.filter(name =>
name !== 'Transition'
&& name !== 'TransitionGroup'
Expand Down Expand Up @@ -391,16 +391,16 @@ export function create(
},
provideAttributes: (tag) => {

namedPipeClient.getTemplateContextProps;
tsPluginClient?.getTemplateContextProps;

let failed = false;

let tagInfo = tagInfos.get(tag);
if (!tagInfo) {
promises.push((async () => {
const attrs = await namedPipeClient.getElementAttrs(vueCode.fileName, tag) ?? [];
const props = await namedPipeClient.getComponentProps(vueCode.fileName, tag) ?? [];
const events = await namedPipeClient.getComponentEvents(vueCode.fileName, tag) ?? [];
const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? [];
const props = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
tagInfos.set(tag, {
attrs,
props,
Expand All @@ -423,7 +423,7 @@ export function create(
if (_tsCodegen) {
if (!templateContextProps) {
promises.push((async () => {
templateContextProps = await namedPipeClient.getTemplateContextProps(vueCode.fileName) ?? [];
templateContextProps = await tsPluginClient?.getTemplateContextProps(vueCode.fileName) ?? [];
version++;
})());
return [];
Expand Down Expand Up @@ -556,7 +556,7 @@ export function create(

const replacement = getReplacement(completionList, sourceDocument);
const componentNames = new Set(
(await namedPipeClient.getComponentNames(code.fileName) ?? [])
(await tsPluginClient?.getComponentNames(code.fileName) ?? [])
.map(hyphenateTag)
);

Expand Down
8 changes: 5 additions & 3 deletions packages/language-service/lib/plugins/vue-twoslash-queries.ts
@@ -1,11 +1,13 @@
import type { ServicePlugin, ServicePluginInstance } from '@volar/language-service';
import * as vue from '@vue/language-core';
import type * as vscode from 'vscode-languageserver-protocol';
import { getQuickInfoAtPosition } from '@vue/typescript-plugin/lib/client';

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

export function create(ts: typeof import('typescript')): ServicePlugin {
export function create(
ts: typeof import('typescript'),
tsPluginClient?: typeof import('@vue/typescript-plugin/lib/client'),
): ServicePlugin {
return {
name: 'vue-twoslash-queries',
create(context): ServicePluginInstance {
Expand All @@ -31,7 +33,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
for (const [pointerPosition, hoverOffset] of hoverOffsets) {
for (const [_1, [_2, map]] of context.language.files.getMaps(virtualCode)) {
for (const [sourceOffset] of map.getSourceOffsets(hoverOffset)) {
const quickInfo = await getQuickInfoAtPosition(sourceFile.generated.code.fileName, sourceOffset);
const quickInfo = await tsPluginClient?.getQuickInfoAtPosition(sourceFile.generated.code.fileName, sourceOffset);
if (quickInfo) {
inlayHints.push({
position: { line: pointerPosition.line, character: pointerPosition.character + 2 },
Expand Down
2 changes: 1 addition & 1 deletion packages/language-service/package.json
Expand Up @@ -22,7 +22,6 @@
"@vue/compiler-dom": "^3.4.0",
"@vue/language-core": "2.0.2",
"@vue/shared": "^3.4.0",
"@vue/typescript-plugin": "2.0.2",
"computeds": "^0.0.1",
"path-browserify": "^1.0.1",
"volar-service-css": "0.0.31",
Expand All @@ -40,6 +39,7 @@
"@types/node": "latest",
"@types/path-browserify": "latest",
"@volar/kit": "~2.1.0",
"@vue/typescript-plugin": "2.0.2",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-uri": "^3.0.8"
}
Expand Down

0 comments on commit 23102ca

Please sign in to comment.