microsoft / tsdoc

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

Handling/documenting merged declarations #137

Open natalieethell opened 5 years ago

natalieethell commented 5 years ago

Pete and I were discussing ways that TSDoc can handle merged declarations, and wanted to summarize a couple ideas here.

Say you have a scenario where you want to export the same API with a different name. You could have something like

export { X as Y } from '...'

where Y is an enum. For certain reasons, we are considering splitting it up into something like the following instead:

export enum Y {
...
}

export type X = Y;
export const X = Y;

We likely still want both X's to be documented together. We have two tags in mind to solve this: @documentAs and @partOf.

@documentAs would be used to tell the documentation system how to present this API, for example grouping it under the "Enums" section even though it isn't technically an enum.

@partOf would be used to group the second definition with the first.

Using the example above, we might have something like the following:

/**
 * Another name for {@link Y}.
 * {@documentAs enum}
 */
export type X = Y;

/**
 * {@partOf (X:type)}
 */
export const X = Y;

I'm curious to see if anyone else has thought about this much and/or has any other ideas or suggestions.

octogonz commented 5 years ago

I'm curious in general about how documentation systems represent merged declarations, which seem to be a TypeScript-specific requirement.

Consider a definition like ApiReleaseTagMixin:

Currently DocFX groups API items under headings by type (e.g. "Classes", "Interfaces", "Enums", and "Functions" in this example). Today ApiReleaseTagMixin will get three declarations that appear under "Interfaces", "Functions", and "Namespaces", and each of these entries would have its own documentation.

Using the TSDoc tags @natalieethell proposed above, we could maybe improve that:

/**
 * The mixin base class for declarations that have an associated release tag.
 *
 * @remarks
 * The interface describes the non-static members of the mixin class.
 * The namespace contains the static members.
 *
 * {@documentAs mixin}
 */
interface ApiReleaseTagMixin {
}

/** {@partOf (ApiReleaseTagMixin:interface)} */
namespace ApiReleaseTagMixin {
}

/**
 * Constructs a mixin base class conforming to the {@link (ApiReleaseTagMixin:interface)}
 * interface.
 * @param baseClass - The base class to extend
 * @returns a base class that implements `ApiReleaseTagMixin` and extends `baseClass`
 */
function ApiReleaseTagMixin<TBaseClass extends IApiItemConstructor>(baseClass: TBaseClass):
  TBaseClass & (new (...args: any[]) => ApiReleaseTagMixin) {
}

The @documentAs tag would cause this to go under a custom heading "Mixins" instead of "Interfaces". And @partOf tag causes the namespace signature to be documented with the interface (rather than getting its own documentation entry), like this:

ApiReleaseTagMixin Mixin

The mixin base class for declarations that have an associated release tag.

Signature:

interface ApiReleaseTagMixin
namespace ApiReleaseTagMixin

Remarks

The interface describes the non-static members of the mixin class. The namespace contains the static members.

Whereas the function would get its own documentation entry under the "Functions" section as usual:

ApiReleaseTagMixin Function

Constructs a mixin base class conforming to the ApiReleaseTagMixin interface.

Signature:

function ApiReleaseTagMixin<TBaseClass extends IApiItemConstructor>(baseClass: TBaseClass):
  TBaseClass & (new (...args: any[]) => ApiReleaseTagMixin);

Parameters

. . .

@dend

octogonz commented 5 years ago

We already established in https://github.com/Microsoft/tsdoc/issues/9 that function overloads should be documented separately. But Office Addin-ins encountered a bunch of function overloads that exist purely for technical reasons (due to two different enum representations in this case). For example:

/**
 *
 * Deletes the cells associated with the range.
 *
 * [Api set: ExcelApi 1.1]
 *
 * @param shift - Specifies which way to shift the cells. See Excel.DeleteShiftDirection for details.
 */
delete(shift: Excel.DeleteShiftDirection): void;
/**
 *
 * Deletes the cells associated with the range.
 *
 * [Api set: ExcelApi 1.1]
 *
 * @param shift - Specifies which way to shift the cells. See Excel.DeleteShiftDirection for details.
 */
delete(shift: "Up" | "Left"): void;

The @partOf tag could help with that problem, by allowing the above pattern to be simplified to this:

/**
 *
 * Deletes the cells associated with the range.
 *
 * [Api set: ExcelApi 1.1]
 *
 * @param shift - Specifies which way to shift the cells. See Excel.DeleteShiftDirection for details.
 */
delete(shift: Excel.DeleteShiftDirection): void;

/** {@partOf (delete:1)} */
delete(shift: "Up" | "Left"): void;

@AlexJerabek @Zlatkovsky

AlexJerabek commented 5 years ago

How would this affect overloads with differently documented parameters? Here's a common example from our d.ts:

load(option?: Excel.Interfaces.StyleCollectionLoadOptions & Excel.Interfaces.CollectionLoadOptions): Excel.StyleCollection;

load(option?: string | string[]): Excel.StyleCollection;

load(option?: OfficeExtension.LoadOption): Excel.StyleCollection;
octogonz commented 5 years ago

The documentation would refer to the "main" declaration (i.e. the one that doesn't have an @partOf tag). If the other declarations had different parameters, they wouldn't get documented.

AlexJerabek commented 5 years ago

Am I correct in thinking @partOf wouldn't help the Intellisense experience? Would the user see the partOf text or the "main" function's description?

octogonz commented 5 years ago

It depends. If @partOf was considered important enough to be made part of the TSDoc core standard, then to the extent that Visual Studio Code supports TSDoc, the IntelliSense would work.

AlexJerabek commented 5 years ago

Just to be clear, individual parameters on overloads would not be explicitly documented with @param tags, correct? We'd have to fully contain all that data in the base function's description. That's all reasonable, though there might be cases with particular restrictions for the overloaded parameters (@ElizabethSamuel-MSFT might have such scenarios in Outlook).

Please let us know when this is functionality is supported in the web-build-tools suite.

octogonz commented 4 years ago

Update: PR https://github.com/microsoft/rushstack/pull/1646 adds most of the machinery needed to implement a @partOf tag.

bajtos commented 4 years ago

Would this work for merging interface docs into class docs? Using the example from https://github.com/microsoft/rushstack/issues/1921:

/**
 * @public
 */
export class MyWidget extends EventEmitter {
  // class implementation goes here
}

/**
 * {@partOf (MyWidget:class)}
 */
export interface MyWidget {
  // `on()` and `once()` declarations for MyWidget events
}
octogonz commented 4 years ago

Would this work for merging interface docs into class docs?

When designing @partOf, we did not think very much about how to handle API items that contain other nested items. Consider this example:

/**
 * {@partof (Example:namespace)}
 */
interface Example {
    a: number;

    /** Description 1 */
    b: number
}

namespace Example {
    /** Description 2 */
    export const b: string = 'b';

    export const c: string = 'c';
}

What does the table of contents show? Should the items be mixed together like:

A different strategy would be to keep them as separate items, but with hyperlinks that makes it easy to switch between them:

Something like this:

Example Interface

Related to: Example Class

The mixin base class for declarations that have an associated release tag.

Signature:

interface Example