Skip to content

Commit 355991c

Browse files
authored
feat(49323): Render JSDoc @throws {type} as a link (#49891)
* feat(49323): add support throws jsdoc tag * change "name" to "typeExpression". parse "exception" as a synonym for "throws" * include typeExpression from the throws tag in the quick info * add JSDocThrowsTag to ForEachChildNodes
1 parent f1288c3 commit 355991c

30 files changed

+752
-29
lines changed

src/compiler/emitter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
21222122
case SyntaxKind.JSDocReturnTag:
21232123
case SyntaxKind.JSDocThisTag:
21242124
case SyntaxKind.JSDocTypeTag:
2125+
case SyntaxKind.JSDocThrowsTag:
21252126
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
21262127
case SyntaxKind.JSDocTemplateTag:
21272128
return emitJSDocTemplateTag(node as JSDocTemplateTag);

src/compiler/factory/nodeFactory.ts

+3
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ import {
255255
JSDocTemplateTag,
256256
JSDocText,
257257
JSDocThisTag,
258+
JSDocThrowsTag,
258259
JSDocType,
259260
JSDocTypedefTag,
260261
JSDocTypeExpression,
@@ -871,6 +872,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
871872
get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
872873
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
873874
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
875+
get createJSDocThrowsTag() { return getJSDocTypeLikeTagCreateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
876+
get updateJSDocThrowsTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
874877
createJSDocUnknownTag,
875878
updateJSDocUnknownTag,
876879
createJSDocText,

src/compiler/factory/nodeTests.ts

+5
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import {
109109
JSDocSignature,
110110
JSDocTemplateTag,
111111
JSDocThisTag,
112+
JSDocThrowsTag,
112113
JSDocTypedefTag,
113114
JSDocTypeExpression,
114115
JSDocTypeLiteral,
@@ -1176,6 +1177,10 @@ export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
11761177
return node.kind === SyntaxKind.JSDocImplementsTag;
11771178
}
11781179

1180+
export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag {
1181+
return node.kind === SyntaxKind.JSDocThrowsTag;
1182+
}
1183+
11791184
// Synthesized list
11801185

11811186
/** @internal */

src/compiler/parser.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ import {
196196
JSDocTemplateTag,
197197
JSDocText,
198198
JSDocThisTag,
199+
JSDocThrowsTag,
199200
JSDocTypedefTag,
200201
JSDocTypeExpression,
201202
JSDocTypeLiteral,
@@ -1100,10 +1101,11 @@ const forEachChildTable: ForEachChildTable = {
11001101
visitNode(cbNode, node.typeExpression) ||
11011102
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
11021103
},
1103-
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag,
1104-
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag,
1105-
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag,
1106-
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag,
1104+
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocTypeLikeTag,
1105+
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag,
1106+
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag,
1107+
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag,
1108+
[SyntaxKind.JSDocThrowsTag]: forEachChildInJSDocTypeLikeTag,
11071109
[SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature<T>(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
11081110
return forEach(node.typeParameters, cbNode) ||
11091111
forEach(node.parameters, cbNode) ||
@@ -1197,7 +1199,7 @@ function forEachChildInJSDocParameterOrPropertyTag<T>(node: JSDocParameterTag |
11971199
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
11981200
}
11991201

1200-
function forEachChildInJSDocReturnTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
1202+
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
12011203
return visitNode(cbNode, node.tagName) ||
12021204
visitNode(cbNode, node.typeExpression) ||
12031205
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
@@ -8785,6 +8787,10 @@ namespace Parser {
87858787
case "see":
87868788
tag = parseSeeTag(start, tagName, margin, indentText);
87878789
break;
8790+
case "exception":
8791+
case "throws":
8792+
tag = parseThrowsTag(start, tagName, margin, indentText);
8793+
break;
87888794
default:
87898795
tag = parseUnknownTag(start, tagName, margin, indentText);
87908796
break;
@@ -9090,6 +9096,12 @@ namespace Parser {
90909096
return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start);
90919097
}
90929098

9099+
function parseThrowsTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocThrowsTag {
9100+
const typeExpression = tryParseTypeExpression();
9101+
const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
9102+
return finishNode(factory.createJSDocThrowsTag(tagName, typeExpression, comment), start);
9103+
}
9104+
90939105
function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag {
90949106
const commentStart = getNodePos();
90959107
const textOnly = parseAuthorNameAndEmail();

src/compiler/types.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ export const enum SyntaxKind {
431431
JSDocTypedefTag,
432432
JSDocSeeTag,
433433
JSDocPropertyTag,
434+
JSDocThrowsTag,
434435

435436
// Synthesized list
436437
SyntaxList,
@@ -475,9 +476,9 @@ export const enum SyntaxKind {
475476
LastStatement = DebuggerStatement,
476477
FirstNode = QualifiedName,
477478
FirstJSDocNode = JSDocTypeExpression,
478-
LastJSDocNode = JSDocPropertyTag,
479+
LastJSDocNode = JSDocThrowsTag,
479480
FirstJSDocTagNode = JSDocTag,
480-
LastJSDocTagNode = JSDocPropertyTag,
481+
LastJSDocTagNode = JSDocThrowsTag,
481482
/** @internal */ FirstContextualKeyword = AbstractKeyword,
482483
/** @internal */ LastContextualKeyword = OfKeyword,
483484
}
@@ -954,6 +955,7 @@ export type ForEachChildNodes =
954955
| JSDocProtectedTag
955956
| JSDocReadonlyTag
956957
| JSDocDeprecatedTag
958+
| JSDocThrowsTag
957959
| JSDocOverrideTag
958960
;
959961

@@ -3827,6 +3829,11 @@ export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration {
38273829
readonly typeExpression: JSDocSignature;
38283830
}
38293831

3832+
export interface JSDocThrowsTag extends JSDocTag {
3833+
readonly kind: SyntaxKind.JSDocThrowsTag;
3834+
readonly typeExpression?: JSDocTypeExpression;
3835+
}
3836+
38303837
export interface JSDocSignature extends JSDocType, Declaration {
38313838
readonly kind: SyntaxKind.JSDocSignature;
38323839
readonly typeParameters?: readonly JSDocTemplateTag[];
@@ -8257,6 +8264,8 @@ export interface NodeFactory {
82578264
updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocDeprecatedTag;
82588265
createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
82598266
updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
8267+
createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment>): JSDocThrowsTag;
8268+
updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment> | undefined): JSDocThrowsTag;
82608269
createJSDocText(text: string): JSDocText;
82618270
updateJSDocText(node: JSDocText, text: string): JSDocText;
82628271
createJSDocComment(comment?: string | NodeArray<JSDocComment> | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc;

src/services/classifier.ts

+6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
JSDocSeeTag,
4848
JSDocTemplateTag,
4949
JSDocThisTag,
50+
JSDocThrowsTag,
5051
JSDocTypedefTag,
5152
JSDocTypeTag,
5253
JsxAttribute,
@@ -847,6 +848,11 @@ export function getEncodedSyntacticClassifications(cancellationToken: Cancellati
847848
case SyntaxKind.JSDocImplementsTag:
848849
commentStart = (tag as JSDocImplementsTag | JSDocAugmentsTag).class.end;
849850
break;
851+
case SyntaxKind.JSDocThrowsTag:
852+
processElement((tag as JSDocThrowsTag).typeExpression);
853+
pos = tag.end;
854+
commentStart = (tag as JSDocThrowsTag).typeExpression?.end || commentStart;
855+
break;
850856
}
851857
if (typeof tag.comment === "object") {
852858
pushCommentRange(tag.comment.pos, tag.comment.end - tag.comment.pos);

src/services/completions.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ import {
237237
JSDocTag,
238238
JSDocTagInfo,
239239
JSDocTemplateTag,
240+
JSDocThrowsTag,
240241
JSDocTypedefTag,
241242
JSDocTypeExpression,
242243
JSDocTypeTag,
@@ -2955,7 +2956,14 @@ function getCompletionData(
29552956
flags,
29562957
};
29572958

2958-
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag;
2959+
type JSDocTagWithTypeExpression =
2960+
| JSDocParameterTag
2961+
| JSDocPropertyTag
2962+
| JSDocReturnTag
2963+
| JSDocTypeTag
2964+
| JSDocTypedefTag
2965+
| JSDocTemplateTag
2966+
| JSDocThrowsTag;
29592967

29602968
function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression {
29612969
switch (tag.kind) {
@@ -2964,6 +2972,7 @@ function getCompletionData(
29642972
case SyntaxKind.JSDocReturnTag:
29652973
case SyntaxKind.JSDocTypeTag:
29662974
case SyntaxKind.JSDocTypedefTag:
2975+
case SyntaxKind.JSDocThrowsTag:
29672976
return true;
29682977
case SyntaxKind.JSDocTemplateTag:
29692978
return !!(tag as JSDocTemplateTag).constraint;

src/services/jsDoc.ts

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
JSDocTag,
5757
JSDocTagInfo,
5858
JSDocTemplateTag,
59+
JSDocThrowsTag,
5960
JSDocTypedefTag,
6061
JSDocTypeTag,
6162
lastOrUndefined,
@@ -258,6 +259,10 @@ function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDis
258259
const { comment, kind } = tag;
259260
const namePart = getTagNameDisplayPart(kind);
260261
switch (kind) {
262+
case SyntaxKind.JSDocThrowsTag:
263+
const typeExpression = (tag as JSDocThrowsTag).typeExpression;
264+
return typeExpression ? withNode(typeExpression) :
265+
comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker);
261266
case SyntaxKind.JSDocImplementsTag:
262267
return withNode((tag as JSDocImplementsTag).class);
263268
case SyntaxKind.JSDocAugmentsTag:

src/testRunner/unittests/jsDocParsing.ts

+29
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,35 @@ describe("unittests:: JSDocParsing", () => {
294294
parsesCorrectly("paramWithoutType",
295295
`/**
296296
* @param foo
297+
*/`);
298+
parsesCorrectly("throwsTag1",
299+
`/**
300+
* @throws {Error}
301+
*/`);
302+
303+
parsesCorrectly("throwsTag2",
304+
`/**
305+
* @throws free-form description
306+
*/`);
307+
308+
parsesCorrectly("throwsTag3",
309+
`/**
310+
* @throws {Error} description
311+
*/`);
312+
313+
parsesCorrectly("exceptionTag1",
314+
`/**
315+
* @exception {Error}
316+
*/`);
317+
318+
parsesCorrectly("exceptionTag2",
319+
`/**
320+
* @exception free-form description
321+
*/`);
322+
323+
parsesCorrectly("exceptionTag3",
324+
`/**
325+
* @exception {Error} description
297326
*/`);
298327
parsesCorrectly("typedefTagWithChildrenTags",
299328
`/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"kind": "JSDoc",
3+
"pos": 0,
4+
"end": 31,
5+
"flags": "JSDoc",
6+
"modifierFlagsCache": 0,
7+
"transformFlags": 0,
8+
"tags": {
9+
"0": {
10+
"kind": "JSDocThrowsTag",
11+
"pos": 8,
12+
"end": 29,
13+
"modifierFlagsCache": 0,
14+
"transformFlags": 0,
15+
"tagName": {
16+
"kind": "Identifier",
17+
"pos": 9,
18+
"end": 18,
19+
"modifierFlagsCache": 0,
20+
"transformFlags": 0,
21+
"escapedText": "exception"
22+
},
23+
"typeExpression": {
24+
"kind": "JSDocTypeExpression",
25+
"pos": 19,
26+
"end": 26,
27+
"modifierFlagsCache": 0,
28+
"transformFlags": 0,
29+
"type": {
30+
"kind": "TypeReference",
31+
"pos": 20,
32+
"end": 25,
33+
"modifierFlagsCache": 0,
34+
"transformFlags": 1,
35+
"typeName": {
36+
"kind": "Identifier",
37+
"pos": 20,
38+
"end": 25,
39+
"modifierFlagsCache": 0,
40+
"transformFlags": 0,
41+
"escapedText": "Error"
42+
}
43+
}
44+
}
45+
},
46+
"length": 1,
47+
"pos": 8,
48+
"end": 29,
49+
"hasTrailingComma": false,
50+
"transformFlags": 0
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"kind": "JSDoc",
3+
"pos": 0,
4+
"end": 45,
5+
"flags": "JSDoc",
6+
"modifierFlagsCache": 0,
7+
"transformFlags": 0,
8+
"tags": {
9+
"0": {
10+
"kind": "JSDocThrowsTag",
11+
"pos": 8,
12+
"end": 43,
13+
"modifierFlagsCache": 0,
14+
"transformFlags": 0,
15+
"tagName": {
16+
"kind": "Identifier",
17+
"pos": 9,
18+
"end": 18,
19+
"modifierFlagsCache": 0,
20+
"transformFlags": 0,
21+
"escapedText": "exception"
22+
},
23+
"comment": "free-form description"
24+
},
25+
"length": 1,
26+
"pos": 8,
27+
"end": 43,
28+
"hasTrailingComma": false,
29+
"transformFlags": 0
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"kind": "JSDoc",
3+
"pos": 0,
4+
"end": 43,
5+
"flags": "JSDoc",
6+
"modifierFlagsCache": 0,
7+
"transformFlags": 0,
8+
"tags": {
9+
"0": {
10+
"kind": "JSDocThrowsTag",
11+
"pos": 8,
12+
"end": 41,
13+
"modifierFlagsCache": 0,
14+
"transformFlags": 0,
15+
"tagName": {
16+
"kind": "Identifier",
17+
"pos": 9,
18+
"end": 18,
19+
"modifierFlagsCache": 0,
20+
"transformFlags": 0,
21+
"escapedText": "exception"
22+
},
23+
"comment": "description",
24+
"typeExpression": {
25+
"kind": "JSDocTypeExpression",
26+
"pos": 19,
27+
"end": 26,
28+
"modifierFlagsCache": 0,
29+
"transformFlags": 0,
30+
"type": {
31+
"kind": "TypeReference",
32+
"pos": 20,
33+
"end": 25,
34+
"modifierFlagsCache": 0,
35+
"transformFlags": 1,
36+
"typeName": {
37+
"kind": "Identifier",
38+
"pos": 20,
39+
"end": 25,
40+
"modifierFlagsCache": 0,
41+
"transformFlags": 0,
42+
"escapedText": "Error"
43+
}
44+
}
45+
}
46+
},
47+
"length": 1,
48+
"pos": 8,
49+
"end": 41,
50+
"hasTrailingComma": false,
51+
"transformFlags": 0
52+
}
53+
}

0 commit comments

Comments
 (0)