自定义 linter 规则需基于 go/analysis 构建语义感知检查器,通过 singlechecker 打包并集成至 golangci-lint;必须避免仅依赖 ast.inspect 的浅层遍历,而应利用 pass.typesinfo 获取类型信息、声明依赖 inspect.analyzer 以启用作用域分析,并严格遵循 analyzers 变量命名与结构要求。

直接说结论:自定义 Linter 规则不是“写个检查脚本”,而是用 go/analysis 定义语义感知的检查器,并通过 singlechecker 打包成可执行命令,再集成进 golangci-lint;语言学习中的代码规范自动化,必须绕过“风格提示”陷阱,聚焦可判定、可复现、有业务含义的约束。
为什么 go/ast 直接遍历写不稳?
很多人一上来就手写 ast.Inspect 遍历函数节点,结果在 switch 块里找不到 fallthrough,或在嵌套 if 里漏掉参数类型判断——因为原始 AST 没有作用域、没有父节点指针、也没有类型绑定信息。
-
ast.Inspect只提供扁平节点访问,fallthrough实际藏在*ast.CaseClause.Body里,不是*ast.IfStmt的直系子节点 - 字段类型是
time.Time还是*time.Time,仅靠*ast.Ident名字无法区分,必须查pass.TypesInfo - 手动递归容易 panic:比如对
nil字段调用Len(),或在未初始化的funcDecl.Type.Results上循环
怎么让 analyzer 被 golangci-lint 正确加载?
golangci-lint 不认任意导出变量,它只扫描名为 Analyzers 的全局变量,且要求每个 analysis.Analyzer 实例满足三要素:非空 Doc、合法 Name(小写无下划线)、可运行的 Run 函数。
-
Name写成"ctx-param"或"ctx_param"会导致加载失败,必须是"ctxparam" -
Doc字段不能为"",哪怕只写"check http handler context usage"也比空字符串强 - 报错必须用
pass.Reportf(node.Pos(), "..."),node得是当前包内真实 AST 节点(如*ast.FuncDecl),传token.NoPos或硬编码位置会导致错误定位失效
如何避免“伪规范”规则拖慢 CI?
语言学习场景下最容易堆一堆“命名要小写”“注释要以句号结尾”这类规则,它们要么已由 golint(虽 deprecated 但仍在用)覆盖,要么根本无法形成稳定判断(比如“注释是否足够”)。真正值得自动化的,是那些影响行为、暴露设计缺陷、或违反团队契约的点。
- 检查
http.HandlerFunc是否漏掉context.Context参数——这关系到超时控制和取消传播 - 禁止结构体字段直接用
time.Time(强制*time.Time)——避免零值时间引发逻辑误判 - 检测
os.RemoveAll调用后是否忽略返回 error——这是典型资源清理遗漏风险 - 所有规则必须跑在
golangci-lint的fast模式下,单次检查耗时超过 200ms 就该被砍掉或加缓存
最常被忽略的点是:pass.ResultOf[inspect.Analyzer] 必须显式声明依赖,否则 inspect 提供的带作用域节点树根本不会生成——这不是可选优化,是运行前提。写完 Run 函数后,第一件事不是测逻辑,而是确认 Analyzer.Requires 里有没有 inspect.Analyzer。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











