-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bfc974d
commit ce37499
Showing
7 changed files
with
182 additions
and
14 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
changelog/new_add_new_style_endless_method_operator_precedence.md
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 @@ | ||
* [#12988](https://github.com/rubocop/rubocop/issues/12988): Add new `Style/AmbiguousEndlessMethodDefinition` cop. ([@dvandersluis][]) |
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,24 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
# Common functionality for rewriting endless methods to normal method definitions | ||
module EndlessMethodRewriter | ||
def correct_to_multiline(corrector, node) | ||
replacement = <<~RUBY.strip | ||
def #{node.method_name}#{arguments(node)} | ||
#{node.body.source} | ||
end | ||
RUBY | ||
|
||
corrector.replace(node, replacement) | ||
end | ||
|
||
private | ||
|
||
def arguments(node, missing = '') | ||
node.arguments.any? ? node.arguments.source : missing | ||
end | ||
end | ||
end | ||
end |
79 changes: 79 additions & 0 deletions
79
lib/rubocop/cop/style/ambiguous_endless_method_definition.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,79 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Style | ||
# Looks for endless methods inside operations of lower precedence (`and`, `or`, and | ||
# modifier forms of `if`, `unless`, `while`, `until`) that are ambiguous due to | ||
# lack of parentheses. This may lead to unexpected behavior as the code may appear | ||
# to use these keywords as part of the method but in fact they modify | ||
# the method definition itself. | ||
# | ||
# In these cases, using a normal method definition is more clear. | ||
# | ||
# @example | ||
# | ||
# # bad | ||
# def foo = true if bar | ||
# | ||
# # good - using a non-endless method is more explicit | ||
# def foo | ||
# true | ||
# end if bar | ||
# | ||
# # ok - method body is explicit | ||
# def foo = (true if bar) | ||
# | ||
# # ok - method definition is explicit | ||
# (def foo = true) if bar | ||
class AmbiguousEndlessMethodDefinition < Base | ||
extend TargetRubyVersion | ||
extend AutoCorrector | ||
include EndlessMethodRewriter | ||
include RangeHelp | ||
|
||
minimum_target_ruby_version 3.0 | ||
|
||
MSG = 'Avoid using `%<keyword>s` statements with endless methods.' | ||
|
||
# @!method ambiguous_endless_method_body(node) | ||
def_node_matcher :ambiguous_endless_method_body, <<~PATTERN | ||
^${ | ||
(if _ <def _>) | ||
({and or} def _) | ||
({while until} _ def) | ||
} | ||
PATTERN | ||
|
||
def on_def(node) | ||
return unless node.endless? | ||
|
||
operation = ambiguous_endless_method_body(node) | ||
return unless operation | ||
|
||
return unless modifier_form?(operation) | ||
|
||
add_offense(operation, message: format(MSG, keyword: keyword(operation))) do |corrector| | ||
correct_to_multiline(corrector, node) | ||
end | ||
end | ||
|
||
private | ||
|
||
def modifier_form?(operation) | ||
return true if operation.and_type? || operation.or_type? | ||
|
||
operation.modifier_form? | ||
end | ||
|
||
def keyword(operation) | ||
if operation.respond_to?(:keyword) | ||
operation.keyword | ||
else | ||
operation.operator | ||
end | ||
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
70 changes: 70 additions & 0 deletions
70
spec/rubocop/cop/style/ambiguous_endless_method_definition_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,70 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Style::AmbiguousEndlessMethodDefinition, :config do | ||
context 'Ruby >= 3.0', :ruby30 do | ||
it 'does not register an offense for a non endless method' do | ||
expect_no_offenses(<<~RUBY) | ||
def foo | ||
end | ||
RUBY | ||
end | ||
|
||
%i[and or if unless while until].each do |operator| | ||
context "with #{operator}" do | ||
it "does not register an offense for a non endless method followed by `#{operator}`" do | ||
expect_no_offenses(<<~RUBY) | ||
def foo | ||
end #{operator} bar | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when the operator is already wrapped in parens' do | ||
expect_no_offenses(<<~RUBY) | ||
def foo = (true #{operator} bar) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when the method definition is already wrapped in parens' do | ||
expect_no_offenses(<<~RUBY) | ||
(def foo = true) #{operator} bar | ||
RUBY | ||
end | ||
|
||
unless %i[and or].include?(operator) | ||
it "does not register an offense for non-modifier `#{operator}`" do | ||
expect_no_offenses(<<~RUBY) | ||
#{operator} bar | ||
def foo = true | ||
end | ||
RUBY | ||
end | ||
end | ||
|
||
it "registers and offense and corrects an endless method followed by `#{operator}`" do | ||
expect_offense(<<~RUBY, operator: operator) | ||
def foo = true #{operator} bar | ||
^^^^^^^^^^^^^^^^{operator}^^^^ Avoid using `#{operator}` statements with endless methods. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
def foo | ||
true | ||
end #{operator} bar | ||
RUBY | ||
end | ||
end | ||
end | ||
|
||
it 'does not register an offense for `&&`' do | ||
expect_no_offenses(<<~RUBY) | ||
def foo = true && bar | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense for `||`' do | ||
expect_no_offenses(<<~RUBY) | ||
def foo = true || bar | ||
RUBY | ||
end | ||
end | ||
end |