Skip to content

Commit

Permalink
Add new Layout/EmptyLineAroundAttributeAccessor cop
Browse files Browse the repository at this point in the history
This PR adds new `Layout/EmptyLineAroundAttributeAccessor` cop
that checks for a newline after attribute accessor.

```ruby
# bad
attr_accessor :foo
def do_something
end

# good
attr_accessor :foo

def do_something
end

# good
attr_accessor :foo
attr_reader :bar
attr_writer :baz
attr :qux

def do_something
end
```

This PR first checks an empty line after an attribute accessor to
implement the example shown in The Ruby Style Guide's rule.
rubocop/ruby-style-guide#817

In the future this cop will provide an `EnforcedStyle: around` that
checks empty lines before and after an attribute accessor, and this
`EnforcedStyle: only_after` option could be users selectable option.
  • Loading branch information
koic committed May 10, 2020
1 parent 6329595 commit 8831824
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,7 @@
* [#7910](https://github.com/rubocop-hq/rubocop/pull/7910): Support autocorrection for `Lint/ParenthesesAsGroupedExpression`. ([@koic][])
* [#7925](https://github.com/rubocop-hq/rubocop/pull/7925): Support autocorrection for `Layout/ConditionPosition`. ([@koic][])
* [#7934](https://github.com/rubocop-hq/rubocop/pull/7934): Support autocorrection for `Lint/EnsureReturn`. ([@koic][])
* [#7922](https://github.com/rubocop-hq/rubocop/pull/7922): Add new `Layout/EmptyLineAroundAttributeAccessor` cop. ([@koic][])

### Bug fixes

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Expand Up @@ -462,6 +462,12 @@ Layout/EmptyLinesAroundArguments:
Enabled: true
VersionAdded: '0.52'

Layout/EmptyLinesAroundAttributeAccessor:
Description: "Keep blank lines around attribute accessors."
StyleGuide: '#empty-lines-around-attribute-accessor'
Enabled: pending
VersionAdded: '0.83'

Layout/EmptyLinesAroundBeginBody:
Description: "Keeps track of empty lines around begin-end bodies."
StyleGuide: '#empty-lines-around-bodies'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -213,6 +213,7 @@
require_relative 'rubocop/cop/layout/empty_line_between_defs'
require_relative 'rubocop/cop/layout/empty_lines_around_access_modifier'
require_relative 'rubocop/cop/layout/empty_lines_around_arguments'
require_relative 'rubocop/cop/layout/empty_lines_around_attribute_accessor'
require_relative 'rubocop/cop/layout/empty_lines_around_begin_body'
require_relative 'rubocop/cop/layout/empty_lines_around_block_body'
require_relative 'rubocop/cop/layout/empty_lines_around_class_body'
Expand Down
4 changes: 4 additions & 0 deletions lib/rubocop/ast/node/send_node.rb
Expand Up @@ -8,6 +8,10 @@ module AST
class SendNode < Node
include ParameterizedNode
include MethodDispatchNode

def_node_matcher :attribute_accessor?, <<~PATTERN
(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
PATTERN
end
end
end
68 changes: 68 additions & 0 deletions lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb
@@ -0,0 +1,68 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Layout
# Checks for a newline after attribute accessor.
#
# @example
# # bad
# attr_accessor :foo
# def do_something
# end
#
# # good
# attr_accessor :foo
#
# def do_something
# end
#
# # good
# attr_accessor :foo
# attr_reader :bar
# attr_writer :baz
# attr :qux
#
# def do_something
# end
#
class EmptyLinesAroundAttributeAccessor < Cop
include RangeHelp

MSG = 'Add an empty line after attribute accessor.'

def on_send(node)
return unless node.attribute_accessor?
return if next_line_empty?(node.last_line)

next_line_node = next_line_node(node)
return if next_line_node.nil? || attribute_accessor?(next_line_node)

add_offense(node)
end

def autocorrect(node)
lambda do |corrector|
range = range_by_whole_lines(node.source_range)

corrector.insert_after(range, "\n")
end
end

private

def next_line_empty?(line)
processed_source[line].blank?
end

def next_line_node(node)
node.parent.children[node.sibling_index + 1]
end

def attribute_accessor?(node)
node.send_type? && node.attribute_accessor?
end
end
end
end
end
6 changes: 1 addition & 5 deletions lib/rubocop/cop/lint/duplicate_methods.rb
Expand Up @@ -95,10 +95,6 @@ def on_alias(node)
(send nil? :alias_method (sym $_name) _)
PATTERN

def_node_matcher :attr?, <<~PATTERN
(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
PATTERN

def_node_matcher :sym_name, '(sym $_name)'

def on_send(node)
Expand All @@ -108,7 +104,7 @@ def on_send(node)
return if possible_dsl?(node)

found_instance_method(node, name)
elsif (attr = attr?(node))
elsif (attr = node.attribute_accessor?)
on_attr(node, *attr)
end
end
Expand Down
6 changes: 1 addition & 5 deletions lib/rubocop/cop/naming/method_name.rb
Expand Up @@ -35,15 +35,11 @@ class MethodName < Cop

MSG = 'Use %<style>s for method names.'

def_node_matcher :attr?, <<~PATTERN
(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
PATTERN

def_node_matcher :sym_name, '(sym $_name)'
def_node_matcher :str_name, '(str $_name)'

def on_send(node)
return unless (attrs = attr?(node))
return unless (attrs = node.attribute_accessor?)

attrs.last.each do |name_item|
name = attr_name(name_item)
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/variable_force/assignment.rb
Expand Up @@ -10,6 +10,7 @@ class Assignment
MULTIPLE_LEFT_HAND_SIDE_TYPE = :mlhs

attr_reader :node, :variable, :referenced, :references

alias referenced? referenced

def initialize(node, variable)
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/variable_force/scope.rb
Expand Up @@ -16,6 +16,7 @@ class Scope
}.freeze

attr_reader :node, :variables, :naked_top_level

alias naked_top_level? naked_top_level

def initialize(node)
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/variable_force/variable.rb
Expand Up @@ -11,6 +11,7 @@ class Variable

attr_reader :name, :declaration_node, :scope,
:assignments, :references, :captured_by_block

alias captured_by_block? captured_by_block

def initialize(name, declaration_node, scope)
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Expand Up @@ -107,6 +107,7 @@ In the following section you find all available cops:
* [Layout/EmptyLines](cops_layout.md#layoutemptylines)
* [Layout/EmptyLinesAroundAccessModifier](cops_layout.md#layoutemptylinesaroundaccessmodifier)
* [Layout/EmptyLinesAroundArguments](cops_layout.md#layoutemptylinesaroundarguments)
* [Layout/EmptyLinesAroundAttributeAccessor](cops_layout.md#layoutemptylinesaroundattributeaccessor)
* [Layout/EmptyLinesAroundBeginBody](cops_layout.md#layoutemptylinesaroundbeginbody)
* [Layout/EmptyLinesAroundBlockBody](cops_layout.md#layoutemptylinesaroundblockbody)
* [Layout/EmptyLinesAroundClassBody](cops_layout.md#layoutemptylinesaroundclassbody)
Expand Down
36 changes: 36 additions & 0 deletions manual/cops_layout.md
Expand Up @@ -1199,6 +1199,42 @@ some_method(
)
```

## Layout/EmptyLinesAroundAttributeAccessor

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
--- | --- | --- | --- | ---
Pending | Yes | Yes | 0.83 | -

Checks for a newline after attribute accessor.

### Examples

```ruby
# bad
attr_accessor :foo
def do_something
end

# good
attr_accessor :foo

def do_something
end

# good
attr_accessor :foo
attr_reader :bar
attr_writer :baz
attr :qux

def do_something
end
```

### References

* [https://rubystyle.guide#empty-lines-around-attribute-accessor](https://rubystyle.guide#empty-lines-around-attribute-accessor)

## Layout/EmptyLinesAroundBeginBody

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down
1 change: 1 addition & 0 deletions spec/rubocop/cop/corrector_spec.rb
Expand Up @@ -24,6 +24,7 @@ def do_rewrite(corrections = nil, &block)
matcher :rewrite_to do |expected|
supports_block_expectations
attr_accessor :result

match { |corrections| (self.result = do_rewrite corrections) == expected }

failure_message do
Expand Down
@@ -0,0 +1,71 @@
# frozen_string_literal: true

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

let(:config) { RuboCop::Config.new }

it 'registers an offense and corrects for code ' \
'that immediately follows accessor' do
expect_offense(<<~RUBY)
attr_accessor :foo
^^^^^^^^^^^^^^^^^^ Add an empty line after attribute accessor.
def do_something
end
RUBY

expect_correction(<<~RUBY)
attr_accessor :foo
def do_something
end
RUBY
end

it 'registers an offense and corrects for code ' \
'that immediately follows accessor with comment' do
expect_offense(<<~RUBY)
attr_accessor :foo # comment
^^^^^^^^^^^^^^^^^^ Add an empty line after attribute accessor.
def do_something
end
RUBY

expect_correction(<<~RUBY)
attr_accessor :foo # comment
def do_something
end
RUBY
end

it 'accepts code that separates a attribute accessor from the code ' \
'with a newline' do
expect_no_offenses(<<~RUBY)
attr_accessor :foo
def do_something
end
RUBY
end

it 'accepts code that separates attribute accessors from the code ' \
'with a newline' do
expect_no_offenses(<<~RUBY)
attr_accessor :foo
attr_reader :bar
attr_writer :baz
def do_something
end
RUBY
end

it 'accepts code when used in class definition' do
expect_no_offenses(<<~RUBY)
class Foo
attr_accessor :foo
end
RUBY
end
end

0 comments on commit 8831824

Please sign in to comment.