123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Layout::EmptyLineBetweenDefs

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::AutoCorrector, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/layout/empty_line_between_defs.rb

Overview

Checks whether class/module/method definitions are separated by one or more empty lines.

NumberOfEmptyLines can be an integer (default is 1) or an array (e.g. [1, 2]) to specify a minimum and maximum number of empty lines permitted.

AllowAdjacentOneLineDefs configures whether adjacent one-line definitions are considered an offense.

Examples:

EmptyLineBetweenMethodDefs: true (default)

# checks for empty lines between method definitions.

# bad
def a
end
def b
end

# good
def a
end

def b
end

EmptyLineBetweenClassDefs: true (default)

# checks for empty lines between class definitions.

# bad
class A
end
class B
end
def b
end

# good
class A
end

class B
end

def b
end

EmptyLineBetweenModuleDefs: true (default)

# checks for empty lines between module definitions.

# bad
module A
end
module B
end
def b
end

# good
module A
end

module B
end

def b
end

AllowAdjacentOneLineDefs: true (default)

# good
class ErrorA < BaseError; end
class ErrorB < BaseError; end

# good
class ErrorA < BaseError; end

class ErrorB < BaseError; end

# good - DefLikeMacros: [memoize]
memoize :attribute_a
memoize :attribute_b

# good
memoize :attribute_a

memoize :attribute_b

AllowAdjacentOneLineDefs: false

# bad
class ErrorA < BaseError; end
class ErrorB < BaseError; end

# good
class ErrorA < BaseError; end

class ErrorB < BaseError; end

# bad - DefLikeMacros: [memoize]
memoize :attribute_a
memoize :attribute_b

# good
memoize :attribute_a

memoize :attribute_b

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::RangeHelp - Included

#add_range,
#arguments_range

A range containing the first to the last argument of a method call or method definition.

#column_offset_between,
#contents_range

