RuboCop 开发指南:如何编写自定义代码检查规则
2025-07-06 03:50:13作者:董灵辛Dennis
前言
RuboCop 是一个强大的 Ruby 代码静态分析工具,它不仅可以检查代码风格问题,还能检测潜在的错误和不良实践。本文将深入讲解如何为 RuboCop 开发自定义检查规则(称为 "cop"),从基础概念到高级功能实现。
创建新 Cop
初始化 Cop 模板
RuboCop 提供了便捷的 Rake 任务来生成 Cop 模板:
bundle exec rake 'new_cop[Department/Name]'
执行后会生成以下文件:
lib/rubocop/cop/department/name.rb
:Cop 主文件spec/rubocop/cop/department/name_spec.rb
:测试文件
同时会自动:
- 在
lib/rubocop.rb
中添加 require 语句 - 在
config/default.yml
中添加默认配置
理解抽象语法树(AST)
AST 基础
RuboCop 使用 parser
库将 Ruby 代码转换为抽象语法树。理解 AST 是开发 Cop 的关键。
安装 parser 工具:
gem install parser
查看代码的 AST 表示:
$ ruby-parse -e 'name = "John"'
(lvasgn :name
(str "John"))
每个括号表示一个 AST 节点,第一个元素是节点类型,其余是子节点。
AST 调试技巧
使用 RuboCop 的 REPL 调试 AST:
$ bin/console
示例调试:
code = '!something.empty?'
source = RuboCop::ProcessedSource.new(code, RUBY_VERSION.to_f)
node = source.ast
# => s(:send, s(:send, s(:send, nil, :something), :empty?), :!)
节点常用属性:
node.type
:节点类型node.children
:子节点数组node.source
:原始代码
实现 Cop 逻辑
使用 NodePattern 匹配节点
NodePattern 是 RuboCop 提供的 DSL,用于简洁地匹配 AST 节点。
示例模式匹配:
NodePattern.new('(send (send (...) :empty?) :!)').match(node)
# 匹配 "!<expression>.empty?" 形式的代码
实现 Cop 类
完整 Cop 实现示例:
module RuboCop
module Cop
module Style
# 检查并建议将 !array.empty? 替换为 array.any?
class SimplifyNotEmptyWithAny < Base
MSG = '使用 `.any?` 并移除否定部分'.freeze
RESTRICT_ON_SEND = [:!].freeze # 优化:仅当方法名为 ! 时才调用 on_send
def_node_matcher :not_empty_call?, <<~PATTERN
(send (send $(...) :empty?) :!)
PATTERN
def on_send(node)
expression = not_empty_call?(node)
return unless expression
add_offense(node)
end
end
end
end
end
编写测试用例
测试文件示例:
describe RuboCop::Cop::Style::SimplifyNotEmptyWithAny, :config do
it '检测 !a.empty? 用法' do
expect_offense(<<~RUBY)
!array.empty?
^^^^^^^^^^^^^ 使用 `.any?` 并移除否定部分
RUBY
end
it '不检测 .any? 或 .empty? 直接用法' do
expect_no_offenses(<<~RUBY)
array.any?
array.empty?
RUBY
end
end
自动修正功能
实现自动修正
扩展 AutoCorrector
模块并实现修正逻辑:
extend AutoCorrector
def on_send(node)
expression = not_empty_call?(node)
return unless expression
add_offense(node) do |corrector|
corrector.replace(node, "#{expression.source}.any?")
end
end
测试自动修正
测试修正功能:
it '自动修正 !a.empty?' do
expect_offense(<<~RUBY)
!array.empty?
^^^^^^^^^^^^^ 使用 `.any?` 并移除否定部分
RUBY
expect_correction(<<~RUBY)
array.any?
RUBY
end
防止修正冲突
使用 IgnoredNode
处理嵌套修正:
include IgnoredNode
def on_send(node)
return unless some_condition?(node)
add_offense(node) do |corrector|
next if part_of_ignored_node?(node)
corrector.replace(node, "...")
end
ignore_node(node)
end
配置选项
添加可配置参数
在 .rubocop.yml
中:
Style/SimplifyNotEmptyWithAny:
Enabled: true
ReplaceAnyWith: "size > 0"
在 Cop 中使用配置:
def on_send(node)
expression = not_empty_call?(node)
return unless expression
add_offense(node) do |corrector|
replacement = cop_config['ReplaceAnyWith'] || 'any?'
corrector.replace(node, "#{expression.source}.#{replacement}")
end
end
文档规范
编写 Cop 文档
每个 Cop 都需要清晰的文档和示例:
module Department
# 描述你的 Cop。包括所有配置选项的描述。
#
# @example EnforcedStyle: bar
# # 关于此选项的描述
#
# # bad
# bad_example1
# bad_example2
#
# # good
# good_example1
# good_example2
#
class YourCop
# ...
文档要点:
- 配置按键按字母顺序排列
- 标明默认值
(default)
- 在
class YourCop
前留一个空行 - 所有示例使用有效的 Ruby 语法
测试 Cop
在真实项目中测试
两种常用方法:
- 从 RuboCop 仓库直接运行:
exe/rubocop ~/your/other/codebase
- 在其他项目的 Gemfile 中指定本地路径:
gem 'rubocop', path: '/full/path/to/rubocop'
使用 --only
参数只运行特定 Cop:
rubocop --only Style/SimplifyNotEmptyWithAny
测试运行
默认使用 Parser gem 运行测试:
bundle exec rake spec
支持 Prism 解析器(实验性):
PARSER_ENGINE=parser_prism bundle exec rake spec
总结
开发 RuboCop 自定义检查规则需要掌握 AST 知识和 NodePattern 用法。本文涵盖了从创建 Cop 模板、实现检查逻辑、添加自动修正、配置选项到编写文档和测试的完整流程。通过遵循这些最佳实践,你可以为 RuboCop 生态贡献高质量的检查规则。