Class: Rigor::Configuration::Dependencies

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/configuration/dependencies.rb

Overview

Parsed ‘dependencies:` section of `.rigor.yml`. Per [ADR-10](../../../docs/adr/10-dependency-source-inference.md), the only nested key today is `source_inference:`, listing gems whose Ruby implementation Rigor MAY walk during inference instead of degrading to `Dynamic` at the dependency boundary.

Slice 1 lands the parser only — ‘Configuration#dependencies` is read, but no analyzer machinery consumes it yet. Slice 2 wires `Analysis::DependencySourceInference` against this value object.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

VALID_MODES =

Walking modes per [ADR-10 § “Decision”](../../../docs/adr/10-dependency-source-inference.md#decision).

%i[disabled when_missing full].freeze
DEFAULT_ROOTS =

Default ‘roots:` for an entry that does not supply one. The hard-excluded directories (`spec/` / `test/` / `bin/` / C extensions) are enforced by the walker, not the parser — see ADR-10 § “Hard exclusions”.

%w[lib].freeze
DEFAULT_BUDGET_PER_GEM =

Default per-gem catalog cap. ADR-10 slice 4 picks 5000 method definitions: it covers Rack (~1500), Faraday (~500), Sidekiq (~800) and other realistic opt-in targets, while still surfacing a diagnostic for ActiveSupport-class libraries (~10 000+ methods) where the user should ship RBS or de-list the gem instead.

5000
MIN_BUDGET_PER_GEM =

Range bounds per ADR-10 § “Budget interaction” (“range 0.25× – 4×”). Configured against the default, this lands at 1250 – 20 000.

(DEFAULT_BUDGET_PER_GEM * 0.25).to_i
MAX_BUDGET_PER_GEM =
(DEFAULT_BUDGET_PER_GEM * 4).to_i
VALID_BUDGET_OVERRUN_STRATEGIES =

ADR-10 5b — budget-overrun strategy enum.

  • ‘:walker_cap` (default): the (α) semantics. The walker stops harvesting at the cap; methods past the cap fall through to the existing user-class fallback path. Existing v0.1.3 behaviour.

  • ‘:dependency_silence`: the (β) semantics. Same walker behaviour, but the dispatcher additionally consults `Index#class_to_gem` after a catalog miss. When the receiver’s class belongs to a budget- exceeded gem, the call resolves to ‘Dynamic` rather than falling through to user-class fallback. This silences `call.undefined-method` for unrecorded methods at the cost of weaker static checking on that gem’s surface.

%i[walker_cap dependency_silence].freeze
DEFAULT_BUDGET_OVERRUN_STRATEGY =
:walker_cap

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source_inference, budget_per_gem = DEFAULT_BUDGET_PER_GEM, warnings = [], budget_overrun_strategy = DEFAULT_BUDGET_OVERRUN_STRATEGY) ⇒ Dependencies

Returns a new instance of Dependencies.



89
90
91
92
93
94
95
96
# File 'lib/rigor/configuration/dependencies.rb', line 89

def initialize(source_inference, budget_per_gem = DEFAULT_BUDGET_PER_GEM,
               warnings = [], budget_overrun_strategy = DEFAULT_BUDGET_OVERRUN_STRATEGY)
  @source_inference = source_inference.freeze
  @budget_per_gem = budget_per_gem
  @warnings = warnings.freeze
  @budget_overrun_strategy = budget_overrun_strategy
  freeze
end

Instance Attribute Details

#budget_overrun_strategyObject (readonly)

Returns the value of attribute budget_overrun_strategy.



70
71
72
# File 'lib/rigor/configuration/dependencies.rb', line 70

def budget_overrun_strategy
  @budget_overrun_strategy
end

#budget_per_gemObject (readonly)

Returns the value of attribute budget_per_gem.



70
71
72
# File 'lib/rigor/configuration/dependencies.rb', line 70

def budget_per_gem
  @budget_per_gem
