module SyntaxTree::WithScope

WithScope is a module intended to be included in classes inheriting from Visitor. The module overrides a few visit methods to automatically keep track of local variables and arguments defined in the current scope. Example usage:

class MyVisitor < Visitor
  include WithScope

  def visit_ident(node)
    # Check if we're visiting an identifier for an argument, a local
    # variable or something else
    local = current_scope.find_local(node)

    if local.type == :argument
      # handle identifiers for arguments
    elsif local.type == :variable
      # handle identifiers for variables
    else
      # handle other identifiers, such as method names
    end
  end
end

Attributes

current_scope[R]

Public Class Methods

new(*args, **kwargs, &block) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 122
def initialize(*args, **kwargs, &block)
  super

  @current_scope = Scope.new(0)
  @next_scope_id = 0
end

Public Instance Methods

visit_binary(node) click to toggle source

Visit for capturing local variables defined in regex named capture groups

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 236
def visit_binary(node)
  if node.operator == :=~
    left = node.left

    if left.is_a?(RegexpLiteral) && left.parts.length == 1 &&
         left.parts.first.is_a?(TStringContent)
      content = left.parts.first

      value = content.value
      location = content.location
      start_line = location.start_line

      Regexp
        .new(value, Regexp::FIXEDENCODING)
        .names
        .each do |name|
          offset = value.index(/\(\?<#{Regexp.escape(name)}>/)
          line = start_line + value[0...offset].count("\n")

          # We need to add 3 to account for these three characters
          # prefixing a named capture (?<
          column = location.start_column + offset + 3
          if value[0...offset].include?("\n")
            column =
              value[0...offset].length - value[0...offset].rindex("\n") +
                3 - 1
          end

          ident_location =
            Location.new(
              start_line: line,
              start_char: location.start_char + offset,
              start_column: column,
              end_line: line,
              end_char: location.start_char + offset + name.length,
              end_column: column + name.length
            )

          identifier = Ident.new(value: name, location: ident_location)
          current_scope.add_local_definition(identifier, :variable)
        end
    end
  end

  super
end
visit_block_var(node) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 189
def visit_block_var(node)
  node.locals.each do |local|
    current_scope.add_local_definition(local, :variable)
  end

  super
end
Also aliased as: visit_lambda_var
visit_blockarg(node) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 182
def visit_blockarg(node)
  name = node.name
  current_scope.add_local_definition(name, :argument) if name

  super
end
visit_class(node) click to toggle source

Visits for nodes that create new scopes, such as classes, modules and method definitions.

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 131
def visit_class(node)
  with_scope { super }
end
visit_def(node) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 147
def visit_def(node)
  with_scope { super }
end
visit_kwrest_param(node) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 175
def visit_kwrest_param(node)
  name = node.name
  current_scope.add_local_definition(name, :argument) if name

  super
end
visit_lambda_var(node)
Alias for: visit_block_var
visit_method_add_block(node) click to toggle source

When we find a method invocation with a block, only the code that happens inside of the block needs a fresh scope. The method invocation itself happens in the same scope.

# File lib/syntax_tree/with_scope.rb, line 142
def visit_method_add_block(node)
  visit(node.call)
  with_scope(current_scope) { visit(node.block) }
end
visit_module(node) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 135
def visit_module(node)
  with_scope { super }
end
visit_params(node) click to toggle source

Visit for keeping track of local arguments, such as method and block arguments.

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 153
def visit_params(node)
  add_argument_definitions(node.requireds)
  add_argument_definitions(node.posts)

  node.keywords.each do |param|
    current_scope.add_local_definition(param.first, :argument)
  end

  node.optionals.each do |param|
    current_scope.add_local_definition(param.first, :argument)
  end

  super
end
visit_pinned_var_ref(node) click to toggle source

Visit for keeping track of local variable definitions

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 207
def visit_pinned_var_ref(node)
  value = node.value
  current_scope.add_local_usage(value, :variable) if value.is_a?(Ident)

  super
end
visit_rest_param(node) click to toggle source
Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 168
def visit_rest_param(node)
  name = node.name
  current_scope.add_local_definition(name, :argument) if name

  super
end
visit_var_field(node) click to toggle source

Visit for keeping track of local variable definitions

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 199
def visit_var_field(node)
  value = node.value
  current_scope.add_local_definition(value, :variable) if value.is_a?(Ident)

  super
end
visit_var_ref(node) click to toggle source

Visits for keeping track of variable and argument usages

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 215
def visit_var_ref(node)
  value = node.value

  if value.is_a?(Ident)
    definition = current_scope.find_local(value.value)
    current_scope.add_local_usage(value, definition.type) if definition
  end

  super
end
visit_vcall(node) click to toggle source

When using regex named capture groups, vcalls might actually be a variable

Calls superclass method
# File lib/syntax_tree/with_scope.rb, line 227
def visit_vcall(node)
  value = node.value
  definition = current_scope.find_local(value.value)
  current_scope.add_local_usage(value, definition.type) if definition

  super
end