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
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.yml` is a developer-local override (typically gitignored); `.rigor.dist.yml` is 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_PATH` for help text / fixture paths still work; the discovery list is the canonical source.
DISCOVERY_ORDER.first
- 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 has `tmp/`, libraries usually don’t); user-supplied ‘exclude:` entries in `.rigor.yml` cover 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, "cache" => { "path" => ".rigor/cache", # LRU eviction cap in bytes. nil (the default) disables eviction; # the cache grows until the user runs `rigor check --clear-cache`. # Set to a positive integer (e.g. 536870912 for 512 MB) to keep the # cache bounded — the least-recently-used entries are removed at the # end of each run when the total exceeds this limit. "max_bytes" => nil }, "plugins_io" => { "network" => "disabled", "allowed_paths" => [], "allowed_url_hosts" => [] }, "severity_profile" => "balanced", "severity_overrides" => {}, "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
Instance Attribute Summary collapse
-
#baseline_path ⇒ Object
readonly
Returns the value of attribute baseline_path.
-
#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 ⇒ Object
readonly
Returns the value of attribute cache_path.
-
#dependencies ⇒ Object
readonly
Returns the value of attribute dependencies.
-
#disabled_rules ⇒ Object
readonly
Returns the value of attribute disabled_rules.
-
#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.
-
#paths ⇒ Object
readonly
Returns the value of attribute paths.
-
#plugins ⇒ Object
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 ⇒ Object
readonly
Returns the value of attribute target_ruby.
Class Method Summary collapse
-
.discover ⇒ Object
Returns the path to the config file Rigor would load under auto-discovery, or ‘nil` when neither candidate exists.
-
.load(path = nil) ⇒ Object
Loads a configuration file.
- .resolve_path_key!(out, key, base_dir) ⇒ Object
- .resolve_plugins_io_paths!(out, base_dir) ⇒ Object
Instance Method Summary collapse
-
#initialize(data = DEFAULTS) ⇒ Configuration
constructor
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity.
-
#to_h ⇒ Object
rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity.
Constructor Details
#initialize(data = DEFAULTS) ⇒ Configuration
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/rigor/configuration.rb', line 321 def initialize(data = DEFAULTS) 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") ) == true @cache_path = cache.fetch("path").to_s raw_max = cache.fetch("max_bytes") @cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max) @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 ⇒ Object (readonly)
Returns the value of attribute baseline_path.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def baseline_path @baseline_path end |
#bundler_auto_detect ⇒ Object (readonly)
Returns the value of attribute bundler_auto_detect.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def bundler_auto_detect @bundler_auto_detect end |
#bundler_bundle_path ⇒ Object (readonly)
Returns the value of attribute bundler_bundle_path.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def bundler_bundle_path @bundler_bundle_path end |
#bundler_lockfile ⇒ Object (readonly)
Returns the value of attribute bundler_lockfile.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def bundler_lockfile @bundler_lockfile end |
#cache_max_bytes ⇒ Object (readonly)
Returns the value of attribute cache_max_bytes.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def cache_max_bytes @cache_max_bytes end |
#cache_path ⇒ Object (readonly)
Returns the value of attribute cache_path.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def cache_path @cache_path end |
#dependencies ⇒ Object (readonly)
Returns the value of attribute dependencies.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def dependencies @dependencies end |
#disabled_rules ⇒ Object (readonly)
Returns the value of attribute disabled_rules.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def disabled_rules @disabled_rules end |
#exclude_patterns ⇒ Object (readonly)
Returns the value of attribute exclude_patterns.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def exclude_patterns @exclude_patterns end |
#fold_platform_specific_paths ⇒ Object (readonly)
Returns the value of attribute fold_platform_specific_paths.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def fold_platform_specific_paths @fold_platform_specific_paths end |
#libraries ⇒ Object (readonly)
Returns the value of attribute libraries.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def libraries @libraries end |
#parallel_workers ⇒ Object (readonly)
Returns the value of attribute parallel_workers.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def parallel_workers @parallel_workers end |
#paths ⇒ Object (readonly)
Returns the value of attribute paths.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def paths @paths end |
#plugins ⇒ Object (readonly)
Returns the value of attribute plugins.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def plugins @plugins end |
#plugins_io_allowed_paths ⇒ Object (readonly)
Returns the value of attribute plugins_io_allowed_paths.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 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.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 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.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def plugins_io_network @plugins_io_network end |
#pre_eval ⇒ Object (readonly)
Returns the value of attribute pre_eval.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def pre_eval @pre_eval end |
#rbs_collection_auto_detect ⇒ Object (readonly)
Returns the value of attribute rbs_collection_auto_detect.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def rbs_collection_auto_detect @rbs_collection_auto_detect end |
#rbs_collection_lockfile ⇒ Object (readonly)
Returns the value of attribute rbs_collection_lockfile.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def rbs_collection_lockfile @rbs_collection_lockfile end |
#severity_overrides ⇒ Object (readonly)
Returns the value of attribute severity_overrides.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def severity_overrides @severity_overrides end |
#severity_profile ⇒ Object (readonly)
Returns the value of attribute severity_profile.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def severity_profile @severity_profile end |
#signature_paths ⇒ Object (readonly)
Returns the value of attribute signature_paths.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def signature_paths @signature_paths end |
#target_ruby ⇒ Object (readonly)
Returns the value of attribute target_ruby.
175 176 177 |
# File 'lib/rigor/configuration.rb', line 175 def target_ruby @target_ruby end |
Class Method Details
.discover ⇒ Object
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.
213 214 215 |
# File 'lib/rigor/configuration.rb', line 213 def self.discover DISCOVERY_ORDER.find { |candidate| File.exist?(candidate) } end |
.load(path = nil) ⇒ Object
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](phpstan.org/config-reference#paths).
201 202 203 204 205 206 207 |
# File 'lib/rigor/configuration.rb', line 201 def self.load(path = nil) resolved = path || discover return new(DEFAULTS) if resolved.nil? || !File.exist?(resolved) data = load_with_includes(resolved) new(DEFAULTS.merge(data)) end |
.resolve_path_key!(out, key, base_dir) ⇒ Object
266 267 268 269 270 |
# File 'lib/rigor/configuration.rb', line 266 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
272 273 274 275 276 277 278 279 |
# File 'lib/rigor/configuration.rb', line 272 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
#to_h ⇒ Object
rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/rigor/configuration.rb', line 383 def to_h # rubocop:disable Metrics/MethodLength { "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, "cache" => { "path" => cache_path, "max_bytes" => cache_max_bytes }, "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] }, "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 |