Skip to content

Commit

Permalink
feat(language-service): display deprecated info of props in completion (
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX authored Jan 22, 2025
1 parent a0511b8 commit 9eeab0c
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 48 deletions.
49 changes: 30 additions & 19 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Disposable, LanguageServiceContext, LanguageServicePluginInstance
import { VueVirtualCode, hyphenateAttr, hyphenateTag, tsCodegen } from '@vue/language-core';
import { camelize, capitalize } from '@vue/shared';
import { getComponentSpans } from '@vue/typescript-plugin/lib/common';
import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/componentInfos';
import { create as createHtmlService } from 'volar-service-html';
import { create as createPugService } from 'volar-service-pug';
import * as html from 'vscode-html-languageservice';
Expand Down Expand Up @@ -44,7 +45,7 @@ export function create(
let extraCustomData: html.IHTMLDataProvider[] = [];
let lastCompletionComponentNames = new Set<string>();

const tsDocumentations = new Map<string, string>();
const cachedPropInfos = new Map<string, ComponentPropInfo>();
const onDidChangeCustomDataListeners = new Set<() => void>();
const onDidChangeCustomData = (listener: () => void): Disposable => {
onDidChangeCustomDataListeners.add(listener);
Expand Down Expand Up @@ -488,15 +489,15 @@ export function create(
const promises: Promise<void>[] = [];
const tagInfos = new Map<string, {
attrs: string[];
propsInfo: { name: string, commentMarkdown?: string; }[];
propInfos: ComponentPropInfo[];
events: string[];
directives: string[];
}>();

let version = 0;
let components: string[] | undefined;

tsDocumentations.clear();
cachedPropInfos.clear();

updateExtraCustomData([
html.newHTMLDataProvider('vue-template-built-in', builtInData),
Expand Down Expand Up @@ -557,12 +558,12 @@ export function create(
if (!tagInfo) {
promises.push((async () => {
const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? [];
const propsInfo = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
const propInfos = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
const directives = await tsPluginClient?.getComponentDirectives(vueCode.fileName) ?? [];
tagInfos.set(tag, {
attrs,
propsInfo: propsInfo.filter(prop =>
propInfos: propInfos.filter(prop =>
!prop.name.startsWith('ref_')
),
events,
Expand All @@ -573,8 +574,8 @@ export function create(
return [];
}

const { attrs, propsInfo, events, directives } = tagInfo;
const props = propsInfo.map(prop =>
const { attrs, propInfos, events, directives } = tagInfo;
const props = propInfos.map(prop =>
hyphenateTag(prop.name).startsWith('on-vnode-')
? 'onVue:' + prop.name.slice('onVnode'.length)
: prop.name
Expand Down Expand Up @@ -611,14 +612,14 @@ export function create(
else {

const propName = name;
const propKey = generateItemKey('componentProp', isGlobal ? '*' : tag, propName);
const propDescription = propsInfo.find(prop => {
const propInfo = propInfos.find(prop => {
const name = casing.attr === AttrNameCasing.Camel ? prop.name : hyphenateAttr(prop.name);
return name === propName;
})?.commentMarkdown;
});
const propKey = generateItemKey('componentProp', isGlobal ? '*' : tag, propName, propInfo?.deprecated);

if (propDescription) {
tsDocumentations.set(propName, propDescription);
if (propInfo) {
cachedPropInfos.set(propName, propInfo);
}

attributes.push(
Expand Down Expand Up @@ -792,7 +793,7 @@ export function create(

for (const item of completionList.items) {
const documentation = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value;
if (documentation && !isItemKey(documentation) && item.documentation) {
if (documentation && !isItemKey(documentation)) {
htmlDocumentations.set(item.label, documentation);
}
}
Expand All @@ -819,11 +820,14 @@ export function create(
const itemKeyStr = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value;

let parsedItemKey = itemKeyStr ? parseItemKey(itemKeyStr) : undefined;
let propInfo: ComponentPropInfo | undefined;

if (parsedItemKey) {
const documentations: string[] = [];

if (tsDocumentations.has(parsedItemKey.prop)) {
documentations.push(tsDocumentations.get(parsedItemKey.prop)!);
propInfo = cachedPropInfos.get(parsedItemKey.prop);
if (propInfo?.commentMarkdown) {
documentations.push(propInfo.commentMarkdown);
}

let { isEvent, propName } = getPropName(parsedItemKey);
Expand Down Expand Up @@ -861,22 +865,28 @@ export function create(
type: 'componentProp',
tag: '^',
prop: propName,
deprecated: false,
leadingSlash: false
};
}

if (tsDocumentations.has(propName)) {
propInfo = cachedPropInfos.get(propName);
if (propInfo?.commentMarkdown) {
const originalDocumentation = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value;
item.documentation = {
kind: 'markdown',
value: [
tsDocumentations.get(propName)!,
propInfo.commentMarkdown,
originalDocumentation,
].filter(str => !!str).join('\n\n'),
};
}
}

if (propInfo?.deprecated) {
item.tags = [1 satisfies typeof vscode.CompletionItemTag.Deprecated];
}

const tokens: string[] = [];

if (
Expand Down Expand Up @@ -1019,8 +1029,8 @@ function parseLabel(label: string) {
};
}

function generateItemKey(type: InternalItemId, tag: string, prop: string) {
return '__VLS_data=' + type + ',' + tag + ',' + prop;
function generateItemKey(type: InternalItemId, tag: string, prop: string, deprecated?: boolean) {
return `__VLS_data=${type},${tag},${prop},${Number(deprecated)}`;
}

function isItemKey(key: string) {
Expand All @@ -1035,6 +1045,7 @@ function parseItemKey(key: string) {
type: strs[0] as InternalItemId,
tag: strs[1],
prop: strs[2],
deprecated: strs[3] === '1',
leadingSlash
};
}
Expand Down
49 changes: 36 additions & 13 deletions packages/typescript-plugin/lib/requests/componentInfos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import * as path from 'node:path';
import type * as ts from 'typescript';
import type { RequestContext } from './types';

export interface ComponentPropInfo {
name: string;
required?: boolean;
deprecated?: boolean;
commentMarkdown?: string;
}

export function getComponentProps(
this: RequestContext,
fileName: string,
Expand All @@ -27,11 +34,7 @@ export function getComponentProps(
return [];
}

const result = new Map<string, {
name: string;
required?: true;
commentMarkdown?: string;
}>();
const result = new Map<string, ComponentPropInfo>();

for (const sig of componentType.getCallSignatures()) {
const propParam = sig.parameters[0];
Expand All @@ -41,9 +44,17 @@ export function getComponentProps(
for (const prop of props) {
const name = prop.name;
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
const commentMarkdown = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags()) || undefined;

result.set(name, { name, required, commentMarkdown });
const {
content: commentMarkdown,
deprecated
} = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());

result.set(name, {
name,
required,
deprecated,
commentMarkdown
});
}
}
}
Expand All @@ -60,9 +71,17 @@ export function getComponentProps(
}
const name = prop.name;
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
const commentMarkdown = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags()) || undefined;

result.set(name, { name, required, commentMarkdown });
const {
content: commentMarkdown,
deprecated
} = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());

result.set(name, {
name,
required,
deprecated,
commentMarkdown
});
}
}
}
Expand Down Expand Up @@ -302,8 +321,12 @@ function searchVariableDeclarationNode(
function generateCommentMarkdown(parts: ts.SymbolDisplayPart[], jsDocTags: ts.JSDocTagInfo[]) {
const parsedComment = _symbolDisplayPartsToMarkdown(parts);
const parsedJsDoc = _jsDocTagInfoToMarkdown(jsDocTags);
let result = [parsedComment, parsedJsDoc].filter(str => !!str).join('\n\n');
return result;
const content = [parsedComment, parsedJsDoc].filter(str => !!str).join('\n\n');
const deprecated = jsDocTags.some((tag) => tag.name === 'deprecated');
return {
content,
deprecated
};
}

function _symbolDisplayPartsToMarkdown(parts: ts.SymbolDisplayPart[]) {
Expand Down
8 changes: 2 additions & 6 deletions packages/typescript-plugin/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as fs from 'node:fs';
import * as net from 'node:net';
import type * as ts from 'typescript';
import { collectExtractProps } from './requests/collectExtractProps';
import { getComponentDirectives, getComponentEvents, getComponentNames, getComponentProps, getElementAttrs } from './requests/componentInfos';
import { type ComponentPropInfo, getComponentDirectives, getComponentEvents, getComponentNames, getComponentProps, getElementAttrs } from './requests/componentInfos';
import { getImportPathForFile } from './requests/getImportPathForFile';
import { getPropertiesAtLocation } from './requests/getPropertiesAtLocation';
import { getQuickInfoAtPosition } from './requests/getQuickInfoAtPosition';
Expand Down Expand Up @@ -70,11 +70,7 @@ export async function startNamedPipeServer(
const dataChunks: Buffer[] = [];
const currentData = new FileMap<[
componentNames: string[],
Record<string, {
name: string;
required?: true;
commentMarkdown?: string;
}[]>,
Record<string, ComponentPropInfo[]>,
]>(false);
const allConnections = new Set<net.Socket>();
const pendingRequests = new Set<number>();
Expand Down
13 changes: 3 additions & 10 deletions packages/typescript-plugin/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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/componentInfos';
import type { NotificationData, ProjectInfo, RequestData, ResponseData } from './server';

export { TypeScriptProjectHost } from '@volar/typescript';
Expand All @@ -29,11 +30,7 @@ class NamedPipeServer {
projectInfo?: ProjectInfo;
containsFileCache = new Map<string, Promise<boolean | undefined | null>>();
componentNamesAndProps = new FileMap<
Record<string, null | {
name: string;
required?: true;
commentMarkdown?: string;
}[]>
Record<string, null | ComponentPropInfo[]>
>(false);

constructor(kind: ts.server.ProjectKind, id: number) {
Expand Down Expand Up @@ -170,11 +167,7 @@ class NamedPipeServer {
const components = this.componentNamesAndProps.get(fileName) ?? {};
const [name, props]: [
name: string,
props: {
name: string;
required?: true;
commentMarkdown?: string;
}[],
props: ComponentPropInfo[],
] = data;
components[name] = props;
}
Expand Down

0 comments on commit 9eeab0c

Please sign in to comment.