microsoft / tsdoc

A doc comment standard for TypeScript
https://tsdoc.org/
MIT License
4.71k stars 131 forks source link

RFC: @defaultValue to indicate a default value #27

Open octogonz opened 6 years ago

octogonz commented 6 years ago

In API Extractor issue #720 @RueDeRennes asked about supporting @default to indicate a default value.

Some questions:

typhonrt commented 6 years ago

This tag seems to still be useful for properties and fields. It seems less applicable for function parameters with the introduction of default values accessible in the AST with ES6+ and TS. However, it's still applicable on the JS side of things IMHO due to call time evaluation and default parameters being available for later evaluation (examples taken from here):

function callSomething(thing = something()) {
 return thing;
}

function something() {
  return 'sth';
}

or default parameters being available to later default parameters

function singularAutoPlural(singular, plural = singular + 's', rallyingCry = plural + ' ATTACK!!!') {
  return [singular, plural, rallyingCry]; 
}

Granted JS examples on tricky edge cases, but likely as well w/ TS? The @param w/ types and default values JSDoc syntax handles this for function parameters. It would be interesting to see the TS counterpart to the above perceived edge cases.

The examples above would cause trouble for various documentation tooling pipelines likely as resolving things requires a two pass algorithm and potential evaluation which can be fragile where the @default @param w/ default values syntax provides up front clarification.

octogonz commented 5 years ago

Based on the discussion with office-ui-fabric-react, we're thinking @default tag would have the following spec:

Usage example:


enum WarningStyle {
  DialogBox,
  StatusMessage,
  LogOnly
}

interface IWarningOptions {
  /**
   * Determines how the warning will be displayed.
   *
   * @remarks
   * See {@link WarningStyle| the WarningStyle enum} for more details.
   * 
   * @default `WarningStyle.DialogBox`
   */
  warningStyle: WarningStyle;

  /**
   * Whether the warning can interrupt a user's current activity.
   * @default 
   * The default is `true` unless
   *  `WarningStyle.StatusMessage` was requested.
   */
  cancellable?: boolean;

  /**
   * The warning message
   */
  message: string;
}
octogonz commented 5 years ago

I noticed that although JSDoc supports @default as shown above, it also allows @default to act as a modifier tag that instructs a documentation engine to show the assigned value of a variable. This is the example they give:

Document the number value of a constant

/**
 *  @constant
 *  @default
 */
const RED = 0xff0000;

I interpret this to mean that, JSDoc parses @default as a block tag if there's more text on the same line, otherwise it's parsed as a modifier tag. Since TSDoc doesn't force each tag to be on its own line, this is unconventional and likely to cause confusion.

I'm also not even sure this is needed with TypeScript. In my experience the type system already makes it fairly easy to determine whether or not a variable is a constant whose value should be documented.

Since JSDoc defines @defaultvalue as a synonym, I propose the following refinements:

This ensures that the two scenarios don't get confused, and we won't need a special grammar rule to handle this tag.

octogonz commented 5 years ago

Updated usage example:


enum WarningStyle {
  DialogBox,
  StatusMessage,
  LogOnly
}

interface IWarningOptions {
  /**
   * Determines how the warning will be displayed.
   *
   * @remarks
   * See {@link WarningStyle| the WarningStyle enum} for more details.
   * 
   * @defaultValue `WarningStyle.DialogBox`
   */
  warningStyle: WarningStyle;

  /**
   * Whether the warning can interrupt a user's current activity.
   * @defaultValue
   * The default is `true` unless
   *  `WarningStyle.StatusMessage` was requested.
   */
  cancellable?: boolean;

  /**
   * The warning message
   */
  message: string;
}
octogonz commented 5 years ago

@kkjeer

aciccarello commented 5 years ago

Related TypeStrong/typedoc#856

Typedoc show values but in some cases that isn't desired, such as here when the text is super long.

sindresorhus commented 5 years ago

@octogonz How about documenting default function parameters? What will that look like?

octogonz commented 5 years ago

How about documenting default function parameters? What will that look like?

@sindresorhus After thinking about this question, I don't think @defaultValue is the right tag for this. I opened a separate GitHub issue to discuss your question: https://github.com/Microsoft/tsdoc/issues/151

kononenko-a-m commented 4 years ago

Hi @octogonz , sorry don't know where to ask, hope this would be a good place.

You've outlined that @defaultValue should be used only for classes or interfaces. What about type? It's quite often used in React-land, for defining props via type instead of interface, as sometime it gives better flexibility, and it may have a default value. In separate thread that you have for function default values it's not really applicable I think.

Let me provide you an example:

const enum ButtonType {
  DEFAULT = 'default',
  PRIMARY = 'primary'
}

type ButtonProps = {
 /**
 * This is identify Button type
 * @defaultValue? ButtonType.DEFAULT
 */
  type?: ButtonType;
  children: React.ReactNode;
} & Omit<JSX.IntrinsicElements['button'], 'children'>;

const Button = ({ type, children, ...props }: ButtonProps) => {
  const type = type ?? ButtonType.DEFAULT;
  return <button className={mapTypeToClassName(type)} {...props}>{children}</button>;
};

This is quite common approach for declaring React component props, and as you see function has no default parameters, at least in obvious way, so my question is, should it be ok to use @defaultValue for the type's as well? Or should be some other tag introduced?

octogonz commented 4 years ago

Short answer: Yes @defaultValue is okay.

Long answer: My perspective comes from projects where we are designing third-party APIs with an API documentation website. So I would try to write it something like this:

interface IButtonCoreProps {
 /**
 * This is identify Button type
 * @defaultValue? ButtonType.DEFAULT
 */
  type?: ButtonType;
  children: React.ReactNode;
};

type ButtonProps = IButtonCoreProps & Omit<JSX.IntrinsicElements['button'], 'children'>;

The reason is that an interface is a familiar stereotype that can be nicely displayed on a documentation website, with nice sections for each its interface members.

Whereas type is a mishmash of arbitrary type algebra. It contains a sort of "interface" buried in there (that @defaultValue could apply to), but a documentation tool would see it as an amoeba whose structure may change whenever a developer expands it with some more type subexpressions. Thus as an API designer I strongly prefer stereotypical patterns (interface, class, function, etc) instead of mishmashes of type algebra.

But these concerns maybe aren't relevant to your case. The ButtonProps type may be some internal app code that does not need to be displayed as a nice API manual. And React's "props" system is a widely understood pattern that establishes some proprietary stereotypes of its own. So in that context your more concise notation is perhaps better.

Maybe the TSDoc spec should say it in a more general way, like: @defaultValue is meant for optional properties of interface members, or other type expressions that behave like interfaces.

More thoughts about stereotypes

I'm reluctant for TSDoc to specify stereotypes. They seem like an implementation detail that may vary between documentation tools. For example, one tool might consider this to be a "function":

/**
 * @param x - the input
 * @returns the result
 */
const f: (x: string) => string;

Another tool may consider it to be a "variable" whose value is a mishmash of algebra.

What about this thing below?

/**
 * Some docs
 */
const f:
  /**
   * @param x - the input
   * @returns the result
   */
   ((x: string) => string) |
  /**
   * @param x - the input
   * @returns the result
   */
    ((x: number) => number);

Is that two "functions" in there? API Extractor would consider it a misuse of TSDoc. But if some other tool has a way to present (x: number) => number) as being a "function" in some meaningful way, it feels wrong for TSDoc to specify that no, nobody should do that.

Perhaps what we should specify is a minimal set of "core" stereotypes that all TSDoc implementors should be expected to recognize. If you write doc comments using those patterns, you know they will have consistent behavior everywhere.