123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Lint::SafeNavigationConsistency

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/lint/safe_navigation_consistency.rb

Overview

Check to make sure that if safe navigation is used in an && or {||} condition, consistent and appropriate safe navigation, without excess or deficiency, is used for all method calls on the same object.

Examples:

# bad
foo&.bar && foo&.baz

# good
foo&.bar && foo.baz

# bad
foo.bar && foo&.baz

# good
foo.bar && foo.baz

# bad
foo&.bar || foo.baz

# good
foo&.bar || foo&.baz

# bad
foo.bar || foo&.baz

# good
foo.bar || foo.baz

# bad
foo&.bar && (foobar.baz || foo&.baz)

# good
foo&.bar && (foobar.baz || foo.baz)

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

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::NilMethods - Included

::RuboCop::Cop::AllowedMethods - Included

::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

Instance Method Details

#already_appropriate_call?(operand, dot_op) ⇒ Boolean (private)

Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 98

def already_appropriate_call?(operand, dot_op)
  return true if operand.safe_navigation? && dot_op == '&.'

  (operand.dot? || operand.operator_method?) && dot_op == '.'
end

#collect_operands(node, operand_nodes) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 67

def collect_operands(node, operand_nodes)
  operand_nodes(node.lhs, operand_nodes)
  operand_nodes(node.rhs, operand_nodes)

  operand_nodes
end

#find_consistent_parts(grouped_operands) (private)

Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 83

def find_consistent_parts(grouped_operands)
  csend_in_and, csend_in_or, send_in_and, send_in_or = most_left_indices(grouped_operands)

  return if csend_in_and && csend_in_or && csend_in_and < csend_in_or

  if csend_in_and
    ['.', (send_in_and ? [send_in_and, csend_in_and].min : csend_in_and) + 1]
  elsif send_in_or && csend_in_or
    send_in_or < csend_in_or ? ['.', send_in_or + 1] : ['&.', csend_in_or + 1]
  elsif send_in_and && csend_in_or && send_in_and < csend_in_or
    ['.', csend_in_or]
  end
end

#most_left_indices(grouped_operands) (private)

Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 124

def most_left_indices(grouped_operands)
  indices = { csend_in_and: nil, csend_in_or: nil, send_in_and: nil, send_in_or: nil }

  grouped_operands.each_with_index do |operand, index|
    indices[:csend_in_and] ||= index if operand_in_and?(operand) && operand.csend_type?
    indices[:csend_in_or] ||= index if operand_in_or?(operand) && operand.csend_type?
    indices[:send_in_and] ||= index if operand_in_and?(operand) && !nilable?(operand)
    indices[:send_in_or] ||= index if operand_in_or?(operand) && !nilable?(operand)
  end

  indices.values
end

#nilable?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 154

def nilable?(node)
  node.csend_type? || nil_methods.include?(node.method_name)
end

#on_and(node) Also known as: #on_or

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 48

def on_and(node)
  all_operands = collect_operands(node, [])
  operand_groups = all_operands.group_by { |operand| receiver_name_as_key(operand, +'') }

  operand_groups.each_value do |grouped_operands|
    next unless (dot_op, begin_of_rest_operands = find_consistent_parts(grouped_operands))

    rest_operands = grouped_operands[begin_of_rest_operands..]
    rest_operands.each do |operand|
      next if already_appropriate_call?(operand, dot_op)

      register_offense(operand, dot_op)
    end
  end
end

#on_or(node)

Alias for #on_and.

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 63

alias on_or on_and

#operand_in_and?(node) ⇒ Boolean (private)

Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 138

def operand_in_and?(node)
  return true if node.parent.and_type?

  parent = node.parent.parent while node.parent.begin_type?

  parent&.and_type?
end

#operand_in_or?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 146

def operand_in_or?(node)
  return true if node.parent.or_type?

  parent = node.parent.parent while node.parent.begin_type?

  parent&.or_type?
end

#operand_nodes(operand, operand_nodes) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 115

def operand_nodes(operand, operand_nodes)
  if operand.operator_keyword?
    collect_operands(operand, operand_nodes)
  elsif operand.call_type?
    operand_nodes << operand
  end
end

#receiver_name_as_key(method, fully_receivers) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 74

def receiver_name_as_key(method, fully_receivers)
  if method.parent.call_type?
    receiver(method.parent, fully_receivers)
  else
    fully_receivers << method.receiver&.source.to_s
  end
end

#register_offense(operand, dot_operator) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/safe_navigation_consistency.rb', line 104

def register_offense(operand, dot_operator)
  offense_range = operand.operator_method? ? operand : operand.loc.dot
  message = dot_operator == '.' ? USE_DOT_MSG : USE_SAFE_NAVIGATION_MSG

  add_offense(offense_range, message: message) do |corrector|
    next if operand.operator_method?

    corrector.replace(operand.loc.dot, dot_operator)
  end
end