Typescript: JSDoc generic return type is treated as any

Created on 9 Jul 2020  路  2Comments  路  Source: microsoft/TypeScript

I have a JavaScript file using JSDoc to indicate the return type of a function. The function's return type is a kind of generic (DataContainer<NumberData>) but when I call the method from a TypeScript file the TypeScript compiler treats it as any.


TypeScript Version: 3.9.6


JSDoc, generic, template, any, not working

Code

// data.ts

export interface DataContainer<T> {
  data(): T
}

export interface NumberData {
  value(): number
}
// factory.js

/**
 * @typedef { import('./data').DataContainer } DataContainer
 * @typedef { import('./data').NumberData } NumberData
 */

/**
 * @function
 * @returns { DataContainer<NumberData> }
 */
export default function getDataContainer() {
  const numberData = {
    value: () => 42,
  }
  const dataContainer = {
    data: () => numberData,
  }
  return dataContainer
}
// index.ts

import getDataContainer from './factory'

const container = getDataContainer() // tsc thinks this returns an any but it should be DataContainer<NumberData>
console.log(container.data().value())
console.log(container.someBogusMethod()) // XXX should be tsc compiler error here but container is treated as any so we get a runtime exception instead

Expected behavior:
When compiling this code tsc --allowJs src/* --outDir build/ we expect the TypeScript compiler to give an error at container.someBogusMethod() since DataContainer<NumberData> has no method named someBogusMethod.

Actual behavior:
The build works because it is incorrectly treating container as an any.
This results in a runtime exception when we run it with node build/index.js

Playground Link:

Related Issues:
https://github.com/microsoft/TypeScript/issues/26883

Most helpful comment

Ah! Thank you. Like you suggested I needed to import DataContainer with the template <T>. I also needed to break out the NumberData typedef into it's own JSDoc comment which I didn't realize. This works:

/**
 * @template T
 * @typedef { import('./data').DataContainer<T> } DataContainer
 */

/**
 * @typedef { import('./data').NumberData } NumberData
 */

/**
 * @function
 * @returns { DataContainer<NumberData> }
 */
export default function getDataContainer() {
  const numberData = {
    value: () => 42,
  }
  const dataContainer = {
    data: () => numberData,
  }
  return dataContainer
}

All 2 comments

Try

/**
 * @template T
 * @typedef {import('./data').DataContainer<T>} DataContainer
 */

Ah! Thank you. Like you suggested I needed to import DataContainer with the template <T>. I also needed to break out the NumberData typedef into it's own JSDoc comment which I didn't realize. This works:

/**
 * @template T
 * @typedef { import('./data').DataContainer<T> } DataContainer
 */

/**
 * @typedef { import('./data').NumberData } NumberData
 */

/**
 * @function
 * @returns { DataContainer<NumberData> }
 */
export default function getDataContainer() {
  const numberData = {
    value: () => 42,
  }
  const dataContainer = {
    data: () => numberData,
  }
  return dataContainer
}
Was this page helpful?
0 / 5 - 0 ratings