
我们即将发布Go的新的linter(静态分析器),这也是在静态分析领域中为您的想法制作原型的沙箱。
评论家围绕以下观察结果而建立:
- 有一个“足够好”的测试实现比根本没有测试更好
- 如果支票有争议,这并不意味着它没有用。 我们标记为“调皮”,然后倒入
- 如果框架本身易于理解,那么从头开始编写linter通常比向现有框架添加新检查要困难得多。
在这篇文章中,我们将研究go-critic的用法和体系结构,以及其中实现的一些检查 ,并描述向其添加分析器功能的主要步骤。
快速上手
$ cd $GOPATH $ go get -u github.com/go-critic/go-critic/... $ ./bin/gocritic check-package strings $GOROOT/src/strings/replace.go:450:22: unslice: could simplify s[:] to s $GOROOT/src/strings/replace.go:148:2: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:156:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:219:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:370:1: paramTypeCombine: func(pattern string, value string) *singleStringReplacer could be replaced with func(pattern, value string) *singleStringReplacer $GOROOT/src/strings/replace.go:259:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/replace.go:264:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/strings.go:791:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:800:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:809:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:44:1: unnamedResult: consider to give name to results $GOROOT/src/strings/strings.go:61:1: unnamedResult: consider to give name to results $GOROOT/src/strings/export_test.go:28:3: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/export_test.go:42:1: unnamedResult: consider to give name to results
(警告的格式已被编辑;原稿位于gist中 。)
gocritic实用程序可以通过其导入路径( check-package
)来检查各个软件包,还可以递归遍历所有目录( check-project
)。 例如,您可以使用单个命令检查整个$GOROOT
或$GOPATH
:
$ gocritic check-project $GOROOT/src $ gocritic check-project $GOPATH/src
支持检查“白名单”,以便明确列出应执行的检查( -enable
标志)。 默认情况下,将运行所有未标记为“ Experimental
或“ VeryOpinionated
图标的检查。
计划整合golangci-lint和gometalinter 。
一切如何开始
对Go项目进行下一次代码审查,或者在审核某些第三方库时,您会一遍又一遍地注意到相同的问题。
令您遗憾的是,找不到能够诊断此类问题的棉绒。
您的第一步可能是尝试对问题进行分类,并与现有短毛绒的作者联系,建议他们添加新的支票。 您的提案被接受的机会在很大程度上取决于项目,并且可能会非常低。 此外,很可能会持续数月的预期。
但是,如果支票完全模棱两可并且被某人认为过于主观或不够准确怎么办?
尝试自己写这张支票也许有意义吗?
go-critic
存在是为了成为实验测试的家,与将其连接到现有的静态分析仪相比,我们更容易实现这些测试。 go-critic
设备本身将添加新检查所需的上下文和操作数量减至最少-可以说,您只需要添加一个文件(不计算测试)。
评论家如何工作
批判者是一组描述检查属性的规则 ,而微型检查器则执行代码检查以符合规则。
嵌入了linter的应用程序(例如, cmd / gocritic或golangci-lint )将接收受支持规则的列表,以特定方式对其进行过滤,为每个选定规则创建检查功能,然后在正在研究的程序包上启动每个规则。
添加新检查器的工作可分为三个主要步骤:
- 添加测试。
- 验证本身的执行。
- 为短绒棉添加文档。
我们将使用示例captLocal规则遍历所有这些点,该规则要求缺少以大写字母开头的本地名称。

