安全调用 parser.ParseFile 解析字符串源码需满足四条件:fset 必须为 token.NewFileSet() 创建的非 nil 值;src 为非空字节切片(如 os.ReadFile 结果);mode 至少含 parser.ParseComments;必须检查 err 是否为 nil,否则 file 为 nil 导致 .Pos() 返回 0、ast.Inspect panic。

怎么安全调用 parser.ParseFile 读取字符串源码
直接传字符串进 parser.ParseFile 不会自动建 token.FileSet,也不会校验 UTF-8 或跳过语法错误——漏掉任一环节,file 就是 nil,后续所有 .Pos() 返回 0,ast.Inspect 遍历直接 panic。
必须满足四个条件:
-
fset必须非 nil:用token.NewFileSet()创建,别复用旧的(位置信息错乱) -
src参数要非空:比如os.ReadFile("handler.go")的返回值,不是文件路径字符串 -
mode至少带parser.ParseComments:否则注释节点全丢,FuncDecl.Doc为空 - 必须检查
err != nil:BOM 头、CRLF/LF 混用、非法标识符都可能导致解析失败
正确写法示例:
fset := token.NewFileSet()
src, _ := os.ReadFile("main.go")
file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
if err != nil {
log.Fatal(err) // 别忽略!
}
// 此时 file 才可安全使用
为什么遍历 AST 要用 ast.Inspect 而不是手写递归
手写递归遍历 *ast.File.Decls 看似简单,但实际会漏掉大量节点:比如 func() {} 这类 *ast.FuncLit 不在 Decls 里;switch 分支里的 fallthrough 在 *ast.CaseClause.Body 中;type T struct{ f int } 的字段定义藏在 *ast.StructType.Fields,不经过 GenDecl 就根本碰不到。
立即学习“go语言免费学习笔记(深入)”;
ast.Inspect 是唯一能覆盖全节点类型的遍历方式,它内部已处理所有分支类型。常见误操作包括:
- 误以为
Inspect能改 AST —— 它不能,所有节点都是只读副本 - 在回调里对
node做指针比较(如node == someNode),AST 节点没实现==,恒为 false - 依赖
node.(*ast.FuncDecl)后直接访问Type.Params.List,但Params可能为nil(无参函数),不判空就 panic
怎么提取函数名、参数和返回值类型
函数名在 funcDecl.Name.Name,不是 funcDecl.Name;参数列表在 funcDecl.Type.Params.List,每个元素是 *ast.Field,类型藏在 Field.Type;返回值在 funcDecl.Type.Results。
关键陷阱在于空值判断:
-
funcDecl.Type.Params和funcDecl.Type.Results都可能为nil,不是空切片 —— 直接遍历会 panic - 参数名藏在
*ast.Field.Names,它是[]*ast.Ident:匿名参数(如func(int))的Names是空切片,不能直接取[0] - 省略号参数(
...T)对应*ast.Ellipsis类型,不是普通*ast.ArrayType
安全写法:
if f.Type.Params != nil {
for _, field := range f.Type.Params.List {
if len(field.Names) > 0 {
paramName := field.Names[0].Name
}
// field.Type 是参数类型节点
}
}
怎么拿到注释,尤其是函数文档注释
// 单行注释不会挂到 AST 节点上,默认被丢弃;/* */ 块注释虽被保留,但不绑定到任何节点,只是按顺序堆在 *ast.File.Comments 里。
想提取函数文档注释,得同时做两件事:
- 启用
parser.ParseComments模式,否则file.Comments是空的 - 遍历
file.Comments中的每个*ast.CommentGroup,再遍历其List取*ast.Comment,用fset.Position(c.Pos()).Line获取行号 - 函数文档注释通常紧贴
FuncDecl上方,需手动比对位置:c.Pos() < fdecl.Doc.Pos()或c.Pos() < fdecl.Pos(),且中间无空行(需读原始字节算换行数)
注意:funcDecl.Doc 只包含显式绑定的文档注释(即紧贴声明上方、无空行的 // 或 /* */),不覆盖所有块注释。
真正难的不是遍历节点,而是位置对齐和空值防御——AST 结构严格按语法定义,字段名和语义不直观,nil 出现频率远高于预期。


















