Ruby 3.0 was just released on Dec. 25, 2020, with some new features and some breaking changes. GitLab was at Ruby 2.6, and we wanted to upgrade to Ruby 2.7 in preparation to eventually upgrade to Ruby 3.
In Ruby 3.0, positional and keyword arguments will be separated. To help developers prepare for this, in Ruby 2.7, warnings were added. In GitLab, we discovered we have thousands of such warnings across hundreds of files:
warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
Boring solutions
To address this warning, the obvious, and boring solution was to simply add **
to the last keyword argument.
For the most part, this is what we did. However, while this was under way, we also developed a RuboCop check that could
detect, and automatically fix the keyword arguments. The benefit for this approach was that we can
autocorrect any existing warnings en masse.
The tricky part about this is that RuboCop is designed to statically analyze Ruby code, whereas the warnings were generated by Ruby at runtime.
A way forward
After some research, we found a way to utilize our comprehensive RSpec test suite to gather all the warnings using the Deprecation Toolkit gem. We also considered using the warning gem at one point, but preferred Deprecation Toolkit as the results were easier to process.
Deprecation Toolkit supports RSpec out of the box, so it was really simple to configure. It also has a simple YAML-based file format to record all deprecations. We then adapted this to record deprecation warnings for Ruby 2.7 last keyword arguments with:
kwargs_warnings = [
# Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18
%r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\n\z}
]
DeprecationToolkit::Configuration.warnings_treated_as_deprecation = kwargs_warnings
Lastly, we wrote a new RuboCop check, called
Lint/LastKeywordArgument
,
that checks against the YAML files generated by Deprecation Toolkit, and
generates offenses. Now we can very quickly, statically check the whole GitLab
codebase, and even autocorrect! You can see how Deprecation Toolkit and the
LastKeywordArgument
check was put together in this merge
request. You can
see a sample output from running the LastKeywordArgument
cop check:
Sample output from running the LastKeywordArgument
cop check
Automatically fix everything
Now we have an automatic RuboCop check, which can also autocorrect, we create merge requests to autocorrect! For example, we autocorrected 62 instances across 39 spec files. Automation for the win!
We then went one step further, and integrated this in our GitLab CI pipelines. Using the artifacts
feature of GitLab CI, we
gathered the deprecations
directory from all RSpec jobs (we have about 400 such jobs). After all the RSpec jobs have passed, we then made a post-test
job to
check the results with the LastKeywordArgument
cop. Below is a
snippet of the GitLab CI .gitlab-ci.yml
configuration:
stages:
- test
- post-test
# This inherited job is used by all RSpec jobs
.rspec-base:
stage: test
artifacts:
- deprecations/
# GitLab CI job artifacts from previous stages are passed to this job
rspec:deprecations:
stage: post-test
script:
- bundle exec rubocop --only Lint/LastKeywordArgument --parallel
artifacts:
- deprecations/
This enabled us to have a single job where we can see all deprecation warnings.
Conclusion
With this measure we went from about 30,000 warnings related to keyword arguments to about 800 remaining warnings, largely stemming from dependencies. Feel free to follow our progress in GitLab issue #257438, and contribute to fix the remaining warnings if you are interested!
Cover image by Daria Nepriakhina on Unsplash