go没有内置go-check工具,实际指基于golang.org/x/tools/go/analysis框架的自定义静态分析器;核心是实现analysis.analyzer的run函数,结合ast与types.info进行nil比较等语义检查,并通过analysis.main支持go vet集成。

Go 本身没有内置的 go-check 工具——你搜到的“go-check”大概率是指 golang.org/x/tools/go/analysis 框架下的自定义静态分析器,或者误把测试库 gopkg.in/check.v1(已归档)当成了代码检查工具。真正可扩展、被 gopls 和 staticcheck 等广泛采用的是 analysis 包。
用 analysis.Analyzer 写一个基础的 nil 检查器
静态分析器本质是一个函数:接收 AST、类型信息、源码位置等,返回诊断(analysis.Diagnostic)。核心是实现 run 函数并注册为 analysis.Analyzer。
常见错误是直接操作 ast.Node 却忽略类型信息,导致误报(比如把接口变量当指针判空)。必须结合 types.Info 判断实际类型。
- 用
analysis.Load加载包时传入analysis.Config{Types: true},否则pass.TypesInfo为空 - 遍历
pass.Files中的 AST,对每个ast.BinaryExpr检查是否为==或!=,左/右操作数是否为nil - 但仅此不够:要确认另一个操作数是可比较 nil 的类型(如指针、切片、map、chan、func、interface{}),需查
pass.TypesInfo.Types[expr].Type - 示例片段:
func run(pass *analysis.Pass) (interface{}, error) { for _, file := range pass.Files { ast.Inspect(file, func(n ast.Node) bool { bin, ok := n.(*ast.BinaryExpr) if !ok || bin.Op != token.EQL && bin.Op != token.NEQ { return true } if isNilLiteral(bin.X) { checkNilComparison(pass, bin.Y, bin) } else if isNilLiteral(bin.Y) { checkNilComparison(pass, bin.X, bin) } return true }) } return nil, nil }
让分析器支持命令行调用(go vet 风格)
Go 官方推荐用 golang.org/x/tools/go/analysis/passes/.../runner 启动,但更轻量的做法是直接调用 analysis.Main。
不这么做会导致你的工具无法被 go vet -vettool=./mycheck 接入,也无法和 gopls 协同工作。
- 主函数必须调用
analysis.Main并传入*analysis.Analyzer实例 - 不要自己解析 flag 或构建
loader.Config——analysis.Main已封装好包加载、类型检查、跨文件依赖处理 - 二进制名决定默认行为:叫
mycheck就能运行go vet -vettool=./mycheck ./... - 若想支持
go list -f '{{.Name}}' -deps . | xargs mycheck这类管道用法,需在Analyzer.Flags中注册 flag,并在run中读取pass.FlagSet
避免误报:区分有意 nil 检查和可疑赋值
真实项目里,if err != nil 是合法且高频的,但 if ptr == nil { ptr = &v } 可能暴露初始化疏漏。光靠语法树无法判断意图,得加启发式规则。
性能上,每多一次 types.TypeString 调用或 ast.Print 日志都会拖慢全量分析;兼容性上,Go 1.21+ 的泛型实例化类型(如 map[string]T)需用 types.TypeString(t, nil) 而非直接 t.String(),否则 panic。
- 跳过标准库和 vendor 下的文件:
if pass.Pkg.Path() == "fmt" || strings.HasPrefix(pass.Pkg.Path(), "vendor/") - 对赋值语句
ast.AssignStmt,若右边是nil且左边是导出变量(首字母大写),才报告 —— 避免误标var buf *bytes.Buffer - 用
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)获取预构建的inspector,比手动ast.Inspect快 2–3 倍(尤其大文件) - 禁用
go:generate注释所在文件的分析:检查file.Comments是否含//go:generate
最易被忽略的是类型推导边界:当遇到 var x interface{} = nil,types.Info.Types[x].Type 是 interface{},但 x == nil 是合法的;而 var x struct{} = struct{}{} 的零值不是 nil,却可能被 AST 层误认为“类似 nil”。这类 case 必须靠 types.IsNil 辅助判断,不能只看字面量。











