Skip to content

Commit 7e1c297

Browse files
a-tarasyuksandersn
andauthored
fix(53181): Overloads Are Not Generated as Expected When Using JsDocOverload Tag to Declare Overloads of Functions Assigned to a Prototype (#53317)
Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 1d7c0c9 commit 7e1c297

12 files changed

+353
-49
lines changed

src/compiler/checker.ts

+9-23
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ import {
305305
getJSDocDeprecatedTag,
306306
getJSDocEnumTag,
307307
getJSDocHost,
308+
getJSDocOverloadTags,
308309
getJSDocParameterTags,
309310
getJSDocRoot,
310311
getJSDocSatisfiesExpressionType,
@@ -15277,22 +15278,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1527715278
}
1527815279
}
1527915280
if (isInJSFile(decl) && decl.jsDoc) {
15280-
let hasJSDocOverloads = false;
15281-
for (const node of decl.jsDoc) {
15282-
if (node.tags) {
15283-
for (const tag of node.tags) {
15284-
if (isJSDocOverloadTag(tag)) {
15285-
const jsDocSignature = tag.typeExpression;
15286-
if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) {
15287-
reportImplicitAny(jsDocSignature, anyType);
15288-
}
15289-
result.push(getSignatureFromDeclaration(jsDocSignature));
15290-
hasJSDocOverloads = true;
15291-
}
15281+
const tags = getJSDocOverloadTags(decl);
15282+
if (length(tags)) {
15283+
for (const tag of tags) {
15284+
const jsDocSignature = tag.typeExpression;
15285+
if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) {
15286+
reportImplicitAny(jsDocSignature, anyType);
1529215287
}
15288+
result.push(getSignatureFromDeclaration(jsDocSignature));
1529315289
}
15294-
}
15295-
if (hasJSDocOverloads) {
1529615290
continue;
1529715291
}
1529815292
}
@@ -40514,15 +40508,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4051440508
}
4051540509
}
4051640510
if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) {
40517-
for (const node of current.jsDoc) {
40518-
if (node.tags) {
40519-
for (const tag of node.tags) {
40520-
if (isJSDocOverloadTag(tag)) {
40521-
hasOverloads = true;
40522-
}
40523-
}
40524-
}
40525-
}
40511+
hasOverloads = length(getJSDocOverloadTags(current)) > 0;
4052640512
}
4052740513
}
4052840514
}

src/compiler/utilities.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ import {
158158
FunctionExpression,
159159
FunctionLikeDeclaration,
160160
GetAccessorDeclaration,
161+
getAllJSDocTags,
161162
getBaseFileName,
162163
GetCanonicalFileName,
163164
getCombinedModifierFlags,
@@ -350,9 +351,11 @@ import {
350351
isWhiteSpaceLike,
351352
isWhiteSpaceSingleLine,
352353
JSDoc,
354+
JSDocArray,
353355
JSDocCallbackTag,
354356
JSDocEnumTag,
355357
JSDocMemberName,
358+
JSDocOverloadTag,
356359
JSDocParameterTag,
357360
JSDocPropertyLikeTag,
358361
JSDocSatisfiesExpression,
@@ -4282,13 +4285,13 @@ export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): read
42824285
let result: (JSDoc | JSDocTag)[] | undefined;
42834286
// Pull parameter comments from declaring function as well
42844287
if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) {
4285-
result = addRange(result, filterOwnedJSDocTags(hostNode, last((hostNode.initializer as HasJSDoc).jsDoc!)));
4288+
result = addRange(result, filterOwnedJSDocTags(hostNode, hostNode.initializer.jsDoc!));
42864289
}
42874290

42884291
let node: Node | undefined = hostNode;
42894292
while (node && node.parent) {
42904293
if (hasJSDocNodes(node)) {
4291-
result = addRange(result, filterOwnedJSDocTags(hostNode, last(node.jsDoc!)));
4294+
result = addRange(result, filterOwnedJSDocTags(hostNode, node.jsDoc!));
42924295
}
42934296

42944297
if (node.kind === SyntaxKind.Parameter) {
@@ -4304,12 +4307,17 @@ export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): read
43044307
return result || emptyArray;
43054308
}
43064309

4307-
function filterOwnedJSDocTags(hostNode: Node, jsDoc: JSDoc | JSDocTag) {
4308-
if (isJSDoc(jsDoc)) {
4309-
const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag));
4310-
return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags;
4311-
}
4312-
return ownsJSDocTag(hostNode, jsDoc) ? [jsDoc] : undefined;
4310+
function filterOwnedJSDocTags(hostNode: Node, comments: JSDocArray) {
4311+
const lastJsDoc = last(comments);
4312+
return flatMap<JSDoc, JSDoc | JSDocTag>(comments, jsDoc => {
4313+
if (jsDoc === lastJsDoc) {
4314+
const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag));
4315+
return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags;
4316+
}
4317+
else {
4318+
return filter(jsDoc.tags, isJSDocOverloadTag);
4319+
}
4320+
});
43134321
}
43144322

