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
Public Class Methods
# 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 for capturing local variables defined in regex named capture groups
# 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
# 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
# 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
Visits for nodes that create new scopes, such as classes, modules and method definitions.
# File lib/syntax_tree/with_scope.rb, line 131 def visit_class(node) with_scope { super } end
# File lib/syntax_tree/with_scope.rb, line 147 def visit_def(node) with_scope { super } end
# 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
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
# File lib/syntax_tree/with_scope.rb, line 135 def visit_module(node) with_scope { super } end
Visit for keeping track of local arguments, such as method and block arguments.
# 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 for keeping track of local variable definitions
# 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
# 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 for keeping track of local variable definitions
# 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
Visits for keeping track of variable and argument usages
# 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
When
using regex named capture groups, vcalls might actually be a variable
# 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