RuboCop
RuboCop is the de facto standard linter for Ruby projects and it's pretty great.
What sets it apart from most linters in other languages is that it goes above and beyond just basic syntax and style checks. It also provides a wide range of rules for enforcing idiomatic API usage, best practices and even performance optimizations. This makes it feel more like a comprehensive code quality tool than just a linter, especially when extending it with plugins for specific domains like Rails, GraphQL and RSpec (the most popular testing library for Ruby).
Here are some examples of the kinds of diffs that its auto-correction features can produce:
# [!code word:reject { |_, v| v.nil? }]
matches = hash.reject { |_, v| v.nil? } # [!code --]
# [!code word:compact]
matches = hash.compact # [!code ++]
# [!code word:map { |key, value| ]
# [!code word:key, value * value]
# [!code word: }.to_h]
hash.map { |key, value| [key, value * value] }.to_h # [!code --]
# [!code word:transform_values { |value| value * value }]
hash.transform_values { |value| value * value } # [!code ++]
rescue UnauthorizedError => e # [!code --]
return error_payload(e) # [!code --]
rescue NotFoundError => e # [!code --]
return error_payload(e) # [!code --]
rescue UnauthorizedError, NotFoundError => e # [!code ++]
return error_payload(e) # [!code ++]
With rubocop-rails ActiveSupport cops enabled, you can get:
# [!code word:t? ? params]
# [!code word:source_type]
# [!code word: "Source"]
params[:source_type].present? ? params[:source_type] : "Source" # [!code --]
# [!code word:ce || "Source"]
params[:source_type].presence || "Source" # [!code ++]
# [!code word:s]
time = 1.days.ago.at_beginning_of_day # [!code --]
time = 1.day.ago.at_beginning_of_day # [!code ++]
With rubocop-rspec enabled, you can get:
# [!code word:should be]
it "should be able to handle it" do # [!code --]
# [!code word:is]
it "is able to handle it" do # [!code ++]
# [!code word:will send]
it "will send the email" do # [!code --]
# [!code word:sends]
it "sends the email" do # [!code ++]
# [!code word:will set]
it "will set a subject" do # [!code --]
# [!code word:sets]
it "sets a subject" do # [!code ++]
Note how the last example not only removes the future tense "will", but also changes "send" to "sends" and "set" to "sets" to make the grammar correct. This might not seem like such a big deal these days, with LLMs being fluent in English, but this predates LLMs and is all heuristic-based. Hallucination proof!
Ongoing controversy
It has taken a very long time for RuboCop to become an accepted standard in the community.
First, in the early days, it didn't provide a lot of auto-correction features, which could make it feel like a nagging nitpicker rather than a helpful pair programmer.
Second, its default settings weren't really based on how the majority of Ruby programmers wrote code, but rather reflected the preferences of its original author and contributors.
At some point, auto-correction got good enough that people could see clear value in starting to use it, which was enough for teams to take the next step and spend the time necessary to configure it to match whatever style they preferred.
I think, somewhat ironically, that it might be a good thing that the default configuration has created enough friction for many teams to actually go through the process of customizing the configuration to their liking. This has ended up serving to preserve the diversity of styles in the Ruby community, which I feel is a more interesting outcome than everyone just conforming to the same style.
For those who actually prefer a fully standardized style though, there are a few popular shared configurations that can be used, such as Standard Ruby.