rigortype 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +104 -15
- data/docs/manual/02-cli-reference.md +43 -12
- data/docs/manual/03-configuration.md +20 -7
- data/docs/manual/04-diagnostics.md +56 -3
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +3 -2
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +40 -4
- data/docs/manual/18-removing-dead-code.md +23 -2
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-rspec.md +101 -1
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +60 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
- data/lib/rigor/analysis/check_rules.rb +274 -51
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +61 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +270 -28
- data/lib/rigor/analysis/path_expansion.rb +3 -1
- data/lib/rigor/analysis/reachability/graph.rb +111 -14
- data/lib/rigor/analysis/reachability/scan.rb +305 -23
- data/lib/rigor/analysis/rule_catalog.rb +22 -4
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +5 -3
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
- data/lib/rigor/analysis/runner/pool_coordinator.rb +179 -31
- data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
- data/lib/rigor/analysis/runner.rb +102 -72
- data/lib/rigor/analysis/severity_stamp.rb +45 -1
- data/lib/rigor/analysis/worker_session.rb +69 -2
- data/lib/rigor/cache/descriptor.rb +79 -12
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +39 -5
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +32 -11
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +163 -22
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +29 -3
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +67 -11
- data/lib/rigor/inference/block_call_timing.rb +12 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/captured_locals.rb +67 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/definer_resolution.rb +101 -25
- data/lib/rigor/inference/expression_typer.rb +94 -9
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher.rb +67 -3
- data/lib/rigor/inference/module_function_state.rb +213 -51
- data/lib/rigor/inference/narrowing.rb +235 -75
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +120 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +414 -1
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer.rb +1061 -122
- data/lib/rigor/inference/self_call_ivar_writes.rb +346 -0
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +512 -150
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/lens/anchor.rb +3 -3
- data/lib/rigor/lens/contract_reader.rb +60 -2
- data/lib/rigor/lens/document.rb +4 -3
- data/lib/rigor/lens/file_reader.rb +452 -73
- data/lib/rigor/lens/members.rb +81 -17
- data/lib/rigor/lens/run.rb +73 -4
- data/lib/rigor/lens.rb +2 -1
- data/lib/rigor/mcp/server.rb +63 -22
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/project_environment.rb +6 -3
- data/lib/rigor/protection/discovery_seed.rb +9 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +27 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +56 -8
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +167 -22
- data/lib/rigor/sig_gen/layout_index.rb +40 -1
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/observation_collector.rb +137 -11
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/renderer.rb +13 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +50 -35
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/constant.rb +10 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +57 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +4 -3
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +16 -1
|
@@ -26,6 +26,7 @@ require_relative "../effects/identity"
|
|
|
26
26
|
require_relative "../effects/propagator"
|
|
27
27
|
require_relative "../inference/parameter_inference_collector"
|
|
28
28
|
require_relative "../inference/pre_eval_constants"
|
|
29
|
+
require_relative "../inference/project_seed_tables"
|
|
29
30
|
require_relative "../inference/scope_indexer"
|
|
30
31
|
require_relative "../inference/synthetic_method_scanner"
|
|
31
32
|
require_relative "../inference/project_patched_scanner"
|
|
@@ -98,6 +99,16 @@ module Rigor
|
|
|
98
99
|
pooled.empty? ? @file_dependencies : @file_dependencies.merge(pooled)
|
|
99
100
|
end
|
|
100
101
|
|
|
102
|
+
# Issue #1553 — the plugin reads ({Plugin::IoBoundary.attributing}) each file's analysis made this run,
|
|
103
|
+
# `{path => [row]}`, reconciled from the sequential and pooled halves exactly as {#file_dependencies} is.
|
|
104
|
+
# Empty unless `record_dependencies: true`; a file whose analysis read nothing has no key. The incremental
|
|
105
|
+
# session keeps them beside each file's cached rows so a later recheck re-analyses the file once one of
|
|
106
|
+
# them goes stale.
|
|
107
|
+
def analysis_reads
|
|
108
|
+
pooled = @pool_coordinator.collected_analysis_reads
|
|
109
|
+
pooled.empty? ? @analysis_reads : @analysis_reads.merge(pooled)
|
|
110
|
+
end
|
|
111
|
+
|
|
101
112
|
# ADR-103 — the run's propagated effect graph, or {Effects::EffectTable.empty} when collection did
|
|
102
113
|
# not run. Reconciled from the sequential and pooled halves exactly as {#file_dependencies} is, then
|
|
103
114
|
# closed by the post-pool fixpoint in {#assemble_run_diagnostics}.
|
|
@@ -297,7 +308,8 @@ module Rigor
|
|
|
297
308
|
record_dependencies: false, record_self_calls: false, analyze_only: nil,
|
|
298
309
|
seed_bundles: nil, collect_seed_bundles: false, param_inferred_types: nil,
|
|
299
310
|
discovery_seed: nil, no_tolerated_effects: false,
|
|
300
|
-
restored_run_level_rows: nil, served_per_file_diagnostics: nil,
|
|
311
|
+
restored_run_level_rows: nil, served_per_file_diagnostics: nil, served_effect_collections: nil,
|
|
312
|
+
on_file_indexed: nil)
|
|
301
313
|
@configuration = configuration
|
|
302
314
|
# ADR-113 WD2 — `rigor lens` reads a target's seeded index here: called with `(path, parse_result, scope,
|
|
303
315
|
# index)` for every file this process indexes, after its rules ran and inside the run window, so the reader
|
|
@@ -311,6 +323,10 @@ module Rigor
|
|
|
311
323
|
# narrowed run does not analyse, spliced into this run's per-file stream by
|
|
312
324
|
# {#interleave_served_diagnostics}. Empty on every other run.
|
|
313
325
|
@served_per_file_diagnostics = served_per_file_diagnostics || {}
|
|
326
|
+
# Issue #1898 — `{path => FileCollection}` the {IncrementalSession} serves for the same unanalysed files, so the
|
|
327
|
+
# effect graph this run closes (and the envelope pass judges) spans the whole project, not just the closure.
|
|
328
|
+
# Never part of {#effect_collections_by_path}, which stays what THIS run collected. Empty on every other run.
|
|
329
|
+
@served_effect_collections = served_effect_collections || {}
|
|
314
330
|
@explain = explain
|
|
315
331
|
@cache_store = enforce_read_only_cache(cache_store, buffer)
|
|
316
332
|
@plugin_requirer = plugin_requirer
|
|
@@ -459,6 +475,9 @@ module Rigor
|
|
|
459
475
|
@project_struct_member_layouts = {}.freeze
|
|
460
476
|
# Issue #684 — set per run by {#project_discovery_expansion}; nil on every path that does not widen.
|
|
461
477
|
@project_discovery_expansion = nil
|
|
478
|
+
# Issue #1583 — whether this run widened its discovery to the configured paths, even when the widening
|
|
479
|
+
# added no file; {#discovery_glob_entries} lists the configured roots for every such run.
|
|
480
|
+
@discovery_widened = false
|
|
462
481
|
# ADR-67 WD6a — the call-site parameter-inference table, populated by the opt-in pre-pass in
|
|
463
482
|
# `assemble_run_diagnostics` when `parameter_inference:` is enabled, then seeded onto every per-file
|
|
464
483
|
# scope (sequential + fork-worker) through `project_scope_seed_tables`. Empty by default, so the gate-off
|
|
@@ -513,6 +532,7 @@ module Rigor
|
|
|
513
532
|
|
|
514
533
|
expansion = expand_paths(paths)
|
|
515
534
|
@project_discovery_expansion = project_discovery_expansion(paths, expansion)
|
|
535
|
+
@discovery_widened = widen_discovery_to_project?(paths)
|
|
516
536
|
@snapshots.reset_for_run
|
|
517
537
|
# Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
|
|
518
538
|
@project_discovery_done = false
|
|
@@ -594,6 +614,19 @@ module Rigor
|
|
|
594
614
|
expand_paths(paths).fetch(:files)
|
|
595
615
|
end
|
|
596
616
|
|
|
617
|
+
# Issue #1583 — the project files a run over `paths` would DISCOVER without analysing them (#684's
|
|
618
|
+
# widening), by globbing only. Empty whenever the run would not widen, which includes every run over
|
|
619
|
+
# the configured `paths:`. {IncrementalSession} digests them, because an edit to one moves the
|
|
620
|
+
# diagnostics of the files it does analyse.
|
|
621
|
+
def discovery_only_file_set(paths = @configuration.paths)
|
|
622
|
+
expansion = expand_paths(paths)
|
|
623
|
+
widened = project_discovery_expansion(paths, expansion)
|
|
624
|
+
return [] if widened.nil?
|
|
625
|
+
|
|
626
|
+
analysed = expansion.fetch(:files).to_set
|
|
627
|
+
widened.fetch(:files).reject { |path| analysed.include?(path) }
|
|
628
|
+
end
|
|
629
|
+
|
|
597
630
|
# ADR-46 §2 — inverts {#file_dependencies} into the reverse edge the incremental step walks:
|
|
598
631
|
# `dependents[X] = { A : A read a declaration / body from X }`. On an edit to X, the body tier
|
|
599
632
|
# (slice 2) re-analyses `{X} ∪ dependents[X]` and serves every other file from the per-file cache.
|
|
@@ -786,6 +819,7 @@ module Rigor
|
|
|
786
819
|
@project_discovery_done = false
|
|
787
820
|
# This entry point supplies its own expansion; it never widens (see {#project_discovery_expansion}).
|
|
788
821
|
@project_discovery_expansion = nil
|
|
822
|
+
@discovery_widened = false
|
|
789
823
|
@run_generation = Object.new.freeze
|
|
790
824
|
run_project_pre_passes(expansion: expansion)
|
|
791
825
|
ensure_project_discovery(expansion)
|
|
@@ -851,7 +885,8 @@ module Rigor
|
|
|
851
885
|
analysis = serve_effect_collections(expansion, environment, key_descriptor, rbs_descriptor)
|
|
852
886
|
computed = false
|
|
853
887
|
diagnostics = @cache_store.fetch_or_validate(
|
|
854
|
-
producer_id: RunCacheKey::RUN_DIAGNOSTICS_PRODUCER_ID,
|
|
888
|
+
producer_id: RunCacheKey::RUN_DIAGNOSTICS_PRODUCER_ID,
|
|
889
|
+
key_descriptor: RunCacheKey.with_roots(key_descriptor, expansion.fetch(:roots)),
|
|
855
890
|
generation_cap: RunCacheKey::GENERATION_CAP
|
|
856
891
|
) do
|
|
857
892
|
computed = true
|
|
@@ -1380,17 +1415,32 @@ module Rigor
|
|
|
1380
1415
|
|
|
1381
1416
|
def build_run_dependency_descriptor(expansion, rbs_descriptor)
|
|
1382
1417
|
entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) +
|
|
1383
|
-
pre_eval_file_entries + template_unit_file_entries + rbs_descriptor.files
|
|
1418
|
+
pre_eval_file_entries + template_unit_file_entries + rbs_descriptor.files +
|
|
1419
|
+
root_presence_entries(expansion)
|
|
1384
1420
|
# #979 — the signature ROOTS' listings, one glob row each. `rbs_descriptor.files` covers only the
|
|
1385
1421
|
# `.rbs` files that existed while the run read them, so without these a signature file written after
|
|
1386
1422
|
# the run left the slot validating fresh and the warm run answered without it.
|
|
1387
|
-
globs = rbs_descriptor.globs + template_unit_glob_entries
|
|
1423
|
+
globs = rbs_descriptor.globs + template_unit_glob_entries + discovery_glob_entries
|
|
1388
1424
|
boundary = plugin_boundary_rows
|
|
1389
1425
|
entries.concat(boundary.files)
|
|
1390
1426
|
globs.concat(boundary.globs).concat(producer_watch_glob_entries)
|
|
1391
1427
|
Cache::Descriptor.new(files: entries, globs: globs.uniq)
|
|
1392
1428
|
end
|
|
1393
1429
|
|
|
1430
|
+
# #1962 / #1555 — a kind row per path the run's answer depends on the presence and kind of, not only its
|
|
1431
|
+
# bytes. An analysis root: its path errors (`no such file or directory`, `not a Ruby file`) answer to whether
|
|
1432
|
+
# it exists and whether it is a directory or a file, while what a directory root holds is the analysed-file
|
|
1433
|
+
# set the key carries. A `pre_eval:` entry: `pre-eval.file-not-found` answers to whether it is a file (one
|
|
1434
|
+
# that is has its `:stat` row from {#pre_eval_file_entries}). A project signature root, configured or the
|
|
1435
|
+
# auto-detected `sig`: the loader reads a directory and a file differently, and #979's listing rows cover a
|
|
1436
|
+
# file appearing under a root that existed, not the root appearing. A kind row rather than an existence row,
|
|
1437
|
+
# because a directory replaced by a file of the same name leaves an existence row fresh. The `--incremental`
|
|
1438
|
+
# slot records kind rows for the same paths ({#incremental_slot_rows}, {#baseline_dependency_rows}, #1977).
|
|
1439
|
+
def root_presence_entries(expansion)
|
|
1440
|
+
(expansion.fetch(:roots) + @configuration.pre_eval + signature_root_candidates)
|
|
1441
|
+
.uniq.map { |path| Cache::Descriptor::FileEntry.kind(path: path) }
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1394
1444
|
# Every file a plugin read or probed through its `IoBoundary` (#577 / #613), and every directory it listed
|
|
1395
1445
|
# (#954), as the boundaries stand when asked.
|
|
1396
1446
|
def plugin_boundary_rows
|
|
@@ -1431,11 +1481,11 @@ module Rigor
|
|
|
1431
1481
|
# globs, which a producer replays into its boundary whether it computed or was served (#1558). A save
|
|
1432
1482
|
# after the read leaves the row stale, so no guard is needed.
|
|
1433
1483
|
# - `derived` — rows taken when the run ENDS: the template files and their claimed globs (#392, re-analysed
|
|
1434
|
-
# every run), and one
|
|
1435
|
-
# such file or directory`, `pre-eval.file-not-found`) answer to those last rows
|
|
1436
|
-
#
|
|
1437
|
-
#
|
|
1438
|
-
#
|
|
1484
|
+
# every run), and one `:kind` row per analysis root and per `pre_eval:` entry. A run's path errors (`no
|
|
1485
|
+
# such file or directory`, `not a Ruby file`, `pre-eval.file-not-found`) answer to those last rows, and the
|
|
1486
|
+
# incremental path regenerates the errors every run, so a slot that served one past the edit that retracts
|
|
1487
|
+
# it would print what no run of that tree prints. A kind row rather than an existence row (#1977): an
|
|
1488
|
+
# existence row reads a directory replaced by a file of the same name as fresh, ADR-45 WD1b's bound.
|
|
1439
1489
|
# - `by_file` — `{path => Descriptor}`, the plugin reads each file's analysis made
|
|
1440
1490
|
# ({Plugin::IoBoundary.attributing}). A narrowed run analyses only its closure, so the session carries the
|
|
1441
1491
|
# other files' reads forward from the previous slot.
|
|
@@ -1459,7 +1509,7 @@ module Rigor
|
|
|
1459
1509
|
IncrementalSlotRows.new(
|
|
1460
1510
|
observed: plugin_boundary_rows,
|
|
1461
1511
|
derived: Cache::Descriptor.new(
|
|
1462
|
-
files: template_unit_file_entries +
|
|
1512
|
+
files: template_unit_file_entries + kind_entries(Array(roots) + @configuration.pre_eval),
|
|
1463
1513
|
globs: template_unit_glob_entries
|
|
1464
1514
|
),
|
|
1465
1515
|
by_file: @analysis_reads.transform_values do |reads|
|
|
@@ -1486,13 +1536,14 @@ module Rigor
|
|
|
1486
1536
|
# which the session therefore carries unchanged from the full run that recorded them to every later slot:
|
|
1487
1537
|
#
|
|
1488
1538
|
# - the signature tree the plain slot records (`RbsDescriptor.file_entries` / `.glob_entries` over the loader
|
|
1489
|
-
# roots this run's analysis resolved), and
|
|
1490
|
-
# auto-detected `sig` — a recheck rebuilds no environment unless its closure is non-empty
|
|
1491
|
-
# fingerprint digests the same roots
|
|
1492
|
-
#
|
|
1539
|
+
# roots this run's analysis resolved), and a `:kind` row per signature root, configured or the
|
|
1540
|
+
# auto-detected `sig` — a recheck rebuilds no environment unless its closure is non-empty. The snapshot
|
|
1541
|
+
# fingerprint digests the same roots, so a change to one makes the next full-path run a full run rather
|
|
1542
|
+
# than a recheck, but the engine-free slot probe serves before any fingerprint is consulted: a root that
|
|
1543
|
+
# turns from a file into a directory (or back) is declined there by this `:kind` row alone (#1977);
|
|
1493
1544
|
# - the discovered-not-analysed files (#684) and a listing row per discovery root — a narrowed recheck does
|
|
1494
1545
|
# not widen discovery at all;
|
|
1495
|
-
# - every `pre_eval:` file outside `files` (a stat row, or
|
|
1546
|
+
# - every `pre_eval:` file outside `files` (a stat row, or a kind row) — the session tracks only analysed
|
|
1496
1547
|
# files, and one that is analysed is guarded by its own row.
|
|
1497
1548
|
#
|
|
1498
1549
|
# Recomputing any of these on a recheck would vouch for the tree the recheck saw while its answer still
|
|
@@ -1516,7 +1567,7 @@ module Rigor
|
|
|
1516
1567
|
own, pinned = signature_rows
|
|
1517
1568
|
BaselineRows.new(
|
|
1518
1569
|
owned: Cache::Descriptor.new(
|
|
1519
|
-
files: own.files +
|
|
1570
|
+
files: own.files + kind_entries(signature_root_candidates) +
|
|
1520
1571
|
discovery_file_entries({ files: files }) + unanalysed_pre_eval_entries(files),
|
|
1521
1572
|
globs: (own.globs + discovery_glob_entries).uniq
|
|
1522
1573
|
),
|
|
@@ -1542,16 +1593,18 @@ module Rigor
|
|
|
1542
1593
|
|
|
1543
1594
|
# The project's own signature roots, whether or not they exist: the configured `signature_paths:`, or the
|
|
1544
1595
|
# `sig` directory `Environment.default_signature_paths` auto-detects when none are configured. The
|
|
1545
|
-
# signature rows' listing rows cover a file appearing under a root that existed; only
|
|
1546
|
-
# covers
|
|
1596
|
+
# signature rows' listing rows cover a file appearing under a root that existed; only a row for the root
|
|
1597
|
+
# itself covers it appearing.
|
|
1547
1598
|
def signature_root_candidates
|
|
1548
1599
|
@configuration.signature_paths || ["sig"]
|
|
1549
1600
|
end
|
|
1550
1601
|
|
|
1551
1602
|
# One names-mode listing row per directory the discovery widening globbed, so a file appearing there is seen
|
|
1552
|
-
# as well as an edit to one that existed.
|
|
1603
|
+
# as well as an edit to one that existed. Issue #1583 — keyed on the widening decision, not on what it
|
|
1604
|
+
# added: a run over `lib/b.rb` alone in `lib` widens to nothing, and a `lib/a.rb` created afterwards must
|
|
1605
|
+
# still move the answer of the run-result cache and the `--incremental` slot.
|
|
1553
1606
|
def discovery_glob_entries
|
|
1554
|
-
return []
|
|
1607
|
+
return [] unless @discovery_widened
|
|
1555
1608
|
|
|
1556
1609
|
@configuration.paths.select { |root| File.directory?(root) }.map do |root|
|
|
1557
1610
|
Cache::Descriptor::GlobEntry.compute(root: root, pattern: PathExpansion::RUBY_GLOB, mode: :names)
|
|
@@ -1565,22 +1618,18 @@ module Rigor
|
|
|
1565
1618
|
if File.file?(path)
|
|
1566
1619
|
Cache::Descriptor::FileEntry.stat(path: path, digest: Cache::FileDigest.hexdigest(path))
|
|
1567
1620
|
else
|
|
1568
|
-
|
|
1621
|
+
kind_entries([path]).first
|
|
1569
1622
|
end
|
|
1570
1623
|
end
|
|
1571
1624
|
end
|
|
1572
1625
|
|
|
1573
|
-
|
|
1626
|
+
# #1977 — one `:kind` row per path, the row {#root_presence_entries} records in the plain slot.
|
|
1627
|
+
def kind_entries(paths)
|
|
1574
1628
|
paths.uniq.map do |path|
|
|
1575
|
-
|
|
1576
|
-
if File.exist?(physical)
|
|
1577
|
-
Cache::Descriptor::FileEntry.present(path: physical)
|
|
1578
|
-
else
|
|
1579
|
-
Cache::Descriptor::FileEntry.absent(path: physical)
|
|
1580
|
-
end
|
|
1629
|
+
Cache::Descriptor::FileEntry.kind(path: @buffer ? @buffer.resolve(path) : path)
|
|
1581
1630
|
end
|
|
1582
1631
|
end
|
|
1583
|
-
private :plugin_boundary_rows, :producer_watch_glob_entries, :
|
|
1632
|
+
private :plugin_boundary_rows, :producer_watch_glob_entries, :kind_entries, :root_presence_entries,
|
|
1584
1633
|
:analysis_reads_attributed?, :signature_root_candidates, :discovery_glob_entries,
|
|
1585
1634
|
:unanalysed_pre_eval_entries, :signature_rows, :within?
|
|
1586
1635
|
|
|
@@ -1804,8 +1853,14 @@ module Rigor
|
|
|
1804
1853
|
# on what else was on the command line.
|
|
1805
1854
|
#
|
|
1806
1855
|
# Excluded, each because its expansion is already the one it means: the LSP `prebuilt:` path (which
|
|
1807
|
-
# seeds discovery from a snapshot), editor `buffer:` mode, `run_source`'s in-memory single file
|
|
1808
|
-
#
|
|
1856
|
+
# seeds discovery from a snapshot), editor `buffer:` mode, and `run_source`'s in-memory single file.
|
|
1857
|
+
#
|
|
1858
|
+
# Issue #1583 — ADR-46's `analyze_only` subset mode widens like a full run. It was excluded on the
|
|
1859
|
+
# reading that it "already passes the full expansion", which holds only when the incremental session
|
|
1860
|
+
# runs over the configured `paths:`. `rigor check --incremental lib/b.rb` passes the argument's
|
|
1861
|
+
# expansion, so its rechecks (and `--verify-incremental`'s partitions) discovered the argument alone
|
|
1862
|
+
# while its baseline discovered the project, and `lib/b.rb`'s report on a sibling's constant came and
|
|
1863
|
+
# went with the run kind.
|
|
1809
1864
|
#
|
|
1810
1865
|
# Issue #1556 — the widened set holds each FILE once. `.rigor.yml`'s `paths:` load as absolute paths and a
|
|
1811
1866
|
# path argument usually does not, so `rigor check lib` widened discovery over both spellings of every file:
|
|
@@ -1843,8 +1898,7 @@ module Rigor
|
|
|
1843
1898
|
end
|
|
1844
1899
|
|
|
1845
1900
|
def widen_discovery_to_project?(paths)
|
|
1846
|
-
@prebuilt.nil? && @buffer.nil? && @in_memory_sources.nil? && @
|
|
1847
|
-
paths != @configuration.paths
|
|
1901
|
+
@prebuilt.nil? && @buffer.nil? && @in_memory_sources.nil? && paths != @configuration.paths
|
|
1848
1902
|
end
|
|
1849
1903
|
|
|
1850
1904
|
# ADR-46 — the dependency-recording and subset-analysis modes read the discovery tables OUTSIDE the
|
|
@@ -1907,8 +1961,10 @@ module Rigor
|
|
|
1907
1961
|
# The run's per-file effect collections in sorted path order — the sequential half merged with the
|
|
1908
1962
|
# pool's, exactly as {#file_dependencies} reconciles its two halves. Sorted, because the fold below
|
|
1909
1963
|
# it must not depend on pool-completion order.
|
|
1964
|
+
# Issue #1898 — a narrowed incremental run adds the collections its session served for the files it did not
|
|
1965
|
+
# analyse, under this run's own for any path both name.
|
|
1910
1966
|
def effect_collections
|
|
1911
|
-
effect_collections_by_path.sort_by { |path, _| path.to_s }.map(&:last)
|
|
1967
|
+
@served_effect_collections.merge(effect_collections_by_path).sort_by { |path, _| path.to_s }.map(&:last)
|
|
1912
1968
|
end
|
|
1913
1969
|
|
|
1914
1970
|
# Editor mode § "Scope choice — option A". Under `buffer:` non-nil the per-file analysis emits
|
|
@@ -2024,7 +2080,8 @@ module Rigor
|
|
|
2024
2080
|
analyze_file: ->(path, environment) { analyze_file(path, environment) },
|
|
2025
2081
|
restored_run_level_rows: @restored_run_level_rows,
|
|
2026
2082
|
feature_sources: -> { required_feature_sources },
|
|
2027
|
-
plugin_prepare: -> { @pre_passes.prepare_throwaway_registry }
|
|
2083
|
+
plugin_prepare: -> { @pre_passes.prepare_throwaway_registry },
|
|
2084
|
+
prepare_coordinator_plugins: -> { @pre_passes.prepare_registry(@plugin_registry) }
|
|
2028
2085
|
)
|
|
2029
2086
|
@diagnostic_aggregator = DiagnosticAggregator.new(
|
|
2030
2087
|
configuration: @configuration,
|
|
@@ -2044,7 +2101,8 @@ module Rigor
|
|
|
2044
2101
|
definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
|
|
2045
2102
|
hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
|
|
2046
2103
|
conformance_results_snapshot: -> { @snapshots.conformance_results },
|
|
2047
|
-
pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures }
|
|
2104
|
+
pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures },
|
|
2105
|
+
pooled_trust_refusals: -> { @pool_coordinator.collected_trust_refusals }
|
|
2048
2106
|
)
|
|
2049
2107
|
end
|
|
2050
2108
|
|
|
@@ -2197,7 +2255,7 @@ module Rigor
|
|
|
2197
2255
|
bad << [path, "no such file or directory"]
|
|
2198
2256
|
end
|
|
2199
2257
|
end
|
|
2200
|
-
{ files: files, errors: path_expansion_errors(bad, any_files: files.any?) }
|
|
2258
|
+
{ files: files, roots: Array(paths).map(&:to_s), errors: path_expansion_errors(bad, any_files: files.any?) }
|
|
2201
2259
|
end
|
|
2202
2260
|
|
|
2203
2261
|
# A bad path *among valid ones* is a warn-and-skip — the run still does useful work, so `rigor check
|
|
@@ -2400,50 +2458,21 @@ module Rigor
|
|
|
2400
2458
|
tables[:published_constant_names] = names unless names.empty?
|
|
2401
2459
|
end
|
|
2402
2460
|
|
|
2403
|
-
# Issue #617 — the
|
|
2404
|
-
#
|
|
2405
|
-
# segment span two files: either may load first and set the constant the other then reads, so only a
|
|
2406
|
-
# memo no other file shares keeps the idiom's reading. Files are counted expanded, because discovery can
|
|
2407
|
-
# walk one file under two spellings (`lib/b.rb`, `./lib/b.rb`).
|
|
2461
|
+
# Issue #617 / #1290 — the binding and shadowing constant names, by last segment; derived by
|
|
2462
|
+
# {Inference::ProjectSeedTables}, which the seed paths that run no `check` share.
|
|
2408
2463
|
def constant_writer_index
|
|
2409
|
-
@constant_writer_index ||=
|
|
2410
|
-
memo_files = memo_files_by_segment
|
|
2411
|
-
@project_constant_writes.each_with_object({}) do |(name, by_path), index|
|
|
2412
|
-
segment = name.split("::").last
|
|
2413
|
-
next if memo_only?(by_path) && memo_files.fetch(segment).size < 2
|
|
2414
|
-
|
|
2415
|
-
(index[segment] ||= []) << name
|
|
2416
|
-
end.each_value(&:freeze).freeze
|
|
2417
|
-
end
|
|
2464
|
+
@constant_writer_index ||= Inference::ProjectSeedTables.constant_writers(@project_constant_writes)
|
|
2418
2465
|
end
|
|
2419
2466
|
|
|
2420
|
-
# Issue #1290 — the censused names some write other than a memo `||=` assigns, grouped by last segment
|
|
2421
|
-
# (`Scope#shadowing_constant_names`): the ones that exist whether or not an outer constant of the name
|
|
2422
|
-
# does, and so stop the lexical ladder above it. A memo-only name is left out however many files memoize
|
|
2423
|
-
# it, unlike in {#constant_writer_index}: `X ||= v` resolves `X` through the very lookup whose lower rung
|
|
2424
|
-
# the stop would replace, so wherever that rung answers, the memo finds it and never creates the name.
|
|
2425
2467
|
def constant_shadower_index
|
|
2426
|
-
@constant_shadower_index ||= @project_constant_writes
|
|
2427
|
-
(index[name.split("::").last] ||= []) << name unless memo_only?(by_path)
|
|
2428
|
-
end.each_value(&:freeze).freeze
|
|
2468
|
+
@constant_shadower_index ||= Inference::ProjectSeedTables.constant_shadowers(@project_constant_writes)
|
|
2429
2469
|
end
|
|
2430
2470
|
|
|
2431
|
-
def memo_files_by_segment
|
|
2432
|
-
@project_constant_writes.each_with_object({}) do |(name, by_path), files|
|
|
2433
|
-
set = (files[name.split("::").last] ||= Set.new)
|
|
2434
|
-
by_path.each { |path, descriptor| set << File.absolute_path(path) if memo_descriptor?(descriptor) }
|
|
2435
|
-
end
|
|
2436
|
-
end
|
|
2437
|
-
|
|
2438
|
-
def memo_only?(by_path) = by_path.each_value.all? { |descriptor| memo_descriptor?(descriptor) }
|
|
2439
|
-
def memo_descriptor?(descriptor) = descriptor == Inference::ScopeIndexer::CONSTANT_MEMO
|
|
2440
|
-
|
|
2441
2471
|
# Issue #644 — the LAST SEGMENTS of the published table, the run-wide half of
|
|
2442
2472
|
# `Scope#published_constant?`. Seeded on EVERY run (unlike `constant_sources`, which only a recording
|
|
2443
2473
|
# run needs) because {CheckRules::PublishedConstantGuard} asks the question on every `if` / `unless`.
|
|
2444
2474
|
def published_constant_name_set
|
|
2445
|
-
@published_constant_name_set ||=
|
|
2446
|
-
@project_constant_values.keys.to_set { |name| name.split("::").last }.freeze
|
|
2475
|
+
@published_constant_name_set ||= Inference::ProjectSeedTables.published_constant_names(@project_constant_values)
|
|
2447
2476
|
end
|
|
2448
2477
|
|
|
2449
2478
|
# Issue #644 — the seeded `in_source_constants` table, joining the two cross-file constant producers.
|
|
@@ -2654,7 +2683,8 @@ module Rigor
|
|
|
2654
2683
|
node_collectors: node_collectors,
|
|
2655
2684
|
stated_runtime_ruby: @configuration.stated_runtime_ruby
|
|
2656
2685
|
)
|
|
2657
|
-
|
|
2686
|
+
plugin_rows = plugin_emitted_diagnostics(path, root, scope, node_results)
|
|
2687
|
+
diagnostics + CheckRules.filter_plugin_suppressed(plugin_rows, path: path, comments: parse_result.comments)
|
|
2658
2688
|
end
|
|
2659
2689
|
|
|
2660
2690
|
# ADR-24 slice 4a — runs `block` (the typing pass) with the self-call recorder active when either
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "check_rules/rule_ids"
|
|
3
4
|
require_relative "diagnostic"
|
|
4
5
|
require_relative "../configuration/severity_profile"
|
|
5
6
|
|
|
@@ -9,14 +10,55 @@ module Rigor
|
|
|
9
10
|
# overrides and drops any diagnostic resolving to `:off`. Rules emit with their authored severity; the
|
|
10
11
|
# profile is the final filter.
|
|
11
12
|
#
|
|
13
|
+
# Issue #1895 — the `.rigor.yml` `disable:` list is applied here too, ahead of the profile, with the same
|
|
14
|
+
# token expansion and match per-file analysis uses ({CheckRules.config_disabled?}), but only to a built-in
|
|
15
|
+
# check-rule row (see {.check_rule_row?}): the rows the diagnostic policy already says `disable:` reaches. Most
|
|
16
|
+
# are filtered where they were produced; this catches the ones that are not (the `effect.annotations-unchecked`
|
|
17
|
+
# residual under `disable: [all]`) on every run shape (sequential, pool, `--incremental`, a warm cache hit).
|
|
18
|
+
# Issue #1953 — a plugin's `plugin.<id>.*` row answers here to its qualified id (`plugin.<id>.<rule>`) or to
|
|
19
|
+
# `plugin.<id>`, never to a bare id or `all`. The run-level rows outside the check-rule catalogue
|
|
20
|
+
# (`rbs.coverage.*`, `dynamic.dependency-source.*`, the plugin loader, `plugin_trust.*`, `pool-degraded`) keep
|
|
21
|
+
# answering to `severity_overrides:` only, by any spelling.
|
|
22
|
+
# Issue #1985 — `severity_overrides:` follows the same rule: a plugin's row answers to its qualified id or
|
|
23
|
+
# `plugin.<id>`, never to its bare rule or a family wildcard; the plugin loader's rows answer to
|
|
24
|
+
# `plugin_loader.<rule>` first and to their bare `load-error` / `runtime-error` after it.
|
|
25
|
+
#
|
|
12
26
|
# Extracted (ADR-87 WD4) so the miss path ({Runner::DiagnosticAggregator}) and the boot-slimming hit path
|
|
13
27
|
# ({RunCacheProbe}, which never loads the aggregator) apply the IDENTICAL final filter — a warm HIT's
|
|
14
28
|
# served diagnostics are byte-identical to what a full run would print.
|
|
15
29
|
module SeverityStamp
|
|
16
30
|
module_function
|
|
17
31
|
|
|
32
|
+
CHECK_RULES = CheckRules::ALL_RULES.to_set.freeze
|
|
33
|
+
private_constant :CHECK_RULES
|
|
34
|
+
|
|
18
35
|
def apply(diagnostics, configuration)
|
|
19
|
-
|
|
36
|
+
disabled = CheckRules.expand_rule_tokens(configuration.disabled_rules)
|
|
37
|
+
diagnostics.filter_map do |diagnostic|
|
|
38
|
+
next nil if config_disabled_row?(diagnostic, disabled)
|
|
39
|
+
|
|
40
|
+
stamp(diagnostic, configuration)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A built-in check-rule row answers to the expanded list by its id or `all`; a plugin's `plugin.<id>.*` row
|
|
45
|
+
# by its qualified id or `plugin.<id>` only ({CheckRules.plugin_row_disabled?}, issue #1953). Every other
|
|
46
|
+
# row is out of the list's reach.
|
|
47
|
+
def config_disabled_row?(diagnostic, disabled)
|
|
48
|
+
return CheckRules.config_disabled?(diagnostic.rule, disabled) if check_rule_row?(diagnostic)
|
|
49
|
+
|
|
50
|
+
CheckRules.plugin_row_disabled?(diagnostic, disabled)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A built-in check-rule row: its id is in the CheckRules catalogue and no plugin emitted it.
|
|
54
|
+
def check_rule_row?(diagnostic)
|
|
55
|
+
diagnostic.source_family == Diagnostic::DEFAULT_SOURCE_FAMILY && CHECK_RULES.include?(diagnostic.rule)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# `plugin.<id>` for a row a plugin emitted, nil for every other row.
|
|
59
|
+
def plugin_family(diagnostic)
|
|
60
|
+
family = diagnostic.source_family.to_s
|
|
61
|
+
family if family.start_with?(CheckRules::PLUGIN_FAMILY_PREFIX)
|
|
20
62
|
end
|
|
21
63
|
|
|
22
64
|
def stamp(diagnostic, configuration)
|
|
@@ -24,6 +66,8 @@ module Rigor
|
|
|
24
66
|
|
|
25
67
|
resolved = Configuration::SeverityProfile.resolve(
|
|
26
68
|
rule: diagnostic.rule,
|
|
69
|
+
qualified_rule: diagnostic.qualified_rule,
|
|
70
|
+
plugin_family: plugin_family(diagnostic),
|
|
27
71
|
authored_severity: diagnostic.severity,
|
|
28
72
|
profile: configuration.severity_profile,
|
|
29
73
|
overrides: configuration.severity_overrides,
|
|
@@ -170,8 +170,13 @@ module Rigor
|
|
|
170
170
|
return analyze_with_effects(path) unless @record_dependencies
|
|
171
171
|
|
|
172
172
|
diagnostics = nil
|
|
173
|
-
|
|
173
|
+
reads = []
|
|
174
|
+
record = Plugin::IoBoundary.attributing(reads) do
|
|
175
|
+
DependencyRecorder.record_for(path) { diagnostics = analyze_with_effects(path) }
|
|
176
|
+
end
|
|
174
177
|
@file_dependencies[path] = record
|
|
178
|
+
# Issue #1553 — the plugin reads this file's analysis made, as `Runner#analyze_file` keeps them.
|
|
179
|
+
(@analysis_reads ||= {})[path] = reads unless reads.empty?
|
|
175
180
|
diagnostics
|
|
176
181
|
end
|
|
177
182
|
|
|
@@ -241,7 +246,8 @@ module Rigor
|
|
|
241
246
|
node_collectors: node_collectors,
|
|
242
247
|
stated_runtime_ruby: @configuration.stated_runtime_ruby
|
|
243
248
|
)
|
|
244
|
-
|
|
249
|
+
plugin_rows = plugin_emitted_diagnostics(path, root, scope, node_results)
|
|
250
|
+
diagnostics += CheckRules.filter_plugin_suppressed(plugin_rows, path: path, comments: parse_result.comments)
|
|
245
251
|
diagnostics + explain_diagnostics(path, parse_result.value, scope)
|
|
246
252
|
rescue Errno::ENOENT => e
|
|
247
253
|
[analyzer_error(path, e.message)]
|
|
@@ -308,6 +314,12 @@ module Rigor
|
|
|
308
314
|
@file_dependencies
|
|
309
315
|
end
|
|
310
316
|
|
|
317
|
+
# Issue #1553 — the plugin reads ({Plugin::IoBoundary.attributing}) each `#analyze` call's file made (empty
|
|
318
|
+
# unless `record_dependencies:` was set). The rows are value objects of frozen Strings, so they marshal back.
|
|
319
|
+
def drain_analysis_reads
|
|
320
|
+
@analysis_reads || {}
|
|
321
|
+
end
|
|
322
|
+
|
|
311
323
|
# ADR-103 WD12 — the per-file {Effects::FileCollection}s this session collected (empty unless the
|
|
312
324
|
# configuration carries an `effects:` block). Marshal-clean by construction — frozen Hashes of
|
|
313
325
|
# Strings, `LabelSet`s of frozen Arrays, and `Data` edges — so the fork pool ships them back to the
|
|
@@ -336,6 +348,45 @@ module Rigor
|
|
|
336
348
|
end
|
|
337
349
|
end
|
|
338
350
|
|
|
351
|
+
# Issue #1900 — `{plugin id => refused-read count}` over `registry`'s boundaries right now: the baseline
|
|
352
|
+
# a later drain subtracts, so it reports only the refusals made after the baseline was taken.
|
|
353
|
+
def self.refusal_counts(registry)
|
|
354
|
+
registry.plugins.to_h do |plugin|
|
|
355
|
+
summary = begin
|
|
356
|
+
plugin.io_boundary.refusal_summary
|
|
357
|
+
rescue StandardError
|
|
358
|
+
nil
|
|
359
|
+
end
|
|
360
|
+
[safe_plugin_id(plugin), summary ? summary[:count] : 0]
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def self.safe_plugin_id(plugin)
|
|
365
|
+
plugin.manifest.id
|
|
366
|
+
rescue StandardError
|
|
367
|
+
plugin.class.to_s
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Issue #1900 — the {Plugin::IoBoundary} trust-policy refusals this session's plugin instances made
|
|
371
|
+
# since the previous drain, one `{plugin_id:, count:, first_path:, nearest_root:}` per plugin that hit
|
|
372
|
+
# any, in registry order. Unlike {#drain_run_disclosures} each drain reports only what is new — the
|
|
373
|
+
# boundary keeps its running total and this session remembers how much of it was already reported —
|
|
374
|
+
# because the coordinator SUMS the drains: a fork child inherits the parent's `#prepare` refusals,
|
|
375
|
+
# and reporting them again would count them once per worker. `first_path` is the boundary's first
|
|
376
|
+
# refusal overall, which is the delta's own first path whenever nothing was reported before it, and
|
|
377
|
+
# the coordinator keeps the first-merged path otherwise. It is not baselined after `#init`, so a plugin
|
|
378
|
+
# whose `#init` refuses differently here than on the coordinator registry can have this session's
|
|
379
|
+
# `#init` path named. Marshal-clean and Ractor-copyable.
|
|
380
|
+
def drain_trust_refusals
|
|
381
|
+
return [] if @plugin_registry.empty?
|
|
382
|
+
|
|
383
|
+
@plugin_registry.plugins.filter_map do |plugin|
|
|
384
|
+
trust_refusal_delta(plugin)
|
|
385
|
+
rescue StandardError
|
|
386
|
+
nil
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
339
390
|
private
|
|
340
391
|
|
|
341
392
|
# Mirrors {Runner#seed_project_scope}: applies the cross-file pre-pass discovery tables the
|
|
@@ -402,6 +453,10 @@ module Rigor
|
|
|
402
453
|
def run_plugin_prepare
|
|
403
454
|
return [] if @plugin_registry.empty?
|
|
404
455
|
|
|
456
|
+
# Issue #1900 — per plugin id, how much of its boundary's count is already reported. Starts at what
|
|
457
|
+
# `#init` refused while the registry was materialised: `#init` runs on the coordinator-side registry
|
|
458
|
+
# too, which reports those reads itself, so counting them here as well would double them.
|
|
459
|
+
@reported_refusal_counts = self.class.refusal_counts(@plugin_registry)
|
|
405
460
|
@plugin_registry.plugins.flat_map do |plugin|
|
|
406
461
|
plugin.prepare(plugin.services)
|
|
407
462
|
[]
|
|
@@ -490,6 +545,18 @@ module Rigor
|
|
|
490
545
|
)
|
|
491
546
|
end
|
|
492
547
|
|
|
548
|
+
def trust_refusal_delta(plugin)
|
|
549
|
+
summary = plugin.io_boundary.refusal_summary
|
|
550
|
+
return nil if summary.nil?
|
|
551
|
+
|
|
552
|
+
plugin_id = safe_plugin_id(plugin)
|
|
553
|
+
delta = summary[:count] - @reported_refusal_counts.fetch(plugin_id, 0)
|
|
554
|
+
return nil unless delta.positive?
|
|
555
|
+
|
|
556
|
+
@reported_refusal_counts[plugin_id] = summary[:count]
|
|
557
|
+
summary.merge(count: delta, plugin_id: plugin_id)
|
|
558
|
+
end
|
|
559
|
+
|
|
493
560
|
def safe_plugin_id(plugin)
|
|
494
561
|
plugin.manifest.id
|
|
495
562
|
rescue StandardError
|