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
|
@@ -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
|
#
|
|
@@ -120,16 +120,17 @@ module Rigor
|
|
|
120
120
|
|
|
121
121
|
# The `watch:` glob tuples covering the scan's entire input — one `[root, pattern]` per project `paths:`
|
|
122
122
|
# entry. A directory entry watches its whole `**/*.rb` subtree; a single-file `.rb` entry watches
|
|
123
|
-
# exactly that file (`[dir, basename]`
|
|
124
|
-
# `Plugin::Base#watch_glob_entries`, so a relative CLI path
|
|
125
|
-
# tree from the project root on the next run. Co-extensive
|
|
126
|
-
# would read is watched, so any edit / addition / removal
|
|
123
|
+
# exactly that file (`[dir, basename]`, with the basename escaped so `[`, `{`, `*` or `?` match literally).
|
|
124
|
+
# Roots are expanded to absolute paths by `Plugin::Base#watch_glob_entries`, so a relative CLI path
|
|
125
|
+
# (`app/models`) re-validates against the same tree from the project root on the next run. Co-extensive
|
|
126
|
+
# with {#scannable_paths}: every `.rb` the scan would read is watched, so any edit / addition / removal
|
|
127
|
+
# under those paths invalidates the cache.
|
|
127
128
|
def alias_watch_globs
|
|
128
129
|
services.configuration.paths.filter_map do |entry|
|
|
129
130
|
if io_boundary.directory?(entry)
|
|
130
131
|
[entry, "**/*.rb"]
|
|
131
132
|
elsif io_boundary.file?(entry) && entry.end_with?(".rb")
|
|
132
|
-
[File.dirname(entry), File.basename(entry)]
|
|
133
|
+
[File.dirname(entry), Rigor::RootedGlob.literal(File.basename(entry))]
|
|
133
134
|
end
|
|
134
135
|
end
|
|
135
136
|
end
|
|
@@ -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
|
|
@@ -16,7 +16,7 @@ module Rigor
|
|
|
16
16
|
#
|
|
17
17
|
# Scope:
|
|
18
18
|
#
|
|
19
|
-
# - Recognises the standard
|
|
19
|
+
# - Recognises the standard controllers (`:sessions`, `:passwords`, `:confirmations`, `:unlocks`,
|
|
20
20
|
# `:registrations`, `:omniauth_callbacks`).
|
|
21
21
|
# - Honours `skip: [...]` to suppress controllers the project disables.
|
|
22
22
|
# - Honours `path: "..."` to remap the URL path (does NOT remap the helper-name prefix — Devise uses
|
|
@@ -162,8 +162,7 @@ module Rigor
|
|
|
162
162
|
OMNIAUTH_SUFFIXES
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
# ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available)
|
|
166
|
-
# replacing the tiny AS-replica this module used to duplicate from `RoutesParser`.
|
|
165
|
+
# ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available).
|
|
167
166
|
def singularize(word)
|
|
168
167
|
Rigor::Plugin::Inflector.singularize(word)
|
|
169
168
|
end
|
|
@@ -7,7 +7,7 @@ require "prism"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class RailsRoutes < Rigor::Plugin::Base
|
|
10
|
-
# Walks
|
|
10
|
+
# Walks the configured `helper_paths:` directories (default `app/`) and extracts every public
|
|
11
11
|
# module-level method name so the analyzer's `unknown-helper` rule does not false-fire on calls to
|
|
12
12
|
# project-defined `*_path` / `*_url` helpers (the canonical Rails pattern: a `UrlHelper` module
|
|
13
13
|
# exposing `full_asset_url`, `host_to_url`, `frontend_asset_url`, and so on).
|
|
@@ -409,14 +409,9 @@ module Rigor
|
|
|
409
409
|
end
|
|
410
410
|
end
|
|
411
411
|
|
|
412
|
-
# ADR-39: inflection delegates to the shared `Rigor::Plugin::Inflector`, which
|
|
413
|
-
# `ActiveSupport::Inflector` (the authority Rails
|
|
414
|
-
#
|
|
415
|
-
# AS-replica these methods used to carry — every special case they accreted (`news` uncountable,
|
|
416
|
-
# `media`→`medium`, `databases`→`database`, `custom_css` ss-preservation,
|
|
417
|
-
# `async_refresh`→`async_refreshes`) was reverse-engineered `ActiveSupport::Inflector` behaviour,
|
|
418
|
-
# so the real inflector produces the same result for those and the correct one for the words the
|
|
419
|
-
# replica never covered.
|
|
412
|
+
# ADR-39: inflection delegates to the shared `Rigor::Plugin::Inflector`, which uses the real
|
|
413
|
+
# `ActiveSupport::Inflector` (the authority Rails uses to generate helper names) when available,
|
|
414
|
+
# and a built-in approximation otherwise.
|
|
420
415
|
def singularize(word)
|
|
421
416
|
Rigor::Plugin::Inflector.singularize(word)
|
|
422
417
|
end
|
|
@@ -1286,7 +1281,7 @@ module Rigor
|
|
|
1286
1281
|
return nil unless plural
|
|
1287
1282
|
|
|
1288
1283
|
# Rails appends `_index_path` to the index helper name when the singular form of the resource
|
|
1289
|
-
# matches the plural form
|
|
1284
|
+
# matches the plural form (uncountable nouns included). The collision
|
|
1290
1285
|
# would otherwise put both the index (`:id`-less) and show (`:id`-bearing) helpers under the same
|
|
1291
1286
|
# name, and Rails disambiguates by suffixing the index form. Mastodon's `resources
|
|
1292
1287
|
# :reblogged_by, controller: :reblogged_by_accounts, only: :index` and similar rely on this —
|
|
@@ -1432,8 +1427,6 @@ module Rigor
|
|
|
1432
1427
|
end
|
|
1433
1428
|
|
|
1434
1429
|
# ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available).
|
|
1435
|
-
# Previously a hand-tuned AS-replica kept in sync with `Context#singularize`; both now share the
|
|
1436
|
-
# one authoritative implementation.
|
|
1437
1430
|
def singularize_word(word)
|
|
1438
1431
|
Rigor::Plugin::Inflector.singularize(word)
|
|
1439
1432
|
end
|
|
@@ -14,7 +14,7 @@ module Rigor
|
|
|
14
14
|
# rigor-rails-routes — validates Rails route-helper calls (`users_path`, `edit_user_path(@user)`, …)
|
|
15
15
|
# against the project's `config/routes.rb`.
|
|
16
16
|
#
|
|
17
|
-
# Tier 1A of the [Rails plugins roadmap](
|
|
17
|
+
# Tier 1A of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
18
18
|
# Statically interprets the routes DSL via Prism — no `rails` runtime dependency. Recognised v0.1.0
|
|
19
19
|
# surface:
|
|
20
20
|
#
|
|
@@ -35,7 +35,8 @@ module Rigor
|
|
|
35
35
|
class RbsInline < Rigor::Plugin::Base
|
|
36
36
|
# Synthesizer callable invoked once per project Ruby source file by `Environment.for_project` at
|
|
37
37
|
# env-build time. Returns the synthesised RBS source as a String, or `nil` when the file contributes
|
|
38
|
-
# nothing (no
|
|
38
|
+
# nothing (no annotation in the default mode, no magic comment under `require_magic_comment: true`,
|
|
39
|
+
# or a parse error per WD6).
|
|
39
40
|
class Synthesizer
|
|
40
41
|
# An RDoc directive comment: `#:` followed by a bare word (RDoc allows a hyphen, as in `:call-seq:`)
|
|
41
42
|
# and a closing colon. RDoc predates rbs-inline by about two decades and `#:<name>:` is
|