直接修改ast.basiclit.value不生效,因go/format.node忽略该字段;须用strconv.quote构造新节点并替换父节点引用,配合astutil.cursor安全增删,且必须使用正确token.fileset。

直接改 ast.BasicLit.Value 不生效
很多人试过把 ast.BasicLit 节点的 Value 字段直接赋新字符串,比如 lit.Value = `"new"`,结果生成的代码完全没变。这是因为 ast.BasicLit 是只读结构体,go/format.Node 根本不读这个字段——它只看节点类型、子节点引用和位置信息。改了 Value 就像往空盒子里贴新标签,盒子本身还是旧的。
真正起作用的是:构造一个新 &ast.BasicLit,用 strconv.Quote(newStr) 包装内容(它会自动选双引号或反引号,并正确转义),然后把父节点里指向原节点的字段,换成这个新节点的指针。
常见父节点类型包括:
-
ast.AssignStmt.Rhs(赋值语句右侧) -
ast.ReturnStmt.Results(返回值列表) -
ast.CompositeLit.Elts(结构体/切片字面量元素) -
ast.CallExpr.Args(函数调用参数)
用 astutil.Cursor 安全删除或替换节点
ast.Inspect 是只读的,返回 false 只能跳过子树,不能删节点。硬写 parent.List[i] = nil 会破坏切片结构,go/format 输出时 panic 或丢语句。
必须用 golang.org/x/tools/go/ast/astutil:
- 创建
astutil.Cursor,在Leave回调里判断节点类型(如*ast.ExprStmt) - 调用
c.Delete(),它会自动从父节点对应切片中移除该节点,并保持索引连续 - 若要替换,先
c.Delete(),再调用c.InsertAfter(newNode)或c.InsertBefore(newNode)
注意:astutil.Apply 更底层,但 Cursor 对简单增删更直观,且不需手动维护父节点字段名。
保存修改后 AST 必须带有效 token.FileSet
没挂 token.FileSet 或复用了错误的 FileSet,会导致所有 Pos()/End() 返回 0,go/format.Node 直接 panic。
正确流程是:
- 解析时用同一个
fset := token.NewFileSet(),传给parser.ParseFile(fset, ...) - 所有新构造的节点(如新
ast.BasicLit)都得用fset.Position(node.Pos()).Offset校验位置是否合理 - 输出时用
go/format.Node(&buf, fset, file),其中file是修改后的*ast.File
漏掉 parser.ParseComments 模式,注释节点会丢失,FuncDecl.Doc 为空——看起来“改成功了”,但文档没了。
别忽略原始字符串和 Unicode 的解析差异
字符串节点统一是 ast.BasicLit,但 node.Kind == token.STRING 才是真正的字符串字面量(含 `` 和 "")。别用正则或 strings.Trim 去掉引号——"a\nb" 和 ``a\nb`` 解析后内容一样,但 strconv.Unquote(node.Value) 才能拿到真实值。
替换时也必须用 strconv.Quote 重包,否则:
- 手拼
"\"" + s + "\""会崩掉含双引号的字符串 - 忽略 Unicode(如
"\u4f60")会导致乱码 - 原始字符串中的反斜杠(
`C:\temp`)会被错误转义
位置信息错位是最隐蔽的问题:原字符串长 5 字节,新字符串长 20 字节,但 End() 没更新,go/format 会把后续代码挤进这一行,格式全乱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











