Improve this page

Doc Comment Syntax

Why not JSDoc?

JSDoc is a great starting point for documenting your code, however it’s designed around the needs of JavaScript, not TypeScript. The vast majority of JSDoc tags (e.g. @abstract, @constructor, @class, @constant, @enum, etc) turn out to be redundant for a statically typed language. To support community conventions, API Extractor initially attempted to implement a proper subset of JSDoc. There was some confusion about which tags people were supposed to use, so we started reporting warnings for the inappropriate tags. We also invented some new tags to handle problems outside the scope of JSDoc, such as API graduation, NPM package references, and certain documentation generator requirements. Today, although API Extractor’s “AEDoc” syntax still attempts to follow JSDoc as much as possible, there are enough differences that it made sense to give it a different name.

Key goals of the “AEDoc” syntax:

  • Based on JSDoc - but designed for TypeScript
  • NPM aware - understands NPM package boundaries, allows cross-references between packages
  • Simplified syntax - we aim for “one obvious way” versus many equivalent notations for the same thing
  • Precise parsing - Unsupported, misspelled, or malformed tags are not silently ignored by our tooling. This content is published on your web site, so we should handle it with care!
  • Errors vs warnings - Easily fixable issues (e.g. a misspelled tag) are reported as errors, but deeper issues (e.g. missing documentation, unsupported types) are emitted as trackable warnings in your API file
  • Back-end agnostic - AEDoc does not informally “pass through” HTML or Markdown codes; the API JSON files use a rich text element stream that is suitable for producing consistent results in multiple formats (e.g. HTML, Markdown, PDF, or a proprietary system)

UPDATE: We’re working to standardize the syntax used by various tools that process TypeScript source code. Eventually AEDoc will align with this effort. If you’re interested, please check out the Microsoft/tsdoc project.

AEDoc comment structure

Here’s some sample TypeScript code that illustrates how AEDoc markup looks:

/**
 * The base class for all widgets.
 * @remarks
 * For details, see {@link https://example.com/shpadoinkle | the Shpadoinkle standard}.
 * @public
 */
abstract class BaseWidget {
  /**
   * Draws the widget.
   * @param force - whether to force redrawing
   * @returns true, if rendering occurred; false, if the view was already up to date
   */
  public draw(force: boolean): boolean {
    ...
  }

  /**
   * Gets or set the title of this widget
   */
  public get title(): string {
    ...
  }

  // NOTE: API Extractor considers your property getter and setter functions to be
  // a single API item.  Don't write any documentation for the setter.
  public set title(value: string) {
    ...
  }
}

Summary block

The preamble of your comment is referred to as the “summary block”. It should contain a short synopsis of your API item. API Extractor reports an error if your summary is too long; additional content should be moved to the optional @remarks section. If the summary is missing or extremely short, the API item will be marked as “undocumented” in your API file (*.api.ts).

Inline tags

A few “inline tags” accept parameters and must always be enclosed in curly braces (“{” and “}”). See {@link} and {@inheritdoc} for examples. Non-inline tags are called “block tags” and never have curly braces.

API item references

Certain tags such as {@inheritdoc} contain references to other API items. There are two versions of the notation:

  • For internal references, use: exportName.memberName <p/>Example: {@inheritdoc BaseWidget.draw}<p/>The “.memberName” is optional and included e.g. if the API item is a member of a class or interface.

  • For external references, use: @scopeName/packageName#exportName.memberName <p/>Example: {@inheritdoc @microsoft/widget-lib:BaseWidget.draw} <p/>The “@scopeName/” and “.memberName” are optional. The “@scopeName/” is used for scoped NPM packages. The external package must be installed in your node_modules folder, and must include an *.api.json file generated by API Extractor.

Release tags

The four release tags are: @internal, @alpha, @beta, @public. They are applied to API items such as classes, member functions, enums, etc. API Extractor classifies each exported API item individually, according to its intended level of support:

  • internal: Indicates that an API item is meant only for usage by other NPM packages from the same maintainer. Third parties should never use “internal” APIs. To emphasize this, underscore prefixes should be used for items with an (explicit) @internal tag.
  • alpha: Indicates that an API item is eventually intended to be public, but currently is in an early stage of development. Third parties should not use “alpha” APIs.
  • beta: Indicates that an API item has been released in an experimental state. Third parties are encouraged to try it and provide feedback. However, a “beta” API should NOT be used in production.
  • public: Indicates that an API item has been officially released. It is part of the supported contract (e.g. SemVer) for a package.

