直接用 parser.parsefile 解析 go 源码生成 *ast.file 再用 ast.inspect 遍历是唯一稳定可落地的起点;必须传非 nil fset、正确区分磁盘/字符串源码参数、启用必要 mode、严格判 err,且遍历时始终检查字段非 nil。

直接用 parser.ParseFile 解析 Go 源码生成 *ast.File,再用 ast.Inspect 遍历,是唯一稳定、可落地的起点。别从手写递归或拼字符串开始,那只会卡在 nil panic 和字段缺失上。
怎么安全调用 parser.ParseFile 得到非 nil 的 *ast.File
常见错误是传错参数组合,导致返回 nil 或后续 .Pos() 全为 0,一调用就 panic。
-
fset必须非 nil:用token.NewFileSet()创建,不能复用旧的(位置信息错乱)或传nil - 源码来源分两种——磁盘文件 or 字符串:
– 磁盘读取:
parser.ParseFile(fset, "main.go", nil, mode),第 3 个参数传nil– 字符串解析:parser.ParseFile(fset, "dummy.go", srcBytes, mode),第 3 个参数必须是非空[]byte,第 2 个只是占位名 -
mode至少带parser.ParseComments,否则注释全丢;需要函数体(比如看FuncDecl.Body)必须加parser.ParseFullFiles - 必须检查
err != nil:BOM、CRLF/LF 混用、语法错误都会让file为nil,不判错直接用必 panic
为什么遍历一定要用 ast.Inspect,而不是手写 switch 或递归
手写遍历看似可控,实则漏节点、易 panic、难维护。Go AST 节点类型多且嵌套深,靠人脑枚举根本不可靠。
-
ast.Inspect自动覆盖所有实现ast.Node的类型,包括*ast.FuncLit(匿名函数)、*ast.TypeSpec(结构体定义)、*ast.Field(字段声明)等冷门但高频节点 - 它跳过所有
nil字段(比如FuncDecl.Type.Params是nil时自动略过),手写递归得每个字段都加判空 - 回调返回
false可立即中断,适合“找到第一个main就停”这种场景;而ast.Walk强制走完全部,没法跳过无关分支 - 别在回调里改节点:AST 是只读副本,改了也不生效,还可能干扰遍历逻辑
解析结构体或函数时,哪些字段最容易 panic
字段名和实际语义不一致,加上大量字段默认为 nil,是 panic 最高发区域。
-
FuncDecl.Name.Name才是函数名字符串,FuncDecl.Name是*ast.Ident,直接打印是地址 -
FuncDecl.Type.Params和FuncDecl.Type.Results都可能为nil(无参/无返回),不是空切片,必须先判空再访问.List - 结构体字段注释不在
TypeSpec.Doc,而在其所属的*ast.GenDecl的Doc字段里;单靠TypeSpec找不到紧邻的文档注释 -
Field.Names是[]*ast.Ident,匿名字段(如io.Reader)该切片为空,不能直接取[0]
提取函数列表或结构体字段这种基础需求,别绕远路
90% 的日常分析任务,只需要遍历 file.Decls,挑出对应节点,再逐层取值。没必要一开始就搭完整分析器。
- 找函数:遍历
Decls,对每个node做if f, ok := node.(*ast.FuncDecl); ok { ... },注意init函数的f.Name.Name == "init" - 找结构体:同样在
Decls里找*ast.GenDecl,再遍历其Specs,对每个spec判断是否为*ast.TypeSpec,再判断spec.Type是否为*ast.StructType - 字段名和类型:从
*ast.StructType.Fields.List拿*ast.Field,field.Names是标识符列表,field.Type是类型节点(可能是*ast.Ident、*ast.StarExpr等) - 别依赖
f.Scope:它是nil,go/ast 不做语义分析;要作用域信息,得上go/types
真正容易被忽略的是:所有节点字段都可能为 nil,而且 nil 不等于空切片。每次解引用前加一行 if x != nil,比事后 debug 十分钟更省时间。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











