Skip to content

Commit c0d560e

Browse files
authored
Don't parse @link inside @example JSDoc tags (microsoft#187877)
Fixes microsoft#187768
1 parent 86bb523 commit c0d560e

File tree

2 files changed

+49
-10
lines changed

2 files changed

+49
-10
lines changed

extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ function getTagBodyText(
4949
return '```\n' + text + '\n```';
5050
}
5151

52-
const text = convertLinkTags(tag.text, filePathConverter);
52+
let text = convertLinkTags(tag.text, filePathConverter);
5353
switch (tag.name) {
5454
case 'example': {
55+
// Example text does not support `{@link}` as it is considered code.
56+
// TODO: should we support it if it appears outside of an explicit code block?
57+
text = asPlainText(tag.text);
58+
5559
// check for caption tags, fix for #79704
5660
const captionTagMatches = text.match(/<caption>(.*?)<\/caption>\s*(\r\n|\n)/);
5761
if (captionTagMatches && captionTagMatches.index === 0) {
@@ -132,6 +136,13 @@ function getTagBody(tag: Proto.JSDocTagInfo, filePathConverter: IFilePathToResou
132136
return (convertLinkTags(tag.text, filePathConverter)).split(/^(\S+)\s*-?\s*/);
133137
}
134138

139+
function asPlainText(parts: readonly Proto.SymbolDisplayPart[] | string): string {
140+
if (typeof parts === 'string') {
141+
return parts;
142+
}
143+
return parts.map(part => part.text).join('');
144+
}
145+
135146
export function asPlainTextWithLinks(
136147
parts: readonly Proto.SymbolDisplayPart[] | string,
137148
filePathConverter: IFilePathToResourceConverter,

extensions/typescript-language-features/src/test/unit/textRendering.test.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const noopToResource: IFilePathToResourceConverter = {
1414
};
1515

1616
suite('typescript.previewer', () => {
17-
test('Should ignore hyphens after a param tag', async () => {
17+
test('Should ignore hyphens after a param tag', () => {
1818
assert.strictEqual(
1919
tagsToMarkdown([
2020
{
@@ -25,7 +25,7 @@ suite('typescript.previewer', () => {
2525
'*@param* `a` — b');
2626
});
2727

28-
test('Should parse url jsdoc @link', async () => {
28+
test('Should parse url jsdoc @link', () => {
2929
assert.strictEqual(
3030
documentationToMarkdown(
3131
'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z',
@@ -35,7 +35,7 @@ suite('typescript.previewer', () => {
3535
'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');
3636
});
3737

38-
test('Should parse url jsdoc @link with text', async () => {
38+
test('Should parse url jsdoc @link with text', () => {
3939
assert.strictEqual(
4040
documentationToMarkdown(
4141
'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z',
@@ -45,7 +45,7 @@ suite('typescript.previewer', () => {
4545
'x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z');
4646
});
4747

48-
test('Should treat @linkcode jsdocs links as monospace', async () => {
48+
test('Should treat @linkcode jsdocs links as monospace', () => {
4949
assert.strictEqual(
5050
documentationToMarkdown(
5151
'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z',
@@ -55,7 +55,7 @@ suite('typescript.previewer', () => {
5555
'x [`http://www.example.com/foo`](http://www.example.com/foo) y [http://www.example.com/bar](http://www.example.com/bar) z');
5656
});
5757

58-
test('Should parse url jsdoc @link in param tag', async () => {
58+
test('Should parse url jsdoc @link in param tag', () => {
5959
assert.strictEqual(
6060
tagsToMarkdown([
6161
{
@@ -66,7 +66,7 @@ suite('typescript.previewer', () => {
6666
'*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z');
6767
});
6868

69-
test('Should ignore unclosed jsdocs @link', async () => {
69+
test('Should ignore unclosed jsdocs @link', () => {
7070
assert.strictEqual(
7171
documentationToMarkdown(
7272
'x {@link http://www.example.com/foo y {@link http://www.example.com/bar bar} z',
@@ -76,7 +76,7 @@ suite('typescript.previewer', () => {
7676
'x {@link http://www.example.com/foo y [bar](http://www.example.com/bar) z');
7777
});
7878

79-
test('Should support non-ascii characters in parameter name (#90108)', async () => {
79+
test('Should support non-ascii characters in parameter name (#90108)', () => {
8080
assert.strictEqual(
8181
tagsToMarkdown([
8282
{
@@ -135,7 +135,35 @@ suite('typescript.previewer', () => {
135135
);
136136
});
137137

138-
test('Should render @linkcode symbol name as code', async () => {
138+
test('Should not render @link inside of @example #187768', () => {
139+
assert.strictEqual(
140+
tagsToMarkdown([
141+
{
142+
"name": "example",
143+
"text": [
144+
{
145+
"text": "1 + 1 ",
146+
"kind": "text"
147+
},
148+
{
149+
"text": "{@link ",
150+
"kind": "link"
151+
},
152+
{
153+
"text": "foo",
154+
"kind": "linkName"
155+
},
156+
{
157+
"text": "}",
158+
"kind": "link"
159+
}
160+
]
161+
}
162+
], noopToResource),
163+
'*@example* \n```\n1 + 1 {@link foo}\n```');
164+
});
165+
166+
test('Should render @linkcode symbol name as code', () => {
139167
assert.strictEqual(
140168
asPlainTextWithLinks([
141169
{ "text": "a ", "kind": "text" },
@@ -155,7 +183,7 @@ suite('typescript.previewer', () => {
155183
'a [`dog`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b');
156184
});
157185

158-
test('Should render @linkcode text as code', async () => {
186+
test('Should render @linkcode text as code', () => {
159187
assert.strictEqual(
160188
asPlainTextWithLinks([
161189
{ "text": "a ", "kind": "text" },

0 commit comments

Comments
 (0)