Class: Dependencytree::TreeInterpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/dependencytree/treeinterpreter.rb

Overview

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(log) ⇒ TreeInterpreter

Returns a new instance of TreeInterpreter.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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

Instance Attribute Details

#classes_and_modulesObject (readonly)

Returns the value of attribute classes_and_modules.



9
10
11
# File 'lib/dependencytree/treeinterpreter.rb', line 9

def classes_and_modules
  @classes_and_modules
end

Instance Method Details

#_casgn(node) ⇒ Object

Handle a def expression.

Parameters:

  • node

    the def node itself to handle.

Raises:

  • (ArgumentError)


142
143
144
145
146
147
148
149
150
# 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) ⇒ Object

Handle a class expression.

Parameters:

  • node

    the class node itself to handle.

Raises:

  • (ArgumentError)


83
84
85
86
87
88
89
90
# 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) ⇒ Object

Handle a const expression.

Parameters:

  • node

    the const node itself to handle.

Raises:

  • (ArgumentError)


57
58
59
60
61
62
63
64
65
66
67
# 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) ⇒ Object

Handle a def expression.

Parameters:

  • node

    the def node itself to handle.

Raises:

  • (ArgumentError)


130
131
132
133
134
135
136
137
138
# 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) ⇒ Object

Handle the common parts of a module or class definition. Will try to resolve the instance or create it if not found.

Parameters:

  • type

    :module or :class.

  • name

    the local class name.

  • node

    the AST node of the class or module, can be nil if no children traversal required.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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) ⇒ Object

Handle a module expression.

Parameters:

  • node

    the module node itself to handle.

Raises:

  • (ArgumentError)


71
72
73
74
75
76
77
78
79
# 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) ⇒ Object

Finds a class or module by its full name.

Parameters:

  • full_name

    the full name.



51
52
53
# 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) ⇒ Object

Make an array of strings out of a encapsulated tree of :const expressions.

Parameters:

  • node

    the top const node to start flattening at.

Raises:

  • (ArgumentError)


38
39
40
41
42
43
44
45
46
47
# 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_stackObject

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



28
29
30
31
32
33
34
# 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) ⇒ Object

Visits all children of the AST tree.

Parameters:

  • path

    the filesystem path of the parsed entity (ruby file).

  • tree

    the AST tree node.



183
184
185
186
187
188
189
190
191
192
193
194
# 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) ⇒ Object

Visit all children of a node. Will call #visit_node on each node child.

Parameters:

  • children

    the array of children to visit.



173
174
175
176
177
178
# 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) ⇒ Object

Visit a AST node and do the appropriate actions.

Parameters:

  • node

    the node to visit.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# 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