end

#source_inferenceObject (readonly)

Returns the value of attribute source_inference.



70
71
72
# File 'lib/rigor/configuration/dependencies.rb', line 70

def source_inference
  @source_inference
end

#warningsObject (readonly)

Returns the value of attribute warnings.



70
71
72
# File 'lib/rigor/configuration/dependencies.rb', line 70

def warnings
  @warnings
end

Class Method Details

.dedupe_entries(entries) ⇒ Object

ADR-10 § “config-conflict diagnostic” — merges a potentially-duplicated entry list (the ‘includes:` chain produces concatenated arrays via `Configuration.deep_merge`’s special-case for ‘dependencies.source_inference`) into a single canonical entry per gem name. The merge rules:

  • Same gem, same all fields → idempotent collapse (no warning).

  • Same gem, different ‘mode:` → keep the LAST entry (matches existing right-wins semantics elsewhere) AND emit a `:warning` so the user knows their `includes:` chain is ambiguous.

  • Same gem, different ‘roots:` → union the roots silently (no warning). The walker is happy to visit the union.

Returns ‘[entries, warnings]` so the caller can plumb the warning list through to the Runner for diagnostic emission.



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/rigor/configuration/dependencies.rb', line 135

def dedupe_entries(entries)
  warnings = []
  by_gem = {}
  entries.each do |entry|
    existing = by_gem[entry.gem]
    by_gem[entry.gem] = if existing.nil?
                          entry
                        else
                          merge_entry_pair(existing, entry, warnings)
                        end
  end
  [by_gem.values, warnings]
end

.from_h(data) ⇒ Object

Parse the YAML-shaped ‘dependencies:` value into a frozen Rigor::Configuration::Dependencies. Accepts `nil` / `{}` / a Hash with `source_inference:` and / or `budget_per_gem:` / `budget_overrun_strategy:` present.

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/rigor/configuration/dependencies.rb', line 76

def self.from_h(data)
  return new([]) if data.nil?
  raise ArgumentError, "dependencies: must be a Hash, got #{data.inspect}" unless data.is_a?(Hash)

  raw_entries = Array(data["source_inference"]).map { |raw| coerce_entry(raw) }
  entries, warnings = dedupe_entries(raw_entries)
  budget = coerce_budget_per_gem(data.fetch("budget_per_gem", DEFAULT_BUDGET_PER_GEM))
  strategy = coerce_budget_overrun_strategy(
    data.fetch("budget_overrun_strategy", DEFAULT_BUDGET_OVERRUN_STRATEGY)
  )
  new(entries, budget, warnings, strategy)
end

.merge_entry_pair(existing, incoming, warnings) ⇒ Object



149
150
151
152
153
154
155
156
157
# File 'lib/rigor/configuration/dependencies.rb', line 149

def merge_entry_pair(existing, incoming, warnings)
  if existing.mode != incoming.mode
    warnings << "dependencies.source_inference[].gem #{incoming.gem.inspect} declared with " \
                "conflicting modes (#{existing.mode.inspect} vs #{incoming.mode.inspect}); " \
                "the later (#{incoming.mode.inspect}) wins."
  end
  merged_roots = (existing.roots + incoming.roots).uniq.freeze
  Entry.new(gem: incoming.gem, mode: incoming.mode, roots: merged_roots)
end

Instance Method Details

#empty?Boolean

Returns:

  • (Boolean)


112
# File 'lib/rigor/configuration/dependencies.rb', line 112

def empty? = @source_inference.empty?

#to_hObject



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rigor/configuration/dependencies.rb', line 98

def to_h
  {
    "source_inference" => @source_inference.map do |entry|
      {
        "gem" => entry.gem,
        "mode" => entry.mode.to_s,
        "roots" => entry.roots
      }
    end,
    "budget_per_gem" => @budget_per_gem,
    "budget_overrun_strategy" => @budget_overrun_strategy.to_s
  }
end