# The Ruby linter and formatter that serves and protects
RuboCop checks your Ruby code against the community Ruby Style Guide, points out what's off and fixes most of it for you. It knows about bugs, style, layout and complexity, it bends to your team's taste, and it's been at it since 2012.
gem install rubocop~/shop $ rubocop Inspecting 3 files .CW Offenses: lib/greeter.rb:2:7: C: Naming/MethodName: Use snake_case for method names. def sayHello(name) ^^^^^^^^ lib/greeter.rb:3:5: C: [Correctable] Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||. if name.nil? ^^ lib/greeter.rb:4:14: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols. name = "stranger" ^^^^^^^^^^ lib/greeter.rb:6:5: C: [Correctable] Style/RedundantReturn: Redundant return detected. return "Hello, #{name}!" ^^^^^^ lib/order.rb:3:5: W: [Correctable] Lint/UselessAssignment: Useless assignment to variable - discount. discount = 0.1 ^^^^^^^^ 3 files inspected, 5 offenses detected, 4 offenses autocorrectable
~/shop $ rubocop --diff lib/greeter.rb Inspecting 1 file C ... 1 file inspected, 5 offenses detected, 4 offenses corrected --- a/lib/greeter.rb +++ b/lib/greeter.rb @@ -1,8 +1,6 @@ class Greeter def sayHello(name) - if name.nil? - name = "stranger" - end - return "Hello, #{name}!" + name = 'stranger' if name.nil? + "Hello, #{name}!" end end
~/shop $ rubocop -a Inspecting 3 files .CW Offenses: lib/greeter.rb:2:7: C: Naming/MethodName: Use snake_case for method names. def sayHello(name) ^^^^^^^^ lib/greeter.rb:3:5: C: [Corrected] Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||. if name.nil? ^^ lib/greeter.rb:3:12: C: [Corrected] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols. name = "stranger" if name.nil? ^^^^^^^^^^ lib/greeter.rb:4:14: C: [Corrected] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols. name = "stranger" ^^^^^^^^^^ lib/greeter.rb:6:5: C: [Corrected] Style/RedundantReturn: Redundant return detected. return "Hello, #{name}!" ^^^^^^ lib/order.rb:3:5: W: [Corrected] Lint/UselessAssignment: Useless assignment to variable - discount. discount = 0.1 ^^^^^^^^ lib/order.rb:3:5: W: [Corrected] Lint/Void: Literal 0.1 used in void context. 0.1 ^^^ 3 files inspected, 7 offenses detected, 6 offenses corrected
What it checks
Every check is a cop, and cops live in departments. Lint catches code that's probably a bug, Style, Layout and Naming keep a codebase consistent, Metrics flags methods and classes that got too big, and Security spots the usual footguns.
Most cops come straight from a guideline in the style guide, and most of them can fix what they find.
Normally each cop sees one file at a time. Add the optional rubydex gem and set UseProjectIndex: true under AllCops, and RuboCop indexes the whole project first. The index serves 17 cops: Lint/DuplicateMethods catches a method defined twice in different files, others stop flagging code that's fine once they can see the rest of the project, and a few new ones, like Lint/NameTypo, only work with it. It's experimental and needs Ruby 3.2 or newer.
What it fixes
rubocop -a applies the corrections that can't change what your code does. rubocop -A goes further and applies the ones that might, so run it with your tests nearby.
Want to look before you leap? --diff prints the patch and leaves your files alone. The Layout cops make RuboCop a formatter too, and rubocop -x runs just those.
~/shop $ rubocop -A --diff Inspecting 3 files .CW ... 3 files inspected, 7 offenses detected, 6 offenses corrected --- a/lib/greeter.rb +++ b/lib/greeter.rb @@ -1,8 +1,6 @@ class Greeter def sayHello(name) - if name.nil? - name = "stranger" - end - return "Hello, #{name}!" + name = 'stranger' if name.nil? + "Hello, #{name}!" end end --- a/lib/order.rb +++ b/lib/order.rb @@ -1,6 +1,5 @@ class Order def total(items) - discount = 0.1 items.sum(&:price) end end
Make it yours
The defaults follow the style guide, but Ruby has never had one true style and RuboCop doesn't pretend otherwise. Most cops support several styles, and it's all set in a single .rubocop.yml, which can inherit from a shared one.
Adopting it on an old codebase? rubocop --auto-gen-config records every existing offense in .rubocop_todo.yml, so new code meets the standard today and the old debt gets paid off one cop at a time.
For real-world examples, see how Rails, GitLab, Discourse and Mastodon set it up.
# .rubocop.yml inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 3.4 NewCops: enable Style/StringLiterals: EnforcedStyle: double_quotes # we know, we know Metrics/MethodLength: Max: 15 Exclude: - db/migrate/**/*
How it works
RuboCop never runs your code. It parses each file into a syntax tree, with Prism, or with the parser gem for code that targets Ruby older than 3.3. Then it walks the tree once and calls each cop for the kinds of nodes it asked to see.
Most cops are a node pattern and a message. Style/NilComparison asks to see calls to ==, === and nil?, and reports the ones its pattern matches.
Corrections don't touch the tree. A cop rewrites the source where its node came from, and RuboCop parses the result and runs the cops again, until a pass changes nothing. That's how one rubocop -a can sort out code that takes several cops in turn.
# lib/user.rb return if user == nil # the tree RuboCop inspects (if (send (send nil :user) :== (nil)) (return) nil)
# Style/NilComparison def_node_matcher :nil_comparison?, '(send _ {:== :===} nil)' (if (send (send nil :user) :== (nil)) (return) nil)
~/shop $ rubocop lib/user.rb Inspecting 1 file C Offenses: lib/user.rb:1:16: C: [Correctable] Style/NilComparison: Prefer the use of the nil? predicate. return if user == nil ^^ 1 file inspected, 1 offense detected, 1 offense autocorrectable
~/shop $ rubocop -a --diff lib/user.rb Inspecting 1 file C ... 1 file inspected, 1 offense detected, 1 offense corrected --- a/lib/user.rb +++ b/lib/user.rb @@ -1 +1 @@ -return if user == nil +return if user.nil?
Plugins
The core knows Ruby. Plugins teach it about the frameworks and libraries around it. The RuboCop team maintains the ones on the right, and there are plenty more out there, from rubocop-graphql to rubocop-sorbet.
Writing your own cops is easier than it sounds, and a project can keep a few in-house cops for its own conventions.
plugins: - rubocop-performance # faster idioms - rubocop-rails # Rails best practices - rubocop-rspec # RSpec - rubocop-rspec_rails # RSpec + Rails - rubocop-minitest # Minitest - rubocop-capybara # Capybara - rubocop-factory_bot # factory_bot - rubocop-rake # Rakefiles - rubocop-thread_safety # thread safety - rubocop-sequel # Sequel - rubocop-i18n # gettext and rails-i18n
In your editor
RuboCop has a language server built in. Your editor gets offenses as you type, a quick fix for each one, and RuboCop's corrections as a formatter, which most editors can run on save. It's the same RuboCop with the same config your CI runs.
Ruby LSP, which the docs recommend for most editors, runs RuboCop for you. Otherwise point your editor's LSP client at rubocop --lsp, or install the vscode-rubocop extension. RubyMine has RuboCop support of its own.
For editors that just shell out to rubocop, --server keeps a warm process around, so each run starts instantly. It isn't available on Windows or JRuby.
1class Greeter2 def sayHello (Naming/MethodName: Use snake_case for method names. This offense is not autocorrectable.)(name)3 if (Style/IfUnlessModifier: Favor modifier `if` usage when having a single-line body. Another good alternative is the usage of control flow `&&`/`||`.) name.nil?4 name = "stranger" (Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.)Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.RuboCop(Style/StringLiterals)Autocorrect Style/StringLiteralsDisable Style/StringLiterals for this line5 end6 return (Style/RedundantReturn: Redundant `return` detected.) "Hello, #{name}!"7 end8end
1class Greeter2 def sayHello (Naming/MethodName: Use snake_case for method names. This offense is not autocorrectable.)(name)Naming/MethodName: Use snake_case for method names. This offense is not autocorrectable.RuboCop(Naming/MethodName)Disable Naming/MethodName for this line3 name = 'stranger' if name.nil?4 "Hello, #{name}!"5 end6end
In your agent
Coding agents run RuboCop far more often than people do, and everything it prints ends up in their context. rubocop --mcp gives them inspection, autocorrection and cop explanations as tools, answered by a RuboCop that's already loaded. It needs the mcp gem.
From the shell, --format json tells an agent which offenses autocorrection can fix and the edits it would make, --explain tells it what a cop wants when the message isn't enough, and --max-offenses-per-cop keeps an old codebase from flooding its context.
An agent told to make RuboCop pass can disable its way there. Enable the cop that polices rubocop:disable comments with AllowWithReason, and every one of them needs a reason, so whoever reviews the change sees why.
~/shop $ rubocop -f json | jq '.files[1].offenses[1]' { "severity": "convention", "message": "Favor modifier `if` usage when having a single-line body. Another good alternative is the usage of control flow `&&`/`||`.", "cop_name": "Style/IfUnlessModifier", "corrected": false, "correctable": true, "location": { "start_line": 3, "start_column": 5, "last_line": 3, "last_column": 6, "length": 2, "line": 3, "column": 5 }, "correction": { "safe": true, "edits": [ { "start_line": 3, "start_column": 5, "last_line": 5, "last_column": 7, "begin_pos": 39, "end_pos": 83, "replacement": "name = \"stranger\" if name.nil?" } ] } }
~/shop $ rubocop --explain Style/StringLiterals Style/StringLiterals Checks if uses of quotes match the configured preference. Properties Enabled: true Safe: true Autocorrect: safe, applied by -a Added: 0.9 Changed: 0.36 Example: EnforcedStyle: single_quotes (default) # bad "No special symbols" "No string interpolation" "Just text" # good 'No special symbols' 'No string interpolation' 'Just text' "Wait! What's #{this}!" Example: EnforcedStyle: double_quotes # bad 'Just some text' 'No special chars or interpolation' # good "Just some text" "No special chars or interpolation" "Every string in #{project} uses double_quotes" Configuration ConsistentQuotesInMultiline: false EnforcedStyle: single_quotes SupportedStyles: single_quotes, double_quotes References https://rubystyle.guide#consistent-string-literals https://docs.rubocop.org/rubocop/cops_style.html#stylestringliterals
In CI
Offenses can land right on the pull request. --format github turns them into annotations, sarif feeds code scanning and junit the rest of CI. --fail-level decides which severities fail the build, and --changed=main... checks only the files a branch changed since it forked.
When there's too much to read, -f offenses and -f worst show where the offenses pile up.
~/shop $ rubocop -f github ::error file=lib/greeter.rb,line=2,col=7::Naming/MethodName: Use snake_case for method names. ::error file=lib/greeter.rb,line=3,col=5::Style/IfUnlessModifier: Favor modifier `if` usage when having a single-line body. Another good alternative is the usage of control flow `&&`/`||`. ::error file=lib/greeter.rb,line=4,col=14::Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols. ::error file=lib/greeter.rb,line=6,col=5::Style/RedundantReturn: Redundant `return` detected. ::error file=lib/order.rb,line=3,col=5::Lint/UselessAssignment: Useless assignment to variable - `discount`.
~/shop $ rubocop -f offenses 1 Lint/UselessAssignment [Safe Correctable] 1 Naming/MethodName 1 Style/IfUnlessModifier [Safe Correctable] 1 Style/RedundantReturn [Safe Correctable] 1 Style/StringLiterals [Safe Correctable] -- 5 Total in 2 files
~/shop $ rubocop -f worst 4 lib/greeter.rb 1 lib/order.rb -- 5 Total in 2 files
News
A new release lands every few weeks. The changelog has every detail, and the bigger ones get a write-up on Meta Redux.
- 2026-10-10 RuboCop 1.92: Agents Welcomepost
- 2026-10-10 1.92.0: 11 new features, 328 bug fixes, 5 changes
- 2026-09-11 RuboCop 1.91: The Journey to 2.0 Beginspost
-
2026-09-10
1.91.0: 10 new features, 55 bug fixes, 13 changes
new: Lint/MisplacedMagicComment -
2026-08-24
1.90.0: 10 new features, 35 bug fixes, 10 changes
new: Lint/ArgumentMismatch, Lint/SuperArgumentMismatch, Style/TimeNow, ... - 2026-08-05 RuboCop 1.89: Project-Wide Analysis with Rubydexpost
History
I started RuboCop in April 2012 to make it easy to actually follow the Ruby Style Guide, which I'd started a few months earlier. Fourteen years, two parsers and over 1,100 contributors later, it's how a big chunk of the Ruby world keeps its code in shape.
- afbead3 2012-04-21 Initial commit to rubocop.
-
237292d
2013-05-28
0.8 moves onto the
parsergem - 453e12c 2013-07-01 0.9 adds autocorrection and formatters
- ------- 2018-05-31 RuboCop moves to its own GitHub org (RubyKaigi 2018)
- f74c118 2020-10-21 1.0, exactly 8.5 years after the first commit
- 80485d2 2023-06-23 1.53 ships a built-in language server
- 96989aa 2025-02-14 1.72 introduces the plugin system
- 9c2bc8e 2025-03-26 1.75 makes Prism the default parser for Ruby 3.4+
- 6eee0af 2026-09-10 1.91 starts the road to 2.0
- 4172ce5 2026-10-10 1.92 makes RuboCop a better fit for coding agents
Who's behind it
RuboCop is free and maintained by a small core team of volunteers, with help from thousands of contributors. If your company's code goes through RuboCop every day, consider giving something back. It's what keeps the lights on and the releases coming.
Bozhidar Batsov
Jonas Arvidsson
Ted Johansson
Koichi Ito
Maxim Krizhanovski
Benjamin Quorning
Marc-André Lafortune
Daniel Vandersluis
# organizations backing RuboCop on Open Collective
# and individuals