Skip to content

Commit

Permalink
solution of vuejs#4663
Browse files Browse the repository at this point in the history
  • Loading branch information
joy-yu committed Aug 23, 2024
1 parent 1fcd22d commit 4de2a31
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 6 deletions.
5 changes: 4 additions & 1 deletion packages/language-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { create as createVueInlayHintsPlugin } from './lib/plugins/vue-inlayhint
import { parse, VueCompilerOptions } from '@vue/language-core';
import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/common';
import { collectExtractProps } from '@vue/typescript-plugin/lib/requests/collectExtractProps';
import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from '@vue/typescript-plugin/lib/requests/componentInfos';
import { getComponentEvents, getComponentNames, getComponentProps, getComponentPropsWithComment, getElementAttrs, getTemplateContextProps } from '@vue/typescript-plugin/lib/requests/componentInfos';
import { getImportPathForFile } from '@vue/typescript-plugin/lib/requests/getImportPathForFile';
import { getPropertiesAtLocation } from '@vue/typescript-plugin/lib/requests/getPropertiesAtLocation';
import type { RequestContext } from '@vue/typescript-plugin/lib/requests/types';
Expand Down Expand Up @@ -120,6 +120,9 @@ export function getFullLanguageServicePlugins(ts: typeof import('typescript')):
async getComponentProps(...args) {
return await getComponentProps.apply(requestContext, args);
},
async getComponentPropsWithComment(...args) {
return await getComponentPropsWithComment.apply(requestContext, args);
},
async getElementAttrs(...args) {
return await getElementAttrs.apply(requestContext, args);
},
Expand Down
54 changes: 52 additions & 2 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { URI, Utils } from 'vscode-uri';
import { getNameCasing } from '../ideFeatures/nameCasing';
import { AttrNameCasing, LanguageServicePlugin, TagNameCasing } from '../types';
import { loadModelModifiersData, loadTemplateData } from './data';
import * as ts from 'typescript';

let builtInData: html.HTMLDataV1;
let modelData: html.HTMLDataV1;
Expand Down Expand Up @@ -449,6 +450,7 @@ export function create(
attrs: string[];
props: string[];
events: string[];
propsWithComment: { name: string, description: string; }[];
}>();

let version = 0;
Expand Down Expand Up @@ -517,6 +519,7 @@ export function create(
promises.push((async () => {
const attrs = await tsPluginClient?.getElementAttrs(vueCode.fileName, tag) ?? [];
const props = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
const propsWithComment = await tsPluginClient?.getComponentPropsWithComment(vueCode.fileName, tag) ?? [];
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
tagInfos.set(tag, {
attrs,
Expand All @@ -525,13 +528,29 @@ export function create(
&& !hyphenate(prop).startsWith('on-vnode-')
),
events,
propsWithComment: propsWithComment.filter(({ name: prop }) =>
typeof prop === 'string' && !prop.startsWith('ref_')
&& !hyphenate(prop).startsWith('on-vnode-')
).map(({ name, comment, jsdoc }) => {
const markdownComment = symbolDisplayPartsToMarkdown(comment);
const markdownJsdoc = jsDocTagInfoToMarkdown(jsdoc);
let description = markdownComment;

if (markdownJsdoc) {
if (description) {
description += '\n\n';
}
description += jsDocTagInfoToMarkdown(jsdoc);
}
return { name, description };
}),
});
version++;
})());
return [];
}

const { attrs, props, events } = tagInfo;
const { attrs, props, events, propsWithComment } = tagInfo;
const attributes: html.IAttributeData[] = [];
const _tsCodegen = tsCodegen.get(vueCode.sfc);

Expand Down Expand Up @@ -587,7 +606,8 @@ export function create(
{

const propName = name;
const propKey = createInternalItemId('componentProp', [isGlobal ? '*' : tag, propName]);
const propDesc = propsWithComment.find(p => p.name === propName)?.description;
const propKey = propDesc || createInternalItemId('componentProp', [isGlobal ? '*' : tag, propName]);

attributes.push(
{
Expand Down Expand Up @@ -917,3 +937,33 @@ function getReplacement(list: html.CompletionList, doc: TextDocument) {
}
}
}



function jsDocTagInfoToMarkdown(jsDocTags: ts.JSDocTagInfo[]) {
return jsDocTags.map(tag => {
const tagName = `*@${tag.name}*`;
const tagText = tag.text?.map(t => {
if (t.kind === 'parameterName') {
return `\`${t.text}\``;
} else {
return t.text;
}
}).join('') || '';

return `${tagName} ${tagText}`;
}).join('\n\n');
}

function symbolDisplayPartsToMarkdown(parts: ts.SymbolDisplayPart[]) {
return parts.map(part => {
switch (part.kind) {
case 'keyword':
return `\`${part.text}\``;
case 'functionName':
return `**${part.text}**`;
default:
return part.text;
}
}).join('');
}
9 changes: 9 additions & 0 deletions packages/typescript-plugin/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export function getComponentProps(
});
}

