Skip to content

Commit e4816ed

Browse files
apenduasandersn
andauthored
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 <[email protected]>
1 parent 4076ff8 commit e4816ed

35 files changed

+1979
-46
lines changed

src/compiler/binder.ts

+3
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ import {
220220
JSDocClassTag,
221221
JSDocEnumTag,
222222
JSDocFunctionType,
223+
JSDocOverloadTag,
223224
JSDocParameterTag,
224225
JSDocPropertyLikeTag,
225226
JSDocSignature,
@@ -2966,6 +2967,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
29662967
case SyntaxKind.JSDocCallbackTag:
29672968
case SyntaxKind.JSDocEnumTag:
29682969
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
2970+
case SyntaxKind.JSDocOverloadTag:
2971+
return bind((node as JSDocOverloadTag).typeExpression);
29692972
}
29702973
}
29712974

src/compiler/checker.ts

+17
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ import {
549549
isJSDocNullableType,
550550
isJSDocOptionalParameter,
551551
isJSDocOptionalType,
552+
isJSDocOverloadTag,
552553
isJSDocParameterTag,
553554
isJSDocPropertyLikeTag,
554555
isJSDocPropertyTag,
@@ -14270,6 +14271,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1427014271
continue;
1427114272
}
1427214273
}
14274+
if (isInJSFile(decl) && decl.jsDoc) {
14275+
let hasJSDocOverloads = false;
14276+
for (const node of decl.jsDoc) {
14277+
if (node.tags) {
14278+
for (const tag of node.tags) {
14279+
if (isJSDocOverloadTag(tag)) {
14280+
result.push(getSignatureFromDeclaration(tag.typeExpression));
14281+
hasJSDocOverloads = true;
14282+
}
14283+
}
14284+
}
14285+
}
14286+
if (hasJSDocOverloads) {
14287+
continue;
14288+
}
14289+
}
1427314290
// If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters.
1427414291
// 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.
1427514292
result.push(

src/compiler/emitter.ts

+8
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ import {
261261
JSDocNonNullableType,
262262
JSDocNullableType,
263263
JSDocOptionalType,
264+
JSDocOverloadTag,
264265
JSDocPropertyLikeTag,
265266
JSDocReturnTag,
266267
JSDocSeeTag,
@@ -2117,6 +2118,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
21172118
return;
21182119
case SyntaxKind.JSDocCallbackTag:
21192120
return emitJSDocCallbackTag(node as JSDocCallbackTag);
2121+
case SyntaxKind.JSDocOverloadTag:
2122+
return emitJSDocOverloadTag(node as JSDocOverloadTag);
21202123
// SyntaxKind.JSDocEnumTag (see below)
21212124
case SyntaxKind.JSDocParameterTag:
21222125
case SyntaxKind.JSDocPropertyTag:
@@ -4375,6 +4378,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
43754378
emitJSDocSignature(tag.typeExpression);
43764379
}
43774380

4381+
function emitJSDocOverloadTag(tag: JSDocOverloadTag) {
4382+
emitJSDocComment(tag.comment);
4383+
emitJSDocSignature(tag.typeExpression);
4384+
}
4385+
43784386
function emitJSDocSimpleTag(tag: JSDocTag) {
43794387
emitJSDocTagName(tag.tagName);
43804388
emitJSDocComment(tag.comment);

src/compiler/factory/nodeFactory.ts

+20
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ import {
239239
JSDocNonNullableType,
240240
JSDocNullableType,
241241
JSDocOptionalType,
242+
JSDocOverloadTag,
242243
JSDocOverrideTag,
243244
JSDocParameterTag,
244245
JSDocPrivateTag,
@@ -830,6 +831,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
830831
updateJSDocPropertyTag,
831832
createJSDocCallbackTag,
832833
updateJSDocCallbackTag,
834+
createJSDocOverloadTag,
835+
updateJSDocOverloadTag,
833836
createJSDocAugmentsTag,
834837
updateJSDocAugmentsTag,
835838
createJSDocImplementsTag,
@@ -5305,6 +5308,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
53055308
: node;
53065309
}
53075310

5311+
// @api
5312+
function createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag {
5313+
const node = createBaseJSDocTag<JSDocOverloadTag>(SyntaxKind.JSDocOverloadTag, tagName ?? createIdentifier("overload"), comment);
5314+
node.typeExpression = typeExpression;
5315+
return node;
5316+
}
5317+
5318+
// @api
5319+
function updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag {
5320+
return node.tagName !== tagName
5321+
|| node.typeExpression !== typeExpression
5322+
|| node.comment !== comment
5323+
? update(createJSDocOverloadTag(tagName, typeExpression, comment), node)
5324+
: node;
5325+
}
5326+
53085327
// @api
53095328
function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag {
53105329
const node = createBaseJSDocTag<JSDocAugmentsTag>(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment);
@@ -7193,6 +7212,7 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
71937212
case SyntaxKind.JSDocParameterTag: return "param";
71947213
case SyntaxKind.JSDocPropertyTag: return "prop";
71957214
case SyntaxKind.JSDocCallbackTag: return "callback";
7215+
case SyntaxKind.JSDocOverloadTag: return "overload";
71967216
case SyntaxKind.JSDocAugmentsTag: return "augments";
71977217
case SyntaxKind.JSDocImplementsTag: return "implements";
71987218
default:

src/compiler/factory/nodeTests.ts

+5
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import {
9898
JSDocNonNullableType,
9999
JSDocNullableType,
100100
JSDocOptionalType,
101+
JSDocOverloadTag,
101102
JSDocOverrideTag,
102103
JSDocParameterTag,
103104
JSDocPrivateTag,
@@ -1135,6 +1136,10 @@ export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag {
11351136
return node.kind === SyntaxKind.JSDocOverrideTag;
11361137
}
11371138

1139+
export function isJSDocOverloadTag(node: Node): node is JSDocOverloadTag {
1140+
return node.kind === SyntaxKind.JSDocOverloadTag;
1141+
}
1142+
11381143
export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
11391144
return node.kind === SyntaxKind.JSDocDeprecatedTag;
11401145
}

src/compiler/parser.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ import {
177177
JSDocNonNullableType,
178178
JSDocNullableType,
179179
JSDocOptionalType,
180+
JSDocOverloadTag,
180181
JSDocOverrideTag,
181182
JSDocParameterTag,
182183
JSDocPrivateTag,
@@ -8782,6 +8783,9 @@ namespace Parser {
87828783
case "callback":
87838784
tag = parseCallbackTag(start, tagName, margin, indentText);
87848785
break;
8786+
case "overload":
8787+
tag = parseOverloadTag(start, tagName, margin, indentText);
8788+
break;
87858789
case "see":
87868790
tag = parseSeeTag(start, tagName, margin, indentText);
87878791
break;
@@ -9275,10 +9279,7 @@ namespace Parser {
92759279
return createNodeArray(parameters || [], pos);
92769280
}
92779281

9278-
function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag {
9279-
const fullName = parseJSDocTypeNameWithNamespace();
9280-
skipWhitespace();
9281-
let comment = parseTagComments(indent);
9282+
function parseJSDocSignature(start: number, indent: number): JSDocSignature {
92829283
const parameters = parseCallbackTagParameters(indent);
92839284
const returnTag = tryParse(() => {
92849285
if (parseOptionalJsdoc(SyntaxKind.AtToken)) {
@@ -9288,14 +9289,32 @@ namespace Parser {
92889289
}
92899290
}
92909291
});
9291-
const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start);
9292+
return finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start);
9293+
}
9294+
9295+
function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag {
9296+
const fullName = parseJSDocTypeNameWithNamespace();
9297+
skipWhitespace();
9298+
let comment = parseTagComments(indent);
9299+
const typeExpression = parseJSDocSignature(start, indent);
92929300
if (!comment) {
92939301
comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
92949302
}
92959303
const end = comment !== undefined ? getNodePos() : typeExpression.end;
92969304
return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start, end);
92979305
}
92989306

9307+
function parseOverloadTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocOverloadTag {
9308+
skipWhitespace();
9309+
let comment = parseTagComments(indent);
9310+
const typeExpression = parseJSDocSignature(start, indent);
9311+
if (!comment) {
9312+
comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
9313+
}
9314+
const end = comment !== undefined ? getNodePos() : typeExpression.end;
9315+
return finishNode(factory.createJSDocOverloadTag(tagName, typeExpression, comment), start, end);
9316+
}
9317+
92999318
function escapedTextsEqual(a: EntityName, b: EntityName): boolean {
93009319
while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) {
93019320
if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) {

src/compiler/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ export const enum SyntaxKind {
423423
JSDocReadonlyTag,
424424
JSDocOverrideTag,
425425
JSDocCallbackTag,
426+
JSDocOverloadTag,
426427
JSDocEnumTag,
427428
JSDocParameterTag,
428429
JSDocReturnTag,
@@ -4065,6 +4066,13 @@ export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration, LocalsCont
40654066
readonly typeExpression: JSDocSignature;
40664067
}
40674068

4069+
4070+
export interface JSDocOverloadTag extends JSDocTag {
4071+
readonly kind: SyntaxKind.JSDocOverloadTag;
4072+
readonly parent: JSDoc;
4073+
readonly typeExpression: JSDocSignature;
4074+
}
4075+
40684076
export interface JSDocThrowsTag extends JSDocTag {
40694077
readonly kind: SyntaxKind.JSDocThrowsTag;
40704078
readonly typeExpression?: JSDocTypeExpression;
@@ -8524,6 +8532,8 @@ export interface NodeFactory {
85248532
updateJSDocEnumTag(node: JSDocEnumTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocEnumTag;
85258533
createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray<JSDocComment>): JSDocCallbackTag;
85268534
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
8535+
createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag;
8536+
updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag;
85278537
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
85288538
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
85298539
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;

src/compiler/utilities.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ import {
271271
isJSDocMemberName,
272272
isJSDocNameReference,
273273
isJSDocNode,
274+
isJSDocOverloadTag,
274275
isJSDocParameterTag,
275276
isJSDocPropertyLikeTag,
276277
isJSDocSignature,
@@ -5801,7 +5802,7 @@ export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParam
58015802

58025803
/** template tags are only available when a typedef isn't already using them */
58035804
function isNonTypeAliasTemplate(tag: JSDocTag): tag is JSDocTemplateTag {
5804-
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDoc && tag.parent.tags!.some(isJSDocTypeAlias));
5805+
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDoc && (tag.parent.tags!.some(isJSDocTypeAlias) || tag.parent.tags!.some(isJSDocOverloadTag)));
58055806
}
58065807

58075808
/**

src/compiler/utilitiesPublic.ts

+9
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ import {
124124
isJSDocEnumTag,
125125
isJSDocFunctionType,
126126
isJSDocImplementsTag,
127+
isJSDocOverloadTag,
127128
isJSDocOverrideTag,
128129
isJSDocParameterTag,
129130
isJSDocPrivateTag,
@@ -1210,6 +1211,14 @@ function formatJSDocLink(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
12101211
*/
12111212
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): readonly TypeParameterDeclaration[] {
12121213
if (isJSDocSignature(node)) {
1214+
if (isJSDoc(node.parent)) {
1215+
const overloadTag = find(node.parent.tags, (tag) => {
1216+
return isJSDocOverloadTag(tag) && tag.typeExpression === node;
1217+
});
1218+
if (overloadTag) {
1219+
return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined);
1220+
}
1221+
}
12131222
return emptyArray;
12141223
}
12151224
if (isJSDocTypeAlias(node)) {

tests/baselines/reference/api/tsserverlibrary.d.ts

+29-20
Original file line numberDiff line numberDiff line change
@@ -4339,24 +4339,25 @@ declare namespace ts {
43394339
JSDocReadonlyTag = 339,
43404340
JSDocOverrideTag = 340,
43414341
JSDocCallbackTag = 341,
4342-
JSDocEnumTag = 342,
4343-
JSDocParameterTag = 343,
4344-
JSDocReturnTag = 344,
4345-
JSDocThisTag = 345,
4346-
JSDocTypeTag = 346,
4347-
JSDocTemplateTag = 347,
4348-
JSDocTypedefTag = 348,
4349-
JSDocSeeTag = 349,
4350-
JSDocPropertyTag = 350,
4351-
JSDocThrowsTag = 351,
4352-
SyntaxList = 352,
4353-
NotEmittedStatement = 353,
4354-
PartiallyEmittedExpression = 354,
4355-
CommaListExpression = 355,
4356-
MergeDeclarationMarker = 356,
4357-
EndOfDeclarationMarker = 357,
4358-
SyntheticReferenceExpression = 358,
4359-
Count = 359,
4342+
JSDocOverloadTag = 342,
4343+
JSDocEnumTag = 343,
4344+
JSDocParameterTag = 344,
4345+
JSDocReturnTag = 345,
4346+
JSDocThisTag = 346,
4347+
JSDocTypeTag = 347,
4348+
JSDocTemplateTag = 348,
4349+
JSDocTypedefTag = 349,
4350+
JSDocSeeTag = 350,
4351+
JSDocPropertyTag = 351,
4352+
JSDocThrowsTag = 352,
4353+
SyntaxList = 353,
4354+
NotEmittedStatement = 354,
4355+
PartiallyEmittedExpression = 355,
4356+
CommaListExpression = 356,
4357+
MergeDeclarationMarker = 357,
4358+
EndOfDeclarationMarker = 358,
4359+
SyntheticReferenceExpression = 359,
4360+
Count = 360,
43604361
FirstAssignment = 63,
43614362
LastAssignment = 78,
43624363
FirstCompoundAssignment = 64,
@@ -4385,9 +4386,9 @@ declare namespace ts {
43854386
LastStatement = 256,
43864387
FirstNode = 163,
43874388
FirstJSDocNode = 312,
4388-
LastJSDocNode = 351,
4389+
LastJSDocNode = 352,
43894390
FirstJSDocTagNode = 330,
4390-
LastJSDocTagNode = 351
4391+
LastJSDocTagNode = 352
43914392
}
43924393
type TriviaSyntaxKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia | SyntaxKind.NewLineTrivia | SyntaxKind.WhitespaceTrivia | SyntaxKind.ShebangTrivia | SyntaxKind.ConflictMarkerTrivia;
43934394
type LiteralSyntaxKind = SyntaxKind.NumericLiteral | SyntaxKind.BigIntLiteral | SyntaxKind.StringLiteral | SyntaxKind.JsxText | SyntaxKind.JsxTextAllWhiteSpaces | SyntaxKind.RegularExpressionLiteral | SyntaxKind.NoSubstitutionTemplateLiteral;
@@ -5946,6 +5947,11 @@ declare namespace ts {
59465947
readonly name?: Identifier;
59475948
readonly typeExpression: JSDocSignature;
59485949
}
5950+
interface JSDocOverloadTag extends JSDocTag {
5951+
readonly kind: SyntaxKind.JSDocOverloadTag;
5952+
readonly parent: JSDoc;
5953+
readonly typeExpression: JSDocSignature;
5954+
}
59495955
interface JSDocThrowsTag extends JSDocTag {
59505956
readonly kind: SyntaxKind.JSDocThrowsTag;
59515957
readonly typeExpression?: JSDocTypeExpression;
@@ -7800,6 +7806,8 @@ declare namespace ts {
78007806
updateJSDocEnumTag(node: JSDocEnumTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocEnumTag;
78017807
createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray<JSDocComment>): JSDocCallbackTag;
78027808
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
7809+
createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag;
7810+
updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag;
78037811
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
78047812
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
78057813
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;
@@ -9056,6 +9064,7 @@ declare namespace ts {
90569064
function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag;
90579065
function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag;
90589066
function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag;
9067+
function isJSDocOverloadTag(node: Node): node is JSDocOverloadTag;
90599068
function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag;
90609069
function isJSDocSeeTag(node: Node): node is JSDocSeeTag;
90619070
function isJSDocEnumTag(node: Node): node is JSDocEnumTag;

0 commit comments

Comments
 (0)