注释写的好✍️,文档不潦草👍!

9,140 阅读8分钟

大家好,long time no see!这次聊一聊「注释」。写「注释」的好处众所周知,但有时在实现一些「公共代码」后,需要编写「文档」,其中「注释」和「文档」的内容是大致相同的,比如paramreturns等(相信有不少同学试过把「注释」copy到「文档」中吧🌝)。So,有没有一种可能,直接把「注释」直接转化为「文档」呢?

背景

👨‍💼:一会抽空优化下JsBridge,写好注释!写好文档!

JsBridge:用于「Hybrid」开发模式下,「Native 原生」和「Web H5」之间通信。

🧑‍💻:JsBridge里面几十个方法,写完注释还要写文档,得花不少时间。

👨‍💼:你傻啊,不会用TypeDoc吗?

🧑‍💻:就是那个根据「代码」直接生成「文档」的工具吗?我这就去试试!

TypeDoc

TypeScript的「文档生成器」,通过读取TypeScript源文件,解析其中包含的注释,并为代码生成包含文档的「静态站点」。

安装

$ npm install typedoc --save-dev

安装时,要注意对应项目的typescript版本

TypeDoc VersionTypeScript Version(s)
0.234.6 through 4.8
0.224.0 through 4.7
0.214.0 through 4.4
0.203.9 through 4.2
0.193.9 through 4.0

使用方式

TypeDoc提供两种了运行方式:

  • Cli
  • Node Module

Cli

使用「命令行」运行时,有三种配置方式:

  1. 把所有参数添加在「命令」中:
$ typedoc --out docs src/index.ts
  1. 使用typedoc.json(推荐):

在项目的「根路径」下创建typedoc.json

./typedoc.json

{
    "entryPoints": ["src/index.ts"],
    "out": "docs"
}
  1. tsconfig.json中配置:

tsconfig.json中的typedocOptions字段定义参数。

{
    // ...
    "typedocOptions": {
        "entryPoints": ["src/index.ts"],
        "out": "docs"
    }
}

Node Module

如果想要「动态配置」或者不使用Cli来运行typedoc,可以require("typedoc")进行文档构建。

const TypeDoc = require("typedoc")

// 初始化
const app = new TypeDoc.Application()
// 读取 tsconfig.json
app.options.addReader(new TypeDoc.TSConfigReader())
// or 读取 typedoc.json
app.options.addReader(new TypeDoc.TypeDocReader())
// or 直接配置
app.bootstrap({
   entryPoints: ["src/index.ts"],
});

const project = app.convert()
if (project) {
  const outputDir = "docs"
  // 生成文档
  await app.generateDocs(project, outputDir)
  // 生成json
  await app.generateJson(project, outputDir + "/documentation.json")
}

具体的参数配置,👉 传送门

插件

使用plugin字段扩展TypeDoc的能力。

{
  // ...
  plugin: ['typedoc-plugin-markdown'],
}

默认情况下,TypeDoc只生成html的静态站点文档,如需Markdown格式,可添加typedoc-plugin-markdown,生成对应的Markdown文档。

更多的官方插件,👉 传送门

Tips: 在npmtypedoc-plugin关键字下可以找到更多插件。

主题

使用theme字段配置生成文档的主题。

{
  // ...
  "plugin": ["typedoc-theme-hierarchy"],
  "theme": "hierarchy",
}

使用主题时,要先添加进plugin中,再添加到theme中。

更多主题,👉 传送门

Tips: 在npmtypedoc-theme关键字下可以找到更多主题。

TSDoc

TypeDoc不是万能的,“龙飞凤舞”的「注释」生成不了「文档」,或者生成后的「文档」阅读性很差。

因此,这就要求我们✍️「注释」时要符合“规范”!

没错,用别人的工具,就要遵守别人的规则,这个“规范”就是TSDoc

TSDoc是标准化TypeScript代码中使用的文档注释,这样不同的工具就可以提取内容,而不会被彼此的标记混淆。

通俗来说,TSDoc就是「微软」的一套「注释规范」,主要用于统一某些工具(除了TypeDoc外还有DocFXAPI Extractor等)对「注释」的识别,避免每个工具都搞一套自己的注释规范。

Tags(重点)

正确使用Tags是写好注释的基础。

标签名以 @ 开头,后面跟着使用「驼峰大小写的ASCII字母」。

kind

Tags根据「类型」来区分,分三种:

  • Block tags
  • Modifier tags
  • Inline tags

Block tags

「块级」标签:

  1. tags作为一行中「第一个」出现的元素;
  2. 一行中仅允许该tags存在(很多Block tag都没遵守该约定);
  3. 直至遇到下个Block tags前,都是该tags的内容;
  4. 第一个Block tags前面的内容为“摘要”;

Modifier tags

「修饰」标签:

  1. 内容为空的Block tags
  2. 出现在注释底部;

