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
Showing
11 changed files
with
297 additions
and
11 deletions.
There are no files selected for viewing
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
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
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,136 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Style | ||
# This cop checks for grouping of accessors in `class` and `module` bodies. | ||
# By default it enforces accessors to be placed in grouped declarations, | ||
# but it can be configured to enforce separating them in multiple declarations. | ||
# | ||
# @example EnforcedStyle: grouped (default) | ||
# # bad | ||
# class Foo | ||
# attr_reader :bar | ||
# attr_reader :baz | ||
# end | ||
# | ||
# # good | ||
# class Foo | ||
# attr_reader :bar, :baz | ||
# end | ||
# | ||
# @example EnforcedStyle: separated | ||
# # bad | ||
# class Foo | ||
# attr_reader :bar, :baz | ||
# end | ||
# | ||
# # good | ||
# class Foo | ||
# attr_reader :bar | ||
# attr_reader :baz | ||
# end | ||
# | ||
class AccessorGrouping < Cop | ||
include ConfigurableEnforcedStyle | ||
|
||
GROUPED_MSG = 'Group together all `%<accessor>s` attributes.' | ||
SEPARATED_MSG = 'Use one attribute per `%<accessor>s`.' | ||
|
||
ACCESSOR_METHODS = %i[attr_reader attr_writer attr_accessor attr].freeze | ||
|
||
def on_class(node) | ||
class_send_elements(node).each do |macro| | ||
next unless accessor?(macro) | ||
|
||
check(macro) | ||
end | ||
end | ||
alias on_module on_class | ||
|
||
def autocorrect(node) | ||
lambda do |corrector| | ||
corrector.replace(node, correction(node)) | ||
end | ||
end | ||
|
||
private | ||
|
||
def class_send_elements(class_node) | ||
class_def = class_node.body | ||
|
||
if !class_def || class_def.def_type? | ||
[] | ||
elsif class_def.send_type? | ||
[class_def] | ||
else | ||
class_def.each_child_node(:send).to_a | ||
end | ||
end | ||
|
||
def accessor?(send_node) | ||
send_node.macro? && ACCESSOR_METHODS.include?(send_node.method_name) | ||
end | ||
|
||
def check(send_node) | ||
if grouped_style? && sibling_accessors(send_node).size > 1 | ||
add_offense(send_node) | ||
elsif separated_style? && send_node.arguments.size > 1 | ||
add_offense(send_node) | ||
end | ||
end | ||
|
||
def grouped_style? | ||
style == :grouped | ||
end | ||
|
||
def separated_style? | ||
style == :separated | ||
end | ||
|
||
def sibling_accessors(send_node) | ||
send_node.parent.each_child_node(:send).select do |sibling| | ||
sibling.macro? && sibling.method?(send_node.method_name) | ||
end | ||
end | ||
|
||
def message(send_node) | ||
msg = grouped_style? ? GROUPED_MSG : SEPARATED_MSG | ||
format(msg, accessor: send_node.method_name) | ||
end | ||
|
||
def correction(node) | ||
if grouped_style? | ||
accessors = sibling_accessors(node) | ||
if node == accessors.first | ||
group_accessors(node, accessors) | ||
else | ||
'' | ||
end | ||
else | ||
separate_accessors(node) | ||
end | ||
end | ||
|
||
def group_accessors(node, accessors) | ||
accessor_names = accessors.flat_map do |accessor| | ||
accessor.arguments.map(&:source) | ||
end | ||
|
||
"#{node.method_name} #{accessor_names.join(', ')}" | ||
end | ||
|
||
def separate_accessors(node) | ||
node.arguments.map do |arg| | ||
if arg == node.arguments.first | ||
"#{node.method_name} #{arg.source}" | ||
else | ||
indent = ' ' * node.loc.column | ||
"#{indent}#{node.method_name} #{arg.source}" | ||
end | ||
end.join("\n") | ||
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
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,79 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Style::AccessorGrouping, :config do | ||
subject(:cop) { described_class.new(config) } | ||
|
||
context 'when EnforcedStyle is grouped' do | ||
let(:cop_config) do | ||
{ 'EnforcedStyle' => 'grouped' } | ||
end | ||
|
||
it 'registers an offense and corrects when using separated accessors' do | ||
expect_offense(<<~RUBY) | ||
class Foo | ||
attr_reader :bar1 | ||
^^^^^^^^^^^^^^^^^ Group together all `attr_reader` attributes. | ||
attr_reader :bar2 | ||
^^^^^^^^^^^^^^^^^ Group together all `attr_reader` attributes. | ||
attr_accessor :quux | ||
attr_reader :bar3, :bar4 | ||
^^^^^^^^^^^^^^^^^^^^^^^^ Group together all `attr_reader` attributes. | ||
other_macro :zoo | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
class Foo | ||
attr_reader :bar1, :bar2, :bar3, :bar4 | ||
attr_accessor :quux | ||
other_macro :zoo | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using grouped accessors' do | ||
expect_no_offenses(<<~RUBY) | ||
class Foo | ||
attr_reader :bar, :baz | ||
end | ||
RUBY | ||
end | ||
end | ||
|
||
context 'when EnforcedStyle is separated' do | ||
let(:cop_config) do | ||
{ 'EnforcedStyle' => 'separated' } | ||
end | ||
|
||
it 'registers an offense and corrects when using grouped accessors' do | ||
expect_offense(<<~RUBY) | ||
class Foo | ||
attr_reader :bar, :baz | ||
^^^^^^^^^^^^^^^^^^^^^^ Use one attribute per `attr_reader`. | ||
attr_accessor :quux | ||
other_macro :zoo, :woo | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
class Foo | ||
attr_reader :bar | ||
attr_reader :baz | ||
attr_accessor :quux | ||
other_macro :zoo, :woo | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using separated accessors' do | ||
expect_no_offenses(<<~RUBY) | ||
class Foo | ||
attr_reader :bar | ||
attr_reader :baz | ||
end | ||
RUBY | ||
end | ||
end | ||
end |