唯一可靠路径是 go/parser 解析+go/ast 构造+go/format.Source 输出;必须显式传 parser.ParseFullFiles 否则 FuncDecl.Body 为 nil;token.FileSet 必须初始化并复用;节点需挂载到合法父节点;注释、格式、标识符须校验;路径解析需动态获取模块根目录。

直接用 go/parser 解析源码 + go/ast 构造节点 + go/format.Source 输出,是唯一能保证语法合法、结构可控的路径。手拼字符串、反射、模板渲染都不可靠,一碰关键字或嵌套结构就崩。
parser.ParseFullFiles 必须显式传入,否则 FuncDecl.Body 是 nil
默认模式下 parser.ParseFile 会跳过函数体解析,只保留声明骨架。一旦你试图访问 fn.Body.List 或遍历语句,就会 panic。
- 正确写法:
parser.ParseFile(fset, filename, src, parser.ParseComments|parser.ParseFullFiles) - 如果只提取 struct 字段或 import 列表,可以省略
ParseFullFiles,节省内存 -
parser.AllErrors单独加没用,它不控制函数体是否解析,必须和ParseFullFiles同时启用
生成 ast.Node 前必须挂载 token.FileSet,且节点要挂到合法父节点上
go/format.Node 会检查 *token.FileSet,传 nil 直接 panic:「nil *token.FileSet」;更隐蔽的问题是节点孤立——比如你 new 了一个 *ast.ValueSpec,但没塞进 *ast.GenDecl.Specs,最终输出为空或报 invalid node。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 每新建一个
*ast.File,必须用token.NewFileSet()初始化,并在ParseFile和format.Source中复用同一个实例 - 变量声明不能只写
ast.NewIdent("x"),得包成*ast.ValueSpec,再放进[]ast.Spec,最后赋给*ast.GenDecl.Specs - 替换已有节点(如把
fmt.Println改成log.Println)时,必须在父节点(如*ast.ExprStmt)里显式重赋值stmt.X = newCallExpr,ast.Walk不自动处理引用
注释、格式、标识符三处最容易漏掉校验
生成的代码看似结构对了,但编译失败往往卡在这三个地方:注释丢失导致 //go:generate 消失;格式不合法让 go vet 报错;字段名含 type func 等关键字直接语法错误。
立即学习“go语言免费学习笔记(深入)”;
- 要保留注释,
ParseFile必须加parser.ParseComments,且新插入的函数前要构造*ast.CommentGroup赋给FuncDecl.Doc - 绝不要对
format.Node输出做字符串拼接或替换,必须走完整流程:bytes.Buffer → format.Node → bytes.Buffer.Bytes() → format.Source - 从 AST 提取的字段名、类型名不能直接插进字符串,要用
ast.Print写入 buffer 再转 string,或用types.Info.Types拿安全表示——否则type string这种非法组合立刻炸
最常被忽略的是路径解析:CI 中执行 go run ./cmd/gen 时,go/parser 默认按当前目录找 go.mod,结果 import 路径全错。唯一稳解是用 go list -m -f "{{.Dir}}" 动态获取模块根目录,再拼接相对路径。

















