/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import type * as Proto from '../protocol';
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: Proto.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```';
}
switch (tag.name) {
case 'example':
// check for caption tags, fix for #79704
const captionTagMatches = tag.text.match(/
(.*?)<\/caption>\s*(\r\n|\n)/);
if (captionTagMatches && captionTagMatches.index === 0) {
return captionTagMatches[1] + '\n\n' + makeCodeblock(tag.text.substr(captionTagMatches[0].length));
} else {
return makeCodeblock(tag.text);
}
case 'author':
// fix obsucated email address, #80898
const emailMatch = tag.text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/);
if (emailMatch === null) {
return tag.text;
} else {
return `${emailMatch[1]} ${emailMatch[2]}`;
}
case 'default':
return makeCodeblock(tag.text);
}
return processInlineTags(tag.text);
}
function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
switch (tag.name) {
case 'augments':
case 'extends':
case 'param':
case 'template':
const body = (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)}`);
}
}
// 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: Proto.SymbolDisplayPart[] | string): string {
return processInlineTags(
typeof parts === 'string'
? parts
: parts.map(part => part.text).join(''));
}
export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string {
return tags.map(getTagDocumentation).join(' \n\n');
}
export function markdownDocumentation(
documentation: Proto.SymbolDisplayPart[] | string,
tags: Proto.JSDocTagInfo[]
): vscode.MarkdownString {
const out = new vscode.MarkdownString();
addMarkdownDocumentation(out, documentation, tags);
return out;
}
export function addMarkdownDocumentation(
out: vscode.MarkdownString,
documentation: Proto.SymbolDisplayPart[] | string | undefined,
tags: Proto.JSDocTagInfo[] | undefined
): vscode.MarkdownString {
if (documentation) {
out.appendMarkdown(plain(documentation));
}
if (tags) {
const tagsPreview = tagsMarkdownPreview(tags);
if (tagsPreview) {
out.appendMarkdown('\n\n' + tagsPreview);
}
}
return out;
}