Go go/doc.Filter 怎样从文档树中移除未导出声明
来源:17golang原创
时间:2026-10-13 05:05:01 178浏览 收藏
做 Go 文档生成器时,我遇到过一个很具体的分叉:同一份 *doc.Package 既要支持内部 API 视图,也要支持对外发布的公开 API 视图。继续手写“哪些声明保留”的循环,很快会漏掉常量、变量、类型和关联方法中的某一类。
这时可以把过滤规则交给 go/doc:定义一个接收名称的 doc.Filter,再调用 Package.Filter。它会按名称裁剪包级文档节点,但有两个容易被忽略的边界:调用后包级 Doc 会被清空;如果类型名称本身命中过滤器,该类型的成员不会再自动逐个过滤。
如果目标是“只展示公开声明”,最小做法是用ast.IsExported作为doc.Filter,在渲染公开文档前调用一次Package.Filter;如果还要隐藏类型内部成员,就必须在自己的渲染层继续做字段和方法策略。
先看 Filter 改动了哪些节点
go/doc 面向的是已经从 Go AST 提取出来的文档结构。官方定义的过滤器本质上是 func(string) bool:返回 true 的名称通过,返回 false 的名称被排除。Package.Filter 会处理包级常量、变量、类型和函数集合,并把筛选结果写回传入的 Package。

这意味着它更像“准备渲染数据”的一步,而不是独立的只读查询。调用前后使用的是同一个对象,过滤完成后不应再把它当成完整文档树使用。
定义一个真正可复用的导出名称过滤器
不要把规则写成 name[0] >= 'A' && name[0] 。Go 标识符可以使用 Unicode,直接取第一个字节会把非 ASCII 的导出判断做错。标准库已经提供了匹配 Go 标识符导出规则的辅助函数,适合直接传给 doc.Filter。
package main
import (
"fmt"
"go/ast"
"go/doc"
)
// exportedOnly 返回 true 表示名称应出现在公开文档中。
func exportedOnly(name string) bool {
// ast.IsExported 遵循 Go 的 Unicode 导出规则,不要手写 ASCII 判断。
return ast.IsExported(name)
}
func filterPublicDocs(pkg *doc.Package) {
// Filter 会原地裁剪 pkg;调用后不要再把它当作完整文档树。
pkg.Filter(doc.Filter(exportedOnly))
fmt.Printf("公开函数数量:%d\\n", len(pkg.Funcs))
}
这里没有创建新的过滤器类型,普通函数就足够满足 doc.Filter 的函数签名。若项目需要特殊公开策略,也可以在这个函数里加入命名白名单,但要明确那已经不是“所有导出声明”了。
把过滤放到文档生成流程的正确位置
通常先用 doc.NewFromFiles 或 doc.New 得到完整的 Package,再根据输出目标选择是否过滤。内部检索、交叉引用和诊断需要完整树;公开 API 文档、对外索引或精简导航则适合使用过滤后的视图。
func publicPackage(fset *token.FileSet, files []*ast.File, importPath string) (*doc.Package, error) {
// 先构造完整文档,保留 _test.go 中可关联的示例信息。
pkg, err := doc.NewFromFiles(fset, files, importPath)
if err != nil {
return nil, fmt.Errorf("生成文档包失败:%w", err)
}
// 公开输出只保留通过 ast.IsExported 的包级声明。
pkg.Filter(doc.Filter(ast.IsExported))
return pkg, nil
}
上面的片段需要同时导入 fmt、go/ast、go/doc 和 go/token。真正的工程里,最好在函数注释中写清返回对象已经是“公开视图”,避免调用方误以为它仍包含未导出声明。
最容易误判的边界:类型命中后成员不会自动裁剪
过滤器的名称匹配不是递归的“每个字段都检查一遍”。类型名称本身通过过滤器时,go/doc 会保留这个类型文档;这条路径不会继续把它的字段、方法和关联声明逐个过滤。
反过来,如果类型名称没有通过,但它关联的常量、变量、构造函数或方法中有名称通过,类型节点仍可能作为承载这些匹配声明的容器留下。也就是说,判断结果不能只看 len(pkg.Types),还要看类型名是否命中以及类型下的成员集合。

