jsdoc overloads
You can specify overloads in Typescript by providing multiple signatures. You should be able to do the same thing in Javascript. The simplest way I can think of is to allow a type annotation on a function declaration.
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
/** @type {Gioconda} */
function monaLisa(sb) {
// neither overloads' return types are assignable to 1 | 2, so two errors should be logged
return typeof sb === 'string' ? 1 : 2;
}
/** @type {2 | 3} - call should resolve to (b: boolean) => 2 | 3, and not error */
var twothree = monaLisa(false);
// js special property assignment should still be allowed
monaLisa.overdrive = true;
Note that checking parameters against the implementation parameters will be trivial since there's no obvious type but any
for the implementation parameters. Checking of return types against the implementation return type will have to use the return type of the function body as the implementation return type. That check will also likely be trivial since function-body return types usually depend on parameters.
How would one provide comments and parameter descriptions (/** @param */
) when doing this?
Sometimes it is necessary to highlight the differences between overloads. In .d.ts files this works by annotating each single overload with its own comment. See also https://github.com/Microsoft/TypeScript/issues/407
One way I could think of is
/**
* Does something
* @callback foo_overload1
* @param {number} arg1 Something
*/
/**
* Does something else
* @callback foo_overload2
* @param {string} arg1 Something else
* @param {number} arg2 Something
*/
/** @type {foo_overload1 | foo_overload2} */
function foo(arg1, arg2) {
}
but this way, we get an error on the type annotation that the signatures don't match.
Too bad this seems pretty low on your priorities :( I just ran into an issue where it is impossible to document a method signature in JavaScript due to this. I have a function where all arguments except the last one are optional:
// valid overloads:
declare function test(arg1: string, arg2: number, arg3: () => void): void;
declare function test(arg2: number, arg3: () => void): void;
declare function test(arg1: string, arg3: () => void): void;
declare function test(arg3: () => void): void;
I'd be really glad if documenting this via JSDoc would be possible in the near future.
Here's a type that is too permissive, but it's pretty close:
declare function test(arg123: string | number | () => void, arg23?: number | () => void, arg3?: () => void): void
In order to express dependencies between types without overloads, you need conditional types:
declare function test<T extends string | number | () => void>(
arg123: T,
arg23?: T extends string ? number :
T extends number ? () => void :
never,
arg3?: T extends string ? () => void : never): void
But conditional type syntax doesn't work in JS so you'd have to write a series of type aliases in a .d.ts and reference them.
@sandersn Thanks for the explanation, but it does not quite achieve what I'm trying to do.
I have a JS CommonJS module which contains said function with a couple of overloads similar to the ones I mentioned above. Inside the module, this function is heavily used so I want to give it proper type checking and document the parameters. The function is not exported.
The project maintainer does not use TypeScript, but lets me work with JSDoc comments to get type checking support. Asking him to move every overloaded function into separate modules and add .d.ts files for them is too much to ask.
When annotating the function with @type {(arg1: union | of | types, ...) => void}
, the type checker fails to understand the type of variables inside the function and unnecessarily complains about seemingly disallowed assignments. Also, I cannot provide additional explanation to the single parameters like I would do in an overloaded declaration + JSDoc @param
comments.
The external .d.ts file does not work, even if I hack something like this:
// file1.d.ts
export declare function test(arg1: string, arg2: number, arg3: () => void): void;
export declare function test(arg2: number, arg3: () => void): void;
export declare function test(arg1: string, arg3: () => void): void;
export declare function test(arg3: () => void): void;
// ^ the function is NOT exported
// file1.js
/** @type {typeof import("./file1").test} */
// ^ ERROR: The type of a function declaration must match the function's signature. [8030]
function test(arg1, arg2, arg3) {
return;
}
I could live with the signature being to permissive, but in this situation, I'm unable to document the parameter types without "breaking" the implementation.
Another suggestion from the VS user voice: https://developercommunity.visualstudio.com/content/idea/425961/support-3.html
I can't believe this does not get more feedback. This is a pain in the a** to work with in legacy codebases with overloaded callback-style APIs.
Function overloading can be done using:
/** @type {((name: string) => Buffer) & ((name: string, encoding: string) => string))} */
const readFile = (name, encoding = null) => { … }
I discovered this purely by accident.
@ExE-Boss, your solution doesn't work for me. It says Parameter 'name' implicitly has an 'any' type.ts(7006)
and Parameter 'encoding' implicitly has an 'any' type.ts(7006)
That’s how overloads work, the implementation takes any
for each parameter.
You need to do:
/** @type {((name: string) => Buffer) & ((name: string, encoding: string) => string))} */
const readFile = (/** @type {string} */ name, /** @type {string | null} */ encoding = null) => { … };
To set the types for the actual implementation.
It’d be nice if TypeScript was able to infer the argument types from the function intersection type as unions.
This however becomes a lot harder with overloads that take much more different sets of arguments:
/**
* @callback TestCallback
* @return {void | PromiseLike<void>}
*
* @typedef {object} TestOptions
* @property {number} [timeout]
*/
/**
* @type {((cb: TestCallback) => void) & ((description: string, cb: TestCallback) => void) & ((options: TestOptions, cb: TestCallback) => void) & ((description: string, options: TestOptions, cb: TestCallback) => void)}
*/
const test = (arg0, arg1, arg2) => { … };
or
/** @type {((num: number, obj: {stuff?: unknown}) => bigint) & ((str1: string, str2: string) => object)} */
const doStuff = (arg0, arg1) => { … };
Just as an alternative for syntax, what about accepting multiple @type
? This would be fairly similar to how it looks in TypeScript.
/**
* @type {(x: string) => number}
* @type {(x: number) => string}
* @param {number | string} x
*/
function flipType(x) {
if (typeof x === 'string') {
return Number(x);
} else {
return String(x);
}
}
What about situations where I have a different number of arguments. For example:
/**
* @param {Object} row - The row
* @param {String} field - Field name
* @param {*} value - Field value
*/
foo( row, field, value )
and
/**
* @param {Object} row - The row
* @param {Object} fieldValues - Key value pair of fields to values
*/
foo( row, fieldValues )
The recommended approach appears to be:
/** @type {((row: Object, field: String, value: any) => Object) & ((row: Object, fieldValues: Object) => Object)} */
But where are all my lovely argument descriptions going to reside? How do I communicate that the third parameter is only valid if the second parameter is a string?
I have been using the following syntax(es) for overloading the event handling for a class which inherits "EventEmitter"
class Diamond extends EventEmitter {
constructor() {
/ TYPE DEFINITION OF DiamondEvent HERE
/** @type {DiamondEvent} */
this.on;
}
};
The following all appear to work equally well. Its up to you as to which is more readable.
/**
* @typedef {(event: 'error', listener: (error: Error) => void) => this} EventError
* @typedef {(event: 'response_required', listener: (token: string) => void) => this} EventResponseRequired
* @typedef {EventError & EventResponseRequired} DiamondEvent
*/
/**
* @callback EventError
* @param {'error'} event
* @param {(error: Error)=>void} listener
* @return this
* @callback EventResponseRequired
* @param {'response_required'} event
* @param {(token: string)=>void} listener
* @return this
* @typedef {EventError & EventResponseRequired} DiamondEvent
*/
/**
* @typedef {function('error',function(Error):void):this} EventError
* @typedef {function('response_required',function(string):void):this} EventResponseRequired
* @typedef {EventError & EventResponseRequired} DiamondEvent
*/
/**
* @typedef {((event: 'error', listener: (err: Error) => void) => this) & ((event: 'response_required', listener: (token: string) => void) => this)} DiamondEvent
*/
/**
* @typedef {(function('error',function(Error):void):this) & (function('response_required',function(string):void):this)} DiamondEvent
*/
When I press the '(' after typing on
, the autocomplete popup looks like this
let dm = new Diamond()
on(event: "error", listener: (error: Error) => void): Diamond
on(event: "response_required", listener: (token: string) => void): Diamond
dm.on(|
and my editor correctly identifies the parameter types based on the the event name in the first parameter.
I hope this helps anyone trying to clean deal with functions which have multiple prototypes. Until I figured this out, I needed to write a '.d.ts' file to describe the functions. This is so much cleaner.
So the key piece is basically the typedef with an intersection type
@typedef {EventError & EventResponseRequired} DiamondEvent
You can also do:
/** @typedef {{
(event: 'error', listener: (error: Error) => void): this;
(event: 'response_required', listener: (token: string) => void): this;
}} DiamondEvent */
30940 points out that Jsdoc has an established format for representing overloads.
@sandersn,
This format is used by the xlsx-populate project (example). I was looking at adding type definitions to that project, but this issue prevents that from being possible (there are 77 instances of *//**
, and I wanted to avoid making too many changes to the JSDoc comments).
Is this issue likely to see any progress in the near future? It would really help with adding type definitions to JavaScript projects.
It sounds like https://github.com/microsoft/TypeScript/issues/25590#issuecomment-561493756 suggests that Closure supports intersection types (A & B
) but it's a syntax error when I use tsd-jsdoc
.
I tried the run-together block comment syntax and it works. Per https://github.com/englercj/tsd-jsdoc/issues/30 you must tag each block with a different @variation
but once you do that, the generated typings are correct. This is a strong argument in favor of Typescript natively supporting the same construct, IMHO.
30940 points out that Jsdoc has an established format for representing overloads.
Every info I find in this topic suggests using the *//**
syntax to document function overloads. Something like that:
/**
* Do something with numbers.
* @param {number} an - First number parameter.
* @param {number} bn - Second number parameter.
* @returns {number} Result as number.
*//**
* Do something with strings.
* @param {string} as - First string parameter.
* @param {string} bs - Second string parameter.
* @param {string} cs - Third string parameter.
* @returns {string} Result as string.
*/
function something(a, b, c) {
...
}
Hope this gets implemented soon, as overloading is a basic and widely used technique.
Unfortunately none of the solutions above work with js ES6 class functions. ESlint and VSCode can't seem to process a @type
on an es6 function.
no beuno:
class Person {
/**
* @callback MultSq1
* @param {Number} x
* @returns {Number}
*/
/**
* @callback MultSq2
* @param {Number} x
* @param {Number} y
* @returns {Number}
*/
/**
* @type {MultSq1 & MultSq2} <--ESLint error, VSCode shows the attribute but doesn't show overloads.
*/
multSq(x, y) {
return x * (y || x);
}
}
if you use a traditional function
or arrow function with a variable - it will show an overload.
The only trick I found around this is to set the type from a const function outside the class then set a property in the constructor to that const function... unfortunately it shows a blue property icon in intellisense instead of the pink function icon. Not sure what to do about that yet.
@chriseaton
Well, ESLint’s built‑in JSDoc validation is broken, and eslint‑plugin‑jsdoc
has incomplete support for TypeScript types: https://github.com/jsdoctypeparser/jsdoctypeparser/issues/50.
Also, your code has a bug when y === 0
.
The implementation should instead be using the nullish‑coalescing (??
)Â operator:
multSq(x, y) {
return x * (y ?? x);
}
@ExE-Boss I was pointing out that while the above solutions have workarounds for function vars, they don't work for ES6 class methods. The title of this issue again: "In JS, jsdoc..." as far as I know ES6 classes and methods are finished proposals and officially part of JS.
Also, it's not a bug if you expect multSq to square when y is 0 - it's all about use-case if we wanna dive into the logic of my quick and dirty sample :expressionless: which... why bother?
30940 points out that Jsdoc has an established format for representing overloads.
Every info I find in this topic suggests using the
*//**
syntax to document function overloads. Something like that:/** * Do something with numbers. * @param {number} an - First number parameter. * @param {number} bn - Second number parameter. * @returns {number} Result as number. *//** * Do something with strings. * @param {string} as - First string parameter. * @param {string} bs - Second string parameter. * @param {string} cs - Third string parameter. * @returns {string} Result as string. */ function something(a, b, c) { ... }
Hope this gets implemented soon, as overloading is a basic and widely used technique.
This would be the most important syntax to support. It's a clean and readable way to define overloads and it is the correct Jsdoc syntax for it.
Most helpful comment
30940 points out that Jsdoc has an established format for representing overloads.