rigortype 0.4.4 → 0.4.6
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/docs/handbook/11-sig-gen.md +100 -18
- data/docs/manual/02-cli-reference.md +85 -16
- data/docs/manual/03-configuration.md +12 -9
- data/docs/manual/04-diagnostics.md +17 -6
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +2 -1
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +17 -2
- data/docs/manual/12-caching.md +8 -1
- data/docs/manual/18-removing-dead-code.md +18 -7
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +30 -0
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +102 -1
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +30 -9
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +142 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +115 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +253 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +64 -4
- data/lib/rigor/analysis/check_rules.rb +319 -21
- data/lib/rigor/analysis/dependency_recorder.rb +11 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/edit_replay.rb +67 -0
- data/lib/rigor/analysis/incremental.rb +86 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +336 -46
- data/lib/rigor/analysis/path_expansion.rb +44 -1
- data/lib/rigor/analysis/reachability/graph.rb +90 -13
- data/lib/rigor/analysis/reachability/scan.rb +279 -39
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +22 -5
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +11 -3
- data/lib/rigor/analysis/runner/pool_coordinator.rb +82 -16
- data/lib/rigor/analysis/runner/project_pre_passes.rb +45 -3
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner.rb +222 -111
- data/lib/rigor/analysis/severity_stamp.rb +11 -0
- data/lib/rigor/analysis/worker_session.rb +24 -1
- data/lib/rigor/cache/descriptor.rb +40 -7
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +49 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/check_command.rb +93 -4
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/diff_command.rb +2 -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/lens_command.rb +5 -5
- data/lib/rigor/cli/mcp_command.rb +2 -1
- data/lib/rigor/cli/options.rb +20 -3
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +7 -1
- data/lib/rigor/cli/plugins_renderer.rb +24 -2
- data/lib/rigor/cli/show_bleedingedge_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +73 -13
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/type_of_project_scope.rb +27 -0
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +72 -16
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +43 -7
- 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 +136 -7
- data/lib/rigor/environment/required_features.rb +136 -29
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment.rb +4 -2
- data/lib/rigor/inference/block_call_timing.rb +7 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/callee_mutation_chain.rb +115 -0
- data/lib/rigor/inference/captured_locals.rb +44 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/deep_erasure.rb +138 -0
- data/lib/rigor/inference/def_return_typer.rb +159 -9
- data/lib/rigor/inference/definer_resolution.rb +101 -20
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +97 -18
- data/lib/rigor/inference/global_write_census.rb +36 -1
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/ivar_writer_census.rb +195 -0
- data/lib/rigor/inference/loop_value_record.rb +53 -0
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +278 -42
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
- data/lib/rigor/inference/method_dispatcher.rb +113 -19
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +149 -42
- data/lib/rigor/inference/mutation_widening.rb +5 -2
- data/lib/rigor/inference/narrowing.rb +116 -11
- data/lib/rigor/inference/object_mixins.rb +13 -1
- 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 +121 -0
- data/lib/rigor/inference/project_yielder.rb +128 -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 +6 -6
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +162 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +1144 -197
- data/lib/rigor/inference/self_call_ivar_writes.rb +29 -12
- 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 +1088 -213
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +72 -12
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/language_server/buffer_table.rb +6 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +20 -6
- data/lib/rigor/language_server/project_context.rb +59 -3
- data/lib/rigor/language_server/server.rb +5 -3
- data/lib/rigor/lens/contract_reader.rb +45 -2
- data/lib/rigor/lens/document.rb +2 -1
- data/lib/rigor/lens/file_reader.rb +12 -2
- data/lib/rigor/lens/members.rb +16 -2
- data/lib/rigor/lens/run.rb +34 -7
- data/lib/rigor/mcp/server.rb +11 -3
- data/lib/rigor/plugin/base.rb +29 -13
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/plugin/manifest.rb +29 -3
- data/lib/rigor/plugin/registry.rb +38 -0
- data/lib/rigor/protection/discovery_seed.rb +64 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +36 -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 +59 -7
- 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 +465 -49
- data/lib/rigor/sig_gen/layout_index.rb +5 -2
- data/lib/rigor/sig_gen/observation_collector.rb +141 -12
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +16 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- 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 +5 -3
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/combinator.rb +4 -3
- data/lib/rigor/type/constant.rb +54 -3
- data/lib/rigor/type/hash_shape.rb +14 -9
- data/lib/rigor/type/struct_instance.rb +3 -2
- 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 +61 -44
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -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-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
- 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-activejob/lib/rigor/plugin/activejob.rb +4 -2
- 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-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -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 +102 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -2
- 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/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +3 -2
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +6 -0
- data/sig/rigor/plugin/additional_initializer.rbs +5 -5
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +10 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +34 -52
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +32 -1
|
@@ -12,6 +12,8 @@ require_relative "../cache/file_digest"
|
|
|
12
12
|
require_relative "../cache/incremental_snapshot"
|
|
13
13
|
require_relative "run_cache_key"
|
|
14
14
|
require_relative "path_expansion"
|
|
15
|
+
require_relative "require_relative_closure"
|
|
16
|
+
require_relative "run_disclosure_credit"
|
|
15
17
|
require_relative "../plugin"
|
|
16
18
|
require_relative "../plugin/source_rbs_synthesis_reporter"
|
|
17
19
|
require_relative "../rbs_extended/reporter"
|
|
@@ -26,6 +28,7 @@ require_relative "../effects/identity"
|
|
|
26
28
|
require_relative "../effects/propagator"
|
|
27
29
|
require_relative "../inference/parameter_inference_collector"
|
|
28
30
|
require_relative "../inference/pre_eval_constants"
|
|
31
|
+
require_relative "../inference/project_seed_tables"
|
|
29
32
|
require_relative "../inference/scope_indexer"
|
|
30
33
|
require_relative "../inference/synthetic_method_scanner"
|
|
31
34
|
require_relative "../inference/project_patched_scanner"
|
|
@@ -74,7 +77,10 @@ module Rigor
|
|
|
74
77
|
# this and serves reused files from it without re-stamping: a run-level row is regenerated
|
|
75
78
|
# every run wherever it is positioned, and slicing the run's full stream by path cached the
|
|
76
79
|
# file-positioned ones (`effect.annotations-unchecked`, `source-rbs-*`).
|
|
77
|
-
:per_file_diagnostics
|
|
80
|
+
:per_file_diagnostics,
|
|
81
|
+
# PR #2286 review M1 — the ADR-16 index this run's pre-pass built; `IncrementalSession` digests it
|
|
82
|
+
# to tell when a class-body DSL edit moved what a served file's dispatch read.
|
|
83
|
+
:synthetic_method_index
|
|
78
84
|
|
|
79
85
|
# Issues #796 / #794 — the two run-level rows a NARROWED run cannot re-derive from the files it
|
|
80
86
|
# analyses: the `definition-build-failed` set (whose producer is the analysis's own demand, and #696
|
|
@@ -98,6 +104,23 @@ module Rigor
|
|
|
98
104
|
pooled.empty? ? @file_dependencies : @file_dependencies.merge(pooled)
|
|
99
105
|
end
|
|
100
106
|
|
|
107
|
+
# Issue #1553 — the plugin reads ({Plugin::IoBoundary.attributing}) each file's analysis made this run,
|
|
108
|
+
# `{path => [row]}`, reconciled from the sequential and pooled halves exactly as {#file_dependencies} is.
|
|
109
|
+
# Empty unless `record_dependencies: true`; a file whose analysis read nothing has no key. The incremental
|
|
110
|
+
# session keeps them beside each file's cached rows so a later recheck re-analyses the file once one of
|
|
111
|
+
# them goes stale.
|
|
112
|
+
def analysis_reads
|
|
113
|
+
pooled = @pool_coordinator.collected_analysis_reads
|
|
114
|
+
pooled.empty? ? @analysis_reads : @analysis_reads.merge(pooled)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Issue #2020 — `{path => [run-disclosure record]}`, the run-scoped rows each analysed file's plugin hooks
|
|
118
|
+
# registered ({RunDisclosureCredit}), sequential and pooled alike; empty unless `record_dependencies:`.
|
|
119
|
+
def analysis_disclosures
|
|
120
|
+
pooled = @pool_coordinator.collected_analysis_disclosures
|
|
121
|
+
pooled.empty? ? @analysis_disclosures : @analysis_disclosures.merge(pooled)
|
|
122
|
+
end
|
|
123
|
+
|
|
101
124
|
# ADR-103 — the run's propagated effect graph, or {Effects::EffectTable.empty} when collection did
|
|
102
125
|
# not run. Reconciled from the sequential and pooled halves exactly as {#file_dependencies} is, then
|
|
103
126
|
# closed by the post-pool fixpoint in {#assemble_run_diagnostics}.
|
|
@@ -283,10 +306,11 @@ module Rigor
|
|
|
283
306
|
# worker continues to build its own Environment.
|
|
284
307
|
# @param discovery_seed — issue #260 — opt-in cross-file discovery tables, keyed by
|
|
285
308
|
# {Scope::DiscoveryIndex} slot name, seeded onto every per-file scope through
|
|
286
|
-
# `project_scope_seed_tables`.
|
|
287
|
-
#
|
|
288
|
-
# judges anchors against,
|
|
289
|
-
#
|
|
309
|
+
# `project_scope_seed_tables`. A `prebuilt:` runner computes no discovery tables of its own, so this is
|
|
310
|
+
# how one learns the project: {Protection::DiagnosticOracle} threads the table set Tier 2's site filter
|
|
311
|
+
# already judges anchors against, and (issue #2147, ADR-124) the LSP's per-buffer publish threads
|
|
312
|
+
# `LanguageServer::ProjectContext#discovery_seed_for`, so a class another file declares is not an
|
|
313
|
+
# external ancestor there. nil (the default) seeds nothing.
|
|
290
314
|
# @param no_tolerated_effects — ADR-103 WD1 / #385 — `rigor check --no-tolerated-effects`.
|
|
291
315
|
# Judges effect envelopes as if `effects.tolerated:` were empty. A judgment-time switch only: the
|
|
292
316
|
# run, its collection and its cache identity are unchanged.
|
|
@@ -298,7 +322,7 @@ module Rigor
|
|
|
298
322
|
seed_bundles: nil, collect_seed_bundles: false, param_inferred_types: nil,
|
|
299
323
|
discovery_seed: nil, no_tolerated_effects: false,
|
|
300
324
|
restored_run_level_rows: nil, served_per_file_diagnostics: nil, served_effect_collections: nil,
|
|
301
|
-
on_file_indexed: nil)
|
|
325
|
+
served_run_disclosures: nil, on_file_indexed: nil)
|
|
302
326
|
@configuration = configuration
|
|
303
327
|
# ADR-113 WD2 — `rigor lens` reads a target's seeded index here: called with `(path, parse_result, scope,
|
|
304
328
|
# index)` for every file this process indexes, after its rules ran and inside the run window, so the reader
|
|
@@ -316,6 +340,9 @@ module Rigor
|
|
|
316
340
|
# effect graph this run closes (and the envelope pass judges) spans the whole project, not just the closure.
|
|
317
341
|
# Never part of {#effect_collections_by_path}, which stays what THIS run collected. Empty on every other run.
|
|
318
342
|
@served_effect_collections = served_effect_collections || {}
|
|
343
|
+
# Issue #2020 — the run-scoped plugin rows ({RunDisclosureCredit}) those unanalysed files registered when
|
|
344
|
+
# they were last analysed, emitted beside this run's own registrations. Empty on every other run.
|
|
345
|
+
@served_run_disclosures = served_run_disclosures || []
|
|
319
346
|
@explain = explain
|
|
320
347
|
@cache_store = enforce_read_only_cache(cache_store, buffer)
|
|
321
348
|
@plugin_requirer = plugin_requirer
|
|
@@ -371,6 +398,7 @@ module Rigor
|
|
|
371
398
|
# rows as they stood around the per-file loop; see {#incremental_slot_rows}. Recorded only by a
|
|
372
399
|
# dependency-recording (incremental) run.
|
|
373
400
|
@analysis_reads = {}
|
|
401
|
+
@analysis_disclosures = {}
|
|
374
402
|
@pre_analysis_plugin_rows = nil
|
|
375
403
|
@post_analysis_plugin_rows = nil
|
|
376
404
|
# Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed).
|
|
@@ -464,6 +492,9 @@ module Rigor
|
|
|
464
492
|
@project_struct_member_layouts = {}.freeze
|
|
465
493
|
# Issue #684 — set per run by {#project_discovery_expansion}; nil on every path that does not widen.
|
|
466
494
|
@project_discovery_expansion = nil
|
|
495
|
+
# Issue #1583 — whether this run widened its discovery to the configured paths, even when the widening
|
|
496
|
+
# added no file; {#discovery_glob_entries} lists the configured roots for every such run.
|
|
497
|
+
@discovery_widened = false
|
|
467
498
|
# ADR-67 WD6a — the call-site parameter-inference table, populated by the opt-in pre-pass in
|
|
468
499
|
# `assemble_run_diagnostics` when `parameter_inference:` is enabled, then seeded onto every per-file
|
|
469
500
|
# scope (sequential + fork-worker) through `project_scope_seed_tables`. Empty by default, so the gate-off
|
|
@@ -507,6 +538,16 @@ module Rigor
|
|
|
507
538
|
end
|
|
508
539
|
end
|
|
509
540
|
|
|
541
|
+
# ADR-45 WD2 (#1507) / #2020 — the per-run reset of what each file's analysis is credited with: its plugin
|
|
542
|
+
# reads, its run-scoped plugin registrations, and the boundary rows around the per-file loop.
|
|
543
|
+
def reset_analysis_attribution
|
|
544
|
+
@analysis_reads = {}
|
|
545
|
+
@analysis_disclosures = {}
|
|
546
|
+
@pre_analysis_plugin_rows = nil
|
|
547
|
+
@post_analysis_plugin_rows = nil
|
|
548
|
+
end
|
|
549
|
+
private :reset_analysis_attribution
|
|
550
|
+
|
|
510
551
|
def run_analysis(paths)
|
|
511
552
|
Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths =
|
|
512
553
|
@configuration.fold_platform_specific_paths
|
|
@@ -518,6 +559,7 @@ module Rigor
|
|
|
518
559
|
|
|
519
560
|
expansion = expand_paths(paths)
|
|
520
561
|
@project_discovery_expansion = project_discovery_expansion(paths, expansion)
|
|
562
|
+
@discovery_widened = widen_discovery_to_project?(paths)
|
|
521
563
|
@snapshots.reset_for_run
|
|
522
564
|
# Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
|
|
523
565
|
@project_discovery_done = false
|
|
@@ -533,9 +575,7 @@ module Rigor
|
|
|
533
575
|
# #788 — the per-file reader is assigned only on the analysis (miss) path; reset it here so a run the
|
|
534
576
|
# ADR-45 result cache serves does not answer with the previous run's rows.
|
|
535
577
|
@per_file_diagnostics = [].freeze
|
|
536
|
-
|
|
537
|
-
@pre_analysis_plugin_rows = nil
|
|
538
|
-
@post_analysis_plugin_rows = nil
|
|
578
|
+
reset_analysis_attribution
|
|
539
579
|
# ADR-84 WD2 — roll the return-memo bucket: a fresh frozen token per run makes every per-file scope
|
|
540
580
|
# of THIS run share one memo bucket while entries from any earlier run in this process (stale after
|
|
541
581
|
# an edit) become unreachable.
|
|
@@ -599,6 +639,19 @@ module Rigor
|
|
|
599
639
|
expand_paths(paths).fetch(:files)
|
|
600
640
|
end
|
|
601
641
|
|
|
642
|
+
# Issue #1583 — the project files a run over `paths` would DISCOVER without analysing them (#684's
|
|
643
|
+
# widening), by globbing only. Empty whenever the run would not widen, which includes every run over
|
|
644
|
+
# the configured `paths:`. {IncrementalSession} digests them, because an edit to one moves the
|
|
645
|
+
# diagnostics of the files it does analyse.
|
|
646
|
+
def discovery_only_file_set(paths = @configuration.paths)
|
|
647
|
+
expansion = expand_paths(paths)
|
|
648
|
+
widened = project_discovery_expansion(paths, expansion)
|
|
649
|
+
return [] if widened.nil?
|
|
650
|
+
|
|
651
|
+
analysed = expansion.fetch(:files).to_set
|
|
652
|
+
widened.fetch(:files).reject { |path| analysed.include?(path) }
|
|
653
|
+
end
|
|
654
|
+
|
|
602
655
|
# ADR-46 §2 — inverts {#file_dependencies} into the reverse edge the incremental step walks:
|
|
603
656
|
# `dependents[X] = { A : A read a declaration / body from X }`. On an edit to X, the body tier
|
|
604
657
|
# (slice 2) re-analyses `{X} ∪ dependents[X]` and serves every other file from the per-file cache.
|
|
@@ -791,6 +844,7 @@ module Rigor
|
|
|
791
844
|
@project_discovery_done = false
|
|
792
845
|
# This entry point supplies its own expansion; it never widens (see {#project_discovery_expansion}).
|
|
793
846
|
@project_discovery_expansion = nil
|
|
847
|
+
@discovery_widened = false
|
|
794
848
|
@run_generation = Object.new.freeze
|
|
795
849
|
run_project_pre_passes(expansion: expansion)
|
|
796
850
|
ensure_project_discovery(expansion)
|
|
@@ -1385,13 +1439,13 @@ module Rigor
|
|
|
1385
1439
|
end
|
|
1386
1440
|
|
|
1387
1441
|
def build_run_dependency_descriptor(expansion, rbs_descriptor)
|
|
1388
|
-
entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) +
|
|
1442
|
+
entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) + required_missing_entries +
|
|
1389
1443
|
pre_eval_file_entries + template_unit_file_entries + rbs_descriptor.files +
|
|
1390
1444
|
root_presence_entries(expansion)
|
|
1391
1445
|
# #979 — the signature ROOTS' listings, one glob row each. `rbs_descriptor.files` covers only the
|
|
1392
1446
|
# `.rbs` files that existed while the run read them, so without these a signature file written after
|
|
1393
1447
|
# the run left the slot validating fresh and the warm run answered without it.
|
|
1394
|
-
globs = rbs_descriptor.globs + template_unit_glob_entries
|
|
1448
|
+
globs = rbs_descriptor.globs + template_unit_glob_entries + discovery_glob_entries
|
|
1395
1449
|
boundary = plugin_boundary_rows
|
|
1396
1450
|
entries.concat(boundary.files)
|
|
1397
1451
|
globs.concat(boundary.globs).concat(producer_watch_glob_entries)
|
|
@@ -1406,7 +1460,7 @@ module Rigor
|
|
|
1406
1460
|
# auto-detected `sig`: the loader reads a directory and a file differently, and #979's listing rows cover a
|
|
1407
1461
|
# file appearing under a root that existed, not the root appearing. A kind row rather than an existence row,
|
|
1408
1462
|
# because a directory replaced by a file of the same name leaves an existence row fresh. The `--incremental`
|
|
1409
|
-
# slot records
|
|
1463
|
+
# slot records kind rows for the same paths ({#incremental_slot_rows}, {#baseline_dependency_rows}, #1977).
|
|
1410
1464
|
def root_presence_entries(expansion)
|
|
1411
1465
|
(expansion.fetch(:roots) + @configuration.pre_eval + signature_root_candidates)
|
|
1412
1466
|
.uniq.map { |path| Cache::Descriptor::FileEntry.kind(path: path) }
|
|
@@ -1452,11 +1506,11 @@ module Rigor
|
|
|
1452
1506
|
# globs, which a producer replays into its boundary whether it computed or was served (#1558). A save
|
|
1453
1507
|
# after the read leaves the row stale, so no guard is needed.
|
|
1454
1508
|
# - `derived` — rows taken when the run ENDS: the template files and their claimed globs (#392, re-analysed
|
|
1455
|
-
# every run), and one
|
|
1456
|
-
# such file or directory`, `pre-eval.file-not-found`) answer to those last rows
|
|
1457
|
-
#
|
|
1458
|
-
#
|
|
1459
|
-
#
|
|
1509
|
+
# every run), and one `:kind` row per analysis root and per `pre_eval:` entry. A run's path errors (`no
|
|
1510
|
+
# such file or directory`, `not a Ruby file`, `pre-eval.file-not-found`) answer to those last rows, and the
|
|
1511
|
+
# incremental path regenerates the errors every run, so a slot that served one past the edit that retracts
|
|
1512
|
+
# it would print what no run of that tree prints. A kind row rather than an existence row (#1977): an
|
|
1513
|
+
# existence row reads a directory replaced by a file of the same name as fresh, ADR-45 WD1b's bound.
|
|
1460
1514
|
# - `by_file` — `{path => Descriptor}`, the plugin reads each file's analysis made
|
|
1461
1515
|
# ({Plugin::IoBoundary.attributing}). A narrowed run analyses only its closure, so the session carries the
|
|
1462
1516
|
# other files' reads forward from the previous slot.
|
|
@@ -1480,7 +1534,8 @@ module Rigor
|
|
|
1480
1534
|
IncrementalSlotRows.new(
|
|
1481
1535
|
observed: plugin_boundary_rows,
|
|
1482
1536
|
derived: Cache::Descriptor.new(
|
|
1483
|
-
files: template_unit_file_entries +
|
|
1537
|
+
files: template_unit_file_entries + kind_entries(Array(roots) + @configuration.pre_eval) +
|
|
1538
|
+
required_missing_entries,
|
|
1484
1539
|
globs: template_unit_glob_entries
|
|
1485
1540
|
),
|
|
1486
1541
|
by_file: @analysis_reads.transform_values do |reads|
|
|
@@ -1507,13 +1562,14 @@ module Rigor
|
|
|
1507
1562
|
# which the session therefore carries unchanged from the full run that recorded them to every later slot:
|
|
1508
1563
|
#
|
|
1509
1564
|
# - the signature tree the plain slot records (`RbsDescriptor.file_entries` / `.glob_entries` over the loader
|
|
1510
|
-
# roots this run's analysis resolved), and
|
|
1511
|
-
# auto-detected `sig` — a recheck rebuilds no environment unless its closure is non-empty
|
|
1512
|
-
# fingerprint digests the same roots
|
|
1513
|
-
#
|
|
1565
|
+
# roots this run's analysis resolved), and a `:kind` row per signature root, configured or the
|
|
1566
|
+
# auto-detected `sig` — a recheck rebuilds no environment unless its closure is non-empty. The snapshot
|
|
1567
|
+
# fingerprint digests the same roots, so a change to one makes the next full-path run a full run rather
|
|
1568
|
+
# than a recheck, but the engine-free slot probe serves before any fingerprint is consulted: a root that
|
|
1569
|
+
# turns from a file into a directory (or back) is declined there by this `:kind` row alone (#1977);
|
|
1514
1570
|
# - the discovered-not-analysed files (#684) and a listing row per discovery root — a narrowed recheck does
|
|
1515
1571
|
# not widen discovery at all;
|
|
1516
|
-
# - every `pre_eval:` file outside `files` (a stat row, or
|
|
1572
|
+
# - every `pre_eval:` file outside `files` (a stat row, or a kind row) — the session tracks only analysed
|
|
1517
1573
|
# files, and one that is analysed is guarded by its own row.
|
|
1518
1574
|
#
|
|
1519
1575
|
# Recomputing any of these on a recheck would vouch for the tree the recheck saw while its answer still
|
|
@@ -1537,8 +1593,9 @@ module Rigor
|
|
|
1537
1593
|
own, pinned = signature_rows
|
|
1538
1594
|
BaselineRows.new(
|
|
1539
1595
|
owned: Cache::Descriptor.new(
|
|
1540
|
-
files: own.files +
|
|
1541
|
-
discovery_file_entries({ files: files }) +
|
|
1596
|
+
files: own.files + kind_entries(signature_root_candidates) +
|
|
1597
|
+
discovery_file_entries({ files: files }) + required_missing_entries +
|
|
1598
|
+
unanalysed_pre_eval_entries(files),
|
|
1542
1599
|
globs: (own.globs + discovery_glob_entries).uniq
|
|
1543
1600
|
),
|
|
1544
1601
|
pinned: pinned
|
|
@@ -1563,16 +1620,18 @@ module Rigor
|
|
|
1563
1620
|
|
|
1564
1621
|
# The project's own signature roots, whether or not they exist: the configured `signature_paths:`, or the
|
|
1565
1622
|
# `sig` directory `Environment.default_signature_paths` auto-detects when none are configured. The
|
|
1566
|
-
# signature rows' listing rows cover a file appearing under a root that existed; only
|
|
1567
|
-
# covers
|
|
1623
|
+
# signature rows' listing rows cover a file appearing under a root that existed; only a row for the root
|
|
1624
|
+
# itself covers it appearing.
|
|
1568
1625
|
def signature_root_candidates
|
|
1569
1626
|
@configuration.signature_paths || ["sig"]
|
|
1570
1627
|
end
|
|
1571
1628
|
|
|
1572
1629
|
# One names-mode listing row per directory the discovery widening globbed, so a file appearing there is seen
|
|
1573
|
-
# as well as an edit to one that existed.
|
|
1630
|
+
# as well as an edit to one that existed. Issue #1583 — keyed on the widening decision, not on what it
|
|
1631
|
+
# added: a run over `lib/b.rb` alone in `lib` widens to nothing, and a `lib/a.rb` created afterwards must
|
|
1632
|
+
# still move the answer of the run-result cache and the `--incremental` slot.
|
|
1574
1633
|
def discovery_glob_entries
|
|
1575
|
-
return []
|
|
1634
|
+
return [] unless @discovery_widened
|
|
1576
1635
|
|
|
1577
1636
|
@configuration.paths.select { |root| File.directory?(root) }.map do |root|
|
|
1578
1637
|
Cache::Descriptor::GlobEntry.compute(root: root, pattern: PathExpansion::RUBY_GLOB, mode: :names)
|
|
@@ -1586,22 +1645,18 @@ module Rigor
|
|
|
1586
1645
|
if File.file?(path)
|
|
1587
1646
|
Cache::Descriptor::FileEntry.stat(path: path, digest: Cache::FileDigest.hexdigest(path))
|
|
1588
1647
|
else
|
|
1589
|
-
|
|
1648
|
+
kind_entries([path]).first
|
|
1590
1649
|
end
|
|
1591
1650
|
end
|
|
1592
1651
|
end
|
|
1593
1652
|
|
|
1594
|
-
|
|
1653
|
+
# #1977 — one `:kind` row per path, the row {#root_presence_entries} records in the plain slot.
|
|
1654
|
+
def kind_entries(paths)
|
|
1595
1655
|
paths.uniq.map do |path|
|
|
1596
|
-
|
|
1597
|
-
if File.exist?(physical)
|
|
1598
|
-
Cache::Descriptor::FileEntry.present(path: physical)
|
|
1599
|
-
else
|
|
1600
|
-
Cache::Descriptor::FileEntry.absent(path: physical)
|
|
1601
|
-
end
|
|
1656
|
+
Cache::Descriptor::FileEntry.kind(path: @buffer ? @buffer.resolve(path) : path)
|
|
1602
1657
|
end
|
|
1603
1658
|
end
|
|
1604
|
-
private :plugin_boundary_rows, :producer_watch_glob_entries, :
|
|
1659
|
+
private :plugin_boundary_rows, :producer_watch_glob_entries, :kind_entries, :root_presence_entries,
|
|
1605
1660
|
:analysis_reads_attributed?, :signature_root_candidates, :discovery_glob_entries,
|
|
1606
1661
|
:unanalysed_pre_eval_entries, :signature_rows, :within?
|
|
1607
1662
|
|
|
@@ -1780,20 +1835,19 @@ module Rigor
|
|
|
1780
1835
|
end
|
|
1781
1836
|
|
|
1782
1837
|
# Internal: builds the deferred cross-file discovery tables at most once per run and adopts them.
|
|
1783
|
-
# Memoised on `@project_discovery_done` (reset at the start of `#run`). No-op under `@prebuilt
|
|
1784
|
-
#
|
|
1785
|
-
#
|
|
1786
|
-
#
|
|
1787
|
-
#
|
|
1788
|
-
#
|
|
1789
|
-
# cache HIT (which never assembles) never pays the double parse.
|
|
1838
|
+
# Memoised on `@project_discovery_done` (reset at the start of `#run`). No-op under `@prebuilt`: the
|
|
1839
|
+
# snapshot carries no discovery tables, and a per-request re-walk of the project is what `prebuilt:` exists
|
|
1840
|
+
# to avoid. A caller that wants cross-file knowledge under `prebuilt:` supplies it through `discovery_seed:`
|
|
1841
|
+
# (issue #260), as the LSP publish does since issue #2147 (ADR-124). Called eagerly from `#run` for the
|
|
1842
|
+
# recording / subset (ADR-46) modes and lazily from `#assemble_run_diagnostics` on the analysis path, so a
|
|
1843
|
+
# warm cache HIT (which never assembles) never pays the double parse.
|
|
1790
1844
|
def ensure_project_discovery(expansion)
|
|
1791
1845
|
return if @prebuilt
|
|
1792
1846
|
return if @project_discovery_done
|
|
1793
1847
|
|
|
1794
1848
|
@project_discovery_done = true
|
|
1795
1849
|
# Issue #684 — the cross-file index spans the PROJECT even when the analysis targets a file list.
|
|
1796
|
-
expansion = @project_discovery_expansion || once_each(expansion)
|
|
1850
|
+
expansion = buffer_spelled(@project_discovery_expansion || once_each(expansion))
|
|
1797
1851
|
if @collect_seed_bundles
|
|
1798
1852
|
# ADR-85 WD2 — rebuild discovery from the prior run's bundles (re-walking only changed files) and
|
|
1799
1853
|
# capture the refreshed bundle set for the session to persist.
|
|
@@ -1825,23 +1879,71 @@ module Rigor
|
|
|
1825
1879
|
# on what else was on the command line.
|
|
1826
1880
|
#
|
|
1827
1881
|
# Excluded, each because its expansion is already the one it means: the LSP `prebuilt:` path (which
|
|
1828
|
-
# seeds discovery from a snapshot
|
|
1829
|
-
#
|
|
1882
|
+
# seeds discovery from a snapshot, ADR-124) and `run_source`'s in-memory single file.
|
|
1883
|
+
#
|
|
1884
|
+
# Issue #2165 — editor `buffer:` mode (`rigor check --tmp-file T --instead-of F F`) widens like a plain run.
|
|
1885
|
+
# It was excluded on the reading that its expansion is already the one it means, but an editor passes the
|
|
1886
|
+
# buffer's own path, so discovery saw that file alone: every class another file declares read as an external
|
|
1887
|
+
# ancestor, and the run reported what `rigor check F` does not. The discovery pre-pass reads through the
|
|
1888
|
+
# binding, so the buffer's file is walked from the buffer's bytes.
|
|
1889
|
+
#
|
|
1890
|
+
# Issue #1583 — ADR-46's `analyze_only` subset mode widens like a full run. It was excluded on the
|
|
1891
|
+
# reading that it "already passes the full expansion", which holds only when the incremental session
|
|
1892
|
+
# runs over the configured `paths:`. `rigor check --incremental lib/b.rb` passes the argument's
|
|
1893
|
+
# expansion, so its rechecks (and `--verify-incremental`'s partitions) discovered the argument alone
|
|
1894
|
+
# while its baseline discovered the project, and `lib/b.rb`'s report on a sibling's constant came and
|
|
1895
|
+
# went with the run kind.
|
|
1830
1896
|
#
|
|
1831
1897
|
# Issue #1556 — the widened set holds each FILE once. `.rigor.yml`'s `paths:` load as absolute paths and a
|
|
1832
1898
|
# path argument usually does not, so `rigor check lib` widened discovery over both spellings of every file:
|
|
1833
1899
|
# the pre-pass then saw two writers for every constant a file declares and withheld its publication (#644),
|
|
1834
1900
|
# and every file was a discovered-not-analysed dependency of the run's cache entry.
|
|
1835
1901
|
#
|
|
1902
|
+
# Issue #2021 — an analysed file outside the configured paths has no project there, so the widened set also
|
|
1903
|
+
# holds what it loads with a literal `require_relative` ({#required_outside_paths}). A plugin file checked on
|
|
1904
|
+
# its own otherwise missed its sibling's nested `Effects` and resolved the project's `Rigor::Effects`.
|
|
1905
|
+
#
|
|
1836
1906
|
# The widening decision itself compares the paths as spelled. `exclude:` patterns apply to both spellings
|
|
1837
1907
|
# alike (#1576), so the configured absolute `lib` and the argument `lib` exclude the same files.
|
|
1838
1908
|
def project_discovery_expansion(paths, expansion)
|
|
1839
1909
|
return nil unless widen_discovery_to_project?(paths)
|
|
1840
1910
|
|
|
1841
1911
|
analysed = expansion.fetch(:files)
|
|
1842
|
-
|
|
1912
|
+
project = expand_paths(@configuration.paths | paths).fetch(:files)
|
|
1913
|
+
walked = required_outside_paths(analysed)
|
|
1914
|
+
files = discovery_files(project + walked.found, analysed)
|
|
1843
1915
|
# Overlapping arguments (`lib lib/user.rb`) name an analysed file twice; the widened set names it once.
|
|
1844
|
-
files.size > analysed.uniq { |path| File.absolute_path(path) }.size ?
|
|
1916
|
+
return nil unless files.size > analysed.uniq { |path| File.absolute_path(path) }.size || walked.missing.any?
|
|
1917
|
+
|
|
1918
|
+
{ files: files, errors: [], uncertain: walked.found.to_set.freeze, missing: walked.missing }
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
# Issue #2021 — what the analysed files outside the configured `paths:` load with a literal `require_relative`,
|
|
1922
|
+
# through targets that are themselves outside them ({RequireRelativeClosure}). A file under a configured path
|
|
1923
|
+
# is discovered already, so the walk adds nothing for a run whose files all lie inside them. The comparison is
|
|
1924
|
+
# by real path, so a symlinked spelling of a configured file does not join discovery a second time (two
|
|
1925
|
+
# writers would withhold its constants, #644). `exclude:` drops a target from discovery as it drops a
|
|
1926
|
+
# configured file; the walk still follows what an excluded target requires.
|
|
1927
|
+
#
|
|
1928
|
+
# The found files are discovered as `uncertain` ({Analysis::Runner::ProjectPrePasses#uncertain_paths}): the walk
|
|
1929
|
+
# sees one entry point's load graph, and a file it does not reach may redefine what they define.
|
|
1930
|
+
def required_outside_paths(analysed)
|
|
1931
|
+
roots = @configuration.paths.map { |root| RequireRelativeClosure.real_path(root) }
|
|
1932
|
+
covered = ->(real) { roots.any? { |root| real == root || real.start_with?(File.join(root, "")) } }
|
|
1933
|
+
seeds = analysed.reject { |path| covered.call(RequireRelativeClosure.real_path(path)) }
|
|
1934
|
+
return RequireRelativeClosure::Result.new(found: [], missing: []) if seeds.empty?
|
|
1935
|
+
|
|
1936
|
+
read = ->(path) { File.read(@buffer ? @buffer.resolve(path) : path) }
|
|
1937
|
+
walked = RequireRelativeClosure.closure(seeds, covered: covered, read: read)
|
|
1938
|
+
walked.with(found: PathExpansion.reject_excluded(walked.found, @configuration.exclude_patterns,
|
|
1939
|
+
base: @configuration.exclude_base))
|
|
1940
|
+
end
|
|
1941
|
+
|
|
1942
|
+
# Issue #2021 — a kind row per `require_relative` target the walk named and found absent: a file appearing
|
|
1943
|
+
# there joins the discovery, so it moves the answer of the run-result cache and the `--incremental` slot.
|
|
1944
|
+
def required_missing_entries
|
|
1945
|
+
missing = @project_discovery_expansion&.fetch(:missing, nil)
|
|
1946
|
+
missing.nil? || missing.empty? ? [] : kind_entries(missing)
|
|
1845
1947
|
end
|
|
1846
1948
|
|
|
1847
1949
|
# The expansion with each file once, first spelling kept: overlapping arguments (`lib ./lib`) that do not
|
|
@@ -1853,6 +1955,23 @@ module Rigor
|
|
|
1853
1955
|
unique.size == files.size ? expansion : expansion.merge(files: unique)
|
|
1854
1956
|
end
|
|
1855
1957
|
|
|
1958
|
+
# Issue #2165 — the discovery set with the buffer's file under the binding's own spelling, once. The binding
|
|
1959
|
+
# substitutes bytes only for a path spelled exactly as its logical path, while the file can reach discovery as
|
|
1960
|
+
# the argument's spelling, the configured one, or through a symlink; each of those read it from disk, and a
|
|
1961
|
+
# second spelling made it a second declarer. Every member whose real path is the buffer's file is replaced by
|
|
1962
|
+
# the logical path, and the duplicates that leaves are dropped (`IncrementalSession#rebind_buffer` is option
|
|
1963
|
+
# B's counterpart).
|
|
1964
|
+
def buffer_spelled(expansion)
|
|
1965
|
+
return expansion if @buffer.nil?
|
|
1966
|
+
|
|
1967
|
+
target = Analysis::BufferBinding.real_path(@buffer.logical_path)
|
|
1968
|
+
files = expansion.fetch(:files)
|
|
1969
|
+
spelled = files.map do |path|
|
|
1970
|
+
Analysis::BufferBinding.real_path(path) == target ? @buffer.logical_path : path
|
|
1971
|
+
end.uniq
|
|
1972
|
+
spelled == files ? expansion : expansion.merge(files: spelled)
|
|
1973
|
+
end
|
|
1974
|
+
|
|
1856
1975
|
# Each file once, in project order, under the spelling the analysis uses when it is analysed.
|
|
1857
1976
|
def discovery_files(project_files, analysed_files)
|
|
1858
1977
|
spelling = analysed_files.to_h { |path| [File.absolute_path(path), path] }
|
|
@@ -1864,8 +1983,7 @@ module Rigor
|
|
|
1864
1983
|
end
|
|
1865
1984
|
|
|
1866
1985
|
def widen_discovery_to_project?(paths)
|
|
1867
|
-
@prebuilt.nil? && @
|
|
1868
|
-
paths != @configuration.paths
|
|
1986
|
+
@prebuilt.nil? && @in_memory_sources.nil? && paths != @configuration.paths
|
|
1869
1987
|
end
|
|
1870
1988
|
|
|
1871
1989
|
# ADR-46 — the dependency-recording and subset-analysis modes read the discovery tables OUTSIDE the
|
|
@@ -1879,7 +1997,8 @@ module Rigor
|
|
|
1879
1997
|
|
|
1880
1998
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result,
|
|
1881
1999
|
:apply_discovery_result, :ensure_project_discovery, :force_eager_discovery?,
|
|
1882
|
-
:project_discovery_expansion, :discovery_files, :once_each, :widen_discovery_to_project
|
|
2000
|
+
:project_discovery_expansion, :discovery_files, :once_each, :widen_discovery_to_project?,
|
|
2001
|
+
:required_outside_paths, :required_missing_entries
|
|
1883
2002
|
|
|
1884
2003
|
# Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
|
|
1885
2004
|
# `version:`. Prism exposes no version list, so the floor is found empirically — only when a
|
|
@@ -2069,6 +2188,7 @@ module Rigor
|
|
|
2069
2188
|
hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
|
|
2070
2189
|
conformance_results_snapshot: -> { @snapshots.conformance_results },
|
|
2071
2190
|
pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures },
|
|
2191
|
+
served_run_disclosures: -> { @served_run_disclosures },
|
|
2072
2192
|
pooled_trust_refusals: -> { @pool_coordinator.collected_trust_refusals }
|
|
2073
2193
|
)
|
|
2074
2194
|
end
|
|
@@ -2216,10 +2336,8 @@ module Rigor
|
|
|
2216
2336
|
# client editing a brand-new file.
|
|
2217
2337
|
elsif accept_as_ruby_file?(path)
|
|
2218
2338
|
files << path
|
|
2219
|
-
elsif File.exist?(path)
|
|
2220
|
-
bad << [path, "not a Ruby file (expected `.rb` or a directory)"]
|
|
2221
2339
|
else
|
|
2222
|
-
bad << [path,
|
|
2340
|
+
bad << [path, PathExpansion.unusable_message(path)]
|
|
2223
2341
|
end
|
|
2224
2342
|
end
|
|
2225
2343
|
{ files: files, roots: Array(paths).map(&:to_s), errors: path_expansion_errors(bad, any_files: files.any?) }
|
|
@@ -2229,27 +2347,24 @@ module Rigor
|
|
|
2229
2347
|
# app lib` with no `lib/` (the 20260620 field trial's strap case) analyses `app` instead of aborting
|
|
2230
2348
|
# on exit 1. A bad path that leaves NOTHING to analyse stays an error so a lone typo (`rigor check
|
|
2231
2349
|
# typo.rb`) is not silently masked (the regression the path-error check was added to catch in the
|
|
2232
|
-
# first place).
|
|
2350
|
+
# first place). The wording and grading live in {PathExpansion.diagnostics}, shared with `sig-gen` (#1991).
|
|
2233
2351
|
def path_expansion_errors(bad, any_files:)
|
|
2234
|
-
|
|
2235
|
-
suffix = any_files ? " (skipped)" : ""
|
|
2236
|
-
bad.map { |path, message| path_error(path, "#{message}#{suffix}", severity: severity) }
|
|
2352
|
+
PathExpansion.diagnostics(bad, any_files: any_files)
|
|
2237
2353
|
end
|
|
2238
2354
|
|
|
2355
|
+
# #2191 — the buffer's logical path is accepted under any spelling that names the same file (relative, absolute,
|
|
2356
|
+
# or through a symlinked root), since the discovery set respells it to the logical path (`buffer_spelled`).
|
|
2239
2357
|
def accept_as_ruby_file?(path)
|
|
2240
2358
|
(File.file?(path) && path.end_with?(".rb")) ||
|
|
2241
|
-
(
|
|
2359
|
+
buffer_path?(path) ||
|
|
2242
2360
|
@in_memory_sources&.key?(path)
|
|
2243
2361
|
end
|
|
2244
2362
|
|
|
2245
|
-
def
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
message: message,
|
|
2251
|
-
severity: severity
|
|
2252
|
-
)
|
|
2363
|
+
def buffer_path?(path)
|
|
2364
|
+
return false if @buffer.nil?
|
|
2365
|
+
|
|
2366
|
+
path == @buffer.logical_path ||
|
|
2367
|
+
Analysis::BufferBinding.real_path(path) == Analysis::BufferBinding.real_path(@buffer.logical_path)
|
|
2253
2368
|
end
|
|
2254
2369
|
|
|
2255
2370
|
# Reads + parses the source at `path`. Under editor mode (`@buffer` set) reads bytes from
|
|
@@ -2425,50 +2540,21 @@ module Rigor
|
|
|
2425
2540
|
tables[:published_constant_names] = names unless names.empty?
|
|
2426
2541
|
end
|
|
2427
2542
|
|
|
2428
|
-
# Issue #617 — the
|
|
2429
|
-
#
|
|
2430
|
-
# segment span two files: either may load first and set the constant the other then reads, so only a
|
|
2431
|
-
# memo no other file shares keeps the idiom's reading. Files are counted expanded, because discovery can
|
|
2432
|
-
# walk one file under two spellings (`lib/b.rb`, `./lib/b.rb`).
|
|
2543
|
+
# Issue #617 / #1290 — the binding and shadowing constant names, by last segment; derived by
|
|
2544
|
+
# {Inference::ProjectSeedTables}, which the seed paths that run no `check` share.
|
|
2433
2545
|
def constant_writer_index
|
|
2434
|
-
@constant_writer_index ||=
|
|
2435
|
-
memo_files = memo_files_by_segment
|
|
2436
|
-
@project_constant_writes.each_with_object({}) do |(name, by_path), index|
|
|
2437
|
-
segment = name.split("::").last
|
|
2438
|
-
next if memo_only?(by_path) && memo_files.fetch(segment).size < 2
|
|
2439
|
-
|
|
2440
|
-
(index[segment] ||= []) << name
|
|
2441
|
-
end.each_value(&:freeze).freeze
|
|
2442
|
-
end
|
|
2546
|
+
@constant_writer_index ||= Inference::ProjectSeedTables.constant_writers(@project_constant_writes)
|
|
2443
2547
|
end
|
|
2444
2548
|
|
|
2445
|
-
# Issue #1290 — the censused names some write other than a memo `||=` assigns, grouped by last segment
|
|
2446
|
-
# (`Scope#shadowing_constant_names`): the ones that exist whether or not an outer constant of the name
|
|
2447
|
-
# does, and so stop the lexical ladder above it. A memo-only name is left out however many files memoize
|
|
2448
|
-
# it, unlike in {#constant_writer_index}: `X ||= v` resolves `X` through the very lookup whose lower rung
|
|
2449
|
-
# the stop would replace, so wherever that rung answers, the memo finds it and never creates the name.
|
|
2450
2549
|
def constant_shadower_index
|
|
2451
|
-
@constant_shadower_index ||= @project_constant_writes
|
|
2452
|
-
(index[name.split("::").last] ||= []) << name unless memo_only?(by_path)
|
|
2453
|
-
end.each_value(&:freeze).freeze
|
|
2550
|
+
@constant_shadower_index ||= Inference::ProjectSeedTables.constant_shadowers(@project_constant_writes)
|
|
2454
2551
|
end
|
|
2455
2552
|
|
|
2456
|
-
def memo_files_by_segment
|
|
2457
|
-
@project_constant_writes.each_with_object({}) do |(name, by_path), files|
|
|
2458
|
-
set = (files[name.split("::").last] ||= Set.new)
|
|
2459
|
-
by_path.each { |path, descriptor| set << File.absolute_path(path) if memo_descriptor?(descriptor) }
|
|
2460
|
-
end
|
|
2461
|
-
end
|
|
2462
|
-
|
|
2463
|
-
def memo_only?(by_path) = by_path.each_value.all? { |descriptor| memo_descriptor?(descriptor) }
|
|
2464
|
-
def memo_descriptor?(descriptor) = descriptor == Inference::ScopeIndexer::CONSTANT_MEMO
|
|
2465
|
-
|
|
2466
2553
|
# Issue #644 — the LAST SEGMENTS of the published table, the run-wide half of
|
|
2467
2554
|
# `Scope#published_constant?`. Seeded on EVERY run (unlike `constant_sources`, which only a recording
|
|
2468
2555
|
# run needs) because {CheckRules::PublishedConstantGuard} asks the question on every `if` / `unless`.
|
|
2469
2556
|
def published_constant_name_set
|
|
2470
|
-
@published_constant_name_set ||=
|
|
2471
|
-
@project_constant_values.keys.to_set { |name| name.split("::").last }.freeze
|
|
2557
|
+
@published_constant_name_set ||= Inference::ProjectSeedTables.published_constant_names(@project_constant_values)
|
|
2472
2558
|
end
|
|
2473
2559
|
|
|
2474
2560
|
# Issue #644 — the seeded `in_source_constants` table, joining the two cross-file constant producers.
|
|
@@ -2477,12 +2563,31 @@ module Rigor
|
|
|
2477
2563
|
# widener erases it to the class, and listing a file under `pre_eval:` must never LOSE precision. The
|
|
2478
2564
|
# per-file table still wins over both (`ScopeIndexer.index`'s merge) — same-file stays the most
|
|
2479
2565
|
# specific authority.
|
|
2566
|
+
#
|
|
2567
|
+
# ADR-124 — a `prebuilt:` runner computes no literal table of its own; a `discovery_seed:` (the editor
|
|
2568
|
+
# publish, ADR-124) brings the project's, which then plays the same part.
|
|
2480
2569
|
def merged_seed_constants
|
|
2481
2570
|
pre_eval = publishable_pre_eval_constants
|
|
2482
|
-
|
|
2483
|
-
return
|
|
2571
|
+
literal = literal_seed_constants
|
|
2572
|
+
return literal if pre_eval.empty?
|
|
2573
|
+
return pre_eval if literal.empty?
|
|
2574
|
+
|
|
2575
|
+
pre_eval.merge(literal).freeze
|
|
2576
|
+
end
|
|
2577
|
+
|
|
2578
|
+
# The project's literal constant table: this run's, or the one a `discovery_seed:` carries (ADR-124).
|
|
2579
|
+
def literal_seed_constants
|
|
2580
|
+
return @project_constant_values unless @project_constant_values.empty? && @discovery_seed
|
|
2581
|
+
|
|
2582
|
+
@discovery_seed.fetch(:in_source_constants, @project_constant_values)
|
|
2583
|
+
end
|
|
2584
|
+
|
|
2585
|
+
# The per-name writer paths the #663 retraction reads: this run's census, or the one a `discovery_seed:` carries
|
|
2586
|
+
# (ADR-124).
|
|
2587
|
+
def constant_writer_sources
|
|
2588
|
+
return @project_constant_sources unless @project_constant_sources.empty? && @discovery_seed
|
|
2484
2589
|
|
|
2485
|
-
|
|
2590
|
+
@discovery_seed.fetch(:constant_sources, @project_constant_sources)
|
|
2486
2591
|
end
|
|
2487
2592
|
|
|
2488
2593
|
# Issue #663 — the `pre_eval:` publication with every name an UNLISTED file also assigns retracted.
|
|
@@ -2511,8 +2616,9 @@ module Rigor
|
|
|
2511
2616
|
# name of its own for the wildcard's paths to have been folded into.
|
|
2512
2617
|
def unlisted_constant_writer?(name, listed)
|
|
2513
2618
|
segment = name.split("::").last
|
|
2514
|
-
|
|
2515
|
-
|
|
2619
|
+
sources = constant_writer_sources
|
|
2620
|
+
writers = sources[name]
|
|
2621
|
+
wildcard = sources["#{Inference::ScopeIndexer::DYNAMIC_TARGET_PREFIX}#{segment}"]
|
|
2516
2622
|
[writers, wildcard].any? do |paths|
|
|
2517
2623
|
paths&.any? { |path| !listed.include?(File.absolute_path(path)) }
|
|
2518
2624
|
end
|
|
@@ -2556,13 +2662,18 @@ module Rigor
|
|
|
2556
2662
|
@pre_analysis_plugin_rows ||= plugin_boundary_rows
|
|
2557
2663
|
reads = []
|
|
2558
2664
|
diagnostics = nil
|
|
2559
|
-
record =
|
|
2560
|
-
|
|
2561
|
-
|
|
2665
|
+
record = nil
|
|
2666
|
+
# Issue #2020 — and every run-scoped row its analysis registered, with the registering plugins' reads.
|
|
2667
|
+
disclosures = RunDisclosureCredit.collect do
|
|
2668
|
+
record = Plugin::IoBoundary.attributing(reads) do
|
|
2669
|
+
DependencyRecorder.record_for(path) do
|
|
2670
|
+
diagnostics = analyze_with_effects(path, environment)
|
|
2671
|
+
end
|
|
2562
2672
|
end
|
|
2563
2673
|
end
|
|
2564
2674
|
@file_dependencies[path] = record
|
|
2565
2675
|
@analysis_reads[path] = reads unless reads.empty?
|
|
2676
|
+
@analysis_disclosures[path] = disclosures unless disclosures.empty?
|
|
2566
2677
|
diagnostics
|
|
2567
2678
|
end
|
|
2568
2679
|
|