rigortype 0.4.6 → 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 +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- 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 +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -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/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- 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 +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -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 +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- 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 +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- 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/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- 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 +24 -31
- 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 +31 -12
- 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 +108 -102
- 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 +25 -20
- 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 +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- 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 +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- 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 +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- 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 +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- 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 +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- 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 +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- 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 +88 -77
- 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 +39 -7
- 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 +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- 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/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- 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 +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -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 +3 -3
- 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 +1 -1
- 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 +9 -7
- 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 +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -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 +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- 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_yielder.rb +11 -17
- 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 +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- 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.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- 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 +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- 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 +3 -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 +8 -3
- 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 +39 -1
- 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 +7 -3
- 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 +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- 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 +9 -13
- data/lib/rigor/plugin/registry.rb +3 -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 +44 -5
- 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 +15 -1
- 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 +365 -31
- 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 +371 -58
- 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 +3 -5
- 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/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- 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 +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- 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 +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -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 +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -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-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 +1 -1
- 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 +1 -1
- 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 +3 -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/sig/rigor/analysis/baseline.rbs +0 -3
- 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/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- 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 +36 -3
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "rigor/plugin"
|
|
4
4
|
|
|
5
5
|
require_relative "activesupport_core_ext/effects"
|
|
6
|
+
require_relative "activesupport_core_ext/symbolize_keys"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Plugin
|
|
@@ -43,12 +44,15 @@ module Rigor
|
|
|
43
44
|
manifest(
|
|
44
45
|
id: "activesupport-core-ext",
|
|
45
46
|
target_gems: ["activesupport"],
|
|
47
|
+
# Bumped 2026-10-27 (#2433) — `Hash#symbolize_keys` / `#to_options` map each key member through a
|
|
48
|
+
# `dynamic_return` hook instead of claiming `Hash[Symbol, V]`.
|
|
49
|
+
#
|
|
46
50
|
# Bumped 2026-09-10 (#534 item 7) — `ActiveSupport::Concern` is declared, so `extend
|
|
47
51
|
# ActiveSupport::Concern` resolves instead of reading as an unresolved constant.
|
|
48
52
|
#
|
|
49
53
|
# Bumped 2026-09-02 (#632) — `ActiveSupport::Duration`'s reader surface (`ago`/`to_i`/`iso8601`/…)
|
|
50
54
|
# is now declared, open_receivers-protected; see the class comment above.
|
|
51
|
-
version: "0.
|
|
55
|
+
version: "0.6.0",
|
|
52
56
|
description: "RBS bundle for the most-frequently-flagged ActiveSupport core_ext extensions, " \
|
|
53
57
|
"plus the `ActiveSupport::Duration` type of the numeric time multipliers and its " \
|
|
54
58
|
"reader surface.",
|
|
@@ -190,11 +194,9 @@ module Rigor
|
|
|
190
194
|
argument = duration_arithmetic_argument(call_node)
|
|
191
195
|
next nil if argument.nil?
|
|
192
196
|
|
|
193
|
-
# The ARGUMENT is checked before the receiver: it is the cheaper
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
# what makes the ordering worth anything: an argument that is no Duration operand at all
|
|
197
|
-
# (`"a" + b`, `list + other`) declines here and never types its receiver on this rule's behalf.
|
|
197
|
+
# The ARGUMENT is checked before the receiver: it is the cheaper test on the common shapes (`i + 1`,
|
|
198
|
+
# `n * 2`), and this rule is consulted on every `+` / `-` / `*`. An argument that is no Duration
|
|
199
|
+
# operand (`"a" + b`, `list + other`) declines here and never types its receiver on this rule's behalf.
|
|
198
200
|
argument_kind = duration_operand_kind(scope&.type_of(argument))
|
|
199
201
|
next nil if argument_kind.nil?
|
|
200
202
|
|
|
@@ -204,6 +206,17 @@ module Rigor
|
|
|
204
206
|
duration_arithmetic_result(call_node.name, receiver_kind, argument_kind)
|
|
205
207
|
end
|
|
206
208
|
|
|
209
|
+
# #2433 — `symbolize_keys` / `to_options` keep a key without `to_sym`, so the key type is mapped per
|
|
210
|
+
# member; see {SymbolizeKeys}. The RBS rows answer `Hash[untyped, V]` wherever this declines.
|
|
211
|
+
dynamic_return methods: %i[symbolize_keys to_options] do |call_node, scope|
|
|
212
|
+
next nil unless call_node.is_a?(Prism::CallNode)
|
|
213
|
+
next nil unless call_node.receiver
|
|
214
|
+
next nil unless call_node.arguments.nil?
|
|
215
|
+
|
|
216
|
+
# A block is ignored by `symbolize_keys`, so it does not decline.
|
|
217
|
+
SymbolizeKeys.result(scope&.type_of(call_node.receiver), scope)
|
|
218
|
+
end
|
|
219
|
+
|
|
207
220
|
# ADR-88 WD1 — both tables are static and the RBS bundle ships with the gem; nothing here reads a
|
|
208
221
|
# project file, so a project enabling this plugin stays incremental-capable.
|
|
209
222
|
def incremental_state_fingerprint
|
|
@@ -1565,28 +1565,35 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
1565
1565
|
# non-bang half is `%a{pure}`. `assert_valid_keys` raises rather than
|
|
1566
1566
|
# mutating (raising is not an effect label).
|
|
1567
1567
|
%a{pure}
|
|
1568
|
-
|
|
1568
|
+
# `key.to_sym rescue key` keeps a key without `to_sym`, so no one key type is right: `Hash[untyped, V]` here,
|
|
1569
|
+
# and the plugin's `dynamic_return` hook (#2433) maps each key member where it can.
|
|
1570
|
+
def symbolize_keys: () -> Hash[untyped, V]
|
|
1571
|
+
# `symbolize_keys!` keeps `-> self`: `key.to_sym rescue key` leaves a key without `to_sym` as it is, so
|
|
1572
|
+
# `Hash[Symbol, V]` is wrong for an Integer-keyed receiver (#2424 review). The other bang forms below
|
|
1573
|
+
# answer the re-keyed or re-valued hash.
|
|
1569
1574
|
def symbolize_keys!: () -> self
|
|
1570
1575
|
%a{pure}
|
|
1571
1576
|
def deep_symbolize_keys: () -> Hash[untyped, untyped]
|
|
1572
|
-
def deep_symbolize_keys!: () ->
|
|
1577
|
+
def deep_symbolize_keys!: () -> Hash[untyped, untyped]
|
|
1573
1578
|
%a{pure}
|
|
1574
1579
|
def stringify_keys: () -> Hash[String, V]
|
|
1575
|
-
def stringify_keys!: () ->
|
|
1580
|
+
def stringify_keys!: () -> Hash[String, V]
|
|
1576
1581
|
%a{pure}
|
|
1577
1582
|
def deep_stringify_keys: () -> Hash[untyped, untyped]
|
|
1578
|
-
def deep_stringify_keys!: () ->
|
|
1583
|
+
def deep_stringify_keys!: () -> Hash[untyped, untyped]
|
|
1579
1584
|
%a{pure}
|
|
1580
1585
|
def assert_valid_keys: (*K | Array[K]) -> self
|
|
1586
|
+
# The block's result is the new key at every level, so the keys are its type, not `K`: `{ a: 1 }
|
|
1587
|
+
# .deep_transform_keys(&:to_s)` has String keys. Nested values are re-keyed too, so they read `untyped`.
|
|
1581
1588
|
%a{pure}
|
|
1582
|
-
def deep_transform_keys: () { (K) ->
|
|
1583
|
-
def deep_transform_keys!: () { (K) ->
|
|
1589
|
+
def deep_transform_keys: [X] () { (K) -> X } -> Hash[X, untyped]
|
|
1590
|
+
def deep_transform_keys!: [X] () { (K) -> X } -> Hash[X, untyped]
|
|
1584
1591
|
%a{pure}
|
|
1585
1592
|
def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
|
|
1586
|
-
def deep_transform_values!: () { (V) -> untyped } ->
|
|
1593
|
+
def deep_transform_values!: () { (V) -> untyped } -> Hash[K, untyped]
|
|
1587
1594
|
# `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
|
|
1588
1595
|
%a{pure}
|
|
1589
|
-
def to_options: () -> Hash[
|
|
1596
|
+
def to_options: () -> Hash[untyped, V]
|
|
1590
1597
|
def to_options!: () -> self
|
|
1591
1598
|
|
|
1592
1599
|
# `core_ext/hash/deep_dup` — allocates a deep copy, never touches the
|
|
@@ -1600,11 +1607,17 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
1600
1607
|
# `core_ext/hash/deep_merge` — the block receives `(key, this_val,
|
|
1601
1608
|
# other_val)` and returns the merged value; neither `deep_merge` nor its
|
|
1602
1609
|
# block mutates either hash (containment covers the block itself).
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1610
|
+
# The result carries the argument's keys and values as well as the
|
|
1611
|
+
# receiver's, and a key both hold a Hash under merges into a third Hash
|
|
1612
|
+
# that is neither side's value type (`{ n: { x: 1 } }.deep_merge(n: { y:
|
|
1613
|
+
# "s" })[:n]` is `{ x: 1, y: "s" }`), so the values read `untyped`.
|
|
1614
|
+
# `(Hash[K, V]) -> Hash[K, V]` typed `h.deep_merge(b: "x")[:b]` as the
|
|
1615
|
+
# receiver's value type (#2424 review). The bang form answers the receiver.
|
|
1616
|
+
%a{pure}
|
|
1617
|
+
def deep_merge: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
|
|
1618
|
+
| [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
|
|
1619
|
+
def deep_merge!: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
|
|
1620
|
+
| [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
|
|
1608
1621
|
# `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
|
|
1609
1622
|
# recursing into a value. `:nodoc:`, but public.
|
|
1610
1623
|
%a{pure}
|
|
@@ -37,9 +37,9 @@ module Rigor
|
|
|
37
37
|
@classes.empty?
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# The classes alba infers for the associations that name no resource, limited to
|
|
41
|
-
#
|
|
42
|
-
#
|
|
40
|
+
# The classes alba infers for the associations that name no resource, limited to those that exist in
|
|
41
|
+
# the project. The block receives each association name and returns alba's `classify` of it (the
|
|
42
|
+
# caller owns the inflector; ADR-39).
|
|
43
43
|
def inferred_roots(&classify)
|
|
44
44
|
roots = @associations.filter_map do |association|
|
|
45
45
|
next unless resource?(association.owner)
|
|
@@ -212,9 +212,7 @@ module Rigor
|
|
|
212
212
|
# ... end` row's own recursive call (see {#each_block_type_info}). It caps the recursion at ONE
|
|
213
213
|
# level deep: with `nested: true`, {#walk_predicate_chain} declines a FURTHER `each do ... end`
|
|
214
214
|
# rather than recursing again (issue #137 deliberately does not model doubly-nested arrays of
|
|
215
|
-
# arrays).
|
|
216
|
-
# is the only caller that ever passes `nested: true}` — so the call graph has no cycle for the
|
|
217
|
-
# engine's return-type inference to approximate through.
|
|
215
|
+
# arrays). The recursion through {#each_block_type_info} is therefore bounded and terminates.
|
|
218
216
|
def collect_schema_shape(block_node, type_aliases, nested:)
|
|
219
217
|
required = {}
|
|
220
218
|
optional = {}
|
|
@@ -34,9 +34,9 @@ module Rigor
|
|
|
34
34
|
#
|
|
35
35
|
# The synthetic reader's return type is resolved via ADR-18's `returns_from_arg:` fact lookup: the
|
|
36
36
|
# call-site's second argument (`Types::String` etc.) is looked up through the `:dry_type_aliases`
|
|
37
|
-
# fact published by `rigor-dry-types`, yielding `
|
|
38
|
-
# misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
|
|
39
|
-
# falls back to
|
|
37
|
+
# fact published by `rigor-dry-types`, yielding the aliased class (`String`) for common cases. When the
|
|
38
|
+
# lookup misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
|
|
39
|
+
# falls back to the untyped floor silently.
|
|
40
40
|
#
|
|
41
41
|
# ## Scope (slice 2c minimum)
|
|
42
42
|
#
|
|
@@ -58,8 +58,8 @@ module Rigor
|
|
|
58
58
|
"by consuming `rigor-dry-types`'s `:dry_type_aliases` fact.",
|
|
59
59
|
# ADR-9 consumption — the precision-promotion path below uses `:dry_type_aliases` published by
|
|
60
60
|
# `rigor-dry-types`. The fact is optional: when the `rigor-dry-types` plugin isn't loaded, the
|
|
61
|
-
# `returns_from_arg:` lookup misses and the synthetic readers fall back to
|
|
62
|
-
# pre-ADR-18
|
|
61
|
+
# `returns_from_arg:` lookup misses and the synthetic readers fall back to the untyped floor (the
|
|
62
|
+
# pre-ADR-18 behaviour).
|
|
63
63
|
consumes: [{ plugin_id: "dry-types", name: :dry_type_aliases, optional: true }],
|
|
64
64
|
heredoc_templates: [
|
|
65
65
|
Rigor::Plugin::Macro::HeredocTemplate.new(
|
|
@@ -69,7 +69,7 @@ module Rigor
|
|
|
69
69
|
# ADR-18 — the synthetic reader's return type comes from the call site's second argument
|
|
70
70
|
# (`Types::String` etc.), resolved through the `:dry_type_aliases` fact. When the lookup
|
|
71
71
|
# misses (e.g. inline `attribute :tag, Types::String.constrained(...)`, whose receiver chain
|
|
72
|
-
# head isn't currently extracted), the row falls back to
|
|
72
|
+
# head isn't currently extracted), the row falls back to the untyped floor.
|
|
73
73
|
emit: [{
|
|
74
74
|
name: "\#{name}",
|
|
75
75
|
returns_from_arg: {
|
|
@@ -33,10 +33,9 @@ module Rigor
|
|
|
33
33
|
# `Coercible::*` (everything-to-target coercion), `Strict::*` (no coercion; raise if mismatch),
|
|
34
34
|
# `Params::*` (HTTP / query-string-style coercion, used by Hanami / Roda / dry-web in request
|
|
35
35
|
# handling), `JSON::*` (JSON-shape coercion). Each category exposes the same set of names as the
|
|
36
|
-
# canonical shortcuts above
|
|
37
|
-
#
|
|
38
|
-
# coercion semantics are a runtime concern.
|
|
39
|
-
# mapping the upstream gem publishes so call-site references work uniformly.
|
|
36
|
+
# canonical shortcuts above; only those names are registered here, so a category-specific extra
|
|
37
|
+
# is not covered. For Rigor's purposes the underlying class is the same regardless of category —
|
|
38
|
+
# coercion semantics are a runtime concern.
|
|
40
39
|
NESTED_CATEGORIES = %w[Coercible Strict Params JSON].freeze
|
|
41
40
|
private_constant :NESTED_CATEGORIES
|
|
42
41
|
|
|
@@ -229,8 +228,8 @@ module Rigor
|
|
|
229
228
|
|
|
230
229
|
# `include Dry.Types()` at the top of the module body is the canonical alias declaration. We
|
|
231
230
|
# accept the call anywhere in the body (some projects guard it with a `if defined?` check). The
|
|
232
|
-
# argument list
|
|
233
|
-
#
|
|
231
|
+
# `Dry.Types(...)` argument list is not inspected: kwargs such as `default: :nominal` install the
|
|
232
|
+
# aliases too.
|
|
234
233
|
def contains_dry_types_include?(body)
|
|
235
234
|
return false if body.nil?
|
|
236
235
|
|
|
@@ -21,7 +21,7 @@ module Rigor
|
|
|
21
21
|
# cross-plugin fact (ADR-9). Other dry-rb adapter plugins consume this fact:
|
|
22
22
|
#
|
|
23
23
|
# - `rigor-dry-struct` reads it so `attribute :city, Types::String` promotes `address.city` from
|
|
24
|
-
#
|
|
24
|
+
# from an untyped value to `String` via ADR-18's `returns_from_arg:` fact lookup.
|
|
25
25
|
# - `rigor-dry-validation` / `rigor-dry-schema` read it for per-key type recognition in `schema { … }`
|
|
26
26
|
# / `params { … }` blocks (separate plugin slice).
|
|
27
27
|
#
|
|
@@ -44,9 +44,8 @@ module Rigor
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# Renders `Foo` / `Foo::Bar` / `::Foo::Bar` as the `::`-joined String {ContractScanner}'s table is
|
|
47
|
-
# keyed by. Duplicated from rigor-dry-schema
|
|
48
|
-
#
|
|
49
|
-
# guard here too even though this leaf never touches dry-schema's own type/alias machinery.
|
|
47
|
+
# keyed by. Duplicated from rigor-dry-schema rather than shared: this leaf must not depend on that
|
|
48
|
+
# plugin, and reusing the helper would need a `registered_for` guard here too.
|
|
50
49
|
def constant_name(node)
|
|
51
50
|
case node
|
|
52
51
|
when Prism::ConstantReadNode then node.name.to_s
|
|
@@ -58,19 +58,16 @@ module Rigor
|
|
|
58
58
|
|
|
59
59
|
# ADR-37 dynamic return — refines `NewUserContract.new.call(input).to_h` to the per-contract
|
|
60
60
|
# `HashShape` when {#result_shape_for} finds one. Gated on the method name alone (mirrors
|
|
61
|
-
# rigor-dry-schema's
|
|
62
|
-
# generic `Result
|
|
63
|
-
#
|
|
64
|
-
# rigor-dry-schema isn't loaded, or the chain isn't a Contract call, this returns nil and the RBS
|
|
65
|
-
# overlay's generic `Hash[Symbol, untyped]` stands.
|
|
61
|
+
# rigor-dry-schema's `.to_h` rule); the chain match is in {ParamsShape.contract_name}, because the
|
|
62
|
+
# RBS overlay's generic `Result` cannot select a per-contract shape. Otherwise returns nil and the
|
|
63
|
+
# overlay's generic hash type stands.
|
|
66
64
|
dynamic_return methods: [:to_h] do |call_node, _scope|
|
|
67
65
|
result_shape_for(call_node)
|
|
68
66
|
end
|
|
69
67
|
|
|
70
68
|
# ADR-37 slice 1c two-pass — the "collect" half of `dry-validation.rule-key-mismatch`. Reads the
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
# in-block memo isn't needed here the way it is across many per-call `dynamic_return` invocations).
|
|
69
|
+
# same `:dry_type_aliases` fact as `#prepare`, straight from `services.fact_store` (no `#read_fact`
|
|
70
|
+
# memo: `services` here is run-wide, unlike the per-call `dynamic_return` blocks).
|
|
74
71
|
node_file_context do |root, _scope|
|
|
75
72
|
type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
|
|
76
73
|
ContractScanner.rule_key_issues(root, type_aliases)
|
|
@@ -14,7 +14,6 @@ module Rigor
|
|
|
14
14
|
signature_paths: ["sig"]
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
# Option catalogue and return types for Easy methods
|
|
18
17
|
EASY_RETURN_TYPES = {
|
|
19
18
|
perform: -> { Rigor::Type::Combinator.nominal_of("Integer") },
|
|
20
19
|
response_code: -> { Rigor::Type::Combinator.nominal_of("Integer") },
|
|
@@ -115,7 +115,7 @@ module Rigor
|
|
|
115
115
|
# 3. Inflected from the factory name — `:user` → `"User"`, `:admin_user` → `"AdminUser"`. The
|
|
116
116
|
# factory name is already singular by FactoryBot convention, so we only need camelization.
|
|
117
117
|
#
|
|
118
|
-
# Returns
|
|
118
|
+
# Returns the canonical class name.
|
|
119
119
|
def factory_model_class(call_node, factory_name)
|
|
120
120
|
explicit = explicit_class_option(call_node)
|
|
121
121
|
return explicit if explicit
|
|
@@ -84,27 +84,20 @@ module Rigor
|
|
|
84
84
|
@factory_search_paths = Array(config.fetch("factory_search_paths")).map(&:to_s)
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
# ADR-102 WD3 / #350 — the model classes
|
|
88
|
-
#
|
|
87
|
+
# ADR-102 WD3 / #350 — the factories' model classes are published as `:reachability_references`,
|
|
88
|
+
# not `:reachability_roots`.
|
|
89
89
|
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# It is a
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# It is a REFERENCE rather than a root because factories live in the test tree. Rooting them would make
|
|
97
|
-
# every factoried class production-reachable and erase ADR-102 WD8's "reachable only from tests"
|
|
98
|
-
# category for exactly the classes it is most likely to be about — a model kept alive by its factory
|
|
99
|
-
# and its spec and nothing else is the archetype of dead production code with a live test, and that is
|
|
100
|
-
# the single most actionable row this report produces. Carrying the `:test` role keeps the finding.
|
|
90
|
+
# It is a contribution because a factory names its class by a mechanism the constant scan cannot
|
|
91
|
+
# follow (`factory :user, class: "Admin::User"` is a string; a bare `factory :user` is constantized by
|
|
92
|
+
# FactoryBot). It is a reference rather than a root because factories live in the test tree: rooting
|
|
93
|
+
# them would make every factoried class production-reachable and erase ADR-102 WD8's "reachable only
|
|
94
|
+
# from tests" category. The `:test` role keeps that finding.
|
|
101
95
|
def prepare(services)
|
|
102
96
|
index = producer_value(:factory_index)
|
|
103
97
|
return if index.nil? || index.empty?
|
|
104
98
|
|
|
105
|
-
# The producer value and the ADR-9 fact are different channels (#921)
|
|
106
|
-
#
|
|
107
|
-
# what makes it visible to another plugin's `read_fact`, which is how rigor-rspec binds
|
|
99
|
+
# The producer value and the ADR-9 fact are different channels (#921): `fact_store.publish` is what
|
|
100
|
+
# makes the index visible to another plugin's `read_fact`, as rigor-rspec uses it to bind
|
|
108
101
|
# `create(:name)` to the factory's model class.
|
|
109
102
|
services.fact_store.publish(plugin_id: manifest.id, name: :factory_index, value: index)
|
|
110
103
|
|
|
@@ -118,8 +111,7 @@ module Rigor
|
|
|
118
111
|
|
|
119
112
|
# ADR-37 — per-call factory/attribute validation over the engine-owned walk. Each violation carries
|
|
120
113
|
# its own location (the call's message_loc, or the offending attribute key), so it is positioned
|
|
121
|
-
# via `diagnostic(node, location:)`.
|
|
122
|
-
# `diagnostics_for_file`.
|
|
114
|
+
# via `diagnostic(node, location:)`.
|
|
123
115
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
124
116
|
index = producer_value(:factory_index)
|
|
125
117
|
next [] if index.nil? || index.empty?
|
|
@@ -149,7 +149,6 @@ module Rigor
|
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
151
|
when :callback
|
|
152
|
-
# Issue 4 fix: extract callback definitions
|
|
153
152
|
args = node.arguments&.arguments || []
|
|
154
153
|
if args.size >= 3
|
|
155
154
|
cb_name = Analyzer.extract_symbol(args[0])
|
|
@@ -193,7 +192,6 @@ module Rigor
|
|
|
193
192
|
end
|
|
194
193
|
end
|
|
195
194
|
else
|
|
196
|
-
# Issue 5 fix: pass mod_name to sub-plugin recognizers
|
|
197
195
|
FFI.binding_recognizers.each do |recognizer|
|
|
198
196
|
facts = recognizer.recognize(node, mod_name)
|
|
199
197
|
Array(facts).each do |fact|
|
|
@@ -38,11 +38,10 @@ module Rigor
|
|
|
38
38
|
# Dynamic return rule for attached FFI functions
|
|
39
39
|
# Gated on known FFI library receivers and attached function names
|
|
40
40
|
#
|
|
41
|
-
# Issue #701 — both receiver kinds are declared because the block below already
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# on a `receivers:` entry answering for both.
|
|
41
|
+
# Issue #701 — both receiver kinds are declared because the block below already resolves `Nominal`
|
|
42
|
+
# and `Singleton` alike: `attach_function` installs the binding on the library module itself
|
|
43
|
+
# (`MyLib.strlen`), and `include`ing that module makes the same name an instance call. The
|
|
44
|
+
# kind-aware gate makes that intent explicit rather than relying on one `receivers:` entry for both.
|
|
46
45
|
dynamic_return receivers: -> { both_receiver_kinds(producer_value(:ffi_catalog)&.libraries) },
|
|
47
46
|
methods: -> { producer_value(:ffi_catalog)&.function_method_names || [] } do |call_node, scope|
|
|
48
47
|
catalog = producer_value(:ffi_catalog)
|
|
@@ -85,7 +85,6 @@ module Rigor
|
|
|
85
85
|
method_names: NAMESPACE_METHODS,
|
|
86
86
|
self_type: "singleton(Grape::API::Instance)"
|
|
87
87
|
),
|
|
88
|
-
# `params do ... end` — body `instance_eval`s on a `ParamsScope` instance.
|
|
89
88
|
Rigor::Plugin::Macro::BlockAsMethod.new(
|
|
90
89
|
receiver_constraint: "Grape::API",
|
|
91
90
|
method_names: %i[params],
|
|
@@ -121,7 +120,6 @@ module Rigor
|
|
|
121
120
|
method_names: %i[desc],
|
|
122
121
|
self_type: "Grape::DSL::Desc::ConfigContext"
|
|
123
122
|
),
|
|
124
|
-
# Verb bodies run inside `Grape::Endpoint` instances.
|
|
125
123
|
Rigor::Plugin::Macro::BlockAsMethod.new(
|
|
126
124
|
receiver_constraint: "Grape::API",
|
|
127
125
|
method_names: VERB_METHODS,
|
|
@@ -23,9 +23,8 @@ module Rigor
|
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
25
25
|
# The base class name a Schema::Object subclass MUST inherit from to be recognised. Match is on
|
|
26
|
-
# the
|
|
27
|
-
#
|
|
28
|
-
# the canonical path.
|
|
26
|
+
# the last two segments of the superclass constant chain (see {#schema_subclass?}), so a locally
|
|
27
|
+
# aliased base such as `BaseObject = GraphQL::Schema::Object` is not followed.
|
|
29
28
|
SCHEMA_OBJECT_TAIL = "Object"
|
|
30
29
|
SCHEMA_ENUM_TAIL = "Enum"
|
|
31
30
|
SCHEMA_INPUT_OBJECT_TAIL = "InputObject"
|
|
@@ -183,7 +182,7 @@ module Rigor
|
|
|
183
182
|
private_class_method :statement_nodes
|
|
184
183
|
|
|
185
184
|
# Walks every top-level `value "..."` call inside an enum subclass body and returns the value
|
|
186
|
-
# names
|
|
185
|
+
# names. Both shapes graphql-ruby accepts work:
|
|
187
186
|
#
|
|
188
187
|
# value "ACTIVE"
|
|
189
188
|
# value "DISABLED", value: :off, description: "..."
|
|
@@ -299,7 +298,7 @@ module Rigor
|
|
|
299
298
|
end
|
|
300
299
|
private_class_method :parse_field_call
|
|
301
300
|
|
|
302
|
-
# Resolves the `Type` positional argument to a `
|
|
301
|
+
# Resolves the `Type` positional argument to a `type:` class name and a `list:` flag.
|
|
303
302
|
# ArrayNode forms (`[String]` / `[Types::User]`) unwrap the single element and mark `list: true`.
|
|
304
303
|
# Bare constant refs are not lists. Returns nil for unrecognised shapes (string types `"User"`,
|
|
305
304
|
# Proc lazy types, etc.) so callers drop the field.
|
|
@@ -350,8 +349,8 @@ module Rigor
|
|
|
350
349
|
end
|
|
351
350
|
private_class_method :extract_nullability
|
|
352
351
|
|
|
353
|
-
# Returns the constant chain
|
|
354
|
-
#
|
|
352
|
+
# Returns the constant chain's segments, e.g. `["GraphQL", "Schema", "Object"]`. Empty for
|
|
353
|
+
# unrecognised node kinds.
|
|
355
354
|
def constant_path_segments(node)
|
|
356
355
|
case node
|
|
357
356
|
when Prism::ConstantReadNode then [node.name.to_s]
|
|
@@ -20,10 +20,8 @@ module Rigor
|
|
|
20
20
|
@contracts = contracts
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# Per-`ClassNode` check over the engine-owned walk (ADR-37): called once per class node the
|
|
24
|
-
# `node_rule` in `hanami.rb
|
|
25
|
-
# file. No cross-class-node state is needed, so the checker ships no `class_nodes` traversal of
|
|
26
|
-
# its own.
|
|
23
|
+
# Per-`ClassNode` check over the engine-owned walk (ADR-37): called once per class node by the
|
|
24
|
+
# `node_rule` in `hanami.rb`, against every contract whose `path_glob` matches the file.
|
|
27
25
|
def check_class(class_node, path:)
|
|
28
26
|
@contracts.filter_map do |contract|
|
|
29
27
|
next unless path_matches?(contract.path_glob, path)
|
|
@@ -87,11 +87,8 @@ module Rigor
|
|
|
87
87
|
@protocol_contracts || manifest.protocol_contracts
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# ADR-37 — per-class-node validation over the engine-owned walk
|
|
91
|
-
#
|
|
92
|
-
# traversal; `ActionChecker#check_class` keeps the per-class contract logic. (A per-class contract
|
|
93
|
-
# check is exactly what `node_rule` is for — the return type is void, so no `scope` query is
|
|
94
|
-
# needed.)
|
|
90
|
+
# ADR-37 — per-class-node validation over the engine-owned walk; `ActionChecker#check_class` holds the
|
|
91
|
+
# per-class contract logic. The return type is void, so no `scope` query is needed.
|
|
95
92
|
node_rule Prism::ClassNode do |node, _scope, path|
|
|
96
93
|
contracts = protocol_contracts
|
|
97
94
|
next [] if contracts.empty?
|
|
@@ -51,8 +51,8 @@ module Rigor
|
|
|
51
51
|
#
|
|
52
52
|
# - `is_a?(Result::Ok)` / `Some` / `None` exhaustive narrowing — core control-flow analysis over a sealed
|
|
53
53
|
# hierarchy, not a plugin surface.
|
|
54
|
-
# - The `variants do variant Const, Type end` Enum DSL is
|
|
55
|
-
# this plugin's manifest (Slice A
|
|
54
|
+
# - The `variants do variant Const, Type end` Enum DSL is not part of the unwrap contribution; it is
|
|
55
|
+
# synthesised through the ADR-36 `nested_class_templates:` in this plugin's manifest (Slice A).
|
|
56
56
|
class Mangrove < Rigor::Plugin::Base
|
|
57
57
|
manifest(
|
|
58
58
|
id: "mangrove",
|
|
@@ -17,8 +17,8 @@ module Rigor
|
|
|
17
17
|
#
|
|
18
18
|
# assert_kind_of(T, x) → narrow x to T
|
|
19
19
|
# assert_instance_of(T, x) → narrow x to T
|
|
20
|
-
# assert_nil(x) → narrow x to
|
|
21
|
-
# assert_equal(literal, x) → meet x with
|
|
20
|
+
# assert_nil(x) → narrow x to nil
|
|
21
|
+
# assert_equal(literal, x) → meet x with the literal (see "Equality is not identity")
|
|
22
22
|
# assert_match(regex, x) → narrow x to String
|
|
23
23
|
#
|
|
24
24
|
# ### (2) Minitest / Test::Unit `refute_*` / `assert_not_*` (negative) ###
|
|
@@ -26,7 +26,7 @@ module Rigor
|
|
|
26
26
|
# refute_kind_of(T, x) → narrow x AWAY from T
|
|
27
27
|
# refute_instance_of(T, x) → narrow x AWAY from T
|
|
28
28
|
# refute_nil(x) → narrow x AWAY from nil
|
|
29
|
-
# refute_equal(literal, x) → negative
|
|
29
|
+
# refute_equal(literal, x) → negative fact on the literal (the engine keeps x unchanged)
|
|
30
30
|
#
|
|
31
31
|
# Test::Unit's `assert_not_nil(x)` / `assert_not_equal(...)` / `assert_not_kind_of(...)` /
|
|
32
32
|
# `assert_not_instance_of(...)` share the recognizer with `refute_*` (they're aliases).
|
|
@@ -35,8 +35,8 @@ module Rigor
|
|
|
35
35
|
#
|
|
36
36
|
# _(x).must_be_kind_of(T) → narrow x to T
|
|
37
37
|
# _(x).must_be_instance_of(T) → narrow x to T
|
|
38
|
-
# _(x).must_be_nil → narrow x to
|
|
39
|
-
# _(x).must_equal(literal) → meet x with
|
|
38
|
+
# _(x).must_be_nil → narrow x to nil
|
|
39
|
+
# _(x).must_equal(literal) → meet x with the literal (see "Equality is not identity")
|
|
40
40
|
# _(x).must_match(regex) → narrow x to String
|
|
41
41
|
#
|
|
42
42
|
# The legacy bare `x.must_be_kind_of(T)` form (Minitest < 6.0 monkey-patched onto Object) is
|
|
@@ -48,7 +48,7 @@ module Rigor
|
|
|
48
48
|
#
|
|
49
49
|
# _(x).wont_be_kind_of(T) → narrow x AWAY from T
|
|
50
50
|
# _(x).wont_be_nil → narrow x AWAY from nil
|
|
51
|
-
# _(x).wont_equal(literal) → negative
|
|
51
|
+
# _(x).wont_equal(literal) → negative fact on the literal (the engine keeps x unchanged)
|
|
52
52
|
#
|
|
53
53
|
# ## Equality is not identity
|
|
54
54
|
#
|
|
@@ -10,7 +10,8 @@ module Rigor
|
|
|
10
10
|
module Plugin
|
|
11
11
|
class Pundit < Rigor::Plugin::Base
|
|
12
12
|
# Walks the configured policy-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
|
|
13
|
-
# Prism, and collects classes whose immediate superclass is one of the configured base classes
|
|
13
|
+
# Prism, and collects classes whose immediate superclass is one of the configured base classes and
|
|
14
|
+
# whose name ends in `Policy`.
|
|
14
15
|
#
|
|
15
16
|
# For each discovered policy class, the discoverer collects every instance-side `def name?` predicate
|
|
16
17
|
# method. Non-predicate methods (`initialize`, `resolve`, helper methods) are ignored — Pundit's
|
|
@@ -192,15 +192,10 @@ module Rigor
|
|
|
192
192
|
first.unescaped
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
# Pulls the interpolation hash from the call's arguments. The trailing
|
|
195
|
+
# Pulls the interpolation hash from the call's arguments. The trailing hash argument (or
|
|
196
196
|
# `Prism::KeywordHashNode`) carries both reserved I18n options (`default:`, `scope:`, …) and
|
|
197
|
-
# interpolation variables. Returns
|
|
198
|
-
#
|
|
199
|
-
# :has_default => bool,
|
|
200
|
-
# :all_keys => Set<Symbol> (every assoc key in the hash),
|
|
201
|
-
# :non_reserved => Set<Symbol> (keys NOT in RESERVED_OPTION_KEYS),
|
|
202
|
-
# :hash_node => Prism::Node (or nil)
|
|
203
|
-
# }
|
|
197
|
+
# interpolation variables. Returns `:has_default`, `:all_keys` (every assoc key), `:non_reserved`
|
|
198
|
+
# (keys outside RESERVED_OPTION_KEYS) and `:hash_node` (nil when there is no hash argument).
|
|
204
199
|
#
|
|
205
200
|
# Note: a reserved option key (e.g. `count:`) can also serve as an interpolation value when the
|
|
206
201
|
# locale's leaf string has `%{count}`. The analyzer therefore checks the missing-placeholder set
|
|
@@ -11,9 +11,8 @@ module Rigor
|
|
|
11
11
|
# placeholder names. The actual translated values are not retained — the analyzer doesn't need them
|
|
12
12
|
# and keeping them would bloat the cache slice.
|
|
13
13
|
class LocaleIndex
|
|
14
|
-
# `placeholders`
|
|
15
|
-
# locale
|
|
16
|
-
# `locale_name => Symbol` (`:string` / `:array` / `:hash`).
|
|
14
|
+
# `placeholders` maps each locale name to the `%{var}` placeholder names of its leaf. `value_kinds`
|
|
15
|
+
# maps each locale name to the leaf's kind (`:string` / `:array` / `:hash` / `:scalar`).
|
|
17
16
|
Entry = Data.define(:dotted_key, :placeholders, :value_kinds) do
|
|
18
17
|
def locales
|
|
19
18
|
placeholders.keys
|
|
@@ -36,8 +36,8 @@ module Rigor
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def load
|
|
39
|
-
per_key = {} # dotted_key => { locale =>
|
|
40
|
-
per_key_kinds = {} # dotted_key => { locale =>
|
|
39
|
+
per_key = {} # dotted_key => { locale => placeholder names }
|
|
40
|
+
per_key_kinds = {} # dotted_key => { locale => leaf kind }
|
|
41
41
|
locales = Set.new
|
|
42
42
|
|
|
43
43
|
locale_files.each do |path|
|
|
@@ -98,12 +98,9 @@ module Rigor
|
|
|
98
98
|
# leaves are *not* recorded as entries themselves — only their descendants — but every leaf scalar
|
|
99
99
|
# / array IS recorded.
|
|
100
100
|
#
|
|
101
|
-
# `breadcrumbs` is
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
# every node plus an intermediate result Array at every level — millions of short-lived objects and
|
|
105
|
-
# the run's top allocation site. The dotted key is still materialised once per leaf (it has to
|
|
106
|
-
# be); everything else is now allocation-free traversal.
|
|
101
|
+
# `breadcrumbs` is one mutable stack reused across the walk (push before recursing, pop after). A
|
|
102
|
+
# per-node Array allocation was the run's top allocation site on the 530-file Mastodon corpus; only
|
|
103
|
+
# the dotted key is materialised, once per leaf.
|
|
107
104
|
def each_flattened(node, breadcrumbs, &)
|
|
108
105
|
if node.is_a?(Hash)
|
|
109
106
|
node.each do |k, v|
|
|
@@ -124,7 +124,6 @@ module Rigor
|
|
|
124
124
|
if name
|
|
125
125
|
names << name
|
|
126
126
|
elsif call_node.name == :subject && call_node.arguments.nil?
|
|
127
|
-
# Bare `subject { ... }` defines `:subject` itself.
|
|
128
127
|
names << "subject"
|
|
129
128
|
end
|
|
130
129
|
end
|
|
@@ -201,8 +200,7 @@ module Rigor
|
|
|
201
200
|
end
|
|
202
201
|
|
|
203
202
|
def unknown_helper_violation(name, helper_table)
|
|
204
|
-
# ADR-39 / boilerplate 0c — the shared DidYouMean-backed suggester
|
|
205
|
-
# Levenshtein this module used to carry.
|
|
203
|
+
# ADR-39 / boilerplate 0c — the shared DidYouMean-backed suggester.
|
|
206
204
|
suggestion = Rigor::Plugin::Base.suggest(name, helper_table.names)
|
|
207
205
|
message = "no route helper `#{name}`"
|
|
208
206
|
message += " (did you mean `#{suggestion}`?)" if suggestion
|