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
class ErrorC < BaseError; end

# good
class ErrorA < BaseError; end

class ErrorB < BaseError; end

class ErrorC < BaseError; end

AllowAdjacentOneLineDefs: false

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

# good
class ErrorA < BaseError; end

class ErrorB < BaseError; end

class ErrorC < BaseError; end

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, #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_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, #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 114

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 291

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 145

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 275

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 268

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 228

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 172

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 130

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 192

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 256

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 164

def def_location(correction_node)
  if correction_node.block_type?
    correction_node.source_range.join(correction_node.children.first.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 248

def def_start(node)
  if node.block_type? && node.children.first.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 179

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

#end_loc(node) (private)

[ GitHub ]

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

def end_loc(node)
  if (node.def_type? || node.defs_type?) && node.endless?
    node.source_range.end
  else
    node.loc.end
  end
end

#expected_lines (private)

[ GitHub ]

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

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 224

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 240

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 183

def macro_candidate?(node)
  node.block_type? && node.children.first.macro? &&
    empty_line_between_macros.include?(node.children.first.method_name)
end

#maximum_empty_lines (private)

[ GitHub ]

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

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 200

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 188

def method_candidate?(node)
  cop_config['EmptyLineBetweenMethodDefs'] && (node.def_type? || node.defs_type?)
end

#minimum_empty_lines (private)

[ GitHub ]

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

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 196

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 215

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 282

def node_type(node)
  case node.type
  when :def, :defs
    :method
  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 123

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