class RuboCop::Cop::Style::ClassAndModuleChildren

Checks the style of children definitions at classes and modules. Basically there are two different styles:

@safety

Autocorrection is unsafe.

Moving from compact to nested children requires knowledge of whether the
outer parent is a module or a class. Moving from nested to compact requires
verification that the outer parent is defined elsewhere. Rubocop does not
have the knowledge to perform either operation safely and thus requires
manual oversight.

@example EnforcedStyle: nested (default)

# good
# have each child on its own line
class Foo
  class Bar
  end
end

@example EnforcedStyle: compact

# good
# combine definitions as much as possible
class Foo::Bar
end

The compact style is only forced for classes/modules with one child.

Constants

COMPACT_MSG
NESTED_MSG

Public Instance Methods

on_class(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 41
def on_class(node)
  return if node.parent_class && style != :nested

  check_style(node, node.body)
end
on_module(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 47
def on_module(node)
  check_style(node, node.body)
end

Private Instance Methods

add_trailing_end(corrector, node, padding) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 88
def add_trailing_end(corrector, node, padding)
  replacement = "#{padding}end\n#{leading_spaces(node)}end"
  corrector.replace(node.loc.end, replacement)
end
autocorrect(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 177
def autocorrect(corrector, node)
  return if node.class_type? && node.parent_class && style != :nested

  nest_or_compact(corrector, node)
end
check_compact_style(node, body) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 166
def check_compact_style(node, body)
  parent = node.parent
  return if parent&.class_type? || parent&.module_type?

  return unless needs_compacting?(body)

  add_offense(node.loc.name, message: COMPACT_MSG) do |corrector|
    autocorrect(corrector, node)
  end
end
check_nested_style(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 158
def check_nested_style(node)
  return unless compact_node_name?(node)

  add_offense(node.loc.name, message: NESTED_MSG) do |corrector|
    autocorrect(corrector, node)
  end
end
check_style(node, body) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 148
def check_style(node, body)
  return if node.identifier.children[0]&.cbase_type?

  if style == :nested
    check_nested_style(node)
  else
    check_compact_style(node, body)
  end
end
compact_definition(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 93
def compact_definition(corrector, node)
  compact_node(corrector, node)
  remove_end(corrector, node.body)
  unindent(corrector, node)
end
compact_identifier_name(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 114
def compact_identifier_name(node)
  "#{node.identifier.const_name}::" \
    "#{node.body.children.first.const_name}"
end
compact_node(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 99
def compact_node(corrector, node)
  range = range_between(node.loc.keyword.begin_pos, node.body.loc.name.end_pos)
  corrector.replace(range, compact_replacement(node))
end
compact_node_name?(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 187
def compact_node_name?(node)
  /::/.match?(node.identifier.source)
end
compact_replacement(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 104
def compact_replacement(node)
  replacement = "#{node.body.type} #{compact_identifier_name(node)}"

  body_comments = processed_source.ast_with_comments[node.body]
  unless body_comments.empty?
    replacement = body_comments.map(&:text).push(replacement).join("\n")
  end
  replacement
end
configured_indentation_width() click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 127
def configured_indentation_width
  config.for_badge(Layout::IndentationWidth.badge).fetch('Width', 2)
end
indent_width() click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 144
def indent_width
  @config.for_cop('Layout/IndentationWidth')['Width'] || 2
end
leading_spaces(node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 140
def leading_spaces(node)
  node.source_range.source_line[/\A\s*/]
end
needs_compacting?(body) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 183
def needs_compacting?(body)
  body && %i[module class].include?(body.type)
end
nest_definition(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 61
def nest_definition(corrector, node)
  padding = ((' ' * indent_width) + leading_spaces(node)).to_s
  padding_for_trailing_end = padding.sub(' ' * node.loc.end.column, '')

  replace_namespace_keyword(corrector, node)
  split_on_double_colon(corrector, node, padding)
  add_trailing_end(corrector, node, padding_for_trailing_end)
end
nest_or_compact(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 53
def nest_or_compact(corrector, node)
  if style == :nested
    nest_definition(corrector, node)
  else
    compact_definition(corrector, node)
  end
end
remove_end(corrector, body) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 119
def remove_end(corrector, body)
  remove_begin_pos = body.loc.end.begin_pos - leading_spaces(body).size
  adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
  range = range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)

  corrector.remove(range)
end
replace_namespace_keyword(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 70
def replace_namespace_keyword(corrector, node)
  class_definition = node.left_sibling&.each_node(:class)&.find do |class_node|
    class_node.identifier == node.identifier.namespace
  end
  namespace_keyword = class_definition ? 'class' : 'module'

  corrector.replace(node.loc.keyword, namespace_keyword)
end
split_on_double_colon(corrector, node, padding) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 79
def split_on_double_colon(corrector, node, padding)
  children_definition = node.children.first
  range = range_between(children_definition.loc.double_colon.begin_pos,
                        children_definition.loc.double_colon.end_pos)
  replacement = "\n#{padding}#{node.loc.keyword.source} "

  corrector.replace(range, replacement)
end
unindent(corrector, node) click to toggle source
# File lib/rubocop/cop/style/class_and_module_children.rb, line 131
def unindent(corrector, node)
  return if node.body.children.last.nil?

  column_delta = configured_indentation_width - leading_spaces(node.body.children.last).size
  return if column_delta.zero?

  AlignmentCorrector.correct(corrector, processed_source, node, column_delta)
end