tsdoc
tsdoc copied to clipboard
Question: tsdoc for function type declaration
export type OnEnter = (success: boolean) => void;
export interface OnLeave {
(success: boolean): void
}
How should we reasonably add the type declaration of the above function type 'OnEnter' and 'OnLeave' ?
I tried as follows:
Good question!
I would document it like this:
/**
* A callback that occurs after the control receives focus.
* @param success - true if successful
*/
export type OnEnter = (success: boolean) => void;
/**
* A callback that occurs after the control receives focus.
*/
export interface OnLeave {
/**
* The call signature for `OnLeave`.
* @param success - true if successful
*/
(success: boolean): void
}
The TSDoc design focuses mainly on how comments get parsed. The spec will give better guidance about /where/ comments should go (but this is guidance for implementors; it doesn't affect the parser library at all).
You could consider API Extractor to be a functionally complete reference implementation of a TSDoc application. API Extractor distinguishes specific syntax elements as being "API items" (see ApiItem.ts and AstDeclaration.ts). These are the things that get their own entry on the documentation website. For OnEnter
it would be the ts.SyntaxKind.TypeAliasDeclaration
declaration. For OnLeave
it would be the ts.SyntaxKind.CallSignature
.
Some additional thoughts:
- API Extractor doesn't implement the
@param
tag for atype
. Probably we should support it. - It could be ambiguous, though. For example:
How is the documentation website supposed to represent this? I think we would ask to split it into two declarations, or else to rename the parameters to// here there are two different parameters called "success" export type OnEnter = ((success: boolean) => void) | ((success: ISuccessCode, details: string) => void);
success
andsuccess2
. - The interface-with-a-call-signature form is very rarely used, only in cases where the interface would have other members (e.g. due to a merged declaration that describes a legacy JavaScript API.) The
type
representation is generally the best practice.
/**
* A callback that occurs after the control receives focus.
*/
export type OnEnter =
/**
*
* @param success - true if successful
*/
& ((success: boolean) => void)
/**
*
* @param message - success message
*/
& ((message: string) => void)
/**
* A callback that occurs after the control receives focus.
*/
export interface OnLeave {
/**
* The call signature for `OnLeave`.
* @param success - true if successful
*/
(success: boolean): void
/**
* The call signature for `OnLeave`.
* @param message - success message
*/
(message: string): void
}
I think this can better represent overloading.
It could be ambiguous, though.
// here there are two different parameters called "success"
export type OnEnter = ((success: boolean) => void)
| ((success: ISuccessCode, details: string) => void);
In typescript, we generally use the intersection type of the functions to represent overloading.
If tsdoc correctly parses the following notation, then it has the same ability to write doc as interface-with-a-call-signature.
/**
* A callback that occurs after the control receives focus.
*/
export type OnEnter =
/**
*
* @param success - true if successful
*/
& ((success: boolean) => void)
/**
*
* @param success - success code
* @param details - the details
*/
& ((success: ISuccessCode, details: string) => void);
This design has a problem: it introduces the possibility that /** */
comment can appear anywhere in an expression. For example, should this be valid?
/**
* A callback that occurs after the control receives focus.
*/
export type OnEnter =
&
/**
* the first overload
*/
(
(
/**
* @param success - true if successful
*/
success: boolean
) => /** The return value */ void
)
& (
/**
* The second overload
* @param success - success code
* @param details - the details
*/
(
success: ISuccessCode,
details: Set</** this value must not be empty */ string>
) => void
);
If not, which parts of an expression are allowed to have doc comments? We would need to make rules about where a comment can go, and I suspect those rules would be fairly complicated. It might be difficult for a user to understand /why/ a comment is allowed in one place, but not in another place. A goal of TSDoc is that the notation should be easy to learn from looking at examples, without having to consult a manual. And its interpretation should be easy to predict, without needing to run it through a parser/renderer.
There's also a question of how a documentation engine is supposed to present these comments. Documentation templates tend to be based on stereotypes ("functions", "interfaces", "classes", etc.) Free-form type algebra expressions are challenging to document, at least if you want great documentation that looks professional.
Bump. JSDoc has @callback
for this purpose.
Not particularly happy with TSDoc approach, but at least it somewhat works, albeit weird that it omits parameter documentation if you place caret on the name of the parameter.
Bump. Still looking for a good solution, too. I currently use a workaround like below, but i'm not really happy with the type unawareness inside the handler function:
type SuccessCode = 200 | 201;
/**
* A callback that occurs after the control receives focus.
* @param success true if successful
*/
declare function OnEnterFn(success: boolean): void;
/**
* A callback that occurs after the control receives focus, contains details.
* @param success success code
* @param details the details
*/
declare function OnEnterFn(success: SuccessCode, details: string): void;
/**
* Type description when hovering over OnEnter.
*/
export type OnEnter = typeof OnEnterFn;
const handler: OnEnter = (success: boolean | SuccessCode, details?: string | undefined) => {
if (typeof success === "boolean") {
console.log("overload 1", success);
} else {
console.log("overload 2", success, details);
}
};
const foo = (onEnter: OnEnter) => {
onEnter(true);
};
const bar = (onEnter: OnEnter) => {
onEnter(201, "more details");
};
foo(handler);
bar(handler);