rigortype 0.3.9 → 0.4.1
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 -4
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- 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_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +49 -37
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +88 -26
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- 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 +3 -2
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +488 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +179 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +674 -124
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -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 +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +196 -13
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +580 -47
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +112 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +14 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +177 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +132 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../source/constant_path"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module DeclarationWalk
|
|
8
|
+
# What `self`, the cref and `Module.nesting` are at one node of a {DeclarationWalk} (ADR-116 WD5). The walk
|
|
9
|
+
# derives each child's context from its parent's through the transitions below, so every collector reads
|
|
10
|
+
# the same answer for the same node and none of them recomputes it.
|
|
11
|
+
#
|
|
12
|
+
# - `prefix` — the qualified-name segments of the innermost `class` / `module` a name reaches, which is
|
|
13
|
+
# what the tables key lexical facts under: `["Admin", "User"]` inside `module Admin; class User`, `[]`
|
|
14
|
+
# at the top level. It is not always the cref. Directly inside `class << self` the cref is the
|
|
15
|
+
# singleton class, which has no name, and `prefix` stays the enclosing class's while `singleton_cref`
|
|
16
|
+
# says so; below a bare header there (`class D` opens `#<Class:C>::D`, which no name spells) it is
|
|
17
|
+
# `[]`.
|
|
18
|
+
# - `self_owner` — the rebound `self` a `self::` header, write target or eval receiver anchors on. nil
|
|
19
|
+
# while `self` is the lexical class; a prefix while a meta-new or eval-family block rebinds it; `[]`
|
|
20
|
+
# while it names nothing (a `class <<` body, an anonymous factory block, an eval receiver no name
|
|
21
|
+
# reaches).
|
|
22
|
+
# - `singleton_cref` — whether the lexical cref is unnameable. `class <<` sets it, and a header that
|
|
23
|
+
# still names its class re-anchors it.
|
|
24
|
+
# - `nesting` — `Module.nesting` as the ancestry tables record it (issue #682): a nameable
|
|
25
|
+
# `class`/`module` keyword pushes, a `self::` header pushes the prefix its rebound self gives it, and
|
|
26
|
+
# nothing else pushes. nil when the caller does not track it, or when it was lost below a header that
|
|
27
|
+
# renders no name ({#lost_header_body}). The context keeps the two apart: a `self::` header below a
|
|
28
|
+
# rebound self grows a lost chain again, while an untracked one stays nil everywhere.
|
|
29
|
+
# - `scope` — the census scope a collector types an rvalue under, carrying the chain
|
|
30
|
+
# {ScopeIndexer.scope_entering_declaration} stamps at each header. nil when the caller has none.
|
|
31
|
+
# - `source_path` — the path of the file being walked, which an anonymous class's synthetic name carries
|
|
32
|
+
# ({AnonymousMetaClass.name_for}). nil when the caller has none. Read it through
|
|
33
|
+
# {#anonymous_class_path}, which answers each variant of that rule.
|
|
34
|
+
# - `class_body` — whether a `class` / `module` body, a meta-new write's body or an eval-family block's
|
|
35
|
+
# body encloses the node. A `class <<` body and a bare factory block do not set it. It exists for the
|
|
36
|
+
# `anonymous_class_path` variant {#anonymous_class_path} documents, and nothing else reads it.
|
|
37
|
+
#
|
|
38
|
+
# Nameability is derived, not stored: {#unnameable_self?} answers it from the fields above.
|
|
39
|
+
#
|
|
40
|
+
# `nesting` and the chain on `scope` are two fields because the walks that threaded them disagree. The
|
|
41
|
+
# scope's chain is pushed at EVERY header, an unnameable one and the lenient render of a `self::` header
|
|
42
|
+
# included (`class self::D` pushes `Outer::D`); the ancestry chain is not. A move keeps both answers
|
|
43
|
+
# (ADR-116's variant rule); the scope's is wrong below `class <<` (#1520), and converging the two is
|
|
44
|
+
# #1521's.
|
|
45
|
+
#
|
|
46
|
+
# The rules themselves stay in {ScopeIndexer} ({ScopeIndexer.decl_body_context},
|
|
47
|
+
# {ScopeIndexer.meta_new_block_split}, {ScopeIndexer.eval_block_split}), where the walkers not yet ported
|
|
48
|
+
# still call them; this class is the one place a ported collector reaches them from. That is why the
|
|
49
|
+
# entry point is `rigor/inference/declaration_walk`, which loads `ScopeIndexer` and, through it, this file.
|
|
50
|
+
class Context
|
|
51
|
+
EMPTY_PREFIX = [].freeze
|
|
52
|
+
|
|
53
|
+
# The chain below a header that renders no name, held in place of it so that a `self::` header can
|
|
54
|
+
# tell it from an untracked one. {#nesting} answers nil for it; only its identity is read. Not an empty
|
|
55
|
+
# Array: Ruby folds every `[].freeze` literal into one shared object, so the sentinel would be the
|
|
56
|
+
# top level's `[]` chain.
|
|
57
|
+
LOST_NESTING = Object.new.freeze
|
|
58
|
+
private_constant :LOST_NESTING
|
|
59
|
+
|
|
60
|
+
attr_reader :prefix, :self_owner, :singleton_cref, :scope, :source_path, :class_body
|
|
61
|
+
|
|
62
|
+
# A file's top level: `self` is `main`, which the lexical-class convention (a nil `self_owner`)
|
|
63
|
+
# already answers.
|
|
64
|
+
def self.root(scope: nil, nesting: nil, source_path: nil)
|
|
65
|
+
new(EMPTY_PREFIX, nil, false, nesting, scope, source_path, false)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def initialize(prefix, self_owner, singleton_cref, nesting, scope, source_path, class_body)
|
|
69
|
+
@prefix = prefix
|
|
70
|
+
@self_owner = self_owner
|
|
71
|
+
@singleton_cref = singleton_cref
|
|
72
|
+
@nesting = nesting
|
|
73
|
+
@scope = scope
|
|
74
|
+
@source_path = source_path
|
|
75
|
+
@class_body = class_body
|
|
76
|
+
freeze
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def nesting
|
|
80
|
+
@nesting unless @nesting.equal?(LOST_NESTING)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Whether a bare, `self` or `self::` reference here names nothing a table can key on: inside a
|
|
84
|
+
# `class <<` body, under a `self` an eval or factory block left unnamed, or below an unnameable cref
|
|
85
|
+
# that no rebound self re-anchors.
|
|
86
|
+
def unnameable_self?
|
|
87
|
+
ScopeIndexer.unnameable_eval_self?(false, self_owner, prefix, singleton_cref)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The file path an anonymous class created at this node carries in its synthetic name, under the
|
|
91
|
+
# variant of that rule the asking collector follows (see {DeclarationWalk::RULE_VARIANTS}):
|
|
92
|
+
#
|
|
93
|
+
# - `:whole_file` — the walk's rule: the path everywhere, as the evaluator, the dispatcher and the
|
|
94
|
+
# methods walker name the class.
|
|
95
|
+
# - `:outside_class_bodies` — `walk_class_superclasses`' rule, kept so the superclass table stays
|
|
96
|
+
# byte-identical: nil once {#class_body} is set, so `Class.new(P) { }` inside `class C` is keyed
|
|
97
|
+
# `#<Class:L:C>` while the other tables name it `#<Class:path:L:C>` (#1521 item 11).
|
|
98
|
+
def anonymous_class_path(variant)
|
|
99
|
+
case variant
|
|
100
|
+
when :whole_file then source_path
|
|
101
|
+
when :outside_class_bodies then class_body ? nil : source_path
|
|
102
|
+
else raise ArgumentError, "no anonymous_class_path variant #{variant.inspect}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The context a `class` / `module` header gives its body, or nil when the header renders no prefix
|
|
107
|
+
# (a parsed header always renders one; nil keeps the transition total). `self` is the class again,
|
|
108
|
+
# so the rebound self resets.
|
|
109
|
+
def declaration_body(node)
|
|
110
|
+
self_decl, child_prefix, child_cref =
|
|
111
|
+
ScopeIndexer.decl_body_context(node, prefix, self_owner, singleton_cref)
|
|
112
|
+
return nil unless child_prefix
|
|
113
|
+
|
|
114
|
+
Context.new(child_cref ? EMPTY_PREFIX : child_prefix, nil, child_cref,
|
|
115
|
+
declaration_nesting(node, self_decl, child_cref),
|
|
116
|
+
ScopeIndexer.scope_entering_declaration(scope, node.constant_path), source_path, true)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The context of a `class << expr` body. `self` is the singleton class, which no `self::` path names,
|
|
120
|
+
# and the cref is unnameable until a nameable header re-anchors it. The rung Ruby pushes names nothing,
|
|
121
|
+
# so neither chain moves.
|
|
122
|
+
def singleton_class_body
|
|
123
|
+
Context.new(prefix, EMPTY_PREFIX, true, @nesting, scope, source_path, class_body)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The body of a `K = Class.new { … }`-shaped write: `self` is the class the write names (`[]` when it
|
|
127
|
+
# names none). The cref and both chains stay lexical.
|
|
128
|
+
def meta_new_body(owner)
|
|
129
|
+
Context.new(prefix, owner, singleton_cref, @nesting, scope, source_path, true)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The body of an eval-family block: `self` is the receiver (`[]` when no name reaches it). The cref and
|
|
133
|
+
# both chains stay lexical.
|
|
134
|
+
def eval_body(owner)
|
|
135
|
+
Context.new(prefix, owner, singleton_cref, @nesting, scope, source_path, true)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The body of a bare factory block under the walk's rule: `self` is an anonymous class no name reaches.
|
|
139
|
+
# The cref and both chains stay lexical.
|
|
140
|
+
def factory_body
|
|
141
|
+
Context.new(prefix, EMPTY_PREFIX, singleton_cref, @nesting, scope, source_path, class_body)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# The body of a `class` / `module` whose header renders no name (`class foo`, `module` followed by a
|
|
145
|
+
# `def`: parse-error shapes), under the `:body_with_lost_nesting` variant of the `unrendered_header`
|
|
146
|
+
# rule (see {DeclarationWalk::RULE_VARIANTS}). `self` is the class again; the chain is kept below an
|
|
147
|
+
# unnameable cref and lost otherwise, because nothing can be pushed for a header with no name. An
|
|
148
|
+
# untracked chain stays untracked.
|
|
149
|
+
def lost_header_body
|
|
150
|
+
lost = singleton_cref || @nesting.nil? ? @nesting : LOST_NESTING
|
|
151
|
+
Context.new(prefix, nil, singleton_cref, lost, scope, source_path, true)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The prefix the meta-new and eval-family splits resolve against, under the variant of the
|
|
155
|
+
# `lexical_prefix` rule the asking collectors follow: `:prefix` is the walk's rule; `:nesting_head` is
|
|
156
|
+
# `walk_def_nestings`' — the innermost `Module.nesting` entry split into segments, or `[]`. The two
|
|
157
|
+
# differ below a compact header (`["Admin::Widget"]` against `["Admin", "Widget"]`, which resolve an
|
|
158
|
+
# eval receiver through different chains) and below an unnameable cref (`[]` against the enclosing
|
|
159
|
+
# entry) (#1521).
|
|
160
|
+
def lexical_prefix(variant = :prefix)
|
|
161
|
+
case variant
|
|
162
|
+
when :prefix then prefix
|
|
163
|
+
when :nesting_head then (head = nesting&.first) ? head.split("::") : EMPTY_PREFIX
|
|
164
|
+
else raise UnknownVariant, "no lexical_prefix variant #{variant.inspect}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# `[enclosing_parts, body, body_self]` for a `K = Class.new { … }`-shaped write, or nil when its
|
|
169
|
+
# rvalue is not the idiom ({ScopeIndexer.meta_new_block_split}).
|
|
170
|
+
def meta_new_split(node, variant = :prefix)
|
|
171
|
+
ScopeIndexer.meta_new_block_split(node, lexical_prefix(variant), self_owner, singleton_cref)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# `[enclosing_parts, body, eval_self]` for an eval-family call with a block, or nil
|
|
175
|
+
# ({ScopeIndexer.eval_block_split}).
|
|
176
|
+
def eval_split(node, variant = :prefix)
|
|
177
|
+
ScopeIndexer.eval_block_split(node, lexical_prefix(variant), self_owner, singleton_cref)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
# An untracked chain stays untracked. A `self::` header pushes the prefix its rebound self gives it even
|
|
183
|
+
# below a lost chain, which is how `walk_def_nestings` has always read `[joined, *nil]`; any other
|
|
184
|
+
# header keeps a lost chain lost.
|
|
185
|
+
def declaration_nesting(node, self_decl, child_cref)
|
|
186
|
+
return @nesting if child_cref || @nesting.nil?
|
|
187
|
+
return [self_decl.join("::"), *nesting].freeze if self_decl
|
|
188
|
+
return @nesting if @nesting.equal?(LOST_NESTING)
|
|
189
|
+
|
|
190
|
+
Source::ConstantPath.pushed_nesting(@nesting, node.constant_path) || @nesting
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module DeclarationWalk
|
|
6
|
+
# A failure of the walk's contract rather than of the file being walked: a shadow-harness divergence
|
|
7
|
+
# ({Shadow::Divergence}) or a collector naming a variant no rule has ({UnknownVariant}). The discovery
|
|
8
|
+
# passes skip a file they cannot read or parse, and the run-result cache path falls back to an uncached
|
|
9
|
+
# run on an error; both let this through, because skipping the file would hide the failure and drop the
|
|
10
|
+
# file from the project index. In a file's own index the per-file rescue still reports it as an error
|
|
11
|
+
# row on the file.
|
|
12
|
+
class ContractError < StandardError; end
|
|
13
|
+
|
|
14
|
+
# A collector's `VARIANTS` names a rule or a variant {RULE_VARIANTS} does not have.
|
|
15
|
+
class UnknownVariant < ContractError; end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../analysis/shadow_harness"
|
|
6
|
+
require_relative "errors"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
module DeclarationWalk
|
|
11
|
+
# ADR-53 Track B's shadow harness, extended from the rule collectors to the discovery tables (ADR-116
|
|
12
|
+
# WD5). With `RIGOR_SHADOW_RULE_WALK` set, a table a {DeclarationWalk} collector builds is compared with
|
|
13
|
+
# the one the legacy walker it replaces builds from the same input, and the first difference raises
|
|
14
|
+
# {Divergence}. Unset, the legacy walker never runs.
|
|
15
|
+
#
|
|
16
|
+
# Equality is stricter than `Hash#==`: key ORDER counts, and so do a container's frozenness and
|
|
17
|
+
# `compare_by_identity`. A table is iterated when it is merged, folded and written to a seed bundle, so
|
|
18
|
+
# an order change is a change in what later stages see even where `==` holds. Not compared yet: a
|
|
19
|
+
# `Set`'s order and frozenness (a `Set` compares by `==` alone), a Hash's `default` / `default_proc`,
|
|
20
|
+
# and the fields inside a `Data` or `Struct` value beyond its `==`. No ported table holds one yet; a
|
|
21
|
+
# port whose table does extends {#first_difference} first.
|
|
22
|
+
#
|
|
23
|
+
# ## Running it
|
|
24
|
+
#
|
|
25
|
+
# Set the variable for the whole `rigor check` process, pool workers included: `RIGOR_SHADOW_RULE_WALK=1
|
|
26
|
+
# rigor check …`. Where a divergence surfaces depends on which pass built the table:
|
|
27
|
+
#
|
|
28
|
+
# - in a file's own index ({ScopeIndexer.index}), the raise lands in the per-file rescue and reports
|
|
29
|
+
# as an error diagnostic on the file, carrying the message, the same way a rule-walk divergence does;
|
|
30
|
+
# - in the cross-file project pre-pass (the superclass tables are built there too), the raise aborts
|
|
31
|
+
# the run with the message. {Divergence} is a {ContractError}, which the pre-pass loops, the
|
|
32
|
+
# run-result cache fallback and the parameter-inference discovery seed all let through (see
|
|
33
|
+
# {ContractError}).
|
|
34
|
+
#
|
|
35
|
+
# A run that completes with no such row found no divergence. The rule-walk harness reads the same
|
|
36
|
+
# variable ({Analysis::ShadowHarness}).
|
|
37
|
+
#
|
|
38
|
+
# A warm cache cannot answer for a run the harness did not check. The ADR-45 run-result cache (and the
|
|
39
|
+
# ADR-87 boot-slim probe and the effects sidecar, which share its key) and the ADR-46 incremental
|
|
40
|
+
# snapshot, seed bundles included, key on {Analysis::ShadowHarness.cache_identity}. So a run with the
|
|
41
|
+
# variable never replays a result computed without it, and a divergence row cached under it never
|
|
42
|
+
# replays into a run without it. A warm hit WITH the variable replays what the same engine computed on
|
|
43
|
+
# the same inputs with the harness on, divergence rows included; `--no-cache` sends every file through
|
|
44
|
+
# the check again.
|
|
45
|
+
module Shadow
|
|
46
|
+
ENV_KEY = Analysis::ShadowHarness::ENV_KEY
|
|
47
|
+
|
|
48
|
+
class Divergence < ContractError; end
|
|
49
|
+
|
|
50
|
+
module_function
|
|
51
|
+
|
|
52
|
+
def enabled?
|
|
53
|
+
Analysis::ShadowHarness.enabled?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# `walk`, after checking it against the table the block builds when the harness is on. The block is
|
|
57
|
+
# the legacy walker, so a disabled harness never pays for it.
|
|
58
|
+
def verified(table, path, walk)
|
|
59
|
+
verify!(table, path, walk, yield) if enabled?
|
|
60
|
+
walk
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def verify!(table, path, walk, legacy)
|
|
64
|
+
difference = first_difference(legacy, walk, "")
|
|
65
|
+
return if difference.nil?
|
|
66
|
+
|
|
67
|
+
raise Divergence,
|
|
68
|
+
"#{ENV_KEY} divergence: discovery table `#{table}` for #{path || '(no path)'}: #{difference}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A description of the first place `walk` differs from `legacy`, or nil when they are the same.
|
|
72
|
+
# `at` is the key path walked so far, rendered as Ruby index syntax.
|
|
73
|
+
def first_difference(legacy, walk, at)
|
|
74
|
+
if legacy.is_a?(Hash) && walk.is_a?(Hash)
|
|
75
|
+
hash_difference(legacy, walk, at)
|
|
76
|
+
elsif legacy.is_a?(Array) && walk.is_a?(Array)
|
|
77
|
+
array_difference(legacy, walk, at)
|
|
78
|
+
elsif legacy.class != walk.class || legacy != walk
|
|
79
|
+
"#{location(at)}: legacy #{render(legacy)}, declaration walk #{render(walk)}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def hash_difference(legacy, walk, at)
|
|
84
|
+
container_difference(legacy, walk, at) ||
|
|
85
|
+
key_difference(legacy, walk, at) ||
|
|
86
|
+
legacy.each_key.lazy.filter_map do |key|
|
|
87
|
+
first_difference(legacy[key], walk[key], "#{at}[#{render_key(key)}]")
|
|
88
|
+
end.first
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def array_difference(legacy, walk, at)
|
|
92
|
+
container_difference(legacy, walk, at) ||
|
|
93
|
+
(legacy.size == walk.size ? nil : size_difference(legacy, walk, at)) ||
|
|
94
|
+
legacy.each_index.lazy.filter_map do |index|
|
|
95
|
+
first_difference(legacy[index], walk[index], "#{at}[#{index}]")
|
|
96
|
+
end.first
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def container_difference(legacy, walk, at)
|
|
100
|
+
if legacy.frozen? != walk.frozen?
|
|
101
|
+
"#{location(at)}: legacy frozen=#{legacy.frozen?}, declaration walk frozen=#{walk.frozen?}"
|
|
102
|
+
elsif legacy.is_a?(Hash) && legacy.compare_by_identity? != walk.compare_by_identity?
|
|
103
|
+
"#{location(at)}: legacy compare_by_identity=#{legacy.compare_by_identity?}, " \
|
|
104
|
+
"declaration walk compare_by_identity=#{walk.compare_by_identity?}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def key_difference(legacy, walk, at)
|
|
109
|
+
missing = legacy.each_key.find { |key| !walk.key?(key) }
|
|
110
|
+
return "#{location(at)}: key #{render_key(missing)} only in legacy" if missing
|
|
111
|
+
|
|
112
|
+
extra = walk.each_key.find { |key| !legacy.key?(key) }
|
|
113
|
+
return "#{location(at)}: key #{render_key(extra)} only in declaration walk" if extra
|
|
114
|
+
|
|
115
|
+
index = legacy.keys.zip(walk.keys).index { |ours, theirs| !ours.eql?(theirs) }
|
|
116
|
+
return nil if index.nil?
|
|
117
|
+
|
|
118
|
+
"#{location(at)}: key order differs at position #{index}: legacy #{render_key(legacy.keys[index])}, " \
|
|
119
|
+
"declaration walk #{render_key(walk.keys[index])}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def size_difference(legacy, walk, at)
|
|
123
|
+
"#{location(at)}: legacy has #{legacy.size} elements, declaration walk #{walk.size}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def location(at)
|
|
127
|
+
at.empty? ? "the table" : at
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# A key as it inspects, except a Prism node, which keys the identity tables and would inspect as its
|
|
131
|
+
# whole subtree: that renders as its class, its name where it has one, and where it starts.
|
|
132
|
+
def render_key(key)
|
|
133
|
+
return key.inspect unless key.is_a?(Prism::Node)
|
|
134
|
+
|
|
135
|
+
name = key.respond_to?(:name) ? " #{key.name}" : ""
|
|
136
|
+
start = key.location
|
|
137
|
+
"#<#{key.class.name.delete_prefix('Prism::')}#{name} at #{start.start_line}:#{start.start_column}>"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# A Rigor type renders as it describes itself; anything else as it inspects.
|
|
141
|
+
def render(value)
|
|
142
|
+
value.respond_to?(:describe) ? value.describe : value.inspect
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|