Class: Rigor::Analysis::Runner
- Inherits:
-
Object
- Object
- Rigor::Analysis::Runner
- Defined in:
- lib/rigor/analysis/runner.rb,
lib/rigor/analysis/runner/run_snapshots.rb,
lib/rigor/analysis/runner/pool_coordinator.rb,
lib/rigor/analysis/runner/project_pre_passes.rb,
lib/rigor/analysis/runner/diagnostic_aggregator.rb,
sig/rigor.rbs
Overview
rubocop:disable Metrics/ClassLength
Defined Under Namespace
Classes: DiagnosticAggregator, PoolCoordinator, ProjectPrePasses, RunSnapshots
Constant Summary collapse
- RUBY_GLOB =
"**/*.rb"- DEFAULT_CACHE_ROOT =
".rigor/cache"- PRISM_VERSION_LADDER =
Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
version:. Prism exposes no version list, so the floor is found empirically — only when a misconfiguredtarget_rubyis rejected — so the diagnostic can name it instead of leaving the user to guess (the dogfood field trial, 20260620-skill-driven-onboarding-dogfood.md, burned cycles on exactly this). %w[ 3.0.0 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 4.0.0 4.1.0 4.2.0 ].freeze
Instance Attribute Summary collapse
-
#analyzed_files ⇒ Array[String]
readonly
Returns the value of attribute analyzed_files.
-
#boundary_cross_reporter ⇒ Object
readonly
Returns the value of attribute boundary_cross_reporter.
-
#buffer ⇒ Object
readonly
ADR-pending editor mode — present when the runner is wired for the
--tmp-file/--instead-ofbuffer-binding shape (docs/design/20260516-editor-mode.md). -
#cache_store ⇒ Object
readonly
Rigor::Cache::Storeitself is not yet sig-covered (the cache namespace is inUNSIGNED_NAMESPACESperspec/rigor/public_api_drift_spec.rb), so reference it asuntypeduntil the full Cache::Store sig lands. -
#dependency_source_index ⇒ Object
readonly
Returns the value of attribute dependency_source_index.
-
#file_dependencies ⇒ Hash[String, untyped]
readonly
Returns the value of attribute file_dependencies.
-
#plugin_registry ⇒ Object
readonly
Returns the value of attribute plugin_registry.
-
#rbs_extended_reporter ⇒ Object
readonly
Returns the value of attribute rbs_extended_reporter.
-
#unresolved_self_calls ⇒ Object
readonly
Returns the value of attribute unresolved_self_calls.
Class Method Summary collapse
-
.prism_supported_floor ⇒ String?
The lowest
target_rubythis Prism build accepts, or nil if none of the ladder parses.
Instance Method Summary collapse
-
#analysis_file_set(paths = @configuration.paths) ⇒ Array[String]
ADR-46 — the project file set that a run over
pathswould analyze, computed by globbing only (no RBS environment build), so the incremental fingerprint can be derived cheaply on the warm path before deciding whether to build the env at all. - #analyzed_file_entries(expansion) ⇒ Object
- #assemble_run_diagnostics(expansion, environment: nil) ⇒ Object
-
#class_declarations ⇒ Object
ADR-46 slice 3 — per-file set of the qualified class/module names declared in that file.
-
#compute_run_diagnostics(expansion) ⇒ Object
ADR-45 — unchanged-project fast path.
- #config_hash_entry(key, payload) ⇒ Object
-
#file_dependents ⇒ Hash[String, untyped]
ADR-46 §2 — inverts #file_dependencies into the reverse edge the incremental step walks:
dependents[X] = { A : A read a declaration / body from X }. -
#initialize(configuration:, explain: false, cache_store: Cache::Store.new(root: DEFAULT_CACHE_ROOT), plugin_requirer: nil, workers: 0, collect_stats: true, buffer: nil, prebuilt: nil, environment: nil, record_dependencies: false, record_self_calls: false, analyze_only: nil) ⇒ Runner
constructor
A new instance of Runner.
-
#prepare_project_scan(paths: @configuration.paths) ⇒ Object
Runs every project-wide pre-pass (
load_plugins+plugin#prepare+ dependency-source builder + synthetic-method scanner + project-patched scanner) exactly once, then returns a frozen ProjectScan snapshot. -
#run(paths = @configuration.paths) ⇒ Result
Walks every Ruby file under
paths, parses it, builds a per-node scope index throughRigor::Inference::ScopeIndexer, and runs theRigor::Analysis::CheckRulescatalogue over it. -
#run_dependency_descriptor(expansion, rbs_descriptor) ⇒ Object
Files the run actually depended on, collected AFTER it ran: every analyzed file, every RBS
sigfile (rbs_descriptor.files), and every file each plugin read (complete post-run, so reads made mid-analysis are included). -
#run_key_descriptor(expansion, rbs_descriptor) ⇒ Object
Stable cache key inputs — known before the run: a digest of the resolved configuration, the engine + rbs versions +
--explain, and the analyzed-path SET (adding/removing a file changes the key; editing one is caught by dependency validation). -
#run_result_cacheable? ⇒ Boolean
Cacheable only for a full sequential project run with a writable cache and no per-buffer / prebuilt override — every other mode has a different result identity (pool workers read in separate processes; editor mode is per-buffer; prebuilt is the LSP path).
-
#run_source(source:, path: "(source).rb") ⇒ Result
Analyze a single source String in memory, without writing it to disk — a clean entry point for embedders (LSP / editor mode) and a faster spec path than the per-call tmpdir + chdir.
-
#stats_for_run(wall_started_at:, expansion:) ⇒ Object
A cache hit skipped the analysis, so the per-run stats (wall split, RBS-class counts, …) were never gathered — report none rather than the stale snapshot defaults.
-
#symbol_fingerprints ⇒ Object
ADR-46 slice 4 — per-symbol body fingerprints, computed from the project pre-pass def index.
-
#validate_target_ruby ⇒ Object
target_rubyflows through to Prism'sversion:option.
Constructor Details
#initialize(configuration:, explain: false, cache_store: Cache::Store.new(root: DEFAULT_CACHE_ROOT), plugin_requirer: nil, workers: 0, collect_stats: true, buffer: nil, prebuilt: nil, environment: nil, record_dependencies: false, record_self_calls: false, analyze_only: nil) ⇒ Runner
Returns a new instance of Runner.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/rigor/analysis/runner.rb', line 78 def initialize(configuration:, explain: false, # rubocop:disable Metrics/ParameterLists,Metrics/AbcSize,Metrics/MethodLength cache_store: Cache::Store.new(root: DEFAULT_CACHE_ROOT), plugin_requirer: nil, workers: 0, collect_stats: true, buffer: nil, prebuilt: nil, environment: nil, record_dependencies: false, record_self_calls: false, analyze_only: nil) @configuration = configuration @explain = explain @cache_store = enforce_read_only_cache(cache_store, buffer) @plugin_requirer = plugin_requirer @workers = workers @collect_stats = collect_stats @buffer = buffer @prebuilt = prebuilt @environment_override = environment # ADR-46 slice 1 — opt-in cross-file dependency recording. Off by default; when true, # `analyze_file` records each file's cross-file reads into `file_dependencies` (the incremental # cache, a later slice, consumes them). @record_dependencies = record_dependencies # ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording. Off by default; when true, # `analyze_file` activates the engine choke-point recorder and collects each file's misses into # `unresolved_self_calls` (a later closed-class-gated rule consumes them). Purely observational — # diagnostics are byte-identical. @record_self_calls = record_self_calls @unresolved_self_calls = {} # Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed). # See `self_undefined_rule_active?`. @self_undefined_rule_active = nil @analyzed_files = [].freeze # In-memory source map for `#run_source` — `{ logical_path => source String }`. When set, # `parse_source` reads bytes from here instead of disk and `expand_paths` accepts the (possibly # non-existent) logical path. nil on a normal disk-backed run. @in_memory_sources = nil # ADR-46 slice 2 — the subset-analysis hook. When set (a collection of paths), the whole-project # pre-pass still runs over every file (so the cross-file index is complete), but only files in this # set are analyzed for diagnostics — the body tier re-analyses the affected closure and serves the # rest from the per-file cache. `nil` (the default) analyzes everything. @analyze_only = analyze_only && Set.new(analyze_only) @file_dependencies = {} @plugin_registry = Plugin::Registry::EMPTY @dependency_source_index = DependencySourceInference::Index::EMPTY @rbs_extended_reporter = RbsExtended::Reporter.new @boundary_cross_reporter = DependencySourceInference::BoundaryCrossReporter.new @source_rbs_synthesis_reporter = Plugin::SourceRbsSynthesisReporter.new # `#run` resets these for each invocation; pre-seed them to empty containers so `build_run_stats` / # `pre_file_diagnostics` (private, called only from `#run`) can read them without nil-guards. The # four end-of-pass snapshots (RBS class / signature-path tables, synthesized-namespace names, # `rigor:v1:conforms-to` results) live in one shared mutable {RunSnapshots} sink so the analysis # path that writes them and the run / aggregator code that reads them stay in separate # collaborators without a back-reference cycle. @snapshots = RunSnapshots.new @cached_plugin_prepare_diagnostics = [].freeze @project_discovered_classes = {}.freeze @project_discovered_def_nodes = {}.freeze @project_discovered_singleton_def_nodes = {}.freeze @project_discovered_def_sources = {}.freeze @project_discovered_superclasses = {}.freeze @project_discovered_includes = {}.freeze @project_discovered_class_sources = {}.freeze @project_discovered_method_visibilities = {}.freeze @project_discovered_methods = {}.freeze @project_data_member_layouts = {}.freeze @project_struct_member_layouts = {}.freeze build_collaborators end |
Instance Attribute Details
#analyzed_files ⇒ Array[String] (readonly)
Returns the value of attribute analyzed_files.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def analyzed_files @analyzed_files end |
#boundary_cross_reporter ⇒ Object (readonly)
Returns the value of attribute boundary_cross_reporter.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def boundary_cross_reporter @boundary_cross_reporter end |
#buffer ⇒ Object (readonly)
ADR-pending editor mode — present when the runner is wired for the --tmp-file / --instead-of
buffer-binding shape (docs/design/20260516-editor-mode.md). Nil for normal project runs.
145 146 147 |
# File 'lib/rigor/analysis/runner.rb', line 145 def buffer @buffer end |
#cache_store ⇒ Object (readonly)
Rigor::Cache::Store itself is not yet sig-covered (the
cache namespace is in UNSIGNED_NAMESPACES per
spec/rigor/public_api_drift_spec.rb), so reference it as
untyped until the full Cache::Store sig lands. Steep
otherwise raises RBS::UnknownTypeName for the named type.
113 114 115 |
# File 'sig/rigor.rbs', line 113 def cache_store @cache_store end |
#dependency_source_index ⇒ Object (readonly)
Returns the value of attribute dependency_source_index.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def dependency_source_index @dependency_source_index end |
#file_dependencies ⇒ Hash[String, untyped] (readonly)
Returns the value of attribute file_dependencies.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def file_dependencies @file_dependencies end |
#plugin_registry ⇒ Object (readonly)
Returns the value of attribute plugin_registry.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def plugin_registry @plugin_registry end |
#rbs_extended_reporter ⇒ Object (readonly)
Returns the value of attribute rbs_extended_reporter.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def rbs_extended_reporter @rbs_extended_reporter end |
#unresolved_self_calls ⇒ Object (readonly)
Returns the value of attribute unresolved_self_calls.
46 47 48 |
# File 'lib/rigor/analysis/runner.rb', line 46 def unresolved_self_calls @unresolved_self_calls end |
Class Method Details
.prism_supported_floor ⇒ String?
Returns the lowest target_ruby this Prism build accepts, or nil if none of the
ladder parses. Memoised per process (the value is constant for a given Prism).
443 444 445 446 447 448 449 450 |
# File 'lib/rigor/analysis/runner.rb', line 443 def self.prism_supported_floor @prism_supported_floor ||= PRISM_VERSION_LADDER.find do |candidate| Prism.parse("nil", version: candidate) true rescue ArgumentError false end end |
Instance Method Details
#analysis_file_set(paths = @configuration.paths) ⇒ Array[String]
ADR-46 — the project file set that a run over paths would analyze, computed by globbing only (no
RBS environment build), so the incremental fingerprint can be derived cheaply on the warm path
before deciding whether to build the env at all.
198 199 200 |
# File 'lib/rigor/analysis/runner.rb', line 198 def analysis_file_set(paths = @configuration.paths) (paths).fetch(:files) end |
#analyzed_file_entries(expansion) ⇒ Object
343 344 345 346 347 348 349 350 |
# File 'lib/rigor/analysis/runner.rb', line 343 def analyzed_file_entries(expansion) expansion.fetch(:files).map do |path| physical = @buffer ? @buffer.resolve(path) : path Cache::Descriptor::FileEntry.new( path: physical, comparator: :digest, value: Digest::SHA256.file(physical).hexdigest ) end end |
#assemble_run_diagnostics(expansion, environment: nil) ⇒ Object
279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/rigor/analysis/runner.rb', line 279 def assemble_run_diagnostics(expansion, environment: nil) diagnostics = @diagnostic_aggregator.pre_file_diagnostics(expansion) # ADR-46 — record which project files this run actually analyzed (the `analyze_only` subset, or # all of them). The incremental orchestrator serves every analyzed-but-not-affected file from the # per-file cache, so it needs the full analyzed set to subtract the affected closure from. targets = target_files(expansion) @analyzed_files = targets diagnostics += @pool_coordinator.analyze_files(targets, environment: environment) diagnostics += @diagnostic_aggregator.rbs_synthesized_namespace_diagnostics diagnostics += @diagnostic_aggregator.conforms_to_diagnostics diagnostics += @diagnostic_aggregator.rbs_extended_reporter_diagnostics diagnostics += @diagnostic_aggregator.boundary_cross_diagnostics diagnostics + @diagnostic_aggregator.source_rbs_synthesis_diagnostics end |
#class_declarations ⇒ Object
ADR-46 slice 3 — per-file set of the qualified class/module names declared in that file. Used to detect a class that appeared in an edit so a subclass whose ancestor was previously undefined (and so recorded a negative class edge) is re-checked. Inverts the project class-source attribution (class → declaring files).
239 240 241 242 243 244 245 |
# File 'lib/rigor/analysis/runner.rb', line 239 def class_declarations result = Hash.new { |hash, key| hash[key] = Set.new } @project_discovered_class_sources.each do |class_name, files| files.each { |file| result[file] << class_name } end result.transform_values(&:freeze).freeze end |
#compute_run_diagnostics(expansion) ⇒ Object
ADR-45 — unchanged-project fast path. Serves the whole run's (pre-severity-profile) diagnostics from one record-and-validate cache entry when every file the previous run read is unchanged, skipping the dominant per-file inference. The dependency set is collected AFTER the run (so it captures files the plugins read mid-analysis, e.g. a Pundit policy) and re-validated on the next run; the entry is keyed on the inputs known up front (config, gem / engine versions, analyzed-path set).
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/rigor/analysis/runner.rb', line 253 def compute_run_diagnostics(expansion) @run_served_from_cache = false return assemble_run_diagnostics(expansion) unless run_result_cacheable? environment = @pool_coordinator.resolve_sequential_environment(source_files: target_files(expansion)) rbs_descriptor = environment&.rbs_loader ? Cache::RbsDescriptor.build(environment.rbs_loader) : Cache::Descriptor.new key_descriptor = run_key_descriptor(expansion, rbs_descriptor) return assemble_run_diagnostics(expansion, environment: environment) if key_descriptor.nil? computed = false diagnostics = @cache_store.fetch_or_validate( producer_id: "analysis.run-diagnostics", key_descriptor: key_descriptor ) do computed = true diags = assemble_run_diagnostics(expansion, environment: environment) [diags, run_dependency_descriptor(expansion, rbs_descriptor)] end @run_served_from_cache = !computed diagnostics rescue StandardError # The result cache must never break a run. If anything in the cache path fails, fall back to a # direct, uncached analysis. @run_served_from_cache = false assemble_run_diagnostics(expansion) end |
#config_hash_entry(key, payload) ⇒ Object
352 353 354 |
# File 'lib/rigor/analysis/runner.rb', line 352 def config_hash_entry(key, payload) Cache::Descriptor::ConfigEntry.new(key: key, value_hash: Digest::SHA256.hexdigest(payload)) end |
#file_dependents ⇒ Hash[String, untyped]
ADR-46 §2 — inverts #file_dependencies into the reverse edge the incremental step walks:
dependents[X] = { A : A read a declaration / body from X }. On an edit to X, the body tier
(slice 2) re-analyses {X} ∪ dependents[X] and serves every other file from the per-file cache.
Built on demand from the recorded sources sets (so it reflects whatever analyze_file captured —
empty unless the runner was constructed with record_dependencies: true). The negative (missing)
edges are NOT inverted here: they feed the structural tier (slice 3), which re-checks a consumer
when a name it looked up and did not resolve later appears.
209 210 211 |
# File 'lib/rigor/analysis/runner.rb', line 209 def file_dependents Incremental.invert(@file_dependencies.transform_values(&:sources)) end |
#prepare_project_scan(paths: @configuration.paths) ⇒ Object
Runs every project-wide pre-pass (load_plugins +
plugin#prepare + dependency-source builder +
synthetic-method scanner + project-patched scanner)
exactly once, then returns a frozen
ProjectScan snapshot.
Long-lived integrations (Rigor::LanguageServer::ProjectContext)
call this once per project-state generation and feed the
snapshot back into Runner.new(prebuilt: scan) for every
subsequent per-buffer publish. The cold pre-pass cost is
paid once per generation rather than once per keystroke.
Notes for callers:
- The runner this method is called on may be a "build only"
instance —
@bufferis typically nil so the scanners observe on-disk bytes for the full project. Callers that want pre-passes to see a particular buffer's edits should build the runner WITHbuffer:set. - The returned ProjectScan is frozen and shareable; the
underlying
plugin_registryis the same object that ran#prepare, so the per-pluginservices.fact_storeis already populated for subsequent dispatch use.
378 379 380 381 382 383 |
# File 'lib/rigor/analysis/runner.rb', line 378 def prepare_project_scan(paths: @configuration.paths) expansion = (paths) result = @pre_passes.run(expansion: expansion) apply_pre_passes_result(result) @pre_passes.build_project_scan(result) end |
#run(paths = @configuration.paths) ⇒ Result
Walks every Ruby file under paths, parses it, builds a per-node scope index through
Rigor::Inference::ScopeIndexer, and runs the Rigor::Analysis::CheckRules catalogue over it.
Returns a Rigor::Analysis::Result aggregating every produced diagnostic plus any Prism parse
errors. The Environment is built once at run start through Environment.for_project so all files
share the same RBS load.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/rigor/analysis/runner.rb', line 152 def run(paths = @configuration.paths) Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths = @configuration.fold_platform_specific_paths wall_started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) target_ruby_error = validate_target_ruby return Result.new(diagnostics: [target_ruby_error]) if target_ruby_error expansion = (paths) @snapshots.reset_for_run if @prebuilt adopt_prebuilt_project_scan(@prebuilt) else run_project_pre_passes(expansion: expansion) end diagnostics = compute_run_diagnostics(expansion) Result.new( diagnostics: @diagnostic_aggregator.apply_severity_profile(diagnostics), stats: stats_for_run(wall_started_at: wall_started_at, expansion: expansion) ) end |
#run_dependency_descriptor(expansion, rbs_descriptor) ⇒ Object
Files the run actually depended on, collected AFTER it ran: every analyzed file, every RBS sig
file (rbs_descriptor.files), and every file each plugin read (complete post-run, so reads made
mid-analysis are included). Re-digested on the next run by Descriptor#fresh?.
331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/rigor/analysis/runner.rb', line 331 def run_dependency_descriptor(expansion, rbs_descriptor) entries = analyzed_file_entries(expansion) + rbs_descriptor.files @plugin_registry.plugins.each do |plugin| # Read the boundary WITHOUT triggering its lazy `@io_boundary ||=` initializer: plugin instances # are frozen after the run, and a plugin that never built a boundary read no files through it, # so it contributes no dependencies. boundary = plugin.instance_variable_get(:@io_boundary) entries.concat(boundary.cache_descriptor.files) if boundary end Cache::Descriptor.new(files: entries) end |
#run_key_descriptor(expansion, rbs_descriptor) ⇒ Object
Stable cache key inputs — known before the run: a digest of the resolved configuration, the engine
- rbs versions +
--explain, and the analyzed-path SET (adding/removing a file changes the key; editing one is caught by dependency validation). nil disables the cache for this run rather than risking a malformed key.
315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/rigor/analysis/runner.rb', line 315 def run_key_descriptor(expansion, rbs_descriptor) Cache::Descriptor.new( gems: rbs_descriptor.gems, configs: rbs_descriptor.configs + [ config_hash_entry("configuration", Marshal.dump(@configuration.to_h)), config_hash_entry("engine", "#{Rigor::VERSION}:#{Cache::Descriptor::SCHEMA_VERSION}:#{@explain}"), config_hash_entry("paths", expansion.fetch(:files).sort.join("\n")) ] ) rescue StandardError nil end |
#run_result_cacheable? ⇒ Boolean
Cacheable only for a full sequential project run with a writable cache and no per-buffer / prebuilt override — every other mode has a different result identity (pool workers read in separate processes; editor mode is per-buffer; prebuilt is the LSP path).
306 307 308 309 |
# File 'lib/rigor/analysis/runner.rb', line 306 def run_result_cacheable? !@cache_store.nil? && !@cache_store.read_only? && @buffer.nil? && @prebuilt.nil? && !pool_mode? end |
#run_source(source:, path: "(source).rb") ⇒ Result
Analyze a single source String in memory, without writing it to disk — a clean entry point for
embedders (LSP / editor mode) and a faster spec path than the per-call tmpdir + chdir. The source
is bound to path (purely a logical identity carried in diagnostic locations; it need not exist on
disk). The full run machinery still runs — environment build, plugin prepare, severity profile —
so the result matches a one-file disk run; only the cross-file project pre-pass is empty (there is
one file, and the per-file indexer self-discovers its own classes / defs).
188 189 190 191 192 193 |
# File 'lib/rigor/analysis/runner.rb', line 188 def run_source(source:, path: "(source).rb") @in_memory_sources = { path => source } run([path]) ensure @in_memory_sources = nil end |
#stats_for_run(wall_started_at:, expansion:) ⇒ Object
A cache hit skipped the analysis, so the per-run stats (wall split, RBS-class counts, …) were never gathered — report none rather than the stale snapshot defaults.
296 297 298 299 300 301 |
# File 'lib/rigor/analysis/runner.rb', line 296 def stats_for_run(wall_started_at:, expansion:) return nil unless @collect_stats return nil if @run_served_from_cache build_run_stats(wall_started_at: wall_started_at, expansion: expansion) end |
#symbol_fingerprints ⇒ Object
ADR-46 slice 4 — per-symbol body fingerprints, computed from the project pre-pass def index. Returns a frozen hash of the form:
{ "path/to/file.rb" => { "ClassName#method" => sha256_hex, … }, … }
Used by IncrementalSession to detect which symbols in a changed file actually changed
bodies, so only callers of those specific symbols are re-checked. Only meaningful after a run that
populated @project_discovered_def_nodes (i.e. any full or subset analysis); returns an empty
frozen hash before the first run.
220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/rigor/analysis/runner.rb', line 220 def symbol_fingerprints result = Hash.new { |h, k| h[k] = {} } @project_discovered_def_sources.each do |class_name, methods| methods.each do |method_sym, path_line| path = path_line.split(":", 2).first node = @project_discovered_def_nodes.dig(class_name, method_sym) next unless node result[path]["#{class_name}##{method_sym}"] = Digest::SHA256.hexdigest(node.location.slice) end end result.transform_values(&:freeze).freeze end |
#validate_target_ruby ⇒ Object
target_ruby flows through to Prism's version: option. Prism enforces the supported range and
raises ArgumentError for versions it does not recognise. Run a one-time smoke parse here so a
misconfigured target_ruby surfaces as a single project-level diagnostic instead of crashing the
whole run on the first file — and name the supported floor + where to read the right value, so the
fix is obvious without a guess-and-retry loop.
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/rigor/analysis/runner.rb', line 457 def validate_target_ruby Prism.parse("nil", version: @configuration.target_ruby) nil rescue ArgumentError => e floor = self.class.prism_supported_floor Diagnostic.new( path: ".rigor.yml", line: 1, column: 1, message: "target_ruby #{@configuration.target_ruby.inspect} is not supported by this Rigor build " \ "(Prism accepts #{floor} and newer). Set target_ruby to your project's Ruby version " \ "(>= #{floor}) — read it from Gemfile.lock's `RUBY VERSION` or .ruby-version. " \ "(Prism: #{e.})", severity: :error, rule: "configuration-error", source_family: :builtin ) end |