Inline tags

「行内」标签:

  1. Markdown表达式一起作为内容元素出现;
  2. { } 字符包围;

Standardization

Tags根据「兼容性」来区分,分三组:

  • Core
  • Extended
  • Discretionary

Core

每个「文档工具」都支持。

Extended

「文档工具」可能支持,也可能不支持,需要查阅该工具的文档。

Discretionary

针对某个/些「文档工具」而自身定制的tags

常用的Tags

@alpha

表示该Api的发布阶段为alpha

Standardizationkind
DiscretionaryModifier
export class Book {
  /**
   * The title of the book.
   * @alpha
   */
  public get title(): string;
};

@beta @experimental

beta与experimental同义

表示该Api的发布阶段为beta/experimental

Standardizationkind
DiscretionaryModifier
export class Book {
  /**
   * The title of the book.
   * @beta
   */
  public get title(): string;
};

@internal

表示该Api不计划供第三方使用。

Standardizationkind
DiscretionaryModifier
export class Book {
  /**
   * The title of the book.
   * @internal
   */
  public get _title(): string;
};

@public

表示该Api的发布阶段为public,代表已正式发布且稳定的(可省略)。

Standardizationkind
DiscretionaryModifier
export class Book {
  public get title(): string;
};

@decorator

由于TypeScript编译器并不能识别.d.ts中的「装饰器」,所以使用该tag在文档注释中引用decorator表达式。

Standardizationkind
ExtendedBlock
class Book {
  /**
   * The title of the book.
   * @decorator `@jsonSerialized`
   * @decorator `@jsonFormat(JsonFormats.Url)`
   */
  @jsonSerialized
  @jsonFormat(JsonFormats.Url)
  public website: string;
}

@deprecated

表示该Api不再被支持,未来可能会被删除,其后面是替代方案。

Standardizationkind
CoreBlock
/**
 * The base class for controls that can be rendered.
 *
 * @deprecated Use the new {@link Control} base class instead.
 */
export class VisualControl {
  . . .
}

@defaultValue

如果未显式分配值,则用于记录字段或属性的默认值。

⚠️:该标记只能用于作为类或接口成员的字段或属性。

Standardizationkind
ExtendedBlock
enum WarningStyle {
  DialogBox,
  StatusMessage,
  LogOnly
}

interface IWarningOptions {
  /**
   * Determines how the warning will be displayed.
   *
   * @defaultValue `WarningStyle.DialogBox`
   */
  warningStyle: WarningStyle;
}

@eventProperty

应用于「类」或者「接口属性」时,表示其返回的「事件处理程序」可以附加到事件对象上(通常具备addHandler()removeHandler())。

⚠️:「文档工具」会把该类/属性归纳到「事件」内。

Standardizationkind
ExtendedModifier
class MyClass {
  /**
    * This event is fired whenever the application navigates to a new page.
    * @eventProperty
    */
  public readonly navigatedEvent: FrameworkEvent<NavigatedEventArgs>;
}

@example

演示如何使用该Api,可能包含一个代码示例。

与该tag出现在同一行上的后续文本为示例的标题。

Standardizationkind
ExtendedBlock
/**
 * Adds two numbers together.
 * @example
 * Here's a simple example:
 * ```
 * // Prints "2":
 * console.log(add(1,1));
 * ```
 */
export function add(x: number, y: number): number {
}

@inheritDoc

用于复制另一个Apitag,甚至可以从一个不相关的package中进行复制。

⚠️:并不是所有的tags都能被复制,只有remarksparamstypeParamreturns能被复制。

Standardizationkind
ExtendedInline
import { Serializer } from 'example-library';

export interface IWidget {
  /**
   * Draws the widget on the display surface.
   * @param x - the X position of the widget
   * @param y - the Y position of the widget
   */
  public draw(x: number, y: number): void;
}

export class Button implements IWidget {
  /** {@inheritDoc IWidget.draw} */
  public draw(x: number, y: number): void {
    . . .
  }

  /**
   * {@inheritDoc example-library#Serializer.writeFile}
   */
  public save(): void {
    . . .
  }
}

@label

表示一个声明,可在TSDoc中使用「选择器」引用它。

⚠️:该tag还没有最终确定。👉 GitHub issue #9

Standardizationkind
CoreInline
export interface Interface {
  /**
   * Shortest name:  {@link InterfaceL1.(:STRING_INDEXER)}
   * Full name:      {@link (InterfaceL1:interface).(:STRING_INDEXER)}
   *
   * {@label STRING_INDEXER}
   */
  [key: string]: number;

  /**
   * Shortest name:  {@link InterfaceL1.(:CONSTRUCTOR)}
   * Full name:      {@link (InterfaceL1:interface).(:CONSTRUCTOR)}
   *
   * {@label CONSTRUCTOR}
   */
  new (hour: number, minute: number);
}

