Filipin.eu

Željko Filipin's blog.
Home Blog Tags License

View on GitHub
22 December 2014

RuboCop

by Željko Filipin

RuboCop logo

RuboCop logo CC BY-NC 4.0

TLDR

RuboCop (GitHub, rubygems, ruby-toolbox) is a “Ruby static code analyzer”. Over 1 million downloads at rubygems.org. It’s popularity has doubled this year. It is the second most pupular tool in the code metrics category.

The problem

From community-driven Ruby coding style guide prelude:

One thing has always bothered me as a Ruby developer - Python developers have a great programming style reference (PEP-8) and we never got an official guide, documenting Ruby coding style and best practices. And I do believe that style matters.

At work, with just a few exceptions here and there, all code is reviewed (by somebody that did not write it) before it is merged into master branch. It is really useful if you can focus on giving meaningful comments when reviewing code, instead of getting distracted by trivial stuff like formatting.

The solution

RuboCop. Make it run after every commit and it will complain if anything obvious is wrong, so humans can focus on what the code should do.

Setup

The installation is trivial and explained in the readme file, so I will not cover it here.

Let’s pick a Ruby repository and make the code beautiful. For inspiration, let’s take a look at GitHub trending Ruby repositories for the last month. The most interesting one to me was jekyll, the tool I am using to write this blog post.

The first thing to do (after cloning a repository) is to run rubocop, or bundle exec rubocop (if you are using Bundler). If you run it in a repository that RuboCop has already cleaned up (for example, rubocop repository), output will look similar to this:

$ bundle exec rubocop
Inspecting 532 files
...
532 files inspected, no offenses detected

Things get more interesting if you run it in a repository that is not already cleaned up, for example jekyll:

$ rubocop
Inspecting 95 files
...
95 files inspected, 3930 offenses detected

3930 offenses! Where to even start? There is a nice command-line option:

--auto-gen-config Generate a configuration file acting as a TODO list.

Let’s try it:

$ rubocop --auto-gen-config
Inspecting 95 files
...
95 files inspected, 3930 offenses detected
Created .rubocop_todo.yml.
Run `rubocop --config .rubocop_todo.yml`, or
add inherit_from: .rubocop_todo.yml in a .rubocop.yml file.

Take a look at the file, it should look similar to this:

# This configuration was generated by `rubocop --auto-gen-config`
# on 2014-12-22 17:14:22 +0100 using RuboCop version 0.28.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the codebase.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 1
Lint/AmbiguousOperator:
  Enabled: false
...

For now we will ignore the contents of the file. RuboCop createad .rubocop_todo.yml file that lists all violations. The file on purpose has todo in the name, since it is supposed to be short lived, until the problems are resolved, not permanent.

Let’s do as suggested. All that needs to be done is to create the permanent configuration file named .rubocop.yml. The only contents for now will be a note to inherit everything from the todo file: inherit_from: .rubocop_todo.yml.

$ echo "inherit_from: .rubocop_todo.yml" > .rubocop.yml

Let’s run RuboCop again.

$ rubocop
Inspecting 95 files
...
95 files inspected, no offenses detected

At this point, RuboCop ignores all existing offenses, but it would complain if new offenses are introduced. Commit what you have so far.

Continuous Integration

This is a great time for setting up RuboCop to run after every commit to the repository. Since there are a lot of ways you could do that, let’s try setting up RuboCop to run in TravisCI.

It is as simple as adding gem "rubocop" to the Gemfile and adding bundle exec rubocop to .travis.yml.

Clean up

The next step is cleaning up. Open the .rubocop_todo.yml file and take a closer look. Find an offense with low offense count (ideally 1) that supports --auto-correct. This one looks good:

# Offense count: 1
# Cop supports --auto-correct.
Lint/DeprecatedClassMethods:
  Enabled: false

Delete the lines and run RuboCop again, this time with -a (or --auto-correct) flag.

$ rubocop -a

Inspecting 95 files
...W...

Offenses:

lib/jekyll/configuration.rb:144:16: W: [Corrected] File.exists? is deprecated in favor of File.exist?.
  File.exists? Jekyll.sanitized_path(source(override), "_config.#{ext}")
       ^^^^^^^

95 files inspected, 1 offense detected, 1 offense corrected

Rubocop says it fixed the problem. Let’s see what happened.

$ git diff
...
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
...
-# Offense count: 1
-# Cop supports --auto-correct.
-Lint/DeprecatedClassMethods:
-  Enabled: false
...
--- a/lib/jekyll/configuration.rb
+++ b/lib/jekyll/configuration.rb
...
-File.exists? Jekyll.sanitized_path(source(override), "_config.#{ext}")
+File.exist? Jekyll.sanitized_path(source(override), "_config.#{ext}")
...

RuboCop has changed exists? to exist?. A trivial change, but a step forward. This is another point where I would make a commit. (I like small commits.) Commit message can be simple:

$ git commit -am "Fixed Lint/DeprecatedClassMethods RuboCop offense"
[master fd88fd5] Fixed Lint/DeprecatedClassMethods RuboCop offense
 2 files changed, 1 insertion(+), 6 deletions(-)

RuboCop will not be able to fix all offenses, and those that are not fixed automatically tend to be the most interesting ones. For example, Lint/UselessAssignment will complain if variables are defined but not used (real life example).

I would suggest that you clean up the repository, one offense by one. Start with the ones that can be auto-corrected and that have a small number of offenses. Make a commit for every fixed offense. Stop when you get bored. You can always continue later.

Configuration

Style/StringLiterals is an interesting rule.

Ruby style guide says:

Adopt a consistent string literal quoting style. There are two popular styles in the Ruby community, both of which are considered good - single quotes by default (Option A) and double quotes by default (Option B).

The default is single quotes. But this repository uses double quotes and RuboCop reports almost 2000 offenses.

# Offense count: 1945
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/StringLiterals:
  Enabled: false

RuboCop can automatically change all double quotes to single quotes, but if you decide to continue using the current style (double quotes), delete the above lines from the .rubocop_todo.yml file and put this into .rubocop.yml file:

Style/StringLiterals:
  EnforcedStyle: double_quotes

Let’s see the change:

$ git diff
...
--- a/.rubocop.yml
+++ b/.rubocop.yml
...
+Style/StringLiterals:
+  EnforcedStyle: double_quotes
...
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
...
-# Offense count: 1945
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-Style/StringLiterals:
-  Enabled: false
...

RuboCop does not complain about double quotes any more, but now it complains about single qoutes. :)

$ rubocop
...
test/test_utils.rb:60:84: C: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
  assert_equal ['dog', 'cat'], Utils.pluralized_array_from_hash(data, 'tag', 'tags')
                                                                             ^^^^^^

95 files inspected, 1541 offenses detected

As the style guide says, you should pick one style.

Misc

1 AD to 2003 AD Historical Trends in global distribution of GDP (China, India, Western Europe, USA, Middle East) is a really interesting graph.

tags: code - event - featured - ruby - speaker