parser.ParseFile 返回 nil 或 panic 的根本原因是参数组合错误:fset 必须为新 token.NewFileSet(),磁盘读时路径有效且 src 为 nil,字符串读时路径占位且 src 为非空 UTF-8 []byte,mode 需含 parser.ParseComments,并必须检查 err。

parser.ParseFile 为什么返回 nil 或 panic
根本原因不是代码写错了,而是参数组合没对上。最常见的错误是:传了文件路径但 src 没设为 nil,或者传了字符串源码但 src 是 nil。解析器只认你给的参数,不猜意图。
必须确保三点同时满足:
-
fset是新创建的token.NewFileSet(),不能复用或传nil - 从磁盘读:第二个参数是真实路径(如
"main.go"),第四个参数src必须为nil - 从字符串读:第二个参数只是占位名(如
"dummy.go"),src必须是非空[]byte,且 UTF-8 编码合法 - mode 至少带
parser.ParseComments,否则注释全丢,file.Comments为空 - 必须检查
err != nil—— BOM、CRLF 混用、语法错都会让file为nil,后续调file.Decls直接 panic
怎么安全遍历出所有函数声明
别直接循环 file.Decls 然后断言 *ast.FuncDecl —— 这会漏掉 func() {} 字面量、方法声明(在 *ast.TypeSpec 里)、甚至 init() 函数(它的 Name.Name == "init",但仍是 *ast.FuncDecl)。
正确做法是用 ast.Inspect:
立即学习“go语言免费学习笔记(深入)”;
- 回调函数里做类型断言:
if f, ok := n.(*ast.FuncDecl); ok { ... } - 注意
f.Type.Params和f.Type.Results可能为nil(比如func() {}),要先判空再访问.List - 函数名取
f.Name.Name,不是f.Name(后者是*ast.Ident节点) - 想提前退出(比如只找第一个
main),在匹配后返回false
如何拿到函数上方的文档注释
*ast.FuncDecl.Doc 不是总存在。只有紧贴函数声明上方、中间无空行的 // 或 /* */ 才会被绑定为 Doc;其他注释都堆在 file.Comments 里,不和任何节点关联。
提取逻辑要分两路:
- 优先查
f.Doc:它已清洗过格式,f.Doc.Text()直接可用 - 若
f.Doc == nil,再遍历file.Comments,用fset.Position(c.Pos()).Line对比注释行号与f.Pos()行号,找最近的前导注释 - 别用
strings.Contains匹配注释内容——位置关系才是唯一可靠依据
为什么结构体注释拿不到 TypeSpec.Doc
*ast.TypeSpec 本身没有 Doc 字段。结构体定义(如 type T struct{})属于 *ast.GenDecl 的 Specs 列表,而文档注释挂在 *ast.GenDecl 上,不是 TypeSpec。
所以得这样走:
- 先找到
*ast.GenDecl(Kind == token.TYPE) - 检查它的
Doc字段是否非空 - 再遍历
Specs找到*ast.TypeSpec,确认名字匹配 - 字段级注释在
*ast.Field.Doc,不是结构体整体注释
真正稳定提取文档的方案,是绕过 AST 直接用 go/doc 包 —— 它内部已处理好这些绑定逻辑,但代价是必须有完整可构建的包路径,不能只喂一个孤立文件。


















