Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(language-service): dependency injection typescript plugin #3944

Merged
merged 1 commit into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/language-server/node.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.