-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New cop Style/ComparableBetween
#13936
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
Conversation
def register_offense(node, min_and_value, max_and_value) | ||
value = (min_and_value & max_and_value).first | ||
min = min_and_value.find { _1 != value } | ||
max = max_and_value.find { _1 != value } |
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 very happy with this solution, but I couldn't find something more elegant because node matchers apparently don't support named captures that are returned as a hash instead of an array in the order of captures.
For example, in the matcher {$_value :>= $_min | $_min :<= $_value}
, the return value will either be [value, min]
or [min, value]
, but I can't know which is which without the extra logic.
If I'm missing something here to make this logic simpler, let me know. 🙏
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 guess that will do for now.
I was not aware of #12333 before opening the PR. I'd like to evaluate the comments which led to closing the issue. The benchmark which showed The benchmark I attached in PR description uses a random value which can be either below the minimum, inside the boundary, or above the maximum. With it, We should also consider the case when Benchmark scriptrequire 'benchmark/ips'
class Run
def initialize
@value = rand(800..2200)
end
attr_reader :value
def comparison
value >= 1000 && value <= 2000
end
def between
value.between?(1000, 2000)
end
end
Benchmark.ips do |x|
x.report('logical comparison') do
Run.new.comparison
end
x.report('Comparable#between?') do
Run.new.between
end
x.compare!
end
which brings the two even closer in terms of performance. In this codebase, there's one such example: rubocop/lib/rubocop/cop/style/if_unless_modifier.rb Lines 167 to 168 in 2873bb8
I think that this example also shows the stylistic benefit of having such a cop because it got autocorrected to:
which makes it easier to understand the intention (checking that a value is within a boundary) and what are the boundaries (in original code, condition for max comes before condition for min, which does not read naturally). It's also shorter since While someone might find logical comparison preferable, I think With this in mind, I think we should reevaluate the decision to not implement this cop (or update the style guide). |
I agree with your sentiment and I'm fine with including this cop. I guess it might not be a bad idea to mention the small performance hit, so that people are aware of it. Few people will ever be in a situation where such performance difference will matter, though. |
455a0dc
to
152db46
Compare
Updated cop docs with a note on performance. |
aca62af
to
94006cf
Compare
94006cf
to
ca0ae0b
Compare
Implements cop for style guide rule Ranges or between (picking
Comparable#between?
since it's the next fastest option after logical comparison andRange#include?
isn't equivalent to logical comparison in all cases).The cop registers cases such as
x >= min && x <= max
as offenses and autocorrects tox.between?(min, max)
.Benchmark
Benchmark script
Before 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.