Typescript: Support for Type Casting in JSDoc

Created on 17 Jun 2018  路  5Comments  路  Source: microsoft/TypeScript

Search Terms

jsdoc, type casting

Suggestion

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

Use Cases

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.

Examples

/**
 * @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.

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. new expression-level syntax)

Most helpful comment

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.

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

seanzer picture seanzer  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments

dlaberge picture dlaberge  路  3Comments

wmaurer picture wmaurer  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments