go/doc - 包文档提取
go/doc 包提供了从 Go AST 中提取文档注释的功能,用于生成包的文档信息。
概述
go/doc 包用于从 Go 源代码的 AST 中提取和组织文档信息,是 godoc 工具的核心组件。
包导入:
import (
"go/doc"
"go/ast"
"go/token"
"fmt"
)
基本使用:
// 1. 解析源文件
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "main.go", src, parser.ParseComments)
// 2. 提取包文档
pkgDoc := doc.New(file, "main", doc.AllDecls)
// 3. 访问文档信息
fmt.Printf("包名:%s\n", pkgDoc.Name)
fmt.Printf("文档:%s\n", pkgDoc.Doc)
fmt.Printf("函数数量:%d\n", len(pkgDoc.Funcs))
典型示例:
示例 1:提取包的完整文档:
package main
import (
"fmt"
"go/ast"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
// Package math 提供基本的数学常量和函数。
package math
// Pi 是圆周率。
const Pi = 3.14159
// E 是自然对数的底。
const E = 2.71828
// Add 计算两个整数的和。
func Add(a, b int) int {
return a + b
}
// Person 表示一个人。
type Person struct {
Name string
Age int
}
// SayHello 打印问候语。
func (p *Person) SayHello() {
fmt.Println("Hello, I'm", p.Name)
}
`
// 解析源文件
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "math.go", src, parser.ParseComments)
// 提取包文档
pkgDoc := doc.New(file, "math", doc.AllDecls)
// 打印包信息
fmt.Printf("包名:%s\n", pkgDoc.Name)
fmt.Printf("包文档:%s\n", pkgDoc.Doc)
fmt.Printf("\n常量:\n")
for _, c := range pkgDoc.Consts {
fmt.Printf(" %s: %s\n", c.Names[0], c.Doc)
}
fmt.Printf("\n函数:\n")
for _, f := range pkgDoc.Funcs {
fmt.Printf(" %s: %s\n", f.Name, f.Doc)
}
fmt.Printf("\n类型:\n")
for _, t := range pkgDoc.Types {
fmt.Printf(" %s: %s\n", t.Name, t.Doc)
for _, m := range t.Methods {
fmt.Printf(" 方法:%s: %s\n", m.Name, m.Doc)
}
}
}
运行:
$ go run main.go
包名:math
包文档:Package math 提供基本的数学常量和函数。
常量:
E: E 是自然对数的底。
Pi: Pi 是圆周率。
函数:
Add: Add 计算两个整数的和。
类型:
Person: Person 表示一个人。
方法:SayHello: SayHello 打印问候语。
示例 2:使用不同选项提取文档:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
// PublicFunc 是导出的函数。
func PublicFunc() {}
func privateFunc() {}
// PublicVar 是导出的变量。
var PublicVar int
var privateVar int
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
// 只提取导出的内容
pkgDoc1 := doc.New(file, "example", doc.AllDecls)
fmt.Println("=== 所有声明 ===")
fmt.Printf("函数:%d\n", len(pkgDoc1.Funcs))
fmt.Printf("变量:%d\n", len(pkgDoc1.Vars))
// 过滤未导出的内容
pkgDoc2 := doc.New(file, "example", doc.AllDecls|doc.PreserveAST)
fmt.Println("\n=== 保留 AST ===")
fmt.Printf("AST: %v\n", pkgDoc2.Decls != nil)
}
运行:
$ go run main.go
=== 所有声明 ===
函数:1
变量:1
=== 保留 AST ===
AST: true
一、包级别常量
模式标志
Mode
定义:
type Mode uint
说明:
- 控制文档提取的行为
- 使用位掩码组合多个选项
包含所有声明
AllDecls
定义:
const AllDecls Mode = 1 << iota
说明:
- 包含所有声明(包括未导出的)
- 默认只包含导出的声明
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
// Public 是导出的函数。
func Public() {}
func private() {}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
// 默认模式(只导出)
pkgDoc1 := doc.New(file, "example", 0)
fmt.Printf("默认模式 - 函数数:%d\n", len(pkgDoc1.Funcs))
// 包含所有声明
pkgDoc2 := doc.New(file, "example", doc.AllDecls)
fmt.Printf("AllDecls 模式 - 函数数:%d\n", len(pkgDoc2.Funcs))
}
运行:
$ go run main.go
默认模式 - 函数数:1
AllDecls 模式 - 函数数:2
保留 AST
PreserveAST
定义:
const PreserveAST Mode = 1 << iota
说明:
- 保留对 AST 的引用
- 用于需要进一步分析 AST 的场景
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
func Test() {}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
// 不保留 AST
pkgDoc1 := doc.New(file, "example", 0)
fmt.Printf("默认 - Decls: %v\n", pkgDoc1.Decls == nil)
// 保留 AST
pkgDoc2 := doc.New(file, "example", doc.PreserveAST)
fmt.Printf("PreserveAST - Decls: %v\n", pkgDoc2.Decls != nil)
}
运行:
$ go run main.go
默认 - Decls: true
PreserveAST - Decls: false
过滤方法
FilterFuncs
定义:
const FilterFuncs Mode = 1 << iota
说明:
- 只包含函数(过滤掉类型、变量、常量)
- 用于只关心函数的场景
包含方法
AllMethods
定义:
const AllMethods Mode = 1 << iota
说明:
- 包含所有方法(包括未导出的)
- 默认只包含导出的方法
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
type MyType struct{}
// PublicMethod 是导出的方法。
func (m MyType) PublicMethod() {}
func (m MyType) privateMethod() {}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
// 默认模式
pkgDoc1 := doc.New(file, "example", 0)
if len(pkgDoc1.Types) > 0 {
fmt.Printf("默认模式 - 方法数:%d\n", len(pkgDoc1.Types[0].Methods))
}
// 包含所有方法
pkgDoc2 := doc.New(file, "example", doc.AllMethods)
if len(pkgDoc2.Types) > 0 {
fmt.Printf("AllMethods 模式 - 方法数:%d\n", len(pkgDoc2.Types[0].Methods))
}
}
运行:
$ go run main.go
默认模式 - 方法数:1
AllMethods 模式 - 方法数:2
二、核心结构体
包文档结构体
Package
定义:
type Package struct {
Doc *ast.CommentGroup // 包文档注释
ImportPath string // 导入路径
Name string // 包名
Notes map[string][]*CommentGroup // 注释(如 BUGs、TODOs)
// 导出内容
Consts []*Value // 常量列表
Vars []*Value // 变量列表
Types []*Type // 类型列表
Funcs []*Func // 函数列表
// AST 相关
Files []*ast.File // 源文件列表
Decls []ast.Decl // 声明列表
Bugs []*CommentGroup // BUG 注释
Deprecated string // 弃用说明
}
说明:
- 表示一个包的完整文档信息
- 包含所有导出的常量、变量、类型和函数
- 可选择性地保留 AST 信息
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
// Package example 提供示例功能。
package example
// Version 是版本号。
const Version = "1.0.0"
// Config 是配置结构。
type Config struct {
Name string
}
// NewConfig 创建配置。
func NewConfig() *Config {
return &Config{}
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
pkgDoc := doc.New(file, "example", doc.AllDecls)
fmt.Printf("包名:%s\n", pkgDoc.Name)
fmt.Printf("包文档:%s\n", pkgDoc.Doc.Text())
fmt.Printf("常量数:%d\n", len(pkgDoc.Consts))
fmt.Printf("类型数:%d\n", len(pkgDoc.Types))
fmt.Printf("函数数:%d\n", len(pkgDoc.Funcs))
}
运行:
$ go run main.go
包名:example
包文档:Package example 提供示例功能。
常量数:1
类型数:1
函数数:1
值文档结构体
Value
定义:
type Value struct {
Doc *ast.CommentGroup // 值的文档注释
Names []string // 值名列表(对于 iota 组)
Type ast.Expr // 类型表达式
Decl *ast.GenDecl // 声明节点
}
说明:
- 表示常量或变量的文档
- 支持 iota 枚举组(多个名字共享一个类型)
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
// 星期枚举
const (
Sunday = iota // 星期日
Monday // 星期一
Tuesday // 星期二
)
// MaxSize 是最大尺寸。
const MaxSize = 1024
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
pkgDoc := doc.New(file, "example", doc.AllDecls)
for _, c := range pkgDoc.Consts {
fmt.Printf("常量:%v\n", c.Names)
fmt.Printf(" 文档:%s\n", c.Doc.Text())
}
}
运行:
$ go run main.go
常量:[Sunday Monday Tuesday]
文档:星期枚举
常量:[MaxSize]
文档:MaxSize 是最大尺寸。
类型文档结构体
Type
定义:
type Type struct {
Doc *ast.CommentGroup // 类型的文档注释
Name string // 类型名
Decl *ast.GenDecl // 声明节点
Const []*Value // 关联的常量
Var []*Value // 关联的变量
Func []*Func // 关联的函数(工厂函数等)
Method []*Func // 类型的方法
}
说明:
- 表示一个类型的完整文档
- 包含与该类型关联的常量、变量、函数和方法
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
// Result 表示操作结果。
type Result int
// Success 表示成功。
const Success Result = 0
// NewResult 创建结果。
func NewResult() Result {
return Success
}
// Error 返回错误信息。
func (r Result) Error() string {
return "error"
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
pkgDoc := doc.New(file, "example", doc.AllDecls)
for _, t := range pkgDoc.Types {
fmt.Printf("类型:%s\n", t.Name)
fmt.Printf(" 文档:%s\n", t.Doc.Text())
fmt.Printf(" 关联常量:%d\n", len(t.Const))
fmt.Printf(" 关联函数:%d\n", len(t.Func))
fmt.Printf(" 方法:%d\n", len(t.Method))
}
}
运行:
$ go run main.go
类型:Result
文档:Result 表示操作结果。
关联常量:1
关联函数:1
方法:1
函数文档结构体
Func
定义:
type Func struct {
Doc *ast.CommentGroup // 函数的文档注释
Name string // 函数名
Decl *ast.FuncDecl // 函数声明节点
Recv *Field // 接收者(如果是方法)
}
说明:
- 表示函数或方法的文档
- 对于方法,Recv 字段包含接收者信息
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
// Add 计算两个数的和。
func Add(a, b int) int {
return a + b
}
// Calculator 是计算器。
type Calculator struct{}
// Subtract 计算两个数的差。
func (c Calculator) Subtract(a, b int) int {
return a - b
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
pkgDoc := doc.New(file, "example", doc.AllDecls)
fmt.Println("=== 函数 ===")
for _, f := range pkgDoc.Funcs {
fmt.Printf("%s: %s\n", f.Name, f.Doc.Text())
}
fmt.Println("\n=== 类型方法 ===")
for _, t := range pkgDoc.Types {
for _, m := range t.Methods {
fmt.Printf("%s.%s: %s\n", t.Name, m.Name, m.Doc.Text())
}
}
}
运行:
$ go run main.go
=== 函数 ===
Add: Add 计算两个数的和。
=== 类型方法 ===
Calculator.Subtract: Subtract 计算两个数的差。
字段结构体
Field
定义:
type Field struct {
Names []*ast.Ident // 字段名列表
Type ast.Expr // 字段类型
}
说明:
- 表示函数参数、返回值或方法接收者
- Names 为空表示匿名参数
三、包级别函数(按字母顺序)
从 AST 创建包文档
New
定义:
func New(pkg *ast.File, importPath string, mode Mode) *Package
说明:
- 从 AST 创建包文档
- 最常用的函数
参数:
pkg:解析后的 AST 文件importPath:包的导入路径mode:文档提取模式
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
// Package math 提供数学函数。
package math
// Pi 是圆周率。
const Pi = 3.14159
// Add 计算和。
func Add(a, b float64) float64 {
return a + b
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "math.go", src, parser.ParseComments)
pkgDoc := doc.New(file, "math", doc.AllDecls)
fmt.Printf("包:%s\n", pkgDoc.Name)
fmt.Printf("文档:%s\n", pkgDoc.Doc.Text())
fmt.Printf("常量:%d\n", len(pkgDoc.Consts))
fmt.Printf("函数:%d\n", len(pkgDoc.Funcs))
}
运行:
$ go run main.go
包:math
文档:Package math 提供数学函数。
常量:1
函数:1
从多个文件创建包文档
NewFromFiles
定义:
func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, mode Mode) *Package
说明:
- 从多个 AST 文件创建包文档
- 适合多文件包
参数:
fset:token 文件集files:AST 文件列表importPath:包的导入路径mode:文档提取模式
示例:
package main
import (
"fmt"
"go/ast"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src1 := `
package example
// Func1 是函数 1。
func Func1() {}
`
src2 := `
package example
// Func2 是函数 2。
func Func2() {}
`
fset := token.NewFileSet()
// 解析多个文件
file1, _ := parser.ParseFile(fset, "file1.go", src1, parser.ParseComments)
file2, _ := parser.ParseFile(fset, "file2.go", src2, parser.ParseComments)
files := []*ast.File{file1, file2}
// 从多个文件创建包文档
pkgDoc := doc.NewFromFiles(fset, files, "example", doc.AllDecls)
fmt.Printf("包:%s\n", pkgDoc.Name)
fmt.Printf("函数数:%d\n", len(pkgDoc.Funcs))
for _, f := range pkgDoc.Funcs {
fmt.Printf(" - %s: %s\n", f.Name, f.Doc.Text())
}
}
运行:
$ go run main.go
包:example
函数数:2
- Func1: Func1 是函数 1。
- Func2: Func2 是函数 2。
读取包文档
Readme
定义:
func Readme(fsys fs.FS) string
说明:
- 从文件系统中读取 README 文件
- 支持 README、README.md、README.txt 等
参数:
fsys:文件系统接口
示例:
package main
import (
"embed"
"fmt"
"go/doc"
)
//go:embed *.md
var fsys embed.FS
func main() {
// 假设有 README.md 文件
readme := doc.Readme(fsys)
fmt.Printf("README 长度:%d\n", len(readme))
}
提取函数文档
Synopsis
定义:
func Synopsis(s string) string
说明:
- 提取文档的第一句话作为摘要
- 用于列表或概览显示
示例:
package main
import (
"fmt"
"go/doc"
)
func main() {
longDoc := `Add 计算两个整数的和。
这是一个详细的说明,
包含多行内容。`
short := doc.Synopsis(longDoc)
fmt.Printf("摘要:%s\n", short)
}
运行:
$ go run main.go
摘要:Add 计算两个整数的和。
判断是否导出
IsExported
定义:
func IsExported(name string) bool
说明:
- 检查标识符是否已导出(首字母大写)
- 与 ast.IsExported 功能相同
示例:
package main
import (
"fmt"
"go/doc"
)
func main() {
names := []string{"Public", "private", "Export", "internal"}
for _, name := range names {
fmt.Printf("%s: %v\n", name, doc.IsExported(name))
}
}
运行:
$ go run main.go
Public: true
private: false
Export: true
internal: false
判断是否是空白标识符
IsBlank
定义:
func IsBlank(name string) bool
说明:
- 检查是否为空白标识符(_)
示例:
package main
import (
"fmt"
"go/doc"
)
func main() {
names := []string{"_", "x", "unused"}
for _, name := range names {
fmt.Printf("%s: %v\n", name, doc.IsBlank(name))
}
}
运行:
$ go run main.go
_: true
x: false
unused: false
获取类型名称
TypeName
定义:
func TypeName(x ast.Expr) string
说明:
- 从类型表达式获取类型名称
- 用于显示类型信息
示例:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
type MyType struct{}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, 0)
// 获取类型声明
for _, decl := range file.Decls {
if gen, ok := decl.(*ast.GenDecl); ok {
for _, spec := range gen.Specs {
if ts, ok := spec.(*ast.TypeSpec); ok {
name := doc.TypeName(ts.Type)
fmt.Printf("类型名:%s\n", name)
}
}
}
}
}
运行:
$ go run main.go
类型名:struct{MyType struct{}}
打印包文档
定义:
func Print(pkg *Package)
说明:
- 打印包文档到标准输出
- 用于调试
示例:
package main
import (
"go/doc"
"go/parser"
"go/token"
)
func main() {
src := `
package example
func Test() {}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "example.go", src, parser.ParseComments)
pkgDoc := doc.New(file, "example", doc.AllDecls)
// 打印包文档
doc.Print(pkgDoc)
}
四、快速参考
模式常量
| 常量 | 说明 |
|---|---|
| AllDecls | 包含所有声明(包括未导出的) |
| PreserveAST | 保留 AST 引用 |
| FilterFuncs | 只包含函数 |
| AllMethods | 包含所有方法 |
核心结构体
| 结构体 | 说明 | 主要字段 |
|---|---|---|
| Package | 包文档 | Name, Doc, Consts, Vars, Types, Funcs |
| Value | 值文档(常量/变量) | Doc, Names, Type, Decl |
| Type | 类型文档 | Doc, Name, Const, Var, Func, Methods |
| Func | 函数/方法文档 | Doc, Name, Decl, Recv |
| Field | 字段信息 | Names, Type |
包级别函数
| 函数 | 说明 |
|---|---|
| New(pkg, importPath, mode) | 从 AST 创建包文档 |
| NewFromFiles(fset, files, importPath, mode) | 从多个文件创建包文档 |
| Readme(fsys) | 读取 README 文件 |
| Synopsis(s) | 提取文档摘要 |
| IsExported(name) | 检查是否已导出 |
| IsBlank(name) | 检查是否空白标识符 |
| TypeName(x) | 获取类型名称 |
| Print(pkg) | 打印包文档 |
使用场景
| 场景 | 推荐函数 |
|---|---|
| 单文件包文档 | New |
| 多文件包文档 | NewFromFiles |
| 只关心导出内容 | mode = 0 |
| 包含未导出内容 | mode = AllDecls |
| 需要进一步分析 AST | mode = PreserveAST |
| 提取文档摘要 | Synopsis |
| 检查导出状态 | IsExported |
最后更新:2026-04-04
Go 版本:Go 1.23+