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, the only nested key today is source_inference:, listing gems whose Ruby implementation Rigor MAY walk during inference instead of degrading to Dynamic[top] at the dependency boundary.

Parser for the dependencies: YAML section; consumed by Analysis::DependencySourceInference (ADR-10).

Defined Under Namespace

Classes: Entry

Constant Summary collapse

VALID_MODES =

Walking modes per ADR-10 § "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[top] 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.



70
71
72
73
74
75
76
77
# File 'lib/rigor/configuration/dependencies.rb', line 70

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.



53
54
55
# File 'lib/rigor/configuration/dependencies.rb', line 53

def budget_overrun_strategy
  @budget_overrun_strategy
end

#budget_per_gemObject (readonly)

Returns the value of attribute budget_per_gem.



53
54
55
# File 'lib/rigor/configuration/dependencies.rb', line 53

def budget_per_gem
  @budget_per_gem
end

#source_inferenceObject (readonly)

Returns the value of attribute source_inference.



53
54
55
# File 'lib/rigor/configuration/dependencies.rb', line 53

def source_inference
  @source_inference
end

#warningsObject (readonly)

Returns the value of attribute warnings.



53
54
55
# File 'lib/rigor/configuration/dependencies.rb', line 53

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.



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/rigor/configuration/dependencies.rb', line 108

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)


57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/rigor/configuration/dependencies.rb', line 57

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 = coerce_source_inference(data["source_inference"])
  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



122
123
124
125
126
127
128
129
130
# File 'lib/rigor/configuration/dependencies.rb', line 122

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)


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

def empty? = @source_inference.empty?

#to_hObject



79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/rigor/configuration/dependencies.rb', line 79

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