Class: Rigor::Configuration
- Inherits:
-
Object
- Object
- Rigor::Configuration
- Defined in:
- lib/rigor/configuration.rb,
lib/rigor/configuration/dependencies.rb,
lib/rigor/configuration/severity_profile.rb,
sig/rigor.rbs
Overview
rubocop:disable Metrics/ClassLength
Defined Under Namespace
Modules: SeverityProfile Classes: Dependencies
Constant Summary collapse
- DISCOVERY_ORDER =
File-discovery order for
Configuration.load(nil).The first file present is loaded; the others are NOT implicitly merged. To extend a base config explicitly the winning file MUST list the base via
includes:..rigor.ymlis a developer-local override (typically gitignored);.rigor.dist.ymlis the project default (committed to the repo). When both are present the developer's local override wins outright — there is no implicit auto-merge. %w[.rigor.yml .rigor.dist.yml].freeze
- DEFAULT_PATH =
Back-compat alias. Keep here so external callers that read
Configuration::DEFAULT_PATHfor help text / fixture paths still work; the discovery list is the canonical source. DISCOVERY_ORDER.first
- DEFAULT_EFFECTS_SNAPSHOT_PATH =
ADR-103 WD7 — where
rigor effects updatewrites wheneffects.snapshot.path:is unset. Public because the schema pins its owndefaultagainst it. ".rigor-effects.yml"- BUILTIN_EXCLUDES =
Built-in exclusion patterns appended to
exclude:so vendored dependencies, Bundler artefacts, and JavaScript node_modules are never analysed by accident when a directory glob expands. Users cannot disable these defaults; the trade-off is that analysing any of these paths is essentially never what the user wants (they're build outputs / external dependencies, not source).We deliberately keep this list narrow.
tmp/and similar directories vary across project layouts (Rails hastmp/, libraries usually don't); user-suppliedexclude:entries in.rigor.ymlcover the project-specific cases. %w[ **/vendor/bundle/** **/.bundle/** **/node_modules/** ].freeze
- DEFAULTS =
{ "target_ruby" => "4.0", "paths" => ["lib"], "exclude" => [], "plugins" => [], "disable" => [], "libraries" => [], "signature_paths" => nil, # ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit # files that the analyzer walks before per-file inference so patched-method declarations are visible # across the project (e.g. `lib/core_ext/string_extensions.rb`). Slice 1 plumbing only — listed files # are validated at config-load time (`pre-eval.file-not-found` on a missing path), but the dispatcher # tier consuming the registry lands in slice 2. "pre_eval" => [], # ADR-22 — baseline file path. nil (default) means no baseline is loaded; the `false` literal is # treated as the explicit-disable form for `.rigor.yml`-side override of an upstream `.rigor.dist.yml` # `baseline:` declaration. The presence of `.rigor-baseline.yml` on disk alone does NOT activate # filtering — the path must be named here (WD2 (b) of ADR-22). "baseline" => nil, "fold_platform_specific_paths" => false, # ADR-67 WD6a — opt-in call-site parameter type inference on the `check` walk. When `true`, `rigor # check` runs the {Inference::ParameterInferenceCollector} as a one-round pre-pass into the discovery # seed, so an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved # call-site argument types (precision-additive only — the in-body negative rules decline on the inferred # lower bound, WD6b). `false` (the default, resolved off by every severity profile) keeps the table empty # and the run byte-identical to today. Composes with `--incremental` (the WD6c mutual exclusion is # lifted): the incremental session recomputes the table each run and diffs it against its snapshot, # re-checking any callee whose seeds moved. "parameter_inference" => false, # ADR-103 WD13 — the effect-labels opt-in, and the ONLY thing that turns effect collection on # besides running `rigor effects`. **Presence is the switch**: `effects: {}` — or the bare key, which # YAML parses as nil — enables collection with every sub-key at its default, because an annotation in # a project's RBS must never create a project-wide cost cliff (that case earns a `:info` residual # instead, #384). # # The default is `false` rather than `nil` precisely because presence is the switch and # `Configuration.load` merges these DEFAULTS UNDER the loaded file: a `nil` default would be # indistinguishable from a user's bare `effects:` key and would turn collection on for every project # in existence. `false` is also the explicit-disable form a `.rigor.yml` uses to override an upstream # `.rigor.dist.yml`'s `effects:` block — the same shape `baseline:` uses. # # The sub-keys are declared in `schemas/rigor-config.schema.json`. `check` (#383, # {#effects_check?}), `snapshot.{path,reach,gate}`, `tolerated` (#381) and the policy trio # `labels` / `attribution` / `envelopes` (#385, {#coerce_effects_policy}) are read; `views` lands # with the slice that implements it (#390). Reserving a sub-key's shape before a reader exists is # the same discipline the schema applies to a sibling implementation's namespace (ADR-99). "effects" => false, "cache" => { "path" => ".rigor/cache", # LRU eviction cap in bytes (ADR-54 WD3). The least-recently-used entries are removed at the end of a # run when the total exceeds this limit. The 256 MB default exists to reap orphans — entries whose # content key nothing references any more (an rbs gem bump, a signature change) and that no run # would otherwise ever delete; a full active per-project set is ~2 MB, so the cap never touches live # entries. Set explicitly to `null` to disable eviction (pre-WD3 behaviour: the cache grows until # `--clear-cache`). "max_bytes" => 268_435_456, # ADR-87 WD1 — file-freshness validation strategy. `"stat"` validates a recorded file # dependency by stat-ing it first and re-hashing only when the `(size, mtime_ns, ctime_ns, inode)` # tuple moved (or the racy window fires), so an unchanged monorepo hashes ~0 bytes on a warm run. # `"digest"` restores the pre-ADR-87 behaviour of SHA-256'ing every recorded file on every run — the # per-run escape hatch is the `RIGOR_STRICT_VALIDATION=1` env var, which wins over this setting. # `"auto"` (default, #190) resolves per environment: `"digest"` when {CiDetector} recognises a CI # provider — a fresh checkout regenerates every stat tuple, so the stat tier can never short-circuit # there and stat-signature glob slots would recompute on every run — and `"stat"` everywhere else. # A self-hosted runner with a persistent workspace opts back into the stat floor with an explicit # `"stat"` (or `RIGOR_CI_DETECT=0`). "validation" => "auto" }, "plugins_io" => { "network" => "disabled", "allowed_paths" => [], "allowed_url_hosts" => [] }, "severity_profile" => "balanced", "severity_overrides" => {}, # ADR-50 § WD2 — bleeding-edge overlay opt-in. Selects which of the *next major's* queued changes # ({Rigor::BleedingEdge}) this project adopts early. Orthogonal to `severity_profile:`. Accepts `false` # (default — adopt none), `true` (adopt the whole overlay), a list of feature ids (adopt only those), # or `{ all: true, except: [ids] }` (adopt all but the named). A queued change is either a severity # promotion (composed by {SeverityProfile.resolve}) or a behaviour switch (read at its call site # through {#bleeding_edge_active?}); `bleeding_edge:` selects both kinds the same way. "bleeding_edge" => false, "dependencies" => { "source_inference" => [], "budget_per_gem" => Configuration::Dependencies::DEFAULT_BUDGET_PER_GEM }, "parallel" => { # ADR-15 Phase 4c — when greater than zero, `rigor check` dispatches per-file analysis across N # Ractor workers built around {Rigor::Analysis::WorkerSession}. `0` (default) keeps the sequential # coordinator path bit-for-bit unchanged. The CLI's `--workers=N` flag and the `RIGOR_RACTOR_WORKERS` # env var both override this setting; precedence is CLI > env > config > 0. "workers" => 0 }, "bundler" => { # Open item O4 — target-project Bundler awareness. When `bundle_path:` is set (or auto-detected), # Rigor walks `<bundle_path>/ruby/*/gems/*/sig/` and adds each gem-shipped sig directory to # `signature_paths:`. With O7's failure-memo in place, conflicts (a vendored sig already declares the # same constant) degrade gracefully to "no RBS env" with a single-line warning naming the offending # file, rather than hanging. # # `bundle_path:` (String, optional): explicit path to the bundler install root (e.g., # "vendor/bundle" or an absolute path). Resolved relative to the project root (`paths:`'s base) when # relative. # # `auto_detect:` (Boolean, default true): when no explicit `bundle_path:` is set, try # `.bundle/config`'s `BUNDLE_PATH:` first; fall back to `vendor/bundle/` under the project root if it # exists. When neither is found, no extra sigs are added — the analyzer sees only rigor's vendored # RBS and the user's `signature_paths:`. # # O4 Layer 3 keys: # # `lockfile:` (String, optional): explicit path to a `Gemfile.lock`. Resolved relative to the # project root when relative. When set (or auto-detected via the `auto_detect:` flag below) Rigor # parses the lockfile and uses it to FILTER the bundle-discovered `sig/` directories: only gems whose # `(name, version, platform)` matches a lockfile entry are admitted to `signature_paths:`. Stale or # out-of-band gems sitting in the bundle install tree are silently dropped. # # `auto_detect:` (Boolean, also gates the lockfile search): when true and `lockfile:` is nil, look # for `<project_root>/Gemfile.lock`. "bundle_path" => nil, "auto_detect" => true, "lockfile" => nil }, "rbs_collection" => { # Open item O4 Layer 3 slice 2 — `rbs collection install` awareness. When the target project has been # set up with `rbs collection install`, the resulting `rbs_collection.lock.yaml` carries the resolved # (gem, version, source) triples and `.gem_rbs_collection/` holds the downloaded `.rbs` files. Rigor # parses the lockfile and auto-feeds each gem's `<collection_root>/<name>/<version>/` directory into # `RbsLoader`'s `signature_paths:`. Sources of type `stdlib` are skipped because rigor's bundled # `DEFAULT_LIBRARIES` already covers that surface. # # `lockfile:` (String, optional): explicit path to `rbs_collection.lock.yaml`. Resolved relative to # the project root when relative. # # `auto_detect:` (Boolean, default true): when no explicit `lockfile:` is set, look for # `<project_root>/rbs_collection.lock.yaml`. "lockfile" => nil, "auto_detect" => true } }.freeze
- RESERVED_NAMESPACES =
Top-level keys this implementation DECLARES but never reads — see
docs/internal-spec/config.md§ "Reserved namespaces" and ADR-99.A sibling implementation (
rigor-rs, which vendors our schema rather than keeping its own) groups keys for concepts we do not have under its own namespace, so one.rigor.ymlfeeds both. Such a key answers to the SCHEMA TIER ONLY: it is type-checked where it is written, and here it is never read, never validated, never coerced, and never an error however invalid its value.That is already the emergent behaviour —
#initializefetches each key it owns and never enumerates the rest — so this constant does not gate the runtime. It exists to make the reservation FINDABLE by the next person adding config validation (#166 is exactly that), and to give the schema-parity spec something to key on: a reserved namespace is by definition absent from DEFAULTS, so the DEFAULTS-driven schema gate can never see it. %w[rigor_rs].freeze
- KNOWN_KEYS =
Every top-level key a conforming
.rigor.ymlmay carry: the keys this implementation owns, plusincludes:, plus the namespaces reserved for another implementation. Anything else is recorded in #unknown_keys and warned about by Rigor::ConfigAudit, and is also the did-you-mean dictionary for a near-miss.includes:is a load-time directive — load_with_includesdeletes it while merging, so it never reaches#initializeand could not be seen as unknown regardless. It is listed because a user legitimately writes it, so a typo likeinclude:must be able to suggest it. (DEFAULTS.keys + %w[includes] + RESERVED_NAMESPACES).freeze
- AUTOWIRED_RBS_INLINE_GEM =
ADR-93 WD2 — the one bundled plugin default-wired without a
plugins:entry.rigor-rbs-inlineis the gem;rbs-inlineis its manifest id (the loader raises on a duplicate id, so both forms count as "already listed"). "rigor-rbs-inline"- AUTOWIRED_RBS_INLINE_ID =
"rbs-inline"
Instance Attribute Summary collapse
-
#baseline_path ⇒ String?
readonly
Returns the value of attribute baseline_path.
-
#bleeding_edge ⇒ Object
readonly
Returns the value of attribute bleeding_edge.
-
#bleeding_edge_severity_overrides ⇒ Object
readonly
Returns the value of attribute bleeding_edge_severity_overrides.
-
#bundler_auto_detect ⇒ Object
readonly
Returns the value of attribute bundler_auto_detect.
-
#bundler_bundle_path ⇒ Object
readonly
Returns the value of attribute bundler_bundle_path.
-
#bundler_lockfile ⇒ Object
readonly
Returns the value of attribute bundler_lockfile.
-
#cache_max_bytes ⇒ Object
readonly
Returns the value of attribute cache_max_bytes.
-
#cache_path ⇒ String
readonly
Returns the value of attribute cache_path.
-
#cache_validation ⇒ Object
readonly
Returns the value of attribute cache_validation.
-
#dependencies ⇒ Object
readonly
Returns the value of attribute dependencies.
-
#disabled_rules ⇒ Object
readonly
Returns the value of attribute disabled_rules.
-
#effects ⇒ Hash[String, untyped]?
readonly
ADR-103 WD13 — the
effects:block, nil when the key was absent. -
#effects_attribution ⇒ Object
readonly
Returns the value of attribute effects_attribution.
-
#effects_envelopes ⇒ Object
readonly
Returns the value of attribute effects_envelopes.
-
#effects_labels ⇒ Object
readonly
Returns the value of attribute effects_labels.
-
#effects_snapshot_gate ⇒ Object
readonly
Returns the value of attribute effects_snapshot_gate.
-
#effects_snapshot_path ⇒ Object
readonly
Returns the value of attribute effects_snapshot_path.
-
#effects_snapshot_reach ⇒ Object
readonly
Returns the value of attribute effects_snapshot_reach.
-
#effects_tolerated ⇒ Object
readonly
Returns the value of attribute effects_tolerated.
-
#exclude_patterns ⇒ Object
readonly
Returns the value of attribute exclude_patterns.
-
#fold_platform_specific_paths ⇒ Object
readonly
Returns the value of attribute fold_platform_specific_paths.
-
#libraries ⇒ Object
readonly
Returns the value of attribute libraries.
-
#parallel_workers ⇒ Object
readonly
Returns the value of attribute parallel_workers.
-
#parameter_inference ⇒ Object
readonly
Returns the value of attribute parameter_inference.
-
#paths ⇒ Array[String]
readonly
Returns the value of attribute paths.
-
#plugins ⇒ Array[String]
readonly
Returns the value of attribute plugins.
-
#plugins_io_allowed_paths ⇒ Object
readonly
Returns the value of attribute plugins_io_allowed_paths.
-
#plugins_io_allowed_url_hosts ⇒ Object
readonly
Returns the value of attribute plugins_io_allowed_url_hosts.
-
#plugins_io_network ⇒ Object
readonly
Returns the value of attribute plugins_io_network.
-
#pre_eval ⇒ Object
readonly
Returns the value of attribute pre_eval.
-
#rbs_collection_auto_detect ⇒ Object
readonly
Returns the value of attribute rbs_collection_auto_detect.
-
#rbs_collection_lockfile ⇒ Object
readonly
Returns the value of attribute rbs_collection_lockfile.
-
#severity_overrides ⇒ Object
readonly
Returns the value of attribute severity_overrides.
-
#severity_profile ⇒ Object
readonly
Returns the value of attribute severity_profile.
-
#signature_paths ⇒ Object
readonly
Returns the value of attribute signature_paths.
-
#target_ruby ⇒ String
readonly
Returns the value of attribute target_ruby.
-
#unknown_keys ⇒ Object
readonly
Top-level keys the loaded config carried that this implementation does not own — neither a DEFAULTS key, nor
includes:, nor a reserved namespace.
Class Method Summary collapse
-
.autowire_default_plugins(data) ⇒ Object
ADR-93 WD2 — default-wire the bundled
rigor-rbs-inlineplugin, in WD1's annotation-gated, magic-comment-free mode, when the upstreamrbs-inlinelibrary is resolvable and the user has not already listed the plugin. -
.discover ⇒ String?
Returns the path to the config file Rigor would load under auto-discovery, or
nilwhen neither candidate exists. -
.load(path = nil) ⇒ Configuration
Loads a configuration file.
-
.load_with_includes(path, visited: Set.new) ⇒ Hash[String, untyped]
Reads
path(which MUST exist) plus every file listed in itsincludes:chain, merging them under the order: included files first (in declaration order), then the current file's own keys override. -
.rbs_inline_library_resolvable? ⇒ Boolean
A probe with no load side effect: it does not
requirethe library, only asks whether RubyGems can resolve the name. -
.rbs_inline_plugin_entry?(entry) ⇒ Boolean
True when a
plugins:entry (String or Hash, string- or symbol-keyed) already references therigor-rbs-inlineplugin by gem name or by manifest id. -
.read_yaml(absolute) ⇒ Object
#433's sibling: a typo in the file the user is about to be told to fix is a configuration mistake like any other, and escaped as a
Psych::SyntaxErrorbacktrace naming a file inside Ruby's stdlib. - .resolve_path_key!(out, key, base_dir) ⇒ Object
- .resolve_plugins_io_paths!(out, base_dir) ⇒ Object
Instance Method Summary collapse
-
#bleeding_edge_active?(id) ⇒ Boolean
ADR-50 § WD2 — whether the bleeding-edge feature
idis in force for this run. -
#cache_validation_strict?(env = ENV) ⇒ Boolean
Resolves the
cache.validationtri-state to the boolean the run installs via Rigor::Cache::FileDigest.with_run'sstrict:. -
#effects_check? ⇒ Boolean
ADR-103 WD14 / #383 — whether author-declared effect envelopes (
%a{pure},%a{rigor:v1:effect …}) are checked against the collected summaries, surfacingeffect.envelope-exceeded. -
#effects_enabled? ⇒ Boolean
ADR-103 WD13 — whether effect collection runs.
-
#initialize(data = DEFAULTS, effects_key_present = data.key?("effects")) ⇒ Configuration
constructor
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity.
-
#to_h ⇒ Hash[String, untyped]
rubocop:disable Metrics/MethodLength, Metrics/AbcSize.
-
#with_bleeding_edge(value) ⇒ Object
ADR-50 § WD2 — returns a sibling Configuration whose bleeding-edge selection (and the derived
bleeding_edge_severity_overridesthe two SeverityProfile.resolve sites consult) is replaced byvalue, leaving every other field shared with the receiver. -
#with_effects_enabled ⇒ Rigor::Configuration
ADR-103 WD14 — the ad-hoc opt-in
rigor effectsuses when the project's configuration carries noeffects:block: a sibling Configuration with an implicit empty block, every other field shared.
Constructor Details
#initialize(data = DEFAULTS, effects_key_present = data.key?("effects")) ⇒ Configuration
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/rigor/configuration.rb', line 475 def initialize(data = DEFAULTS, effects_key_present = data.key?("effects")) # Record before the per-key fetches below discard the evidence. Top level only, deliberately — # see {ConfigAudit.unknown_key_warnings} for why a nested check cannot key on DEFAULTS. @unknown_keys = (data.keys.map(&:to_s) - KNOWN_KEYS).sort.freeze cache = DEFAULTS.fetch("cache").merge(data.fetch("cache", {})) plugins_io = DEFAULTS.fetch("plugins_io").merge(data.fetch("plugins_io", {})) @target_ruby = coerce_target_ruby(data.fetch("target_ruby", DEFAULTS.fetch("target_ruby"))) @paths = Array(data.fetch("paths", DEFAULTS.fetch("paths"))).map(&:to_s).freeze user_excludes = Array(data.fetch("exclude", DEFAULTS.fetch("exclude"))).map(&:to_s) @exclude_patterns = (BUILTIN_EXCLUDES + user_excludes).uniq.freeze @plugins = Array(data.fetch("plugins", DEFAULTS.fetch("plugins"))).map do |entry| coerce_plugin_entry(entry) end.freeze @disabled_rules = Array(data.fetch("disable", DEFAULTS.fetch("disable"))).map(&:to_s).freeze @libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths")) @signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze @pre_eval = ( Array(data.fetch("pre_eval", DEFAULTS.fetch("pre_eval"))).map(&:to_s) ) @baseline_path = coerce_baseline_path(data.fetch("baseline", DEFAULTS.fetch("baseline"))) @fold_platform_specific_paths = data.fetch( "fold_platform_specific_paths", DEFAULTS.fetch("fold_platform_specific_paths") ) # ADR-67 WD6a — resolve to a strict Boolean so a truthy non-`true` value (e.g. a stray String) does not # silently enable the gate; only the literal `true` activates the check-walk collector pre-pass. @parameter_inference = data.fetch("parameter_inference", DEFAULTS.fetch("parameter_inference")) == true # Resolved before `@effects` — ADR-103 WD15's "effects-on-by-default" reads {#bleeding_edge_active?}, # which reads `@bleeding_edge_active_ids`. @bleeding_edge = coerce_bleeding_edge( data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge")) ) @bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge) @bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge) # ADR-103 WD13 — presence, not truthiness: `effects:` written with no body parses as nil and still # means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads. @effects = coerce_effects(data, effects_key_present: effects_key_present) coerce_effects_snapshot(@effects) coerce_effects_policy(@effects) @cache_path = cache.fetch("path").to_s raw_max = cache.fetch("max_bytes") @cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max) @cache_validation = coerce_cache_validation(cache.fetch("validation", "auto")) @plugins_io_network = coerce_network_policy(plugins_io.fetch("network")) @plugins_io_allowed_paths = Array(plugins_io.fetch("allowed_paths")).map(&:to_s).freeze @plugins_io_allowed_url_hosts = Array(plugins_io.fetch("allowed_url_hosts")).map(&:to_s).freeze @severity_profile = coerce_severity_profile( data.fetch("severity_profile", DEFAULTS.fetch("severity_profile")) ) @severity_overrides = coerce_severity_overrides( data.fetch("severity_overrides", DEFAULTS.fetch("severity_overrides")) ) @dependencies = Dependencies.from_h( data.fetch("dependencies", DEFAULTS.fetch("dependencies")) ) parallel = DEFAULTS.fetch("parallel").merge(data.fetch("parallel", {})) @parallel_workers = coerce_parallel_workers(parallel.fetch("workers")) bundler = DEFAULTS.fetch("bundler").merge(data.fetch("bundler", {})) bp = bundler.fetch("bundle_path") @bundler_bundle_path = bp.nil? ? nil : bp.to_s.dup.freeze @bundler_auto_detect = bundler.fetch("auto_detect") == true lf = bundler.fetch("lockfile") @bundler_lockfile = lf.nil? ? nil : lf.to_s.dup.freeze rbs_collection = DEFAULTS.fetch("rbs_collection").merge(data.fetch("rbs_collection", {})) rclf = rbs_collection.fetch("lockfile") @rbs_collection_lockfile = rclf.nil? ? nil : rclf.to_s.dup.freeze @rbs_collection_auto_detect = rbs_collection.fetch("auto_detect") == true # Ractor migration Phase 2a: deep-freeze the Configuration so it is `Ractor.shareable?`. Every ivar # above is now either a frozen value (Symbol / nil / Boolean) or an explicitly frozen collection / # value object; freezing `self` makes the whole carrier safe to send across Ractor boundaries (and # catches accidental post-init mutation in any caller). See `docs/design/20260514-ractor-migration.md`. freeze end |
Instance Attribute Details
#baseline_path ⇒ String? (readonly)
Returns the value of attribute baseline_path.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def baseline_path @baseline_path end |
#bleeding_edge ⇒ Object (readonly)
Returns the value of attribute bleeding_edge.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def bleeding_edge @bleeding_edge end |
#bleeding_edge_severity_overrides ⇒ Object (readonly)
Returns the value of attribute bleeding_edge_severity_overrides.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def bleeding_edge_severity_overrides @bleeding_edge_severity_overrides end |
#bundler_auto_detect ⇒ Object (readonly)
Returns the value of attribute bundler_auto_detect.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def bundler_auto_detect @bundler_auto_detect end |
#bundler_bundle_path ⇒ Object (readonly)
Returns the value of attribute bundler_bundle_path.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def bundler_bundle_path @bundler_bundle_path end |
#bundler_lockfile ⇒ Object (readonly)
Returns the value of attribute bundler_lockfile.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def bundler_lockfile @bundler_lockfile end |
#cache_max_bytes ⇒ Object (readonly)
Returns the value of attribute cache_max_bytes.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def cache_max_bytes @cache_max_bytes end |
#cache_path ⇒ String (readonly)
Returns the value of attribute cache_path.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def cache_path @cache_path end |
#cache_validation ⇒ Object (readonly)
Returns the value of attribute cache_validation.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def cache_validation @cache_validation end |
#dependencies ⇒ Object (readonly)
Returns the value of attribute dependencies.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def dependencies @dependencies end |
#disabled_rules ⇒ Object (readonly)
Returns the value of attribute disabled_rules.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def disabled_rules @disabled_rules end |
#effects ⇒ Hash[String, untyped]? (readonly)
ADR-103 WD13 — the effects: block, nil when the key was absent. Presence is the opt-in.
20 21 22 |
# File 'sig/rigor.rbs', line 20 def effects @effects end |
#effects_attribution ⇒ Object (readonly)
Returns the value of attribute effects_attribution.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_attribution @effects_attribution end |
#effects_envelopes ⇒ Object (readonly)
Returns the value of attribute effects_envelopes.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_envelopes @effects_envelopes end |
#effects_labels ⇒ Object (readonly)
Returns the value of attribute effects_labels.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_labels @effects_labels end |
#effects_snapshot_gate ⇒ Object (readonly)
Returns the value of attribute effects_snapshot_gate.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_snapshot_gate @effects_snapshot_gate end |
#effects_snapshot_path ⇒ Object (readonly)
Returns the value of attribute effects_snapshot_path.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_snapshot_path @effects_snapshot_path end |
#effects_snapshot_reach ⇒ Object (readonly)
Returns the value of attribute effects_snapshot_reach.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_snapshot_reach @effects_snapshot_reach end |
#effects_tolerated ⇒ Object (readonly)
Returns the value of attribute effects_tolerated.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def effects_tolerated @effects_tolerated end |
#exclude_patterns ⇒ Object (readonly)
Returns the value of attribute exclude_patterns.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def exclude_patterns @exclude_patterns end |
#fold_platform_specific_paths ⇒ Object (readonly)
Returns the value of attribute fold_platform_specific_paths.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def fold_platform_specific_paths @fold_platform_specific_paths end |
#libraries ⇒ Object (readonly)
Returns the value of attribute libraries.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def libraries @libraries end |
#parallel_workers ⇒ Object (readonly)
Returns the value of attribute parallel_workers.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def parallel_workers @parallel_workers end |
#parameter_inference ⇒ Object (readonly)
Returns the value of attribute parameter_inference.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def parameter_inference @parameter_inference end |
#paths ⇒ Array[String] (readonly)
Returns the value of attribute paths.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def paths @paths end |
#plugins ⇒ Array[String] (readonly)
Returns the value of attribute plugins.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def plugins @plugins end |
#plugins_io_allowed_paths ⇒ Object (readonly)
Returns the value of attribute plugins_io_allowed_paths.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def plugins_io_allowed_paths @plugins_io_allowed_paths end |
#plugins_io_allowed_url_hosts ⇒ Object (readonly)
Returns the value of attribute plugins_io_allowed_url_hosts.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def plugins_io_allowed_url_hosts @plugins_io_allowed_url_hosts end |
#plugins_io_network ⇒ Object (readonly)
Returns the value of attribute plugins_io_network.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def plugins_io_network @plugins_io_network end |
#pre_eval ⇒ Object (readonly)
Returns the value of attribute pre_eval.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def pre_eval @pre_eval end |
#rbs_collection_auto_detect ⇒ Object (readonly)
Returns the value of attribute rbs_collection_auto_detect.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def rbs_collection_auto_detect @rbs_collection_auto_detect end |
#rbs_collection_lockfile ⇒ Object (readonly)
Returns the value of attribute rbs_collection_lockfile.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def rbs_collection_lockfile @rbs_collection_lockfile end |
#severity_overrides ⇒ Object (readonly)
Returns the value of attribute severity_overrides.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def severity_overrides @severity_overrides end |
#severity_profile ⇒ Object (readonly)
Returns the value of attribute severity_profile.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def severity_profile @severity_profile end |
#signature_paths ⇒ Object (readonly)
Returns the value of attribute signature_paths.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def signature_paths @signature_paths end |
#target_ruby ⇒ String (readonly)
Returns the value of attribute target_ruby.
235 236 237 |
# File 'lib/rigor/configuration.rb', line 235 def target_ruby @target_ruby end |
#unknown_keys ⇒ Object (readonly)
Top-level keys the loaded config carried that this implementation does not own — neither a
DEFAULTS key, nor includes:, nor a reserved namespace. Recorded rather than acted on: an
unknown key stays as inert at run time as it has always been, and Rigor::ConfigAudit turns the record
into a warning. Empty for every conforming config.
This exists because #initialize fetches each key it owns and never enumerates the rest, so by
the time anything holds a Configuration the unknown keys are gone. The audit reads a
Configuration, so without this the class of mistake it exists to catch — a value that silently
resolves to nothing — was structurally invisible to it for whole keys.
233 234 235 |
# File 'lib/rigor/configuration.rb', line 233 def unknown_keys @unknown_keys end |
Class Method Details
.autowire_default_plugins(data) ⇒ Object
ADR-93 WD2 — default-wire the bundled rigor-rbs-inline plugin, in WD1's annotation-gated,
magic-comment-free mode, when the upstream rbs-inline library is resolvable and the user has not
already listed the plugin. Runs from load only — the real-project route — never from a bare
Configuration.new, so the suite's unit constructions do not auto-wire. The gate is presence-based
(ADR-72's shape): an annotation-free project pays only a comment scan and contributes nothing, so
wiring it on cannot regress the run. This partially reverses the ADR-27/ADR-31 auto-load deferral for
this one bundled plugin (the executed code is already vendored, not arbitrary third-party plugin code).
Opt out with a plugins: entry disabling it (enabled: false), or per-file # rbs_inline: disabled.
309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/rigor/configuration.rb', line 309 def self.autowire_default_plugins(data) entries = Array(data["plugins"]) return data if entries.any? { |entry| rbs_inline_plugin_entry?(entry) } return data unless rbs_inline_library_resolvable? merged = data.dup merged["plugins"] = entries + [{ "gem" => AUTOWIRED_RBS_INLINE_GEM, "id" => AUTOWIRED_RBS_INLINE_ID, "config" => { "require_magic_comment" => false } }] merged end |
.discover ⇒ String?
Returns the path to the config file Rigor would load under auto-discovery, or nil when neither
candidate exists. Public so the CLI / spec drift checks can introspect the resolved file.
351 352 353 |
# File 'lib/rigor/configuration.rb', line 351 def self.discover DISCOVERY_ORDER.find { |candidate| File.exist?(candidate) } end |
.load(path = nil) ⇒ Configuration
Loads a configuration file.
path == nil triggers auto-discovery against DISCOVERY_ORDER. The first present file in that list is
loaded; if none exist the built-in DEFAULTS are used.
When a path is supplied (whether by auto-discovery or by the caller) the YAML body is processed for
includes: recursively, and every relative path inside path-bearing keys (paths:,
signature_paths:, plugins_io.allowed_paths:, includes:) is resolved against THAT file's
directory. The resolution is per-file: an included file's relative paths resolve against the included
file's directory, not the top-level file. Path resolution mirrors
PHPStan.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/rigor/configuration.rb', line 278 def self.load(path = nil) resolved = path || discover if resolved.nil? || !File.exist?(resolved) data = DEFAULTS effects_key_present = false else # ADR-103 WD15 — captured from the RAW, pre-`DEFAULTS.merge` file (+ its `includes:` chain) because # `DEFAULTS` itself carries an `"effects" => false` entry: once merged, "the file never wrote # `effects:`" and "the file wrote `effects: false`" collapse to the same value and become # indistinguishable to {#initialize}. This is the only place that distinction still exists. raw = load_with_includes(resolved) effects_key_present = raw.key?("effects") data = DEFAULTS.merge(raw) end new(autowire_default_plugins(data), effects_key_present) end |
.load_with_includes(path, visited: Set.new) ⇒ Hash[String, untyped]
Reads path (which MUST exist) plus every file listed in its includes: chain, merging them under the
order: included files first (in declaration order), then the current file's own keys override.
Relative paths inside each file are resolved against that file's directory. Public so the CLI can run
the include-aware load before applying --treat-all-as-inline-rbs's plugin injection (see
Rigor::CLI::CheckCommand#load_check_configuration).
360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/rigor/configuration.rb', line 360 def self.load_with_includes(path, visited: Set.new) absolute = File.(path) raise ConfigurationError, "circular include: #{absolute}" if visited.include?(absolute) raw = read_yaml(absolute) raise ConfigurationError, "config file must be a YAML mapping: #{absolute}" unless raw.is_a?(Hash) base_dir = File.dirname(absolute) includes = Array(raw.delete("includes") || []) data = resolve_paths_in(raw, base_dir) next_visited = visited + [absolute] merge_includes(data, includes, base_dir, next_visited) end |
.rbs_inline_library_resolvable? ⇒ Boolean
A probe with no load side effect: it does not require the library, only asks whether RubyGems can
resolve the name. Gem::Specification.find_by_name raises Gem::MissingSpecError (a Gem::LoadError)
when the gem is absent — the standalone gem install rigortype case, WD3's residual. ADR-90's
project-bundle fallback resolves the same name at the plugin's own require time.
342 343 344 345 346 347 |
# File 'lib/rigor/configuration.rb', line 342 def self.rbs_inline_library_resolvable? Gem::Specification.find_by_name("rbs-inline") true rescue Gem::LoadError false end |
.rbs_inline_plugin_entry?(entry) ⇒ Boolean
True when a plugins: entry (String or Hash, string- or symbol-keyed) already references the
rigor-rbs-inline plugin by gem name or by manifest id. Single-homed here so the CLI's
--treat-all-as-inline-rbs injection and the WD2 auto-wire gate agree on what "already listed" means.
326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/rigor/configuration.rb', line 326 def self.rbs_inline_plugin_entry?(entry) case entry when String entry == AUTOWIRED_RBS_INLINE_GEM when Hash string_keyed = entry.to_h { |k, v| [k.to_s, v] } string_keyed["gem"] == AUTOWIRED_RBS_INLINE_GEM || string_keyed["id"] == AUTOWIRED_RBS_INLINE_ID else false end end |
.read_yaml(absolute) ⇒ Object
#433's sibling: a typo in the file the user is about to be told to fix is a configuration mistake
like any other, and escaped as a Psych::SyntaxError backtrace naming a file inside Ruby's stdlib.
Psych's own #message embeds the path in a parenthesised prefix that reads badly after rigor: ,
so the position is re-rendered in the path:line:column form the rest of Rigor's output uses.
378 379 380 381 382 383 |
# File 'lib/rigor/configuration.rb', line 378 def self.read_yaml(absolute) YAML.safe_load_file(absolute, aliases: false) || {} rescue Psych::SyntaxError => e detail = [e.problem, e.context].compact.join(" ") raise ConfigurationError, "#{absolute}:#{e.line}:#{e.column}: not valid YAML: #{detail}" end |
.resolve_path_key!(out, key, base_dir) ⇒ Object
413 414 415 416 417 |
# File 'lib/rigor/configuration.rb', line 413 def self.resolve_path_key!(out, key, base_dir) return unless out.key?(key) && !out[key].nil? out[key] = Array(out[key]).map { |p| File.(p.to_s, base_dir) } end |
.resolve_plugins_io_paths!(out, base_dir) ⇒ Object
419 420 421 422 423 424 425 426 |
# File 'lib/rigor/configuration.rb', line 419 def self.resolve_plugins_io_paths!(out, base_dir) plugins_io = out["plugins_io"] return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"] duped = plugins_io.dup duped["allowed_paths"] = Array(plugins_io["allowed_paths"]).map { |p| File.(p.to_s, base_dir) } out["plugins_io"] = duped end |
Instance Method Details
#bleeding_edge_active?(id) ⇒ Boolean
ADR-50 § WD2 — whether the bleeding-edge feature id is in force for this run.
This is how a behaviour feature reaches its call site: a severity feature composes itself into
bleeding_edge_severity_overrides and needs no engine code, but a queued change to a measurement, an
algorithm, or a default has to be asked about where it happens. Answering from Configuration keeps
the selection logic in one place and puts nothing but the id — already ADR-50 WD1 contract vocabulary
— into engine code.
Two deliberate asymmetries with the bleeding_edge: config key:
- An id in BleedingEdge::GRADUATED answers
trueunconditionally. Graduation (§ WD7) turns a feature on for everyone, so a gate that outlives it keeps the graduated behaviour rather than silently reverting, and the call-site cleanup can land whenever it is convenient. - An id that is in neither BleedingEdge::FEATURES nor BleedingEdge::GRADUATED raises. An
unknown id in a config file stays inert on purpose (it may come from a newer gem — see
#coerce_bleeding_edge), but an id here is written by a Rigor contributor against the registry in
the same checkout: a typo is a bug, and reading it as
falsewould ship the feature permanently off with no signal.
629 630 631 632 633 634 635 636 637 638 |
# File 'lib/rigor/configuration.rb', line 629 def bleeding_edge_active?(id) return true if BleedingEdge.graduated?(id) unless BleedingEdge.known_id?(id) raise ArgumentError, "unknown bleeding-edge feature id #{id.inspect}; known ids: " \ "#{(BleedingEdge.feature_ids + BleedingEdge::GRADUATED).inspect}" end @bleeding_edge_active_ids.include?(id) end |
#cache_validation_strict?(env = ENV) ⇒ Boolean
Resolves the cache.validation tri-state to the boolean the run installs via
Rigor::Cache::FileDigest.with_run's strict:. Explicit "digest" / "stat" win outright; the "auto"
default is strict exactly when Rigor::CiDetector recognises a CI provider (#190) — deliberately NOT stored at
construction so the resolution honours the environment of the run, not of Configuration.load, and the
frozen carrier stays env-independent. RIGOR_STRICT_VALIDATION=1 is enforced separately inside
Rigor::Cache::FileDigest.strict_validation? and wins over all three values.
558 559 560 561 562 563 564 |
# File 'lib/rigor/configuration.rb', line 558 def cache_validation_strict?(env = ENV) case cache_validation when "digest" then true when "stat" then false else !CiDetector.detect(env).nil? end end |
#effects_check? ⇒ Boolean
ADR-103 WD14 / #383 — whether author-declared effect envelopes (%a{pure},
%a{rigor:v1:effect …}) are checked against the collected summaries, surfacing
effect.envelope-exceeded. Defaults to true when the effects: block is present: the block is
the opt-in, and a project that asked for effects asked for its own declarations to hold. An explicit
check: false disables the diagnostic and leaves collection — and therefore rigor effects and the
snapshot — untouched. Always false without the block, so an annotation alone changes nothing.
263 264 265 |
# File 'lib/rigor/configuration.rb', line 263 def effects_check? @effects_check end |
#effects_enabled? ⇒ Boolean
ADR-103 WD13 — whether effect collection runs. True exactly when the loaded configuration carried an
effects: block, whatever its body; rigor effects enables it for its own run by loading an
implicit effects: {} instead of by consulting anything else. Nothing else — no annotation, no
plugin, no severity profile — can turn collection on.
253 254 255 |
# File 'lib/rigor/configuration.rb', line 253 def effects_enabled? !@effects.nil? end |
#to_h ⇒ Hash[String, untyped]
rubocop:disable Metrics/MethodLength, Metrics/AbcSize
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 |
# File 'lib/rigor/configuration.rb', line 566 def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize { "target_ruby" => target_ruby, "paths" => paths, "exclude" => exclude_patterns - BUILTIN_EXCLUDES, "plugins" => plugins, "disable" => disabled_rules, "libraries" => libraries, "signature_paths" => signature_paths, "pre_eval" => pre_eval, "fold_platform_specific_paths" => fold_platform_specific_paths, "parameter_inference" => parameter_inference, "cache" => { "path" => cache_path, "max_bytes" => cache_max_bytes, "validation" => cache_validation }, "plugins_io" => { "network" => plugins_io_network.to_s, "allowed_paths" => plugins_io_allowed_paths, "allowed_url_hosts" => plugins_io_allowed_url_hosts }, "severity_profile" => severity_profile.to_s, "severity_overrides" => severity_overrides.to_h { |k, v| [k, v.to_s] }, "bleeding_edge" => bleeding_edge_to_h, "dependencies" => dependencies.to_h, "parallel" => { "workers" => parallel_workers }, "bundler" => { "bundle_path" => bundler_bundle_path, "auto_detect" => bundler_auto_detect, "lockfile" => bundler_lockfile }, "rbs_collection" => { "lockfile" => rbs_collection_lockfile, "auto_detect" => rbs_collection_auto_detect } } end |
#with_bleeding_edge(value) ⇒ Object
ADR-50 § WD2 — returns a sibling Configuration whose bleeding-edge selection (and the derived
bleeding_edge_severity_overrides the two Rigor::Configuration::SeverityProfile.resolve sites consult) is replaced by
value, leaving every other field shared with the receiver. value takes the same forms as the
bleeding_edge: config key — false / true / a feature-id Array / { "all" => true, "except" => [...] } — and is normalised through the same #coerce_bleeding_edge path, so an unknown id stays
inert.
The CLI's --bleeding-edge[=ids] / --no-bleeding-edge flag uses this to override the configured
selection for a single run (the same CLI-over-config precedence --workers and --no-cache follow).
It is a frozen dup with the bleeding-edge ivars re-set: dup returns an unfrozen shallow copy
(every other ivar is the receiver's deeply-frozen value, safe to share read-only), the replacements
are themselves deeply frozen, and the re-freeze keeps the result Ractor.shareable? for the worker
path. Every ivar #initialize derives from @bleeding_edge has to be re-derived here — the
selector, the severity map, and the active-id set the #bleeding_edge_active? predicate reads.
654 655 656 657 658 659 660 661 662 |
# File 'lib/rigor/configuration.rb', line 654 def with_bleeding_edge(value) selector = coerce_bleeding_edge(value) copy = dup copy.instance_variable_set(:@bleeding_edge, selector) copy.instance_variable_set(:@bleeding_edge_severity_overrides, BleedingEdge.severity_overrides_for(selector)) copy.instance_variable_set(:@bleeding_edge_active_ids, BleedingEdge.active_ids_for(selector)) copy.freeze end |
#with_effects_enabled ⇒ Rigor::Configuration
ADR-103 WD14 — the ad-hoc opt-in rigor effects uses when the project's configuration carries no
effects: block: a sibling Configuration with an implicit empty block, every other field shared.
A configuration that already enables effects is returned unchanged, so a project's own settings
always win over the implicit ones.
Same dup + re-freeze shape as #with_bleeding_edge: every other ivar is the receiver's deeply
frozen value, safe to share read-only, and the result stays Ractor.shareable? for the worker path.
671 672 673 674 675 676 677 678 679 680 |
# File 'lib/rigor/configuration.rb', line 671 def with_effects_enabled return self if effects_enabled? copy = dup copy.instance_variable_set(:@effects, {}.freeze) # The implicit block defaults like a written one. `rigor effects` emits no diagnostic either way, # but the two configurations must not differ in a field a later reader could branch on. copy.instance_variable_set(:@effects_check, true) copy.freeze end |