Class: Mutineer::Project::SubjectVisitor
- Inherits:
-
Prism::Visitor
- Object
- Prism::Visitor
- Mutineer::Project::SubjectVisitor
- 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
-
#subjects ⇒ Object
readonly
Returns the value of attribute subjects.
Instance Method Summary collapse
-
#initialize(file) ⇒ SubjectVisitor
constructor
Builds a subject visitor.
-
#promote_module_functions! ⇒ void
Promote
module_function :name/module_function def namesubjects 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). -
#visit_call_node(node) ⇒ void
Track
module_functionso its methods are recorded as singletons (#20) — the called form is the singleton method on the module object. -
#visit_class_node(node) ⇒ void
Visits class nodes and tracks namespace nesting.
-
#visit_def_node(node) ⇒ void
Records a discovered method definition.
-
#visit_module_node(node) ⇒ void
Visits module nodes and tracks namespace nesting.
-
#visit_singleton_class_node(node) ⇒ void
Methods inside
class << selfare 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).
Constructor Details
#initialize(file) ⇒ SubjectVisitor
Builds a subject visitor.
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
#subjects ⇒ Object (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.
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.
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.
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.
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).
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 |