因此,“公开 API 文档”有两种常见目标:
| 目标 | 推荐做法 | 需要注意 |
|---|---|---|
| 公开声明导航 | Package.Filter(ast.IsExported) | 接受类型命中后的成员保留语义 |
| 严格隐藏内部成员 | 先过滤包级节点,再在渲染类型时检查字段和方法 | 这是渲染策略,不是 Filter 单独完成的工作 |
| 内部完整文档 | 保留未过滤的原始 Package | 不要把过滤后的对象回写到共享缓存 |
调用后包级 Doc 为什么会变空
Package.Filter 的行为还包括把包级 Doc 设为空。这对公开索引有利:包简介不会在过滤后的文档树中继续作为完整包说明出现。但如果页面还需要包简介,就不能在过滤之后临时期待它仍然存在。
更稳妥的流程是先保存需要展示的摘要,或者保留一份完整 Package,再对用于公开渲染的对象做过滤:
type PublicDocs struct {
Summary string
Package *doc.Package
}
func preparePublicDocs(full *doc.Package) PublicDocs {
// 先保存包简介,因为 Filter 会清空 full.Doc。
summary := full.Doc
// 当前示例只复制顶层指针;若后续还要保留完整树,应使用独立的深拷贝策略。
full.Filter(doc.Filter(ast.IsExported))
return PublicDocs{Summary: summary, Package: full}
}
这里的关键不是为了“修复” Filter,而是把副作用纳入数据生命周期设计:完整树和公开树是两个用途不同的视图。共享缓存、异步渲染或多次导出场景下,最好在架构层明确谁拥有并修改 *doc.Package。
把这个能力当作文档流水线的一个阶段
从工程角度看,go/doc.Filter 解决的是一个长期存在的切换问题:同一套解析和文档模型,可以按发布目标选择“全量”或“公开”。它的收益是减少重复遍历和手写漏项;代价是原地修改、包注释清空以及类型成员边界需要额外处理。
适合采用它的角色包括自动生成 API 目录的工具维护者、需要同时发布内部和外部文档的团队,以及希望用标准库保持过滤规则一致的代码生成器作者。若只是一次性检查源码访问控制,直接在 AST 层分析更合适,不应把 Package.Filter 当成通用权限分析器。
发布前的观察清单
- 过滤器是否只接收名称并返回布尔值,且导出判断没有被 ASCII 规则限制?
- 调用前是否已经决定要不要保留包级
Doc? - 是否明确类型名称命中后成员不会再自动逐个隐藏?
- 公开渲染是否使用了过滤后的 Package,而内部渲染仍保留完整视图?
- 是否把原地修改写进了缓存或并发共享对象的生命周期说明?
把这五项放进文档生成器的设计记录,通常比再写一套“移除未导出声明”的遍历更可靠。go/doc.Filter 的价值就在于它把常见的包级筛选交给标准库,同时把真正需要产品决策的部分——类型成员展示、摘要保存和对象所有权——清楚地留给调用方。
相关问题
go/doc.Filter 会修改原来的 Package 吗?
会。它会原地重排并截短包级集合,并把包级 Doc 清空;需要保留完整视图时,应先保存或复制。
用 ast.IsExported 能处理中文导出名称吗?
它按 Go 的标识符导出规则判断,不应自行用第一个字节比较 ASCII 大写字母。
为什么隐藏类型没有被完全删掉?
类型名未命中时,只要其关联声明中还有名称命中,类型仍可能作为容器保留。要严格控制展示内容,需要在渲染层继续处理成员。
本文依据 Go 官方 go/doc 文档对 Filter 和 Package.Filter 的说明,示例聚焦文档树筛选,不把它扩展成 AST 访问控制分析。
-
313 收藏
-
151 收藏
-
230 收藏
-
352 收藏
-
236 收藏
-
261 收藏
-
417 收藏
-
250 收藏
-
341 收藏
-
360 收藏
-
180 收藏
-
166 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习