作者
José Valim <jose(点)valim(在)gmail(点)com>
状态
最终/27.0 在 OTP 版本 27 中实现
类型
标准跟踪
创建
2021-06-02
发布历史
https://github.com/erlang/otp/pull/7936 https://github.com/erlang/otp/pull/8165 https://github.com/erlang/otp/pull/8127 https://github.com/erlang/otp/pull/8123 https://github.com/erlang/otp/pull/8102 https://github.com/erlang/otp/pull/8089 https://github.com/erlang/otp/pull/8063 https://github.com/erlang/otp/pull/8026 https://github.com/erlang/otp/pull/7999 https://github.com/erlang/otp/pull/7959 https://github.com/erlang/otp/pull/7958

EEP 59: 用于文档的模块属性 #

摘要 #

此 EEP 提议为 Erlang 提供一个结构化的文档 API,其中文档作为语言解析器的一部分进行处理,并直接包含在编译后的 .beam 文件中,以替代 EDoc 样式的注释。Python、Elixir 和 Clojure 是将文档视为数据而不是代码注释的语言示例。

理由 #

目前 EDoc 的主要限制在于文档保留为代码注释。这需要一个显式工具来解析这些代码注释,从而使 IDE、shell 等访问文档变得复杂。最近,通过使 EDoc 编译为 EEP 48,在这方面取得了一些进展,但这仍然需要一个显式的步骤。

此外,“代码注释”方法在实现方面更加复杂,因为它需要解析源代码以及代码注释,解析代码注释等等。在文档和代码注释之间进行明确区分也是有益的:它们有不同的要求和不同的受众。

此 EEP 提议向 Erlang 添加两个模块属性:-doc-moduledoc

EEP 48 一样,此提案仅涉及 API 参考及其文档。它不涵盖指南、教程和其他文档格式。

新模块属性 #

此 EEP 提议两个新属性:-doc-moduledoc。它们可以按如下方式使用

-module(base64).
-moduledoc "
Convenience functions for encoding and decoding from base64.
".

-doc "
Encodes the given binary to base64.
".
-spec encode(binary()) -> binary().
encode(Binary) ->
  % ....

-doc "
Decodes the given binary from base64.
".
-spec decode(binary()) -> {ok, binary()} | error.
encode(Binary) ->
  % ....

新的 -moduledoc 属性可以列在任何位置,它将包含给定模块的文档。-doc 属性必须列在函数、类型属性或回调属性之前的任何位置,它将包含以下函数、类型或回调的文档。例如,下面的示例

-doc "Example".
-spec example() -> ok.
example() -> ok.

等效于

-spec example() -> ok.
-doc "Example".
example() -> ok.

为同一函数列出多个带有字符串值的 -doc 属性应该会发出警告或错误,除非文档被设置为隐藏。例如,这是有效的

-doc "Example".
-doc hidden.
example() -> ok.

但这应该发出警告/错误

-doc "Example".
-doc "Updated example".
example() -> ok.

这也是如此

-doc "Example".
example(one) -> 1;
-doc "Updated example".
example(two) -> 2;

隐藏文档 #

模块属性必须是字符串或原子 false。将模块标记为隐藏意味着它不会成为文档的一部分。例如,假设上面的 base64 模块将其部分逻辑委托给私有的 base64_impl 模块

-module(base64_impl).
-moduledoc false.

请注意,模块可能被隐藏,但仍然可以记录单个函数

-module(base64_impl).
-moduledoc false.

-doc "
Some comments as if it was public.
".
decode64(Binary) ->
  % ...

根据 EEP 48,这是故意的。例如,base64_impl 对于 base64 功能的用户应该是私有的,但是直接在 base64 上工作的开发人员可能仍然希望直接从其 IDE 访问 base64_impl 函数的文档。每个文档工具都应相应地遵守 hidden。如果没有提供 -doc,则根据 EEP 48,它默认为 none

-doc 属性也接受 false 原子。

文件/包含文档 #

一些开发人员倾向于不将文档与源代码放在一起。对于这种情况,-doc-moduledoc 还可以提供 {file, Path},其中 Path 是包含 doc 属性的文件的相对路径

-moduledoc({file, "../doc/src/manual/my_module.asciidoc"}).
-doc({file, "../doc/src/manual/my_module.my_function.asciidoc"}).

编译器将在编译时读取该文件并将其嵌入到块中。

回调和类型 #

-doc 属性也可用于记录类型和回调。

私有函数 #

-doc 属性也可以用于私有函数,以便工具和 IDE 可以在用户需要时提供文档。但是,私有函数不应出现在 EEP 48 文档块中。

