RuboCop 扩展开发完全指南:从新建 Cop 到 Preview 机制与 CI 规范

RuboCop 扩展开发完全指南:从新建 Cop 到 Preview 机制与 CI 规范 RuboCop 扩展开发完全指南从新建 Cop 到 Preview 机制与 CI 规范【免费下载链接】rubocopA Ruby static code analyzer and formatter, based on the community Ruby style guide.项目地址: https://gitcode.com/GitHub_Trending/rub/rubocop导读本文基于 RuboCop 仓库根目录的 AGENTS.mdAI Agent 开发指南也是CLAUDE.md引用的同一份指南整理而成。RuboCop 是一个基于社区 Ruby 风格指南的 Ruby 静态代码分析器与格式化工具本文面向两类读者一是准备为 RuboCop 贡献代码的开发者二是希望理解其内部机制Cop 生命周期、Preview 行为、Severity 分级、测试与 Changelog 规范的进阶用户。读完本文你将掌握如何用一行 rake 命令脚手架一个新 Cop如何按约定实现其类结构、编写符合 CI 校验的测试与 Changelog 条目以及pending、preview、FailLevel等配置背后的源码级原理。文中所引路径均以仓库根目录为基准可直接对照源码深入研读。注意仓库中的CLAUDE.md仅含一行AGENTS.md即指示读取根目录的 AGENTS.md 作为代理指南因此本文主体即该文件的技术内容。一、先读这四份材料贡献前的准备AGENTS.md 明确要求在贡献之前必须先阅读CONTRIBUTING.md项目贡献规范涵盖 Issue/PR 流程、代码风格与提交要求development docsRuboCop 官方开发文档仓库内的 Antora 文档源码即位于 docs/modules/ROOT/pages可离线阅读development.adoc、versioning.adoc等章节。理解 RuboCop 的定位很重要它既是静态分析器lint也是格式化器formatter所有规则都以 Cop 为单位组织分布在Bundler、Gemspec、Layout、Lint、Metrics、Migration、Naming、Security、Style、InternalAffairs十个部门Department中。二、Essential Commands日常开发必用命令命令作用bundle exec rake完整 CIcodespell 拼写检查 文档语法检查 specs 自 lintRuboCop lint 自身bundle exec rake spec运行 specs基于 Parser 解析器bundle exec rake prism_spec运行 specs基于 Prism 解析器bundle exec rake internal_investigation仅运行 RuboCop 对自身的 lintbundle exec rubocop --only Department/CopName仅启用单个 Cop 进行 lint关键约定提交 PR 之前必须完整运行bundle exec rake。AGENTS.md 的 Common Mistakes 第 10 条特别指出只跑部分测试会漏掉 lint 失败与文档语法失败。原因在于 CI 中documentation_syntax_check任务会解析每个example代码块且 specs 以STRICT_WARNINGS1运行见后文 Common Mistakes 第 13 条这两类问题在局部测试中不会暴露。三、Project Layout仓库结构速览lib/rubocop/cop/department/cop_name.rb # Cop 源码 spec/rubocop/cop/department/cop_name_spec.rb # Cop 测试 config/default.yml # 每个 Cop 的默认配置 changelog/ # 待发布 Changelog 条目一个文件一条 lib/rubocop/cop/department.rb # 部门模块内含 register_cop 指令 # 用于懒加载由生成器自动更新几点源码层面的说明懒加载注册lib/rubocop/cop/department.rb中的register_cop指令由生成器自动维护。其底层机制可在 lib/rubocop/cop/registry.rb 与 lib/rubocop/cop/lazy_loader.rb 中查看——Cop 只有在被实际需要时才加载避免启动时加载全部数百个 Cop。默认配置config/default.yml 是每个 Cop 的默认配置总表含Enabled、Severity、FailLevel等本次仓库中共有 161 处Enabled: pending条目说明相当数量的新 Cop 以 pending 状态发布见第四节。部门划分Bundler、Gemspec、Layout、Lint、Metrics、Migration、Naming、Security、Style、InternalAffairs其中InternalAffairs是 RuboCop 用于约束自身代码规范的 Cop 部门。四、Creating a New Cop脚手架一行生成bundle exec rake new_cop[Department/CopName]执行后会自动生成Cop 源文件、spec 测试文件、config/default.yml配置项以及在部门模块中插入register_cop指令用于懒加载。该任务定义在 tasks/new_cop.rake 中其内部调用 lib/rubocop/cop/generator.rb通过RuboCop::Cop::Badge.parse(cop_name)解析Department/CopName徽章调用write_source/write_spec写源文件与测试文件若部门是InternalAffairs仅注入 require 指令到 lib/rubocop/cop/internal_affairs.rb不加入config/default.yml因为 InternalAffairs Cop 单独按需加载其他部门则调用inject_registration注册指令与inject_config默认配置项。生成之后还需按顺序完成四步更新config/default.yml中的 description实现 Cop 逻辑编写 specs添加 Changelog 条目bundle exec rake changelog:new。Cop 类结构模板# frozen_string_literal: true module RuboCop module Cop module Style # One-line summary starting with a verb (e.g. Checks for …, Enforces …). # Additional detail paragraph(s) if needed. # # safety # Explain why autocorrect may be unsafe, or delete this section. # # example # # bad # bad_code # # # good # good_code # class MyCop Base extend AutoCorrector MSG Use #good_method instead of #bad_method. RESTRICT_ON_SEND %i[bad_method].freeze # !method bad_method?(node) def_node_matcher :bad_method?, ~PATTERN (send nil? :bad_method ...) PATTERN def on_send(node) return unless bad_method?(node) add_offense(node) do |corrector| corrector.replace(node, good_method) end end alias on_csend on_send end end end end关键约定逐条解读RESTRICT_ON_SEND声明方法名列表使on_send只在这些方法出现时被回调是性能优化手段。只要使用on_send就必须定义Common Mistakes第 5 条。其基类默认值在 lib/rubocop/cop/base.rb 中定义为空集合Set[].freeze。alias on_csend on_send处理安全导航调用.。凡是定义on_send的 Cop 都应加上除非明确不适用于安全导航Common Mistakes 第 1、12 条遗漏会导致.形式漏检。alias on_numblock on_block/alias on_itblock on_block处理数字参数块_1与it参数块。凡是定义on_block的 Cop 都应加上Common Mistakes 第 2 条。extend AutoCorrector当 Cop 提供自动修正时声明Common Mistakes 第 9 条。其实现位于 lib/rubocop/cop/autocorrect_logic.rb 与 lib/rubocop/cop/corrector.rbadd_offense传入的corrector块提供replace、insert_before等操作。def_node_matcher/def_node_searchAST 模式匹配 DSL。优先用它而非手写node.type :send判断Common Mistakes 第 11 条。每个 matcher 上方必须配!methodYARD 标签Common Mistakes 第 6 条。YARDexample每个 Cop 至少一对# bad/# good示例且必须是合法 Ruby 语法——CI 的文档语法检查会实际解析它们Common Mistakes 第 3 条。Cop 描述YARD 注释首行必须以动词开头、以句号结尾的完整句子例如# Checks for ...而非# Check for ...Common Mistakes 第 4 条。五、Preview非稳定行为的灰度通道AllCops: Preview或命令行--preview是面向尚未准备好成为默认行为的变更的 opt-in 通道。Cop 通过preview?方法读取该开关。何时使用 PreviewCop 的默认值将在下一个大版本改变Enabled状态、Max阈值或EnforcedStyle在config/default.yml的 Cop 条目下新增Preview段并写入新值AllCops也可以携带一个。这不需要写代码——配置加载器在启用 preview 时应用该段否则丢弃。这是最常见的情况。现有 Cop 开始报告之前漏掉的某种情况且该变更争议较大直接对所有用户开启不礼貌时。现有 Cop 需要以不同方式修正而新修正方式需要真实世界暴露后再成为默认。新 Cop 连pending都嫌太投机以Enabled: preview发布仅 opt-in并且与 pending Cop 不同永远不会被提示需要做决定。何时不要用 Preview纯 bug 修复直接发布即可。新 Cop 且预期最终默认开启那是Enabled: pending的用途。判断口诀你已确定它应成为默认 →pending你在请用户帮忙验证 →preview。源码级原理preview?的开关判断在 lib/rubocop/config.rb 的Config#preview?中实现命令行options[:preview]优先未指定时回落到AllCops下的Preview: true配置。也就是说--preview/--no-preview优先于配置文件。命令行选项定义在 lib/rubocop/options.rboption(opts, --[no-]preview)帮助文本为 Opt in to unstable behavior: cops that areEnabled: preview, and changes to existing ...。在 lib/rubocop/cop/registry.rb 中enabled_preview_cop?要求Enabled preview且config.preview?(options)为真才启用而enabled_pending_cop?则处理Enabled pending并支持--disable-pending-cops/--enable-pending-cops两个选项覆盖。配置与代码示例# config/default.yml: 仅改默认值无需写代码 Style/Documentation: Enabled: true Preview: Enabled: false# Cop 内部的行为变更用 preview? 分支 def on_send(node) return unless offense?(node) return if node.csend_type? !preview? add_offense(node) end测试两个分支由于稳定路径才是大多数用户运行的环境specs 必须覆盖两条路径context when preview is enabled do let(:all_cops_config) { super().merge(Preview true) } it registers an offense for the safe-navigation form do # ... end end最后记住Preview 行为按契约即不稳定可在任意版本中变更或撤回。完整生命周期见 docs/modules/ROOT/pages/versioning.adoc。六、Severity严重级别体系部门默认严重级别Cop 的默认 severity 来自其部门定义在 lib/rubocop/cop/base.rb 的DEPARTMENT_SEVERITIES常量中DEPARTMENT_SEVERITIES { Lint: :warning, Security: :warning, Metrics: :refactor }.freeze即Lint与Security报warningMetrics报refactor其余部门Bundler、Gemspec、Layout、Naming、Style、Migration、InternalAffairs 等一律convention。当 Cop 未显式指定 severity 时lib/rubocop/cop/base.rb 通过DEPARTMENT_SEVERITIES.fetch(self.class.department, :convention)兜底。不要在config/default.yml中为 Cop 添加Severity:除非它确实与部门默认不同——spec/project_spec.rb 会拒绝与默认值重复的行。现存少量例外都是混在非 Lint 部门里的 lint 风格 Cop如Bundler/InsecureProtocolSource以及三个原本属于Lint的Layout对齐 Cop。FailLevel 与 --fail-levelAllCops: FailLevel默认refactor是导致运行失败的最低严重级别低于它的 offense 仍会报告但不使运行失败。--fail-level命令行选项可覆盖它。在Preview下FailLevel变为warning样式 offense 仍被报告但不再使构建失败Lint与Security的 offense 仍然会使构建失败。这在 config/default.yml 中有完整注释FailLevel: refactor以及AllCops下的Preview:段内FailLevel: warning命令行选项见 lib/rubocop/options.rb。七、Writing Specs测试约定# frozen_string_literal: true RSpec.describe RuboCop::Cop::Style::MyCop, :config do it registers an offense when using #bad_method do expect_offense(~RUBY) bad_method(foo) ^^^^^^^^^^^^^^^ Use #good_method instead of #bad_method. RUBY expect_correction(~RUBY) good_method(foo) RUBY end it does not register an offense when using #good_method do expect_no_offenses(~RUBY) good_method(foo) RUBY end end断言原语说明expect_offense^脱字符标记 offense 范围必须与违规代码精确对齐消息跟在最后一个脱字符之后。允许在 heredoc 中用%{variable}插值动态值用_{variable}作为 offense 范围占位符。expect_correction自动修正后的期望源码必须与expect_offense在同一 example 中紧随其后。expect_no_offenses断言无违规。这三个方法的实现位于 lib/rubocop/rspec/expect_offense.rbexpect_offense(source, file nil, severity: nil, chomp: false, **replacements)、expect_correction(correction, loop: true, source: nil)、expect_no_offenses(source, file nil)测试辅助工具集在 spec/support 与 lib/rubocop/rspec/cop_helper.rb。版本与配置技巧用 RSpec metadata 标签如:ruby27、:ruby34设定测试的目标 Ruby 版本配置通过let(:cop_config) { { EnforcedStyle bar } }注入。八、Changelog Entries条目规范每个对用户可见的变更都需要 Changelog 条目通过 rake 任务生成bundle exec rake changelog:fix # Bug fix bundle exec rake changelog:new # New feature bundle exec rake changelog:change # Changed behavior先提交再生成任务从最近一次提交标题派生条目文本因此要先 commit条目标题即提交标题。格式为单行* [#123](https://github.com/rubocop/rubocop/issues/123): Description. ([username][])必须以([username][])结尾。spec/project_spec.rb 在 CI 中校验条目格式。纯内部变更无用户可见效果的 refactor可以跳过条目。条目应与它描述的变更在同一提交中禁止单独提交 changelog-only 的提交包括事后补 PR 编号的 follow-up——通过 amend 把条目并入所属提交。生成后再 amendrake changelog:*以最后一次提交标题命名文件先提交才能得到正确的文件名。不要手工创建 changelog 文件请用 rake 任务以获得正确的文件名格式Common Mistakes 第 8 条。本仓库中 changelog/ 目录下已存在一批待发布条目如fix_an_error_for_style_hash_syntax_no_mixed_keys_*.md可作为格式参考。九、PR and Commit Conventions存在对应 Issue 时提交消息加[Fix #N]前缀。每个独立的修复应有独立的逻辑提交。当 PR 包含多个无关修复多个 Cop、多个误报时各自独立提交并附各自的 Changelog 条目而非全部 squash 成一个。只有属于同一个修复的提交才能 squash。Changelog 条目必须与所描述的变更在同一提交内。push 之前必须运行bundle exec rake并确保通过。十、Common Mistakes16 个高频踩坑点缺少alias on_csend on_send—— 检查on_send的 Cop 必须同时处理安全导航除非明确不适用。缺少alias on_numblock on_block/alias on_itblock on_block—— 检查on_block的 Cop 必须同时处理数字参数块与it参数块。YARD 示例中 Ruby 语法非法—— CI 的documentation_syntax_check会解析每个example块只能用合法语法。Cop 描述不是完整句子—— 必须以动词开头、以句号结尾如# Checks for ...而非# Check for ...。缺少RESTRICT_ON_SEND—— 使用on_send时务必定义。缺少!methodYARD 标签—— 每个def_node_matcher/def_node_search上方都需要。忘记 Changelog 条目—— CI 会标记。手工创建 changelog 文件—— 应使用 rake 任务获得正确的文件名格式。缺少extend AutoCorrector—— 若在add_offense中提供corrector块则必须声明。没有运行完整bundle exec rake—— 部分测试会漏掉 lint 与文档语法失败。硬编码节点类型而非使用模式匹配器—— 优先def_node_matcher而非手写node.type :send。只测send不测csend—— 若 alias 了on_csend必须编写覆盖.操作符的 specs。Spec stub 继承RuboCop::Cop::Cop—— 继承已废弃的类会发出警告而 CI 以STRICT_WARNINGS1运行 specs会把警告变成本地看不到的失败。应在Base上用stub_cop_class创建 stub Cop并从cop.send(:complete_investigation).offenses读取 offensesBase#offenses是有意抛错的。用--only测试 Cop 是否启用——--only会强制启用指定 Cop无视Enabled、pending 或 preview。要测试启用状态应不带--only运行并统计输出中该 Cop 的 offense 数。提交重新生成的 Cop 文档——rake update_cops_documentation会重写 docs/modules/ROOT/pages/cops_*.adoc并吸收自上次发布以来合并的所有 Cop 的漂移。这些文件在发布时重新生成不应包含在 PR 中。猜错 Antora 锚点—— 手册中跨页xref锚点是标题转小写并去掉所有非字母字符后的结果如#allowmultilinefinalelement而非 Asciidoctor 默认的_前缀形式。拿不准时在标题上方放显式[#my-anchor]。十一、给 AI Agent 的实践清单将 AGENTS.md 的要点浓缩为可执行清单适用于人类开发者与 AI Agent 协作场景动工前读 CONTRIBUTING.md 与 development.adoc用bundle exec rake new_cop[Department/CopName]脚手架按模板实现 Cop遵守RESTRICT_ON_SEND、alias on_csend on_send、!method等约定需要灰度时优先Preview段无需代码代码内用preview?分支并双路径测试severity 遵循部门默认仅在真正偏离时显式声明用expect_offense/expect_correction/expect_no_offenses编写 specs覆盖send与csend先提交再rake changelog:fix|new|change生成条目并 amend 进同一提交push 前完整运行bundle exec rake。延伸阅读CONTRIBUTING.md贡献流程与社区约定docs/modules/ROOT/pages/development.adoc官方开发文档docs/modules/ROOT/pages/versioning.adoc版本策略与 Preview 生命周期config/default.yml全部 Cop 的默认配置含 pending / preview 条目lib/rubocop/cop/base.rbCop 基类DEPARTMENT_SEVERITIES与preview?的定义处lib/rubocop/cop/registry.rbenabled_pending_cop?/enabled_preview_cop?的启用逻辑lib/rubocop/rspec/expect_offense.rb测试断言原语实现tasks/new_cop.rake 与 lib/rubocop/cop/generator.rbCop 脚手架生成器【免费下载链接】rubocopA Ruby static code analyzer and formatter, based on the community Ruby style guide.项目地址: https://gitcode.com/GitHub_Trending/rub/rubocop创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考