A range containing only the contents of a literal with delimiters (e.g.

#directions,
#effective_column

Returns the column attribute of the range, except if the range is on the first line and there’s a byte order mark at the beginning of that line, in which case 1 is subtracted from the column value.

#final_pos, #move_pos, #move_pos_str, #range_between, #range_by_whole_lines, #range_with_comments, #range_with_comments_and_lines, #range_with_surrounding_comma, #range_with_surrounding_space, #source_range

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?,
#target_gem_version

Returns a gems locked versions (i.e.

#target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #matches_absolute_include_pattern?, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Class Method Details

.autocorrect_incompatible_with

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 120

def self.autocorrect_incompatible_with
  [Layout::EmptyLines]
end

Instance Attribute Details

#allowance_range?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 318

def allowance_range?
  minimum_empty_lines != maximum_empty_lines
end

Instance Method Details

#autocorrect(corrector, prev_def, node, count)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 151

def autocorrect(corrector, prev_def, node, count)
  # finds position of first newline
  end_pos = end_loc(prev_def).end_pos
  source_buffer = end_loc(prev_def).source_buffer
  newline_pos = source_buffer.source.index("\n", end_pos)

  # Handle the case when multiple one-liners are on the same line.
  begin_pos = node.source_range.begin_pos
  newline_pos = begin_pos - 1 if newline_pos > begin_pos

  if count > maximum_empty_lines
    autocorrect_remove_lines(corrector, newline_pos, count)
  else
    autocorrect_insert_lines(corrector, newline_pos, count)
  end
end

#autocorrect_insert_lines(corrector, newline_pos, count) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 300

def autocorrect_insert_lines(corrector, newline_pos, count)
  difference = minimum_empty_lines - count
  where_to_insert = range_between(newline_pos, newline_pos + 1)

  corrector.insert_after(where_to_insert, "\n" * difference)
end

#autocorrect_remove_lines(corrector, newline_pos, count) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 293

def autocorrect_remove_lines(corrector, newline_pos, count)
  difference = count - maximum_empty_lines
  range_to_remove = range_between(newline_pos, newline_pos + difference)

  corrector.remove(range_to_remove)
end

#blank_lines_count_between(first_def_node, second_def_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 242

def blank_lines_count_between(first_def_node, second_def_node)
  lines_between_defs(first_def_node, second_def_node).count(&:blank?)
end

#candidate?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 180

def candidate?(node)
  return false unless node

  method_candidate?(node) || class_candidate?(node) || module_candidate?(node) ||
    macro_candidate?(node)
end

#check_defs(nodes)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 136

def check_defs(nodes)
  count = blank_lines_count_between(*nodes)

  return if line_count_allowed?(count)
  return if multiple_blank_lines_groups?(*nodes)
  return if nodes.all?(&:single_line?) && cop_config['AllowAdjacentOneLineDefs']

  correction_node = nodes.last

  location = def_location(correction_node)
  add_offense(location, message: message(correction_node, count: count)) do |corrector|
    autocorrect(corrector, *nodes, count)
  end
end

#class_candidate?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 206

def class_candidate?(node)
  cop_config['EmptyLineBetweenClassDefs'] && node.class_type?
end

#def_end(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 272

def def_end(node)
  end_loc(node).line
end

#def_location(correction_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 170

def def_location(correction_node)
  if correction_node.any_block_type?
    correction_node.source_range.join(correction_node.children.first.source_range)
  elsif correction_node.send_type?
    correction_node.source_range
  else
    correction_node.loc.keyword.join(correction_node.loc.name)
  end
end

#def_start(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 262

def def_start(node)
  node = node.send_node if node.any_block_type?

  if node.send_type?
    node.source_range.line
  else
    node.loc.keyword.line
  end
end

#empty_line_between_macros (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 187

def empty_line_between_macros
  @empty_line_between_macros ||= cop_config.fetch('DefLikeMacros', []).map(&:to_sym).freeze
end

#end_loc(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 276

def end_loc(node)
  end_location = node.source_range.end
  trailing_heredoc_end(node, end_location) || end_location
end

#expected_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 220

def expected_lines
  if allowance_range?
    "#{minimum_empty_lines..maximum_empty_lines} empty lines"
  else
    lines = maximum_empty_lines == 1 ? 'line' : 'lines'
    "#{maximum_empty_lines} empty #{lines}"
  end
end

#line_count_allowed?(count) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 238

def line_count_allowed?(count)
  (minimum_empty_lines..maximum_empty_lines).cover?(count)
end

#lines_between_defs(first_def_node, second_def_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 254

def lines_between_defs(first_def_node, second_def_node)
  begin_line_num = def_end(first_def_node)
  end_line_num = def_start(second_def_node) - 2
  return [] if end_line_num.negative?

  processed_source.lines[begin_line_num..end_line_num]
end

#macro_candidate?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 191

def macro_candidate?(node)
  macro_candidate = if node.any_block_type?
                      node.send_node
                    elsif node.send_type?
                      node
                    end
  return false unless macro_candidate

  macro_candidate.macro? && empty_line_between_macros.include?(macro_candidate.method_name)
end

#maximum_empty_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 250

def maximum_empty_lines
  Array(cop_config['NumberOfEmptyLines']).last
end

#message(node, count: nil) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 214

def message(node, count: nil)
  type = node_type(node)

  format(MSG, type: type, expected: expected_lines, actual: count)
end

#method_candidate?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 202

def method_candidate?(node)
  cop_config['EmptyLineBetweenMethodDefs'] && node.any_def_type?
end

#minimum_empty_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 246

def minimum_empty_lines
  Array(cop_config['NumberOfEmptyLines']).first
end

#module_candidate?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 210

def module_candidate?(node)
  cop_config['EmptyLineBetweenModuleDefs'] && node.module_type?
end

#multiple_blank_lines_groups?(first_def_node, second_def_node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 229

def multiple_blank_lines_groups?(first_def_node, second_def_node)
  lines = lines_between_defs(first_def_node, second_def_node)
  blank_start = lines.each_index.select { |i| lines[i].blank? }.max
  non_blank_end = lines.each_index.reject { |i| lines[i].blank? }.min
  return false if blank_start.nil? || non_blank_end.nil?

  blank_start > non_blank_end
end

#node_type(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 307

def node_type(node)
  case node.type
  when :def, :defs
    :method
  when :numblock, :itblock
    :block
  else
    node.type
  end
end

#on_begin(node)

We operate on begin nodes, instead of using OnMethodDef, so that we can walk over pairs of consecutive nodes and efficiently access a node’s predecessor; #prev_node ends up doing a linear scan over siblings, so we don’t want to call it on each def.

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 129

def on_begin(node)
  node.children.each_cons(2) do |prev, n|
    nodes = [prev, n]
    check_defs(nodes) if nodes.all? { |def_candidate| candidate?(def_candidate) }
  end
end

#trailing_heredoc_end(node, end_location) (private)

For an endless method whose body is a heredoc (e.g. def a = <<~TEXT), the node’s source range ends at the heredoc opening line, before the heredoc body. Use the heredoc’s closing delimiter so the def’s real end is located after the heredoc and blank-line insertion does not land inside the heredoc body.

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/empty_line_between_defs.rb', line 285

def trailing_heredoc_end(node, end_location)
  heredocs = node.each_descendant(:any_str).select(&:heredoc?)
  return if heredocs.empty?

  heredoc_end = heredocs.map { |heredoc| heredoc.loc.heredoc_end }.max_by(&:end_pos)
  heredoc_end if heredoc_end.end_pos > end_location.end_pos
end