NOTE: TypeScript’s public/protected/private keywords are unrelated to the AEDoc release tags. For example, a member function will typically have the “public” TypeScript keyword regardless of whether it is classified as @public or @internal in the AEDoc comment.

When an API is first introduced, it typically starts as alpha. As the design matures, it graduates from alpha –> beta –> public. The internal designation is mostly used to solve plumbing problems, and usually isn’t on any road map to becoming public. (There is also a @deprecated tag, but it is an option that can be combined with any of the above tags.)

The release tag applies recursively to members of a container (e.g. class or interface). For example, if a class is marked as @beta, then all of its members automatically have this status; you DON’T need add the @beta tag to each member function. However, you could add @internal to a member function to give it a different release status.

NOTE: If a container (e.g. a class or interface) has an @internal tag, then an underscore prefix should be added to its name, but its members do NOT need underscores. Whereas e.g. if @internal is applied to a member function of a @beta class, then the internal function SHOULD have an underscore prefix.

Lastly, note that certain logical rules apply. For example, a @public function should not return a @beta type. A @beta class should not inherit from an @internal base class. etc. API Extractor does not currently validate these rules, but it will soon.

AEDoc tags

Here is a full listing of all currently supported AEDoc tags:

@alpha

A release tag indicating that an API item is eventually intended to be public, but is not yet ready for usage by third parties. See Release tags.

@beta

A release tag indicating that an API item has been released in an experimental state, and should not be used in production. See Release tags.

@betadocumentation

Used to flag documentation that has not been formally reviewed yet.

Example:

/**
 * The base class for all widgets.
 * @remarks
 * Someone shoudl probably check the sppelling of this sentence before we publlish it.
 * @betadocumentation
 * @public
 */
class Widget {
  ...
}

@deprecated

Indicates that an API item is no longer supported and may be removed in a future release. The @deprecated tag is followed by a sentence describing the recommended alternative (this is optional in JSDoc, but required in AEDoc). The @deprecated tag can be combined with any release tag (@internal, @alpha, @beta, or @public). It recursively applies to members of the container, e.g. if a class is deprecated, then so are all of its members.

Example:

/**
 * The base class for all widgets.
 * @deprecated The BaseWidget class has been superceded by the BaseShpadoinkle class.
 * @public
 */
class BaseWidget {
  ...
}

{@inheritdoc}

Syntax:

  • {@inheritdoc API_ITEM_REFERENCE}

Reuses the documentation from another API item, specified using the API item reference notation. For example, suppose that a class BaseWidget implements the interface IWidget. Since it has all the same members as IWidget, we should not have to copy+paste the documentation. The {@inheritdoc} tag solves this problem. It also allows you to reuse documentation from any arbitrary API item, even in a different package. Unlike JSDoc, AEDoc copies only the summary block, @remarks, and any relevant @param or @returns descriptions. API Extractor does NOT copy other metadata such as @readonly, API release tags, etc. A warning is reported if the source is marked as @deprecated (since you probably want to copy that documentation before the deprecated definition goes away).

Example:

/**
 * An interface describing a widget.
 * @public
 */
interface IWidget {
  /**
   * Draws the widget.
   */
  public draw(): void;
}

/**
 * The base class for all widgets.
 * @public
 */
class BaseWidget {
  /** {@inheritdoc IWidget} */
  public draw(): void {
    ...
  }

  /** {@inheritdoc some-other-package:Saver.save} */
  public save(): void {
    ...
  }
}

@internal

A release tag indicating that an API item is not meant for usage by third parties. See Release tags.

@internalremarks

Similar to @remarks, but designates additional commentary that should not be published on your API reference web site.

Example:

/**
 * The base class for all widgets.
 * @remarks
 * All widget implementations should inherit from this class.
 * @internalremarks
 * Yikes, this class really needs some cleanup!
 * @public
 */
class BaseWidget {
  ...
}

