You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm currently working with d.ts files where methods taking object parameters are documented with the following approach:
/*** Method description.* @param options addItemOptions - e.g. { id: 1234, title: "someTitle" }* id: The unique identifier.* title: The title to assign.**/
It's challenging to parse and auto-generate documentation for these parameters given this approach. Ideally there'd be support for dot syntax e.g.:
Reacted by Anthony Ciccarello, Pengfei Yang, Sean Poulter, Alex Leung, Ron Buckton, C. T. Lin, Ananto Ghosh, Travis Fischer, Andrei Sirotin, juicebeer and 66 more
For APIs intended for third parties, generally we recommend to give a name to the interface, for example:
/** Options for {@link ItemCollection.add} */interfaceIAddItemOptions{/** the identifier */id: string;/** a title, not to exceed 40 characters */title: string;}classItemCollection{/** * Adds an item to the collection * @param options - information about the item to be added */publicadd(options: IAddItemOptions): void{}}
Naming the interface improves the structure of the documentation. It also helps developers when they need to work with the object independently, for example:
That said, I believe TypeDoc supports the @param options.id notation. If people find it useful, we could include it in the TSDoc standard.
Reacted by Darren Schnare, Sean Poulter, Chris Thorn, C. T. Lin, Denis <Zaratan> Pasin, Rick Staa, Andrey Gubanov, Jose Antonio Cabañeros, Sal Rahman, Sean Juarez and 7 more
What's the long term plan for TSDoc? Will we be able to use it to annotate vanilla JavaScript like the current JSDoc support? In those cases the it's crucial to be able to declare an interface in the comments or document @param properties.
An additional point to consider when reviewing the dot syntax is the values might not be valid identifiers. When describing a tuple type, it would be great to be able to be able to use a.0 and a.1.
I wouldn't put this functionality in the initial version of TSDoc, because we could go forever like that and not release anything as it's been quite some time already. I'd rather have a working version using non-exported interfaces to document function parameters rather than postpone release by whatever amount of time with more functionality.
Pete Gonzalez (OLD ALIAS) (@pgonzal), if you add me to admins of the project (or whatever role I could be), I could structure things with releases and add initial versions to the issues. If we get arguments saying that something is super important and should be done from the get go, we can always change priorities. But it would be clear to everyone what are we aiming at.
What's more of a pain to document is destructured arguments:
/** * Call home. * @remarks * This texts Mom a message. * @param message - The message to send to Mom * @param ? - The number of seconds to delay calling home * @public */functioncallHome(message: string,{ delay =0}: {delay?: number}={}){// TODO: send a text to Mom!thrownewError('Unimplemented')}
Reacted by Sean Poulter, Alex Leung, C. T. Lin, Jason Dreyzehner, Calum Smith, mv-go, Travis Fischer, jmacura, Feiyang, Jonathan Pollak and 11 more
This practice seems to be entangling the internal implementation details of callHome() with its public API signature. It makes things more concise for the person who's writing the code, but at the expense of hindering our ability to help other people read and understand the code. One of my personal soapboxes is: Reading > Writing. (Writing code is fun and easy. But enabling others to understand and extend a code base is hard work, and a more important goal.)
From a documentation standpoint, destructuring has some downsides:
There's no name that we can use to discuss the second parameter of this function.
If a caller wants to construct and reuse that parameter, they can't easily declare its type
If we later want to deprecate the delay option, or write examples for it, there's no place to put that
For an external-facing API, my own team would normally want to convert it to something like this:
/** Options for the `callHome()` API */exportinterfaceICallHomeOptions{/** * The number of seconds to delay calling home * @defaultValue 0 */delay?: number;};/** * Call home. * * @remarks * This texts Mom a message. If you want to delay the message, * consider using {@link ICallHomeOptions.delay}. * @param message - The message to send to Mom * @param options - Additional options * @public */functioncallHome(message: string,options?: ICallHomeOptions={}){// TODO: send a text to Mom!thrownewError('Unimplemented')}
So now a caller can do this:
// Using this:functiongetStandardOptions(scenario: Scenario): ICallHomeOptions{...}callHome(message,this.getStandardOptions(Scenario.Intranet));
And someone can open a GitHub issue complaining that ICallHomeOptions.delay is broken, and we know what they're talking about.
I have struggled with this use case trying to add type information to a vanilla JS for some time. I wouldn't go so far as to call it the "public API" but this is common practice when consuming props in a React component. I've found the first argument destructured ~450 times in ~300 files in an e-commerce website I work on. If you have access to any other React repos, try searching for const [^ ]+ = \(\{.
I agree with Pete Gonzalez (OLD ALIAS) (@pgonzal)'s comments. IMO it would be better to define an interface. Instead of destructuring in the function arguments, I would also recommend destructuring within the function body. I'm not sure if you were counting this in your react repo but I wanted to note that alternative which I find makes the function signature easier to understand.
It may be common not to create an interface for function arguments, however I don't think TSDoc should be complicated because of this pattern when there is a clear alternative.
/** Options for the `callHome()` API */exportinterfaceICallHomeOptions{/** * The number of seconds to delay calling home * @defaultValue 0 */delay?: number;};/** * Call home. * * @remarks * This texts Mom a message. If you want to delay the message, * consider using {@link ICallHomeOptions.delay}. * @param message - The message to send to Mom * @param options - Additional options * @public */functioncallHome(message: string,options?: ICallHomeOptions={}){const{ delay =0}=options;thrownewError('Unimplemented')}
Reacted by Sean Poulter, Pete Gonzalez, Martynas Žilinskas, Tim Ambler and Maciej Cieślar
I've been brainstorming variants but haven't converged on anything I like. I've shared the variants I've thought of below. If anything, you've won me over that adding the interface is the way to go. It's readable and reduces the complexity to parse things.
Brainstorming ... 💭
I've mashed this into a JSDoc comment in the past which doesn't work as you add members or descriptions:
/** @param {name: string, age: number} ? - A person */consttoExample=({name: n,age: a})=>`${n} - ${a}`;
Consider the case when you refactor a function that has too many arguments to use named arguments with default values. The dot notation is great for this case. You can see the repetition of * @param args. adds to the clutter -- the interface is looking better.
constbad=(a,b,c,d,e, ...rest)=>{};// This is called as: bad('foo', null, null, FORM.FOO_ID, 1)/** * @param args * @param args.a - Placeholder * @param args.b - Accepted values * @param args.c - Validation functions * @param args.d - Field ID * @param args.e - Tab index */constbetter=({ a, b =null, c =null, d =null, e, ...rest})=>{};
It would be nice to remove the args. and have an anonymous argument. Naming things is hard. The ??? placeholder is really awkward and it's clear that this would look better expanded -- like the interface.
If we got a little crazy, we could drop the parent object(s) from the annotation and only document the values after they're destructured and renamed and let the type inference pick up the slack:
/** * @param' n - First name * @param' a - Age */consttoExample=({name: (n: string),age: (a: int)})=>`${n} - ${a}`;
I understand Pete Gonzalez (@octogonz)' argument from 2018. In most cases, it's probably best to define an explicit arguments type.
However, I would still like ergonomic support for anonymous object argument types in TSDoc.
I'm migrating a legacy codebase to TS. I can give Pete Gonzalez (@octogonz)' recommendation for writing new code and refactoring, but I'd like a lower barrier to entry for TS for the legacy code.
Sometimes in JS, I see an object used essentially as "named arguments to a function":
Arguably, creating FArgs is a bit redundant; why couldn't we just do Parameters?
Even if Pete Gonzalez (@octogonz) approach is right 90% of the time, I like the idea of providing the option for anonymous object function args for the 10% where it is actually more readable, and trusting devs to make the right call.
This seems fine to me, particularly since JSDoc has the same solution:
/** * @param args * @param args.a - Placeholder * @param args.b - Accepted values * @param args.c - Validation functions * @param args.d - Field ID * @param args.e - Tab index */constbetter=({ a, b =null, c =null, d =null, e, ...rest})=>{};
I think we could update the TSDoc parser to support this pretty easily.
👉 Let's make a PR to support that at least, since it probably covers 95% of use cases.
Documenting more elaborate structures like this example from JSDoc seems more sketchy:
/** * Assign the project to a list of employees. * @param {Object[]} employees - The employees who are responsible for the project. * @param {string} employees[].name - The name of an employee. * @param {string} employees[].department - The employee's department. */Project.prototype.assign=function(employees){
It implies a complete grammar for referring to nested members (analagous to the declaration reference syntax that we use for {@link} tags -- but different because it traverses runtime objects rather than types).
That seems like overengineering. The frankly simplest approach would be to put the doc comments inline, something like this:
exportfunctionf(a: {/** * the docs for `b` */b: {/** * the docs for `c` */c: number;}[];}): void{}
When I tried this, the TypeScript compiler faithfully emitted these comments in the .d.ts files, and VS Code IntelliSense seems to recognize it. So we would simply want to formalize it a bit:
What subset of TSDoc tags are allowable in nested members?
Where is a comment allowed?
How should a documentation website render such documentation? TSDoc doesn't need to dictate this, but we should provide a reference example to illustrate that the design doesn't have any gaps.
Reacted by Calum Smith, Lazerbeak12345, Mark Swayne, Bryan Hoang and SAITOU Keita
I would be perfectly happy to put the doc comments inline. That also has the benefit of saving one from repeating the param name.
I ended up on this issue because @microsoft/api-extractor does not recognize this inline style of doc comment. But since you said TS is emitting those commits and VSC Intellisense recognizes it, then maybe I need to follow up with @microsoft/api-extractor.
Reacted by Pete Gonzalez, Adam Griffiths and Mark Swayne
As for documenting the interfaces, okay, I could do that, but what about classes? I mostly use DTOs as classes. I can’t use @param, as a class does not use parameters, and there I can’t use `@property'.
Darren Schnare (@dschnare) I thought it may be better to name the options block and then destructure it within the function code block:
/** * Call home. * @remarks * This texts Mom a message. * @param message - The message to send to Mom * @param options - An object containing optional arguments. * @param options.delay - The number of seconds to delay calling home * @public */functioncallHome(message: string,options: {delay?: number}={}){const{ delay =0}=options// TODO: send a text to Mom!thrownewError('Unimplemented')}
Technically this is equivalent, but as far as I can tell, it doesn't really improve things as far as Intellisense is concerned. It avoids having to create a separate interface though.
My question was prompted by having an issue in Visual Studio Code, when I have dot syntax in a param ie. PARAM options.key - some comment, VS shows a problem with it, which appears to be coming from @microsoft/tsdoc@0.14.2 which is part of the eslint-plugin-tsdoc, so it appears to be a tsDoc problem. The documentation generated by typeDoc is working.
/** Options for the `callHome()` API */exportinterfaceICallHomeOptions{/** * The number of seconds to delay calling home * @defaultValue 0 */delay?: number;};/** * Call home. * * @remarks * This texts Mom a message. If you want to delay the message, * consider using {@link ICallHomeOptions.delay}. * @param message - The message to send to Mom * @param options - Additional options * @public */functioncallHome(message: string,options?: ICallHomeOptions={}){// TODO: send a text to Mom!thrownewError('Unimplemented')}
Pete Gonzalez (@octogonz) Would you not use an interface alongside destructuring? That seems common these days, especially in React applications. That leaves us back at square one though, as we now have no way of using @param.
Example:
/** Options for the `callHome()` API */exportinterfaceICallHomeOptions{/** * The number of seconds to delay calling home * @defaultValue 0 */delay?: number;};/** * Call home. * * @remarks * This texts Mom a message. If you want to delay the message, * consider using {@link ICallHomeOptions.delay}. * @param message - The message to send to Mom * @param ??? - Nothing to put here? * @public */functioncallHome(message: string,{ delay =0}: ICallHomeOptions={}){// TODO: send a text to Mom!thrownewError('Unimplemented')}
That is how I usually write components, i.e. with named arguments instead of a generic options.
How would you describe that destructured parameter with TSDoc?
PS: Documentation is helpful to our team internally, not just in public-facing APIs.
For APIs intended for third parties, generally we recommend to give a name to the interface, for example:
While I understand this answer, I think that's quite an opinionated stance, and I believe TSDoc should be unopinionated: JSDoc supports object properties (as already previously mentionned), why shouldn't TSDoc follow? Nothing forces a dev to use this over a type interface, but we should be able to use either if we want, no?
I personally find having to create type interfaces annoyingly redundant in most cases, especially when they're only used once. On top of that, my IDE, VSCode, when a function takes in a type interface as an input, only shows the name of the type interface and not what it's made out of, adding a layer of work to just understand the expected input type of a function as an end user (in which case destructured params are a lot easier to understand the expected API of).
Not only that, but looking at this issue, it seems like this is the general opinion? And the ???.property syntax seems spotless to me, a good contender for unnamed objects. I'm asking this because I don't fully understand why this issue still has the request for comments label.
PS: I glanced over this issue, didn't read everything in-depth, apologies if I'm redundant/not helpful.
Activity
octogonz commentedon May 26, 2018
For APIs intended for third parties, generally we recommend to give a name to the interface, for example:
Naming the interface improves the structure of the documentation. It also helps developers when they need to work with the object independently, for example:
That said, I believe TypeDoc supports the
@param options.idnotation. If people find it useful, we could include it in the TSDoc standard.seanpoulter commentedon May 26, 2018
What's the long term plan for TSDoc? Will we be able to use it to annotate vanilla JavaScript like the current JSDoc support? In those cases the it's crucial to be able to declare an interface in the comments or document
@paramproperties.octogonz commentedon May 27, 2018
Sean Poulter (@seanpoulter) I moved your question into a separate issue, since it's a separate topic.
Gerrit0 commentedon May 27, 2018
An additional point to consider when reviewing the dot syntax is the values might not be valid identifiers. When describing a tuple type, it would be great to be able to be able to use
a.0anda.1.DovydasNavickas commentedon Jun 9, 2018
I wouldn't put this functionality in the initial version of TSDoc, because we could go forever like that and not release anything as it's been quite some time already. I'd rather have a working version using non-exported interfaces to document function parameters rather than postpone release by whatever amount of time with more functionality.
Pete Gonzalez (OLD ALIAS) (@pgonzal), if you add me to admins of the project (or whatever role I could be), I could structure things with releases and add initial versions to the issues. If we get arguments saying that something is super important and should be done from the get go, we can always change priorities. But it would be clear to everyone what are we aiming at.
[-]Support for dot syntax on @param[/-][+]RFC: Support for dot syntax on @param[/+]dschnare commentedon Nov 27, 2018
What's more of a pain to document is destructured arguments:
octogonz commentedon Nov 27, 2018
Darren Schnare (@dschnare) do people commonly do this in a real world public API?
This practice seems to be entangling the internal implementation details of
callHome()with its public API signature. It makes things more concise for the person who's writing the code, but at the expense of hindering our ability to help other people read and understand the code. One of my personal soapboxes is: Reading > Writing. (Writing code is fun and easy. But enabling others to understand and extend a code base is hard work, and a more important goal.)From a documentation standpoint, destructuring has some downsides:
delayoption, or write examples for it, there's no place to put thatFor an external-facing API, my own team would normally want to convert it to something like this:
So now a caller can do this:
And someone can open a GitHub issue complaining that
ICallHomeOptions.delayis broken, and we know what they're talking about.seanpoulter commentedon Nov 27, 2018
I have struggled with this use case trying to add type information to a vanilla JS for some time. I wouldn't go so far as to call it the "public API" but this is common practice when consuming props in a React component. I've found the first argument destructured ~450 times in ~300 files in an e-commerce website I work on. If you have access to any other React repos, try searching for
const [^ ]+ = \(\{.octogonz commentedon Nov 27, 2018
Fair point. But how would you propose for TSDoc to handle that?
aciccarello commentedon Nov 28, 2018
I agree with Pete Gonzalez (OLD ALIAS) (@pgonzal)'s comments. IMO it would be better to define an interface. Instead of destructuring in the function arguments, I would also recommend destructuring within the function body. I'm not sure if you were counting this in your react repo but I wanted to note that alternative which I find makes the function signature easier to understand.
It may be common not to create an interface for function arguments, however I don't think TSDoc should be complicated because of this pattern when there is a clear alternative.
seanpoulter commentedon Nov 28, 2018
I think you've nailed it for the public API. 👌
I've been brainstorming variants but haven't converged on anything I like. I've shared the variants I've thought of below. If anything, you've won me over that adding the interface is the way to go. It's readable and reduces the complexity to parse things.
Brainstorming ... 💭
I've mashed this into a JSDoc comment in the past which doesn't work as you add members or descriptions:
Consider the case when you refactor a function that has too many arguments to use named arguments with default values. The dot notation is great for this case. You can see the repetition of
* @param args.adds to the clutter -- the interface is looking better.It would be nice to remove the
args.and have an anonymous argument. Naming things is hard. The???placeholder is really awkward and it's clear that this would look better expanded -- like the interface.If we got a little crazy, we could drop the parent object(s) from the annotation and only document the values after they're destructured and renamed and let the type inference pick up the slack:
rbuckton commentedon Dec 13, 2019
I would also like to see this implemented, as sometimes it doesn't make sense to introduce an interface for optional named parameters.
For reference, here is the documentation for the same feature in JSDoc: https://jsdoc.app/tags-param.html#parameters-with-properties
1 remaining item
NickHeiner commentedon Jan 26, 2021
I understand Pete Gonzalez (@octogonz)' argument from 2018. In most cases, it's probably best to define an explicit arguments type.
However, I would still like ergonomic support for anonymous object argument types in TSDoc.
Arguably, creating
FArgsis a bit redundant; why couldn't we just doParameters?Even if Pete Gonzalez (@octogonz) approach is right 90% of the time, I like the idea of providing the option for anonymous object function args for the 10% where it is actually more readable, and trusting devs to make the right call.
To me, in the example I listed above, someone could say "parameter
apassed tof" and I'd understand exactly what they meant.They can, with
Parameters<typeof f>.I think the TSDoc for
fwould be a perfectly fine place for examples.octogonz commentedon Jan 28, 2021
This seems fine to me, particularly since JSDoc has the same solution:
I think we could update the TSDoc parser to support this pretty easily.
👉 Let's make a PR to support that at least, since it probably covers 95% of use cases.
Documenting more elaborate structures like this example from JSDoc seems more sketchy:
It implies a complete grammar for referring to nested members (analagous to the declaration reference syntax that we use for
{@link}tags -- but different because it traverses runtime objects rather than types).That seems like overengineering. The frankly simplest approach would be to put the doc comments inline, something like this:
When I tried this, the TypeScript compiler faithfully emitted these comments in the .d.ts files, and VS Code IntelliSense seems to recognize it. So we would simply want to formalize it a bit:
NickHeiner commentedon Jan 28, 2021
I would be perfectly happy to put the doc comments inline. That also has the benefit of saving one from repeating the param name.
I ended up on this issue because @microsoft/api-extractor does not recognize this inline style of doc comment. But since you said TS is emitting those commits and VSC Intellisense recognizes it, then maybe I need to follow up with @microsoft/api-extractor.
aogriffiths commentedon Mar 5, 2021
has anyone started work on this? I would find it very useful!
NickHeiner commentedon Mar 7, 2021
tukusejssirs commentedon May 23, 2022
I’d like to see this implemented as well.
As for documenting the interfaces, okay, I could do that, but what about classes? I mostly use DTOs as classes. I can’t use
@param, as a class does not use parameters, and there I can’t use `@property'.Refactor: TasteGraphコンストラクタの型を変更した
Refactor: TasteGraphコンストラクタの型を変更した
Refactor: TasteGraphコンストラクタの型を変更した
ADTC commentedon Nov 30, 2023
Darren Schnare (@dschnare) I thought it may be better to name the options block and then destructure it within the function code block:
Technically this is equivalent, but as far as I can tell, it doesn't really improve things as far as Intellisense is concerned. It avoids having to create a separate interface though.
pick68 commentedon Dec 22, 2023
Has any work started on this? This will be very useful, while I could use an interface to solve the problem that's not always practical.
Gerrit0 commentedon Dec 22, 2023
Depending on the documentation generator you use, it may already be supported. TypeDoc supports it.
pick68 commentedon Dec 23, 2023
My question was prompted by having an issue in Visual Studio Code, when I have dot syntax in a param ie. PARAM options.key - some comment, VS shows a problem with it, which appears to be coming from @microsoft/tsdoc@0.14.2 which is part of the eslint-plugin-tsdoc, so it appears to be a tsDoc problem. The documentation generated by typeDoc is working.
magnusriga commentedon Aug 24, 2024
Pete Gonzalez (@octogonz) Would you not use an interface alongside destructuring? That seems common these days, especially in React applications. That leaves us back at square one though, as we now have no way of using
@param.Example:
That is how I usually write components, i.e. with named arguments instead of a generic
options.How would you describe that destructured parameter with TSDoc?
PS: Documentation is helpful to our team internally, not just in public-facing APIs.
glypse commentedon Jan 8, 2026
While I understand this answer, I think that's quite an opinionated stance, and I believe TSDoc should be unopinionated: JSDoc supports object properties (as already previously mentionned), why shouldn't TSDoc follow? Nothing forces a dev to use this over a type interface, but we should be able to use either if we want, no?
I personally find having to create type interfaces annoyingly redundant in most cases, especially when they're only used once. On top of that, my IDE, VSCode, when a function takes in a type interface as an input, only shows the name of the type interface and not what it's made out of, adding a layer of work to just understand the expected input type of a function as an end user (in which case destructured params are a lot easier to understand the expected API of).
Not only that, but looking at this issue, it seems like this is the general opinion? And the
???.propertysyntax seems spotless to me, a good contender for unnamed objects. I'm asking this because I don't fully understand why this issue still has therequest for commentslabel.PS: I glanced over this issue, didn't read everything in-depth, apologies if I'm redundant/not helpful.