Class: Mutineer::Project::SubjectVisitor

Inherits:
Prism::Visitor
  • Object
show all
Defined in:
lib/mutineer/project.rb

Overview

Walks an AST, maintaining a namespace stack, emitting Subjects. Nested inside Project to signal its private role.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file) ⇒ SubjectVisitor

Builds a subject visitor.

Parameters:

  • file (String)

    source file path being visited.



36
37
38
39
40
41
42
43
44
# File 'lib/mutineer/project.rb', line 36

def initialize(file)
  @file = file
  @namespace_stack = []
  @subjects = []
  @singleton_depth = 0
  @module_function_active = false # bareword `module_function` seen in this module body
  @module_function_names = []     # names from `module_function :a, :b` / `module_function def`
  super()
end

Instance Attribute Details

#subjectsObject (readonly)

Returns the value of attribute subjects.



31
32
33
# File 'lib/mutineer/project.rb', line 31

def subjects
  @subjects
end

Instance Method Details

#promote_module_functions!void

This method returns an undefined value.

Promote module_function :name / module_function def name subjects to singleton after the full walk — the naming call may appear before or after the def, so it can't be decided at visit_def_node time (#20).



51
52
53
54
55
56
# File 'lib/mutineer/project.rb', line 51

def promote_module_functions!
  return if @module_function_names.empty?

  names = @module_function_names.to_set
  @subjects.each { |s| s.singleton = true if names.include?(s.name) }
end

#visit_call_node(node) ⇒ void

This method returns an undefined value.

Track module_function so its methods are recorded as singletons (#20) — the called form is the singleton method on the module object. Bareword module_function flips all SUBSEQUENT defs in this body; the argument forms (:sym, def) name methods promoted after the walk.

Parameters:

  • node (Prism::CallNode)

    call node.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/mutineer/project.rb', line 91

def visit_call_node(node)
  if node.name == :module_function && node.receiver.nil?
    args = node.arguments&.arguments || []
    if args.empty?
      @module_function_active = true
    else
      args.each do |arg|
        @module_function_names << arg.value.to_sym if arg.is_a?(Prism::SymbolNode)
        @module_function_names << arg.name if arg.is_a?(Prism::DefNode)
      end
    end
  end
  super
end

#visit_class_node(node) ⇒ void

This method returns an undefined value.

Visits class nodes and tracks namespace nesting.

Parameters:

  • node (Prism::ClassNode)

    class node.



62
63
64
65
66
67
68
69
# File 'lib/mutineer/project.rb', line 62

def visit_class_node(node)
  @namespace_stack.push(extract_constant_name(node.constant_path))
  saved = @module_function_active
  @module_function_active = false # module_function state does not cross a class boundary
  super
  @module_function_active = saved
  @namespace_stack.pop
end

#visit_def_node(node) ⇒ void

This method returns an undefined value.

Records a discovered method definition.

Parameters:

  • node (Prism::DefNode)

    method definition node.



126
127
128
129
130
131
132
133
134
135
# File 'lib/mutineer/project.rb', line 126

def visit_def_node(node)
  @subjects << Subject.new(
    file: @file,
    namespace: @namespace_stack.dup,
    name: node.name,
    singleton: !node.receiver.nil? || @singleton_depth.positive? || @module_function_active,
    def_node: node
  )
  super
end

#visit_module_node(node) ⇒ void

This method returns an undefined value.

Visits module nodes and tracks namespace nesting.

Parameters:

  • node (Prism::ModuleNode)

    module node.



75
76
77
78
79
80
81
82
# File 'lib/mutineer/project.rb', line 75

def visit_module_node(node)
  @namespace_stack.push(extract_constant_name(node.constant_path))
  saved = @module_function_active
  @module_function_active = false # each module body starts without module_function active
  super
  @module_function_active = saved
  @namespace_stack.pop
end

#visit_singleton_class_node(node) ⇒ void

This method returns an undefined value.

Methods inside class << self are class methods of the enclosing namespace, but their def nodes have no receiver — track the singleton context so they're recorded as singleton (so redefine targets the singleton_class, not instances). class << some_other_obj can't be represented against the namespace, so its defs are skipped (not recursed).

Parameters:

  • node (Prism::SingletonClassNode)

    singleton-class node.



114
115
116
117
118
119
120
# File 'lib/mutineer/project.rb', line 114

def visit_singleton_class_node(node)
  return unless node.expression.is_a?(Prism::SelfNode)

  @singleton_depth += 1
  super
  @singleton_depth -= 1
end