jsdoc, type casting
Support for indicating type casting in JSDoc comments. Currently there is no supported way to do so, at least not in the documentation for support for JSDoc listed here: JSDoc support in JavaScript Type casting with parenthesis is supported in the Closure Compiler using parenthesis around the property (scroll to the bottom of the page): https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler
I use the support for type checking with JSDoc in JavaScript files in Visual Studio Code. My code deals with DOM nodes and DOM manipulation. I often run into situations where I get an object of type Node and then need to use it's HTMLElement interface to access methods such as setAttribute, etc. Because the type is Node, I get type warnings that the property doesn't exist on type Node. Since there is no way to cast from Node to HTMLElement, the only recourse is to escape the method with braces and quotes. This means my code is littered with escaped properties--string spaghetti with no meaning.
/**
* @description Function to convert hyperscript/JSX into DOM nodes.
* @param {string | number | Object} node A node to create. This may be a hyperscript function or a JSX tag which gets converted to hyperscript during transpilation.
* @param {boolean} [isSVG] Whether the node is SVG or not.
* @returns {Node} An element created from a virtual dom object.
*/
export function createElement(node, isSVG) {
// implementation details
}
The above code is used to create an HTML element. At some point it will be accessed by another function to add attributes and properties:
/**
* @description Function to set properties and attributes on element.
* @param {Node} element The element to set props on.
* @param {string} prop The property/attribute.
* @param {string | number | boolean} value The value of the prop.
* @param {string | number | boolean} oldValue The original value of the prop.
* @param {boolean} isSVG Whether this is SVG or not
* @returns {void} undefined
*/
export function setProp(element, prop, value, oldValue, isSVG) {
//When setting attributes, need to escape to avoid type errors:
if (prop === 'dangerouslysetinnerhtml') {
element['innerHTML'] = value
} else {
element['setAttribute'](prop, value)
}
if (
value == null ||
value === 'null' ||
value === 'undefined' ||
value === 'false' ||
value === 'no' ||
value === 'off'
) {
element['removeAttribute'](prop)
}
}
What I would like to do is something like this to type cast Node to HTMLElement::
/**
* @description Function to set properties and attributes on element.
* @param {Node} element The element to set props on.
* @param {string} prop The property/attribute.
* @param {string | number | boolean} value The value of the prop.
* @param {string | number | boolean} oldValue The original value of the prop.
* @param {boolean} isSVG Whether this is SVG or not
* @returns {void} undefined
*/
export function setProp(element, prop, value, oldValue, isSVG) {
//When setting attributes, need to escape to avoid type errors:
if (prop === 'dangerouslysetinnerhtml') {
// Cast Node to HTMLElement:
/** @type {HTMLElement} (element) */
element.innerHTML = value
} else {
// Cast Node to HTMLElement:
/** @type {HTMLElement} (element) */
element.setAttribute(prop, value)
}
if (
value == null ||
value === 'null' ||
value === 'undefined' ||
value === 'false' ||
value === 'no' ||
value === 'off'
) {
// Cast Node to HTMLElement:
/** @type {HTMLElement} (element) */
element.removeAttribute(prop)
}
}
The above is just one example of the problems I face daily due to lack of type casting with JSDoc when using Visual Studio Code with "javascript.implicitProjectConfig.checkJs": true setting in my preferences.
My suggestion meets these guidelines:
You can already do it:
/**
* @description Function to set properties and attributes on element.
* @param {Node} element The element to set props on.
* @param {string} prop The property/attribute.
* @param {string | number | boolean} value The value of the prop.
* @param {string | number | boolean} oldValue The original value of the prop.
* @param {boolean} isSVG Whether this is SVG or not
* @returns {void} undefined
*/
export function setProp(element, prop, value, oldValue, isSVG) {
//When setting attributes, need to escape to avoid type errors:
if (prop === 'dangerouslysetinnerhtml') {
// Cast Node to HTMLElement:
(/** @type {HTMLElement} */ (element)).innerHTML = "" + value;
} else {
// Cast Node to HTMLElement:
(/** @type {HTMLElement} */ (element)).setAttribute(prop, "" + value)
}
if (
value == null ||
value === 'null' ||
value === 'undefined' ||
value === 'false' ||
value === 'no' ||
value === 'off'
) {
// Cast Node to HTMLElement:
(/** @type {HTMLElement} */ (element)).removeAttribute(prop)
}
}
Note: the two "" + value are because only string is valid.
Wow! That works perfectly. It's a slightly different syntax than Closure Compile, but I don't care. Is this documented somewhere? Also, since when has this been supported?
JSDoc support in JavaScript. For cast documentation see just before Patterns that are known NOT to be supported section.
Seems is supported since version 2.5.
Sheesh! I've looked at that document a million times. I have tried the casting technique mentioned there before but it never worked for me. Now I know why. I use PrettierJS and it strips the parens from the element cast because it sees them as unnecessary, causing the casting to fail. Guess I need to go over and log an issue with PrettierJS.
By the way, I just noticed that this also allows you to handle expando properties:
/** @type {Object} element.vnode */ (element).vnode = node
Unfortunately, you can't be more explicit with the type. This only works if you cast the expando property to *, Object or any, which are all the same for TypeScript. At least you don't have to use braces and quotes around the property. The lesser of two evils.
Most helpful comment
You can already do it:
Note: the two
"" + valueare because only string is valid.