The idea here is to generalize the following pattern:
const element = document.getElementById('foo')
if (!element || !(element instanceof HTMLInputElement)) throw new Error('Failed to locate HTML element with id foo and type HTMLInputElement!')
// now we're safe, element is not null and it has the expected type
In TypeScript I can express it in a generic function:
type Class<T> = new (...args: any[]) => T
function getElement<T extends HTMLElement>(id: string, type: Class<T>): T {
const element = document.getElementById(id)
if (!element || !(element instanceof type)) throw new Error(`Failed to locate HTML element with id ${id} and type ${type}!`)
return element
}
However, I'm simply unable to do the same thing in pure JavaScript with JSDoc, as I don't know how to express the generic Class
type.
I tried the following which does not work, because the Class
does not match the code I wrote in TypeScript:
/**
* @template T
* @typedef {object} Class
* @property {(...args:any[]) => T} new Constructor
* @returns {T}
*/
/**
* Retrieves an element by its id.
* @template {new(...args: any[]) => HTMLElement} T
* @param {string} id The element's identifier.
* @param {Class<T>} type The type of HTML element.
* @returns {T} The HTML element.
*/
function getElement(id, type) {
const element = document.getElementById(id);
if (!element || !(element instanceof type))
throw new Error(`Failed to locate HTML element with id ${id} and type ${type}!`);
return element;
}
With the above code, the IDE shows the following errors:
element instanceof type
→ "The right-hand side of an 'instanceof' expression must be either of type 'any', a class, function, or other type assignable to the 'Function' interface type, or an object type with a 'Symbol.hasInstance' method.ts(2359)"return element
→ "Type 'HTMLElement' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'HTMLElement'.ts(2322)"So is it even possible to express this with JSDoc?
A colleague helped me out. Posting the answer here in case it can help someone else...
/**
* @template T
* @typedef {new (...args: any[]) => T} Class<T>
*/
/**
* Retrieves an element by its ID.
* @template {HTMLElement} T
* @param {string} id - ID of the element.
* @param {Class<T>} type - Type of the element.
* @returns {T} The element bearing the given ID.
*/
function getElement(id, type) {
const element = document.getElementById(id);
if (!element || !(element instanceof type))
throw new Error(`Failed to locate HTML element with id ${id} and type ${type}!`);
return element;
}