因为标准工具无法满足团队特有规范,需基于golang.org/x/tools/go/analysis自定义linter:定义name、doc、run字段,用pass.reportf报错,结合typesinfo精准判断类型,通过go run或单元测试调试验证。

为什么不用 go vet 或 staticcheck,而要自己写 Linter?
因为它们解决不了你团队里特有的编码规范问题——比如禁止用 log.Println,要求所有 HTTP handler 必须带 timeout context,或者强制 struct 字段按字母序排列。这些规则不在标准工具链覆盖范围内,必须自己注入逻辑。
Go 的 golang.org/x/tools/go/analysis 框架就是干这事的:它提供 AST 遍历、类型信息、跨文件分析能力,且能无缝集成进 go vet 流程(通过 go run golang.org/x/tools/cmd/goaz 或直接嵌入 gopls)。
怎么注册一个最简可用的 Analyzer?
核心是定义一个 analysis.Analyzer 实例,关键字段只有三个:Name(命令行标识)、Doc(帮助说明)、Run(主检查函数)。不需要写 CLI 入口,框架自动加载。
-
Run函数签名固定为func(*analysis.Pass) (interface{}, error),其中*analysis.Pass包含当前包所有 AST 节点、类型信息、文件路径等 - 不要在
Run里做全局状态缓存——每个包独立调用一次Run,并发执行 - 报错用
pass.Reportf(node, "message"),node必须是 AST 节点(如*ast.CallExpr),否则位置信息丢失
示例:检测硬编码字符串长度超过 100 的 fmt.Sprintf 调用:
var analyzer = &analysis.Analyzer{
Name: "longsprintf",
Doc: "check fmt.Sprintf with literal string longer than 100 chars",
Run: func(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok || len(call.Args) == 0 {
return true
}
fun, ok := call.Fun.(*ast.SelectorExpr)
if !ok || fun.Sel.Name != "Sprintf" {
return true
}
if pkg, ok := fun.X.(*ast.Ident); ok && pkg.Name == "fmt" {
if lit, ok := call.Args[0].(*ast.BasicLit); ok && lit.Kind == token.STRING {
s := strings.Trim(lit.Value, "`\"")
if len(s) > 100 {
pass.Reportf(lit.Pos(), "long format string (%d chars)", len(s))
}
}
}
return true
})
}
return nil, nil
},
}
如何获取类型信息并避免误报?
纯 AST 检查容易漏判或误判——比如你只想检查 fmt.Sprintf,但用户写了 myfmt.Sprintf,或者用了别名导入。这时候必须依赖 pass.TypesInfo 做类型判定。
- 用
pass.TypesInfo.TypeOf(expr)获取表达式类型,再用types.TypeString()判断是否为fmt.Sprintf的函数签名 - 对函数调用,优先用
pass.TypesInfo.Calls(如果已启用)或手动匹配types.Func的String()输出,比字符串比对更可靠 - 注意:类型信息只在当前包内完整;跨包符号可能为
<nil></nil>,需加空值判断
错误示范:if fun.Sel.Name == "Sprintf" → 会匹配到所有叫 Sprintf 的方法;正确做法是结合 pass.TypesInfo.TypeOf(fun.X) 确认 X 是 *types.Package 且名字为 "fmt"。
怎么调试和本地验证你的 Linter?
别跑 go install 再全局调用——太慢,且错误堆栈难定位。直接用 go run 启动分析器,传入待测目录:
- 命令:
go run . -analyzer=longsprintf ./path/to/test/pkg(假设你的 main.go 注册了 analyzer) - 加
-debug参数能看到 AST 遍历过程、每个文件的节点数、耗时统计 - 用
go test -run=TestYourAnalyzer写单元测试最稳妥:构造 fake 文件、调用analysis.Run、断言pass.Diagnostics数量和内容
容易被忽略的一点:Linter 默认只检查 vendor 外代码,若要扫 vendor,得显式加 -vendor 标志;但生产环境一般禁用 vendor 扫描,避免规则爆炸。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











