From 16790aee7b10db30e556679d30bba3d40094a124 Mon Sep 17 00:00:00 2001 From: matthewp Date: Tue, 10 Aug 2021 13:31:09 +0000 Subject: [PATCH] [ci] yarn format --- tools/language-server/astro.d.ts | 2 +- tools/language-server/src/index.ts | 4 +- .../language-server/src/plugins/PluginHost.ts | 28 +- .../plugins/typescript/DocumentSnapshot.ts | 18 +- .../plugins/typescript/TypeScriptPlugin.ts | 21 +- .../features/CompletionsProvider.ts | 3 +- .../typescript/features/HoverProvider.ts | 56 ++-- .../features/SignatureHelpProvider.ts | 239 ++++++++--------- .../src/plugins/typescript/previewer.ts | 247 ++++++++---------- .../src/plugins/typescript/utils.ts | 2 +- 10 files changed, 280 insertions(+), 340 deletions(-) diff --git a/tools/language-server/astro.d.ts b/tools/language-server/astro.d.ts index 267f6f108..3c35bf911 100644 --- a/tools/language-server/astro.d.ts +++ b/tools/language-server/astro.d.ts @@ -24,4 +24,4 @@ interface Astro { declare const Astro: Astro; -export default function(): string; \ No newline at end of file +export default function (): string; diff --git a/tools/language-server/src/index.ts b/tools/language-server/src/index.ts index 1ca7172c8..5f741d1f7 100644 --- a/tools/language-server/src/index.ts +++ b/tools/language-server/src/index.ts @@ -67,8 +67,8 @@ export function startServer() { hoverProvider: true, signatureHelpProvider: { triggerCharacters: ['(', ',', '<'], - retriggerCharacters: [')'] - } + retriggerCharacters: [')'], + }, }, }; }); diff --git a/tools/language-server/src/plugins/PluginHost.ts b/tools/language-server/src/plugins/PluginHost.ts index f3e50e4d0..3ad21a21c 100644 --- a/tools/language-server/src/plugins/PluginHost.ts +++ b/tools/language-server/src/plugins/PluginHost.ts @@ -7,7 +7,7 @@ import type { Position, SignatureHelp, SignatureHelpContext, - TextDocumentIdentifier + TextDocumentIdentifier, } from 'vscode-languageserver'; import type { DocumentManager } from '../core/documents'; import type * as d from './interfaces'; @@ -74,7 +74,7 @@ export class PluginHost { async doHover(textDocument: TextDocumentIdentifier, position: Position): Promise { const document = this.getDocument(textDocument.uri); if (!document) { - throw new Error('Cannot call methods on an unopened document'); + throw new Error('Cannot call methods on an unopened document'); } return this.execute('doHover', [document, position], ExecuteMode.FirstNonNull); @@ -121,16 +121,12 @@ export class PluginHost { context: SignatureHelpContext | undefined, cancellationToken: CancellationToken ): Promise { - const document = this.getDocument(textDocument.uri); - if (!document) { - throw new Error('Cannot call methods on an unopened document'); - } + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } - return await this.execute( - 'getSignatureHelp', - [document, position, context, cancellationToken], - ExecuteMode.FirstNonNull - ); + return await this.execute('getSignatureHelp', [document, position, context, cancellationToken], ExecuteMode.FirstNonNull); } onWatchFileChanges(onWatchFileChangesParams: any[]): void { @@ -159,10 +155,12 @@ export class PluginHost { } return null; case ExecuteMode.Collect: - return Promise.all(plugins.map((plugin) => { - let ret = this.tryExecutePlugin(plugin, name, args, []); - return ret; - })); + return Promise.all( + plugins.map((plugin) => { + let ret = this.tryExecutePlugin(plugin, name, args, []); + return ret; + }) + ); case ExecuteMode.None: await Promise.all(plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, null))); return; diff --git a/tools/language-server/src/plugins/typescript/DocumentSnapshot.ts b/tools/language-server/src/plugins/typescript/DocumentSnapshot.ts index d7473c7b3..89f8c400e 100644 --- a/tools/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/tools/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -6,7 +6,7 @@ import { isInTag, positionAt, offsetAt } from '../../core/documents/utils'; import { pathToUrl } from '../../utils'; import { getScriptKindFromFileName, isAstroFilePath, toVirtualAstroFilePath } from './utils'; -const ASTRO_DEFINITION = readFileSync(require.resolve('../../../astro.d.ts')); +const ASTRO_DEFINITION = readFileSync(require.resolve('../../../astro.d.ts')); /** * The mapper to get from original snapshot positions to generated and vice versa. @@ -75,9 +75,11 @@ class AstroDocumentSnapshot implements DocumentSnapshot { /** @internal */ private transformContent(content: string) { - return content.replace(/---/g, '///') + - // Add TypeScript definitions - ASTRO_DEFINITION; + return ( + content.replace(/---/g, '///') + + // Add TypeScript definitions + ASTRO_DEFINITION + ); } get filePath() { @@ -139,9 +141,11 @@ export class DocumentFragmentSnapshot implements Omit { - return this.signatureHelpProvider.getSignatureHelp( - document, - position, - context, - cancellationToken - ); + async getSignatureHelp(document: Document, position: Position, context: SignatureHelpContext | undefined, cancellationToken?: CancellationToken): Promise { + return this.signatureHelpProvider.getSignatureHelp(document, position, context, cancellationToken); } /** diff --git a/tools/language-server/src/plugins/typescript/features/CompletionsProvider.ts b/tools/language-server/src/plugins/typescript/features/CompletionsProvider.ts index daeed9766..da4a5cd54 100644 --- a/tools/language-server/src/plugins/typescript/features/CompletionsProvider.ts +++ b/tools/language-server/src/plugins/typescript/features/CompletionsProvider.ts @@ -34,8 +34,7 @@ export class CompletionsProviderImpl implements CompletionsProvider this.toCompletionItem(fragment, entry, document.uri, position, new Set())) diff --git a/tools/language-server/src/plugins/typescript/features/HoverProvider.ts b/tools/language-server/src/plugins/typescript/features/HoverProvider.ts index 2757ed4cd..f772bc390 100644 --- a/tools/language-server/src/plugins/typescript/features/HoverProvider.ts +++ b/tools/language-server/src/plugins/typescript/features/HoverProvider.ts @@ -7,36 +7,34 @@ import { getMarkdownDocumentation } from '../previewer'; import { convertRange, toVirtualAstroFilePath } from '../utils'; export class HoverProviderImpl implements HoverProvider { - constructor(private readonly lang: LanguageServiceManager) {} + constructor(private readonly lang: LanguageServiceManager) {} - async doHover(document: Document, position: Position): Promise { - const { lang, tsDoc } = await this.getLSAndTSDoc(document); - const fragment = await tsDoc.getFragment(); + async doHover(document: Document, position: Position): Promise { + const { lang, tsDoc } = await this.getLSAndTSDoc(document); + const fragment = await tsDoc.getFragment(); - const offset = fragment.offsetAt(fragment.getGeneratedPosition(position)); - const filePath = toVirtualAstroFilePath(tsDoc.filePath); - let info = lang.getQuickInfoAtPosition(filePath, offset); - if (!info) { - return null; - } - - const textSpan = info.textSpan; - - const declaration = ts.displayPartsToString(info.displayParts); - const documentation = getMarkdownDocumentation(info.documentation, info.tags); - - // https://microsoft.github.io/language-server-protocol/specification#textDocument_hover - const contents = ['```typescript', declaration, '```'] - .concat(documentation ? ['---', documentation] : []) - .join('\n'); - - return mapObjWithRangeToOriginal(fragment, { - range: convertRange(fragment, textSpan), - contents - }); + const offset = fragment.offsetAt(fragment.getGeneratedPosition(position)); + const filePath = toVirtualAstroFilePath(tsDoc.filePath); + let info = lang.getQuickInfoAtPosition(filePath, offset); + if (!info) { + return null; } - private async getLSAndTSDoc(document: Document) { - return this.lang.getTypeScriptDoc(document); - } -} \ No newline at end of file + const textSpan = info.textSpan; + + const declaration = ts.displayPartsToString(info.displayParts); + const documentation = getMarkdownDocumentation(info.documentation, info.tags); + + // https://microsoft.github.io/language-server-protocol/specification#textDocument_hover + const contents = ['```typescript', declaration, '```'].concat(documentation ? ['---', documentation] : []).join('\n'); + + return mapObjWithRangeToOriginal(fragment, { + range: convertRange(fragment, textSpan), + contents, + }); + } + + private async getLSAndTSDoc(document: Document) { + return this.lang.getTypeScriptDoc(document); + } +} diff --git a/tools/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts b/tools/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts index 1be286246..93bad724f 100644 --- a/tools/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts +++ b/tools/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts @@ -2,157 +2,128 @@ import type { LanguageServiceManager } from '../LanguageServiceManager'; import type { SignatureHelpProvider } from '../../interfaces'; import ts from 'typescript'; import { - Position, - SignatureHelpContext, - SignatureHelp, - SignatureHelpTriggerKind, - SignatureInformation, - ParameterInformation, - MarkupKind, - CancellationToken + Position, + SignatureHelpContext, + SignatureHelp, + SignatureHelpTriggerKind, + SignatureInformation, + ParameterInformation, + MarkupKind, + CancellationToken, } from 'vscode-languageserver'; import { Document } from '../../../core/documents'; import { getMarkdownDocumentation } from '../previewer'; import { toVirtualAstroFilePath } from '../utils'; export class SignatureHelpProviderImpl implements SignatureHelpProvider { - constructor(private readonly lang: LanguageServiceManager) {} + constructor(private readonly lang: LanguageServiceManager) {} - private static readonly triggerCharacters = ['(', ',', '<']; - private static readonly retriggerCharacters = [')']; + private static readonly triggerCharacters = ['(', ',', '<']; + private static readonly retriggerCharacters = [')']; - async getSignatureHelp( - document: Document, - position: Position, - context: SignatureHelpContext | undefined, - cancellationToken?: CancellationToken - ): Promise { - const { lang, tsDoc } = await this.lang.getTypeScriptDoc(document); - const fragment = await tsDoc.getFragment(); + async getSignatureHelp(document: Document, position: Position, context: SignatureHelpContext | undefined, cancellationToken?: CancellationToken): Promise { + const { lang, tsDoc } = await this.lang.getTypeScriptDoc(document); + const fragment = await tsDoc.getFragment(); - if (cancellationToken?.isCancellationRequested) { - return null; + if (cancellationToken?.isCancellationRequested) { + return null; + } + + const offset = fragment.offsetAt(fragment.getGeneratedPosition(position)); + const triggerReason = this.toTsTriggerReason(context); + const info = lang.getSignatureHelpItems(toVirtualAstroFilePath(tsDoc.filePath), offset, triggerReason ? { triggerReason } : undefined); + if (!info || info.items.some((signature) => this.isInSvelte2tsxGeneratedFunction(signature))) { + return null; + } + + const signatures = info.items.map(this.toSignatureHelpInformation); + + return { + signatures, + activeSignature: info.selectedItemIndex, + activeParameter: info.argumentIndex, + }; + } + + private isReTrigger(isRetrigger: boolean, triggerCharacter: string): triggerCharacter is ts.SignatureHelpRetriggerCharacter { + return isRetrigger && (this.isTriggerCharacter(triggerCharacter) || SignatureHelpProviderImpl.retriggerCharacters.includes(triggerCharacter)); + } + + private isTriggerCharacter(triggerCharacter: string): triggerCharacter is ts.SignatureHelpTriggerCharacter { + return SignatureHelpProviderImpl.triggerCharacters.includes(triggerCharacter); + } + + /** + * adopted from https://github.com/microsoft/vscode/blob/265a2f6424dfbd3a9788652c7d376a7991d049a3/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts#L103 + */ + private toTsTriggerReason(context: SignatureHelpContext | undefined): ts.SignatureHelpTriggerReason { + switch (context?.triggerKind) { + case SignatureHelpTriggerKind.TriggerCharacter: + if (context.triggerCharacter) { + if (this.isReTrigger(context.isRetrigger, context.triggerCharacter)) { + return { kind: 'retrigger', triggerCharacter: context.triggerCharacter }; + } + if (this.isTriggerCharacter(context.triggerCharacter)) { + return { + kind: 'characterTyped', + triggerCharacter: context.triggerCharacter, + }; + } } + return { kind: 'invoked' }; + case SignatureHelpTriggerKind.ContentChange: + return context.isRetrigger ? { kind: 'retrigger' } : { kind: 'invoked' }; - const offset = fragment.offsetAt(fragment.getGeneratedPosition(position)); - const triggerReason = this.toTsTriggerReason(context); - const info = lang.getSignatureHelpItems( - toVirtualAstroFilePath(tsDoc.filePath), - offset, - triggerReason ? { triggerReason } : undefined - ); - if ( - !info || - info.items.some((signature) => this.isInSvelte2tsxGeneratedFunction(signature)) - ) { - return null; - } - - const signatures = info.items.map(this.toSignatureHelpInformation); - - return { - signatures, - activeSignature: info.selectedItemIndex, - activeParameter: info.argumentIndex - }; + case SignatureHelpTriggerKind.Invoked: + default: + return { kind: 'invoked' }; } + } - private isReTrigger( - isRetrigger: boolean, - triggerCharacter: string - ): triggerCharacter is ts.SignatureHelpRetriggerCharacter { - return ( - isRetrigger && - (this.isTriggerCharacter(triggerCharacter) || - SignatureHelpProviderImpl.retriggerCharacters.includes(triggerCharacter)) - ); - } + /** + * adopted from https://github.com/microsoft/vscode/blob/265a2f6424dfbd3a9788652c7d376a7991d049a3/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts#L73 + */ + private toSignatureHelpInformation(item: ts.SignatureHelpItem): SignatureInformation { + const [prefixLabel, separatorLabel, suffixLabel] = [item.prefixDisplayParts, item.separatorDisplayParts, item.suffixDisplayParts].map(ts.displayPartsToString); - private isTriggerCharacter( - triggerCharacter: string - ): triggerCharacter is ts.SignatureHelpTriggerCharacter { - return SignatureHelpProviderImpl.triggerCharacters.includes(triggerCharacter); - } + let textIndex = prefixLabel.length; + let signatureLabel = ''; + const parameters: ParameterInformation[] = []; + const lastIndex = item.parameters.length - 1; - /** - * adopted from https://github.com/microsoft/vscode/blob/265a2f6424dfbd3a9788652c7d376a7991d049a3/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts#L103 - */ - private toTsTriggerReason( - context: SignatureHelpContext | undefined - ): ts.SignatureHelpTriggerReason { - switch (context?.triggerKind) { - case SignatureHelpTriggerKind.TriggerCharacter: - if (context.triggerCharacter) { - if (this.isReTrigger(context.isRetrigger, context.triggerCharacter)) { - return { kind: 'retrigger', triggerCharacter: context.triggerCharacter }; - } - if (this.isTriggerCharacter(context.triggerCharacter)) { - return { - kind: 'characterTyped', - triggerCharacter: context.triggerCharacter - }; - } - } - return { kind: 'invoked' }; - case SignatureHelpTriggerKind.ContentChange: - return context.isRetrigger ? { kind: 'retrigger' } : { kind: 'invoked' }; + item.parameters.forEach((parameter, index) => { + const label = ts.displayPartsToString(parameter.displayParts); - case SignatureHelpTriggerKind.Invoked: - default: - return { kind: 'invoked' }; - } - } + const startIndex = textIndex; + const endIndex = textIndex + label.length; + const doc = ts.displayPartsToString(parameter.documentation); - /** - * adopted from https://github.com/microsoft/vscode/blob/265a2f6424dfbd3a9788652c7d376a7991d049a3/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts#L73 - */ - private toSignatureHelpInformation(item: ts.SignatureHelpItem): SignatureInformation { - const [prefixLabel, separatorLabel, suffixLabel] = [ - item.prefixDisplayParts, - item.separatorDisplayParts, - item.suffixDisplayParts - ].map(ts.displayPartsToString); + signatureLabel += label; + parameters.push(ParameterInformation.create([startIndex, endIndex], doc)); - let textIndex = prefixLabel.length; - let signatureLabel = ''; - const parameters: ParameterInformation[] = []; - const lastIndex = item.parameters.length - 1; + if (index < lastIndex) { + textIndex = endIndex + separatorLabel.length; + signatureLabel += separatorLabel; + } + }); + const signatureDocumentation = getMarkdownDocumentation( + item.documentation, + item.tags.filter((tag) => tag.name !== 'param') + ); - item.parameters.forEach((parameter, index) => { - const label = ts.displayPartsToString(parameter.displayParts); + return { + label: prefixLabel + signatureLabel + suffixLabel, + documentation: signatureDocumentation + ? { + value: signatureDocumentation, + kind: MarkupKind.Markdown, + } + : undefined, + parameters, + }; + } - const startIndex = textIndex; - const endIndex = textIndex + label.length; - const doc = ts.displayPartsToString(parameter.documentation); - - signatureLabel += label; - parameters.push(ParameterInformation.create([startIndex, endIndex], doc)); - - if (index < lastIndex) { - textIndex = endIndex + separatorLabel.length; - signatureLabel += separatorLabel; - } - }); - const signatureDocumentation = getMarkdownDocumentation( - item.documentation, - item.tags.filter((tag) => tag.name !== 'param') - ); - - return { - label: prefixLabel + signatureLabel + suffixLabel, - documentation: signatureDocumentation - ? { - value: signatureDocumentation, - kind: MarkupKind.Markdown - } - : undefined, - parameters - }; - } - - private isInSvelte2tsxGeneratedFunction(signatureHelpItem: ts.SignatureHelpItem) { - return signatureHelpItem.prefixDisplayParts.some((part) => - part.text.includes('__sveltets') - ); - } -} \ No newline at end of file + private isInSvelte2tsxGeneratedFunction(signatureHelpItem: ts.SignatureHelpItem) { + return signatureHelpItem.prefixDisplayParts.some((part) => part.text.includes('__sveltets')); + } +} diff --git a/tools/language-server/src/plugins/typescript/previewer.ts b/tools/language-server/src/plugins/typescript/previewer.ts index deedae1e8..710da4c17 100644 --- a/tools/language-server/src/plugins/typescript/previewer.ts +++ b/tools/language-server/src/plugins/typescript/previewer.ts @@ -7,134 +7,119 @@ * adopted from https://github.com/microsoft/vscode/blob/10722887b8629f90cc38ee7d90d54e8246dc895f/extensions/typescript-language-features/src/utils/previewer.ts */ - import ts from 'typescript'; - import { isNotNullOrUndefined } from '../../utils'; - - function replaceLinks(text: string): string { - return ( - text - // Http(s) links - .replace( - /\{@(link|linkplain|linkcode) (https?:\/\/[^ |}]+?)(?:[| ]([^{}\n]+?))?\}/gi, - (_, tag: string, link: string, text?: string) => { - switch (tag) { - case 'linkcode': - return `[\`${text ? text.trim() : link}\`](${link})`; - - default: - return `[${text ? text.trim() : link}](${link})`; - } - } - ) - ); - } - - function processInlineTags(text: string): string { - return replaceLinks(text); - } - - function getTagBodyText(tag: ts.JSDocTagInfo): string | undefined { - if (!tag.text) { - return undefined; - } - - // Convert to markdown code block if it is not already one - function makeCodeblock(text: string): string { - if (text.match(/^\s*[~`]{3}/g)) { - return text; - } - return '```\n' + text + '\n```'; - } - - function makeExampleTag(text: string) { - // check for caption tags, fix for https://github.com/microsoft/vscode/issues/79704 - const captionTagMatches = text.match(/(.*?)<\/caption>\s*(\r\n|\n)/); - if (captionTagMatches && captionTagMatches.index === 0) { - return ( - captionTagMatches[1] + - '\n\n' + - makeCodeblock(text.substr(captionTagMatches[0].length)) - ); - } else { - return makeCodeblock(text); - } - } - - function makeEmailTag(text: string) { - // fix obsucated email address, https://github.com/microsoft/vscode/issues/80898 - const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/); - - if (emailMatch === null) { - return text; - } else { - return `${emailMatch[1]} ${emailMatch[2]}`; - } - } - - switch (tag.name) { - case 'example': - return makeExampleTag(ts.displayPartsToString(tag.text)); - case 'author': - return makeEmailTag(ts.displayPartsToString(tag.text)); - case 'default': - return makeCodeblock(ts.displayPartsToString(tag.text)); - } - - return processInlineTags(ts.displayPartsToString(tag.text)); - } - - export function getTagDocumentation(tag: ts.JSDocTagInfo): string | undefined { - function getWithType() { - const body = (ts.displayPartsToString(tag.text) || '').split(/^(\S+)\s*-?\s*/); - if (body?.length === 3) { - const param = body[1]; - const doc = body[2]; - const label = `*@${tag.name}* \`${param}\``; - if (!doc) { - return label; - } - return ( - label + - (doc.match(/\r\n|\n/g) - ? ' \n' + processInlineTags(doc) - : ` — ${processInlineTags(doc)}`) - ); - } - } - - switch (tag.name) { - case 'augments': - case 'extends': - case 'param': - case 'template': - return getWithType(); - } - - // Generic tag - const label = `*@${tag.name}*`; - const text = getTagBodyText(tag); - if (!text) { - return label; - } - return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`); - } - - export function plain(parts: ts.SymbolDisplayPart[] | string): string { - return processInlineTags(typeof parts === 'string' ? parts : ts.displayPartsToString(parts)); - } - - export function getMarkdownDocumentation( - documentation: ts.SymbolDisplayPart[] | undefined, - tags: ts.JSDocTagInfo[] | undefined - ) { - let result: Array = []; - if (documentation) { - result.push(plain(documentation)); - } - - if (tags) { - result = result.concat(tags.map(getTagDocumentation)); - } - - return result.filter(isNotNullOrUndefined).join('\n\n'); - } \ No newline at end of file +import ts from 'typescript'; +import { isNotNullOrUndefined } from '../../utils'; + +function replaceLinks(text: string): string { + return ( + text + // Http(s) links + .replace(/\{@(link|linkplain|linkcode) (https?:\/\/[^ |}]+?)(?:[| ]([^{}\n]+?))?\}/gi, (_, tag: string, link: string, text?: string) => { + switch (tag) { + case 'linkcode': + return `[\`${text ? text.trim() : link}\`](${link})`; + + default: + return `[${text ? text.trim() : link}](${link})`; + } + }) + ); +} + +function processInlineTags(text: string): string { + return replaceLinks(text); +} + +function getTagBodyText(tag: ts.JSDocTagInfo): string | undefined { + if (!tag.text) { + return undefined; + } + + // Convert to markdown code block if it is not already one + function makeCodeblock(text: string): string { + if (text.match(/^\s*[~`]{3}/g)) { + return text; + } + return '```\n' + text + '\n```'; + } + + function makeExampleTag(text: string) { + // check for caption tags, fix for https://github.com/microsoft/vscode/issues/79704 + const captionTagMatches = text.match(/(.*?)<\/caption>\s*(\r\n|\n)/); + if (captionTagMatches && captionTagMatches.index === 0) { + return captionTagMatches[1] + '\n\n' + makeCodeblock(text.substr(captionTagMatches[0].length)); + } else { + return makeCodeblock(text); + } + } + + function makeEmailTag(text: string) { + // fix obsucated email address, https://github.com/microsoft/vscode/issues/80898 + const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/); + + if (emailMatch === null) { + return text; + } else { + return `${emailMatch[1]} ${emailMatch[2]}`; + } + } + + switch (tag.name) { + case 'example': + return makeExampleTag(ts.displayPartsToString(tag.text)); + case 'author': + return makeEmailTag(ts.displayPartsToString(tag.text)); + case 'default': + return makeCodeblock(ts.displayPartsToString(tag.text)); + } + + return processInlineTags(ts.displayPartsToString(tag.text)); +} + +export function getTagDocumentation(tag: ts.JSDocTagInfo): string | undefined { + function getWithType() { + const body = (ts.displayPartsToString(tag.text) || '').split(/^(\S+)\s*-?\s*/); + if (body?.length === 3) { + const param = body[1]; + const doc = body[2]; + const label = `*@${tag.name}* \`${param}\``; + if (!doc) { + return label; + } + return label + (doc.match(/\r\n|\n/g) ? ' \n' + processInlineTags(doc) : ` — ${processInlineTags(doc)}`); + } + } + + switch (tag.name) { + case 'augments': + case 'extends': + case 'param': + case 'template': + return getWithType(); + } + + // Generic tag + const label = `*@${tag.name}*`; + const text = getTagBodyText(tag); + if (!text) { + return label; + } + return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`); +} + +export function plain(parts: ts.SymbolDisplayPart[] | string): string { + return processInlineTags(typeof parts === 'string' ? parts : ts.displayPartsToString(parts)); +} + +export function getMarkdownDocumentation(documentation: ts.SymbolDisplayPart[] | undefined, tags: ts.JSDocTagInfo[] | undefined) { + let result: Array = []; + if (documentation) { + result.push(plain(documentation)); + } + + if (tags) { + result = result.concat(tags.map(getTagDocumentation)); + } + + return result.filter(isNotNullOrUndefined).join('\n\n'); +} diff --git a/tools/language-server/src/plugins/typescript/utils.ts b/tools/language-server/src/plugins/typescript/utils.ts index 4b767c8e1..a1a748946 100644 --- a/tools/language-server/src/plugins/typescript/utils.ts +++ b/tools/language-server/src/plugins/typescript/utils.ts @@ -178,7 +178,7 @@ export function isVirtualFilePath(filePath: string) { } export function toVirtualAstroFilePath(filePath: string) { - if(isVirtualFrameworkFilePath('astro', filePath)) { + if (isVirtualFrameworkFilePath('astro', filePath)) { return filePath; } return `${filePath}.ts`;