export function getComponentPropsWithComment(
...args: Parameters<typeof import('./requests/componentInfos.js')['getComponentPropsWithComment']>
) {
return sendRequest<ReturnType<typeof import('./requests/componentInfos')['getComponentPropsWithComment']>>({
type: 'getComponentPropsWithComment',
args,
});
}

export function getComponentEvents(
...args: Parameters<typeof import('./requests/componentInfos.js')['getComponentEvents']>
) {
Expand Down
90 changes: 90 additions & 0 deletions packages/typescript-plugin/lib/requests/componentInfos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,96 @@ export function getComponentProps(
return [...result];
}

// most are copied from getComponentProps except for the return type
export function getComponentPropsWithComment(
this: RequestContext,
fileName: string,
tag: string,
requiredOnly = false,
) {
const { typescript: ts, language, languageService, getFileId } = this;
const volarFile = language.scripts.get(getFileId(fileName));
if (!(volarFile?.generated?.root instanceof vue.VueVirtualCode)) {
return;
}
const vueCode = volarFile.generated.root;
const program: ts.Program = (languageService as any).getCurrentProgram();
if (!program) {
return;
}

const checker = program.getTypeChecker();
const components = getVariableType(ts, languageService, vueCode, '__VLS_components');
if (!components) {
return [];
}

const name = tag.split('.');

let componentSymbol = components.type.getProperty(name[0]);

if (!componentSymbol) {
componentSymbol = components.type.getProperty(camelize(name[0]))
?? components.type.getProperty(capitalize(camelize(name[0])));
}

if (!componentSymbol) {
return [];
}

let componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);

for (let i = 1; i < name.length; i++) {
componentSymbol = componentType.getProperty(name[i]);
if (componentSymbol) {
componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);
}
else {
return [];
}
}

const result = new Set<{ name: string, comment: ts.SymbolDisplayPart[], jsdoc: ts.JSDocTagInfo[]; }>();

for (const sig of componentType.getCallSignatures()) {
const propParam = sig.parameters[0];
if (propParam) {
const propsType = checker.getTypeOfSymbolAtLocation(propParam, components.node);
const props = propsType.getProperties();
for (const prop of props) {
if (!requiredOnly || !(prop.flags & ts.SymbolFlags.Optional)) {
const comment = prop.getDocumentationComment(checker);
const jsdoc = prop.getJsDocTags();

result.add({ name: prop.name, comment, jsdoc });
}
}
}
}

for (const sig of componentType.getConstructSignatures()) {
const instanceType = sig.getReturnType();
const propsSymbol = instanceType.getProperty('$props');
if (propsSymbol) {
const propsType = checker.getTypeOfSymbolAtLocation(propsSymbol, components.node);
const props = propsType.getProperties();
for (const prop of props) {
if (prop.flags & ts.SymbolFlags.Method) { // #2443
continue;
}
if (!requiredOnly || !(prop.flags & ts.SymbolFlags.Optional)) {
const comment = prop.getDocumentationComment(checker);
const jsdoc = prop.getJsDocTags();

result.add({ name: prop.name, comment, jsdoc });
}
}
}
}

return [...result];
}

export function getComponentEvents(
this: RequestContext,
fileName: string,
Expand Down
7 changes: 6 additions & 1 deletion 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 'fs';
import * as net from 'net';
import type * as ts from 'typescript';
import { collectExtractProps } from './requests/collectExtractProps';
import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from './requests/componentInfos';
import { getComponentEvents, getComponentNames, getComponentProps, getComponentPropsWithComment, getElementAttrs, getTemplateContextProps } from './requests/componentInfos';
import { getImportPathForFile } from './requests/getImportPathForFile';
import { getPropertiesAtLocation } from './requests/getPropertiesAtLocation';
import { getQuickInfoAtPosition } from './requests/getQuickInfoAtPosition';
Expand All @@ -19,6 +19,7 @@ export interface Request {
| 'getQuickInfoAtPosition'
// Component Infos
| 'getComponentProps'
| 'getComponentPropsWithComment'
| 'getComponentEvents'
| 'getTemplateContextProps'
| 'getComponentNames'
Expand Down Expand Up @@ -88,6 +89,10 @@ export async function startNamedPipeServer(
const result = getComponentProps.apply(requestContext, request.args as any);
sendResponse(result);
}
else if (request.type === 'getComponentPropsWithComment') {
const result = getComponentPropsWithComment.apply(requestContext, request.args as any);
sendResponse(result);
}
else if (request.type === 'getComponentEvents') {
const result = getComponentEvents.apply(requestContext, request.args as any);
sendResponse(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<script setup lang="ts">
defineProps<{
foo: number;
/** comment from props: foo */
foo: number;
/**
* Represents a book.
* this is new line?
* @constructor
* @param {string} title - The title of the book.
* @param {string} author - The author of the book.
*/
book: { title: string; author: string };
}>();
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import Comp from './Comp.vue';

<template>
<Comp></Comp>
<!-- ^inlayHint: "foo!" -->
<!-- ^inlayHint: "foo! book!" -->
</template>

0 comments on commit 4de2a31

Please sign in to comment.