当前位置:首页 > 文章列表 > Golang > Go问答 > Go go/doc.Filter 怎样从文档树中移除未导出声明

Go go/doc.Filter 怎样从文档树中移除未导出声明

来源:17golang原创 2026-10-13 05:05:01 0浏览 收藏

做 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。

doc.Package 经过 Filter 名称谓词后形成公开 API 视图的结构说明图
图1:结构说明图,go/doc.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),还要看类型名是否命中以及类型下的成员集合。

Go go/doc.Filter 类型命中与方法命中两条过滤路径的边界说明图
图2:边界说明图,类型名称一旦命中过滤器,go/doc.Filter 不会再对该类型成员做第二轮名称裁剪。

因此,“公开 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 当成通用权限分析器。

发布前的观察清单

  1. 过滤器是否只接收名称并返回布尔值,且导出判断没有被 ASCII 规则限制?
  2. 调用前是否已经决定要不要保留包级 Doc?
  3. 是否明确类型名称命中后成员不会再自动逐个隐藏?
  4. 公开渲染是否使用了过滤后的 Package,而内部渲染仍保留完整视图?
  5. 是否把原地修改写进了缓存或并发共享对象的生命周期说明?

把这五项放进文档生成器的设计记录,通常比再写一套“移除未导出声明”的遍历更可靠。go/doc.Filter 的价值就在于它把常见的包级筛选交给标准库,同时把真正需要产品决策的部分——类型成员展示、摘要保存和对象所有权——清楚地留给调用方。

相关问题

go/doc.Filter 会修改原来的 Package 吗?

会。它会原地重排并截短包级集合,并把包级 Doc 清空;需要保留完整视图时,应先保存或复制。

用 ast.IsExported 能处理中文导出名称吗?

它按 Go 的标识符导出规则判断,不应自行用第一个字节比较 ASCII 大写字母。

为什么隐藏类型没有被完全删掉?

类型名未命中时,只要其关联声明中还有名称命中,类型仍可能作为容器保留。要严格控制展示内容,需要在渲染层继续处理成员。

本文依据 Go 官方 go/doc 文档对 Filter 和 Package.Filter 的说明,示例聚焦文档树筛选,不把它扩展成 AST 访问控制分析。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go runtime/coverage.WriteCountersDir 生成的计数文件如何配合分析Go runtime/coverage.WriteCountersDir 生成的计数文件如何配合分析
上一篇
Go runtime/coverage.WriteCountersDir 生成的计数文件如何配合分析
Go go/doc.ToText 如何把注释格式化成终端文本
下一篇
Go go/doc.ToText 如何把注释格式化成终端文本
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    448次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    519次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    535次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    480次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    304次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码