-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new requires_gem
API for declaring which gems a Cop needs
#12186
Conversation
5dcb450
to
7590743
Compare
lib/rubocop/cop/base.rb
Outdated
# rubocop:disable Layout/ClassStructure | ||
class << self | ||
attr_reader :gem_requirements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what the correct layout is.
I also see class << self
is pretty rare in this codebase, but singleton_class.attr_reader(:gem_requirements)
would be weird.
lib/rubocop/cop/base.rb
Outdated
# using the same syntax as a Gemfile, e.g. ">= 1.2.3" | ||
# | ||
# @api public | ||
def requires_gem(gem_name, version_requirement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be in a mixin, like rubocop-rails
's TargetRailsVersion
?
It'll keep the logic nicely separated and tested in its own file, but it means we couldn't use it from target_rails_version_from_bundler_lock_file
which is already in the Base.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have to think about this more, but I can live with the current proposal.
There's also the question whether we want this to be able to work with multiple gems, but I'm guessing the likelihood that a cop depends on multiple gems is pretty tiny, so let's keep it simple for now.
|
||
context 'and neither Gemfile.lock nor gems.locked exist' do | ||
it 'returns nil' do | ||
expect(configuration.gem_versions_in_target.nil?).to be(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not used to RSpec, is this correct?
I originally had:
expect(configuration.gem_versions_in_target.nil?).to be(true) | |
expect(configuration.gem_versions_in_target).to be_nil |
But rubocop auto-corrected to this. It feels a bit... funky
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct! expect(configuration.gem_versions_in_target).to be_nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'll try again, but the RuboCop settings on this project didn't like the use of predicate methods like be_nil
2e28918
to
c379d81
Compare
Hey @koic, I got all the tests to pass, but I'm having some issues I'd appreciate any early thoughts or feedback you might have! |
a79b732
to
4a09730
Compare
4a09730
to
5cd56c3
Compare
# In this case, the rails version was already checked by `#excluded_file?` | ||
return true if defined?(RuboCop::Rails::TargetRailsVersion::USES_REQUIRES_GEM_API) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the versioning between rubocop
and rubocop-rails
gets a bit tricky here.
- This new version of
rubocop
needs to support older versions ofrubocop-rails
. - Inversely, the new version of
rubocop-rails
needs to support older versions ofrubocop
.
I've tested both scenarios, and they both work well.
I added this USES_REQUIRES_GEM_API
constant to detect new rubocop-rails
versions, because it was more explicit/clear than checking against some specific version number.
In the future, TargetRailsVersion
can just be a wrapper for requires_gem
. When that happens, any cops that don't support the target's rails version would have already be filtered out by #excluded_file?
called by #roundup_relevant_cops
above.
For older versions of rubocop-rails
, that won't be correct, and we'll still need to call their support_target_rails_version?
method.
Sorry for the slow turnaround here - I'm fine with the general direction, but I'll need some time before I can take a closer look at the code, as I've been very busy lately at work and wrapping up releases of some other OSS projects. At a glance the one things that's currently missing is some user documentation for the proposed new feature. |
It'd also be nice to benchmark the impact of using |
626b18e
to
9970f1b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bbatsov I had to do a little bit of surgery, but I got this to work with the existing RuboCop::Lockfile
wrapper for Bundler::LockfileParser
.
I left some comments to explain my process, let me know what you think!
Still TODO:
- Write user docs – I can do this
- Benchmark this for perf regression – I'm not sure what the best way to do this is. Let me know if you have any suggestions
# Gems that the bundle directly depends on. | ||
# @return [Array<Bundler::Dependency>, nil] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These methods' semantics aren't obvious (in particular, whether they include indirect dependencies or not, and what type of results they vend), so I documented them all.
The whole class is marked @api private
, which get inherited by all of these methods, so there's no risk of people confusing them for public API (even if they're documented here).
@@ -5,6 +5,13 @@ module RuboCop | |||
# Does not actually resolve gems, just parses the lockfile. | |||
# @api private | |||
class Lockfile | |||
# @param [String, Pathname, nil] lockfile_path | |||
def initialize(lockfile_path: nil) | |||
lockfile_path ||= defined?(Bundler) ? Bundler.default_lockfile : nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I parameterized this class to let you specify your own lockfile_path
, but kept the default behaviour the same as before.
This let's use it with the result from RuboCop::Config#bundler_lock_file_path
.
# @param [Boolean] include_transitive_dependencies: When false, only direct dependencies | ||
# are returned, i.e. those listed explicitly in the `Gemfile`. | ||
# @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names. | ||
def gem_versions(include_transitive_dependencies: true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pre-existing methods (#dependencies
and #spec
) return Gem::Dependency
objects, which describe the version requirements, but not the resolved versions.
This method returns the actual resolved versions, locked by the lock file.
This is what we check gem requirements against.
de7dca7
to
9bfda44
Compare
This class may be private, but its methods' behaviour is still pretty non-obvious, so let's document them. They're all marked `@api private`, so reader third parties should know not to use them.
9bfda44
to
bf7faee
Compare
Rebased to fix the unrelated style failure, which was fixed by #12737 |
Looks good to me. Some of it goes over my head a bit, but it looks like well-written and quite readable code. Good job on the documentation too. |
@jonas054 Would you be okay to share which parts "go over your head a bit"? I'd love to clarify anything that might be confusing. |
No, don't worry about that comment, @amomchilov. That was much more about my abilities to review code in parts of RuboCop that I'm only partly familiar with. I don't see any specific opportunities for improvement when it comes to clarity. |
Ran a quick benchmark against Rails, which shows no discernable performance difference: On the master branch: $ time ../rubocop/rubocop/exe/rubocop --cache false
Inspecting 3265 files
...
3265 files inspected, no offenses detected
../rubocop/rubocop/exe/rubocop --cache false 61.37s user 0.77s system 95% cpu 1:04.84 total On this branch: $ time ../rubocop/rubocop/exe/rubocop --cache false
Inspecting 3265 files
...
3265 files inspected, no offenses detected
../rubocop/rubocop/exe/rubocop --cache false 61.69s user 0.74s system 97% cpu 1:04.02 total @koic I think this is for a final review and merge. Could you please have a look? |
If @koic doesn't share any feedback I'll merge this after the next bug-fix release. |
Fixes #11920.
This PR is used by:
Bundler::LockfileParser
#12180TargetRailsVersion
to the newrequires_gem
RuboCop API rubocop-rails#1137Before submitting the PR make sure the following are checked:
[Fix #issue-number]
(if the related issue exists).master
(if not - rebase it).bundle exec rake default
. It executes all tests and runs RuboCop on its own code.{change_type}_{change_description}.md
if the new code introduces user-observable changes. See changelog entry format for details.