Diem Move Prover Docgen 输出格式解析:从 TestViz 模块看不同可见性函数的文档生成
【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem
本指南以 Diem 仓库中 Move Prover Docgen 的基线测试输出 different_visbilities.spec_inline.md 为核心样本,系统拆解文档生成器(Docgen)为 Move 模块生成的 Markdown 文档结构、锚点与目录机制、函数可见性对文档内容的影响,以及对应的命令行与配置选项。读完本文,你将能读懂并复现 Docgen 生成的模块文档,理解public、public(script)、私有函数在文档中的呈现差异,并掌握如何通过源码与测试基线验证文档生成的正确性。
关联文档的定位:一份 Docgen 基线测试输出
需要先说明本文所依据的样本文档是什么。different_visbilities.spec_inline.md并不是一份手写教程,而是 Diem 仓库中Docgen 测试套件的期望输出基线(baseline):测试工具先对 Move 源文件运行文档生成器,再将生成结果与这份基线文件逐字比对,从而验证生成器行为没有回归。
相关文件均位于 language/move-prover/docgen 目录下:
- 测试输入:different_visbilities.move —— 定义了包含三种可见性函数的
TestViz模块; - 期望输出(本文主体):different_visbilities.spec_inline.md ——
specs_inlined=true、折叠实现区(<details>)模式; - 同源另两个变体:different_visbilities.spec_separate.md 与 different_visbilities.spec_inline_no_fold.md;
- 测试驱动:testsuite.rs;
- 生成器实现:docgen.rs;
- 用户手册:doc/user/docgen.md。
从测试套件 testsuite.rs 的FLAGS可以看到,Docgen 内嵌于 Move Prover 中,通过--docgen开关启用,并依赖--dependency=../../move-stdlib/modules与--dependency=../../diem-framework/modules提供标准库与框架依赖。
源头模块:TestViz 与三种函数可见性
生成这份文档的 Move 源文件很短,却刻意覆盖了 Move 语言函数可见性的三种典型形态,见 different_visbilities.move:
address 0x2 { module TestViz { /// This is a public function public fun this_is_a_public_fun() { } // /// This is a public friend function // public(friend) fun this_is_a_public_friend_fun() {} /// This is a public script function public(script) fun this_is_a_public_script_fun() {} /// This is a private function fun this_is_a_private_fun() {} } }三个被测试的函数分别是:
| 函数 | 可见性关键字 | 文档注释 |
|---|---|---|
this_is_a_public_fun | public fun | /// This is a public function |
this_is_a_public_script_fun | public(script) fun | /// This is a public script function |
this_is_a_private_fun | fun(模块私有) | /// This is a private function |
值得注意,源文件中被注释掉的public(friend) fun this_is_a_public_friend_fun是测试作者留下的痕迹,说明该用例意在对照不同可见性在文档中的渲染;由于被注释,它不会出现在生成文档中。三处///注释正是 Docgen 读取并写入文档正文的文档注释(详见后文"文档注释书写规范"一节)。
生成文档的结构逐段解读
下面以基线 different_visbilities.spec_inline.md 为准,逐段还原其完整内容并解释每一部分的生成机制。
模块锚点与一级标题
文档以 HTML 锚点与一级标题开头:
<a name="0x2_TestViz"></a> # Module `0x2::TestViz`锚点由 docgen.rs 的make_label_for_module生成:取模块全名(含地址)0x2::TestViz,将::替换为_,得到0x2_TestViz。标题级别则由 section_header 控制:section_level_start(默认 1)加上当前嵌套深度(模块层为 0),因此模块标题是单个#,而后续函数小节为##。
自动生成的模块目录
紧随标题之后是 Docgen 自动生成的目录:
- [Function `this_is_a_public_fun`](#0x2_TestViz_this_is_a_public_fun) - [Function `this_is_a_public_script_fun`](#0x2_TestViz_this_is_a_public_script_fun) - [Function `this_is_a_private_fun`](#0x2_TestViz_this_is_a_private_fun)目录条目由 gen_toc 生成,每个条目的锚点格式为{模块标签}_{条目名},即0x2_TestViz_this_is_a_public_fun等(见label_for_module_item的实现,docgen.rs)。条目的可见深度由toc_depth(默认 3)控制,函数按源文件中的出现顺序(按位置排序)依次列出。
模块使用信息代码块
目录之后是一个空代码块:
<pre><code></code></pre>这并非噪声,而是"使用信息"区域:Docgen 会枚举当前模块在字节码层面使用到的其他模块并输出use <模块>;语句(见 docgen.rs)。TestViz没有任何use声明,因此该代码块为空。如果一个模块依赖了其他模块,这里就会列出对应的use行。
函数小节:签名、文档注释与折叠的实现
每个函数生成一个##小节目录,以第一个函数为例,完整结构如下:
<a name="0x2_TestViz_this_is_a_public_fun"></a> ## Function `this_is_a_public_fun` This is a public function <pre><code><b>public</b> <b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_public_fun">this_is_a_public_fun</a>() </code></pre> <details> <summary>Implementation</summary> <pre><code><b>public</b> <b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_public_fun">this_is_a_public_fun</a>() { } </code></pre> </details>组成要素包括:
- 锚点:
<a name="0x2_TestViz_this_is_a_public_fun"></a>,供目录与交叉引用跳转; - 小节标题:
## Function \this_is_a_public_fun``; - 文档注释正文:
This is a public function,直接来自源文件中的///注释; - 函数签名代码块:由
function_header_display(docgen.rs)生成,包含可见性关键字、类型参数、参数列表与返回类型; - 折叠的实现代码块:
<details><summary>Implementation</summary>...</details>包裹原始实现源码,由begin_collapsed/end_collapsed(docgen.rs)输出。
三种可见性在文档签名中的呈现
三个函数的签名代码块完整继承如下,正是这份基线文档的核心内容:
公开函数(public fun):
<pre><code><b>public</b> <b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_public_fun">this_is_a_public_fun</a>() </code></pre> <pre><code><b>public</b> <b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_public_fun">this_is_a_public_fun</a>() { } </code></pre>公开脚本函数(public(script) fun):
<pre><code><b>public</b>(<b>script</b>) <b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_public_script_fun">this_is_a_public_script_fun</a>() </code></pre> <pre><code><b>public</b>(<b>script</b>) <b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_public_script_fun">this_is_a_public_script_fun</a>() {} </code></pre>私有函数(fun,无修饰符):
<pre><code><b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_private_fun">this_is_a_private_fun</a>() </code></pre> <pre><code><b>fun</b> <a href="different_visbilities.md#0x2_TestViz_this_is_a_private_fun">this_is_a_private_fun</a>() {} </code></pre>可见性字符串由func_env.visibility_str()提供(在function_header_display中拼接到fun之前),因此public、public(script)会原样出现在签名中,私有函数则没有任何前缀。<b>标签是 Docgen 的代码装饰(关键词加粗)结果,<a href="different_visbilities.md#...">则是自动生成的交叉引用——指向该模块自身输出文件different_visbilities.md中的对应锚点(详见下文"交叉引用"一节)。
可见性过滤:私有函数何时进入文档
值得注意的是,私有函数this_is_a_private_fun在默认情况下不一定会出现在生成的文档中。Docgen 通过 gen_module 中的过滤器控制:
let funs = module_env .get_functions() .filter(|f| self.options.include_private_fun || f.is_exposed()) .sorted_by(|a, b| Ord::cmp(&a.get_loc(), &b.get_loc())) .collect_vec();即:只有include_private_fun为真、或函数本身是暴露(公开)的,才会进入文档。这正是本测试用例专门安排一个私有函数的原因——它用于验证"私有函数也能被文档化"这条路径。
对应到用户手册 doc/user/docgen.md 中的命令行开关:
| 开关 | 含义 | 用户手册标注的默认值 |
|---|---|---|
--doc-include-private=true\|false | 是否包含私有函数 | false |
但需要提醒:代码层面DocgenOptions的Default实现(docgen.rs)中include_private_fun: true,而测试套件也显式设置了options.docgen.include_private_fun = true(testsuite.rs)。文档与代码默认值存在差异,因此实际使用时建议显式传参,不要依赖默认行为。
交叉引用与代码装饰机制
基线文档中每个标识符都被加工过,这正是 Docgen 区别于普通 Markdown 渲染器的关键能力,实现在 docgen.rs:
- 关键字加粗:
public、fun、script等 Move 关键字(含WEAK_KEYWORDS中的 spec 语言关键字)被包裹为<b>...</b>; - 标识符超链接:
resolve_to_label采用启发式解析——优先尝试0xN::Module::item形式的全限定名,也支持Self::item、Module::item与裸名;裸名只有后跟(或<(函数调用/泛型实例化)时才解析为函数引用,以避免把字段名误链为函数; - HTML 转义:
<、>分别转义为<、>,保证签名在<pre><code>块内正确显示; - 跨文件引用:
ref_for_module(docgen.rs)将引用渲染为目标文件#锚点形式。对 TestViz 而言,其输出文件由compute_output_file计算——源文件different_visbilities.move的扩展名替换为.md(docgen.rs),于是所有引用都指向different_visbilities.md#0x2_TestViz_...。
三种基线变体:inline / separate / no-fold
同一个测试输入在 testsuite.rs 中会以三组不同选项各生成一份基线:
| 基线文件 | specs_inlined | collapsed_sections | 差异点 |
|---|---|---|---|
different_visbilities.spec_inline.md | true | true | 实现区用<details>/<summary>折叠 |
different_visbilities.spec_separate.md | false | true | spec 独立成节;因本模块无 spec,输出与 inline 几乎一致 |
different_visbilities.spec_inline_no_fold.md | true | false | 折叠关闭,实现区标题渲染为##### Implementation |
对比三个文件可验证collapsed_sections的具体行为:开启时begin_collapsed输出<details><summary>Implementation</summary>(docgen.rs),关闭时则退化为硬编码的##### Implementation五级标题。由于TestViz没有任何spec块,specs_inlined的差异在这里没有体现——若模块带 spec,inline 模式会把规范内联到对应声明的小节下,separate 模式则汇总到文档末尾独立的 Specification 章节(见 gen_spec_section)。
运行 Docgen 生成此类文档
若要在自己的 Move 源码上复现同样的文档,按用户手册 doc/user/docgen.md 中的方式调用:
cargo run -p move-prover -- --docgen <flags> .. <sources>常用参数:
-d=<path>:Move 依赖的搜索路径(编译用);--doc-path=<path>:已生成文档的搜索路径(用于交叉引用);--doc-spec-inline=true|false:spec 是内联到声明处(true,默认)还是汇总到文末独立章节(false);--doc-include-impl=true|false:是否包含函数实现体,默认 true;--doc-include-private=true|false:是否包含私有函数,默认 false(与源码Default存在差异,建议显式指定);--output=<path>:生成文档的输出路径;- 完整选项可通过
cargo run -p move-prover -- --help查看。
若想直接参与 Docgen 的验证,可运行 Docgen 目录下的测试套件(datatest_stable会扫描 tests/sources 下所有*.move与*_template.md),并用verify_or_update_baseline机制比对或更新基线(testsuite.rs)。注意:仓库为只读环境,这里仅说明查看与验证方式,不建议在仓库内直接改动基线。
文档注释书写规范与 Spec 块归属
最后,若希望自己写的模块也能生成类似 TestViz 这样结构清晰的文档,需要遵循 Docgen 对注释与 spec 块的约定(详见 doc/user/docgen.md):
- 文档注释:以
///或/** ... */开头,必须置于被注释项之前;可作用于模块、结构体、结构体字段、函数、spec 块及 spec 块成员;连续多段注释会合并为一个文档块; - 注释内 Markdown:支持任意 Markdown(建议兼容 GitHub 风格),也可使用
#小节标题,标题会自动降级嵌入当前上下文层级之下; - 代码引用:注释中的
foo不会解析为当前模块的函数,除非写成foo()或Self::foo; - Spec 块归属:无明确目标的 schema 与
spec module块会归属于其之前最近的函数/结构体 spec 块,否则归属于模块;可在函数 spec 之后放一个空spec module {}强制将后续 spec 归属回模块级,例如:
module M { fun f(): T { ... } spec f { aborts_if f_aborts(); ensures result == f_result(); } // 以下内容跟随函数 f 的文档 spec fun f_aborts() { .. } spec module {} // 以下内容跟随模块文档 spec fun f_result(): T { .. } }小结
通过 different_visbilities.spec_inline.md 这一基线样本,可以看到 Diem Move Prover Docgen 的完整输出契约:模块锚点与标题、自动目录、使用信息、函数签名与文档注释、折叠实现、关键字加粗与交叉引用。结合 docgen.rs 的实现与 testsuite.rs 的三种选项组合,可以准确推断任何模块文档的生成规则——包括public、public(script)与私有函数在文档中的不同呈现,以及--doc-include-private、--doc-spec-inline、collapsed_sections等选项如何改变最终输出。对于维护 Move 生态工具链或需要为 Move 模块生成规范 API 文档的开发者,这份基线与其配套源码是理解 Docgen 行为最直接的参考实现。
【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考