直接调用 parser.parsefile 得 ast.file,遍历 decls 中 ast.funcdecl 节点;注意 parser.mode(如 parsecomments、allerrors),funcdecl.type.params/results 可能为 nil 需判空,优先用 ast.inspect 并类型断言,省略号为 *ast.ellipsis,跨包解析需结合 go/types。

怎么用 go/ast 读取一个 Go 文件的函数列表
直接调用 parser.ParseFile 得到 *ast.File,再遍历它的 Decls 字段,挑出 *ast.FuncDecl 类型的节点就行。别先想着写完整分析器——90% 的需求只是“有哪些函数、参数啥类型、有没有返回值”。
常见错误是忽略 parser.Mode:不加 parser.ParseComments 就拿不到注释;不设 parser.AllErrors,遇到语法错就 panic,而不是收集所有问题。
-
parser.ParseFile的第四个参数传nil表示不读取源码字符串,只从文件路径加载;想内存解析就传src字符串 +parser.FromFile模式 - 函数名在
funcDecl.Name.Name,参数列表在funcDecl.Type.Params.List,每个参数是*ast.Field,类型藏在Field.Type - 如果文件里有
func init(),它也会被包含进来,但没有名字(Name.Name == "init"),注意区分
ast.Inspect 和 ast.Walk 该选哪个
优先用 ast.Inspect。它支持中途返回 false 中断遍历,适合“找到第一个 main 函数就停”这种场景;而 ast.Walk 是纯访问器,必须走完全部节点,没法跳过无关分支。
容易踩的坑是误以为 Inspect 能改 AST —— 它不能。所有节点都是只读副本,改了也没用。真要重写代码,得用 golang.org/x/tools/go/ast/astutil 或手动生成新节点。
立即学习“go语言免费学习笔记(深入)”;
-
Inspect回调函数接收node ast.Node,判断类型用if f, ok := node.(*ast.FuncDecl); ok { ... } - 别在回调里对
node做指针比较(比如node == someNode),AST 节点没实现==,会恒为 false - 如果需要上下文(比如当前在哪个函数里),自己维护栈结构,
Inspect不提供作用域信息
为什么解析出来的 FuncDecl.Type.Params 有时是 nil
因为 Go 允许无参数函数,比如 func hello() {},这时 Params 字段就是 nil,不是空切片。同理,Results 也可能为 nil(无返回值),直接遍历会 panic。
正确做法永远先判空,别依赖 “文档说它是个 *ast.FieldList” 就直接 .List。
- 检查参数:
if f.Type.Params != nil { for _, field := range f.Type.Params.List { ... } } - 函数签名里的省略号(
...T)存在field.Type是*ast.Ellipsis类型,不是普通*ast.ArrayType - 内嵌结构体字段(如
struct{ A, B int })在FieldList里表现为单个*ast.Field,但Names有多个,别当成多个独立参数
跨包解析时 import 路径对不上怎么办
go/ast 本身不处理 import 解析,它只管语法结构。如果你解析的是 fmt.Println,AST 里只有两个标识符节点 fmt 和 Println,中间那个点是 *ast.SelectorExpr,但“fmt 到底指向哪个包”它不管。
想解决这个问题,得接上 go/types:用 types.NewPackage 构建类型环境,再通过 Config.Check 运行类型检查,才能把 fmt 绑定到真实包对象。
- 单独用
go/ast无法识别别名导入(import io2 "io"),它只看到io2这个名字,不知道对应io - 相对路径导入(
import "./local")在 AST 里是合法的,但go/types默认拒绝,需手动设置Importer - 如果只是静态扫描(比如统计函数调用次数),不关心具体包路径,那绕过类型系统反而更轻快
AST 解析最麻烦的从来不是读取结构,而是搞清哪些信息它天然不提供——比如作用域、类型绑定、常量展开。别试图用一棵语法树解决所有问题。










