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 <293473+sandersn@users.noreply.github.com>
This commit is contained in:
Oleksandr T
2023-12-06 06:27:27 -08:00
committed by GitHub
co-authored by Nathan Shively-Sanders
parent 1d7c0c977a
commit 7e1c297730
12 changed files with 353 additions and 49 deletions
+9 -23
View File
@@ -305,6 +305,7 @@ import {
getJSDocDeprecatedTag,
getJSDocEnumTag,
getJSDocHost,
getJSDocOverloadTags,
getJSDocParameterTags,
getJSDocRoot,
getJSDocSatisfiesExpressionType,
@@ -15277,22 +15278,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
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)) {
const jsDocSignature = tag.typeExpression;
if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) {
reportImplicitAny(jsDocSignature, anyType);
}
result.push(getSignatureFromDeclaration(jsDocSignature));
hasJSDocOverloads = true;
}
const tags = getJSDocOverloadTags(decl);
if (length(tags)) {
for (const tag of tags) {
const jsDocSignature = tag.typeExpression;
if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) {
reportImplicitAny(jsDocSignature, anyType);
}
result.push(getSignatureFromDeclaration(jsDocSignature));
}
}
if (hasJSDocOverloads) {
continue;
}
}
@@ -40514,15 +40508,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) {
for (const node of current.jsDoc) {
if (node.tags) {
for (const tag of node.tags) {
if (isJSDocOverloadTag(tag)) {
hasOverloads = true;
}
}
}
}
hasOverloads = length(getJSDocOverloadTags(current)) > 0;
}
}
}
+21 -8
View File
@@ -158,6 +158,7 @@ import {
FunctionExpression,
FunctionLikeDeclaration,
GetAccessorDeclaration,
getAllJSDocTags,
getBaseFileName,
GetCanonicalFileName,
getCombinedModifierFlags,
@@ -350,9 +351,11 @@ import {
isWhiteSpaceLike,
isWhiteSpaceSingleLine,
JSDoc,
JSDocArray,
JSDocCallbackTag,
JSDocEnumTag,
JSDocMemberName,
JSDocOverloadTag,
JSDocParameterTag,
JSDocPropertyLikeTag,
JSDocSatisfiesExpression,
@@ -4282,13 +4285,13 @@ export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): read
let result: (JSDoc | JSDocTag)[] | undefined;
// Pull parameter comments from declaring function as well
if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) {
result = addRange(result, filterOwnedJSDocTags(hostNode, last((hostNode.initializer as HasJSDoc).jsDoc!)));
result = addRange(result, filterOwnedJSDocTags(hostNode, hostNode.initializer.jsDoc!));
}
let node: Node | undefined = hostNode;
while (node && node.parent) {
if (hasJSDocNodes(node)) {
result = addRange(result, filterOwnedJSDocTags(hostNode, last(node.jsDoc!)));
result = addRange(result, filterOwnedJSDocTags(hostNode, node.jsDoc!));
}
if (node.kind === SyntaxKind.Parameter) {
@@ -4304,12 +4307,17 @@ export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): read
return result || emptyArray;
}
function filterOwnedJSDocTags(hostNode: Node, jsDoc: JSDoc | JSDocTag) {
if (isJSDoc(jsDoc)) {
const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag));
return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags;
}
return ownsJSDocTag(hostNode, jsDoc) ? [jsDoc] : undefined;
function filterOwnedJSDocTags(hostNode: Node, comments: JSDocArray) {
const lastJsDoc = last(comments);
return flatMap<JSDoc, JSDoc | JSDocTag>(comments, jsDoc => {
if (jsDoc === lastJsDoc) {
const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag));
return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags;
}
else {
return filter(jsDoc.tags, isJSDocOverloadTag);
}
});
}
/**
@@ -4394,6 +4402,11 @@ export function getEffectiveContainerForJSDocTemplateTag(node: JSDocTemplateTag)
return getHostSignatureFromJSDoc(node);
}
/** @internal */
export function getJSDocOverloadTags(node: Node): readonly JSDocOverloadTag[] {
return getAllJSDocTags(node, isJSDocOverloadTag);
}
/** @internal */
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
const host = getEffectiveJSDocHost(node);
@@ -107,14 +107,69 @@ var example3 = {
//// [jsFileAlternativeUseOfOverloadTag.d.ts]
declare namespace example1 {
function constructor(value: any, options: any): void;
/**
* @overload Example1(value)
* Creates Example1
* @param value [String]
*/
function constructor(value: any): any;
}
declare namespace example2 {
export function constructor_1(): void;
/**
* 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: '',
* });
*/
export function constructor_1(value: any, secretAccessKey: any, sessionToken: any): any;
/**
* 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: '',
* });
*/
export function constructor_1(): any;
export { constructor_1 as constructor };
}
declare namespace example3 {
function evaluate(options: any, callback: any): void;
/**
* @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
*/
function evaluate(): any;
}
/**
* function (error, result)
@@ -6,8 +6,8 @@
// trying to make sure that our changes do not result in any crashes here.
const example1 = {
>example1 : { constructor: (value: any, options: any) => void; }
>{ /** * @overload Example1(value) * Creates Example1 * @param value [String] */ constructor: function Example1(value, options) {},} : { constructor: (value: any, options: any) => void; }
>example1 : { constructor: (value: any) => any; }
>{ /** * @overload Example1(value) * Creates Example1 * @param value [String] */ constructor: function Example1(value, options) {},} : { constructor: (value: any) => any; }
/**
* @overload Example1(value)
@@ -15,17 +15,17 @@ const example1 = {
* @param value [String]
*/
constructor: function Example1(value, options) {},
>constructor : (value: any, options: any) => void
>function Example1(value, options) {} : (value: any, options: any) => void
>Example1 : (value: any, options: any) => void
>constructor : (value: any) => any
>function Example1(value, options) {} : (value: any) => any
>Example1 : (value: any) => any
>value : any
>options : any
};
const example2 = {
>example2 : { constructor: () => void; }
>{ /** * 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; }
>example2 : { constructor: { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }; }
>{ /** * 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; }; }
/**
* Example 2
@@ -46,15 +46,15 @@ const example2 = {
* });
*/
constructor: function Example2() {},
>constructor : () => void
>function Example2() {} : () => void
>Example2 : () => void
>constructor : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
>function Example2() {} : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
>Example2 : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
};
const example3 = {
>example3 : { evaluate: (options: any, callback: any) => void; }
>{ /** * @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; }
>example3 : { evaluate: () => any; }
>{ /** * @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; }
/**
* @overload evaluate(options = {}, [callback])
@@ -70,9 +70,9 @@ const example3 = {
* @see callback
*/
evaluate: function evaluate(options, callback) {},
>evaluate : (options: any, callback: any) => void
>function evaluate(options, callback) {} : (options: any, callback: any) => void
>evaluate : (options: any, callback: any) => void
>evaluate : () => any
>function evaluate(options, callback) {} : () => any
>evaluate : () => any
>options : any
>callback : any
@@ -0,0 +1,45 @@
//// [tests/cases/compiler/jsFileMethodOverloads4.ts] ////
//// [a.js]
export function Foo() { }
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {string} b
* @return {void}
*/
/**
* @param {string | number} a
* @param {string} [b]
* @return {void}
*/
Foo.prototype.bar = function (a, b) { }
//// [a.d.ts]
export function Foo(): void;
export class Foo {
/**
* @overload
* @param {string} a
* @return {void}
*/
bar(a: string): void;
/**
* @overload
* @param {number} a
* @param {string} b
* @return {void}
*/
bar(a: number, b: string): void;
}
@@ -0,0 +1,32 @@
//// [tests/cases/compiler/jsFileMethodOverloads4.ts] ////
=== /a.js ===
export function Foo() { }
>Foo : Symbol(Foo, Decl(a.js, 0, 0))
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {string} b
* @return {void}
*/
/**
* @param {string | number} a
* @param {string} [b]
* @return {void}
*/
Foo.prototype.bar = function (a, b) { }
>Foo.prototype : Symbol(Foo.bar, Decl(a.js, 0, 25))
>Foo : Symbol(Foo, Decl(a.js, 0, 0))
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>bar : Symbol(Foo.bar, Decl(a.js, 0, 25))
>a : Symbol(a, Decl(a.js, 20, 30))
>b : Symbol(b, Decl(a.js, 20, 32))
@@ -0,0 +1,35 @@
//// [tests/cases/compiler/jsFileMethodOverloads4.ts] ////
=== /a.js ===
export function Foo() { }
>Foo : typeof Foo
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {string} b
* @return {void}
*/
/**
* @param {string | number} a
* @param {string} [b]
* @return {void}
*/
Foo.prototype.bar = function (a, b) { }
>Foo.prototype.bar = function (a, b) { } : { (a: string): void; (a: number, b: string): void; }
>Foo.prototype.bar : any
>Foo.prototype : any
>Foo : typeof Foo
>prototype : any
>bar : any
>function (a, b) { } : { (a: string): void; (a: number, b: string): void; }
>a : string | number
>b : string
@@ -0,0 +1,39 @@
//// [tests/cases/compiler/jsFileMethodOverloads5.ts] ////
//// [a.js]
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {number} [b]
* @return {void}
*/
/**
* @param {string | number} a
* @param {number} [b]
*/
export const foo = function (a, b) { }
//// [a.d.ts]
/**
* @overload
* @param {string} a
* @return {void}
*/
export function foo(a: string): void;
/**
* @overload
* @param {number} a
* @param {number} [b]
* @return {void}
*/
export function foo(a: number, b?: number): void;
@@ -0,0 +1,25 @@
//// [tests/cases/compiler/jsFileMethodOverloads5.ts] ////
=== /a.js ===
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {number} [b]
* @return {void}
*/
/**
* @param {string | number} a
* @param {number} [b]
*/
export const foo = function (a, b) { }
>foo : Symbol(foo, Decl(a.js, 17, 12))
>a : Symbol(a, Decl(a.js, 17, 29))
>b : Symbol(b, Decl(a.js, 17, 31))
@@ -0,0 +1,26 @@
//// [tests/cases/compiler/jsFileMethodOverloads5.ts] ////
=== /a.js ===
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {number} [b]
* @return {void}
*/
/**
* @param {string | number} a
* @param {number} [b]
*/
export const foo = function (a, b) { }
>foo : { (a: string): void; (a: number, b?: number): void; }
>function (a, b) { } : { (a: string): void; (a: number, b?: number): void; }
>a : string | number
>b : number
@@ -0,0 +1,25 @@
// @declaration: true
// @emitDeclarationOnly: true
// @allowJs: true
// @filename: /a.js
export function Foo() { }
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {string} b
* @return {void}
*/
/**
* @param {string | number} a
* @param {string} [b]
* @return {void}
*/
Foo.prototype.bar = function (a, b) { }
@@ -0,0 +1,23 @@
// @declaration: true
// @emitDeclarationOnly: true
// @allowJs: true
// @filename: /a.js
/**
* @overload
* @param {string} a
* @return {void}
*/
/**
* @overload
* @param {number} a
* @param {number} [b]
* @return {void}
*/
/**
* @param {string | number} a
* @param {number} [b]
*/
export const foo = function (a, b) { }