Skip to content

Commit

Permalink
[rubocop#5050] Add Style/TrailingBodyOnClass cop
Browse files Browse the repository at this point in the history
  • Loading branch information
garettarrowood committed Jan 18, 2018
1 parent 5b6d701 commit a99a2be
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* [#3666](https://github.com/bbatsov/rubocop/issues/3666): Add new `Naming/UncommunicativeMethodParamName` cop. ([@garettarrowood][])
* [#5356](https://github.com/bbatsov/rubocop/issues/5356): Add new `Lint/UnneededCopEnableDirective` cop. ([@garettarrowood][])
* [#5248](https://github.com/bbatsov/rubocop/pull/5248): Add new `Lint/BigDecimalNew` cop. ([@koic][])
* Add new `Style/TrailingBodyOnClass` cop. ([@garettarrowood][])
* [#3394](https://github.com/bbatsov/rubocop/issues/3394): Add new `Style/TrailingCommmaInArrayLiteral` cop. ([@garettarrowood][])
* [#3394](https://github.com/bbatsov/rubocop/issues/3394): Add new `Style/TrailingCommmaInHashLiteral` cop. ([@garettarrowood][])
* [#5319](https://github.com/bbatsov/rubocop/pull/5319): Add new `Security/Open` cop. ([@mame][])
Expand Down
4 changes: 4 additions & 0 deletions config/enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,10 @@ Style/TernaryParentheses:
Description: 'Checks for use of parentheses around ternary conditions.'
Enabled: true

Style/TrailingBodyOnClass:
Description: 'Class body goes below class statement.'
Enabled: true

Style/TrailingBodyOnMethodDefinition:
Description: 'Method body goes below definition.'
Enabled: true
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
require_relative 'rubocop/cop/mixin/target_ruby_version'
require_relative 'rubocop/cop/mixin/target_rails_version'
require_relative 'rubocop/cop/mixin/too_many_lines'
require_relative 'rubocop/cop/mixin/trailing_body'
require_relative 'rubocop/cop/mixin/trailing_comma'
require_relative 'rubocop/cop/mixin/uncommunicative_name'
require_relative 'rubocop/cop/mixin/unused_argument'
Expand Down Expand Up @@ -503,6 +504,7 @@
require_relative 'rubocop/cop/style/symbol_literal'
require_relative 'rubocop/cop/style/symbol_proc'
require_relative 'rubocop/cop/style/ternary_parentheses'
require_relative 'rubocop/cop/style/trailing_body_on_class'
require_relative 'rubocop/cop/style/trailing_body_on_method_definition'
require_relative 'rubocop/cop/style/trailing_comma_in_arguments'
require_relative 'rubocop/cop/style/trailing_comma_in_array_literal'
Expand Down
31 changes: 31 additions & 0 deletions lib/rubocop/cop/correctors/line_break_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,26 @@ module Cop
# This class handles autocorrection for code that needs to be moved
# to new lines.
class LineBreakCorrector
extend Alignment
extend TrailingBody
extend Util

class << self
attr_reader :processed_source

def correct_trailing_body(configured_width:, corrector:, node:,
processed_source:)
@processed_source = processed_source
range = first_part_of(node.to_a.last)
eol_comment = end_of_line_comment(node.source_range.line)

break_line_before(range: range, node: node, corrector: corrector,
configured_width: configured_width)
move_comment(eol_comment: eol_comment, node: node,
corrector: corrector)
remove_semicolon(node, corrector)
end

def break_line_before(range:, node:, corrector:, indent_steps: 1,
configured_width:)
corrector.insert_before(
Expand All @@ -22,6 +41,18 @@ def move_comment(eol_comment:, node:, corrector:)
text + "\n" + (' ' * node.loc.keyword.column))
corrector.remove(eol_comment.loc.expression)
end

private

def remove_semicolon(node, corrector)
return unless semicolon(node)
corrector.remove(semicolon(node).pos)
end

def semicolon(node)
@semicolon ||= {}
@semicolon[node.object_id] ||= tokens(node).find(&:semicolon?)
end
end
end
end
Expand Down
26 changes: 26 additions & 0 deletions lib/rubocop/cop/mixin/trailing_body.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module RuboCop
module Cop
# Common methods shared by TrailingBody cops
module TrailingBody
def trailing_body?(node)
body = node.to_a.reverse[0]
body && node.multiline? &&
body_on_first_line?(node, body)
end

def body_on_first_line?(node, body)
node.source_range.first_line == body.source_range.first_line
end

def first_part_of(body)
if body.begin_type?
body.children.first.source_range
else
body.source_range
end
end
end
end
end
43 changes: 43 additions & 0 deletions lib/rubocop/cop/style/trailing_body_on_class.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for trailing code after the class definition.
#
# @example
# # bad
# class Foo; def foo; end
# end
#
# # good
# class Foo
# def foo; end
# end
#
class TrailingBodyOnClass < Cop
include Alignment
include TrailingBody

MSG = 'Place the first line of class body on its own line.'.freeze

def on_class(node)
return unless trailing_body?(node)

add_offense(node, location: first_part_of(node.to_a.last))
end

def autocorrect(node)
lambda do |corrector|
LineBreakCorrector.correct_trailing_body(
configured_width: configured_indentation_width,
corrector: corrector,
node: node,
processed_source: processed_source
)
end
end
end
end
end
end
41 changes: 6 additions & 35 deletions lib/rubocop/cop/style/trailing_body_on_method_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module Style
#
class TrailingBodyOnMethodDefinition < Cop
include Alignment
include TrailingBody

MSG = "Place the first line of a multi-line method definition's " \
'body on its own line.'.freeze
Expand All @@ -39,44 +40,14 @@ def on_def(node)

def autocorrect(node)
lambda do |corrector|
LineBreakCorrector.break_line_before(
range: first_part_of(node.body), node: node, corrector: corrector,
configured_width: configured_indentation_width
LineBreakCorrector.correct_trailing_body(
configured_width: configured_indentation_width,
corrector: corrector,
node: node,
processed_source: processed_source
)
LineBreakCorrector.move_comment(
eol_comment: end_of_line_comment(node.source_range.line),
node: node, corrector: corrector
)
remove_semicolon(node, corrector)
end
end

private

def trailing_body?(node)
node.body && node.multiline? && on_def_line?(node)
end

def on_def_line?(node)
node.source_range.first_line == node.body.source_range.first_line
end

def first_part_of(body)
if body.begin_type?
body.children.first.source_range
else
body.source_range
end
end

def remove_semicolon(node, corrector)
return unless semicolon(node)
corrector.remove(semicolon(node).pos)
end

def semicolon(node)
@semicolon ||= tokens(node).find(&:semicolon?)
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ In the following section you find all available cops:
* [Style/SymbolLiteral](cops_style.md#stylesymbolliteral)
* [Style/SymbolProc](cops_style.md#stylesymbolproc)
* [Style/TernaryParentheses](cops_style.md#styleternaryparentheses)
* [Style/TrailingBodyOnClass](cops_style.md#styletrailingbodyonclass)
* [Style/TrailingBodyOnMethodDefinition](cops_style.md#styletrailingbodyonmethoddefinition)
* [Style/TrailingCommaInArguments](cops_style.md#styletrailingcommainarguments)
* [Style/TrailingCommaInArrayLiteral](cops_style.md#styletrailingcommainarrayliteral)
Expand Down
21 changes: 21 additions & 0 deletions manual/cops_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -5370,6 +5370,27 @@ Name | Default value | Configurable values
EnforcedStyle | `require_no_parentheses` | `require_parentheses`, `require_no_parentheses`, `require_parentheses_when_complex`
AllowSafeAssignment | `true` | Boolean

## Style/TrailingBodyOnClass

Enabled by default | Supports autocorrection
--- | ---
Enabled | Yes

This cop checks for trailing code after the class definition.

### Examples

```ruby
# bad
class Foo; def foo; end
end

# good
class Foo
def foo; end
end
```

## Style/TrailingBodyOnMethodDefinition

Enabled by default | Supports autocorrection
Expand Down
83 changes: 83 additions & 0 deletions spec/rubocop/cop/style/trailing_body_on_class_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::TrailingBodyOnClass do
subject(:cop) { described_class.new(config) }

let(:config) do
RuboCop::Config.new('Layout/IndentationWidth' => { 'Width' => 2 })
end

it 'registers an offense when body trails after class definition' do
expect_offense(<<-RUBY.strip_indent)
class Foo; body
^^^^ Place the first line of class body on its own line.
end
class Bar; def bar; end
^^^^^^^^^^^^ Place the first line of class body on its own line.
end
RUBY
end

it 'registers offense with multi-line class' do
expect_offense(<<-RUBY.strip_indent)
class Foo; body
^^^^ Place the first line of class body on its own line.
def bar
qux
end
end
RUBY
end

it 'accepts regular class' do
expect_no_offenses(<<-RUBY.strip_indent)
class Foo
def no_op; end
end
RUBY
end

it 'accepts class inheritance' do
expect_no_offenses(<<-RUBY.strip_indent)
class Foo < Bar
end
RUBY
end

it 'auto-corrects body after class definition' do
corrected = autocorrect_source(['class Foo; body ',
'end'].join("\n"))
expect(corrected).to eq ['class Foo ',
' body ',
'end'].join("\n")
end

it 'auto-corrects with comment after body' do
corrected = autocorrect_source(['class BarQux; foo # comment',
'end'].join("\n"))
expect(corrected).to eq ['# comment',
'class BarQux ',
' foo ',
'end'].join("\n")
end

it 'auto-corrects when there are multiple semicolons' do
corrected = autocorrect_source(['class Bar; def bar; end',
'end'].join("\n"))
expect(corrected).to eq ['class Bar ',
' def bar; end',
'end'].join("\n")
end

context 'when class is not on first line of processed_source' do
it 'auto-correct offense' do
corrected = autocorrect_source(['',
' class Foo; body ',
' end'].join("\n"))
expect(corrected).to eq ['',
' class Foo ',
' body ',
' end'].join("\n")
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ def some_method
' end'].join("\n")
end

context 'when method not on first line of processed_source' do
it '' do
context 'when method is not on first line of processed_source' do
it 'auto-corrects offense' do
corrected = autocorrect_source(['',
' def some_method; body',
' end'].join("\n"))
Expand Down

0 comments on commit a99a2be

Please sign in to comment.