forked from rubocop/rubocop
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fix rubocop#7849] Add ExplicitOperatorPrecedence cop
Fixes rubocop#7849 This cop checks if binary operators of different precedents are used without explicit use of parenthesis. ```ruby a && b || c a * b + c a ** b * c / d % e + f - g << h >> i & j | k ^ l (a && b) || c (a * b) + c (((((a**b) * c / d % e) + f - g) << h >> i) & j) | k ^ l ```
- Loading branch information
Rahul Ahuja
committed
Apr 26, 2020
1 parent
db23f5e
commit ee2f8dc
Showing
7 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# frozen_string_literal: true | ||
# TODO: when finished, run `rake generate_cops_documentation` to update the docs | ||
module RuboCop | ||
module Cop | ||
module Lint | ||
# This cop checks if binary operators of different precedents are used without explicit use of parenthesis. | ||
# when operators are used without parenthesis | ||
# | ||
# @example | ||
# | ||
# # bad | ||
# a && b || c | ||
# a * b + c | ||
# a ** b * c / d % e + f - g << h >> i & j | k ^ l | ||
# | ||
# @example | ||
# | ||
# # good | ||
# # With parenthesis, there is no ambiguity. | ||
# (a && b) || c | ||
# (a * b) + c | ||
# (((((a**b) * c / d % e) + f - g) << h >> i) & j) | k ^ l | ||
class ExplicitOperatorPrecedence < Cop | ||
MSG = 'Operators with varied precedents used in a single statement.' | ||
|
||
PRECEDENCE_PRIORITY = [[:**], [:*, :/, :%], [:+, :-], [:<<, :>>], [:&], [:|, :^]] | ||
|
||
def on_and(node) | ||
add_offense(node) if node.parent.or_type? | ||
end | ||
|
||
def on_send(node) | ||
return unless cop_required?(node) | ||
add_offense(node) if multiple_precedences_used?(node) | ||
end | ||
|
||
def autocorrect(node) | ||
->(corrector) { corrector.replace(replacement_range(node), correction(node)) } | ||
end | ||
|
||
private | ||
|
||
def cop_required?(node) | ||
node.parent && node.parent.respond_to?(:method_name) && | ||
PRECEDENCE_PRIORITY.flatten.include?(node.method_name) && PRECEDENCE_PRIORITY.flatten.include?(node.method_name) | ||
end | ||
|
||
def multiple_precedences_used?(node) | ||
operator_priority(node.method_name) < operator_priority(node.parent.method_name) | ||
end | ||
|
||
def operator_priority(operator) | ||
PRECEDENCE_PRIORITY.find_index { |operators| operators.include?(operator) } | ||
end | ||
|
||
def correction(node) | ||
"(#{node.source})" | ||
end | ||
|
||
def replacement_range(node) | ||
Parser::Source::Range.new(node.loc.expression.source_buffer, | ||
node.loc.expression.begin_pos, | ||
node.loc.expression.end_pos) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
spec/rubocop/cop/lint/explicit_operator_precedence_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# frozen_string_literal: true | ||
RSpec.describe RuboCop::Cop::Lint::ExplicitOperatorPrecedence, :config do | ||
subject(:cop) { described_class.new(config) } | ||
|
||
before do | ||
inspect_source(source) | ||
end | ||
|
||
shared_examples 'code with offense' do |code, expected = nil, offense_count = 1| | ||
context "when checking #{code}" do | ||
let(:source) { code } | ||
|
||
let(:offense_count) { offense_count } | ||
|
||
it 'registers an offense' do | ||
expect(cop.offenses.size).to eq(offense_count) | ||
expect(cop.messages).to eq(offense_count.times.map { |_| message }) | ||
end | ||
|
||
if expected | ||
it 'auto-corrects' do | ||
expect(autocorrect_source(code)).to eq(expected) | ||
end | ||
else | ||
it 'does not auto-correct' do | ||
expect(autocorrect_source(code)).to eq(code) | ||
end | ||
end | ||
end | ||
end | ||
|
||
shared_examples 'code without offense' do |code| | ||
let(:source) { code } | ||
|
||
it 'does not register an offense' do | ||
expect(cop.offenses.empty?).to be(true) | ||
end | ||
end | ||
|
||
let(:message) { 'Operators with varied precedents used in a single statement.' } | ||
|
||
context 'when `&&` is used with `||` without parenthesis' do | ||
it_behaves_like 'code with offense', 'foo && baz || bar', '(foo && baz) || bar' | ||
end | ||
|
||
context 'when `&&` is used with `||` with parenthesis' do | ||
it_behaves_like 'code without offense', '(foo && baz) || bar' | ||
end | ||
|
||
context "when operators with different precedents are used without parenthesis" do | ||
it_behaves_like 'code with offense', 'a ** b * c / d % e + f - g << h >> i & j | k ^ l', | ||
'(((((a**b) * c / d % e) + f - g) << h >> i) & j) | k ^ l', 5 | ||
end | ||
|
||
context "when operators with different precedents are used with parenthesis" do | ||
it_behaves_like 'code without offense', '(((((a**b) * c / d % e) + f - g) << h >> i) & j) | k ^ l' | ||
end | ||
end |