If at first you don’t succeed, call it version 1.0.

RuboCop’s development started exactly 7 and half years ago. I made the first commit on April 21, 2012. That’s a lot of time in general, but even more in the fast moving world of IT. During this long period we’ve racked up some impressive numbers:

  • 9905 commits
  • 152 releases
  • around 3500 closed issues
  • almost 5000 merged pull requests
  • over 120 million downloads
  • over 200 publicly available related gems (extensions, custom configurations, etc)
  • over 700 contributors

I never expected any of this on April 21, 2012. If there’s a person truly surprised by the success of RuboCop that’d be me. But wait, there’s more! We also reached some important milestones in the last couple of years:

  • Created the RuboCop HQ organization that became the home for RuboCop, the community style guides, and many popular RuboCop extensions
  • Extracted the Rails cops to a separate gem (rubocop-rails)
  • Extracted the performance cops to a separate gem (rubocop-performance)
  • Extracted the AST-related functionality to a separate gem (rubocop-ast)
  • Created new extensions focused on Rake (rubocop-rake) and Minitest (rubocop-minitest)
  • Made significant improvements to RuboCop’s code formatting capabilities
  • Reworked the cop API
  • Switched to safe-only cops by default
  • Introduced the notion of “pending” cops
  • Created a brand new documentation site
  • Provided more polished versions of the community style guides over at https://rubystyle.guide

One thing eluded us, though - a “stable” RuboCop release. Today this finally changes with RuboCop 1.0!1

There’s nothing ground-breaking about the new RuboCop release - it’s almost the same as RuboCop 0.93.1 that preceded it. I believe the only change, that most of you are going to notice, is that all cops that used to be “pending” are now enabled by default, which is in line with our release policy. No new cops will be enabled by default until RuboCop 2.0.

The big news for end users and maintainers of extensions is that we’re finally embracing fully Semantic Versioning, which should make the upgrade process simpler (painless?) for everyone. Going forward the following things will happen only on major releases:

  • enabling of new cops
  • changes to the default cop configuration
  • breaking API changes

It’s really funny that I felt for at least a couple of years that we were very close to the 1.0 release, only to come up with more and more things I wanted to include in it. I believe I first spoke about my intentions to ship RuboCop 1.0 at RubyKaigi 2018 and back then I truly believed this was bound to happen in the next 6 months. Classic example of planning in the software development world, right?

Many people urged me for years to label a random release as 1.0 with the argument that if some software is useful and widely used than it probably deserves that magic moniker. It’s not a bad argument and I totally understood the perspective of those people. I, however, was not convinced as for me version 1.0 also stands for “we got to a place we consider feature complete and aligned with our vision”. Needless to say - the vision we (RuboCop’s team) had was quite ambitious and it took us a while to make it a reality.

I cannot begin to explain how happy I am that we got here, and I can assure you that it wasn’t easy. Over the years RuboCop had its ups and downs, it got a lot of praise, but also a lot of flak.2 Some days I was super pumped and motivated to work on it, on other days I couldn’t stand to think about it. Working on popular OSS projects is one of the most rewarding and most frustrating experiences that one can have. I was (un)fortunate enough to be involved in a few of those (RuboCop, CIDER, nREPL, Projectile, Emacs Prelude, etc) and each one was a crazy roller-coaster ride.

I find it funny how my role in RuboCop evolved with time. Originally I used to write mostly code, these days I write mostly tickets, issue/code review comments and documentation. Often I feel more like a project manager rather than a programmer. There was a time when I was super happy to see a PR and I’d immediately respond to it, now I can’t keep up with all the PRs. In fact, our entire team can’t keep up with them, so consider this my apology that it sometimes takes a while to get feedback on your suggestions. I’ll even admit that I rarely read issue tickets these days as there are so many of them and it’s impossible for me to respond to all of them. I’ve just learned that important tickets always get noticed, if not by me than by someone else from our fantastic team.

I want to extend special thanks to RuboCop’s core team, as we would have never gotten so far without all those amazing people working tirelessly on the project:

You rock, guys!

Jonas, in particular, deserves just as much credit for RuboCop existing today as me. He was the first contributor to RuboCop and he pushed me to get RuboCop to the state where it got critical mass, mindshare and some traction. It’s a long story for another day and another article.3

Koichi also deserves a special mention for his tireless work and incredible dedication to RuboCop and its users over the years! And for his great karaoke skills! He has also been a fantastic head maintainer for key RuboCop extensions like rubocop-rails, rubocop-performance and rubocop-minitest.

Last, but not least - another round of big thanks for all the people who contributed to RuboCop in any capacity over the years! RuboCop is all of you! Keep those contributions coming!

Some closing notes:

  • As mentioned above, recently we’ve extracted RuboCop’s AST-related logic to the rubocop-ast gem, that’s going to be very handy for everything looking to supercharge parser’s API. I’d love to see more tools using it, as I think we really managed to simplify the interaction with an AST. Work on the new gem is led by the awesome Marc-André Lafortune. By the way, he released rubocop-ast 1.0 today! We have some cause for double celebration!
  • The cop API was completely reworked recently by Marc-André. He did some truly fantastic work there! Check out the upgrade notes if you maintain any RuboCop extensions, as the legacy API will be removed in RuboCop 2.0.
  • We’ve made some changes to how department names are calculated that might affect some extensions. Read more about them here.
  • Check out the release notes for all the details.
  • rubocop-rspec is currently not compatible with RuboCop 1.0, but we’re working on this. You can follow the progress on that front here.

And that’s a wrap!

I feel a bit sorry for disappointing everyone who hoped we’d make it to RuboCop 0.99, before cutting RuboCop 1.0. We did our best and we had a great 0.x run, but we ran out of things to do.4 :-) On the bright side - now I can finally say that I’ve got 99 problems (and 200 open issues), but cutting RuboCop 1.0 ain’t one.

Enjoy RuboCop 1.0 and share with us your feedback about it! Our focus now shifts to RuboCop 1.1, and I hope that we’ll be dropping new major releases rather infrequently going forward (although RuboCop 2.0 will probably arrive in less than 7 years). Thanks for your help, love, feedback and support! Keep hacking!

P.S. Koichi recently covered in great details our long journey to RuboCop 1.0 in his presentation Road to RuboCop 1.0 at RubyKaigi 2020. I cannot recommend it highly enough to those of you who’d like to learn more about the technical aspects of RuboCop 1.0 and all the challenges we had to solve along the way!

  1. Also known as the one that will bring balance to the (Ruby) Source. 

  2. In a recent survey RuboCop made both the list of most loved and most hated gems. 

  3. And like any good story it does feature Emacs. 

  4. I’m just kidding. Our plans are as a grand and spectacular as ever!