首页
/ RuboCop 开发指南:如何编写自定义代码检查规则

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:测试文件

同时会自动:

  1. lib/rubocop.rb 中添加 require 语句
  2. 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

在真实项目中测试

两种常用方法:

  1. 从 RuboCop 仓库直接运行:
    exe/rubocop ~/your/other/codebase
    
  2. 在其他项目的 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 生态贡献高质量的检查规则。