tsdoc icon indicating copy to clipboard operation
tsdoc copied to clipboard

Question: tsdoc for function type declaration

Open xiaoxiangmoe opened this issue 5 years ago • 6 comments

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:

image

image

xiaoxiangmoe avatar Aug 30 '19 19:08 xiaoxiangmoe

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 a type. Probably we should support it.
  • It could be ambiguous, though. For example:
       // here there are two different parameters called "success"
       export type OnEnter = ((success: boolean) => void) 
         | ((success: ISuccessCode, details: string) => void);
    
    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 success and success2.
  • 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.

octogonz avatar Aug 30 '19 21:08 octogonz

/**
 * 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.

xiaoxiangmoe avatar Aug 31 '19 05:08 xiaoxiangmoe

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);

xiaoxiangmoe avatar Sep 04 '19 03:09 xiaoxiangmoe

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.

octogonz avatar Sep 04 '19 05:09 octogonz

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.

Cheatoid avatar Mar 30 '22 18:03 Cheatoid

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);

theuntitled avatar May 03 '23 23:05 theuntitled