应使用 ast.Inspect 遍历 AST,在 ast.GenDecl(Tok==token.TYPE)中查找 ast.TypeSpec,结构体注释在 GenDecl.Doc 而非 TypeSpec.Doc,字段真实类型需结合 go/types.Info.TypeOf 获取,不可仅依赖 AST 节点类型推断。

直接用 ast.ParseFile 解析源码后遍历 *ast.TypeSpec,但结构体注释不在 TypeSpec.Doc 里,得从它所属的 *ast.GenDecl 拿;字段类型不能靠 *ast.Ident.Name 猜,得结合 go/types 才能拿到真实类型。
怎么定位到结构体声明节点
Go 的结构体定义属于 type 声明,对应 AST 中的 *ast.TypeSpec 节点,但它**不单独出现在顶层 f.Decls 中**,而是嵌套在 *ast.GenDecl(泛型声明)里:
-
*ast.GenDecl的Tok字段等于token.TYPE时,说明它是一组type声明 -
GenDecl.Specs是切片,每个元素是ast.Spec,其中*ast.TypeSpec才真正包含结构体名和*ast.StructType - 别直接遍历
f.Decls并断言*ast.TypeSpec—— 它永远为nil,会 panic
正确做法是用 ast.Inspect,并在回调中逐层判断:
ast.Inspect(file, func(n ast.Node) bool {
if gen, ok := n.(*ast.GenDecl); ok && gen.Tok == token.TYPE {
for _, spec := range gen.Specs {
if ts, ok := spec.(*ast.TypeSpec); ok {
if st, ok := ts.Type.(*ast.StructType); ok {
// ✅ 找到结构体:ts.Name.Name 是名字,st.Fields 是字段列表
}
}
}
}
return true
})
为什么结构体文档注释取不到 TypeSpec.Doc
*ast.TypeSpec 自身的 Doc 字段**总是 nil** —— 注释实际挂在它外层的 *ast.GenDecl 上。Go parser 把紧邻 type X struct { ... } 上方的注释(含空行)归给整个 GenDecl,不是单个 TypeSpec。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
GenDecl.Doc是*ast.CommentGroup,存的是该声明块的顶部注释 - 如果结构体是
type A struct {...}; type B struct {...}连写,它们共享同一个GenDecl.Doc - 字段级注释则在
*ast.Field的Doc字段,这个是有效的
所以提取结构体注释的逻辑必须是:if gen.Doc != nil { docText = gen.Doc.Text() },而不是查 ts.Doc。
如何拿到字段的真实类型(不只是名字)
go/ast 只负责语法,*ast.Field.Type 可能是 *ast.Ident(如 int)、*ast.SelectorExpr(如 bytes.Buffer)、*ast.StarExpr(如 *T),但这些都只是“写法”,不是运行时类型。
- 仅靠 AST 无法知道
type MyInt int的底层类型,也无法解析examplepkg.Foo指向哪个包里的定义 - 必须用
go/types配合:先用packages.Load加载代码,再从pkg.TypesInfo.Types查ast.Node对应的types.Type - 例如,对
*ast.Ident节点调用info.TypeOf(ident),返回types.Basic、types.Named或types.Pointer等
绕过 go/types 直接字符串匹配(比如看到 "bytes.Buffer" 就硬编码处理)在跨包或类型别名场景下必然失败。
常见 panic 和规避方式
以下操作不做防护,几乎必 panic:
-
f.Decls为空或为nil时直接range f.Decls→ 先判f != nil && f.Decls != nil -
ts.Type不是*ast.StructType(比如是interface{}或func())就强转 → 必须类型断言后再用 -
st.Fields为nil(空结构体struct{})就直接st.Fields.List→ 检查st.Fields != nil - 用
gen.Doc.Text()前不检查gen.Doc != nil→ 注释不存在时Text()panic
AST 节点字段大量为 nil 是常态,不是 bug,是 Go parser 的设计选择 —— 它只保留语法必需信息,不做语义补全。

















