class RubbyCop::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
AssignmentReference
CLASSES_BY_TYPE
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
VariableReference
ZERO_ARITY_SUPER_TYPE

Public Instance Methods

investigate(processed_source) click to toggle source

Starting point.

# File lib/rubbycop/cop/variable_force.rb, line 75
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
process_node(node) click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 84
def process_node(node)
  catch(:skip_children) do
    dispatch_node(node)
    process_children(node)
  end
end
variable_table() click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 70
def variable_table
  @variable_table ||= VariableTable.new(self)
end

Private Instance Methods

descendant_reference(node) click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 353
def descendant_reference(node)
  case node.type
  when :lvar
    VariableReference.new(node.children.first)
  when :lvasgn
    AssignmentReference.new(node)
  when *OPERATOR_ASSIGNMENT_TYPES
    asgn_node = node.children.first
    if asgn_node.lvasgn_type?
      VariableReference.new(asgn_node.children.first)
    end
  end
end
dispatch_node(node) click to toggle source

rubbycop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity

# File lib/rubbycop/cop/variable_force.rb, line 112
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
each_descendant_reference(loop_node) { |reference| ... } click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 343
def each_descendant_reference(loop_node)
  # #each_descendant does not consider scope,
  # but we don't need to care about it here.
  loop_node.each_descendant do |node|
    reference = descendant_reference(node)

    yield reference if reference
  end
end
find_variables_in_loop(loop_node) click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 328
def find_variables_in_loop(loop_node)
  referenced_variable_names_in_loop = []
  assignment_nodes_in_loop = []

  each_descendant_reference(loop_node) do |reference|
    if reference.assignment?
      assignment_nodes_in_loop << reference.node
    else
      referenced_variable_names_in_loop << reference.name
    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/rubbycop/cop/variable_force.rb, line 94
def inspect_variables_in_scope(scope_node)
  variable_table.push_scope(scope_node)
  process_children(scope_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/rubbycop/cop/variable_force.rb, line 310
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/rubbycop/cop/variable_force.rb, line 100
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/rubbycop/cop/variable_force.rb, line 247
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_regexp_named_captures(node) click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 172
def process_regexp_named_captures(node)
  regexp_node, rhs_node = *node
  variable_names = regexp_captured_names(regexp_node)

  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/rubbycop/cop/variable_force.rb, line 262
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/rubbycop/cop/variable_force.rb, line 280
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.class_type?
    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/rubbycop/cop/variable_force.rb, line 297
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/rubbycop/cop/variable_force.rb, line 152
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

rubbycop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity

# File lib/rubbycop/cop/variable_force.rb, line 140
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/rubbycop/cop/variable_force.rb, line 235
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/rubbycop/cop/variable_force.rb, line 198
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.lvasgn_type?

  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/rubbycop/cop/variable_force.rb, line 242
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/rubbycop/cop/variable_force.rb, line 273
def process_zero_arity_super(node)
  variable_table.accessible_variables.each do |variable|
    next unless variable.method_argument?
    variable.reference!(node)
  end
end
regexp_captured_names(node) click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 191
def regexp_captured_names(node)
  regexp_string = node.children[0].children[0] || ''
  regexp = Regexp.new(regexp_string)

  regexp.named_captures.keys
end
scanned_node?(node) click to toggle source

Use Node#equal? for accurate check.

# File lib/rubbycop/cop/variable_force.rb, line 368
def scanned_node?(node)
  scanned_nodes.any? do |scanned_node|
    scanned_node.equal?(node)
  end
end
scanned_nodes() click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 374
def scanned_nodes
  @scanned_nodes ||= []
end
skip_children!() click to toggle source
# File lib/rubbycop/cop/variable_force.rb, line 107
def skip_children!
  throw :skip_children
end