@link

表示用于创建到文档系统中其他页面或一般的url超链接。

⚠️:该tag还没有最终确定。👉 GitHub issue #9

Standardizationkind
CoreInline
/**
 * Links can point to a URL: {@link https://github.com/microsoft/tsdoc}
 */

@override @sealed @virtual

  • @override: 表示此定义覆盖父类的定义;
  • @sealed: 表示该定义不能被继承,且不得被重写;
  • @virtual:表示该定义能被继承,能被重写;

⚠️:「文档工具」强制要求virtualoverridesealed同时使用。

Standardizationkind
ExtendedModifier
class Base {
  /** @virtual */
  public render(): void {
  }

  /** @sealed */
  public initialize(): void {
  }
}

class Child extends Base {
  /** @override */
  public render(): void;
}

@packageDocumentation

表示描述整个package的文档注释。

⚠️:注释在.d.ts文件中,且不得用于描述单项Api

Standardizationkind
CoreModifier
// Copyright (c) Example Company. All rights reserved. Licensed under the MIT license.

/**
 * A library for building widgets.
 *
 * @remarks
 * The `widget-lib` defines the {@link IWidget} interface and {@link Widget} class,
 * which are used to build widgets.
 *
 * @packageDocumentation
 */

/**
 * Interface implemented by all widgets.
 * @public
 */
export interface IWidget {
  /**
   * Draws the widget on the screen.
   */
  render(): void;
}

@param

用于记录函数参数,后面是一个参数名,后面是一个连字符,最后是一个描述。

Standardizationkind
CoreBlock
/**
 * @param x - The first input number
 * @param y - The second input number
 */
function getAverage(x: number, y: number): number {
  return (x + y) / 2.0;
}

@returns

用于记录函数的返回值。

Standardizationkind
CoreBlock
/**
 * @returns The arithmetic mean of `x` and `y`
 */
function getAverage(x: number, y: number): number {
  return (x + y) / 2.0;
}

@remarks @privateRemarks

  • @remarks:表示一个简短的公开的“摘要”;
  • @privateRemarks:表示一个简短的非公开的“摘要”;
Standardizationkind
CoreBlock
/**
 * @remarks
 *
 * The main documentation for an API item is separated into a brief
 * "summary" section optionally followed by an `@remarks` block containing
 * additional details.
 *
 * @privateRemarks
 *
 * The `@privateRemarks` tag starts a block of additional commentary that is not meant
 * for an external audience.  A documentation tool must omit this content from an
 * API reference web site.  It should also be omitted when generating a normalized
 * *.d.ts file.
 */

@readonly

表示「只读」属性。

Standardizationkind
ExtendedModifier
export class Book {
  /**
   * Technically property has a setter, but for documentation purposes it should
   * be presented as readonly.
   * @readonly
   */
  public get title(): string {
    return this._title;
  }

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

@see

表示与当前Api相关的其他资源的引用列表。

⚠️:JSDocsee之后立即生成超链接文本。但TSDoc需要显式的link来生成超链接。

Standardizationkind
ExtendedBlock
/**
 * Parses a string containing a Uniform Resource Locator (URL).
 * @see {@link ParsedUrl} for the returned data structure
 * @see {@link https://tools.ietf.org/html/rfc1738|RFC 1738}
 * for syntax
 * @see your developer SDK for code samples
 * @param url - the string to be parsed
 * @returns the parsed result
 */
function parseURL(url: string): ParsedUrl;

@throws

表示可能由「函数」或「属性」引发的异常类型。

⚠️:每种异常类型都应该单独使用一个throw进行描述。

Standardizationkind
ExtendedBlock
/**
 * Retrieves metadata about a book from the catalog.
 *
 * @param isbnCode - the ISBN number for the book
 * @returns the retrieved book object
 *
 * @throws {@link IsbnSyntaxError}
 * This exception is thrown if the input is not a valid ISBN number.
 *
 * @throws {@link book-lib#BookNotFoundError}
 * Thrown if the ISBN number is valid, but no such book exists in the catalog.
 *
 * @public
 */
function fetchBookByIsbn(isbnCode: string): Book;

@typeParam

用于记录通用参数,后面是一个参数名,后面是一个连字符,最后是一个描述。

Standardizationkind
CoreBlock
/**
 * Alias for array
 *
 * @typeParam T - Type of objects the list contains
 */
type List<T> = Array<T>;

/**
 * Wrapper for an HTTP Response
 * @typeParam B - Response body
 * @param <H> - Headers
 */
interface HttpResponse<B, H> {
    body: B;
    headers: H;
    statusCode: number;
}

最后

👨‍💼:不错不错,文档写的很好!顺便把以前的文档都重写一遍吧!

🧑‍💻:卒。

参考文章:

祝大家生活愉快,工作顺利!

「 ---------- The end ---------- 」