Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Layout/EmptyLineAroundAttributeAccessor cop #7922

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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