深入解读 Roc 包文档生成:私有模块与私有类型声明的隐藏机制(Issue 10077)
2026/9/17 23:28:20 网站建设 项目流程

深入解读 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.zigPackageDocs的实现,完整解读package-docs的 S-expression 输出结构、模块可见性规则,以及如何用快照工具复现与验证这一行为。

快照文档的三个组成部分

该文档位于 test/snapshots/docs_package_hides_private_modules.md,采用test/snapshots/目录下所有快照的统一格式,由三个区块构成:

  1. META:以 ini 格式声明快照的元信息,description说明本条快照验证的目标——Issue 10077:包文档会隐藏私有模块与私有类型声明;type=docs表明它属于文档生成(docs)类快照。
  2. SOURCE:编译器的输入,即一组 Roc 源文件(main.rocDate.rocTime.rocUtil.roc)。
  3. 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 :: {}

UtilDate.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模块名DateTime
package模块所属包作用域"mod"
kind模块种类type_mod(类型模块)
doc模块级文档注释"A time of day."
entry模块暴露的条目类型模块本身及其嵌套条目

注意:test/snapshots/README.md中提到,快照后处理会将移除的 header 关键字统一改写为mod,因此 S-expression 内部也统一使用mod表示模块节点。

entry节点:类型与嵌套声明

  • Date模块的entry描述了类型模块Datekindopaque(不透明类型),type"Date :: " (record)doc为模块文档。
  • Format作为嵌套entry出现在Date条目之下,同样为opaque类型,type"Format :: " (record)
  • Time模块的entry结构相同,doc与模块级doc一致(模块文档即类型文档)。

关键观察:什么被隐藏了

对照 SOURCE 与 DOCS 可以发现三处隐藏:

  1. Util模块整体消失:尽管Date.rocimport Util,由于Util不在导出清单[Date, Time]中,整个mod节点都不存在。
  2. Date中的Help声明消失Help是模块级私有声明(::定义的私有类型),尽管有文档注释,仍不出现在Date的条目中。
  3. DateUtil的 import 关系不体现:文档只描述公开 API 形状,不记录内部依赖。

这与同目录下的兄弟快照互为印证:docs_platform_hides_internal_modules.md验证了platform文档只输出exposes中列出的模块(如Stdout),而内部宿主边界模块Host被隐藏;docs_transitive_modules.md则展示了 app 中通过import可达的模块(GeometryHelpers)反而会进入文档。二者共同勾勒出 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持有namemodules列表,mod节点即模块列表中的一项。
  • 每个模块条目携带namepackagekinddoc与嵌套entry,序列化顺序与快照 DOCS 区块严格一致。
  • writeToSExprIndented负责递归缩进输出,快照中的(package-docs根节点即由此产生。
  • 同文件中的resolveDocRefsreshapeBuiltin(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 逐字节比对。一旦文档构建逻辑出现改动(例如意外泄露了私有模块UtilHelp,或改变了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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询