Skip to content

fix(53181): Overloads Are Not Generated as Expected When Using JsDocOverload Tag to Declare Overloads of Functions Assigned to a Prototype #53317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 9 additions & 23 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ import {
getJSDocDeprecatedTag,
getJSDocEnumTag,
getJSDocHost,
getJSDocOverloadTags,
getJSDocParameterTags,
getJSDocRoot,
getJSDocSatisfiesExpressionType,
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -40507,15 +40501,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;
}
}
}
Expand Down
29 changes: 21 additions & 8 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ import {
FunctionExpression,
FunctionLikeDeclaration,
GetAccessorDeclaration,
getAllJSDocTags,
getBaseFileName,
GetCanonicalFileName,
getCombinedModifierFlags,
Expand Down Expand Up @@ -350,9 +351,11 @@ import {
isWhiteSpaceLike,
isWhiteSpaceSingleLine,
JSDoc,
JSDocArray,
JSDocCallbackTag,
JSDocEnumTag,
JSDocMemberName,
JSDocOverloadTag,
JSDocParameterTag,
JSDocPropertyLikeTag,
JSDocSatisfiesExpression,
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
});
}

/**
Expand Down Expand Up @@ -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);
Expand Down
61 changes: 58 additions & 3 deletions tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 15 additions & 15 deletions tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@
// 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)
* Creates 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
Expand All @@ -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])
Expand All @@ -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

Expand Down
45 changes: 45 additions & 0 deletions tests/baselines/reference/jsFileMethodOverloads4.js
Original file line number Diff line number Diff line change
@@ -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;
}
32 changes: 32 additions & 0 deletions tests/baselines/reference/jsFileMethodOverloads4.symbols
Original file line number Diff line number Diff line change
@@ -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))

35 changes: 35 additions & 0 deletions tests/baselines/reference/jsFileMethodOverloads4.types
Original file line number Diff line number Diff line change
@@ -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

Loading