
在本文中,我将讨论新的go-ruleguard
静态分析库(和实用程序),该库将gogrep
为可用于短绒gogrep
。
独特的功能:您可以在特殊的Go-like DSL上描述静态分析的规则,在ruleguard
的一开始, ruleguard
就会变成一组诊断。 也许这是用于实施Go的自定义检查的最容易配置的工具之一。
作为奖励,我们将讨论go/analysis
及其前身 。
静态分析可扩展性
Go有许多短毛猫,其中一些可以扩展。 通常,要扩展linter,您需要使用特殊的linter API编写Go代码。
主要有两种方式: Go插件和Monolith。 整体意味着所有支票(包括您的个人支票)在编译阶段都可用。
revive
要求其内核中包括新的检查以进行扩展。 除了这个可以插入插件的go-critic
插件,它使您可以收集扩展名而与主要代码无关。 这两种方法都暗示您go/ast
使用linter API在Go上实现go/ast
和go/types
操作。 即使是简单的检查也需要大量代码 。
go/analysis
旨在通过以下方案简化图片:皮棉机的“框架”几乎完全相同,但这并不能解决诊断程序本身的技术实施复杂性的问题。
`loader`和`go / packages'的题外话
当您为Go编写分析器时,您的最终目标是与AST和类型进行交互,但是在执行此操作之前,需要以正确的方式“加载”源代码。 为简化起见,加载的概念包括解析 ,类型检查和导入依赖项 。
简化此管道的第一步是go/loader
包,它将允许您通过几个调用来“下载”所需的一切。 一切都差不多了,然后他不赞成使用go/packages
。 go/packages
API略有改进,从理论上讲,它与模块配合良好。
现在,最好不要直接使用以上任何一种来编写分析器,因为go/analysis
提供了go/packages
以前的解决方案所没有的东西-程序的结构。 现在,我们可以使用指定的go/analysis
范例,并更有效地重用分析器。 这种范例具有争议性,例如, go/analysis
非常适合于单个软件包及其依赖项级别的分析,但是要在没有狡猾的工程技巧的情况下对其进行全局分析并不容易。
go/analysis
还可以简化分析仪的测试 。
什么是规则守卫?

go-ruleguard
是静态分析实用程序,默认情况下不包含单个检查。
ruleguard
规则从一个特殊的gorules
文件开始加载,该文件以声明方式描述了应向其发出警告的代码模式。 ruleguard
用户可以自由编辑此文件。
不必gorules
用于连接新支票gorules
控制程序,因此可以将gorules
的规则称为dynamic 。
ruleguard
控制程序如下所示:
package main import ( "github.com/quasilyte/go-ruleguard/analyzer" "golang.org/x/tools/go/analysis/singlechecker" ) func main() { singlechecker.Main(analyzer.Analyzer) }
同时, analyzer
通过ruleguard
程序包实现的,如果要将它用作库,则必须使用该程序。
守护者VS复兴
举一个简单但真实的示例:假设我们要避免在程序中调用runtime.GC()
。 为了复兴,已经有一个单独的诊断程序,称为"call-to-gc"
。
Call-to-gc实现(Elven中的70行)
package rule import ( "go/ast" "github.com/mgechev/revive/lint" )
现在,与go-ruleguard
操作方式进行比较:
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func callToGC(m fluent.Matcher) { m.Match(`runtime.GC()`).Report(`explicit call to the garbage collector`) }
没什么,真正重要的是runtime.GC
和万一触发规则需要发出的消息。
您可能会问:仅此而已吗? 我特别从一个简单的示例开始,以显示在使用传统方法的情况下进行非常简单的诊断可能需要多少代码。 我保证会有更多令人兴奋的例子。
快速上手
go-critic
有一个rangeExprCopy
诊断程序,可以在代码中查找潜在的意外数组副本。
此代码遍历数组的副本 :
var xs [2048]byte for _, x := range xs {
解决此问题的方法是添加一个字符:
var xs [2048]byte - for _, x := range xs { // Copies 2048 bytes + for _, x := range &xs { // No copy // Loop body. }
最有可能的是,您不需要此复制,并且更正版本的性能始终更好。 您可以等到Go编译器变得更好为止,或者可以在代码中检测到此类位置,并立即使用相同的go-critic
对其进行更正。
可以使用gorules
语言( rules.go
文件)实施此诊断:
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func _(m fluent.Matcher) { m.Match(`for $_, $_ := range $x { $*_ }`, `for $_, $_ = range $x { $*_ }`). Where(m["x"].Addressable && m["x"].Type.Size >= 128). Report(`$x copy can be avoided with &$x`). At(m["x"]). Suggest(`&$x`) }
该规则找到所有使用两个可迭代变量的for-range
循环(这种情况导致复制)。 可迭代表达式$x
必须是addressable
并且必须大于所选阈值(以字节为单位)。
Report()
定义了要发送给用户的消息, Suggest()
描述了一个快速quickfix
模板,该模板可以通过gopls (LSP)在编辑器中使用,并且如果使用-fix
参数调用ruleguard
可以交互使用(我们将返回此内容)。 At()
将警告和快速 quickfix
附加到模板的特定部分。 我们需要用$x
&$x
代替$x
,而不是重写整个循环。
Report()
和Suggest()
接受一个字符串,该字符串可以插入模板从Match()
捕获的表达式。 预定义的变量$$
表示“所有捕获的片段”(在正则表达式中为$0
)。
创建rangecopy.go
文件:
package example
现在我们可以运行ruleguard
:
$ ruleguard -rules rules.go -fix rangecopy.go rangecopy.go:12:20: builtins copy can be avoided with &builtins
如果之后再查看rangecopy.go
,则会看到一个固定版本,因为使用-fix
参数调用了ruleguard
。
无需创建gorules
文件即可调试最简单的规则:
$ ruleguard -c 1 -e 'm.Match(`return -1`)' rangecopy.go rangecopy.go:17:2: return -1 16 } 17 return -1 18 }
由于使用了go/analysis/singlechecker
,我们有了-c
选项,它使我们可以显示指定的上下文行以及警告本身。 控制此参数有点违反直觉:缺省值为-c=-1
,表示“无上下文”,而-c=0
将输出上下文的一行(诊断所指示的一行)。
这是一些更有趣的gorules
:
- 类型模板 ,可用于指定期望的类型。 例如,表达式
map[$t]$t
描述了所有具有与键类型匹配的值类型的map,而*[$len]$elem
捕获了所有指向数组的指针。 - 在一个函数中,可能有多个规则,
函数本身应称为规则组 。 - 组中的规则按照定义的顺序依次应用。 触发的第一个规则取消与其余规则的比较。 对于优化而言,这不重要,而对于特定情况下的专用规则而言,则不那么重要。 一个有用的例子是将
$x=$x+$y
重写$x=$x+$y
$x+=$y
,对于$y=1
的情况,您要提供$x++
,而不是$x+=1
。
可以在docs/gorules.md
找到有关所用DSL的更多信息。
更多例子
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func exampleGroup(m fluent.Matcher) {
如果没有对规则的Report()
调用,则将使用Suggest()
的消息输出。 在某些情况下,这可以避免重复。
类型过滤器和子表达式可以检查各种属性。 例如, Pure
和Const
属性非常有用:
对于Where()
条件中的package-qualified
名称,您需要使用Import()
方法。 为了方便起见,所有标准软件包都是为您导入的,因此在上面的示例中,我们不需要进行其他导入。
go/analysis
修复动作
go/analysis
为我们quickfix
对quickfix
支持。
在go/analysis
模型中,分析器生成诊断和事实 。 诊断将发送给用户,并且事实将供其他分析仪使用。
诊断程序可以具有一组建议的修复程序 ,每个修复程序都描述了如何在指定范围内更改源代码,以修复诊断程序发现的问题。
官方说明位于go/analysis/doc/suggested_fixes.md
。
结论

在您的项目上尝试使用ruleguard
,如果您发现错误或想请求新功能,请打开issue 。
如果您仍然很难使用ruleguard
,请参考以下示例:
- 实施自己的Go诊断程序。
- 使用
-fix
自动升级或重构代码。 - 使用
-json
处理-json
结果的代码统计信息收集。
ruleguard
的近期发展计划:
有用的链接和资源