Repository navigation
Allow to explicitly pass type parameters via JSDoc #27387
Copy link
Copy link
Open
Labels
Domain: JSDocRelates to JSDoc parsing and type generationRelates to JSDoc parsing and type generationDomain: JavaScriptThe issue relates to JavaScript specificallyThe issue relates to JavaScript specificallyIn DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScriptcheckJsRelates to checking JavaScript using TypeScriptRelates to checking JavaScript using TypeScript
Description
Search Terms
jsdoc generics type parameters constraints
Suggestion
It seems like it is not possible via JSDoc to explicitly tell compiler which type parameters to pass to a generic function.
Use Cases
In TypeScript it is possible to explicitly pass type parameters such as mongoose.model<Model, Schema>('modelName', Schema) while I could not find a way to do same with JSDoc.
Examples
mongoose.model has two signatures, first one with one type parameter and the other with two. To make use of the second signature we must pass types explicitly.
export function model<T extends Document>(
name: string,
schema?: Schema,
collection?: string,
skipInit?: boolean
): Model<T>;
export function model<T extends Document, U extends Model<T>>(
name: string,
schema?: Schema,
collection?: string,
skipInit?: boolean
): U;// Choose second signature in typescript.
const model = mongoose.model<Model, Schema>('modelName', Schema);
// With JSDoc, compiler always chooses first signature and we receive a type mismatch error.
/** @type {Schema & mongoose.Model<Model>} */
const model = mongoose.model('modelName', Schema);
// But something like this would be great.
const model = mongoose.model/**<Model, Schema>*/('modelName', Schema);My apologies if this is already possible, but I've spend almost a week battling this.
Related: https://github.com/Microsoft/TypeScript-Node-Starter/issues/101
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript / JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. new expression-level syntax)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Domain: JSDocRelates to JSDoc parsing and type generationRelates to JSDoc parsing and type generationDomain: JavaScriptThe issue relates to JavaScript specificallyThe issue relates to JavaScript specificallyIn DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScriptcheckJsRelates to checking JavaScript using TypeScriptRelates to checking JavaScript using TypeScript
Activity
Stuk commentedon Mar 12, 2019
I would also like to see something like this. Currently it's really hard to use functions with generic types from JS.
ekulabuhov commentedon Nov 7, 2019
Another use case with React Hooks:
Removing default value there is no way to pass type info:
pm0u commentedon May 1, 2020
Anything on this? Also stuck on the useState example as shared above:
Edit: Ok this might be a hack, but in the case of the useState example it works:
implied types for error are
string | nulland setError isReact.Dispatch<React.SetStateAction<?string>>i think what's happening "under the hood" is we are forcing the "type" that useState is being passed.
robertknight commentedon Jul 16, 2020
For functions that take an argument of the generic type, casting the argument itself is reasonably practical. We've settled on these patterns with
useStateanduseRefin React for example:In cases where there is no argument for everything else to be inferred from, it gets much more verbose unfortunately. What would be helpful is being able to do something like:
Where the
?is inferred asSet. In this example it only saves a few characters, but it could be a lot more if the generic type name is lengthy and/or has to be imported from somewhere.17 remaining items
noinkling commentedon Oct 18, 2023
Pascal Schilp (@thepassle) Technically this is already solved as per #27387 (comment) -
not really sure why this issue hasn't been closed yet.For example you can now do:
If you're aware of this but your argument is that you would prefer for there to be a more ergonomic syntax, probably best to say that explicitly.
thepassle commentedon Oct 18, 2023
Those indeed seem more like workarounds, so I understand the issue is still open.
So yes, I would prefer for there to be a more ergonomic syntax.
mhofman commentedon Oct 18, 2023
Malcolm (@noinkling), those are not equivalent. A generic method would become unbound from its target if aliased or type annotated that way.
noinkling commentedon Oct 19, 2023
You mean methods won't have the right
thisvalue/type, essentially? I can see the issue with the alias approach in those cases, but nothing new there. Can you give an example where the casting syntax could be problematic/have a different result compared to TS syntax?In my example both references to the method (runtime and in the annotation) are accessed directly from the object using dot notation (
mongoose.model) so I don't see an issue, but maybe I'm missing something.If you're concerned about the parentheses they don't change anything:
gustavopch commentedon Oct 19, 2023
is equivalent to:
not to:
The fact that this ugly but useful workaround exists doesn't mean a proper syntax for actually passing type parameters (instead of casting the whole function) shouldn't be pursued.
noinkling commentedon Oct 19, 2023
Gustavo (@gustavopch) It didn't exist when this issue was created was the point, it was impossible to provide type params unless they corresponded to runtime params. In that sense the person who created the issue might consider it "solved" now. Personally I've learned to take what I can get when it comes to JSDoc, given that it's something of a second-class citizen for TS. But you're right, it would be nice to have a nicer syntax, just wanted to make it clear that there's an option that works now (even if it isn't pretty).
phaux commentedon Feb 4, 2024
I return to this thread to point out that the "solution" with instantiation expressions doesn't seem to work when the function has multiple overloads:
It seems to check all the overloads at once instead of choosing only one:
In TypeScript it works:
noinkling commentedon Feb 4, 2024
Niki (@phaux) You're right, worth noting it could potentially be considered a bug: #51694
apendua commentedon Aug 4, 2024
Nathan Shively-Sanders (@sandersn) I am looking into implementing something like:
Do you think there's a chance this approach will be accepted?
sandersn commentedon Aug 7, 2024
Our approach to JSDoc has evolved since this issue was created. At that time I didn't want to add anything that didn't have precedent in the jsdoc documentation generator, or at least in Closure compiler. However, Typescript is the largest processor of JSDoc at this point, and TS users who want typing are probably the most prolific authors of it. So we've decided on a few new principles:
@importis a good example, as is your own@overloadwork.a. Starts with
@alphanumeric.b. Isn't nested -- starts at
@and continues till the next@.c. Is used in a
/**comment directly above a declaration. Both@importand@overloadviolate this, for good reasons.(1) means that TS users will be able to use the new JS syntax with a minimum of surprise. (2) means that jsdoc processors (including TS itself!) will be able to understand the new tag with a minimum of new parsing.
Re (2c), the rough descending order of goodness, or familiarity, in my opinion is:
/**comment directly preceding a declaration -- like@type./**comment not connected to any JS syntax (which is to say, preceding some non-declaration) -- like@import./**comment preceding an expression -- like@typeas a cast./**comment preceding another/**comment that eventually precedes a declaration -- like@overload./**comment preceding some other, non-top-level node in the parse tree./*::comment or some other new syntax.// @ts-alphanumericcomment preceding a declaration.I think we can come up with something for (3).
This is a very long way to say that, yes, we'd consider a tag for this that looks a lot like your
@specialise. You're probably going to need parentheses around the call expression to make it attach correctly, though.Also I'm not sure whether
<Model, Schema>would be better than{Model, Schema}. I think it's down to how hard it is to convert all the type-parameter syntax to that form. The grammar isn't huge there so probably pretty easy.Daniel Rosenwasser (@DanielRosenwasser) you've thought about jsdoc a lot too and you may have interesting insights from working on the types-in-JS proposal, which ran into exactly this problem.
apendua commentedon Aug 17, 2024
I've got a PR ready for the following syntax:
So basically one can put the
@specializetag at any parent node that accepts JSDoc, e.g. assignment statement or parethesized expression. We can also choose between wrapping type arguments with<...>or{...}.This will also work for JSX, though the element with type arguments will need to put inside curly braces so that we can also add JSDoc there:
DanielRosenwasser commentedon Aug 19, 2024
It's something we'll have to consider. It's not perfect, but the solution right now is to cast to a value that's already specialized:
https://www.typescriptlang.org/play/?filetype=js#code/PQKhFgCgAIWgBALgUwLYAcA2BDF0AqUsC62ATtqtAB7QDe+AvkSMFAGYCuAdgMaIBLAPbdo7ABTUAlPSLR5ZZIk5lR1ANxRmkKJiXQhAIwBW0ALyyY86KAhXrcJGiy5kBOQ5LlKNekw-yrAES0pbW4dCKyqo0mvbQ2oxxuvoA5uY2YAiIAJ7obnS5+ULsYgA8AM6IZALcqQB8jLDA0OLsUnHALdAAegD8UCmIBiYA4hnittl5BUXIJSPGAHTsldW1DU2srUbL7VJLhrUAJuK7HVBd8v1AA
Though noted above in #27387 (comment), it doesn't work in the case of overloads.
apendua commentedon Aug 22, 2024
Daniel Rosenwasser (@DanielRosenwasser)
Good point! Btw, the reason this currently does not work:
is already pointed out in the other issue, here:
#51694
Do you think there's a chance the other PR will be merged? i.e. #51695
tif-calin commentedon Oct 14, 2024
I'm a big TypeScript fan that's trying out a new project where I use JSDocs instead of TS. Here are some of the learnings I've been forced to do along the way.
Passing in a generic slot to a function
The key here is utilizing
ReturnType.TypeScript version:
JSDocs version:
Warning
As pointed out by Niki (@phaux) above, this often fails when there are overloads.
React useState
This was already mentioned above, but I think it's useful to rewrite. Typecasting the initial value has never failed me. Make sure to remeber the parentheses
Note
I would prefer to have instead written on the line before the useState statement
/** @type {ReturnType<typeof useState<null | string>>} */but this also fails due to useState's overloads withundefinedTweaks to work better in practice with `freeway`