43154323
/**
@@ -4394,6 +4402,11 @@ export function getEffectiveContainerForJSDocTemplateTag(node: JSDocTemplateTag)
43944402
return getHostSignatureFromJSDoc(node);
43954403
}
43964404

4405+
/** @internal */
4406+
export function getJSDocOverloadTags(node: Node): readonly JSDocOverloadTag[] {
4407+
return getAllJSDocTags(node, isJSDocOverloadTag);
4408+
}
4409+
43974410
/** @internal */
43984411
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
43994412
const host = getEffectiveJSDocHost(node);

tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js

+58-3
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,69 @@ var example3 = {
107107

108108
//// [jsFileAlternativeUseOfOverloadTag.d.ts]
109109
declare namespace example1 {
110-
function constructor(value: any, options: any): void;
110+
/**
111+
* @overload Example1(value)
112+
* Creates Example1
113+
* @param value [String]
114+
*/
115+
function constructor(value: any): any;
111116
}
112117
declare namespace example2 {
113-
export function constructor_1(): void;
118+
/**
119+
* Example 2
120+
*
121+
* @overload Example2(value)
122+
* Creates Example2
123+
* @param value [String]
124+
* @param secretAccessKey [String]
125+
* @param sessionToken [String]
126+
* @example Creates with string value
127+
* const example = new Example('');
128+
* @overload Example2(options)
129+
* Creates Example2
130+
* @option options value [String]
131+
* @example Creates with options object
132+
* const example = new Example2({
133+
* value: '',
134+
* });
135+
*/
136+
export function constructor_1(value: any, secretAccessKey: any, sessionToken: any): any;
137+
/**
138+
* Example 2
139+
*
140+
* @overload Example2(value)
141+
* Creates Example2
142+
* @param value [String]
143+
* @param secretAccessKey [String]
144+
* @param sessionToken [String]
145+
* @example Creates with string value
146+
* const example = new Example('');
147+
* @overload Example2(options)
148+
* Creates Example2
149+
* @option options value [String]
150+
* @example Creates with options object
151+
* const example = new Example2({
152+
* value: '',
153+
* });
154+
*/
155+
export function constructor_1(): any;
114156
export { constructor_1 as constructor };
115157
}
116158
declare namespace example3 {
117-
function evaluate(options: any, callback: any): void;
159+
/**
160+
* @overload evaluate(options = {}, [callback])
161+
* Evaluate something
162+
* @note Something interesting
163+
* @param options [map]
164+
* @return [string] returns evaluation result
165+
* @return [null] returns nothing if callback provided
166+
* @callback callback function (error, result)
167+
* If callback is provided it will be called with evaluation result
168+
* @param error [Error]
169+
* @param result [String]
170+
* @see callback
171+
*/
172+
function evaluate(): any;
118173
}
119174
/**
120175
* function (error, result)

tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types

+15-15
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
// trying to make sure that our changes do not result in any crashes here.
77

88
const example1 = {
9-
>example1 : { constructor: (value: any, options: any) => void; }
10-
>{ /** * @overload Example1(value) * Creates Example1 * @param value [String] */ constructor: function Example1(value, options) {},} : { constructor: (value: any, options: any) => void; }
9+
>example1 : { constructor: (value: any) => any; }
10+
>{ /** * @overload Example1(value) * Creates Example1 * @param value [String] */ constructor: function Example1(value, options) {},} : { constructor: (value: any) => any; }
1111

1212
/**
1313
* @overload Example1(value)
1414
* Creates Example1
1515
* @param value [String]
1616
*/
1717
constructor: function Example1(value, options) {},
18-
>constructor : (value: any, options: any) => void
19-
>function Example1(value, options) {} : (value: any, options: any) => void
20-
>Example1 : (value: any, options: any) => void
18+
>constructor : (value: any) => any
19+
>function Example1(value, options) {} : (value: any) => any
20+
>Example1 : (value: any) => any
2121
>value : any
2222
>options : any
2323

2424
};
2525

2626
const example2 = {
27-
>example2 : { constructor: () => void; }
28-
>{ /** * Example 2 * * @overload Example2(value) * Creates Example2 * @param value [String] * @param secretAccessKey [String] * @param sessionToken [String] * @example Creates with string value * const example = new Example(''); * @overload Example2(options) * Creates Example2 * @option options value [String] * @example Creates with options object * const example = new Example2({ * value: '', * }); */ constructor: function Example2() {},} : { constructor: () => void; }
27+
>example2 : { constructor: { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }; }
28+
>{ /** * Example 2 * * @overload Example2(value) * Creates Example2 * @param value [String] * @param secretAccessKey [String] * @param sessionToken [String] * @example Creates with string value * const example = new Example(''); * @overload Example2(options) * Creates Example2 * @option options value [String] * @example Creates with options object * const example = new Example2({ * value: '', * }); */ constructor: function Example2() {},} : { constructor: { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }; }
2929

3030
/**
3131
* Example 2
@@ -46,15 +46,15 @@ const example2 = {
4646
* });
4747
*/
4848
constructor: function Example2() {},
49-
>constructor : () => void
50-
>function Example2() {} : () => void
51-
>Example2 : () => void
49+
>constructor : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
50+
>function Example2() {} : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
51+
>Example2 : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
5252

5353
};
5454

