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
|
@@ -5,6 +5,7 @@ require "fileutils"
|
|
|
5
5
|
require "zlib"
|
|
6
6
|
|
|
7
7
|
require_relative "../../version"
|
|
8
|
+
require_relative "../../cache/store"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
module Analysis
|
|
@@ -36,6 +37,7 @@ module Rigor
|
|
|
36
37
|
File.expand_path("scan.rb", __dir__),
|
|
37
38
|
File.expand_path(__FILE__)
|
|
38
39
|
].freeze
|
|
40
|
+
Ractor.make_shareable(SOURCE_FILES)
|
|
39
41
|
|
|
40
42
|
# A file modified within this window of the recording instant is computed but never
|
|
41
43
|
# recorded — the FileDigest racy-write guard, one tier simpler because a refused signature
|
|
@@ -93,7 +95,7 @@ module Rigor
|
|
|
93
95
|
keep = @entries.merge(@live).select { |(_, path), _| File.exist?(path) }
|
|
94
96
|
FileUtils.mkdir_p(File.dirname(@path))
|
|
95
97
|
tmp = "#{@path}.#{Process.pid}.tmp"
|
|
96
|
-
File.binwrite(tmp, Zlib::Deflate.deflate(Marshal.dump([@header, keep])))
|
|
98
|
+
File.binwrite(tmp, Zlib::Deflate.deflate(Marshal.dump([@header, keep]), Cache::Store::DEFLATE_LEVEL))
|
|
97
99
|
File.rename(tmp, @path)
|
|
98
100
|
nil
|
|
99
101
|
rescue StandardError
|
|
@@ -656,6 +656,90 @@ module Rigor
|
|
|
656
656
|
since: "0.1.2"
|
|
657
657
|
),
|
|
658
658
|
|
|
659
|
+
CheckRules::RULE_GLOBAL_WRITE_TYPE_MISMATCH => Entry.new(
|
|
660
|
+
id: CheckRules::RULE_GLOBAL_WRITE_TYPE_MISMATCH,
|
|
661
|
+
summary: "A special global is assigned a literal its setter rejects (TypeError at runtime).",
|
|
662
|
+
fires_when: [
|
|
663
|
+
"The write is `$g = literal` to a special whose setter checks the value: `$/` / `$-0`, `$,` and " \
|
|
664
|
+
"`$\\` (a String or nil), `$;` / `$-F` (a String, a Regexp, nil, or an object with `to_str`), `$~` " \
|
|
665
|
+
"(a MatchData or nil), `$0` / `$PROGRAM_NAME` (a String or an object with `to_str`), `$.` (an " \
|
|
666
|
+
"Integer, a Float, or an object with `to_int`), `$-i` (a String, nil, false, or an object with " \
|
|
667
|
+
"`to_str`), and `$stdout` / `$>` / `$stderr` (an object that responds to `write`, ADR-117 WD2).",
|
|
668
|
+
"The value is a literal node — an Integer, Float, Rational or imaginary literal, a String, Symbol " \
|
|
669
|
+
"or Regexp literal (interpolated or not), an Array or Hash literal, or `nil` / `true` / `false`, " \
|
|
670
|
+
"parenthesised or not — whose class the setter does not take: `$/ = 1`, `$/ = /x/`, `$~ = \"x\"`, " \
|
|
671
|
+
"`$0 = nil`, `$. = \"3\"`, `$stdout = 1`.",
|
|
672
|
+
"Where the setter also converts (`to_str`, `to_int`) or asks for `write`, the literal's object " \
|
|
673
|
+
"cannot answer that method: RBS gives its class neither the method nor a `method_missing` / " \
|
|
674
|
+
"`respond_to_missing?` / `respond_to?` of its own, and the program defines none of them anywhere.",
|
|
675
|
+
"The envelope is the interpreter's setter, not the global's RBS declaration: `$/ = /x/` fires " \
|
|
676
|
+
"although `$;` takes a Regexp."
|
|
677
|
+
],
|
|
678
|
+
does_not_fire_when: [
|
|
679
|
+
"The special's setter accepts every value: `$stdin` (never checked, ADR-117 WD2), `$_`, `$VERBOSE`, " \
|
|
680
|
+
"`$DEBUG`, `$=`; nor `$@`, whose setter depends on whether `$!` is set.",
|
|
681
|
+
"The value is not a literal — a variable, a method call, a constant, a conditional — whatever its " \
|
|
682
|
+
"inferred type: a class RBS declares without `write` or `to_str` does not prove the object lacks it.",
|
|
683
|
+
"RBS gives the literal's class the method or a hatch of its own (a `sig/` reopening or `include` " \
|
|
684
|
+
"counts).",
|
|
685
|
+
"The program — any project or `pre_eval:` file — defines the method or a `method_missing` / " \
|
|
686
|
+
"`respond_to_missing?` / `respond_to?` anywhere, in any spelling (`def`, `define_method`, " \
|
|
687
|
+
"`define_singleton_method`, `alias`, `alias_method`, `attr_*`, a delegation macro, a string `eval`, " \
|
|
688
|
+
"the same through `send`) and on any receiver, or defines a method whose name no literal spells (a " \
|
|
689
|
+
"computed `define_method` or `send` name, a string `eval` whose code is not a plain literal, a name " \
|
|
690
|
+
"whose bytes are not valid in its encoding). " \
|
|
691
|
+
"This is blanket: no census of where a definition lands can be complete, so the literal of every " \
|
|
692
|
+
"class declines, whatever the definition's owner.",
|
|
693
|
+
"An ancestor's surface is rewritten from outside (`Integer.include(M)`, `Object.include(M)`, a string " \
|
|
694
|
+
"`class_eval`), or a top-level `include` / `prepend` / `extend`, or an ancestor's `include`, names a " \
|
|
695
|
+
"module RBS does not rule out.",
|
|
696
|
+
"For `$stdout` / `$>` / `$stderr`, a refinement that may add `write`, to any class, and a `using` in " \
|
|
697
|
+
"effect at the write. Which class it refines is not followed. The `to_str` / `to_int` conversions, and a " \
|
|
698
|
+
"refined `respond_to_missing?` / `respond_to?`, ignore refinements, so those still fire.",
|
|
699
|
+
"Any project or `pre_eval:` file aliases the special (`alias $stdout $out`, on either side).",
|
|
700
|
+
"The write is `$g op= v`, `$g ||= v`, `$g &&= v`, a multiple-assignment target, a `for` index, or a " \
|
|
701
|
+
"`rescue => $g` reference: their value is not type-checked.",
|
|
702
|
+
"The write sits in the dead arm of a decidable version guard. Other code that never runs (`if " \
|
|
703
|
+
"false`, after `return`) is not recognised, and a write there still fires."
|
|
704
|
+
],
|
|
705
|
+
suppression: "`# rigor:disable global.write-type-mismatch` on the write.",
|
|
706
|
+
severity_authored: :error,
|
|
707
|
+
severity_by_profile: { lenient: :warning, balanced: :error, strict: :error },
|
|
708
|
+
# A literal's class is exact, the setter's envelope is the running interpreter's, and every way a program can
|
|
709
|
+
# give the object the method the setter asks for declines; the write then raises every time it runs.
|
|
710
|
+
evidence_tier: :high,
|
|
711
|
+
since: "0.4.0"
|
|
712
|
+
),
|
|
713
|
+
|
|
714
|
+
CheckRules::RULE_GLOBAL_READONLY_WRITE => Entry.new(
|
|
715
|
+
id: CheckRules::RULE_GLOBAL_READONLY_WRITE,
|
|
716
|
+
summary: "A read-only special global is written (NameError at runtime).",
|
|
717
|
+
fires_when: [
|
|
718
|
+
"The write is `$g = value`, `$g op= value`, or a target of a multiple assignment (`$g, x = ...`, " \
|
|
719
|
+
"`*$g`) — forms that always write — to a special Ruby defines read-only: `$!`, `$$`, `$?`, `$<`, " \
|
|
720
|
+
"`$FILENAME`, `$*`, `$:` / `$LOAD_PATH` / `$-I`, `$\"` / `$LOADED_FEATURES`, `$-W`, `$-p`, `$-l`, " \
|
|
721
|
+
"`$-a`.",
|
|
722
|
+
"Whatever the value: the setter raises `NameError` (`$! is a read-only variable`) before it " \
|
|
723
|
+
"looks at it."
|
|
724
|
+
],
|
|
725
|
+
does_not_fire_when: [
|
|
726
|
+
"The write is `$g ||= v` or `$g &&= v`, which writes only when the current value is falsy " \
|
|
727
|
+
"(truthy): `$LOAD_PATH ||= []` never writes.",
|
|
728
|
+
"The write is a `for` index or a `rescue => $g` reference.",
|
|
729
|
+
"Any project or `pre_eval:` file aliases the special (`alias $! $err`, on either side), which can " \
|
|
730
|
+
"make the name another variable.",
|
|
731
|
+
"Mutating the value is not a write: `$LOAD_PATH << dir` and `$LOADED_FEATURES.delete(f)` stay silent.",
|
|
732
|
+
"The write sits in the dead arm of a decidable version guard. Other code that never runs is not " \
|
|
733
|
+
"recognised, and a write there still fires."
|
|
734
|
+
],
|
|
735
|
+
suppression: "`# rigor:disable global.readonly-write` on the write.",
|
|
736
|
+
severity_authored: :error,
|
|
737
|
+
severity_by_profile: { lenient: :error, balanced: :error, strict: :error },
|
|
738
|
+
# Syntactic: the name alone decides it, and the write raises every time it runs.
|
|
739
|
+
evidence_tier: :high,
|
|
740
|
+
since: "0.4.0"
|
|
741
|
+
),
|
|
742
|
+
|
|
659
743
|
CheckRules::RULE_SUPPRESSION_UNKNOWN_RULE => Entry.new(
|
|
660
744
|
id: CheckRules::RULE_SUPPRESSION_UNKNOWN_RULE,
|
|
661
745
|
summary: "A `# rigor:disable[-file]` comment names a rule that does not exist.",
|
|
@@ -896,6 +980,48 @@ module Rigor
|
|
|
896
980
|
since: "0.3.4"
|
|
897
981
|
),
|
|
898
982
|
|
|
983
|
+
CheckRules::RULE_CONTRADICTING_SIGNATURE => Entry.new(
|
|
984
|
+
id: CheckRules::RULE_CONTRADICTING_SIGNATURE,
|
|
985
|
+
summary: "Two declarations of one method provably contradict: no value or call satisfies both.",
|
|
986
|
+
fires_when: [
|
|
987
|
+
"A method is declared both in the project's `sig/` and by an inline `# @rbs` / `#:` " \
|
|
988
|
+
"annotation, and the return or a parameter every call must pass holds two proven-disjoint " \
|
|
989
|
+
"types — distinct literals, a literal outside such a class, or two classes written absolutely " \
|
|
990
|
+
"whose RBS comes from Ruby core or the stdlib, neither an RBS ancestor of the other " \
|
|
991
|
+
"(`::String` against `::Integer`) — or the two keyword-free declarations accept " \
|
|
992
|
+
"positional counts that cannot meet, or one requires a keyword the other cannot take in any " \
|
|
993
|
+
"form. Several overloads are paired by correspondence, never by order. Positioned at the " \
|
|
994
|
+
"`.rbs` member; Rigor reads that declaration.",
|
|
995
|
+
"A member-level `%a{rigor:v1:return: …}` or `%a{rigor:v1:param: …}` refinement is proven " \
|
|
996
|
+
"disjoint from the type its own member declares, in every overload it applies to " \
|
|
997
|
+
"(`rigor:v1:return: positive-int` on `-> String`)."
|
|
998
|
+
],
|
|
999
|
+
does_not_fire_when: [
|
|
1000
|
+
"One declaration refines the other (`String` against `non-empty-string`, `Symbol` against " \
|
|
1001
|
+
"`:asc | :desc`, `bool` against `TrueClass`): the two merge to the more precise one, silently.",
|
|
1002
|
+
"Either side is `untyped`, `void` or `top` in that position — consistent with everything.",
|
|
1003
|
+
"Rigor cannot prove the two contradict: a module or interface on either side, a class the " \
|
|
1004
|
+
"project declares (in `sig/`, inline or only in Ruby), a gem's class, any relative class name, " \
|
|
1005
|
+
"two element types of one generic class, a type alias, `self`, a type variable, a proc, an " \
|
|
1006
|
+
"optional or rest position, any block position, overloads that do not pair one to one, or " \
|
|
1007
|
+
"differently shaped parameter lists that still overlap. The " \
|
|
1008
|
+
"inline signature is then dropped and reported as `source-rbs-annotation-not-honoured` (info).",
|
|
1009
|
+
"A refinement is written on one overload rather than on the member: call sites do not honour it.",
|
|
1010
|
+
"Two `.rbs` files declare the same method (that fails the class's definition build, " \
|
|
1011
|
+
"`rbs.coverage.definition-build-failed`), or a project `.rbs` collides with bundled RBS " \
|
|
1012
|
+
"(the file is quarantined, `rbs.coverage.quarantined-signature`)."
|
|
1013
|
+
],
|
|
1014
|
+
suppression: "`disable: [\"rbs.contradicting-signature\"]` in `.rigor.yml`, or a baseline entry. A " \
|
|
1015
|
+
"`# rigor:disable` comment does not reach a row positioned in a `.rbs` file. The real " \
|
|
1016
|
+
"fix is to regenerate the stale signature or correct the annotation.",
|
|
1017
|
+
severity_authored: :error,
|
|
1018
|
+
severity_by_profile: { lenient: :error, balanced: :error, strict: :error },
|
|
1019
|
+
# Both sides are authored declarations, and a firing needs a proof that no value or call satisfies
|
|
1020
|
+
# both — the ADR-35 both-sides-authored construction, with every doubt routed to the `:info` row.
|
|
1021
|
+
evidence_tier: :high,
|
|
1022
|
+
since: "0.4.0"
|
|
1023
|
+
),
|
|
1024
|
+
|
|
899
1025
|
CheckRules::RULE_SUPPRESSION_UNKNOWN_MARKER => Entry.new(
|
|
900
1026
|
id: CheckRules::RULE_SUPPRESSION_UNKNOWN_MARKER,
|
|
901
1027
|
summary: "A comment uses a suppression marker Rigor does not recognise " \
|
|
@@ -925,6 +1051,7 @@ module Rigor
|
|
|
925
1051
|
since: "0.3.0"
|
|
926
1052
|
)
|
|
927
1053
|
}.freeze
|
|
1054
|
+
Ractor.make_shareable(ENTRIES)
|
|
928
1055
|
|
|
929
1056
|
module_function
|
|
930
1057
|
|
|
@@ -21,6 +21,7 @@ require_relative "../environment/default_libraries"
|
|
|
21
21
|
# lockfile paths without loading `rigor/environment` or the RBS machinery.
|
|
22
22
|
require_relative "../environment/lockfile_resolver"
|
|
23
23
|
require_relative "../environment/rbs_collection_discovery"
|
|
24
|
+
require_relative "shadow_harness"
|
|
24
25
|
|
|
25
26
|
module Rigor
|
|
26
27
|
module Analysis
|
|
@@ -82,15 +83,23 @@ module Rigor
|
|
|
82
83
|
|
|
83
84
|
# @param rbs_config_entries — the RBS-derived config slots
|
|
84
85
|
# (`rbs.libraries` [+ `rbs.virtual_rbs`]). nil on any failure so a malformed key disables the cache.
|
|
85
|
-
|
|
86
|
+
# @param template_units_digest — #392 — the run's template-unit identity
|
|
87
|
+
# ({Analysis::TemplateUnits#digest}: every unit's source bytes + transform id + synthesis version).
|
|
88
|
+
# nil for a project whose plugins declare no `template_globs:`, which is every project today, and a
|
|
89
|
+
# nil contributes NO slot — so no existing key moves. A project that DOES have units produces a key
|
|
90
|
+
# the ADR-87 boot-slim probe (which loads no plugin and so passes nil) cannot reconstruct: the probe
|
|
91
|
+
# simply misses and the full path takes over, the same forgone-fast-lane trade the `rbs.virtual_rbs`
|
|
92
|
+
# slot already makes, and never a wrong hit.
|
|
93
|
+
def descriptor(configuration:, files:, explain:, rbs_config_entries:, template_units_digest: nil)
|
|
86
94
|
Cache::Descriptor.new(
|
|
87
95
|
gems: [Cache::RbsDescriptor.rbs_gem_entry],
|
|
88
|
-
configs: rbs_config_entries + engine_source_entries + lockfile_entries(configuration) +
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
configs: rbs_config_entries + engine_source_entries + lockfile_entries(configuration) +
|
|
97
|
+
template_unit_entries(template_units_digest) + shadow_harness_entries + [
|
|
98
|
+
config_entry("configuration", Marshal.dump(configuration.to_h)),
|
|
99
|
+
config_entry("engine",
|
|
100
|
+
"#{Rigor::VERSION}:#{Cache::Descriptor::SCHEMA_VERSION}:#{explain}"),
|
|
101
|
+
config_entry("paths", files.sort.join("\n"))
|
|
102
|
+
]
|
|
94
103
|
)
|
|
95
104
|
rescue StandardError
|
|
96
105
|
nil
|
|
@@ -104,11 +113,21 @@ module Rigor
|
|
|
104
113
|
#
|
|
105
114
|
# {Cache::EngineSource::Unavailable} is left to propagate into `descriptor`'s rescue, which disables
|
|
106
115
|
# the cache for the run: an engine we cannot identify must not be keyed by its version alone.
|
|
107
|
-
def
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
def template_unit_entries(digest)
|
|
117
|
+
digest.nil? ? [] : [config_entry("template-units", digest)]
|
|
118
|
+
end
|
|
110
119
|
|
|
111
|
-
|
|
120
|
+
# ADR-116 WD5 — a run under `RIGOR_SHADOW_RULE_WALK` compares every table and rule collector with its
|
|
121
|
+
# legacy oracle, and a divergence is a row of the result. Keying on it keeps a warm hit from answering
|
|
122
|
+
# for a run the harness never checked, and a divergence row from replaying into a run without it. No
|
|
123
|
+
# slot with the harness off, so no existing key moves.
|
|
124
|
+
def shadow_harness_entries
|
|
125
|
+
identity = ShadowHarness.cache_identity
|
|
126
|
+
identity.nil? ? [] : [config_entry("shadow-harness", identity)]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def engine_source_entries
|
|
130
|
+
Cache::EngineSource.key_config_entries
|
|
112
131
|
end
|
|
113
132
|
|
|
114
133
|
# Issue #564 — the two dependency lockfiles, BY CONTENT. Both are inputs to the run's diagnostics and
|
|
@@ -128,16 +147,25 @@ module Rigor
|
|
|
128
147
|
# Content-only — the resolved PATH is deliberately not in the payload, so a checkout that moves (a CI
|
|
129
148
|
# runner's workspace, a renamed directory) still hits with an identical lockfile.
|
|
130
149
|
def lockfile_entries(configuration)
|
|
131
|
-
bundler =
|
|
132
|
-
lockfile_path: configuration.bundler_lockfile, auto_detect: configuration.bundler_auto_detect
|
|
133
|
-
)
|
|
134
|
-
collection = Environment::RbsCollectionDiscovery.resolve_lockfile_path(
|
|
135
|
-
lockfile_path: configuration.rbs_collection_lockfile,
|
|
136
|
-
auto_detect: configuration.rbs_collection_auto_detect
|
|
137
|
-
)
|
|
150
|
+
bundler, collection = resolved_lockfile_paths(configuration)
|
|
138
151
|
[lockfile_entry("bundler.lockfile", bundler), lockfile_entry("rbs_collection.lockfile", collection)]
|
|
139
152
|
end
|
|
140
153
|
|
|
154
|
+
# The one resolution of the two dependency lockfiles — `[bundler, rbs_collection]`, each a Pathname or nil
|
|
155
|
+
# — shared by this key and by {Cache::IncrementalSnapshot.fingerprint} so the two caches identify the gem
|
|
156
|
+
# set by the same files (issue #1532): `bundler.lockfile:` / `rbs_collection.lockfile:` and auto-detection.
|
|
157
|
+
def resolved_lockfile_paths(configuration)
|
|
158
|
+
[
|
|
159
|
+
Environment::LockfileResolver.resolve_lockfile_path(
|
|
160
|
+
lockfile_path: configuration.bundler_lockfile, auto_detect: configuration.bundler_auto_detect
|
|
161
|
+
),
|
|
162
|
+
Environment::RbsCollectionDiscovery.resolve_lockfile_path(
|
|
163
|
+
lockfile_path: configuration.rbs_collection_lockfile,
|
|
164
|
+
auto_detect: configuration.rbs_collection_auto_detect
|
|
165
|
+
)
|
|
166
|
+
]
|
|
167
|
+
end
|
|
168
|
+
|
|
141
169
|
# `nil` — no lockfile resolves — is itself part of the identity, so it carries its own sentinel rather
|
|
142
170
|
# than dropping the slot: "absent" and "present but empty" must not key the same.
|
|
143
171
|
ABSENT_LOCKFILE = "\0absent"
|
|
@@ -95,12 +95,12 @@ module Rigor
|
|
|
95
95
|
# with any of the signature path prefixes (after expansion to absolute paths); `bundled` is the
|
|
96
96
|
# remainder.
|
|
97
97
|
def self.partition_classes(class_decl_paths:, signature_paths:)
|
|
98
|
-
prefixes = Array(signature_paths).map { |p| File.
|
|
98
|
+
prefixes = Array(signature_paths).map { |p| File.absolute_path(p.to_s) }
|
|
99
99
|
return [0, class_decl_paths.size] if prefixes.empty?
|
|
100
100
|
|
|
101
101
|
project = 0
|
|
102
102
|
class_decl_paths.each_value do |path|
|
|
103
|
-
expanded = File.
|
|
103
|
+
expanded = File.absolute_path(path)
|
|
104
104
|
project += 1 if prefixes.any? { |prefix| expanded.start_with?("#{prefix}/") || expanded == prefix }
|
|
105
105
|
end
|
|
106
106
|
[project, class_decl_paths.size - project]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../check_rules/rule_ids"
|
|
3
4
|
require_relative "../crash_signature"
|
|
4
5
|
require_relative "../diagnostic"
|
|
5
6
|
require_relative "../severity_stamp"
|
|
@@ -39,7 +40,9 @@ module Rigor
|
|
|
39
40
|
# (`[error_class, first_error_line, conflicting_buffer_names]`) or nil when the env built.
|
|
40
41
|
# @param definition_build_failures_snapshot — issue #696 — reader returning the per-class
|
|
41
42
|
# `RBS::DefinitionBuilder` failures the run observed, as `[class_name, error_class, member,
|
|
42
|
-
# conflicting_buffer_names]` tuples
|
|
43
|
+
# conflicting_buffer_names, unresolved_type_name_detail]` tuples (issue #997 added the fifth
|
|
44
|
+
# element — `[token, location, refinement_hint]`, `nil` unless `error_class` is
|
|
45
|
+
# `RBS::NoTypeFoundError`). Empty for a healthy sig set.
|
|
43
46
|
# @param hkt_scan_failure_snapshot — issue #784 — reader returning the `[error_class_name,
|
|
44
47
|
# first_message_line, raw_frame_or_nil, stage]` tuple whichever stage of the HKT-registry build
|
|
45
48
|
# raised, or nil when both built (or were never demanded). `stage` is `:scan` (the RBS `type`-alias
|
|
@@ -51,7 +54,8 @@ module Rigor
|
|
|
51
54
|
pre_eval_diagnostics_from_scanner:, synthesized_namespaces_snapshot:,
|
|
52
55
|
quarantined_signatures_snapshot:, env_build_failure_snapshot:,
|
|
53
56
|
definition_build_failures_snapshot:, hkt_scan_failure_snapshot:,
|
|
54
|
-
conformance_results_snapshot:, signature_standdowns_snapshot: -> { [] }
|
|
57
|
+
conformance_results_snapshot:, signature_standdowns_snapshot: -> { [] },
|
|
58
|
+
pooled_run_disclosures: -> { [] })
|
|
55
59
|
@configuration = configuration
|
|
56
60
|
@rbs_extended_reporter = rbs_extended_reporter
|
|
57
61
|
@boundary_cross_reporter = boundary_cross_reporter
|
|
@@ -68,6 +72,9 @@ module Rigor
|
|
|
68
72
|
@definition_build_failures_snapshot_reader = definition_build_failures_snapshot
|
|
69
73
|
@hkt_scan_failure_snapshot_reader = hkt_scan_failure_snapshot
|
|
70
74
|
@conformance_results_snapshot_reader = conformance_results_snapshot
|
|
75
|
+
# Issue #1051 — reader for the pool coordinator's de-duplicated run-scoped disclosure table.
|
|
76
|
+
# Empty on a sequential run, where `plugin_registry`'s own instances are the ones that registered.
|
|
77
|
+
@pooled_run_disclosures_reader = pooled_run_disclosures
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
# Pre-file diagnostic streams that fire once per run rather than per analyzed file: plugin load /
|
|
@@ -441,8 +448,9 @@ module Rigor
|
|
|
441
448
|
# "Explicit conformance directive"). A class that declares `conforms-to _Interface` but is missing
|
|
442
449
|
# a required interface method surfaces as `rbs_extended.unsatisfied-conformance`; an unresolvable
|
|
443
450
|
# interface name surfaces as `dynamic.rbs-extended.unresolved` `:info` (the same fail-soft channel
|
|
444
|
-
# the other directive parsers use). Empty for a project with no directive
|
|
445
|
-
# conformance, or
|
|
451
|
+
# the other directive parsers use). Empty for a project with no directive or a well-formed
|
|
452
|
+
# conformance; every analysis path, pooled or degraded, takes the snapshot `synthesized_namespaces`
|
|
453
|
+
# comes from.
|
|
446
454
|
def conforms_to_diagnostics
|
|
447
455
|
results = conformance_results_snapshot
|
|
448
456
|
return [] if results.nil? || results.empty?
|
|
@@ -585,22 +593,50 @@ module Rigor
|
|
|
585
593
|
end
|
|
586
594
|
|
|
587
595
|
# The closing advice follows the FIRST failure's error class: `GenericParameterMismatchError` is two
|
|
588
|
-
# declarations of one CLASS at different generic arity (#610)
|
|
589
|
-
#
|
|
596
|
+
# declarations of one CLASS at different generic arity (#610); `NoTypeFoundError` (issue #997) is
|
|
597
|
+
# ONE declaration naming a type that does not exist, which "remove the duplicate declaration" sends
|
|
598
|
+
# its reader after a duplicate that is not there — the fifth tuple element
|
|
599
|
+
# ({RbsLoader#unresolved_type_name_detail}) is non-nil for exactly that case. Every other error class
|
|
600
|
+
# (the two duplicated-definition errors, `RecursiveAncestorError`, …) keeps the duplicate-declaration
|
|
601
|
+
# wording, which is accurate for them.
|
|
590
602
|
def definition_build_advice(failure)
|
|
591
|
-
_, error_class, = failure
|
|
603
|
+
_, error_class, _member, _buffers, unresolved = failure
|
|
592
604
|
if error_class.to_s.end_with?("GenericParameterMismatchError")
|
|
593
605
|
"Two signature sources declare the class with a different number of type parameters; make " \
|
|
594
606
|
"the declarations agree (`rbs validate`) to restore type coverage."
|
|
607
|
+
elsif unresolved
|
|
608
|
+
unresolved_type_name_advice(unresolved)
|
|
595
609
|
else
|
|
596
610
|
"Two signature sources declare the same member; remove the duplicate declaration " \
|
|
597
611
|
"(`rbs validate`) to restore type coverage."
|
|
598
612
|
end
|
|
599
613
|
end
|
|
600
614
|
|
|
601
|
-
#
|
|
602
|
-
#
|
|
603
|
-
#
|
|
615
|
+
# Issue #997 — `unresolved` is {RbsLoader#unresolved_type_name_detail}'s `[token, location,
|
|
616
|
+
# refinement_hint]`. `refinement_hint` turns the message into a fix: the token is not a typo to
|
|
617
|
+
# chase down but the start of a Rigor refinement name that is not valid in an RBS type position, and
|
|
618
|
+
# the reader is told the spelling that IS valid today. Without a hint, the token is named with its
|
|
619
|
+
# position and the reader is left with the same "not a duplicate" correction plus a typo/missing-load
|
|
620
|
+
# check — still narrower than the duplicate-declaration wording it replaces.
|
|
621
|
+
def unresolved_type_name_advice(unresolved)
|
|
622
|
+
token, location, refinement_hint = unresolved
|
|
623
|
+
position = location ? " at `#{location}`" : ""
|
|
624
|
+
if refinement_hint
|
|
625
|
+
"`#{token}`#{position} does not name a type RBS can resolve; it is the start of the Rigor " \
|
|
626
|
+
"refinement `#{refinement_hint}`, which is not valid in an RBS type position. Write it as " \
|
|
627
|
+
"an `%a{rigor:v1:param: name is #{refinement_hint}}` (or `return:` / `assert`) annotation " \
|
|
628
|
+
"alongside the plain RBS type instead — see docs/manual/16-rbs-extended-annotations.md — " \
|
|
629
|
+
"to restore type coverage."
|
|
630
|
+
else
|
|
631
|
+
"One signature source names `#{token}`#{position}, which is not a type RBS can resolve; fix " \
|
|
632
|
+
"the typo, or if it names a class, confirm it is loaded (`rbs validate`) to restore type " \
|
|
633
|
+
"coverage."
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# `[class_name, error_class, member, buffers, unresolved_type_name_detail]`. The member is nil for
|
|
638
|
+
# the error classes that carry no name at all (`RecursiveAncestorError`), and the clause then names
|
|
639
|
+
# the error class alone rather than inventing a member.
|
|
604
640
|
def first_failure_clause(failure)
|
|
605
641
|
_, error_class, member, = failure
|
|
606
642
|
return " First failure: #{error_class}." if member.nil? || member.empty?
|
|
@@ -849,10 +885,29 @@ module Rigor
|
|
|
849
885
|
return [] if @source_rbs_synthesis_reporter.empty?
|
|
850
886
|
|
|
851
887
|
@source_rbs_synthesis_reporter.entries.map do |entry|
|
|
852
|
-
entry.kind
|
|
888
|
+
case entry.kind
|
|
889
|
+
when :not_honoured then not_honoured_diagnostic(entry)
|
|
890
|
+
when :contradiction then contradicting_signature_diagnostic(entry)
|
|
891
|
+
else synthesis_failed_diagnostic(entry)
|
|
892
|
+
end
|
|
853
893
|
end
|
|
854
894
|
end
|
|
855
895
|
|
|
896
|
+
# ADR-112 WD5 / issue #1075 — a member whose two declarations contradict, or whose refinement is
|
|
897
|
+
# outside its own declared type. An error in every profile: the false-positive discipline is carried
|
|
898
|
+
# by the rule's narrow definition of a contradiction, never by the severity (ADR-112 WD5). The message
|
|
899
|
+
# is composed where the record is read ({Environment.record_member_consistency}), since only there
|
|
900
|
+
# are both sources' paths at hand.
|
|
901
|
+
def contradicting_signature_diagnostic(entry)
|
|
902
|
+
Diagnostic.new(
|
|
903
|
+
path: entry.path, line: entry.line || 1, column: 1,
|
|
904
|
+
message: entry.message,
|
|
905
|
+
severity: :error,
|
|
906
|
+
rule: CheckRules::RULE_CONTRADICTING_SIGNATURE,
|
|
907
|
+
source_family: :builtin
|
|
908
|
+
)
|
|
909
|
+
end
|
|
910
|
+
|
|
856
911
|
def synthesis_failed_diagnostic(entry)
|
|
857
912
|
Diagnostic.new(
|
|
858
913
|
path: entry.path, line: 1, column: 1,
|
|
@@ -910,7 +965,7 @@ module Rigor
|
|
|
910
965
|
|
|
911
966
|
# Issue #959 — surfaces the {Plugin::IoBoundary} refusal history each loaded plugin accumulated over
|
|
912
967
|
# the whole run (prepare AND every per-file call: `#io_boundary` is memoised per plugin instance, so
|
|
913
|
-
# one boundary sees both). `TrustPolicy#allow_read?` stays `File.
|
|
968
|
+
# one boundary sees both). `TrustPolicy#allow_read?` stays `File.absolute_path`-only per ADR-2 — a
|
|
914
969
|
# project rooted under a symlink (macOS' `/tmp` → `/private/tmp` is the common case) can have every
|
|
915
970
|
# plugin read fall outside its own read roots with nothing said about it. One `:info` row per plugin
|
|
916
971
|
# that hit this, never one per path, naming the count and the first path so the row stays legible on
|
|
@@ -938,6 +993,103 @@ module Rigor
|
|
|
938
993
|
plugin_registry.plugins.filter_map { |plugin| plugin_trust_refusal_diagnostic(plugin) }
|
|
939
994
|
end
|
|
940
995
|
|
|
996
|
+
# Issue #1051 — the run-scoped plugin disclosures ({Plugin::Base#disclose_once}), the run's
|
|
997
|
+
# project-global notices about its own inputs. Two sources feed one table:
|
|
998
|
+
#
|
|
999
|
+
# - the coordinator-side registry, which is where a SEQUENTIAL run's `#prepare` and per-file hooks
|
|
1000
|
+
# ran; and
|
|
1001
|
+
# - `pooled_run_disclosures`, the pool coordinator's already-de-duplicated collection from the
|
|
1002
|
+
# pre-fork parent session and every worker payload.
|
|
1003
|
+
#
|
|
1004
|
+
# Both are keyed `[plugin id, key]` and the first registration of a pair wins, so a run emits one
|
|
1005
|
+
# row per pair no matter how many plugin instances (workers) reached it — the #1051 bug, where a
|
|
1006
|
+
# per-instance `@emitted` flag produced one row per WORKER.
|
|
1007
|
+
#
|
|
1008
|
+
# The emitted order is `(registry position, key)`, NOT registration order: a worker's slice
|
|
1009
|
+
# determines which instance registers a key first, so registration order is a function of
|
|
1010
|
+
# `--workers N` and would make a pooled run's stream differ from a sequential one's even with the
|
|
1011
|
+
# duplicates gone. Registry position is the plugin load order (topological by `consumes:`), which
|
|
1012
|
+
# both modes share.
|
|
1013
|
+
#
|
|
1014
|
+
# Position is `.rigor.yml:1:1` for every row; see {Plugin::Base#disclose_once} for why that and not
|
|
1015
|
+
# the first analysed file. A positioned batch ({Plugin::Base#emit_once}) shares the table but not
|
|
1016
|
+
# this stream — see {#plugin_run_emission_diagnostics}.
|
|
1017
|
+
def plugin_run_disclosure_diagnostics
|
|
1018
|
+
ordered_run_disclosure_records
|
|
1019
|
+
.reject { |record| record.key?(:diagnostics) }
|
|
1020
|
+
.map { |record| run_disclosure_diagnostic(record) }
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# Issue #1060 — the positioned batches ({Plugin::Base#emit_once}) from the same table, de-duplicated
|
|
1024
|
+
# by the same `[plugin id, key]` identity over the same sources, so the first registration of a key
|
|
1025
|
+
# wins whole and a batch is never merged row by row across workers. Unlike a disclosure, every row
|
|
1026
|
+
# keeps the position the plugin gave it; the engine only stamps `source_family: "plugin.<id>"`, the
|
|
1027
|
+
# same stamp a `#diagnostics_for_file` row gets, so a baseline keyed to the row's own file and
|
|
1028
|
+
# qualified rule matches it before and after a plugin moves onto this channel.
|
|
1029
|
+
#
|
|
1030
|
+
# Order is `(registry position, key)` and then the batch's own row order — the same
|
|
1031
|
+
# `--workers`-independent order the disclosure stream uses. The runner appends this stream right
|
|
1032
|
+
# after the per-file stream rather than into the run-level block: these rows are about files, and
|
|
1033
|
+
# the files they name are usually not among the analysed targets (a view template), so there is no
|
|
1034
|
+
# file slot to splice them into.
|
|
1035
|
+
def plugin_run_emission_diagnostics
|
|
1036
|
+
ordered_run_disclosure_records
|
|
1037
|
+
.select { |record| record.key?(:diagnostics) }
|
|
1038
|
+
.flat_map { |record| record[:diagnostics].map { |row| run_emission_diagnostic(row, record[:plugin_id]) } }
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
def ordered_run_disclosure_records
|
|
1042
|
+
registry = plugin_registry
|
|
1043
|
+
return [] if registry.empty?
|
|
1044
|
+
|
|
1045
|
+
positions = {}
|
|
1046
|
+
registry.plugins.each_with_index { |plugin, index| positions[disclosure_plugin_id(plugin)] ||= index }
|
|
1047
|
+
collect_run_disclosure_records(registry)
|
|
1048
|
+
.sort_by { |record| [positions.fetch(record[:plugin_id], positions.size), record[:key].to_s] }
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
def collect_run_disclosure_records(registry)
|
|
1052
|
+
records = {}
|
|
1053
|
+
Array(pooled_run_disclosures).each do |record|
|
|
1054
|
+
records[[record[:plugin_id], record[:key]]] ||= record
|
|
1055
|
+
end
|
|
1056
|
+
registry.plugins.each do |plugin|
|
|
1057
|
+
plugin_id = disclosure_plugin_id(plugin)
|
|
1058
|
+
Array(plugin.run_disclosure_records).each do |record|
|
|
1059
|
+
records[[plugin_id, record[:key]]] ||= record.merge(plugin_id: plugin_id)
|
|
1060
|
+
end
|
|
1061
|
+
rescue StandardError
|
|
1062
|
+
next
|
|
1063
|
+
end
|
|
1064
|
+
records.values
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
def run_disclosure_diagnostic(record)
|
|
1068
|
+
Diagnostic.new(
|
|
1069
|
+
path: ".rigor.yml", line: 1, column: 1,
|
|
1070
|
+
message: record.fetch(:message),
|
|
1071
|
+
severity: record.fetch(:severity),
|
|
1072
|
+
rule: record.fetch(:rule),
|
|
1073
|
+
source_family: "plugin.#{record[:plugin_id]}"
|
|
1074
|
+
)
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
def run_emission_diagnostic(row, plugin_id)
|
|
1078
|
+
Diagnostic.new(
|
|
1079
|
+
path: row.path, line: row.line, column: row.column,
|
|
1080
|
+
message: row.message,
|
|
1081
|
+
severity: row.severity,
|
|
1082
|
+
rule: row.rule,
|
|
1083
|
+
source_family: "plugin.#{plugin_id}"
|
|
1084
|
+
)
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
def disclosure_plugin_id(plugin)
|
|
1088
|
+
plugin.manifest.id
|
|
1089
|
+
rescue StandardError
|
|
1090
|
+
plugin.class.to_s
|
|
1091
|
+
end
|
|
1092
|
+
|
|
941
1093
|
def plugin_trust_refusal_diagnostic(plugin)
|
|
942
1094
|
summary = plugin.io_boundary.refusal_summary
|
|
943
1095
|
return nil if summary.nil?
|
|
@@ -1024,6 +1176,10 @@ module Rigor
|
|
|
1024
1176
|
@cached_plugin_prepare_diagnostics_reader.call
|
|
1025
1177
|
end
|
|
1026
1178
|
|
|
1179
|
+
def pooled_run_disclosures
|
|
1180
|
+
@pooled_run_disclosures_reader.call
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1027
1183
|
def pre_eval_diagnostics_from_scanner
|
|
1028
1184
|
@pre_eval_diagnostics_from_scanner_reader.call
|
|
1029
1185
|
end
|
|
@@ -250,9 +250,7 @@ module Rigor
|
|
|
250
250
|
)
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
-
def table_class_names
|
|
254
|
-
@effect_table.keys.filter_map { |key| Effects::MethodKey.owner(key) }.uniq
|
|
255
|
-
end
|
|
253
|
+
def table_class_names = @effect_table.keys.filter_map { |k| Effects::MethodKey.envelope_owner(k) }.uniq
|
|
256
254
|
|
|
257
255
|
def judge(scan)
|
|
258
256
|
Effects::EnvelopeCheck.run(
|
|
@@ -301,7 +299,7 @@ module Rigor
|
|
|
301
299
|
|
|
302
300
|
def build_diagnostic(finding)
|
|
303
301
|
Diagnostic.new(
|
|
304
|
-
path: finding.path || ".rigor.yml",
|
|
302
|
+
path: finding.path || unit_source_path(finding.key) || ".rigor.yml",
|
|
305
303
|
line: finding.line,
|
|
306
304
|
column: 1,
|
|
307
305
|
message: message_for(finding),
|
|
@@ -312,6 +310,13 @@ module Rigor
|
|
|
312
310
|
)
|
|
313
311
|
end
|
|
314
312
|
|
|
313
|
+
# #393 — a template unit has no `def` for {Effects::EnvelopeCheck::Positions} to find, so a view
|
|
314
|
+
# finding would land on `.rigor.yml` — the config file, not the template the reviewer has to open.
|
|
315
|
+
# `Runner#effect_sources` already records the file every unit key came from; the line stays at the
|
|
316
|
+
# finding's own (1 for a whole-file unit), because the bound is exceeded by the unit, not by one
|
|
317
|
+
# line of it, and the explanation chain names the origin site.
|
|
318
|
+
def unit_source_path(key) = Effects::MethodKey.template_unit?(key) ? @unit_sources[key.to_s]&.first : nil
|
|
319
|
+
|
|
315
320
|
def message_for(finding)
|
|
316
321
|
EnvelopeMessages.exceeded(finding)
|
|
317
322
|
end
|