深入解读 Roc 包文档生成:私有模块与私有类型声明的隐藏机制(Issue 10077)
【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc
导读
Roc 编译器在为package生成 API 文档时,只会暴露包声明中列出的模块,而模块内部的私有类型声明、被间接import的辅助模块都会被一并隐藏。本文以仓库中的快照测试文档test/snapshots/docs_package_hides_private_modules.md为骨架,结合src/docs/DocModel.zig中PackageDocs的实现,完整解读package-docs的 S-expression 输出结构、模块可见性规则,以及如何用快照工具复现与验证这一行为。
快照文档的三个组成部分
该文档位于 test/snapshots/docs_package_hides_private_modules.md,采用test/snapshots/目录下所有快照的统一格式,由三个区块构成:
- META:以 ini 格式声明快照的元信息,
description说明本条快照验证的目标——Issue 10077:包文档会隐藏私有模块与私有类型声明;type=docs表明它属于文档生成(docs)类快照。 - SOURCE:编译器的输入,即一组 Roc 源文件(
main.roc、Date.roc、Time.roc、Util.roc)。 - DOCS:期望输出,即文档模型序列化后的 S-expression(
package-docs),由src/docs/DocModel.zig中的PackageDocs.writeToSExprIndented生成。
description=Issue 10077: package docs hide private mods and private type declarations type=docs被测源码:一个导出[Date, Time]的最小包
SOURCE 部分构造了一个仅导出两个模块的package:
包入口main.roc
package [Date, Time] {}这是 Roc 的package头部语法:方括号内是对外导出的模块清单,{}为包的空配置。这一行是整个可见性规则的"总开关"——文档生成器只会为清单中的模块生成文档条目。
类型模块Date.roc
import Util ## A calendar date. Date :: {}.{ ## Date formatting options. Format :: {} } ## Private implementation details. Help :: {}这里展示了两个关键特性:
Date是类型模块(type module),使用::定义。外层{}给出类型的形状(一个记录),内部{}承载模块成员。Format是嵌套在Date内的嵌套类型声明,同样以::定义并附带文档注释,在文档输出中会成为Date条目下的子条目。Help是与Date平级的模块级声明,文档注释明确标注"Private implementation details."(私有实现细节)。它不在导出行列,因此不会出现在生成的文档中。
公开类型模块Time.roc
## A time of day. Time :: {}Time是被导出的第二个模块,::定义了一个空记录形状的类型模块。
被间接引用的私有模块Util.roc
## Private utilities used by Date. Util :: {}Util被Date.roc通过import Util引用,但它不在package的导出清单中。这正是本快照的核心验证点之一:即使一个模块被公开模块内部依赖,只要它未被导出,生成的文档就必须将其完全隐藏。
期望输出:package-docsS-expression 结构
DOCS 区块给出了上述源码对应的期望文档序列化结果:
(package-docs (name "test-app") (mod (name "Date") (package "mod") (kind type_mod) (entry (name "Date") (kind opaque) (type "Date :: " (record)) (doc "A calendar date.") (entry (name "Format") (kind opaque) (type "Format :: " (record)) (doc "Date formatting options.") ) ) ) (mod (name "Time") (package "mod") (kind type_mod) (doc "A time of day.") (entry (name "Time") (kind opaque) (type "Time :: " (record)) (doc "A time of day.") ) ) )顶层结构
(package-docs ...):整个文档模型的根节点,对应src/docs/DocModel.zig中的PackageDocs结构(DocModel.zig),其writeToSExprIndented方法以(package-docs\n起始输出。(name "test-app"):文档归属的包名。
mod节点:模块级信息
每个模块对应一个mod节点,包含:
| 字段 | 含义 | 本快照中的取值 |
|---|---|---|
name | 模块名 | Date、Time |
package | 模块所属包作用域 | "mod" |
kind | 模块种类 | type_mod(类型模块) |
doc | 模块级文档注释 | 如"A time of day." |
entry | 模块暴露的条目 | 类型模块本身及其嵌套条目 |
注意:test/snapshots/README.md中提到,快照后处理会将移除的 header 关键字统一改写为mod,因此 S-expression 内部也统一使用mod表示模块节点。
entry节点:类型与嵌套声明
Date模块的entry描述了类型模块Date:kind为opaque(不透明类型),type为"Date :: " (record),doc为模块文档。Format作为嵌套entry出现在Date条目之下,同样为opaque类型,type为"Format :: " (record)。Time模块的entry结构相同,doc与模块级doc一致(模块文档即类型文档)。
关键观察:什么被隐藏了
对照 SOURCE 与 DOCS 可以发现三处隐藏:
Util模块整体消失:尽管Date.roc中import Util,由于Util不在导出清单[Date, Time]中,整个mod节点都不存在。Date中的Help声明消失:Help是模块级私有声明(::定义的私有类型),尽管有文档注释,仍不出现在Date的条目中。Date对Util的 import 关系不体现:文档只描述公开 API 形状,不记录内部依赖。
这与同目录下的兄弟快照互为印证:docs_platform_hides_internal_modules.md验证了platform文档只输出exposes中列出的模块(如Stdout),而内部宿主边界模块Host被隐藏;docs_transitive_modules.md则展示了 app 中通过import可达的模块(Geometry、Helpers)反而会进入文档。二者共同勾勒出 Roc 文档可见性的完整规则:package/platform 的暴露清单决定文档边界,import 关系不构成暴露依据。
源码印证:PackageDocs如何承载这些结构
文档模型的实现位于 src/docs/DocModel.zig:
pub const PackageDocs = struct { // ... pub fn writeToSExpr(self: *const PackageDocs, writer: anytype) (Allocator.Error || error{WriteFailed})!void { // ... pub fn writeToSExprIndented(self: *const PackageDocs, writer: anytype, depth: usize) (Allocator.Error || error{WriteFailed})!void { try writer.writeAll("(package-docs\n");PackageDocs持有name与modules列表,mod节点即模块列表中的一项。- 每个模块条目携带
name、package、kind、doc与嵌套entry,序列化顺序与快照 DOCS 区块严格一致。 writeToSExprIndented负责递归缩进输出,快照中的(package-docs根节点即由此产生。- 同文件中的
resolveDocRefs与reshapeBuiltin(DocModel.zig)负责文档引用解析与内置类型重塑;注释还提到由reshapeBuiltin合成的模块具有特殊标记(DocModel.zig)。从源码结构看,文档构建过程只遍历暴露模块树,私有模块与私有声明在模型构造阶段即被过滤,快照 DOCS 中看不到它们正是该过滤逻辑的端到端证据。 - 序列化后的
PackageDocs还会被 src/docs/render_html.zig 消费,用于生成独立的 HTML 文档站点,说明该 S-expression 是后续多格式文档渲染的统一中间表示。
如何复现与更新这条快照
快照测试由src/snapshot_tool/main.zig驱动,test/snapshots/README.md 给出了完整的操作方式:
# 生成/刷新所有快照 zig build run-snapshot-tool # 只处理指定快照文件 zig build run-snapshot-tool -- test/snapshots/docs_package_hides_private_modules.md # 用当前编译器输出更新期望结果(谨慎使用) zig build run-snapshot-tool -- test/snapshots/docs_package_hides_private_modules.md --update-expected快照机制的价值在于端到端回归检测:type=docs快照将编译器实际生成的PackageDocs序列化结果与期望 S-expression 逐字节比对。一旦文档构建逻辑出现改动(例如意外泄露了私有模块Util、Help,或改变了opaque的呈现方式),zig build run-snapshot-tool就会立即报告差异,从而守住"公开文档只含公开 API"这一约定。若要在新的修改中增加类似的可见性用例,可在test/snapshots/下仿照本文档创建META+SOURCE+DOCS三段式文件,再运行快照工具生成期望输出。
小结
docs_package_hides_private_modules.md以最小化的 4 个源文件精准锚定了 Roc 包文档生成中的两条硬规则:未列入package导出清单的模块(无论是否被 import)一律不出现在文档中;公开类型模块内部的私有类型声明同样被过滤。配合PackageDocs的 S-expression 输出与快照工具的回归守护,Roc 保证了package文档始终与exposes声明严格一致——这对语言工具链的 API 文档可靠性而言,是一项值得借鉴的测试设计。
【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考