Skip to content

Commit

Permalink
Add support for rendering jsdoc inline @link tags
Browse files Browse the repository at this point in the history
Fixes #28624
  • Loading branch information
mjbvz committed Feb 3, 2020
1 parent 8fd777f commit 55e72d8
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 11 deletions.
6 changes: 2 additions & 4 deletions extensions/typescript-language-features/src/features/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { tagsMarkdownPreview } from '../utils/previewer';
import { markdownDocumentation } from '../utils/previewer';
import * as typeConverters from '../utils/typeConverters';


Expand Down Expand Up @@ -45,9 +45,7 @@ class TypeScriptHoverProvider implements vscode.HoverProvider {
if (data.displayString) {
parts.push({ language: 'typescript', value: data.displayString });
}

const tags = tagsMarkdownPreview(data.tags);
parts.push(data.documentation + (tags ? '\n\n' + tags : ''));
parts.push(markdownDocumentation(data.documentation, data.tags));
return parts;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as assert from 'assert';
import 'mocha';
import { tagsMarkdownPreview } from '../utils/previewer';
import { tagsMarkdownPreview, markdownDocumentation } from '../utils/previewer';

suite('typescript.previewer', () => {
test('Should ignore hyphens after a param tag', async () => {
Expand All @@ -18,5 +18,41 @@ suite('typescript.previewer', () => {
]),
'*@param* `a` — b');
});

test('Should parse url jsdoc @link', async () => {
assert.strictEqual(
markdownDocumentation('x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', []).value,
'x [http://www.example.com/foo](http://www.example.com/foo) y [https://api.jquery.com/bind/#bind-eventType-eventData-handler](https://api.jquery.com/bind/#bind-eventType-eventData-handler) z');
});

test('Should parse url jsdoc @link with text', async () => {
assert.strictEqual(
markdownDocumentation('x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', []).value,
'x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z');
});

test('Should treat @linkcode jsdocs links as monospace', async () => {
assert.strictEqual(
markdownDocumentation('x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', []).value,
'x [`http://www.example.com/foo`](http://www.example.com/foo) y [http://www.example.com/bar](http://www.example.com/bar) z');
});

test('Should parse url jsdoc @link in param tag', async () => {
assert.strictEqual(
tagsMarkdownPreview([
{
name: 'param',
text: 'a x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z'
}
]),
'*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z');
});

test('Should ignore unclosed jsdocs @link', async () => {
assert.strictEqual(
markdownDocumentation('x {@link http://www.example.com/foo y {@link http://www.example.com/bar bar} z', []).value,
'x {@link http://www.example.com/foo y [bar](http://www.example.com/bar) z');
});

});

33 changes: 27 additions & 6 deletions extensions/typescript-language-features/src/utils/previewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@
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;
Expand Down Expand Up @@ -41,7 +59,7 @@ function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
return makeCodeblock(tag.text);
}

return tag.text;
return processInlineTags(tag.text);
}

function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
Expand All @@ -58,7 +76,7 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
if (!doc) {
return label;
}
return label + (doc.match(/\r\n|\n/g) ? ' \n' + doc : ` — ${doc}`);
return label + (doc.match(/\r\n|\n/g) ? ' \n' + processInlineTags(doc) : ` — ${processInlineTags(doc)}`);
}
}

Expand All @@ -71,16 +89,19 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`);
}

export function plain(parts: Proto.SymbolDisplayPart[]): string {
return parts.map(part => part.text).join('');
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[],
documentation: Proto.SymbolDisplayPart[] | string,
tags: Proto.JSDocTagInfo[]
): vscode.MarkdownString {
const out = new vscode.MarkdownString();
Expand All @@ -90,7 +111,7 @@ export function markdownDocumentation(

export function addMarkdownDocumentation(
out: vscode.MarkdownString,
documentation: Proto.SymbolDisplayPart[] | undefined,
documentation: Proto.SymbolDisplayPart[] | string | undefined,
tags: Proto.JSDocTagInfo[] | undefined
): vscode.MarkdownString {
if (documentation) {
Expand Down

0 comments on commit 55e72d8

Please sign in to comment.