私有类型 #

通常,私有类型的处理方式与私有函数相同,但是有时类型用于模块内的文档和代码共享目的,但用户不希望将其导出以供通用使用。一个例子是 ssl 模块中的所有各种选项类型。

因此,公共函数规范或类型引用的任何私有类型也将包含在文档块中。此类类型的 exported 元数据键将设置为 false

元数据 #

新的模块属性还必须通过传递一个 map 作为参数来支持文档元数据

-module(beam64).
-moduledoc "
Convenience functions for encoding and decoding from base64.
".
-moduledoc #{
  author => [<<"The Erlang/OTP team">>],
  license => <<"Apache 2 License">>,
  cross_references => [binary]
}.

如果多次使用 map 调用 -moduledoc,则将合并这些 map。这带来了一个额外的好处,即可以将共享元数据移动到头文件中

%% prelude.hrl
-moduledoc #{
  authors => [<<"The Erlang/OTP team">>],
  license => <<"Apache 2 License">>
}.

然后我们可以包含并增强它

-module(beam64).
-include("prelude.hrl").
-moduledoc "
Convenience functions for encoding and decoding from base64.
".
-moduledoc #{cross_references => [binary]}.

可在 EEP 48 上找到内置属性的列表。

编译 #

使用 -moduledoc-doc 属性编译模块将在其 .beam 文件中生成一个 Docs 块,从而可以在 shell 中直接访问该文档。

默认情况下,发布工具也应从 .beam 文件中修剪掉文档块。请注意,beam_lib:strip_release/1beam_lib:strip_files/1 已经完成了此操作。

warn_missing_doc #

如果包含在 EEP 48 文档块中的函数、类型或回调没有设置 -doc 属性,则编译器将发出警告,前提是将 warn_missing_doc 标志传递给编译器。

该标志既可以在命令行上作为 +warn_missing_doc 传递,也可以作为源代码的 -compile(warn_missing_doc) 属性传递。

文档格式 #

关于文档的一个重要讨论是文档应采用哪种文档格式。幸运的是,EEP 48 对格式不可知,但是仍然必须列出一种格式。

为了方便多种文档格式,Erlang/OTP 允许用户在 -moduledoc 元数据中放置一个 format 键,该键应指定所用格式的 mime 类型,如 EEP 48 所指定。默认格式将为 text/markdown

-moduledoc(#{ format => "text/edoc" }).

其他主题 #

Doctests #

使文档更结构化和可访问的直接结果是 Erlang 可以包含 doctests,这是在文档中运行和验证示例的能力。例如,有人可以这样写

-doc """
Encodes the given binary to base64.

1> base64:encode("hello").
<<"aGVsbG8=">>

""".
-spec encode(binary()) -> binary().
encode(Binary) ->
  % ....

然后在您的测试套件中

doctests(_Config) ->
  ct_doctest:run(base64).

doctest 属性将访问 base64 Docs 块中的文档条目,提取所有示例并运行它们。当然,虽然没有什么可以阻止在今天的 EDoc 之上实现 doctest,但此 EEP 使 doctest 的实现变得相当简单。

Doctests 将受益于单独的 EEP,因为有一些额外的考虑因素,例如 doctesting 异常、不可解析的格式等,但考虑到它们对用户和文档作者的好处,值得一提。

EDoc 和 erl_docgen 怎么样? #

如果此提案被接受,Edoc 会发生什么?

EEP 提出的工作的一个重要方面是尝试统一 Erlang/OTP 生态系统中的文档工具。在此之前,存在多个工具,EDoc 主要由开源项目使用,而 erl_docgen 由 Erlang/OTP 项目和其他第三方解决方案使用。

在短期内,将更新 EDoc,以便能够从包含 text/edoc 文档的 EEP 48 文档块生成 html 报告。指定文档的 EDoc 注释样式将被弃用,但是为了向后兼容,对 EDoc 注释的解析支持将保留很长时间。

从长远来看,目标是将文档渲染引擎切换为使用 ExDoc,后者可以通过将其作为 escript 运行或通过 Rebar3 集成来支持 Erlang 项目。使用 ExDoc 时,用户可以选择使用 EDoc 语法或迁移到 Markdown。

同样,Erlang/OTP 代码将转换为使用 ExDoc 而不是 erl_docgen 来生成文档。所有当前的 XML 文档文件都将转换为使用 Markdown。

版权 #

本文档置于公共领域或 CC0-1.0-Universal 许可之下,以两者中更宽松的为准。