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 ⇒ TreeInterpreter

Returns a new instance of TreeInterpreter.



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/dependencytree/treeinterpreter.rb', line 11

def initialize
  # 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_modules ⇒ Object (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)


139
140
141
142
143
144
145
146
147
# File 'lib/dependencytree/treeinterpreter.rb', line 139

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)


80
81
82
83
84
85
86
87
# File 'lib/dependencytree/treeinterpreter.rb', line 80

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)


54
55
56
57
58
59
60
61
62
63
64
# File 'lib/dependencytree/treeinterpreter.rb', line 54

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)


127
128
129
130
131
132
133
134
135
# File 'lib/dependencytree/treeinterpreter.rb', line 127

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.



93
94
95
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
# File 'lib/dependencytree/treeinterpreter.rb', line 93

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)


68
69
70
71
72
73
74
75
76
# File 'lib/dependencytree/treeinterpreter.rb', line 68

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.



48
49
50
# File 'lib/dependencytree/treeinterpreter.rb', line 48

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)


35
36
37
38
39
40
41
42
43
44
# File 'lib/dependencytree/treeinterpreter.rb', line 35

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 ⇒ Object

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



25
26
27
28
29
30
31
# File 'lib/dependencytree/treeinterpreter.rb', line 25

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.



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/dependencytree/treeinterpreter.rb', line 180

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.



170
171
172
173
174
175
# File 'lib/dependencytree/treeinterpreter.rb', line 170

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.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/dependencytree/treeinterpreter.rb', line 151

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