class RuboCop::Cop::Style::AccessorGrouping

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.

NOTE: If there is a method call before the accessor method it is always allowed as it might be intended like Sorbet.

NOTE: If there is a RBS::Inline annotation comment just after the accessor method it is always allowed.

@example EnforcedStyle: grouped (default)

# bad
class Foo
  attr_reader :bar
  attr_reader :bax
  attr_reader :baz
end

# good
class Foo
  attr_reader :bar, :bax, :baz
end

# good
class Foo
  # may be intended comment for bar.
  attr_reader :bar

  sig { returns(String) }
  attr_reader :bax

  may_be_intended_annotation :baz
  attr_reader :baz
end

@example EnforcedStyle: separated

# bad
class Foo
  attr_reader :bar, :baz
end

# good
class Foo
  attr_reader :bar
  attr_reader :baz
end

Constants

GROUPED_MSG
SEPARATED_MSG

Public Instance Methods

on_class(node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 62
def on_class(node)
  class_send_elements(node).each do |macro|
    next unless macro.attribute_accessor?

    check(macro)
  end
end
Also aliased as: on_sclass, on_module
on_module(node)
Alias for: on_class
on_sclass(node)
Alias for: on_class

Private Instance Methods

autocorrect(corrector, node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 85
def autocorrect(corrector, node)
  if (preferred_accessors = preferred_accessors(node))
    corrector.replace(node, preferred_accessors)
  else
    range = range_with_surrounding_space(node.source_range, side: :left)
    corrector.remove(range)
  end
end
check(send_node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 74
def check(send_node)
  return if previous_line_comment?(send_node) || !groupable_accessor?(send_node)
  return unless (grouped_style? && groupable_sibling_accessors(send_node).size > 1) ||
                (separated_style? && send_node.arguments.size > 1)

  message = message(send_node)
  add_offense(send_node, message: message) do |corrector|
    autocorrect(corrector, send_node)
  end
end
class_send_elements(class_node) click to toggle source

rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/rubocop/cop/style/accessor_grouping.rb, line 122
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
group_accessors(node, accessors) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 165
def group_accessors(node, accessors)
  accessor_names = accessors.flat_map { |accessor| accessor.arguments.map(&:source) }.uniq

  "#{node.method_name} #{accessor_names.join(', ')}"
end
groupable_accessor?(node) click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/rubocop/cop/style/accessor_grouping.rb, line 99
def groupable_accessor?(node)
  return true unless (previous_expression = node.left_siblings.last)

  # Accessors with Sorbet `sig { ... }` blocks shouldn't be groupable.
  if previous_expression.block_type?
    previous_expression.child_nodes.each do |child_node|
      break previous_expression = child_node if child_node.send_type?
    end
  end

  return true unless previous_expression.send_type?

  # Accessors with RBS::Inline annotations shouldn't be groupable.
  return false if processed_source.comments.any? do |c|
    same_line?(c, previous_expression) && c.text.start_with?('#:')
  end

  previous_expression.attribute_accessor? ||
    previous_expression.access_modifier? ||
    node.first_line - previous_expression.last_line > 1 # there is a space between nodes
end
groupable_sibling_accessors(send_node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 142
def groupable_sibling_accessors(send_node)
  send_node.parent.each_child_node(:send).select do |sibling|
    sibling.attribute_accessor? &&
      sibling.method?(send_node.method_name) &&
      node_visibility(sibling) == node_visibility(send_node) &&
      groupable_accessor?(sibling) && !previous_line_comment?(sibling)
  end
end
grouped_style?() click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 134
def grouped_style?
  style == :grouped
end
message(send_node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 151
def message(send_node)
  msg = grouped_style? ? GROUPED_MSG : SEPARATED_MSG
  format(msg, accessor: send_node.method_name)
end
preferred_accessors(node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 156
def preferred_accessors(node)
  if grouped_style?
    accessors = groupable_sibling_accessors(node)
    group_accessors(node, accessors) if node.loc == accessors.first.loc
  else
    separate_accessors(node)
  end
end
previous_line_comment?(node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 94
def previous_line_comment?(node)
  comment_line?(processed_source[node.first_line - 2])
end
separate_accessors(node) click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 171
def separate_accessors(node)
  node.arguments.flat_map do |arg|
    lines = [
      *processed_source.ast_with_comments[arg].map(&:text),
      "#{node.method_name} #{arg.source}"
    ]
    if arg == node.first_argument
      lines
    else
      indent = ' ' * node.loc.column
      lines.map { |line| "#{indent}#{line}" }
    end
  end.join("\n")
end
separated_style?() click to toggle source
# File lib/rubocop/cop/style/accessor_grouping.rb, line 138
def separated_style?
  style == :separated
end