rigortype 0.4.5 → 0.4.7
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 +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +38 -33
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +60 -18
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +188 -93
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +27 -21
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +170 -0
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +9 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +98 -4
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +12 -6
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +38 -16
- data/lib/rigor/plugin/registry.rb +41 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +96 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +25 -2
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +379 -36
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +668 -84
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +7 -6
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +55 -3
data/lib/rigor/configuration.rb
CHANGED
|
@@ -62,6 +62,10 @@ module Rigor
|
|
|
62
62
|
"disable" => [],
|
|
63
63
|
"libraries" => [],
|
|
64
64
|
"signature_paths" => nil,
|
|
65
|
+
# Issue #2517 — a core signature directory loaded IN PLACE OF the `rbs` gem's `core/` (a provider such as
|
|
66
|
+
# rbs-shifter writes one for an older CRuby). `nil` (the default) loads the gem's core. Unlike
|
|
67
|
+
# `signature_paths:`, which only adds, this replaces: the default core is not loaded beside it.
|
|
68
|
+
"core_root" => nil,
|
|
65
69
|
# The project's test roots: the directories (or files) holding its tests. `nil` (the default) auto-detects
|
|
66
70
|
# whichever of `spec/` and `test/` exist, the same way `signature_paths: nil` auto-detects `sig/`; `[]`
|
|
67
71
|
# declares that the project has none. `rigor sig-gen --params=observed` harvests call-site observations
|
|
@@ -77,10 +81,9 @@ module Rigor
|
|
|
77
81
|
"inline_declared" => "write"
|
|
78
82
|
},
|
|
79
83
|
# ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit
|
|
80
|
-
# files that the analyzer walks before per-file inference so patched-method declarations are
|
|
81
|
-
# across the project (e.g. `lib/core_ext/string_extensions.rb`).
|
|
82
|
-
#
|
|
83
|
-
# tier consuming the registry lands in slice 2.
|
|
84
|
+
# files (or globs) that the analyzer walks before per-file inference so patched-method declarations are
|
|
85
|
+
# visible across the project (e.g. `lib/core_ext/string_extensions.rb`). A missing literal path surfaces
|
|
86
|
+
# as `pre-eval.file-not-found`.
|
|
84
87
|
"pre_eval" => [],
|
|
85
88
|
# ADR-22 — baseline file path. nil (default) means no baseline is loaded; the `false` literal is
|
|
86
89
|
# treated as the explicit-disable form for `.rigor.yml`-side override of an upstream `.rigor.dist.yml`
|
|
@@ -229,6 +232,11 @@ module Rigor
|
|
|
229
232
|
PATH_KEYS = %w[paths signature_paths test_paths pre_eval].freeze
|
|
230
233
|
private_constant :PATH_KEYS
|
|
231
234
|
|
|
235
|
+
# The single-path sibling of {PATH_KEYS}: resolved the same way, but kept a String rather than wrapped in an
|
|
236
|
+
# Array.
|
|
237
|
+
SCALAR_PATH_KEYS = %w[core_root].freeze
|
|
238
|
+
private_constant :SCALAR_PATH_KEYS
|
|
239
|
+
|
|
232
240
|
# Top-level keys this implementation DECLARES but never reads — see `docs/internal-spec/config.md`
|
|
233
241
|
# § "Reserved namespaces" and ADR-99.
|
|
234
242
|
#
|
|
@@ -271,7 +279,8 @@ module Rigor
|
|
|
271
279
|
|
|
272
280
|
attr_reader :target_ruby, :paths, :exclude_patterns, :plugins, :cache_path, :cache_max_bytes,
|
|
273
281
|
:cache_validation, :disabled_rules,
|
|
274
|
-
:libraries, :signature_paths, :
|
|
282
|
+
:libraries, :signature_paths, :core_root, :test_paths, :fold_platform_specific_paths,
|
|
283
|
+
:parameter_inference,
|
|
275
284
|
:plugins_isolation,
|
|
276
285
|
:plugins_io_network, :plugins_io_allowed_paths,
|
|
277
286
|
:plugins_io_allowed_url_hosts,
|
|
@@ -321,12 +330,22 @@ module Rigor
|
|
|
321
330
|
|
|
322
331
|
# The test roots a run uses: the declared `test_paths:` as written (already resolved against the
|
|
323
332
|
# config file's directory by {.load}), or — when the key is unset — whichever of
|
|
324
|
-
# {CONVENTIONAL_TEST_PATHS} is a directory under
|
|
325
|
-
# the
|
|
326
|
-
|
|
333
|
+
# {CONVENTIONAL_TEST_PATHS} is a directory under a root, as absolute paths. The roots, unless `root` names
|
|
334
|
+
# one, are the directory of the config file in use, the one `paths:` and a declared `test_paths:` resolve
|
|
335
|
+
# against, and then the working directory, read only when the first holds neither. Auto-detecting against
|
|
336
|
+
# the working directory alone found nothing for a run from a subdirectory, or for the MCP server started
|
|
337
|
+
# elsewhere, though both name the config file (#2078); a config file kept in a subdirectory
|
|
338
|
+
# (`--config=config/rigor.yml`) still finds the project's `spec/` from the project root.
|
|
339
|
+
def resolved_test_paths(root: nil)
|
|
327
340
|
return test_paths unless test_paths.nil?
|
|
328
341
|
|
|
329
|
-
|
|
342
|
+
roots = root ? [root] : [exclude_base, Dir.pwd].uniq
|
|
343
|
+
roots.each do |dir|
|
|
344
|
+
found = CONVENTIONAL_TEST_PATHS.map { |test_dir| File.absolute_path(test_dir, dir) }
|
|
345
|
+
.select { File.directory?(it) }
|
|
346
|
+
return found unless found.empty?
|
|
347
|
+
end
|
|
348
|
+
[]
|
|
330
349
|
end
|
|
331
350
|
|
|
332
351
|
# Loads a configuration file.
|
|
@@ -480,6 +499,7 @@ module Rigor
|
|
|
480
499
|
|
|
481
500
|
out = data.dup
|
|
482
501
|
PATH_KEYS.each { |key| resolve_path_key!(out, key, base_dir) }
|
|
502
|
+
SCALAR_PATH_KEYS.each { |key| resolve_scalar_path_key!(out, key, base_dir) }
|
|
483
503
|
resolve_plugins_io_paths!(out, base_dir)
|
|
484
504
|
out
|
|
485
505
|
end
|
|
@@ -490,6 +510,12 @@ module Rigor
|
|
|
490
510
|
out[key] = Array(out[key]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
|
|
491
511
|
end
|
|
492
512
|
|
|
513
|
+
def self.resolve_scalar_path_key!(out, key, base_dir)
|
|
514
|
+
return unless out.key?(key) && out[key].is_a?(String)
|
|
515
|
+
|
|
516
|
+
out[key] = ConfigPath.absolute(out[key], base_dir)
|
|
517
|
+
end
|
|
518
|
+
|
|
493
519
|
def self.resolve_plugins_io_paths!(out, base_dir)
|
|
494
520
|
plugins_io = out["plugins_io"]
|
|
495
521
|
return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"]
|
|
@@ -572,6 +598,7 @@ module Rigor
|
|
|
572
598
|
@libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
|
|
573
599
|
sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
|
|
574
600
|
@signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
|
|
601
|
+
@core_root = coerce_core_root(data.fetch("core_root", DEFAULTS.fetch("core_root")))
|
|
575
602
|
test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
|
|
576
603
|
@test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
|
|
577
604
|
@sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
|
|
@@ -585,18 +612,13 @@ module Rigor
|
|
|
585
612
|
# ADR-67 WD6a — resolve to a strict Boolean so a truthy non-`true` value (e.g. a stray String) does not
|
|
586
613
|
# silently enable the gate; only the literal `true` activates the check-walk collector pre-pass.
|
|
587
614
|
@parameter_inference = data.fetch("parameter_inference", DEFAULTS.fetch("parameter_inference")) == true
|
|
588
|
-
# Resolved before `@effects` — ADR-103 WD15's "effects-on-by-default" reads {#bleeding_edge_active?},
|
|
589
|
-
# which reads `@bleeding_edge_active_ids`.
|
|
590
|
-
@bleeding_edge = coerce_bleeding_edge(
|
|
591
|
-
data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge"))
|
|
592
|
-
)
|
|
593
|
-
@bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
|
|
594
|
-
@bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
|
|
595
615
|
# ADR-103 WD13 — presence, not truthiness: `effects:` written with no body parses as nil and still
|
|
596
|
-
# means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
616
|
+
# means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads. The raw
|
|
617
|
+
# input is kept so {#with_bleeding_edge} re-derives the effects block from the same source.
|
|
618
|
+
@effects_key_present = effects_key_present
|
|
619
|
+
@effects_written = Ractor.make_shareable(data.fetch("effects", false))
|
|
620
|
+
derive_bleeding_edge(data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge")))
|
|
621
|
+
derive_effects
|
|
600
622
|
@cache_path = cache.fetch("path").to_s
|
|
601
623
|
raw_max = cache.fetch("max_bytes")
|
|
602
624
|
@cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max)
|
|
@@ -662,6 +684,7 @@ module Rigor
|
|
|
662
684
|
"disable" => disabled_rules,
|
|
663
685
|
"libraries" => libraries,
|
|
664
686
|
"signature_paths" => signature_paths,
|
|
687
|
+
"core_root" => core_root,
|
|
665
688
|
# `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
|
|
666
689
|
# incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
|
|
667
690
|
# invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
|
|
@@ -742,15 +765,14 @@ module Rigor
|
|
|
742
765
|
# It is a frozen `dup` with the bleeding-edge ivars re-set: `dup` returns an unfrozen shallow copy
|
|
743
766
|
# (every other ivar is the receiver's deeply-frozen value, safe to share read-only), the replacements
|
|
744
767
|
# are themselves deeply frozen, and the re-`freeze` keeps the result `Ractor.shareable?` for the worker
|
|
745
|
-
# path. **Every ivar `#initialize` derives from
|
|
746
|
-
#
|
|
768
|
+
# path. **Every ivar `#initialize` derives from the bleeding-edge selector has to be re-derived here** — it
|
|
769
|
+
# calls the same {#derive_bleeding_edge} and {#derive_effects} that `#initialize` does, so the selector,
|
|
770
|
+
# the severity map, the active-id set and the `effects:` default (ADR-103 WD15's "effects-on-by-default")
|
|
771
|
+
# all follow the new value, and a derived ivar added to `#initialize` is picked up without a second edit.
|
|
747
772
|
def with_bleeding_edge(value)
|
|
748
|
-
selector = coerce_bleeding_edge(value)
|
|
749
773
|
copy = dup
|
|
750
|
-
copy.
|
|
751
|
-
copy.
|
|
752
|
-
BleedingEdge.severity_overrides_for(selector))
|
|
753
|
-
copy.instance_variable_set(:@bleeding_edge_active_ids, BleedingEdge.active_ids_for(selector))
|
|
774
|
+
copy.send(:derive_bleeding_edge, value)
|
|
775
|
+
copy.send(:derive_effects)
|
|
754
776
|
copy.freeze
|
|
755
777
|
end
|
|
756
778
|
|
|
@@ -784,19 +806,38 @@ module Rigor
|
|
|
784
806
|
#
|
|
785
807
|
# Same `dup` + re-`freeze` shape as {#with_bleeding_edge}: every other ivar is the receiver's deeply
|
|
786
808
|
# frozen value, safe to share read-only, and the result stays `Ractor.shareable?` for the worker path.
|
|
809
|
+
# The implicit block is recorded as a written empty one, so a later {#with_bleeding_edge} re-derives
|
|
810
|
+
# it instead of falling back to the absent-key default and dropping it.
|
|
787
811
|
def with_effects_enabled
|
|
788
812
|
return self if effects_enabled?
|
|
789
813
|
|
|
790
814
|
copy = dup
|
|
791
|
-
copy.instance_variable_set(:@
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
copy.instance_variable_set(:@effects_check, true)
|
|
815
|
+
copy.instance_variable_set(:@effects_key_present, true)
|
|
816
|
+
copy.instance_variable_set(:@effects_written, {}.freeze)
|
|
817
|
+
copy.send(:derive_effects)
|
|
795
818
|
copy.freeze
|
|
796
819
|
end
|
|
797
820
|
|
|
798
821
|
private
|
|
799
822
|
|
|
823
|
+
# ADR-50 § WD2 / ADR-103 WD15 — derives the bleeding-edge selector and the two ivars the
|
|
824
|
+
# {#bleeding_edge_active?} predicate and the severity resolution read. `#initialize` and
|
|
825
|
+
# {#with_bleeding_edge} both call this, so there is one derivation to keep in step.
|
|
826
|
+
def derive_bleeding_edge(value)
|
|
827
|
+
@bleeding_edge = coerce_bleeding_edge(value)
|
|
828
|
+
@bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
|
|
829
|
+
@bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
# ADR-103 WD13 / WD15 — derives the `effects:` block and its snapshot and policy ivars from the raw
|
|
833
|
+
# `effects:` input the instance kept (`@effects_written`, `@effects_key_present`). The effects-on-by-default
|
|
834
|
+
# default reads the active-id set, so {#derive_bleeding_edge} must run first.
|
|
835
|
+
def derive_effects
|
|
836
|
+
@effects = coerce_effects(@effects_written, effects_key_present: @effects_key_present)
|
|
837
|
+
coerce_effects_snapshot(@effects)
|
|
838
|
+
coerce_effects_policy(@effects)
|
|
839
|
+
end
|
|
840
|
+
|
|
800
841
|
# ADR-103 WD13 — the `effects:` block, or nil when the key is absent or explicitly `false`. A
|
|
801
842
|
# present-but-empty key (`effects:` alone, which YAML parses as nil) and `effects: {}` are the same
|
|
802
843
|
# thing: collection on, every sub-key defaulted. A non-Hash body is normalised to `{}` rather than
|
|
@@ -808,8 +849,7 @@ module Rigor
|
|
|
808
849
|
# as `effects: {}` when the "effects-on-by-default" bleeding-edge feature is adopted. An explicit
|
|
809
850
|
# `effects: false` — `effects_key_present == true` — always stays off regardless of the feature, and a
|
|
810
851
|
# written block always wins outright: the feature can only fill an *absence*.
|
|
811
|
-
def coerce_effects(
|
|
812
|
-
value = data.fetch("effects", false)
|
|
852
|
+
def coerce_effects(value, effects_key_present:)
|
|
813
853
|
return {}.freeze if value == false && !effects_key_present && effects_forced_on_by_bleeding_edge?
|
|
814
854
|
return nil if value == false
|
|
815
855
|
|
|
@@ -1044,7 +1084,7 @@ module Rigor
|
|
|
1044
1084
|
end
|
|
1045
1085
|
end
|
|
1046
1086
|
|
|
1047
|
-
# `target_ruby` is passed
|
|
1087
|
+
# `target_ruby` is passed as Prism's `version:` option at the analyser's parse sites
|
|
1048
1088
|
# (`Analysis::Runner`, `CLI::TypeOfCommand`, `CLI::TypeScanCommand`) so projects that target an older
|
|
1049
1089
|
# Ruby get parse errors for syntax their target doesn't support. Format validation here is loose —
|
|
1050
1090
|
# accepts any `<major>.<minor>` or `<major>.<minor>.<patch>` form, plus the literal `"latest"`. Prism
|
|
@@ -1087,6 +1127,18 @@ module Rigor
|
|
|
1087
1127
|
# ADR-22 WD2 (b) — `baseline: <path>` activates the file; `baseline: false` is the explicit-disable form
|
|
1088
1128
|
# (useful in `.rigor.yml` to override an upstream `.rigor.dist.yml` that names a baseline). `nil`
|
|
1089
1129
|
# (default / absent key) is also "no baseline".
|
|
1130
|
+
# Issue #2517 — the directory's existence and contents are checked where the environment is built
|
|
1131
|
+
# ({Environment::CoreRoot.validate!}), not here: `rigor init`, `rigor doctor` and the language server's reload
|
|
1132
|
+
# load a configuration without building an environment, and must still be able to read one naming a core that
|
|
1133
|
+
# is not generated yet.
|
|
1134
|
+
def coerce_core_root(value)
|
|
1135
|
+
return nil if value.nil?
|
|
1136
|
+
raise ConfigurationError, "core_root must be a directory path, got #{value.inspect}" unless value.is_a?(String)
|
|
1137
|
+
raise ConfigurationError, "core_root must not be empty" if value.strip.empty?
|
|
1138
|
+
|
|
1139
|
+
value.dup.freeze
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1090
1142
|
def coerce_baseline_path(value)
|
|
1091
1143
|
return nil if value.nil? || value == false
|
|
1092
1144
|
|
|
@@ -58,11 +58,9 @@ module Rigor
|
|
|
58
58
|
node.is_a?(Prism::SingletonClassNode) || (node.is_a?(Prism::CallNode) && node.block.is_a?(Prism::BlockNode))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
# A class body.
|
|
62
61
|
CLASS_BODY = of(:class, false)
|
|
63
62
|
# A `class << self` body: `self` and the definee are both the singleton class.
|
|
64
63
|
SINGLETON_CLASS_BODY = of(:singleton_class, true)
|
|
65
|
-
# The body of an instance method of the class.
|
|
66
64
|
INSTANCE_METHOD_BODY = of(:instance, false)
|
|
67
65
|
# The top level, where `self` is `main`, an instance of `Object`, and a `def` defines `Object#m`.
|
|
68
66
|
TOP_LEVEL = INSTANCE_METHOD_BODY
|
|
@@ -8,7 +8,7 @@ module Rigor
|
|
|
8
8
|
# An author-declared upper bound on one method's effect labels (ADR-103 WD1; normative in
|
|
9
9
|
# `docs/type-specification/effect-labels.md` § Effect envelopes).
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# Its facts:
|
|
12
12
|
#
|
|
13
13
|
# - {#owner_key} — the method key the envelope binds (`Class#m` / `Class.m`). A class-level
|
|
14
14
|
# envelope is read once and {#rebind}ed onto each method of that class discovery knows.
|
|
@@ -69,7 +69,7 @@ module Rigor
|
|
|
69
69
|
VALIDATION_TRIGGERS = %w[valid? invalid? validate!].freeze
|
|
70
70
|
SINGLETON_SAVE_TRIGGERS = %w[create create!].freeze
|
|
71
71
|
|
|
72
|
-
# `initialize` is not a mailer action
|
|
72
|
+
# `initialize` is not a mailer action.
|
|
73
73
|
NON_ACTION_METHODS = %w[initialize].to_set.freeze
|
|
74
74
|
|
|
75
75
|
module_function
|
|
@@ -32,13 +32,10 @@ module Rigor
|
|
|
32
32
|
# `foo=`, and deliberately not `==` / `<=` / `!=` / `===`.
|
|
33
33
|
ATTRIBUTE_WRITER = /\A[a-z_][A-Za-z0-9_]*=\z/
|
|
34
34
|
|
|
35
|
-
# Every Hash method that mutates its receiver
|
|
36
|
-
# questions of a `HashShape`
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# `h.clear` does (unless the hash already compares by identity, when it returns before the check), and a
|
|
40
|
-
# method whose one effect was `@h.compare_by_identity` did not read as mutating while this cited the first
|
|
41
|
-
# table alone.
|
|
35
|
+
# Every Hash method that mutates its receiver: the union of two widening tables that answer different
|
|
36
|
+
# questions of a `HashShape` (the pair set; what a read of the pairs answers). Either is a write to the
|
|
37
|
+
# receiver, so either counts. `compare_by_identity` lives only in the second, and it raises `FrozenError`
|
|
38
|
+
# on a frozen hash as `h.clear` does, so a method whose one effect is that call must still read as mutating.
|
|
42
39
|
#
|
|
43
40
|
# `rehash` is in neither table because it changes no read a shape can state: a shape's keys are literals, which
|
|
44
41
|
# a rebuild never merges. It still rebuilds the receiver's table in place, and it is the one name spelt here.
|
|
@@ -363,7 +363,7 @@ module Rigor
|
|
|
363
363
|
# dispatch target is spelled under a different key, and this is the rewrite: the same ancestor walk
|
|
364
364
|
# {#resolve_owner} performs, on the instance side, for `initialize`.
|
|
365
365
|
#
|
|
366
|
-
#
|
|
366
|
+
# Four boundaries, and each of them can only narrow:
|
|
367
367
|
#
|
|
368
368
|
# - a project `def self.new` **wins**. It is the definition `Const.new` actually reaches, and a
|
|
369
369
|
# constructor that overrides `new` is the case where `#initialize` is not the answer. Resolved
|
|
@@ -35,8 +35,8 @@ module Rigor
|
|
|
35
35
|
# without proposing an unrelated label for a genuinely new spelling.
|
|
36
36
|
SUGGESTION_DISTANCE_CAP = 2
|
|
37
37
|
|
|
38
|
-
# The shared registry as shipped. Memoised: the YAML parse is a once-per-process cost,
|
|
39
|
-
#
|
|
38
|
+
# The shared registry as shipped. Memoised: the YAML parse is a once-per-process cost, so it stays
|
|
39
|
+
# lazy rather than paying at `require "rigor"`.
|
|
40
40
|
def self.default
|
|
41
41
|
@default ||= load_file(DATA_PATH)
|
|
42
42
|
end
|
|
@@ -55,7 +55,7 @@ module Rigor
|
|
|
55
55
|
#
|
|
56
56
|
# A count rather than the causes themselves (#434). On redmine the cause renderings were 144,771 of
|
|
57
57
|
# the file's 293,276 bytes, on lines up to 821 characters — simultaneously the half a reviewer
|
|
58
|
-
# cannot read and the half that churns when an unrelated call moves.
|
|
58
|
+
# cannot read and the half that churns when an unrelated call moves. {SnapshotDiff} already makes that
|
|
59
59
|
# argument about origins ("a record keyed by them would churn on every refactor"); the causes are
|
|
60
60
|
# the same class of thing and were the one place it was not applied. What a reviewer needs from the
|
|
61
61
|
# record is the stable fact that the row is not exhaustive and by how much, which is what a count
|
|
@@ -8,8 +8,6 @@ module Rigor
|
|
|
8
8
|
# A tainted summary reads "these effects, and possibly more": the cause says which knowledge the
|
|
9
9
|
# analyzer could not have. Taint never produces a finding — diagnostics read the proven lane
|
|
10
10
|
# only — so this enum is a reporting vocabulary, not a diagnostic one.
|
|
11
|
-
#
|
|
12
|
-
# Nothing produces causes yet; the collector of #379 is the first writer.
|
|
13
11
|
module TaintCause
|
|
14
12
|
# Closed and ordered by how it arises: dispatch the analyzer could not resolve first, then
|
|
15
13
|
# attribution it had to take on trust, then the collector's own limits.
|
|
@@ -586,9 +586,9 @@ module Rigor
|
|
|
586
586
|
# #1071 widens the answer per `respond_to` arm: a unit that spelled a dispatcher renders one
|
|
587
587
|
# conventional `<action>.<fmt>` template per arm rather than one `<action>.html`, so the row is
|
|
588
588
|
# applied once per arm the walk read, each answered outright (a `responds:` row at the arm's own
|
|
589
|
-
# top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html`
|
|
590
|
-
#
|
|
591
|
-
#
|
|
589
|
+
# top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html` answer
|
|
590
|
+
# is kept where no arm was read, and beside the arms when the dispatcher is nested under a branch,
|
|
591
|
+
# because the plain fall-through still exists on the path that skips it.
|
|
592
592
|
def apply_unit_callees
|
|
593
593
|
return if @responded || @singleton || @non_public
|
|
594
594
|
return if @owner_class.nil? || @method_name.nil?
|
|
@@ -811,11 +811,9 @@ module Rigor
|
|
|
811
811
|
#
|
|
812
812
|
# **Unless the syntax named the class** (#463). The `Dynamic` exclusion exists because a projected
|
|
813
813
|
# receiver class is a guess; a written constant path is not one, and {#catalog_target} already
|
|
814
|
-
# trusts it for the row lookup. Refusing the posture there made the catalogue's reach
|
|
815
|
-
# whether the bundled rbs
|
|
816
|
-
#
|
|
817
|
-
# `net-imap`. That is why the `Net::FTP` row has never once fired, and why Redmine's IMAP and POP3
|
|
818
|
-
# pollers proved no network at all.
|
|
814
|
+
# trusts it for the row lookup. Refusing the posture there made the catalogue's reach depend on
|
|
815
|
+
# whether the bundled rbs ships a signature for the constant: `Net::HTTP.get` proved `io.net.http`,
|
|
816
|
+
# while `Net::IMAP.new` proved nothing, because rbs ships `net-http` and not `net-imap`.
|
|
819
817
|
#
|
|
820
818
|
# The site is then **claimed**, and so discharges, like every other catalogue answer. That is not a
|
|
821
819
|
# softening of the `dynamic-receiver` rule but its premise failing: the taint says the class the
|
|
@@ -974,12 +972,10 @@ module Rigor
|
|
|
974
972
|
positional_arity(node).positive?
|
|
975
973
|
end
|
|
976
974
|
|
|
977
|
-
# The class the catalogue would look this call up under, as `[owner, singleton,
|
|
975
|
+
# The class the catalogue would look this call up under, as `[owner, singleton, implicit, named]`.
|
|
978
976
|
# Spelled from the syntax where the syntax settles it and from the typer's receiver otherwise;
|
|
979
|
-
# `[nil, …]` when neither does.
|
|
980
|
-
#
|
|
981
|
-
# author wrote**, which is what licenses the posture on a receiver the typer could not resolve
|
|
982
|
-
# ({#posture_allowed?}).
|
|
977
|
+
# `[nil, …]` when neither does. `named` says the owner came from a **constant path the author wrote**,
|
|
978
|
+
# which is what licenses the posture on a receiver the typer could not resolve ({#posture_allowed?}).
|
|
983
979
|
def catalog_target(node, record)
|
|
984
980
|
receiver = node.receiver
|
|
985
981
|
return ["Kernel", false, true, false] if receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
@@ -31,7 +31,7 @@ module Rigor
|
|
|
31
31
|
# The list is hard-coded for the MVP because it tracks rigor's bundled coverage 1:1. When a new gem is
|
|
32
32
|
# vendored under `data/vendored_gem_sigs/` or added to `DEFAULT_LIBRARIES`, add its name here.
|
|
33
33
|
SKIPPED_GEMS_BY_DEFAULT = Set[
|
|
34
|
-
# DEFAULT_LIBRARIES (lib/rigor/environment.rb)
|
|
34
|
+
# DEFAULT_LIBRARIES (lib/rigor/environment/default_libraries.rb)
|
|
35
35
|
"pathname", "optparse", "json", "yaml", "fileutils",
|
|
36
36
|
"tempfile", "tmpdir", "stringio", "forwardable",
|
|
37
37
|
"digest", "securerandom", "uri", "logger", "date",
|
|
@@ -100,14 +100,10 @@ module Rigor
|
|
|
100
100
|
expected_dirs = expected_gem_dirs(locked_gems)
|
|
101
101
|
filtered.select do |sig_dir|
|
|
102
102
|
if git_sourced_layout?(sig_dir)
|
|
103
|
-
# A git install's directory
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
# its NAME is still present in the lockfile (now via the rubygems entry). Matching on name only
|
|
108
|
-
# would silently readmit that stale git install — this repo's own `vendor/bundle` had exactly
|
|
109
|
-
# this after `binpacker` moved from `git:` to a released gem. `locked_gems` must say this name is
|
|
110
|
-
# CURRENTLY git-sourced, not merely present.
|
|
103
|
+
# A git install's directory carries no version, so the lockfile filter can only check the gem NAME.
|
|
104
|
+
# Name alone is not enough: a gem that moved from `git:` to a released version keeps its stale
|
|
105
|
+
# `bundler/gems/` checkout until `bundle clean`, under a name the lockfile still lists. `locked_gems`
|
|
106
|
+
# must say this name is CURRENTLY git-sourced, not merely present.
|
|
111
107
|
locked = locked_gems[gem_name_from_sig_path(sig_dir)]
|
|
112
108
|
!!locked&.git_source
|
|
113
109
|
else
|
|
@@ -9,8 +9,8 @@ module Rigor
|
|
|
9
9
|
# This is the static fast path for built-ins; `Environment` falls through to `RbsLoader` for all other
|
|
10
10
|
# names — the two tiers are complementary.
|
|
11
11
|
#
|
|
12
|
-
# See docs/internal-spec/inference-engine.md for the binding contract (
|
|
13
|
-
# recognised).
|
|
12
|
+
# See docs/internal-spec/inference-engine.md for the binding contract (the classes in
|
|
13
|
+
# `SLICE_1_BUILT_INS` MUST always be recognised).
|
|
14
14
|
class ClassRegistry
|
|
15
15
|
SLICE_1_BUILT_INS = [
|
|
16
16
|
Integer,
|
|
@@ -27,7 +27,7 @@ module Rigor
|
|
|
27
27
|
# Common Ruby core classes that user code routinely names by constant reference. Adding them to the
|
|
28
28
|
# registry lets `nominal_for_name` resolve `Array`, `Hash`, etc. without each call site re-listing
|
|
29
29
|
# them. The static registry is the cheap fast path; `RbsLoader` extends coverage to all other
|
|
30
|
-
# RBS-declared names
|
|
30
|
+
# RBS-declared names.
|
|
31
31
|
SLICE_2_BUILT_INS = [
|
|
32
32
|
Array,
|
|
33
33
|
Hash,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../configuration_error"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
class Environment
|
|
8
|
+
# Issue #2517 — a core signature directory chosen IN PLACE OF the `rbs` gem's own `core/` (`core_root:` in
|
|
9
|
+
# `.rigor.yml`). The caller is a provider such as rbs-shifter, which rewrites upstream core for an older
|
|
10
|
+
# CRuby (`Array#rfind` gone, `String#strip` without arguments); Rigor accepts the directory and passes it to
|
|
11
|
+
# `RBS::EnvironmentLoader.new(core_root:)`, so the default core is never loaded beside it.
|
|
12
|
+
#
|
|
13
|
+
# An explicit choice is never second-guessed by a fallback: a directory that is missing, unreadable or holds
|
|
14
|
+
# no signature, and an environment that fails to build from it, raise {CoreRootError} instead of analysing
|
|
15
|
+
# against the default core or against a degraded `Dynamic[top]` environment. Both would report a project as
|
|
16
|
+
# clean for the very APIs the selection exists to reject.
|
|
17
|
+
module CoreRoot
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param path — the configured directory, already resolved against the config file's directory.
|
|
21
|
+
# @return the absolute directory as a frozen String (`Ractor.shareable?`, so a frozen loader stays shareable).
|
|
22
|
+
# @raise CoreRootError — the directory cannot serve as a core.
|
|
23
|
+
def validate!(path)
|
|
24
|
+
absolute = File.expand_path(path.to_s)
|
|
25
|
+
reason = invalid_reason(absolute)
|
|
26
|
+
raise CoreRootError, "core_root: #{absolute.inspect} #{reason}" if reason
|
|
27
|
+
|
|
28
|
+
absolute.dup.freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# A selected core inside a signature root, or one containing it, loads its files twice — once as core and once
|
|
32
|
+
# as project signatures — and every class it declares then fails to build on its duplicated methods.
|
|
33
|
+
#
|
|
34
|
+
# @param core_root — a {.validate!}d directory.
|
|
35
|
+
# @param signature_paths — the project's own signature roots.
|
|
36
|
+
# @raise CoreRootError — the two overlap.
|
|
37
|
+
def reject_overlap!(core_root, signature_paths)
|
|
38
|
+
core = File.join(real_path(core_root), "")
|
|
39
|
+
Array(signature_paths).each do |path|
|
|
40
|
+
root = File.join(real_path(path), "")
|
|
41
|
+
next unless core.start_with?(root) || root.start_with?(core)
|
|
42
|
+
|
|
43
|
+
raise CoreRootError, "core_root: #{core_root.inspect} overlaps the signature path #{path.to_s.inspect}, so " \
|
|
44
|
+
"its signatures would load twice; move the core outside the project's signature paths"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Compared resolved, because the project-signature walk does not follow a symlinked directory: a `sig/core`
|
|
49
|
+
# link to a core kept elsewhere loads once, as core, and must not be rejected. A path that does not exist yet
|
|
50
|
+
# resolves through its nearest existing ancestor, so the two sides of a comparison are spelled alike.
|
|
51
|
+
def real_path(path)
|
|
52
|
+
absolute = File.expand_path(path.to_s)
|
|
53
|
+
missing = []
|
|
54
|
+
until File.exist?(absolute) || File.dirname(absolute) == absolute
|
|
55
|
+
missing.unshift(File.basename(absolute))
|
|
56
|
+
absolute = File.dirname(absolute)
|
|
57
|
+
end
|
|
58
|
+
File.join(File.realpath(absolute), *missing)
|
|
59
|
+
rescue SystemCallError
|
|
60
|
+
File.expand_path(path.to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def invalid_reason(absolute)
|
|
64
|
+
return "does not exist" unless File.exist?(absolute)
|
|
65
|
+
return "is not a directory" unless File.directory?(absolute)
|
|
66
|
+
return "is not readable" unless File.readable?(absolute)
|
|
67
|
+
|
|
68
|
+
"contains no .rbs signature file" if RootedGlob.glob(absolute, "**/*.rbs").empty?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The selected core cannot be used. A {ConfigurationError}, so the CLI reports it as one `rigor:` line with
|
|
73
|
+
# the usage exit code rather than as a crash.
|
|
74
|
+
class CoreRootError < ConfigurationError
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
class Environment
|
|
5
|
-
#
|
|
5
|
+
# Stdlib libraries that `Environment.for_project` loads on top of RBS core
|
|
6
6
|
# unless the caller passes an explicit `libraries:` array. Each entry MUST be a stdlib library name
|
|
7
7
|
# accepted by `RBS::EnvironmentLoader#has_library?`; unknown libraries MUST fail-soft
|
|
8
8
|
# (`RbsLoader#build_env` already filters through `has_library?`). The default set covers the common
|