It's near the end of 2022, and I believe this question needs an updated answer. I cannot confirm this solution for code editors oher than VS Code
, but if they support JSDoc@3.x
, they should be fine.
The solution uses @callback
definitions, and TypeScript's import(...)
statements (which, AFAIK, is only supported in JSDoc@3
, thus, the version requirement).
Also, JSDoc@3
apparently does not interpret @type
declarations as the return
type, instead, it applies the declaration to the entire statement that follows - thus a function will inherit the @callback
declaration for itself.
Solution
Define function signature
Create a @callback
type specifying everything you need. In the example below, I am writing a graphql-yoga
resolver function definition.
/** @typedef {import("graphql").GraphQLResolveInfo} GraphQLResolveInfo */
/** @typedef {import("@graphql-yoga/node").YogaInitialContext} YogaInitialContext */
/**
* @callback GQLResolver
* @param {TObj} obj
* @param {TArgs} args
* @param {YogaInitialContext} context
* @param {GraphQLResolveInfo} info
* @template TObj
* @template TArgs
* @template TReturn
* @returns {Promise<TReturn>}
*/
Reuse - in the same file
To reuse the type, simply apply it with a @type
declaration:
/** @type {GQLResolver< {}, { id: string }, boolean >} */
function someResolver(obj, args, context, info){}
// (obj: {}, args: { id: string }, context: YogaInitialContext, info: GraphQLResolveInfo) => Promise<boolean>
// You can also override the signature with additional `@param` and `@return` declarations
/**
* @type {GQLResolver< {}, { id: string }, boolean >}
* @param {{ otherId: number }} args
* @param {{ something: string }} info
* @return {Promise<{ success: boolean, errors: Array<string>}>}
*/
function someOtherResolver(obj, args, context, info) {}
// someOtherResolver = (
// obj: {},
// args: { otherId: number },
// context: YogaInitialContext,
// info: { something: string }
// ) => Promise<{ success: boolean, errors: Array<string> }>
Reuse types from an external files without @template
parameters
Assuming that the @callback GQLResolver
definition is in an external file types.js
, make sure that the file exports at least an empty object - otherwise, it will not work. I don't know the exact reason, but it might be that JS/TS bails out when no symbols are exported.
// ./src/types.js
/**
* @callback GQLResolver
* ...
*/
export {} // <-- at least this
Then, use a TS-like import(...).TypeName
declaration:
/** @typedef {import("types.js").GQLResolver} GQLResolver */
/** @type {GQLResolver} **/
function example(){}
NOTE:
If you have a JS file dedicated to typedefs like the above, please note that you do not need to import it as JS source in your project! The typedef {import(...)}
works standalone, as long as the specified import exports any symobls.
Reuse from an external file with @template
parameters
Importing types from external files, that make use of @template
parameters, is possible, but it will not work as expected:
/** @typedef { import("types.js").GQLResolver } GQLResolver */
^^^^^^^^^^^ ^^^^^^^^^^^
<GQLResolver> <any>
The import
misses the @template
declarations, and considers the type invalid - thus, assigns type any
.
The workaround is to specify template parameters near the imported symbol name:
/**
* @typedef {import("types.js").GQLResolver<
* any,
* { id: string }
* boolean
* }>} SomeSpecificGQLResolver
*/
// SomeSpecificGQLResolver = (obj: {}, args: { id: string }, context: YogaInitialContext, info: GraphQLResolveInfo) => Promise<boolean>
/** @type {SomeSpecificGQLResolver} */
function someResolver(obj, args, context, info){}
// someResolver = same as SomeSpecificGQLResolver