rigortype 0.4.0 → 0.4.2
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 +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -297,11 +297,15 @@ module Rigor
|
|
|
297
297
|
record_dependencies: false, record_self_calls: false, analyze_only: nil,
|
|
298
298
|
seed_bundles: nil, collect_seed_bundles: false, param_inferred_types: nil,
|
|
299
299
|
discovery_seed: nil, no_tolerated_effects: false,
|
|
300
|
-
restored_run_level_rows: nil)
|
|
300
|
+
restored_run_level_rows: nil, served_per_file_diagnostics: nil)
|
|
301
301
|
@configuration = configuration
|
|
302
302
|
# Issues #796 / #794 — the previous full run's run-level rows, supplied only by
|
|
303
303
|
# {IncrementalSession} on a narrowed run; see {PoolCoordinator#replay_restored_run_level_rows}.
|
|
304
304
|
@restored_run_level_rows = restored_run_level_rows
|
|
305
|
+
# Issue #1524 — `{path => rows}` the {IncrementalSession} serves from its per-file cache for the files a
|
|
306
|
+
# narrowed run does not analyse, spliced into this run's per-file stream by
|
|
307
|
+
# {#interleave_served_diagnostics}. Empty on every other run.
|
|
308
|
+
@served_per_file_diagnostics = served_per_file_diagnostics || {}
|
|
305
309
|
@explain = explain
|
|
306
310
|
@cache_store = enforce_read_only_cache(cache_store, buffer)
|
|
307
311
|
@plugin_requirer = plugin_requirer
|
|
@@ -353,6 +357,12 @@ module Rigor
|
|
|
353
357
|
@cached_effect_ancestry = nil
|
|
354
358
|
@cached_collections_loader = nil
|
|
355
359
|
@run_dependency_descriptor = nil
|
|
360
|
+
# ADR-45 WD2 (#1507) — the plugin reads each file's analysis made (`{path => [row]}`), and the boundary
|
|
361
|
+
# rows as they stood around the per-file loop; see {#incremental_slot_rows}. Recorded only by a
|
|
362
|
+
# dependency-recording (incremental) run.
|
|
363
|
+
@analysis_reads = {}
|
|
364
|
+
@pre_analysis_plugin_rows = nil
|
|
365
|
+
@post_analysis_plugin_rows = nil
|
|
356
366
|
# Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed).
|
|
357
367
|
# See `self_undefined_rule_active?`.
|
|
358
368
|
@self_undefined_rule_active = nil
|
|
@@ -414,6 +424,7 @@ module Rigor
|
|
|
414
424
|
@project_discovered_prepends = {}.freeze
|
|
415
425
|
# Issue #898 — the singleton-side twin of the include table (`extend M` / `extend self`).
|
|
416
426
|
@project_discovered_extends = {}.freeze
|
|
427
|
+
@project_unpositioned_mixins = {}.freeze
|
|
417
428
|
@project_discovered_deferred_ranges = {}.freeze
|
|
418
429
|
# Issue #1120 — `{refined class => {method => [refining modules]}}`, every `refine X do … end` body.
|
|
419
430
|
@project_discovered_refinements = {}.freeze
|
|
@@ -433,6 +444,9 @@ module Rigor
|
|
|
433
444
|
@constant_shadower_index = nil
|
|
434
445
|
@project_discovered_method_visibilities = {}.freeze
|
|
435
446
|
@project_discovered_methods = {}.freeze
|
|
447
|
+
# ADR-119 WD1 — the `possible_*` / `contested_*` siblings of the paired members, ONE Hash keyed by sibling
|
|
448
|
+
# name so no adoption or seed path names a sibling and none can drop one.
|
|
449
|
+
@project_discovery_siblings = Scope::DiscoveryIndex::EMPTY_SIBLINGS
|
|
436
450
|
# Issue #992 — the cross-file parameter-envelope table `call.wrong-arity` reads for an undeclared `def`.
|
|
437
451
|
@project_discovered_parameter_envelopes = {}.freeze
|
|
438
452
|
@project_data_member_layouts = {}.freeze
|
|
@@ -508,6 +522,9 @@ module Rigor
|
|
|
508
522
|
# #788 — the per-file reader is assigned only on the analysis (miss) path; reset it here so a run the
|
|
509
523
|
# ADR-45 result cache serves does not answer with the previous run's rows.
|
|
510
524
|
@per_file_diagnostics = [].freeze
|
|
525
|
+
@analysis_reads = {}
|
|
526
|
+
@pre_analysis_plugin_rows = nil
|
|
527
|
+
@post_analysis_plugin_rows = nil
|
|
511
528
|
# ADR-84 WD2 — roll the return-memo bucket: a fresh frozen token per run makes every per-file scope
|
|
512
529
|
# of THIS run share one memo bucket while entries from any earlier run in this process (stale after
|
|
513
530
|
# an edit) become unreachable.
|
|
@@ -558,6 +575,12 @@ module Rigor
|
|
|
558
575
|
@in_memory_sources = nil
|
|
559
576
|
end
|
|
560
577
|
|
|
578
|
+
# Issue #1700 — the sources the required-feature scan reads in place of a file on disk: an in-memory
|
|
579
|
+
# source ({#run_source}) and an editor buffer (`--tmp-file` read for its `--instead-of` path).
|
|
580
|
+
def required_feature_sources
|
|
581
|
+
(@in_memory_sources || {}).merge(ProjectEnvironment.buffer_sources(@buffer))
|
|
582
|
+
end
|
|
583
|
+
|
|
561
584
|
# ADR-46 — the project file set that a run over `paths` would analyze, computed by globbing only (no
|
|
562
585
|
# RBS environment build), so the incremental fingerprint can be derived cheaply on the warm path
|
|
563
586
|
# before deciding whether to build the env at all.
|
|
@@ -619,11 +642,16 @@ module Rigor
|
|
|
619
642
|
# detect a class that *appeared* in an edit so a subclass whose ancestor was previously undefined
|
|
620
643
|
# (and so recorded a negative class edge) is re-checked. Inverts the project class-source attribution
|
|
621
644
|
# (class → declaring files).
|
|
645
|
+
#
|
|
646
|
+
# Issue #1820 — each file's set also holds a `[class, superclass as written]` pair for every class it declares
|
|
647
|
+
# with a superclass, so a subclass appearing or vanishing re-checks the consumers that enumerated the parent's
|
|
648
|
+
# subclasses (`subclass_of:<last segment>`).
|
|
622
649
|
def class_declarations
|
|
623
650
|
result = Hash.new { |hash, key| hash[key] = Set.new }
|
|
624
651
|
@project_discovered_class_sources.each do |class_name, files|
|
|
625
652
|
files.each { |file| result[file] << class_name }
|
|
626
653
|
end
|
|
654
|
+
Incremental.add_superclass_edges(result, @project_discovered_class_sources, @project_discovered_superclasses)
|
|
627
655
|
result.transform_values(&:freeze).freeze
|
|
628
656
|
end
|
|
629
657
|
|
|
@@ -828,6 +856,11 @@ module Rigor
|
|
|
828
856
|
# then hit — the stats it would otherwise suppress were all gathered.
|
|
829
857
|
@run_served_from_cache = !computed && analysis.nil?
|
|
830
858
|
diagnostics
|
|
859
|
+
rescue Inference::DeclarationWalk::ContractError
|
|
860
|
+
# A broken walk contract in the project pre-pass (a shadow-harness divergence, an unknown variant) is
|
|
861
|
+
# not a cache failure: the fallback below would re-run the analysis with the pre-pass marked done and
|
|
862
|
+
# no project index, and report the resulting false positives instead of the failure.
|
|
863
|
+
raise
|
|
831
864
|
rescue StandardError
|
|
832
865
|
# The result cache must never break a run. If anything in the cache path fails, fall back to a
|
|
833
866
|
# direct, uncached analysis.
|
|
@@ -1175,19 +1208,55 @@ module Rigor
|
|
|
1175
1208
|
# `#per_file_diagnostics`: the `analyze_files` return, stamped with the same severity profile the run's
|
|
1176
1209
|
# own stream gets (`SeverityStamp` drops `:off` rows and re-stamps overrides, and the per-file cache never
|
|
1177
1210
|
# re-stamps), sliced to the rows positioned at the targets. Returns the RAW return for the run's stream,
|
|
1178
|
-
# which is stamped once, at the end of `#run_analysis
|
|
1211
|
+
# which is stamped once, at the end of `#run_analysis` — on a narrowed incremental run with the served
|
|
1212
|
+
# files' rows spliced in (#1524), which `#per_file_diagnostics` never carries.
|
|
1179
1213
|
def analyze_targets(targets, environment:, project_files:)
|
|
1180
1214
|
raw = template_units.remap(
|
|
1181
1215
|
@pool_coordinator.analyze_files(targets, environment: environment, project_files: project_files)
|
|
1182
1216
|
)
|
|
1217
|
+
# ADR-45 WD2 (#1507) — the boundaries as the per-file loop left them; with the snapshot the first
|
|
1218
|
+
# analysed file took, the rows {#incremental_slot_rows} checks every analysis-time read against.
|
|
1219
|
+
@post_analysis_plugin_rows = plugin_boundary_rows if @record_dependencies
|
|
1183
1220
|
analysed = targets.to_set
|
|
1184
1221
|
@per_file_diagnostics = @diagnostic_aggregator.apply_severity_profile(raw)
|
|
1185
1222
|
.select { |diagnostic| analysed.include?(diagnostic.path) }
|
|
1186
1223
|
.freeze
|
|
1187
|
-
raw
|
|
1224
|
+
interleave_served_diagnostics(raw, analysed, project_files | targets)
|
|
1188
1225
|
end
|
|
1189
1226
|
private :analyze_targets
|
|
1190
1227
|
|
|
1228
|
+
# Issue #1524 — splices the rows {IncrementalSession} serves from its per-file cache into this narrowed
|
|
1229
|
+
# run's per-file stream, each file's rows where a full run of the same tree lists them. A full run
|
|
1230
|
+
# analyses `order` (the project's files, then any buffer and template-unit targets) one file after
|
|
1231
|
+
# another and a narrowed run a subsequence of it, so a served file's rows go just before the first row
|
|
1232
|
+
# positioned at an analysed file later in `order`, or at the end when there is none. Only such a row
|
|
1233
|
+
# marks a place, so the rows this run produced keep their relative order and a pool backend's
|
|
1234
|
+
# `.rigor.yml` rows stay at the head. Splicing here rather than into the finished result keeps the
|
|
1235
|
+
# run-level streams on either side of the per-file stream, where a full run has them. The served rows
|
|
1236
|
+
# are already severity-resolved and the run's final stamp leaves them unchanged: a resolved severity
|
|
1237
|
+
# resolves to itself, and a row the profile turns `:off` is never cached.
|
|
1238
|
+
def interleave_served_diagnostics(raw, analysed, order)
|
|
1239
|
+
served = @served_per_file_diagnostics
|
|
1240
|
+
return raw if served.empty?
|
|
1241
|
+
|
|
1242
|
+
rank = order.each_with_index.to_h
|
|
1243
|
+
# A served path the expansion no longer lists (a file deleted between the session's listing and this
|
|
1244
|
+
# run) keeps its rows, after every other file's, as the concatenation this replaced did.
|
|
1245
|
+
known, unknown = served.keys.partition { |path| rank.key?(path) }
|
|
1246
|
+
pending = known.sort_by { |path| rank.fetch(path) }
|
|
1247
|
+
merged = []
|
|
1248
|
+
raw.each do |diagnostic|
|
|
1249
|
+
# `order` lists every target, so an analysed path always has a rank; one that did not would only
|
|
1250
|
+
# mark no place, since a misplaced row must never abort the run (ADR-5).
|
|
1251
|
+
position = analysed.include?(diagnostic.path) ? rank.fetch(diagnostic.path, -1) : -1
|
|
1252
|
+
merged.concat(served.fetch(pending.shift)) while pending.any? && rank.fetch(pending.first) < position
|
|
1253
|
+
merged << diagnostic
|
|
1254
|
+
end
|
|
1255
|
+
(pending + unknown).each { |path| merged.concat(served.fetch(path)) }
|
|
1256
|
+
merged
|
|
1257
|
+
end
|
|
1258
|
+
private :interleave_served_diagnostics
|
|
1259
|
+
|
|
1191
1260
|
# ADR-67 WD6a — the check-walk parameter-inference pre-pass. Populates `@project_param_inferred_types`
|
|
1192
1261
|
# (read by `project_scope_seed_tables`) with the call-site union of every undeclared parameter, running
|
|
1193
1262
|
# ONE round (a single hop of call-site → param typing; the protection scan's three-round fixpoint stays a
|
|
@@ -1310,24 +1379,204 @@ module Rigor
|
|
|
1310
1379
|
# `.rbs` files that existed while the run read them, so without these a signature file written after
|
|
1311
1380
|
# the run left the slot validating fresh and the warm run answered without it.
|
|
1312
1381
|
globs = rbs_descriptor.globs + template_unit_glob_entries
|
|
1382
|
+
boundary = plugin_boundary_rows
|
|
1383
|
+
entries.concat(boundary.files)
|
|
1384
|
+
globs.concat(boundary.globs).concat(producer_watch_glob_entries)
|
|
1385
|
+
Cache::Descriptor.new(files: entries, globs: globs.uniq)
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
# Every file a plugin read or probed through its `IoBoundary` (#577 / #613), and every directory it listed
|
|
1389
|
+
# (#954), as the boundaries stand when asked.
|
|
1390
|
+
def plugin_boundary_rows
|
|
1391
|
+
files = []
|
|
1392
|
+
globs = []
|
|
1313
1393
|
@plugin_registry.plugins.each do |plugin|
|
|
1314
1394
|
# Read the boundary WITHOUT triggering its lazy `@io_boundary ||=` initializer: plugin instances
|
|
1315
1395
|
# are frozen after the run, and a plugin that never built a boundary read no files through it,
|
|
1316
1396
|
# so it contributes no dependencies.
|
|
1317
1397
|
boundary = plugin.instance_variable_get(:@io_boundary)
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1398
|
+
next unless boundary
|
|
1399
|
+
|
|
1400
|
+
# #629 — a boundary carries GlobEntry listing rows too (`IoBoundary#list_directory`), and they
|
|
1401
|
+
# are the only edge back to a file the run decided was ABSENT from a directory it consulted.
|
|
1402
|
+
boundary_descriptor = boundary.cache_descriptor
|
|
1403
|
+
files.concat(boundary_descriptor.files)
|
|
1404
|
+
globs.concat(boundary_descriptor.globs)
|
|
1405
|
+
end
|
|
1406
|
+
Cache::Descriptor.new(files: files, globs: globs)
|
|
1407
|
+
end
|
|
1408
|
+
|
|
1409
|
+
# One glob row per producer `watch:` pattern (ADR-60 WD3), recomputed against the tree as it stands.
|
|
1410
|
+
def producer_watch_glob_entries
|
|
1411
|
+
@plugin_registry.plugins.flat_map do |plugin|
|
|
1412
|
+
plugin.class.producers.each_value.flat_map do |prod|
|
|
1413
|
+
prod[:watch] ? plugin.send(:watch_glob_entries, prod[:watch]) : []
|
|
1324
1414
|
end
|
|
1325
|
-
|
|
1326
|
-
|
|
1415
|
+
end
|
|
1416
|
+
end
|
|
1417
|
+
|
|
1418
|
+
# ADR-45 WD2 (#1507) — the rows `rigor check --incremental`'s run-result slot records beside the analysed
|
|
1419
|
+
# files (whose digests {IncrementalSession} holds itself). Every run reads these inputs again and re-derives
|
|
1420
|
+
# its answer from them; they are split by when the row was taken, which decides whether the session's
|
|
1421
|
+
# {IncrementalRunSlot::WriteGuard} must vouch for it:
|
|
1422
|
+
#
|
|
1423
|
+
# - `observed` — every plugin row (`#prepare`, producers, template synthesis, and this run's per-file reads
|
|
1424
|
+
# too), recorded by the {Plugin::IoBoundary} as the plugin read. It includes each producer's `watch:`
|
|
1425
|
+
# globs, which a producer replays into its boundary whether it computed or was served (#1558). A save
|
|
1426
|
+
# after the read leaves the row stale, so no guard is needed.
|
|
1427
|
+
# - `derived` — rows taken when the run ENDS: the template files and their claimed globs (#392, re-analysed
|
|
1428
|
+
# every run), and one existence row per analysis root and per `pre_eval:` entry. A run's path errors (`no
|
|
1429
|
+
# such file or directory`, `pre-eval.file-not-found`) answer to those last rows; the plain slot records
|
|
1430
|
+
# neither, and the incremental path regenerates the errors every run, so a slot that served one past the
|
|
1431
|
+
# edit that retracts it would print what no run of that tree prints. Existence rows carry ADR-45 WD1b's
|
|
1432
|
+
# bound: a directory replaced by a file of the same name stays fresh.
|
|
1433
|
+
# - `by_file` — `{path => Descriptor}`, the plugin reads each file's analysis made
|
|
1434
|
+
# ({Plugin::IoBoundary.attributing}). A narrowed run analyses only its closure, so the session carries the
|
|
1435
|
+
# other files' reads forward from the previous slot.
|
|
1436
|
+
#
|
|
1437
|
+
# The template rows stand in for the `template-units` slot the plain key carries, which only a run can
|
|
1438
|
+
# compute. That digest is a function of each unit's compiled Ruby (the template's bytes, and whatever else
|
|
1439
|
+
# the transform read, through its boundary), its transform (the plugin, whose identity and configuration
|
|
1440
|
+
# the key carries) and the synthesis version (the engine's): each of those has a row or a key slot.
|
|
1441
|
+
#
|
|
1442
|
+
# The inputs a recheck does NOT re-derive are {#baseline_dependency_rows}. nil when this run cannot vouch for
|
|
1443
|
+
# its rows — a pool run, whose workers' reads never reach this process; a run that recorded no dependencies,
|
|
1444
|
+
# whose analysis no session owns; or a run in which a boundary row changed during the per-file loop that no
|
|
1445
|
+
# file's analysis was credited with, such as a read from a thread the plugin started.
|
|
1446
|
+
IncrementalSlotRows = Data.define(:observed, :derived, :by_file)
|
|
1447
|
+
|
|
1448
|
+
# @param roots — the analysis roots the run was given.
|
|
1449
|
+
def incremental_slot_rows(roots:)
|
|
1450
|
+
return nil if pool_mode? || !@record_dependencies
|
|
1451
|
+
return nil unless analysis_reads_attributed?
|
|
1452
|
+
|
|
1453
|
+
IncrementalSlotRows.new(
|
|
1454
|
+
observed: plugin_boundary_rows,
|
|
1455
|
+
derived: Cache::Descriptor.new(
|
|
1456
|
+
files: template_unit_file_entries + existence_entries(Array(roots) + @configuration.pre_eval),
|
|
1457
|
+
globs: template_unit_glob_entries
|
|
1458
|
+
),
|
|
1459
|
+
by_file: @analysis_reads.transform_values do |reads|
|
|
1460
|
+
Cache::Descriptor.new(
|
|
1461
|
+
files: reads.grep(Cache::Descriptor::FileEntry).uniq,
|
|
1462
|
+
globs: reads.grep(Cache::Descriptor::GlobEntry).uniq
|
|
1463
|
+
)
|
|
1464
|
+
end
|
|
1465
|
+
)
|
|
1466
|
+
end
|
|
1467
|
+
|
|
1468
|
+
# Whether every boundary row that appeared or changed during the per-file loop is one some file's analysis
|
|
1469
|
+
# was credited with. A row outside every file's attribution would ride this run's `run` rows only, and the
|
|
1470
|
+
# next narrowed run — which does not repeat the read — would drop it.
|
|
1471
|
+
def analysis_reads_attributed?
|
|
1472
|
+
post = @post_analysis_plugin_rows || plugin_boundary_rows
|
|
1473
|
+
pre = @pre_analysis_plugin_rows || post
|
|
1474
|
+
credited = @analysis_reads.each_value.flat_map(&:itself).to_set
|
|
1475
|
+
(post.files - pre.files).all? { |row| credited.include?(row) } &&
|
|
1476
|
+
(post.globs - pre.globs).all? { |row| credited.include?(row) }
|
|
1477
|
+
end
|
|
1478
|
+
|
|
1479
|
+
# ADR-45 WD2 (#1507) — the rows a FULL run records for the inputs an incremental recheck does not re-derive,
|
|
1480
|
+
# which the session therefore carries unchanged from the full run that recorded them to every later slot:
|
|
1481
|
+
#
|
|
1482
|
+
# - the signature tree the plain slot records (`RbsDescriptor.file_entries` / `.glob_entries` over the loader
|
|
1483
|
+
# roots this run's analysis resolved), and an existence row per signature root, configured or the
|
|
1484
|
+
# auto-detected `sig` — a recheck rebuilds no environment unless its closure is non-empty, and the snapshot
|
|
1485
|
+
# fingerprint digests the same roots (configured, or the auto-detected `sig`), so a change to one drops the
|
|
1486
|
+
# snapshot and forces a full run rather than reaching a recheck;
|
|
1487
|
+
# - the discovered-not-analysed files (#684) and a listing row per discovery root — a narrowed recheck does
|
|
1488
|
+
# not widen discovery at all;
|
|
1489
|
+
# - every `pre_eval:` file outside `files` (a stat row, or an absence row) — the session tracks only analysed
|
|
1490
|
+
# files, and one that is analysed is guarded by its own row.
|
|
1491
|
+
#
|
|
1492
|
+
# Recomputing any of these on a recheck would vouch for the tree the recheck saw while its answer still
|
|
1493
|
+
# carries rows computed before the change, so a change to one keeps the probe declining until the next full
|
|
1494
|
+
# run. nil when this process analysed no file itself — a pool run, or a run with no files.
|
|
1495
|
+
#
|
|
1496
|
+
# Split by what identifies the input. `pinned` holds the signature files outside the project's own signature
|
|
1497
|
+
# roots, which the slot's key identifies already: the engine's own (`data/`, a bundled plugin's `sig/`, a
|
|
1498
|
+
# gem overlay) by the engine slot, a gem's `sig/` and an `rbs collection` directory by the lockfiles. `owned`
|
|
1499
|
+
# holds the rest, which only their rows identify. The session's write guard refuses an `owned` row whose
|
|
1500
|
+
# change time it cannot read, and passes over a `pinned` one.
|
|
1501
|
+
BaselineRows = Data.define(:owned, :pinned)
|
|
1502
|
+
|
|
1503
|
+
SignatureRoots = Struct.new(:signature_paths)
|
|
1504
|
+
private_constant :SignatureRoots
|
|
1505
|
+
|
|
1506
|
+
# @param files — the run's analysed-path set.
|
|
1507
|
+
def baseline_dependency_rows(files:)
|
|
1508
|
+
return nil if pool_mode? || !@collect_stats || @analyzed_files.empty?
|
|
1509
|
+
|
|
1510
|
+
own, pinned = signature_rows
|
|
1511
|
+
BaselineRows.new(
|
|
1512
|
+
owned: Cache::Descriptor.new(
|
|
1513
|
+
files: own.files + existence_entries(signature_root_candidates) +
|
|
1514
|
+
discovery_file_entries({ files: files }) + unanalysed_pre_eval_entries(files),
|
|
1515
|
+
globs: (own.globs + discovery_glob_entries).uniq
|
|
1516
|
+
),
|
|
1517
|
+
pinned: pinned
|
|
1518
|
+
)
|
|
1519
|
+
end
|
|
1520
|
+
|
|
1521
|
+
# The signature tree's rows, the project's own and the rest.
|
|
1522
|
+
def signature_rows
|
|
1523
|
+
roots = SignatureRoots.new(@snapshots.signature_paths)
|
|
1524
|
+
own = signature_root_candidates.map { |root| File.join(File.absolute_path(root.to_s), "") }
|
|
1525
|
+
own_files, other_files = Cache::RbsDescriptor.file_entries(roots, comparator: :stat)
|
|
1526
|
+
.partition { |row| within?(row.path, own) }
|
|
1527
|
+
own_globs, other_globs = Cache::RbsDescriptor.glob_entries(roots).partition { |row| within?(row.root, own) }
|
|
1528
|
+
[Cache::Descriptor.new(files: own_files, globs: own_globs.uniq),
|
|
1529
|
+
Cache::Descriptor.new(files: other_files, globs: other_globs.uniq)]
|
|
1530
|
+
end
|
|
1531
|
+
|
|
1532
|
+
def within?(path, roots)
|
|
1533
|
+
directory = File.join(File.absolute_path(path.to_s), "")
|
|
1534
|
+
roots.any? { |root| directory.start_with?(root) }
|
|
1535
|
+
end
|
|
1536
|
+
|
|
1537
|
+
# The project's own signature roots, whether or not they exist: the configured `signature_paths:`, or the
|
|
1538
|
+
# `sig` directory `Environment.default_signature_paths` auto-detects when none are configured. The
|
|
1539
|
+
# signature rows' listing rows cover a file appearing under a root that existed; only an existence row
|
|
1540
|
+
# covers the root itself appearing.
|
|
1541
|
+
def signature_root_candidates
|
|
1542
|
+
@configuration.signature_paths || ["sig"]
|
|
1543
|
+
end
|
|
1544
|
+
|
|
1545
|
+
# One names-mode listing row per directory the discovery widening globbed, so a file appearing there is seen
|
|
1546
|
+
# as well as an edit to one that existed.
|
|
1547
|
+
def discovery_glob_entries
|
|
1548
|
+
return [] if @project_discovery_expansion.nil?
|
|
1549
|
+
|
|
1550
|
+
@configuration.paths.select { |root| File.directory?(root) }.map do |root|
|
|
1551
|
+
Cache::Descriptor::GlobEntry.compute(root: root, pattern: PathExpansion::RUBY_GLOB, mode: :names)
|
|
1552
|
+
end
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
def unanalysed_pre_eval_entries(files)
|
|
1556
|
+
analysed = files.to_set { |path| File.absolute_path(path) }
|
|
1557
|
+
outside = @configuration.pre_eval.reject { |path| analysed.include?(File.absolute_path(path)) }
|
|
1558
|
+
outside.map do |path|
|
|
1559
|
+
if File.file?(path)
|
|
1560
|
+
Cache::Descriptor::FileEntry.stat(path: path, digest: Cache::FileDigest.hexdigest(path))
|
|
1561
|
+
else
|
|
1562
|
+
existence_entries([path]).first
|
|
1563
|
+
end
|
|
1564
|
+
end
|
|
1565
|
+
end
|
|
1566
|
+
|
|
1567
|
+
def existence_entries(paths)
|
|
1568
|
+
paths.uniq.map do |path|
|
|
1569
|
+
physical = @buffer ? @buffer.resolve(path) : path
|
|
1570
|
+
if File.exist?(physical)
|
|
1571
|
+
Cache::Descriptor::FileEntry.present(path: physical)
|
|
1572
|
+
else
|
|
1573
|
+
Cache::Descriptor::FileEntry.absent(path: physical)
|
|
1327
1574
|
end
|
|
1328
1575
|
end
|
|
1329
|
-
Cache::Descriptor.new(files: entries, globs: globs.uniq)
|
|
1330
1576
|
end
|
|
1577
|
+
private :plugin_boundary_rows, :producer_watch_glob_entries, :existence_entries,
|
|
1578
|
+
:analysis_reads_attributed?, :signature_root_candidates, :discovery_glob_entries,
|
|
1579
|
+
:unanalysed_pre_eval_entries, :signature_rows, :within?
|
|
1331
1580
|
|
|
1332
1581
|
# Issue #684 — the project files this run DISCOVERED but did not analyse. A widened run's diagnostics
|
|
1333
1582
|
# are a function of them (that is the entire point: the method whose absence would have been reported
|
|
@@ -1483,8 +1732,10 @@ module Rigor
|
|
|
1483
1732
|
end
|
|
1484
1733
|
|
|
1485
1734
|
# The tables only check rules read beyond dispatch — the issue #1120 refinements and the issue #1367
|
|
1486
|
-
# global-write census
|
|
1735
|
+
# global-write census — and the ADR-119 WD1 siblings of the paired members. Extracted to keep
|
|
1736
|
+
# {#apply_discovery_result} under its ABC budget.
|
|
1487
1737
|
def apply_discovery_call_surface_tables(discovery)
|
|
1738
|
+
@project_discovery_siblings = discovery.siblings
|
|
1488
1739
|
@project_discovered_refinements = discovery.discovered_refinements
|
|
1489
1740
|
@project_discovered_global_write_census = discovery.discovered_global_write_census
|
|
1490
1741
|
end
|
|
@@ -1496,6 +1747,7 @@ module Rigor
|
|
|
1496
1747
|
@project_discovered_includes = discovery.discovered_includes
|
|
1497
1748
|
@project_discovered_prepends = discovery.discovered_prepends
|
|
1498
1749
|
@project_discovered_extends = discovery.discovered_extends
|
|
1750
|
+
@project_unpositioned_mixins = discovery.unpositioned_mixins
|
|
1499
1751
|
end
|
|
1500
1752
|
|
|
1501
1753
|
# Internal: builds the deferred cross-file discovery tables at most once per run and adopts them.
|
|
@@ -1512,7 +1764,7 @@ module Rigor
|
|
|
1512
1764
|
|
|
1513
1765
|
@project_discovery_done = true
|
|
1514
1766
|
# Issue #684 — the cross-file index spans the PROJECT even when the analysis targets a file list.
|
|
1515
|
-
expansion = @project_discovery_expansion || expansion
|
|
1767
|
+
expansion = @project_discovery_expansion || once_each(expansion)
|
|
1516
1768
|
if @collect_seed_bundles
|
|
1517
1769
|
# ADR-85 WD2 — rebuild discovery from the prior run's bundles (re-walking only changed files) and
|
|
1518
1770
|
# capture the refreshed bundle set for the session to persist.
|
|
@@ -1546,11 +1798,42 @@ module Rigor
|
|
|
1546
1798
|
# Excluded, each because its expansion is already the one it means: the LSP `prebuilt:` path (which
|
|
1547
1799
|
# seeds discovery from a snapshot), editor `buffer:` mode, `run_source`'s in-memory single file, and
|
|
1548
1800
|
# ADR-46's `analyze_only` subset mode (which already passes the full expansion and filters later).
|
|
1801
|
+
#
|
|
1802
|
+
# Issue #1556 — the widened set holds each FILE once. `.rigor.yml`'s `paths:` load as absolute paths and a
|
|
1803
|
+
# path argument usually does not, so `rigor check lib` widened discovery over both spellings of every file:
|
|
1804
|
+
# the pre-pass then saw two writers for every constant a file declares and withheld its publication (#644),
|
|
1805
|
+
# and every file was a discovered-not-analysed dependency of the run's cache entry.
|
|
1806
|
+
#
|
|
1807
|
+
# The widening decision itself still compares the paths as spelled. `exclude:` patterns match a path as it
|
|
1808
|
+
# is spelled, so until #1576 is fixed the configured absolute `lib` can discover files the argument `lib`
|
|
1809
|
+
# excludes (a relative `lib/gen/**` pattern never matches `/abs/lib/gen/x.rb`), and treating the two as the
|
|
1810
|
+
# same root would drop those files from discovery.
|
|
1549
1811
|
def project_discovery_expansion(paths, expansion)
|
|
1550
1812
|
return nil unless widen_discovery_to_project?(paths)
|
|
1551
1813
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1814
|
+
analysed = expansion.fetch(:files)
|
|
1815
|
+
files = discovery_files(expand_paths(@configuration.paths | paths).fetch(:files), analysed)
|
|
1816
|
+
# Overlapping arguments (`lib lib/user.rb`) name an analysed file twice; the widened set names it once.
|
|
1817
|
+
files.size > analysed.uniq { |path| File.absolute_path(path) }.size ? { files: files, errors: [] } : nil
|
|
1818
|
+
end
|
|
1819
|
+
|
|
1820
|
+
# The expansion with each file once, first spelling kept: overlapping arguments (`lib ./lib`) that do not
|
|
1821
|
+
# widen discovery still name a file twice, and the pre-pass would take every constant in it for one written
|
|
1822
|
+
# twice (#1556). Symlinked aliases (`src -> lib`) are not folded; that needs a realpath and stays out of scope.
|
|
1823
|
+
def once_each(expansion)
|
|
1824
|
+
files = expansion.fetch(:files)
|
|
1825
|
+
unique = files.uniq { |path| File.absolute_path(path) }
|
|
1826
|
+
unique.size == files.size ? expansion : expansion.merge(files: unique)
|
|
1827
|
+
end
|
|
1828
|
+
|
|
1829
|
+
# Each file once, in project order, under the spelling the analysis uses when it is analysed.
|
|
1830
|
+
def discovery_files(project_files, analysed_files)
|
|
1831
|
+
spelling = analysed_files.to_h { |path| [File.absolute_path(path), path] }
|
|
1832
|
+
seen = Set.new
|
|
1833
|
+
project_files.filter_map do |path|
|
|
1834
|
+
absolute = File.absolute_path(path)
|
|
1835
|
+
spelling.fetch(absolute, path) if seen.add?(absolute)
|
|
1836
|
+
end
|
|
1554
1837
|
end
|
|
1555
1838
|
|
|
1556
1839
|
def widen_discovery_to_project?(paths)
|
|
@@ -1569,7 +1852,7 @@ module Rigor
|
|
|
1569
1852
|
|
|
1570
1853
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result,
|
|
1571
1854
|
:apply_discovery_result, :ensure_project_discovery, :force_eager_discovery?,
|
|
1572
|
-
:project_discovery_expansion, :widen_discovery_to_project?
|
|
1855
|
+
:project_discovery_expansion, :discovery_files, :once_each, :widen_discovery_to_project?
|
|
1573
1856
|
|
|
1574
1857
|
# Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
|
|
1575
1858
|
# `version:`. Prism exposes no version list, so the floor is found empirically — only when a
|
|
@@ -1733,7 +2016,8 @@ module Rigor
|
|
|
1733
2016
|
project_scope_seed: -> { project_scope_seed_tables },
|
|
1734
2017
|
template_units: -> { template_units },
|
|
1735
2018
|
analyze_file: ->(path, environment) { analyze_file(path, environment) },
|
|
1736
|
-
restored_run_level_rows: @restored_run_level_rows
|
|
2019
|
+
restored_run_level_rows: @restored_run_level_rows,
|
|
2020
|
+
feature_sources: -> { required_feature_sources }
|
|
1737
2021
|
)
|
|
1738
2022
|
@diagnostic_aggregator = DiagnosticAggregator.new(
|
|
1739
2023
|
configuration: @configuration,
|
|
@@ -1748,6 +2032,7 @@ module Rigor
|
|
|
1748
2032
|
synthesized_namespaces_snapshot: -> { @snapshots.synthesized_namespaces },
|
|
1749
2033
|
quarantined_signatures_snapshot: -> { @snapshots.quarantined_signatures },
|
|
1750
2034
|
signature_standdowns_snapshot: -> { @snapshots.signature_standdowns },
|
|
2035
|
+
vendored_standdowns_snapshot: -> { @snapshots.vendored_standdowns },
|
|
1751
2036
|
env_build_failure_snapshot: -> { @snapshots.env_build_failure },
|
|
1752
2037
|
definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
|
|
1753
2038
|
hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
|
|
@@ -1832,7 +2117,7 @@ module Rigor
|
|
|
1832
2117
|
end
|
|
1833
2118
|
|
|
1834
2119
|
def shadow_rule_walk?
|
|
1835
|
-
|
|
2120
|
+
ShadowHarness.enabled?
|
|
1836
2121
|
end
|
|
1837
2122
|
|
|
1838
2123
|
def collect_plugin_diagnostics(plugin, path, root, scope, node_result)
|
|
@@ -1977,13 +2262,9 @@ module Rigor
|
|
|
1977
2262
|
# bucket identically to the sequential path.
|
|
1978
2263
|
tables[:run_generation] = @run_generation if @run_generation
|
|
1979
2264
|
tables[:discovered_classes] = @project_discovered_classes unless @project_discovered_classes.empty?
|
|
1980
|
-
tables[:discovered_def_nodes] = @project_discovered_def_nodes unless @project_discovered_def_nodes.empty?
|
|
1981
2265
|
unless @project_discovered_def_nestings.empty?
|
|
1982
2266
|
tables[:discovered_def_nestings] = @project_discovered_def_nestings
|
|
1983
2267
|
end
|
|
1984
|
-
unless @project_discovered_singleton_def_nodes.empty?
|
|
1985
|
-
tables[:discovered_singleton_def_nodes] = @project_discovered_singleton_def_nodes
|
|
1986
|
-
end
|
|
1987
2268
|
seed_def_source_tables(tables)
|
|
1988
2269
|
unless @project_discovered_superclasses.empty?
|
|
1989
2270
|
tables[:discovered_superclasses] = @project_discovered_superclasses
|
|
@@ -1992,34 +2273,65 @@ module Rigor
|
|
|
1992
2273
|
tables[:discovered_header_nestings] = @project_discovered_header_nestings
|
|
1993
2274
|
end
|
|
1994
2275
|
seed_mixin_tables(tables)
|
|
1995
|
-
|
|
1996
|
-
tables[:discovered_method_visibilities] = @project_discovered_method_visibilities
|
|
1997
|
-
end
|
|
1998
|
-
tables[:discovered_methods] = @project_discovered_methods unless @project_discovered_methods.empty?
|
|
1999
|
-
unless @project_discovered_deferred_ranges.empty?
|
|
2000
|
-
tables[:discovered_deferred_ranges] = @project_discovered_deferred_ranges
|
|
2001
|
-
end
|
|
2276
|
+
seed_paired_tables(tables)
|
|
2002
2277
|
seed_call_surface_tables(tables)
|
|
2003
2278
|
seed_opt_in_pre_pass_tables(tables)
|
|
2004
2279
|
seed_member_layout_tables(tables)
|
|
2005
2280
|
seed_dependency_attribution_tables(tables)
|
|
2006
|
-
|
|
2281
|
+
# Only a base seed can bring in half a pair (`seed_paired_tables` writes both halves), so a run without
|
|
2282
|
+
# one skips the copy this costs per file.
|
|
2283
|
+
@discovery_seed ? Scope::DiscoveryIndex.compact_pairs(tables) : tables
|
|
2284
|
+
end
|
|
2285
|
+
|
|
2286
|
+
# ADR-119 WD1 — the members `Scope::DiscoveryIndex::SIBLINGS` pairs, each with its sibling. A pair rides the
|
|
2287
|
+
# seed when either half is non-empty and is left out when both are, which keeps an `discovery_seed:` base's
|
|
2288
|
+
# table for a pair this run computed nothing for. {Scope::DiscoveryIndex#with} takes the two together, and
|
|
2289
|
+
# `compact_pairs` completes a half pair a base seed brought in.
|
|
2290
|
+
PAIRED_MEMBER_IVARS = Scope::DiscoveryIndex::SIBLINGS.keys.to_h do |member|
|
|
2291
|
+
[member, :"@project_#{member}"]
|
|
2292
|
+
end.freeze
|
|
2293
|
+
private_constant :PAIRED_MEMBER_IVARS
|
|
2294
|
+
|
|
2295
|
+
def seed_paired_tables(tables)
|
|
2296
|
+
PAIRED_MEMBER_IVARS.each do |member, ivar|
|
|
2297
|
+
sibling = Scope::DiscoveryIndex::SIBLINGS.fetch(member)
|
|
2298
|
+
table = instance_variable_get(ivar)
|
|
2299
|
+
own = @project_discovery_siblings.fetch(sibling)
|
|
2300
|
+
next if table.empty? && own.empty?
|
|
2301
|
+
|
|
2302
|
+
tables[member] = table
|
|
2303
|
+
tables[sibling] = own
|
|
2304
|
+
end
|
|
2007
2305
|
end
|
|
2008
2306
|
|
|
2009
|
-
# The tables only check rules read: the issue #
|
|
2010
|
-
#
|
|
2011
|
-
#
|
|
2307
|
+
# The tables only check rules read: the issue #1120 refinements (`call.undefined-method`) and the issue #1367
|
|
2308
|
+
# global-write census (the `global.*` write rules); the issue #992 envelopes ride {#seed_paired_tables}. Split
|
|
2309
|
+
# out of {#project_scope_seed_tables} to keep it under the complexity budget.
|
|
2012
2310
|
def seed_call_surface_tables(tables)
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
end
|
|
2016
|
-
tables[:discovered_refinements] = @project_discovered_refinements unless @project_discovered_refinements.empty?
|
|
2311
|
+
refinements = seed_refinements(tables)
|
|
2312
|
+
tables[:discovered_refinements] = refinements unless refinements.nil? || refinements.empty?
|
|
2017
2313
|
# Issue #1367 — read by the `global.*` write rules.
|
|
2018
2314
|
return if @project_discovered_global_write_census.empty?
|
|
2019
2315
|
|
|
2020
2316
|
tables[:discovered_global_write_census] = @project_discovered_global_write_census
|
|
2021
2317
|
end
|
|
2022
2318
|
|
|
2319
|
+
# Issue #1672 — the project's refinements (this run's discovery, else an opt-in `discovery_seed:` base's)
|
|
2320
|
+
# unioned with the opt-in gems' ({DependencySourceInference::Index#refinements}). The gem half comes from
|
|
2321
|
+
# the pre-passes every run takes, warm or cold, so a file re-analysed warm is seeded what a cold run seeds.
|
|
2322
|
+
# The seed is rebuilt per file, so the union is kept while its two inputs are the same objects.
|
|
2323
|
+
def seed_refinements(tables)
|
|
2324
|
+
project = @project_discovered_refinements
|
|
2325
|
+
project = tables[:discovered_refinements] if project.empty?
|
|
2326
|
+
gems = @dependency_source_index.refinements
|
|
2327
|
+
memo = @seed_refinements
|
|
2328
|
+
return memo[2] if memo && memo[0].equal?(project) && memo[1].equal?(gems)
|
|
2329
|
+
|
|
2330
|
+
merged = Inference::ScopeIndexer.merge_refinement_tables(project, gems)
|
|
2331
|
+
@seed_refinements = [project, gems, merged]
|
|
2332
|
+
merged
|
|
2333
|
+
end
|
|
2334
|
+
|
|
2023
2335
|
# The three mixin tables: the ADR-24 instance-side `include` map, its issue #1123 `prepend` twin
|
|
2024
2336
|
# (which carries the instance-ancestor order the include table's set-shaped value cannot), and the
|
|
2025
2337
|
# #898 singleton-side `extend` table. Grouped here because they are read as one picture of a class's
|
|
@@ -2028,12 +2340,14 @@ module Rigor
|
|
|
2028
2340
|
tables[:discovered_includes] = @project_discovered_includes unless @project_discovered_includes.empty?
|
|
2029
2341
|
tables[:discovered_prepends] = @project_discovered_prepends unless @project_discovered_prepends.empty?
|
|
2030
2342
|
tables[:discovered_extends] = @project_discovered_extends unless @project_discovered_extends.empty?
|
|
2343
|
+
tables[:unpositioned_mixins] = @project_unpositioned_mixins unless @project_unpositioned_mixins.empty?
|
|
2031
2344
|
end
|
|
2032
2345
|
|
|
2033
|
-
# ADR-46 slice 1 / issue #644 — the two SOURCE-ATTRIBUTION tables, read
|
|
2346
|
+
# ADR-46 slice 1 / issue #644 — the two SOURCE-ATTRIBUTION tables, read by the recording accessors
|
|
2034
2347
|
# (`Scope#record_class_dependency` for the class-declaration map, `Scope#record_constant_dependency`
|
|
2035
|
-
# for the constant-write map).
|
|
2036
|
-
#
|
|
2348
|
+
# for the constant-write map). The class-declaration map rides every run: an ancestor-order reader also
|
|
2349
|
+
# asks whether a class is declared in more than one file. The constant-write map rides only a recording
|
|
2350
|
+
# run. Extracted to keep {#project_scope_seed_tables} under the complexity budget.
|
|
2037
2351
|
#
|
|
2038
2352
|
# Issue #617 — the census's binding writes, grouped by last segment, ride every run instead: a constant
|
|
2039
2353
|
# compound write whose plain read resolves nothing reads its binding off them, a TYPE rather than an edge.
|
|
@@ -2043,10 +2357,10 @@ module Rigor
|
|
|
2043
2357
|
tables[:constant_writers] = writers unless writers.empty?
|
|
2044
2358
|
shadowers = constant_shadower_index
|
|
2045
2359
|
tables[:constant_shadowers] = shadowers unless shadowers.empty?
|
|
2046
|
-
return unless @record_dependencies
|
|
2047
|
-
|
|
2048
2360
|
tables[:discovered_class_sources] = @project_discovered_class_sources unless
|
|
2049
2361
|
@project_discovered_class_sources.empty?
|
|
2362
|
+
return unless @record_dependencies
|
|
2363
|
+
|
|
2050
2364
|
tables[:constant_sources] = @project_constant_sources unless @project_constant_sources.empty?
|
|
2051
2365
|
end
|
|
2052
2366
|
|
|
@@ -2108,7 +2422,7 @@ module Rigor
|
|
|
2108
2422
|
def memo_files_by_segment
|
|
2109
2423
|
@project_constant_writes.each_with_object({}) do |(name, by_path), files|
|
|
2110
2424
|
set = (files[name.split("::").last] ||= Set.new)
|
|
2111
|
-
by_path.each { |path, descriptor| set << File.
|
|
2425
|
+
by_path.each { |path, descriptor| set << File.absolute_path(path) if memo_descriptor?(descriptor) }
|
|
2112
2426
|
end
|
|
2113
2427
|
end
|
|
2114
2428
|
|
|
@@ -2166,14 +2480,14 @@ module Rigor
|
|
|
2166
2480
|
writers = @project_constant_sources[name]
|
|
2167
2481
|
wildcard = @project_constant_sources["#{Inference::ScopeIndexer::DYNAMIC_TARGET_PREFIX}#{segment}"]
|
|
2168
2482
|
[writers, wildcard].any? do |paths|
|
|
2169
|
-
paths&.any? { |path| !listed.include?(File.
|
|
2483
|
+
paths&.any? { |path| !listed.include?(File.absolute_path(path)) }
|
|
2170
2484
|
end
|
|
2171
2485
|
end
|
|
2172
2486
|
|
|
2173
2487
|
# The listed `pre_eval:` files, expanded so a census path and a configured one compare as the same
|
|
2174
2488
|
# file whichever spelling each arrived in.
|
|
2175
2489
|
def pre_eval_path_set
|
|
2176
|
-
@pre_eval_path_set ||= @configuration.pre_eval.to_set { |path| File.
|
|
2490
|
+
@pre_eval_path_set ||= @configuration.pre_eval.to_set { |path| File.absolute_path(path) }
|
|
2177
2491
|
end
|
|
2178
2492
|
|
|
2179
2493
|
# ADR-46 — seed the instance + singleton `"path:line"` def-source tables (each only when non-empty).
|
|
@@ -2203,11 +2517,18 @@ module Rigor
|
|
|
2203
2517
|
def analyze_file(path, environment)
|
|
2204
2518
|
return analyze_with_effects(path, environment) unless @record_dependencies
|
|
2205
2519
|
|
|
2520
|
+
# ADR-45 WD2 (#1507) — every plugin read this file's analysis makes is attributed to it, so the
|
|
2521
|
+
# incremental session can validate them on a later run that serves this file's answer from cache.
|
|
2522
|
+
@pre_analysis_plugin_rows ||= plugin_boundary_rows
|
|
2523
|
+
reads = []
|
|
2206
2524
|
diagnostics = nil
|
|
2207
|
-
record =
|
|
2208
|
-
|
|
2525
|
+
record = Plugin::IoBoundary.attributing(reads) do
|
|
2526
|
+
DependencyRecorder.record_for(path) do
|
|
2527
|
+
diagnostics = analyze_with_effects(path, environment)
|
|
2528
|
+
end
|
|
2209
2529
|
end
|
|
2210
2530
|
@file_dependencies[path] = record
|
|
2531
|
+
@analysis_reads[path] = reads unless reads.empty?
|
|
2211
2532
|
diagnostics
|
|
2212
2533
|
end
|
|
2213
2534
|
|
|
@@ -2312,7 +2633,8 @@ module Rigor
|
|
|
2312
2633
|
self_call_misses: self_call_misses,
|
|
2313
2634
|
comments: parse_result.comments,
|
|
2314
2635
|
disabled_rules: @configuration.disabled_rules,
|
|
2315
|
-
node_collectors: node_collectors
|
|
2636
|
+
node_collectors: node_collectors,
|
|
2637
|
+
stated_runtime_ruby: @configuration.stated_runtime_ruby
|
|
2316
2638
|
)
|
|
2317
2639
|
diagnostics + plugin_emitted_diagnostics(path, root, scope, node_results)
|
|
2318
2640
|
end
|