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
This commit is contained in:
Oleksandr T
2022-12-12 14:44:38 -08:00
committed by GitHub
parent f1288c33a1
commit 355991c806
30 changed files with 752 additions and 29 deletions
+1
View File
@@ -2122,6 +2122,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
case SyntaxKind.JSDocReturnTag:
case SyntaxKind.JSDocThisTag:
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocThrowsTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
case SyntaxKind.JSDocTemplateTag:
return emitJSDocTemplateTag(node as JSDocTemplateTag);
+3
View File
@@ -255,6 +255,7 @@ import {
JSDocTemplateTag,
JSDocText,
JSDocThisTag,
JSDocThrowsTag,
JSDocType,
JSDocTypedefTag,
JSDocTypeExpression,
@@ -871,6 +872,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get createJSDocThrowsTag() { return getJSDocTypeLikeTagCreateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
get updateJSDocThrowsTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
createJSDocUnknownTag,
updateJSDocUnknownTag,
createJSDocText,
+5
View File
@@ -109,6 +109,7 @@ import {
JSDocSignature,
JSDocTemplateTag,
JSDocThisTag,
JSDocThrowsTag,
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeLiteral,
@@ -1176,6 +1177,10 @@ export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
return node.kind === SyntaxKind.JSDocImplementsTag;
}
export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag {
return node.kind === SyntaxKind.JSDocThrowsTag;
}
// Synthesized list
/** @internal */
+17 -5
View File
@@ -196,6 +196,7 @@ import {
JSDocTemplateTag,
JSDocText,
JSDocThisTag,
JSDocThrowsTag,
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeLiteral,
@@ -1100,10 +1101,11 @@ const forEachChildTable: ForEachChildTable = {
visitNode(cbNode, node.typeExpression) ||
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
},
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocThrowsTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature<T>(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return forEach(node.typeParameters, cbNode) ||
forEach(node.parameters, cbNode) ||
@@ -1197,7 +1199,7 @@ function forEachChildInJSDocParameterOrPropertyTag<T>(node: JSDocParameterTag |
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
}
function forEachChildInJSDocReturnTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.tagName) ||
visitNode(cbNode, node.typeExpression) ||
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
@@ -8785,6 +8787,10 @@ namespace Parser {
case "see":
tag = parseSeeTag(start, tagName, margin, indentText);
break;
case "exception":
case "throws":
tag = parseThrowsTag(start, tagName, margin, indentText);
break;
default:
tag = parseUnknownTag(start, tagName, margin, indentText);
break;
@@ -9090,6 +9096,12 @@ namespace Parser {
return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start);
}
function parseThrowsTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocThrowsTag {
const typeExpression = tryParseTypeExpression();
const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
return finishNode(factory.createJSDocThrowsTag(tagName, typeExpression, comment), start);
}
function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag {
const commentStart = getNodePos();
const textOnly = parseAuthorNameAndEmail();
+11 -2
View File
@@ -431,6 +431,7 @@ export const enum SyntaxKind {
JSDocTypedefTag,
JSDocSeeTag,
JSDocPropertyTag,
JSDocThrowsTag,
// Synthesized list
SyntaxList,
@@ -475,9 +476,9 @@ export const enum SyntaxKind {
LastStatement = DebuggerStatement,
FirstNode = QualifiedName,
FirstJSDocNode = JSDocTypeExpression,
LastJSDocNode = JSDocPropertyTag,
LastJSDocNode = JSDocThrowsTag,
FirstJSDocTagNode = JSDocTag,
LastJSDocTagNode = JSDocPropertyTag,
LastJSDocTagNode = JSDocThrowsTag,
/** @internal */ FirstContextualKeyword = AbstractKeyword,
/** @internal */ LastContextualKeyword = OfKeyword,
}
@@ -954,6 +955,7 @@ export type ForEachChildNodes =
| JSDocProtectedTag
| JSDocReadonlyTag
| JSDocDeprecatedTag
| JSDocThrowsTag
| JSDocOverrideTag
;
@@ -3827,6 +3829,11 @@ export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration {
readonly typeExpression: JSDocSignature;
}
export interface JSDocThrowsTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocThrowsTag;
readonly typeExpression?: JSDocTypeExpression;
}
export interface JSDocSignature extends JSDocType, Declaration {
readonly kind: SyntaxKind.JSDocSignature;
readonly typeParameters?: readonly JSDocTemplateTag[];
@@ -8257,6 +8264,8 @@ export interface NodeFactory {
updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocDeprecatedTag;
createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment>): JSDocThrowsTag;
updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment> | undefined): JSDocThrowsTag;
createJSDocText(text: string): JSDocText;
updateJSDocText(node: JSDocText, text: string): JSDocText;
createJSDocComment(comment?: string | NodeArray<JSDocComment> | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc;
+6
View File
@@ -47,6 +47,7 @@ import {
JSDocSeeTag,
JSDocTemplateTag,
JSDocThisTag,
JSDocThrowsTag,
JSDocTypedefTag,
JSDocTypeTag,
JsxAttribute,
@@ -847,6 +848,11 @@ export function getEncodedSyntacticClassifications(cancellationToken: Cancellati
case SyntaxKind.JSDocImplementsTag:
commentStart = (tag as JSDocImplementsTag | JSDocAugmentsTag).class.end;
break;
case SyntaxKind.JSDocThrowsTag:
processElement((tag as JSDocThrowsTag).typeExpression);
pos = tag.end;
commentStart = (tag as JSDocThrowsTag).typeExpression?.end || commentStart;
break;
}
if (typeof tag.comment === "object") {
pushCommentRange(tag.comment.pos, tag.comment.end - tag.comment.pos);
+10 -1
View File
@@ -237,6 +237,7 @@ import {
JSDocTag,
JSDocTagInfo,
JSDocTemplateTag,
JSDocThrowsTag,
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeTag,
@@ -2955,7 +2956,14 @@ function getCompletionData(
flags,
};
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag;
type JSDocTagWithTypeExpression =
| JSDocParameterTag
| JSDocPropertyTag
| JSDocReturnTag
| JSDocTypeTag
| JSDocTypedefTag
| JSDocTemplateTag
| JSDocThrowsTag;
function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression {
switch (tag.kind) {
@@ -2964,6 +2972,7 @@ function getCompletionData(
case SyntaxKind.JSDocReturnTag:
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocThrowsTag:
return true;
case SyntaxKind.JSDocTemplateTag:
return !!(tag as JSDocTemplateTag).constraint;
+5
View File
@@ -56,6 +56,7 @@ import {
JSDocTag,
JSDocTagInfo,
JSDocTemplateTag,
JSDocThrowsTag,
JSDocTypedefTag,
JSDocTypeTag,
lastOrUndefined,
@@ -258,6 +259,10 @@ function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDis
const { comment, kind } = tag;
const namePart = getTagNameDisplayPart(kind);
switch (kind) {
case SyntaxKind.JSDocThrowsTag:
const typeExpression = (tag as JSDocThrowsTag).typeExpression;
return typeExpression ? withNode(typeExpression) :
comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker);
case SyntaxKind.JSDocImplementsTag:
return withNode((tag as JSDocImplementsTag).class);
case SyntaxKind.JSDocAugmentsTag:
+29
View File
@@ -294,6 +294,35 @@ describe("unittests:: JSDocParsing", () => {
parsesCorrectly("paramWithoutType",
`/**
* @param foo
*/`);
parsesCorrectly("throwsTag1",
`/**
* @throws {Error}
*/`);
parsesCorrectly("throwsTag2",
`/**
* @throws free-form description
*/`);
parsesCorrectly("throwsTag3",
`/**
* @throws {Error} description
*/`);
parsesCorrectly("exceptionTag1",
`/**
* @exception {Error}
*/`);
parsesCorrectly("exceptionTag2",
`/**
* @exception free-form description
*/`);
parsesCorrectly("exceptionTag3",
`/**
* @exception {Error} description
*/`);
parsesCorrectly("typedefTagWithChildrenTags",
`/**