class Dependencytree::TreeInterpreter

Interprets AST trees from the Ruby parser and maintains a list of seen classes, modules, constants and methods.

Attributes

classes_and_modules[R]

Public Class Methods

new(log) click to toggle source
# File lib/dependencytree/treeinterpreter.rb, line 11
def initialize(log)
  # the logging instance
  @@log = log

  # path will be the file system path of the source file
  @path = nil
  # context_stack is the stack of modules/classes loaded (namespacing)
  @context_stack = []
  # this is a flat list of all classes / modules seen
  @classes_and_modules = []

  # force adding the Kernel module to the list of classes
  _handle_class_module_common(:module, "Kernel", nil)
  @kernel = _resolve("Kernel")
end

Public Instance Methods

_casgn(node) click to toggle source

Handle a def expression. @param node the def node itself to handle.

# File lib/dependencytree/treeinterpreter.rb, line 142
def _casgn(node)
  raise ArgumentError, "Children count for casgn is != 3 (#{node.children.length})" if node.children.length != 3

  @@log.debug("casgn #{node.children[1]}")

  top_of_stack.add_constant(node.children[1])

  visit_children(node.children[1..-1])
end
_class(node) click to toggle source

Handle a class expression. @param node the class node itself to handle.

# File lib/dependencytree/treeinterpreter.rb, line 83
def _class(node)    
  raise ArgumentError, "Children count for class is != 3 (#{node.children.length})" if node.children.length != 3
  raise ArgumentError, "First class child needs to be a const (#{node.children[0].type} #{node.children[0].type})" if node.children[0].type != :const
  @@log.debug("class #{node.children[0].children[1]}")

  current_class_name = node.children[0].children[1]
  _handle_class_module_common(:class, current_class_name, node)
end
_const(node) click to toggle source

Handle a const expression. @param node the const node itself to handle.

# File lib/dependencytree/treeinterpreter.rb, line 57
def _const(node)
  @@log.debug("const")

  raise ArgumentError, "type needs to be const (#{node.type})" if node.type != :const
  raise ArgumentError, "Children count needs to be 2 (#{node.children.length})" if node.children.length != 2

  reference = flatten_const_tree(node)

  @@log.debug("Reference to #{reference.to_s}")
  top_of_stack.add_reference(reference)
end
_def(node) click to toggle source

Handle a def expression. @param node the def node itself to handle.

# File lib/dependencytree/treeinterpreter.rb, line 130
def _def(node)
  raise ArgumentError, "Children count for def is != 3 (#{node.children.length})" if node.children.length != 3

  @@log.debug("def #{node.children[0]}")

  top_of_stack.add_method(node.children[0])

  visit_children(node.children[1..-1])
end
_handle_class_module_common(type, name, node) click to toggle source

Handle the common parts of a module or class definition. Will try to resolve the instance or create it if not found. @param type :module or :class. @param name the local class name. @param node the AST node of the class or module, can be nil if no children traversal required.

# File lib/dependencytree/treeinterpreter.rb, line 96
def _handle_class_module_common(type, name, node)
  full_name = name
  parent = nil
  if ! @context_stack.empty?
    parent = @context_stack[-1]
    full_name = parent.full_name.to_s + "::" + name.to_s
  end
  @@log.debug("Full name is #{full_name}")
  resolved = _resolve(full_name)

  if ! resolved.nil?
    # found an existing module/class with the full_name
    model = resolved
  else
    # found no existing module/class with the full_name
    model = ClassModel.new(type, @path, name)
    if parent
      model.set_parent(parent)
    end
    @classes_and_modules << model 
  end

  if resolved.nil?
    @@log.debug("Created new ClassModel for #{model.full_name}")
  end

  @context_stack << model
  # recurse over the contents of the module
  visit_children(node.children[1..-1]) if node
  @context_stack.pop
end
_module(node) click to toggle source

Handle a module expression. @param node the module node itself to handle.

# File lib/dependencytree/treeinterpreter.rb, line 71
def _module(node)
  raise ArgumentError, "Children count for module is != 2 (#{node.children.length})" if node.children.length != 2
  raise ArgumentError, "First module child needs to be a const (#{node.children[0].type} #{node.children[0].type})" if node.children[0].type != :const

  @@log.debug("module #{node.children[0].children[1]}")

  current_module_name = node.children[0].children[1]
  _handle_class_module_common(:module, current_module_name, node)
end
_resolve(full_name) click to toggle source

Finds a class or module by its full name. @param full_name the full name.

# File lib/dependencytree/treeinterpreter.rb, line 51
def _resolve(full_name)
  @classes_and_modules.find  { |clazz| clazz.full_name.to_s == full_name.to_s }
end
flatten_const_tree(node) click to toggle source

Make an array of strings out of a encapsulated tree of :const expressions. @param node the top const node to start flattening at.

# File lib/dependencytree/treeinterpreter.rb, line 38
def flatten_const_tree(node)
  raise ArgumentError, "type needs to be const (#{node.type})" if node.type != :const
  raise ArgumentError, "Children count needs to be 2 (#{node.children.length})" if node.children.length != 2

  result = node.children[1..1]
  if node.children[0] && node.children[0].type == :const
    result = flatten_const_tree(node.children[0]) + result
  end
  result
end
top_of_stack() click to toggle source

Gets the top of stack class/module or Kernel if nothing set.

# File lib/dependencytree/treeinterpreter.rb, line 28
def top_of_stack
  if @context_stack.empty?
    @kernel
  else
    @context_stack[-1]
  end
end
visit(path, tree) click to toggle source

Visits all children of the AST tree. @param path the filesystem path of the parsed entity (ruby file). @param tree the AST tree node.

# File lib/dependencytree/treeinterpreter.rb, line 183
def visit(path, tree)
  begin
    @@log.debug("Visiting path #{path}")
    @path = path
    visit_node(tree)
    @path = nil
  rescue Exception => e
    @@log.error("Error in path #{path}")
    puts "Error in path #{path}"
    raise e
  end
end
visit_children(children) click to toggle source

Visit all children of a node. Will call visit_node on each node child. @param children the array of children to visit.

# File lib/dependencytree/treeinterpreter.rb, line 173
def visit_children(children)
  return if ! children
  children.each do |child|
    visit_node(child) if child.respond_to?(:children)
  end
end
visit_node(node) click to toggle source

Visit a AST node and do the appropriate actions. @param node the node to visit.

# File lib/dependencytree/treeinterpreter.rb, line 154
def visit_node(node)
  case node.type
    when :const
      _const(node)
    when :class
      _class(node)
    when :module
      _module(node)
    when :def
      _def(node)
    when :casgn
      _casgn(node)
    else
      visit_children(node.children)
  end
end