Skip to content

Commit

Permalink
Add new cop Style/EndlessMethod.
Browse files Browse the repository at this point in the history
  • Loading branch information
dvandersluis authored and bbatsov committed Jan 3, 2021
1 parent a408b5b commit 41928e8
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_new_cop_styleendlessmethod.md
@@ -0,0 +1 @@
* [#9281](https://github.com/rubocop-hq/rubocop/pull/9281): Add new cop `Style/EndlessMethod`. ([@dvandersluis][])
11 changes: 11 additions & 0 deletions config/default.yml
Expand Up @@ -3206,6 +3206,17 @@ Style/EndBlock:
VersionAdded: '0.9'
VersionChanged: '0.81'

Style/EndlessMethod:
Description: 'Avoid the use of multi-lined endless method definitions.'
StyleGuide: '#endless-methods'
Enabled: pending
VersionAdded: <<next>>
EnforcedStyle: allow_single_line
SupportedStyles:
- allow_single_line
- allow_always
- disallow

Style/EvalWithLocation:
Description: 'Pass `__FILE__` and `__LINE__` to `eval` method, as they are used by backtraces.'
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -448,6 +448,7 @@
require_relative 'rubocop/cop/style/empty_lambda_parameter'
require_relative 'rubocop/cop/style/empty_literal'
require_relative 'rubocop/cop/style/empty_method'
require_relative 'rubocop/cop/style/endless_method'
require_relative 'rubocop/cop/style/encoding'
require_relative 'rubocop/cop/style/end_block'
require_relative 'rubocop/cop/style/eval_with_location'
Expand Down
102 changes: 102 additions & 0 deletions lib/rubocop/cop/style/endless_method.rb
@@ -0,0 +1,102 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for endless methods.
#
# It can enforce either the use of endless methods definitions
# for single-lined method bodies, or disallow endless methods.
#
# Other method definition types are not considered by this cop.
#
# The supported styles are:
# * allow_single_line (default) - only single line endless method definitions are allowed.
# * allow_always - all endless method definitions are allowed.
# * disallow - all endless method definitions are disallowed.
#
# NOTE: Incorrect endless method definitions will always be
# corrected to a multi-line definition.
#
# @example EnforcedStyle: allow_single_line (default)
# # good
# def my_method() = x
#
# # bad, multi-line endless method
# def my_method() = x.foo
# .bar
# .baz
#
# @example EnforcedStyle: allow_always
# # good
# def my_method() = x
#
# # good
# def my_method() = x.foo
# .bar
# .baz
#
# @example EnforcedStyle: disallow
# # bad
# def my_method; x end
#
# # bad
# def my_method() = x.foo
# .bar
# .baz
#
class EndlessMethod < Base
include ConfigurableEnforcedStyle
extend TargetRubyVersion
extend AutoCorrector

minimum_target_ruby_version 3.0

CORRECTION_STYLES = %w[multiline single_line].freeze
MSG = 'Avoid endless method definitions.'
MSG_MULTI_LINE = 'Avoid endless method definitions with multiple lines.'

def on_def(node)
if style == :disallow
handle_disallow_style(node)
else
handle_allow_style(node)
end
end

private

def handle_allow_style(node)
return unless node.endless?
return if node.single_line? || style == :allow_always

add_offense(node, message: MSG_MULTI_LINE) do |corrector|
correct_to_multiline(corrector, node)
end
end

def handle_disallow_style(node)
return unless node.endless?

add_offense(node) do |corrector|
correct_to_multiline(corrector, node)
end
end

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

def arguments(node, missing = '')
node.arguments.any? ? node.arguments.source : missing
end
end
end
end
end
142 changes: 142 additions & 0 deletions spec/rubocop/cop/style/endless_method_spec.rb
@@ -0,0 +1,142 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::EndlessMethod, :config do
context 'Ruby >= 3.0', :ruby30 do
context 'EnforcedStyle: disallow' do
let(:cop_config) { { 'EnforcedStyle' => 'disallow' } }

it 'registers an offense for an endless method' do
expect_offense(<<~RUBY)
def my_method() = x
^^^^^^^^^^^^^^^^^^^ Avoid endless method definitions.
RUBY

expect_correction(<<~RUBY)
def my_method
x
end
RUBY
end

it 'registers an offense for an endless method with arguments' do
expect_offense(<<~RUBY)
def my_method(a, b) = x
^^^^^^^^^^^^^^^^^^^^^^^ Avoid endless method definitions.
RUBY

expect_correction(<<~RUBY)
def my_method(a, b)
x
end
RUBY
end
end

context 'EnforcedStyle: allow_single_line' do
let(:cop_config) { { 'EnforcedStyle' => 'allow_single_line' } }

it 'does not register an offense for an endless method' do
expect_no_offenses(<<~RUBY)
def my_method() = x
RUBY
end

it 'does not register an offense for an endless method with arguments' do
expect_no_offenses(<<~RUBY)
def my_method(a, b) = x
RUBY
end

it 'registers an offense and corrects for a multiline endless method' do
expect_offense(<<~RUBY)
def my_method() = x.foo
^^^^^^^^^^^^^^^^^^^^^^^ Avoid endless method definitions with multiple lines.
.bar
.baz
RUBY

expect_correction(<<~RUBY)
def my_method
x.foo
.bar
.baz
end
RUBY
end

it 'registers an offense and corrects for a multiline endless method with begin' do
expect_offense(<<~RUBY)
def my_method() = begin
^^^^^^^^^^^^^^^^^^^^^^^ Avoid endless method definitions with multiple lines.
foo && bar
end
RUBY

expect_correction(<<~RUBY)
def my_method
begin
foo && bar
end
end
RUBY
end

it 'registers an offense and corrects for a multiline endless method with arguments' do
expect_offense(<<~RUBY)
def my_method(a, b) = x.foo
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid endless method definitions with multiple lines.
.bar
.baz
RUBY

expect_correction(<<~RUBY)
def my_method(a, b)
x.foo
.bar
.baz
end
RUBY
end
end

context 'EnforcedStyle: allow_always' do
let(:cop_config) { { 'EnforcedStyle' => 'allow_always' } }

it 'does not register an offense for an endless method' do
expect_no_offenses(<<~RUBY)
def my_method() = x
RUBY
end

it 'does not register an offense for an endless method with arguments' do
expect_no_offenses(<<~RUBY)
def my_method(a, b) = x
RUBY
end

it 'registers an offense and corrects for a multiline endless method' do
expect_no_offenses(<<~RUBY)
def my_method() = x.foo
.bar
.baz
RUBY
end

it 'registers an offense and corrects for a multiline endless method with begin' do
expect_no_offenses(<<~RUBY)
def my_method() = begin
foo && bar
end
RUBY
end

it 'registers an offense and corrects for a multiline endless method with arguments' do
expect_no_offenses(<<~RUBY)
def my_method(a, b) = x.foo
.bar
.baz
RUBY
end
end
end
end

0 comments on commit 41928e8

Please sign in to comment.