Class: Rigor::Effects::Scanner

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/effects/scanner.rb

Overview

Turns one file's AST plus the call decisions the typer recorded for it into a FileCollection.

The scanner owns identity — which effect units the file defines and what each is keyed as — and delegates each unit's body to UnitScan. Keys follow the existing symbol tables (ADR-103 WD14): Class#m for an instance method, Class.m for a singleton one, and <toplevel>#m for a def outside any class body. Reopenings in one file join here; reopenings across files join when the runner merges the collections.

Three kinds of unit exist beyond a plain def:

  • define_method(:literal) { … } — the block is the method's body. The def-node discovery tables skip it, so this is the minimal extension WD14 calls for, made here rather than in ScopeIndexer because nothing outside effects needs it yet.
  • attr_reader / attr_writer / attr_accessor — synthesised: a reader is ∅, a writer is mutate.self. Without them a caller's edge into an accessor would read as unresolved.
  • a nested def — its own unit under the same class, never contained in the enclosing method.

This walk exists only when collection is on. ADR-103 WD13 prefers riding ScopeIndexer's existing def descent; a separate walk is taken here because the scanner must also attribute each recorded call node to its enclosing unit, and doing that inside the indexer would put an effects-shaped concern on the hot path for every run. Off, this file is never loaded past require.

Constant Summary collapse

TOP_LEVEL_KEY =

Mirrors Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY. Spelled again rather than required so the effects namespace does not pull the indexer in; the two are pinned together by spec.

"<toplevel>"
DECLARATION_MACROS =

Receiver-less calls in a class / module body that #record_declaration interprets. Everything else in a class body is out of scope in v1 — its statements run at load time, which is a unit of its own that no slice models yet.

%i[include prepend attr_reader attr_writer attr_accessor define_method].to_set.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:, calls:, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty, plugin_facts: PluginFacts.empty) ⇒ Scanner

Returns a new instance of Scanner.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rigor/effects/scanner.rb', line 64

def initialize(path:, calls:, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
               plugin_facts: PluginFacts.empty)
  @path = path
  @calls = calls
  @attribution = attribution
  @envelopes = envelopes
  @plugin_facts = plugin_facts
  @summaries = {}
  @edges = {}
  @superclasses = {}
  @includes = {}
  # ADR-103 WD10 / #387 — the class-body facts the framework-edge strategies read, harvested only
  # when a loaded plugin declared one. A run with no `effect_edges:` never allocates them.
  @harvest = plugin_facts.edges? ? {} : nil
end

Class Method Details

.scan(root:, path:, calls:, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty, plugin_facts: PluginFacts.empty) ⇒ Object



58
59
60
61
62
# File 'lib/rigor/effects/scanner.rb', line 58

def self.scan(root:, path:, calls:, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
              plugin_facts: PluginFacts.empty)
  new(path: path, calls: calls, attribution: attribution, envelopes: envelopes,
      plugin_facts: plugin_facts).scan(root)
end

Instance Method Details

#scan(root) ⇒ Object



80
81
82
83
84
85
86
87
# File 'lib/rigor/effects/scanner.rb', line 80

def scan(root)
  walk(root, [], false)
  synthesize_framework_units
  FileCollection.new(
    path: @path, summaries: @summaries, edges: @edges,
    superclasses: @superclasses, includes: @includes
  )
end