JSDoc overload tag (#51234)

* Add support for JSDocOverloadTag

* Use overload tag to determine function type

* Update baselines

* Add new tests along with baselines

* Add tests for all @overload tags in one comment

* Add tests for find-all-ref and rename operations

* Add tests for alternative uses of @overload tag

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
Tomasz Lenarcik
2022-12-13 15:10:40 -08:00
committed by GitHub
co-authored by Nathan Shively-Sanders
parent 4076ff8fd6
commit e4816ed44c
35 changed files with 1979 additions and 46 deletions
+3
View File
@@ -220,6 +220,7 @@ import {
JSDocClassTag,
JSDocEnumTag,
JSDocFunctionType,
JSDocOverloadTag,
JSDocParameterTag,
JSDocPropertyLikeTag,
JSDocSignature,
@@ -2966,6 +2967,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
case SyntaxKind.JSDocOverloadTag:
return bind((node as JSDocOverloadTag).typeExpression);
}
}
+17
View File
@@ -549,6 +549,7 @@ import {
isJSDocNullableType,
isJSDocOptionalParameter,
isJSDocOptionalType,
isJSDocOverloadTag,
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocPropertyTag,
@@ -14270,6 +14271,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
continue;
}
}
if (isInJSFile(decl) && decl.jsDoc) {
let hasJSDocOverloads = false;
for (const node of decl.jsDoc) {
if (node.tags) {
for (const tag of node.tags) {
if (isJSDocOverloadTag(tag)) {
result.push(getSignatureFromDeclaration(tag.typeExpression));
hasJSDocOverloads = true;
}
}
}
}
if (hasJSDocOverloads) {
continue;
}
}
// If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters.
// Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible.
result.push(
+8
View File
@@ -261,6 +261,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocPropertyLikeTag,
JSDocReturnTag,
JSDocSeeTag,
@@ -2117,6 +2118,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return;
case SyntaxKind.JSDocCallbackTag:
return emitJSDocCallbackTag(node as JSDocCallbackTag);
case SyntaxKind.JSDocOverloadTag:
return emitJSDocOverloadTag(node as JSDocOverloadTag);
// SyntaxKind.JSDocEnumTag (see below)
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
@@ -4375,6 +4378,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
emitJSDocSignature(tag.typeExpression);
}
function emitJSDocOverloadTag(tag: JSDocOverloadTag) {
emitJSDocComment(tag.comment);
emitJSDocSignature(tag.typeExpression);
}
function emitJSDocSimpleTag(tag: JSDocTag) {
emitJSDocTagName(tag.tagName);
emitJSDocComment(tag.comment);
+20
View File
@@ -239,6 +239,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocOverrideTag,
JSDocParameterTag,
JSDocPrivateTag,
@@ -830,6 +831,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateJSDocPropertyTag,
createJSDocCallbackTag,
updateJSDocCallbackTag,
createJSDocOverloadTag,
updateJSDocOverloadTag,
createJSDocAugmentsTag,
updateJSDocAugmentsTag,
createJSDocImplementsTag,
@@ -5305,6 +5308,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}
// @api
function createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag {
const node = createBaseJSDocTag<JSDocOverloadTag>(SyntaxKind.JSDocOverloadTag, tagName ?? createIdentifier("overload"), comment);
node.typeExpression = typeExpression;
return node;
}
// @api
function updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag {
return node.tagName !== tagName
|| node.typeExpression !== typeExpression
|| node.comment !== comment
? update(createJSDocOverloadTag(tagName, typeExpression, comment), node)
: node;
}
// @api
function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag {
const node = createBaseJSDocTag<JSDocAugmentsTag>(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment);
@@ -7193,6 +7212,7 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
case SyntaxKind.JSDocParameterTag: return "param";
case SyntaxKind.JSDocPropertyTag: return "prop";
case SyntaxKind.JSDocCallbackTag: return "callback";
case SyntaxKind.JSDocOverloadTag: return "overload";
case SyntaxKind.JSDocAugmentsTag: return "augments";
case SyntaxKind.JSDocImplementsTag: return "implements";
default:
+5
View File
@@ -98,6 +98,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocOverrideTag,
JSDocParameterTag,
JSDocPrivateTag,
@@ -1135,6 +1136,10 @@ export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag {
return node.kind === SyntaxKind.JSDocOverrideTag;
}
export function isJSDocOverloadTag(node: Node): node is JSDocOverloadTag {
return node.kind === SyntaxKind.JSDocOverloadTag;
}
export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
return node.kind === SyntaxKind.JSDocDeprecatedTag;
}
+24 -5
View File
@@ -177,6 +177,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocOverrideTag,
JSDocParameterTag,
JSDocPrivateTag,
@@ -8782,6 +8783,9 @@ namespace Parser {
case "callback":
tag = parseCallbackTag(start, tagName, margin, indentText);
break;
case "overload":
tag = parseOverloadTag(start, tagName, margin, indentText);
break;
case "see":
tag = parseSeeTag(start, tagName, margin, indentText);
break;
@@ -9275,10 +9279,7 @@ namespace Parser {
return createNodeArray(parameters || [], pos);
}
function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag {
const fullName = parseJSDocTypeNameWithNamespace();
skipWhitespace();
let comment = parseTagComments(indent);
function parseJSDocSignature(start: number, indent: number): JSDocSignature {
const parameters = parseCallbackTagParameters(indent);
const returnTag = tryParse(() => {
if (parseOptionalJsdoc(SyntaxKind.AtToken)) {
@@ -9288,7 +9289,14 @@ namespace Parser {
}
}
});
const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start);
return finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start);
}
function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag {
const fullName = parseJSDocTypeNameWithNamespace();
skipWhitespace();
let comment = parseTagComments(indent);
const typeExpression = parseJSDocSignature(start, indent);
if (!comment) {
comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
}
@@ -9296,6 +9304,17 @@ namespace Parser {
return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start, end);
}
function parseOverloadTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocOverloadTag {
skipWhitespace();
let comment = parseTagComments(indent);
const typeExpression = parseJSDocSignature(start, indent);
if (!comment) {
comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
}
const end = comment !== undefined ? getNodePos() : typeExpression.end;
return finishNode(factory.createJSDocOverloadTag(tagName, typeExpression, comment), start, end);
}
function escapedTextsEqual(a: EntityName, b: EntityName): boolean {
while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) {
if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) {
+10
View File
@@ -423,6 +423,7 @@ export const enum SyntaxKind {
JSDocReadonlyTag,
JSDocOverrideTag,
JSDocCallbackTag,
JSDocOverloadTag,
JSDocEnumTag,
JSDocParameterTag,
JSDocReturnTag,
@@ -4065,6 +4066,13 @@ export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration, LocalsCont
readonly typeExpression: JSDocSignature;
}
export interface JSDocOverloadTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocOverloadTag;
readonly parent: JSDoc;
readonly typeExpression: JSDocSignature;
}
export interface JSDocThrowsTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocThrowsTag;
readonly typeExpression?: JSDocTypeExpression;
@@ -8524,6 +8532,8 @@ export interface NodeFactory {
updateJSDocEnumTag(node: JSDocEnumTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocEnumTag;
createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray<JSDocComment>): JSDocCallbackTag;
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag;
updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag;
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;
+2 -1
View File
@@ -271,6 +271,7 @@ import {
isJSDocMemberName,
isJSDocNameReference,
isJSDocNode,
isJSDocOverloadTag,
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocSignature,
@@ -5801,7 +5802,7 @@ export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParam
/** template tags are only available when a typedef isn't already using them */
function isNonTypeAliasTemplate(tag: JSDocTag): tag is JSDocTemplateTag {
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDoc && tag.parent.tags!.some(isJSDocTypeAlias));
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDoc && (tag.parent.tags!.some(isJSDocTypeAlias) || tag.parent.tags!.some(isJSDocOverloadTag)));
}
/**
+9
View File
@@ -124,6 +124,7 @@ import {
isJSDocEnumTag,
isJSDocFunctionType,
isJSDocImplementsTag,
isJSDocOverloadTag,
isJSDocOverrideTag,
isJSDocParameterTag,
isJSDocPrivateTag,
@@ -1210,6 +1211,14 @@ function formatJSDocLink(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
*/
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): readonly TypeParameterDeclaration[] {
if (isJSDocSignature(node)) {
if (isJSDoc(node.parent)) {
const overloadTag = find(node.parent.tags, (tag) => {
return isJSDocOverloadTag(tag) && tag.typeExpression === node;
});
if (overloadTag) {
return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined);
}
}
return emptyArray;
}
if (isJSDocTypeAlias(node)) {