5555
const example3 = {
56-
>example3 : { evaluate: (options: any, callback: any) => void; }
57-
>{ /** * @overload evaluate(options = {}, [callback]) * Evaluate something * @note Something interesting * @param options [map] * @return [string] returns evaluation result * @return [null] returns nothing if callback provided * @callback callback function (error, result) * If callback is provided it will be called with evaluation result * @param error [Error] * @param result [String] * @see callback */ evaluate: function evaluate(options, callback) {},} : { evaluate: (options: any, callback: any) => void; }
56+
>example3 : { evaluate: () => any; }
57+
>{ /** * @overload evaluate(options = {}, [callback]) * Evaluate something * @note Something interesting * @param options [map] * @return [string] returns evaluation result * @return [null] returns nothing if callback provided * @callback callback function (error, result) * If callback is provided it will be called with evaluation result * @param error [Error] * @param result [String] * @see callback */ evaluate: function evaluate(options, callback) {},} : { evaluate: () => any; }
5858

5959
/**
6060
* @overload evaluate(options = {}, [callback])
@@ -70,9 +70,9 @@ const example3 = {
7070
* @see callback
7171
*/
7272
evaluate: function evaluate(options, callback) {},
73-
>evaluate : (options: any, callback: any) => void
74-
>function evaluate(options, callback) {} : (options: any, callback: any) => void
75-
>evaluate : (options: any, callback: any) => void
73+
>evaluate : () => any
74+
>function evaluate(options, callback) {} : () => any
75+
>evaluate : () => any
7676
>options : any
7777
>callback : any
7878

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [tests/cases/compiler/jsFileMethodOverloads4.ts] ////
2+
3+
//// [a.js]
4+
export function Foo() { }
5+
6+
/**
7+
* @overload
8+
* @param {string} a
9+
* @return {void}
10+
*/
11+
12+
/**
13+
* @overload
14+
* @param {number} a
15+
* @param {string} b
16+
* @return {void}
17+
*/
18+
19+
/**
20+
* @param {string | number} a
21+
* @param {string} [b]
22+
* @return {void}
23+
*/
24+
Foo.prototype.bar = function (a, b) { }
25+
26+
27+
28+
29+
//// [a.d.ts]
30+
export function Foo(): void;
31+
export class Foo {
32+
/**
33+
* @overload
34+
* @param {string} a
35+
* @return {void}
36+
*/
37+
bar(a: string): void;
38+
/**
39+
* @overload
40+
* @param {number} a
41+
* @param {string} b
42+
* @return {void}
43+
*/
44+
bar(a: number, b: string): void;
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/compiler/jsFileMethodOverloads4.ts] ////
2+
3+
=== /a.js ===
4+
export function Foo() { }
5+
>Foo : Symbol(Foo, Decl(a.js, 0, 0))
6+
7+
/**
8+
* @overload
9+
* @param {string} a
10+
* @return {void}
11+
*/
12+
13+
/**
14+
* @overload
15+
* @param {number} a
16+
* @param {string} b
17+
* @return {void}
18+
*/
19+
20+
/**
21+
* @param {string | number} a
22+
* @param {string} [b]
23+
* @return {void}
24+
*/
25+
Foo.prototype.bar = function (a, b) { }
26+
>Foo.prototype : Symbol(Foo.bar, Decl(a.js, 0, 25))
27+
>Foo : Symbol(Foo, Decl(a.js, 0, 0))
28+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
29+
>bar : Symbol(Foo.bar, Decl(a.js, 0, 25))
30+
>a : Symbol(a, Decl(a.js, 20, 30))
31+
>b : Symbol(b, Decl(a.js, 20, 32))
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [tests/cases/compiler/jsFileMethodOverloads4.ts] ////
2+
3+
=== /a.js ===
4+
export function Foo() { }
5+
>Foo : typeof Foo
6+
7+
/**
8+
* @overload
9+
* @param {string} a
10+
* @return {void}
11+
*/
12+
13+
/**
14+
* @overload
15+
* @param {number} a
16+
* @param {string} b
17+
* @return {void}
18+
*/
19+
20+
/**
21+
* @param {string | number} a
22+
* @param {string} [b]
23+
* @return {void}
24+
*/
25+
Foo.prototype.bar = function (a, b) { }
26+
>Foo.prototype.bar = function (a, b) { } : { (a: string): void; (a: number, b: string): void; }
27+
>Foo.prototype.bar : any
28+
>Foo.prototype : any
29+
>Foo : typeof Foo
30+
>prototype : any
31+
>bar : any
32+
>function (a, b) { } : { (a: string): void; (a: number, b: string): void; }
33+
>a : string | number
34+
>b : string
35+

0 commit comments

Comments
 (0)