Rubocop:满足定制需求的定制警察

我正在做一个非常依赖特性标志的项目。每当我们添加一个新特性或修复一个bug时,我们都会为它添加一个标志。这是它的样子。

我们在YAML文件中列出了我们的标志:

#配置/ feature_flags.ymltwo_factor_authentication启用2身份验证所有用户”fix_1234检查尼尔斯用户# can_vote ?”#……

我们在代码中使用它们:

用户defcan_vote吗?如果功能启用?: fix_1234年龄礼物吗?&&年龄>=16其他的年龄>=16结束结束结束

这很好,因为我们可以在生产中切换标志,修复bug,或者为不同的客户端提供不同的功能。

清理旗帜

如果标志指向错误修复(如上面的例子),当QA批准它们并将补丁投入生产时,我们必须清理标志。

配置/ feature_flags.ymltwo_factor_authentication:“为所有用户启用了2因素认证”-fix_1234:“检查用户#can_vote是否为nil ?”...
类用户def can_vote?如果Feature.enabled?(:fix_1234) - age.present?&& age >= 16 - else - age >= 16 - end . / /年龄+ age.present ?&&年龄>= 16结束结束

虽然这工作得很好,但很容易从.yml文件,忘记在代码的某个地方删除它们的使用。测试覆盖率在这里很有帮助,但是如果我们可以做一些静态检查呢?

这时Rubocop就上场了。我们可以创建一个自定义警察为我们检查!

原理很简单:我们需要搜索类似于Feature.enabled ?(<一些制旗>)并检查YAML文件中是否存在该标志。我们可以通过greping实现这一点,但这可能会很困难,因为代码的风格可能不同(缩进、使用或不使用括号,等等)。

我们要做的是直接在解析的代码中搜索模式。就像在AST上的grepping。

grep的什么

当Ruby读取您的代码时,它将代码从纯文本转换为名为抽象语法树(AST).它基本上是一棵表示Ruby如何计算代码的树。

让我们看一下表达式的AST3 * 5 + 1

代码AST  ‾‾‾‾ ‾‾‾ ___________ ________ ( + ) | 3 * 5 + 1 | = > |解析器  | => / \ ‾‾‾‾‾‾‾‾‾‾‾ ‾‾‾‾‾‾‾‾ ( * ) ( 1) / \ (3) (5)
图1 - AST的代码

虽然ASCII艺术很有趣,但用这种方式来表示树的文本显然不实用。更好的方法是使用S-expressions.如果你已经知道,这很简单口齿不清

中的树的s表达式表示图1可以是这样的:

+3.51

提示:如果你想了解更多关于语言(尤其是解释性语言)是如何工作的,请查看令人惊异的制作翻译通过鲍勃Nystrom

哦,好吧。grep AST

回到我们的话题…Rubocop允许我们以与正则表达式相同的方式用s表达式来grep AST。我们要找出这个模式的s表达式是什么Feature.enabled ?(<一些制旗>).这里是最好的部分:我们不需要直接知道这些.宝石被称为解析器,它与Rubocop一起发行,为我们完成了这项工作:

ruby-parse- e“Feature.enabled ? (some_flag):“发送常量:功能:启用?信谊: some_flag))

这里我们使用的是硬编码符号: some_flag,但是我们要把它换成$ _,这意味着Rubocop将捕获符号值并为我们提供它。下面是我们要用来搜索代码的最后一个模式:

发送常量:功能:启用?信谊$ _))

写一个警察

要创建一个自定义Rubocop cop,我们需要创建一个子类RuboCop::警察::Base.然后我们需要在阅读代码时跳到Rubocop运行的钩子上,比如on_classon_ifon_send.在这种情况下,我们将使用on_send

模块CustomCopsUnknownFeatureFlag<RuboCop::警察::基地defon_send节点#使用AST节点进行操作结束结束结束

现在,我们使用Rubocop的宏为前面指定的模式定义一个自定义匹配器def_node_matcher.它会过滤掉所有我们不感兴趣的节点。请注意,匹配器如何将捕获的符号返回给块,以便我们可以使用它。

模块CustomCopsUnknownFeatureFlag<RuboCop::警察::基地def_node_matcher: on_feature_flag< < ~模式发送(const nil:Feature):启用?(信谊$ _))模式RESTRICT_ON_SEND:启用?].冻结#优化:不要调用' on_send '除非方法名在这个列表中defon_send节点on_feature_flag节点|国旗|#用国旗做些事情结束结束结束结束

现在,我们检查这个标志是否存在于我们的文件中,如果不存在,则注册为违规:

模块CustomCopsUnknownFeatureFlag<RuboCop::警察::基地def_node_matcher: on_feature_flag< < ~模式发送(const nil:Feature):启用?(信谊$ _))模式味精"未知特性标志' %<标志>s ' "冻结FEATURE_FLAGSYAMLload_file“配置/ feature_flags.yml”).RESTRICT_ON_SEND:启用?].冻结#优化:不要调用' on_send '除非方法名在这个列表中defon_send节点on_feature_flag节点|国旗|下一个如果FEATURE_FLAGS包括什么?国旗to_s#已知的旗帜,继续register_offense节点国旗结束结束私人defregister_offense节点国旗消息格式味精国旗:国旗add_offense节点信息:消息结束结束结束

它还活着!

就是这样!中可以要求自定义类.rubocop.yml和其他警察一起查

# .rubocop.yml需要-/ lib / custom_cops / unknown_feature_flag.rb

或者单独运行:

rubocop- r/ lib / custom_cops / unknown_feature_flag.rb——只有CustomCops / UnknownFeatureFlag……F app / secret_file。rb:45:10: C: CustomCops/UnknownFeatureFlag: Unknown feature flag如果Feature.enabled吗?: the_cake_is_a_lie^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我们所做的只是冰山一角。我们甚至可以让警察autocorrectable删除旧代码。它可以写成这样

def喷火如果功能启用?: some_flag_we_cleaned_upnew_code其他的old_code结束结束

作为

def喷火new_code结束

经验教训

这里的重点不是学习如何写一个定制的Rubocop cop,但是知道如何编写一个可以避免手工检查而节省您/您的团队时间。

有关如何在gem中编写、测试和捆绑自定义警察的更多细节,请参阅此极为精彩的文章邪恶的火星人和Rubocop官方指南