Class: CallMap::DefinitionCollector

Inherits:
Prism::Visitor
  • Object
show all
Defined in:
lib/call_map/definition_collector.rb

Overview

The single boundary that touches Prism.

Prism node types (ClassNode / ModuleNode / DefNode ...) are referenced only here. Everything else in the codebase works with Definition, so the parser can be swapped out later by replacing just this class.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ DefinitionCollector

Returns a new instance of DefinitionCollector.



22
23
24
25
26
27
28
29
30
31
# File 'lib/call_map/definition_collector.rb', line 22

def initialize(path)
  super()                # Prism::Visitor#initialize takes no args
  @path = path
  @namespace = []        # stack of enclosing class/module names (for qualified naming)
  @singletons = []       # per-scope singleton owner (nil / String / :unresolved)
  @lexical_scopes = []   # syntactic scope stack of FULL names (mirrors Module.nesting)
  @visibilities = [:public] # per-scope visibility state toggled by private/protected/public
  @inline_visibility = nil  # for the `private def foo` inline form
  @definitions = []
end

Class Method Details

.collect(source, path:) ⇒ Array<Definition>

Parse source and return the list of definitions found in it.

Parameters:

  • source (String)

    Ruby source code

  • path (String)

    file path the source came from (kept on each Definition)

Returns:



18
19
20
# File 'lib/call_map/definition_collector.rb', line 18

def self.collect(source, path:)
  new(path).collect(source)
end

Instance Method Details

#collect(source) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/call_map/definition_collector.rb', line 33

def collect(source)
  # Prism.parse returns a ParseResult; .value is the root (ProgramNode).
  # accept(self) starts the visitor traversal from the root.
  result = Prism.parse(source)
  @comment_lines = pure_comment_lines(source, result.comments)
  result.value.accept(self)
  @definitions
end

#visit_call_node(node) ⇒ Object

Track private / protected / public visibility directives at the class-body level (bare toggle, private def foo, and private :foo).



82
83
84
85
86
87
88
89
90
91
# File 'lib/call_map/definition_collector.rb', line 82

def visit_call_node(node)
  directive = visibility_directive(node)
  return super unless directive

  args = node.arguments&.arguments
  return with_inline_visibility(directive) { super } if args&.any? && args.all?(Prism::DefNode)

  apply_visibility(directive, args)
  super
end

#visit_class_node(node) ⇒ Object

Called when the traversal reaches a class definition.



43
44
45
46
47
48
# File 'lib/call_map/definition_collector.rb', line 43

def visit_class_node(node)
  enter_namespace(node.constant_path) do
    @definitions << build_definition(:class, current_namespace, node, superclass: superclass_name(node))
    super # descend into the class body (its methods etc.)
  end
end

#visit_def_node(node) ⇒ Object

Called for every defdef foo, def self.foo, and def Foo.bar.



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/call_map/definition_collector.rb', line 66

def visit_def_node(node)
  info = method_kind_and_owner(node.receiver)
  # info is nil when the method belongs to an unresolvable receiver
  # (e.g. `class << obj`); such defs are skipped rather than mis-registered.
  # No super in either case — do not recurse into method bodies. Nested
  # defs inside a method are runtime-only and should not appear in the index.
  return unless info

  definition = build_definition(info[:kind], node.name.to_s, node, owner: info[:owner])
  comments = leading_comments(node.location.start_line)
  definition.[:comments] = comments if comments.any?
  @definitions << definition
end

#visit_module_node(node) ⇒ Object

Called when the traversal reaches a module definition.



51
52
53
54
55
56
# File 'lib/call_map/definition_collector.rb', line 51

def visit_module_node(node)
  enter_namespace(node.constant_path) do
    @definitions << build_definition(:module, current_namespace, node)
    super
  end
end

#visit_singleton_class_node(node) ⇒ Object

Called for class << self, class << SomeConstant, and class << obj.



59
60
61
62
63
# File 'lib/call_map/definition_collector.rb', line 59

def visit_singleton_class_node(node)
  within_singleton(singleton_owner(node.expression)) do
    super
  end
end