ast.inspect比ast.walk更适合写检测逻辑,因其支持通过返回bool控制是否遍历子树(如跳过测试函数),且回调中可灵活判断节点类型;而ast.walk需显式处理所有节点类型,易漏空节点或嵌套语句。

为什么 ast.Inspect 比 ast.Walk 更适合写检测逻辑
因为 ast.Inspect 允许你在进入和离开每个节点时做判断,且能通过返回值控制是否继续遍历子树——这对“跳过测试函数”“只检查顶层变量声明”这类常见检测需求非常关键;而 ast.Walk 的 Visitor 接口必须显式处理所有节点类型,容易漏掉 ast.EmptyStmt 或嵌套在 ast.IfStmt 里的 ast.AssignStmt。
实操建议:
- 用
ast.Inspect配合闭包捕获状态(如当前函数名、是否在init中),避免为每个节点传参 - 遇到
nil节点(比如FuncType.Params为nil)必须先判空,否则 panic - 别在
Inspect回调里修改 AST 节点——它不保证安全,检测阶段只读
如何准确识别未使用的局部变量(不是导出变量或 struct 字段)
直接比对 ast.AssignStmt 左侧的 Ident 和后续所有 ast.Ident 出现位置,会误报闭包捕获、defer 参数等场景。更可靠的做法是:构建作用域链,在每个 ast.BlockStmt 进入时新建作用域,退出时销毁,并记录每个 Ident 的定义/引用关系。
实操建议:
- 用
map[*ast.Ident]bool记录“已定义但未被读取”的变量,而不是字符串名——同名变量在不同作用域里是独立的 -
ast.RangeStmt的Key/Value是定义点,但它们可能为nil(如for range m),需跳过 - 忽略
_标识符:检查ident.Name == "_"后直接 continue
go/parser.ParseFile 报错 “expected 'package'” 怎么办
这是最常见的解析失败,根本原因是传给 ParseFile 的源码不是完整 Go 文件(比如只传了函数体字符串),或文件开头有 BOM、注释前有空白字符但没写 package 声明。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
实操建议:
- 确保输入是完整文件内容,包含
package xxx行;若只检测片段,用parser.ParseExpr或parser.ParseStmt - 用
token.NewFileSet()创建新fileSet,别复用多个文件共用的 set,否则位置信息错乱 - 错误提示里带
pos时,用fileSet.Position(pos)打印具体行列,比看 panic 更快定位
检测 panic("xxx") 但放过 log.Fatal() 的边界情况
不能只匹配函数名字符串,因为用户可能 alias 了 log 包(l "log"),或用了方法值(logger.Fatal)。必须基于 AST 类型判断调用目标是否为标准库 log.Fatal 或其变体。
实操建议:
- 对
ast.CallExpr.Fun做类型断言:*ast.SelectorExpr→ 看X是否为*ast.Ident且Name是"log",再看Sel.Name是否为"Fatal"等 - 跳过
*ast.Ident直接调用(如Fatal("x")),除非你明确知道它来自哪个包——这种属于未限定调用,检测器不该假设 -
panic是内置函数,它的Fun是*ast.Ident,Name == "panic"即可确认,无需查包
真正难的是跨文件分析:比如一个函数在 a.go 里调用 log.Fatal,但在 b.go 里被间接调用。AST 检测器默认只处理单文件,这种必须结合 golang.org/x/tools/go/packages 加载整个模块才能做调用图分析。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










