Reputation: 938
I'm connecting to multiple email tools and abstracting their APIs to one common sendEmail
function with the same params
and same returns
for each service. That means that for every email service (Mailchimp, SendGrid...), I have to write a function which has an identical JSDoc describing the same @params
and same @returns
...
Is there a valid JSDoc syntax to use @typedef
or similar with a Function, where instead of declaring @params
and @returns
above each function, just describe the type?
...Bonus points for not disabling ESLint's require-jsdoc
Upvotes: 7
Views: 3678
Reputation: 1009
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 [email protected]
, 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.
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>}
*/
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> }>
@template
parametersAssuming 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.
@template
parametersImporting 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
Upvotes: 2
Reputation: 2189
There is a way to define it. Use @callback which is the equivalent of @typedef
for functions.
/**
* @callback sendEmail
* @param {string} to
* @param {string} body
* @returns {boolean} to indicate success or failure
*/
You can then use sendEmail
as a type:
/**
* @param {sendEmail} sender - function to send the email
*/
function createEmailService(sender) { ... }
The problem is there is not a good way to specify the type of a function because @type
on a function is interpreted to be the return type of the function, not the entire function definition. So something like the following does not work.
/**
* @type {sendEmail}
*/
function mailchimpEmailSender(to, body) { ... }
You can get it to work with the following, but it is not a great solution. I am still looking for a better solution.
/**
* @type {sendEmail}
*/
let mailchimpEmailSender
mailchimpEmailSender = function(to, body) { ... }
Update:
I have figured out that if you wrap the function declaration in parens it seems to allow the @type
to apply to the variable instead of the function declaration.
/**
* @type {sendEmail}
*/
const mailchimpEmailSender = (function(to, body) { ... })
This is the solution I like best thus far as it allows you to use the appropriate variable declaration keyword. The only downside is it requires you to remember to add in code that is not strictly necessary.
Upvotes: 6