Class: Taski::StaticAnalysis::Visitor

Inherits:
Prism::Visitor
  • Object
show all
Defined in:
lib/taski/static_analysis/visitor.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target_task_class, target_method = :run, methods_to_analyze = nil) ⇒ Visitor

Returns a new instance of Visitor.

Parameters:

  • target_task_class (Class)

    The task class to analyze

  • target_method (Symbol) (defaults to: :run)

    The method name to analyze (:run or :impl)

  • methods_to_analyze (Set<Symbol>) (defaults to: nil)

    Set of method names to analyze (for following calls)



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/taski/static_analysis/visitor.rb', line 13

def initialize(target_task_class, target_method = :run, methods_to_analyze = nil)
  super()
  @target_task_class = target_task_class
  @target_method = target_method
  @dependencies = Set.new
  @in_target_method = false
  @current_namespace_path = []
  # Methods to analyze: starts with just the target method, grows as we find calls
  @methods_to_analyze = methods_to_analyze || Set.new([@target_method])
  # Track which methods we've already analyzed to prevent infinite loops
  @analyzed_methods = Set.new
  # Collect method calls made within analyzed methods (for following)
  @method_calls_to_follow = Set.new
  # Store method definitions found in the class for later analysis
  @class_method_defs = {}
end

Instance Attribute Details

#dependenciesObject (readonly)

Returns the value of attribute dependencies.



8
9
10
# File 'lib/taski/static_analysis/visitor.rb', line 8

def dependencies
  @dependencies
end

Instance Method Details

#follow_method_callsObject

After visiting, follow any method calls that need analysis



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/taski/static_analysis/visitor.rb', line 66

def follow_method_calls
  new_methods = @method_calls_to_follow - @analyzed_methods
  return if new_methods.empty?

  # Add new methods to analyze
  @methods_to_analyze.merge(new_methods)
  @method_calls_to_follow.clear

  # Re-analyze the class methods
  # Set namespace path from target class name for constant resolution
  @current_namespace_path = @target_task_class.name.split("::")

  @class_method_defs.each do |method_name, method_node|
    next unless new_methods.include?(method_name)

    @analyzed_methods.add(method_name)
    @in_target_method = true
    @current_analyzing_method = method_name
    visit(method_node)
    @in_target_method = false
    @current_analyzing_method = nil
  end

  # Recursively follow any new calls discovered
  follow_method_calls
end

#visit_call_node(node) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/taski/static_analysis/visitor.rb', line 57

def visit_call_node(node)
  if @in_target_method
    detect_task_dependency(node)
    detect_method_call_to_follow(node)
  end
  super
end

#visit_class_node(node) ⇒ Object



30
31
32
33
34
35
36
37
38
# File 'lib/taski/static_analysis/visitor.rb', line 30

def visit_class_node(node)
  within_namespace(extract_constant_name(node.constant_path)) do
    if in_target_class?
      # First pass: collect all method definitions in the target class
      collect_method_definitions(node)
    end
    super
  end
end

#visit_def_node(node) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/taski/static_analysis/visitor.rb', line 44

def visit_def_node(node)
  if in_target_class? && should_analyze_method?(node.name)
    @analyzed_methods.add(node.name)
    @in_target_method = true
    @current_analyzing_method = node.name
    super
    @in_target_method = false
    @current_analyzing_method = nil
  else
    super
  end
end

#visit_module_node(node) ⇒ Object



40
41
42
# File 'lib/taski/static_analysis/visitor.rb', line 40

def visit_module_node(node)
  within_namespace(extract_constant_name(node.constant_path)) { super }
end