Syntax:

  • {@link API_ITEM_REFERENCE}
  • {@link API_ITEM_REFERENCE | DISPLAY_TEXT}
  • {@link URL}
  • {@link URL | DISPLAY_TEXT}

Creates a hyperlink to an internet URL or another API documentation page specified using API item reference notation. If the DISPLAY_TEXT is omitted, then the API item name or URL address will be used as the display text. (To keep things simple and well-defined, AEDoc does not accept JSDOc’s numerous alternative syntaxes for links.)

Example:

/**
 * The base class for all {@link https://en.wikipedia.org/wiki/Widget | widgets}.
 * @remarks
 * Implements the {@link @microsoft/widget-lib:IWidget} interface.  To draw the widget,
 * call the {@link BaseWidget.draw | draw() function}.
 * @public
 */
class BaseWidget implements IWidget {
  /** {@inheritdoc IWidget} */
  public draw(): void {
    ...
  }

  ...
}

@param

Syntax:

  • @param NAME - DESCRIPTION

Provides documentation for a function parameter.

NOTE: JSDoc also includes type information in curly braces, but AEDoc gets this information from the compiler. Also note that the hyphen delimiter is not optional.

Example:

class BaseWidget implements IWidget {
  /**
   * Draws the widget on the screen.
   * @param x - the X-coordinate of the upper-left corner of the bounding rectangle
   * @param y - the Y-coordinate of the upper-left corner of the bounding rectangle
   */
  draw(x: number, y: number): void {
    ...
  }

  ...
}

@preapproved

When an API is marked as @internal, this does not prevent its signature from being emitted in the API files (*.api.ts). This is because a review policy is often interested in internal APIs. (Is it a breaking change? Why do we keep adding more junk to that “InternalUtilities” class? etc.) However, if it is not useful to review a particular API item, it can be marked as @preapproved. This prevents it from being emitted in the API file, and thus exempts it from any review policies.

Example:

/**
 * These tokens are generated by the tooling system, so there is no
 * reason to review them.
 * @internal @preapproved
 */
enum _LogTokens {
  ...
}

@public

A release tag indicating that an API item has been officially released. See Release tags.

@readonly

AEDoc generally does NOT require you to indicate read-only definitions.

The TypeScript language already tells us whether a definition is read-only or not. The AEDoc @readonly tag is only used in special situations where a property is not technically read-only, but we want it to appear as read-only in the documentation.

Example:

class Widget {
  ...
  /**
   * Everday case: API Extractor will document this property as being read-only.
   */
  public get x(): number {
    return this._x;
  }

  /**
   * Special case:  We need to tell API Extractor to ignore the property setter.
   * @readonly
   */
  public get title(): string {
    return this._title;
  }

  public set title(value: string) {
    throw new Error('This property is read-only!');
  }
}

@returns

Provides documentation for a function return value.

NOTE: AEDoc does not accept @return as a synonym for @returns.

Example:

interface IWidget {
  /**
   * Draws the widget on the screen.
   * @returns true, if rendering occurred; false, if the view was already up to date
   */
  draw(): boolean {
    ...
  }

  ...
}

@remarks

When listing a table of API items (e.g. members of a class), an API reference web site typically shows only brief summaries. Then, when you navigate to the detail page for an API item, you see in-depth content.

The @remarks tag delinates where your summary block ends and these additional remarks begin. On a detail page, the remarks will be displayed after the summary block.

AEDoc example:

class BaseWidget {

  /**
   * Draws the widget on the screen.
   * @remarks
   * This method should be called whenever the widget content has been updated.
   */
  public draw(): void {
    ...
  }
  ...
}

NOTE: JSDoc has a different approach. It uses an optional @summary tag to provide *alternative* content. We found that this leads to duplication, and the behavior is less clear when a summary is omitted. Note how in this example, the author ended up duplicating the text “Draws the widget on the screen”:

JSDoc example: (NOT SUPPORTED BY API EXTRACTOR)

class BaseWidget {
  /**
   * Draws the widget on the screen.
   * This method should be called whenever the widget content has been updated.
   * @summary Draws the widget on the screen. <-- (NOT SUPPORTED BY API EXTRACTOR)
   */
  public draw(): void {
    ...
  }

  ...
}