解析go源文件必须传token.fileset,否则parser.parsefile会panic;注释需显式启用parser.parsecomments;funcdecl参数/返回值列表可能为nil,须判空;优先用ast.inspect条件遍历;修改ast须用astutil或重建节点并配合go/printer保留格式。

解析 Go 源文件必须传 token.FileSet
不创建 token.FileSet 就调用 parser.ParseFile 会 panic,因为 AST 节点的 Pos()/End() 需要它来记录位置信息。哪怕你暂时不关心行号,也得初始化:
fset := token.NewFileSet() f, err := parser.ParseFile(fset, "main.go", nil, parser.ParseComments)
-
parser.ParseComments必须显式传入,否则注释节点(*ast.CommentGroup)不会出现在 AST 中 - 第四个参数是
mode,不是布尔值;别写成true或1,要用parser.AllErrors这类常量 - 如果源码在内存里(比如从 git diff 拿到的字符串),把第二个参数设为空字符串
"",第三个参数传字符串,并加parser.FromFile模式
遍历函数声明时先判空再取 List
*ast.FuncDecl 的签名字段 Type.Params 和 Type.Results 是 *ast.FieldList 指针,但它们可能为 nil —— 比如 func init() 或 func hello() {}。直接访问 .List 会 panic。
正确写法是:
if f.Type.Params != nil {
for _, field := range f.Type.Params.List {
// 处理每个参数
}
}
if f.Type.Results != nil {
for _, field := range f.Type.Results.List {
// 处理每个返回值
}
}
- 别依赖文档说“它是个
*ast.FieldList”就跳过判空,Go AST 不保证非空 - 参数名在
field.Names(可能是多个标识符,如a, b int),类型在field.Type;若含省略号(...T),field.Type是*ast.Ellipsis,不是*ast.ArrayType -
f.Name.Name == "init"表示是 init 函数,无导出意义,很多重构工具会跳过
用 ast.Inspect 而不是 ast.Walk 做条件性遍历
想“找到第一个 main 函数就停”,或“只处理某个包路径下的函数”,该用 ast.Inspect。它回调函数返回 false 就终止遍历;ast.Walk 则强制走完全部节点,没法跳过无关分支。
典型误用是以为 Inspect 能改 AST —— 它不能。所有传入的 node 都是只读副本,改了也没用。真要重写代码,得用 golang.org/x/tools/go/ast/astutil 插入/替换节点,或手动生成新节点并重建父链。
- 类型断言用
if f, ok := node.(*ast.FuncDecl); ok { ... },别用指针比较node == someNode(恒为 false) - 需要上下文(如当前是否在某个函数体内)?自己维护栈结构,
Inspect不提供作用域信息 - 修改后输出代码必须用
go/format.Node或go/printer,直接fmt.Printf会丢位置、注释和格式
保留注释和格式的关键在 FileSet 和 printer 配合
AST 修改后若直接拼接字符串,注释、缩进、换行全丢。安全做法是:用原始 token.FileSet + go/printer 输出,它能根据节点 Pos() 自动插入注释到原位置。
但注意:新增节点(如加字段、加函数)的位置必须手动对齐——比如给结构体加字段,新 *ast.Field 的 NamePos 要设为前一个字段的 End() + 1,否则 printer 会因找不到对应行号而错位或降级处理。
- 别删
*ast.CommentGroup,只改cg.List[0].Text;删除会导致注释消失 - 新增注释要注册到
FileSet对应文件中,否则printer不知道该换哪行 - 跨包重构(如把
utils.Stringify替成fmt.Sprintf)需结合go/types确认调用目标,仅靠 AST 可能误改同名不同包函数
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











