13

Before giving up I wanted to give it a shot here.

I have a definition file with typings like the following:

/**
 * My json decode function, in reality very different
 * implementation (class like) but it works as an example
 */
  function decodeJSON<T = unknown>(str: string): T;

If I wanted to use this generic in TypeScript I would do something like the following

const value = decodeJSON<number[]>("[1,2,3]"); // return type will be number[]

However in my actual code I can't use TypeScript, only it's typings for development purposes, and I can't seem to find a way to tell my editor what the type I'm passing to the generic parameter is so that I get the correct type information.

I've tried to use JSDoc to specify what the parameter for the generic might be the same way that TypeScript can

// I was expecting JSDoc to understand something like this, but it doesn't
/** @type {decodeJSON<number[]>} */
const value = decodeJSON("[1,2,3]"); // Type inference will be unknown

But it doesn't work. I don't really care what the result might be in runtime, I have sanity checks for that already implemented. I just want my editor to give me type hints about the generic function result to make my job (this function has some heavy use in my app) easier

My editor is WebStorm in case it matters, but this seems to be a general limitation of JSDoc

Steven Guerrero
  • 876
  • 8
  • 17
  • Does [this help?](https://github.com/microsoft/tsdoc/issues/72) – Jared Smith Jan 12 '21 at 18:24
  • @JaredSmith It doesn't look like it does, the examples only show how `@typeparam` can be used to document the generics, not to actually instantiate them – Steven Guerrero Jan 12 '21 at 18:44
  • What do you mean when you say you want to "use" or "instantiate" a the generic type in JSDoc, exactly? – Alex Wayne Jan 12 '21 at 19:16
  • Yes, agree with @AlexWayne if that github thread is not what you're after it's unclear what you are. JSDoc doesn't actually run your code. – Jared Smith Jan 12 '21 at 19:33
  • @JaredSmith I updated my answer with a little more detail. What I need to know is if there is a way to tell JSDoc how to use a generic function, the same way you would do `myFunction` in TypeScript. And I know JSDoc doesn't run my doc, TypeScript doesn't either – Steven Guerrero Jan 12 '21 at 20:14
  • Ah ok the edit is helpful although this is still unclear. Typescript has a type-checker that checks types and does inference where they are not explicitly defined. It does not exist at runtime. If you are not using some other static type checker, the what difference does it make? There's no type checking at runtime, and you aren't using a static tool at compile-time? It's still unclear why you'd be concerned about this... what problem are you trying to solve here (e.g. intellisense, enabling use of a different static type checker that works on jsdoc comments, etc)? – Jared Smith Jan 12 '21 at 20:20
  • 1
    Your editor environment should already do what you want, I think, if it has good typescript support. Even the online typescript playground will provide a tooltip on hover of the type of generic parameters. Hover over the function usage [here](https://tsplay.dev/GNlqGm) and [you should see this](https://imgur.com/a/aupdMAM). Do you want something else to happen? – Alex Wayne Jan 12 '21 at 20:22
  • @JaredSmith Oh yeah, sorry my wording could have been a lot better. This is not a runtime thing. I just want to tell my editor what the type for the generic is in JSDoc the same way I can do it in TypeScript – Steven Guerrero Jan 12 '21 at 20:29
  • @StevenGuerrero I edited your question to reflect your intentions better. Please also edit in what text editor you're using as it may be relevant (vscode?). – Jared Smith Jan 12 '21 at 20:34
  • 1
    @AlexWayne Oh, I don't have problems with the TypeScript definition, it works just fine. The problem comes when I need to actually use the generic function. Since I'm no longer using TypeScript but plain JavaScript with definitions I can't do `decodeJSON()`, so the editor will just tell me the type of the function result is unknown – Steven Guerrero Jan 12 '21 at 20:35
  • @JaredSmith That's what I'm trying to avoid. It's not up to me (collaborative massive project) what the options for our compiler are. It can be done though – Steven Guerrero Jan 12 '21 at 20:46
  • So you want to have JSDoc somehow know the typescript type of the generic parameter when you aren't even using typescript? I don't see how that could be remotely possible. – Alex Wayne Jan 12 '21 at 20:50
  • @AlexWayne some editors can use jsdoc comments in a limited way to enhance their type inference. I don't think what the OP's after is possible though. Most editors will do static type analysis (or have plugins that will) even on plain JS code, although actually using Typescript obviously makes the editor's job much easier. – Jared Smith Jan 12 '21 at 20:52
  • 1
    @AlexWayne Well TypeScript can use JSDoc `@template` which are basically the same as generics. I don't see how this is an impossible request – Steven Guerrero Jan 12 '21 at 20:53
  • error message is `Type 'X' is not generic.ts(2315)` for the obviously expected `/** @type {decodeJSON} */` – milahu Dec 08 '22 at 18:20

6 Answers6

12

Apparently this isn't something that JSDoc can do, even though the need for it is kinda real

https://github.com/microsoft/TypeScript/issues/27387

Steven Guerrero
  • 876
  • 8
  • 17
3

You can pass generic types using Jsdoc like so:

const value = decodeJSON(/** @type {number[]} */("[1,2,3]"))
  • 4
    This only casts the type of the string into a number array (which is a wrong type for a string). It does not do what the question asks, it will not give the function its type argument. – Shayan Toqraee Jun 09 '22 at 07:32
  • "It does not do what the question asks" ... yes it does. see also https://github.com/microsoft/TypeScript/issues/27387#issuecomment-659671940 – milahu Dec 08 '22 at 19:12
3

My 2 cents if someone came here searching how to type a ts Generic <T> using JsDoc;

this ts

function identity<T>(arg: T): T {
  return arg;
}

could be achieved as:

/**
 * @template T
 * @param {T} arg
 * @return {T}
 */
function identity(arg) {
  return arg;
}

then:

identity({value: 42}).
                     ^ suggests value: number
Manu Artero
  • 9,238
  • 6
  • 58
  • 73
1

Taking into account the problem

const value = decodeJSON<number[]>("[1,2,3]"); // return type will be number[]

I found something more practical in Github for jsDoc

/** @type {ReturnType<typeof decodeJSON<number[]>>} */
const value = decodeJSON("[1,2,3]");
Junior Usca
  • 415
  • 7
  • 10
0
/** 
* @type {string} str
* @returns {number[]} // This infers the type to be returned.
*/
function decodeJSON(str) {
   // ... your logic
};
  • The OP's idea is probably to pass the return type on the function call, as he knows beforehand what the outcome of the decode function will be. Here you're just making it to be of type `number[]` for all situations. – darksoulsong May 19 '23 at 13:16
0

I found a working solution to the question, but it's quite ugly. You were actually quite close with your original solution.

Essentially you cast the generic function to a more narrow version of itself via type cast. Here's the code:

/**
 * @template {unknown} T
 * @param {string} jsonStr
 * @return {T}
 */
function decodeJson(jsonStr) {
    return JSON.parse(jsonStr)
}

// narrow the generic via a type cast
let arr = /** @type {typeof decodeJson<number[]>} */ (decodeJson)("[1, 2, 3]")
console.log(arr) // arr should be typeof "number[]" now

Here's a link to the Typescript playground.

Although the above solution works it may actually be a better idea to just cast the value after returning from the function, like so:

let arr = /** @type {number[]} */ (decodeJson("[1, 2, 3]"))

It's much more concise.

moomoolive
  • 112
  • 1
  • 4