doc-src/parsejs/lib/parsejs/scope.rb (124 lines of code) (raw):

require "parsejs/visitor" module ParseJS module AST module Scope attr_accessor :scope_variables, :parent_variables, :variable_access attr_accessor :parent_scope, :child_scopes def variable_in_scope?(name) return true if variable?(name) parent_scope && parent_scope.variable_in_scope?(name) end def variable?(name) scope_variable?(name) || parent_variable?(name) || variable_access?(name) end def scope_variable?(name) scope_variables && scope_variables.include?(name) end def parent_variable?(name) parent_variables && parent_variables.include?(name) end def variable_access?(name) variable_access && variable_access.include?(name) end # determine whether there is a reference for a particular variable # to a parent scope. def parent_variable_access?(name) # if this scope has a "var x = 1" type of declaration for this name, # it is not referencing a parent scope. return false if scope_variable?(name) # otherwise, if there is a variable access for this name, it's # referencing a parent scope. variable_access?(name) end # determine whether a variable can be added without causing damage # to child scopes. def available_variable?(name) # if the current scope is already using the variable, it's # unavailable. return false if variable?(name) # if any of the child scopes reference the variable as a # parent variable, it's not available. !any_child_references_parent_variable?(name) end def any_child_references_parent_variable?(name) unless child_scopes.nil? # this should really check if all descendent scopes see a # scope variable before they see a parent refernce return false if child_scopes.all? { |s| s.scope_variable?(name) } child_scopes.any? do |s| s.parent_variable_access?(name) || s.parent_variable?(name) || s.any_child_references_parent_variable?(name) end end end end class ProcessVariables < Visitor include ParseJS::Visitor::ScopeManager def self.process(ast) new.visit(ast) end def push_scope_variable(name) vars = current_scope.scope_variables ||= Set.new vars << name end def push_parent_variable(name) vars = current_scope.parent_variables ||= Set.new vars << name end def push_variable_access(name) vars = current_scope.variable_access ||= Set.new vars << name end def possible_variable_access(var) push_variable_access var.val if var && var.type?("Identifier") end def possible_variable_access_array(arr) arr.each { |arg| possible_variable_access arg } end def visit_VariableDeclarator(decl) push_scope_variable decl.id.val possible_variable_access decl.init super end def visit_MemberExpression(expr) possible_variable_access expr.object super end def visit_CallExpression(expr) possible_variable_access expr.callee possible_variable_access_array expr.args super end def visit_NewExpression(expr) possible_variable_access expr.callee possible_variable_access_array expr.args if expr.args super end def visit_ArrayExpression(expr) possible_variable_access_array expr.elements super end def visit_AssignmentExpression(expr) if expr.left.type?("Identifier") push_parent_variable expr.left.val end possible_variable_access expr.right super end def visit_Property(prop) possible_variable_access prop.value super end def visit_SequenceExpression(expr) possible_variable_access_array expr.expressions super end def visit_UpdateExpression(expr) possible_variable_access expr.argument super end def visit_UnaryExpression(expr) possible_variable_access expr.argument super end def visit_BinaryExpression(expr) possible_variable_access expr.left possible_variable_access expr.right super end def visit_ConditionalExpression(expr) possible_variable_access expr.alternate possible_variable_access expr.consequent possible_variable_access expr.test super end end end end