class RuboCop::Cop::VariableForce

This force provides a way to track local variables and scopes of Ruby. Cops interact with this force need to override some of the hook methods.

def before_entering_scope(scope, variable_table)
end

def after_entering_scope(scope, variable_table)
end

def before_leaving_scope(scope, variable_table)
end

def after_leaving_scope(scope, variable_table)
end

def before_declaring_variable(variable, variable_table)
end

def after_declaring_variable(variable, variable_table)
end

Constants

ARGUMENT_DECLARATION_TYPES
LOGICAL_OPERATOR_ASSIGNMENT_TYPES
LOOP_TYPES
MULTIPLE_ASSIGNMENT_TYPE
OPERATOR_ASSIGNMENT_TYPES
POST_CONDITION_LOOP_TYPES
REGEXP_NAMED_CAPTURE_TYPE
RESCUE_TYPE
SCOPE_TYPES
SEND_TYPE
TWISTED_SCOPE_TYPES
VARIABLE_ASSIGNMENT_TYPE
VARIABLE_ASSIGNMENT_TYPES
VARIABLE_REFERENCE_TYPE
ZERO_ARITY_SUPER_TYPE

Public Instance Methods

dispatch_node(node) click to toggle source

rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity

# File lib/rubocop/cop/variable_force.rb, line 98
def dispatch_node(node)
  case node.type
  when VARIABLE_ASSIGNMENT_TYPE
    process_variable_assignment(node)
  when REGEXP_NAMED_CAPTURE_TYPE
    process_regexp_named_captures(node)
  when MULTIPLE_ASSIGNMENT_TYPE
    process_variable_multiple_assignment(node)
  when VARIABLE_REFERENCE_TYPE
    process_variable_referencing(node)
  when RESCUE_TYPE
    process_rescue(node)
  when ZERO_ARITY_SUPER_TYPE
    process_zero_arity_super(node)
  when SEND_TYPE
    process_send(node)
  when *ARGUMENT_DECLARATION_TYPES
    process_variable_declaration(node)
  when *OPERATOR_ASSIGNMENT_TYPES
    process_variable_operator_assignment(node)
  when *LOOP_TYPES
    process_loop(node)
  when *SCOPE_TYPES
    process_scope(node)
  end
