diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ee8fe6572848..e6c59aa622d56 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; } } @@ -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; } } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 64c9b4ee3d009..fe1284c6dfa7a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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(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); diff --git a/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js b/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js index 3f21b11ae03b0..034426c675247 100644 --- a/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js +++ b/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js @@ -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) diff --git a/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types b/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types index e9ce9176e54c0..0cd3d0a290159 100644 --- a/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types +++ b/tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types @@ -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 diff --git a/tests/baselines/reference/jsFileMethodOverloads4.js b/tests/baselines/reference/jsFileMethodOverloads4.js new file mode 100644 index 0000000000000..dc4d3ea9fd5a9 --- /dev/null +++ b/tests/baselines/reference/jsFileMethodOverloads4.js @@ -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; +} diff --git a/tests/baselines/reference/jsFileMethodOverloads4.symbols b/tests/baselines/reference/jsFileMethodOverloads4.symbols new file mode 100644 index 0000000000000..1fca0a1d3b036 --- /dev/null +++ b/tests/baselines/reference/jsFileMethodOverloads4.symbols @@ -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)) + diff --git a/tests/baselines/reference/jsFileMethodOverloads4.types b/tests/baselines/reference/jsFileMethodOverloads4.types new file mode 100644 index 0000000000000..0939e1427e3ce --- /dev/null +++ b/tests/baselines/reference/jsFileMethodOverloads4.types @@ -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 + diff --git a/tests/baselines/reference/jsFileMethodOverloads5.js b/tests/baselines/reference/jsFileMethodOverloads5.js new file mode 100644 index 0000000000000..6895623c95e18 --- /dev/null +++ b/tests/baselines/reference/jsFileMethodOverloads5.js @@ -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; diff --git a/tests/baselines/reference/jsFileMethodOverloads5.symbols b/tests/baselines/reference/jsFileMethodOverloads5.symbols new file mode 100644 index 0000000000000..392107c3d1a0d --- /dev/null +++ b/tests/baselines/reference/jsFileMethodOverloads5.symbols @@ -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)) + diff --git a/tests/baselines/reference/jsFileMethodOverloads5.types b/tests/baselines/reference/jsFileMethodOverloads5.types new file mode 100644 index 0000000000000..45264dbb070c6 --- /dev/null +++ b/tests/baselines/reference/jsFileMethodOverloads5.types @@ -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 + diff --git a/tests/cases/compiler/jsFileMethodOverloads4.ts b/tests/cases/compiler/jsFileMethodOverloads4.ts new file mode 100644 index 0000000000000..d26d219baa140 --- /dev/null +++ b/tests/cases/compiler/jsFileMethodOverloads4.ts @@ -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) { } diff --git a/tests/cases/compiler/jsFileMethodOverloads5.ts b/tests/cases/compiler/jsFileMethodOverloads5.ts new file mode 100644 index 0000000000000..650437b3a02b4 --- /dev/null +++ b/tests/cases/compiler/jsFileMethodOverloads5.ts @@ -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) { }