Making RuboCop 20x Faster
⌘+S and Prettier formats everything for you.
Whenever I went back to working with Ruby on Rails, I really missed “format on save”. I wanted to set it up with RuboCop, but it was just too slow.
In the following video, I press
⌘+V to paste some spaces, and then I immediately press
⌘+S to save the file:
I tried to put up with this for a while, but it was terrible. I would switch to the terminal to run a test, and the file hadn’t even saved before the test was finished.
I starting thinking of ways to speed this up. I thought about daemonizing the process, similar to Spring for Rails. I googled “rubocop daemon”, stumbled onto the fohte/rubocop-daemon gem, and I ended up spending the whole day working on a pull request to fix some issues.
I started using this on all of my Ruby projects, so I just kept finding new things to fix and improve. One time I started working on a different project, and VS Code just deleted all the files whenever they were saved. So I really couldn’t get anything done until everything was working really well. Here’s some of the things I worked on:
- Rewrite the Ruby client as a bash script with
netcat. (much faster)
- Restore the default colorized output (there’s no TTY, so the Rainbow library turns it off.)
- Make clients use the correct exit code (important for pre-commit hooks, etc.)
- Add a file lock so that multiple servers don’t start at the same time.
- When started in a subdirectory, traverse the parent directories to find a
gems.rb, and use that as the project root. (Otherwise VS Code starts a daemon for every subdirectory.)
- Figure out how to deal with
stdinin the bash script.
- Make everything resilient and fault tolerant, so that it can always recover after an error.
After this, I was able to lint and format my Ruby files in less than 200ms:
I think this is almost on par with Prettier, and Ruby development feels so much nicer. I’ve also lowered the “Lint Debounce Time” setting to 300ms, and it’s really nice to get immediate feedback about linting errors.
Try it out
If you use RuboCop, then please try this out in your own projects, and let me know if you run into any issues. My changes have been merged into
rubocop-daemon, and were released in version
You can add the gem to your
group :development, :test do gem 'rubocop-daemon' end
You’ll also need to download and install the
rubocop-daemon-wrapper script (Instructions taken from the README):
curl https://raw.githubusercontent.com/fohte/rubocop-daemon/master/bin/rubocop-daemon-wrapper -o /tmp/rubocop-daemon-wrapper sudo mv /tmp/rubocop-daemon-wrapper /usr/local/bin/rubocop-daemon-wrapper sudo chmod +x /usr/local/bin/rubocop-daemon-wrapper
Finally, to get this working in VS Code, you’ll just need to replace your
rubocop binary with the wrapper script:
# Find your rubocop path $ which rubocop # => /Users/username/.rvm/gems/ruby-2.5.3/bin/rubocop # Override rubocop with a symlink to rubocop-daemon-wrapper $ ln -fs /usr/local/bin/rubocop-daemon-wrapper /Users/username/.rvm/gems/ruby-2.5.3/bin/rubocop
Hopefully there’s a better way to customize the rubocop path for VS Code.
I’ve also posted a comment on this RuboCop issue: “rubocop -a is slow”. (Maybe RuboCop could support daemonization as a native feature?)
Was it worth spending so much time on this?
I save Ruby files way more than 50 times per day, and I shaved off about 2 seconds. So according to this XKCD table, it was time well spent: