module Rubocop::Cop::VariableInspector

This module provides a way to track local variables and scopes of Ruby. This is intended to be used as mix-in, and the user class may override some of hook methods.

Constants

ARGUMENT_DECLARATION_TYPES
SCOPE_TYPES
VARIABLE_ASSIGNMENT_TYPES
VARIABLE_DECLARATION_TYPES
VARIABLE_USE_TYPES

Public Instance Methods

after_declaring_variable(variable_entry) click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 276
def after_declaring_variable(variable_entry)
end
after_entering_scope(scope) click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 264
def after_entering_scope(scope)
end
after_leaving_scope(scope) click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 270
def after_leaving_scope(scope)
end
before_declaring_variable(variable_entry) click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 273
def before_declaring_variable(variable_entry)
end
before_entering_scope(scope) click to toggle source

Hooks

# File lib/rubocop/cop/variable_inspector.rb, line 261
def before_entering_scope(scope)
end
before_leaving_scope(scope) click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 267
def before_leaving_scope(scope)
end
inspect_variables(root_node) click to toggle source

Starting point.

# File lib/rubocop/cop/variable_inspector.rb, line 156
def inspect_variables(root_node)
  return unless root_node

  # Wrap with begin node if it's standalone node.
  unless root_node.type == :begin
    root_node = Parser::AST::Node.new(:begin, [root_node])
  end

  inspect_variables_in_scope(root_node)
end
inspect_variables_in_scope(scope_node) click to toggle source

This is called for each scope recursively.

# File lib/rubocop/cop/variable_inspector.rb, line 168
def inspect_variables_in_scope(scope_node)
  variable_table.push_scope(scope_node)

  NodeScanner.scan_nodes_in_scope(scope_node) do |node, index|
    if scope_node.type == :block && index == 0 && node.type == :send
      # Avoid processing method argument nodes of outer scope
      # in current block scope.
      # See #process_node.
      throw :skip_children
    elsif [:sclass, :defs].include?(scope_node.type) && index == 0
      throw :skip_children
    end

    process_node(node)
  end

  variable_table.pop_scope
end
process_node(node) click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 187
def process_node(node)
  case node.type
  when *ARGUMENT_DECLARATION_TYPES
    variable_table.add_variable_entry(node)
  when *VARIABLE_ASSIGNMENT_TYPES
    variable_name = node.children.first
    variable_entry = variable_table.find_variable_entry(variable_name)
    if variable_entry
      variable_entry.used = true
    else
      variable_table.add_variable_entry(node)
    end
  when *VARIABLE_USE_TYPES
    variable_name = node.children.first
    variable_entry = variable_table.find_variable_entry(variable_name)
    unless variable_entry
      fail "Using undeclared local variable \"#{variable_name}\" " +
           "at #{node.loc.expression}, #{node.inspect}"
    end
    variable_entry.used = true
  when :block
    # The variable foo belongs to the top level scope,
    # but in AST, it's under the block node.
    #
    # Ruby:
    #   some_method(foo = 1) do
    #   end
    #   puts foo
    #
    # AST:
    #   (begin
    #     (block
    #       (send nil :some_method
    #         (lvasgn :foo
    #           (int 1)))
    #       (args) nil)
    #     (send nil :puts
    #       (lvar :foo)))
    #
    # So the nodes of the method argument need to be processed
    # in current scope before dive into the block scope.
    NodeScanner.scan_nodes_in_scope(node.children.first) do |n|
      process_node(n)
    end
    # Now go into the block scope.
    inspect_variables_in_scope(node)
  when :sclass, :defs
    # Same thing.
    #
    # Ruby:
    #   instance = Object.new
    #   class << instance
    #     foo = 1
    #   end
    #
    # AST:
    #   (begin
    #     (lvasgn :instance
    #       (send
    #         (const nil :Object) :new))
    #     (sclass
    #       (lvar :instance)
    #       (begin
    #         (lvasgn :foo
    #           (int 1))
    process_node(node.children.first)
    inspect_variables_in_scope(node)
  when *SCOPE_TYPES
    inspect_variables_in_scope(node)
  end
end
variable_table() click to toggle source
# File lib/rubocop/cop/variable_inspector.rb, line 151
def variable_table
  @variable_table ||= VariableTable.new(self)
end