添加测试
要为新检查添加测试数据,您需要在lint / testdata中创建一个新目录。
每个此类目录都应具有一个positive_tests.go文件,该文件描述了检查应在其上进行的代码示例。 为了测试是否没有误报,在测试中添加了“正确”代码,在该代码中,新检查不应发现任何问题( negative_tests.go )。
范例:
您可以在添加新的lint之后运行测试。
验证实施
创建一个具有检查器名称的文件: lint/captLocal_checker.go
。
按照惯例,所有微_checker
文件都带有_checker
后缀。
package lint
checkerBase是必须嵌入每个检查器中的类型。
它提供了默认的实现,允许您在每个linter中编写更少的代码。
其中,checkerBase包含一个指向lint.context的指针,该指针包含类型信息和有关要检查文件的其他元数据。
upcaseNames
字段将包含一个已知名称的表,我们将使用该表替换为strings.ToLower(name)
版本。 对于地图中未包含的那些名称,建议不要使用大写字母,但不会提供正确的替代名称。
每个实例的内部状态均初始化一次。
仅需要为那些需要进行初步初始化的linter定义Init()
方法。
func (c *captLocalChecker) Init() { c.upcaseNames = map[string]bool{ "IN": true, "OUT": true, "INOUT": true, } }
现在,您需要定义检查功能本身。
对于captLocal
,我们需要检查所有引入新变量的本地ast.Ident
。
为了检查名称的所有本地定义,您应该在检查器中实现带有以下签名的方法:
VisitLocalDef(name astwalk.Name, initializer ast.Expr)
可用的访客接口列表可在文件lint / internal / visitor.go中找到 。
captLocal
实现LocalDefVisitor
。
按照惯例,生成警告的方法通常放在单独的方法中。 很少有例外,但是遵循此规则被认为是良好做法。
添加文件
另一个必要的实现方法是InitDocumentation
:
func (c *captLocalChecker) InitDocumentation(d *Documentation) { d.Summary = "Detects capitalized names for local variables" d.Before = `func f(IN int, OUT *int) (ERR error) {}` d.After = `func f(in int, out *int) (err error) {}` }
通常,只需填写3个字段:
Summary
-用一句话描述验证动作。Before
-更正Before
代码。After
-更正After
代码(不应引起警告)。
文档生成重新生成文档不是新短绒的先决条件,也许在不久的将来,这一步骤将完全自动化。 但是,如果您仍然想检查输出markdown文件的外观,请使用make docs
命令。 docs/overview.md
文件将被更新。
注册一个新的linter并运行测试
最后一点是注册一个新的lint:
addChecker
需要一个指向新的addChecker
的零值的指针。 接下来是可变参数,您可以传递零个或多个描述规则实施属性的属性。
attrSyntaxOnly
是用于在实现中不使用类型信息的attrSyntaxOnly
的可选标记,它允许您在不执行类型检查的情况下运行它们。 golangci-lint
用“快速”标志标记这样的短绒(因为它们运行得更快)。
attrExperimental
是分配给所有新实现的属性。 仅在稳定已实现检查之后才可以删除此属性。
现在,新的linter已通过addChecker注册,您可以运行测试:
乐观合并(几乎)
在考虑请求请求时,我们尝试坚持乐观的合并策略。 这主要表现为接受审稿人可能具有某些(尤其是纯粹主观的)主张的PR。 注入此类补丁后,审阅者会立即进行PR,以纠正这些缺陷,然后将原始补丁的作者添加到CC(副本)中。
在没有完全达成共识的情况下,我们还有两个linter标记可用于避免出现危险信号:
Experimental
:实施可能会有大量的误报,无效(发现问题的根源)或在某些情况下“失败”。 如果使用attrExperimental
属性标记该实现,则可以注入该实现。 有时,在实验的帮助下,那些检查被指示未能从第一次提交中找到好名字。VeryOpinionated
:如果支票可以同时有防御者和敌人,则应该使用属性attrVeryOpinionated
标记它。 这样,我们可以避免拒绝关于代码风格的想法,这些想法可能与某些地鼠的口味不符。
Experimental
是潜在的临时且可修复的实现属性。 VeryOpinionated
是更基本的规则属性,与实现无关。
建议在提交实现之前在github上创建一个[checker-request]
票证,但是如果您已经提交了pull请求,则可以为您打开相应的问题。
有关开发过程的更多详细信息,请参见CONTRIBUTING.md 。
基本规则在“ 主要规则”部分列出。
分词
您不仅可以添加新的linter,还可以参与该项目。
还有许多其他方法:
- 在您的项目或大型/知名的开源项目上尝试一下,并报告误报,误报和其他缺点。 如果您也将有关已发现/已解决问题的注释添加到奖杯页面,我们将不胜感激。
- 为新的检查提出建议。 在我们的跟踪器上创建一个问题就足够了。
- 添加对现有短绒的测试。
go-critic
使用参与其开发的所有程序员的声音来批评您的Go代码。 每个人都可以批评,因此-加入!