end
find_variables_in_loop(loop_node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 310
def find_variables_in_loop(loop_node)
  referenced_variable_names_in_loop = []
  assignment_nodes_in_loop = []

  # #each_descendant does not consider scope,
  # but we don't need to care about it here.
  loop_node.each_descendant do |node|
    case node.type
    when :lvar
      referenced_variable_names_in_loop << node.children.first
    when :lvasgn
      assignment_nodes_in_loop << node
    when *OPERATOR_ASSIGNMENT_TYPES
      asgn_node = node.children.first
      if asgn_node.type == :lvasgn
        referenced_variable_names_in_loop << asgn_node.children.first
      end
    end
  end

  [referenced_variable_names_in_loop, assignment_nodes_in_loop]
end
inspect_variables_in_scope(scope_node) click to toggle source

This is called for each scope recursively.

# File lib/rubocop/cop/variable_force.rb, line 73
def inspect_variables_in_scope(scope_node)
  variable_table.push_scope(scope_node)
  process_children(scope_node)
  variable_table.pop_scope
end
investigate(processed_source) click to toggle source

Starting point.

# File lib/rubocop/cop/variable_force.rb, line 63
def investigate(processed_source)
  root_node = processed_source.ast
  return unless root_node

  variable_table.push_scope(root_node)
  process_node(root_node)
  variable_table.pop_scope
end
mark_assignments_as_referenced_in_loop(node) click to toggle source

Mark all assignments which are referenced in the same loop as referenced by ignoring AST order since they would be referenced in next iteration.

# File lib/rubocop/cop/variable_force.rb, line 292
def mark_assignments_as_referenced_in_loop(node)
  referenced_variable_names_in_loop, assignment_nodes_in_loop =
    find_variables_in_loop(node)

  referenced_variable_names_in_loop.each do |name|
    variable = variable_table.find_variable(name)
    # Non related references which are caught in the above scan
    # would be skipped here.
    next unless variable
    variable.assignments.each do |assignment|
      next if assignment_nodes_in_loop.none? do |assignment_node|
                assignment_node.equal?(assignment.node)
              end
      assignment.reference!
    end
  end
end
process_children(origin_node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 79
def process_children(origin_node)
  origin_node.each_child_node do |child_node|
    next if scanned_node?(child_node)
    process_node(child_node)
  end
end
process_loop(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 229
def process_loop(node)
  if POST_CONDITION_LOOP_TYPES.include?(node.type)
    # See the comment at the end of file for this behavior.
    condition_node, body_node = *node
    process_node(body_node)
    process_node(condition_node)
  else
    process_children(node)
  end

  mark_assignments_as_referenced_in_loop(node)

  skip_children!
end
process_node(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 86
def process_node(node)
  catch(:skip_children) do
    dispatch_node(node)
    process_children(node)
  end
end
process_regexp_named_captures(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 158
def process_regexp_named_captures(node)
  regexp_node, rhs_node = *node

  regexp_string = regexp_node.children[0].children[0]
  regexp = Regexp.new(regexp_string)
  variable_names = regexp.named_captures.keys

  variable_names.each do |name|
    next if variable_table.variable_exist?(name)
    variable_table.declare_variable(name, node)
  end

  process_node(rhs_node)
  process_node(regexp_node)

  variable_names.each do |name|
    variable_table.assign_to_variable(name, node)
  end

  skip_children!
end
process_rescue(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 244
def process_rescue(node)
  resbody_nodes = node.each_child_node(:resbody)

  contain_retry = resbody_nodes.any? do |resbody_node|
    resbody_node.each_descendant.any?(&:retry_type?)
  end

  # Treat begin..rescue..end with retry as a loop.
  process_loop(node) if contain_retry
end
process_scope(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 262
def process_scope(node)
  if TWISTED_SCOPE_TYPES.include?(node.type)
    # See the comment at the end of file for this behavior.
    twisted_nodes = [node.children[0]]
    twisted_nodes << node.children[1] if node.type == :class
    twisted_nodes.compact!

    twisted_nodes.each do |twisted_node|
      process_node(twisted_node)
      scanned_nodes << twisted_node
    end
  end

  inspect_variables_in_scope(node)
  skip_children!
end
process_send(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 279
def process_send(node)
  _receiver, method_name, args = *node
  return unless method_name == :binding
  return if args && !args.children.empty?

  variable_table.accessible_variables.each do |variable|
    variable.reference!(node)
  end
end
process_variable_assignment(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 138
def process_variable_assignment(node)
  name = node.children.first

  unless variable_table.variable_exist?(name)
    variable_table.declare_variable(name, node)
  end

  # Need to scan rhs before assignment so that we can mark previous
  # assignments as referenced if rhs has referencing to the variable
  # itself like:
  #
  #   foo = 1
  #   foo = foo + 1
  process_children(node)

  variable_table.assign_to_variable(name, node)

  skip_children!
end
process_variable_declaration(node) click to toggle source

rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity

# File lib/rubocop/cop/variable_force.rb, line 126
def process_variable_declaration(node)
  variable_name = node.children.first

  # restarg and kwrestarg would have no name:
  #
  #   def initialize(*)
  #   end
  return unless variable_name

  variable_table.declare_variable(variable_name, node)
end
process_variable_multiple_assignment(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 217
def process_variable_multiple_assignment(node)
  lhs_node, rhs_node = *node
  process_node(rhs_node)
  process_node(lhs_node)
  skip_children!
end
process_variable_operator_assignment(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 180
def process_variable_operator_assignment(node)
  if LOGICAL_OPERATOR_ASSIGNMENT_TYPES.include?(node.type)
    asgn_node, rhs_node = *node
  else
    asgn_node, _operator, rhs_node = *node
  end

  return unless asgn_node.type == :lvasgn

  name = asgn_node.children.first

  unless variable_table.variable_exist?(name)
    variable_table.declare_variable(name, asgn_node)
  end

  # The following statements:
  #
  #   foo = 1
  #   foo += foo = 2
  #   # => 3
  #
  # are equivalent to:
  #
  #   foo = 1
  #   foo = foo + (foo = 2)
  #   # => 3
  #
  # So, at operator assignment node, we need to reference the variable
  # before processing rhs nodes.

  variable_table.reference_variable(name, node)
  process_node(rhs_node)
  variable_table.assign_to_variable(name, asgn_node)

  skip_children!
end
process_variable_referencing(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 224
def process_variable_referencing(node)
  name = node.children.first
  variable_table.reference_variable(name, node)
end
process_zero_arity_super(node) click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 255
def process_zero_arity_super(node)
  variable_table.accessible_variables.each do |variable|
    next unless variable.method_argument?
    variable.reference!(node)
  end
end
scanned_node?(node) click to toggle source

Use Node#equal? for accurate check.

# File lib/rubocop/cop/variable_force.rb, line 334
def scanned_node?(node)
  scanned_nodes.any? do |scanned_node|
    scanned_node.equal?(node)
  end
end
scanned_nodes() click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 340
def scanned_nodes
  @scanned_nodes ||= []
end
skip_children!() click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 93
def skip_children!
  throw :skip_children
end
variable_table() click to toggle source
# File lib/rubocop/cop/variable_force.rb, line 58
def variable_table
  @variable_table ||= VariableTable.new(self)
end