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
|
@@ -19,9 +19,9 @@ module Rigor
|
|
|
19
19
|
# - Reads the instance-side `def` nodes and records each one as an action method, capturing the
|
|
20
20
|
# arity envelope.
|
|
21
21
|
# - For each (class, action) pair, attempts to read every candidate view template under
|
|
22
|
-
# `app/views/<mailer_underscore>/<action>.{html,text}.erb`. Existing templates feed the
|
|
22
|
+
# `app/views/<mailer_underscore>/<action>.{html,text}.{erb,haml,slim}`. Existing templates feed the
|
|
23
23
|
# IoBoundary's cache descriptor (so the cache invalidates when the template changes); missing
|
|
24
|
-
# templates are recorded so the plugin can surface a diagnostic on the
|
|
24
|
+
# templates are recorded so the plugin can surface a diagnostic on the action's `def`.
|
|
25
25
|
#
|
|
26
26
|
# Limitations (intentional for v0.1.0):
|
|
27
27
|
#
|
|
@@ -30,9 +30,6 @@ module Rigor
|
|
|
30
30
|
# - Action methods are read from the syntactic instance-side `def` list. Methods built via
|
|
31
31
|
# `define_method`, `private`, or non-action helpers (e.g. methods starting with `_`) are out of
|
|
32
32
|
# scope. The discoverer filters obvious non-actions (`initialize`, names prefixed with `_`).
|
|
33
|
-
# - Adding a brand-new view file under `app/views/<mailer>/` will NOT invalidate the cached index
|
|
34
|
-
# until something the mailer file touches changes. This is the standard read-tracking trade-off
|
|
35
|
-
# — only files we successfully read get digested into the descriptor.
|
|
36
33
|
class MailerDiscoverer
|
|
37
34
|
DEFAULT_VIEWS_ROOT = "app/views"
|
|
38
35
|
VIEW_FORMATS = %w[html text].freeze
|
|
@@ -56,22 +53,22 @@ module Rigor
|
|
|
56
53
|
# Two-pass: first collect every module's defs (for the include-following step), then build
|
|
57
54
|
# per-class entries that pull in actions from include'd modules. GitLab's `Notify` mailer
|
|
58
55
|
# derives every action from `Emails::*` concerns under `app/mailers/emails/`.
|
|
59
|
-
module_actions = {} #
|
|
60
|
-
class_visits = [] # collected (class_name, path, def_nodes, includes)
|
|
56
|
+
module_actions = {} # fully-qualified module name => its action entries, keyed by method name
|
|
57
|
+
class_visits = [] # collected (class_name, path, def_nodes, includes, view_exempt)
|
|
61
58
|
|
|
62
59
|
ruby_files_under(@search_paths).each do |path|
|
|
63
60
|
contents = read_safely(path)
|
|
64
61
|
next if contents.nil?
|
|
65
62
|
|
|
66
63
|
tree = Prism.parse(contents).value
|
|
67
|
-
walk_for_mailers(tree, []) do |class_name, def_nodes, includes|
|
|
68
|
-
class_visits << [class_name, path, def_nodes, includes]
|
|
64
|
+
walk_for_mailers(tree, []) do |class_name, def_nodes, includes, view_exempt|
|
|
65
|
+
class_visits << [class_name, path, def_nodes, includes, view_exempt]
|
|
69
66
|
end
|
|
70
67
|
collect_module_actions(tree, [], module_actions)
|
|
71
68
|
end
|
|
72
69
|
|
|
73
|
-
entries = class_visits.map do |class_name, path, def_nodes, includes|
|
|
74
|
-
build_class_entry(class_name, path, def_nodes, includes, module_actions)
|
|
70
|
+
entries = class_visits.map do |class_name, path, def_nodes, includes, view_exempt|
|
|
71
|
+
build_class_entry(class_name, path, def_nodes, includes, module_actions, view_exempt)
|
|
75
72
|
end
|
|
76
73
|
MailerIndex.new(entries)
|
|
77
74
|
end
|
|
@@ -112,9 +109,9 @@ module Rigor
|
|
|
112
109
|
full_name = declared_constant_name(class_local_name, lexical_path)
|
|
113
110
|
superclass = strip_root(constant_path_name(node.superclass)) if node.superclass
|
|
114
111
|
if superclass && @base_classes.include?(superclass)
|
|
115
|
-
def_nodes = collect_action_defs(node.body)
|
|
112
|
+
def_nodes, view_exempt = collect_action_defs(node.body)
|
|
116
113
|
includes = collect_includes(node.body)
|
|
117
|
-
yield full_name, def_nodes, includes
|
|
114
|
+
yield full_name, def_nodes, includes, view_exempt
|
|
118
115
|
end
|
|
119
116
|
|
|
120
117
|
walk_for_mailers(node.body, [full_name], &) if node.body
|
|
@@ -139,8 +136,7 @@ module Rigor
|
|
|
139
136
|
end
|
|
140
137
|
|
|
141
138
|
# Walks the AST collecting every module's instance-side def nodes by fully-qualified module
|
|
142
|
-
# name. The same `collect_action_defs` filter applies (private / `_`-prefixed
|
|
143
|
-
# methods skipped).
|
|
139
|
+
# name. The same `collect_action_defs` filter applies (private / `_`-prefixed skipped).
|
|
144
140
|
def collect_module_actions(node, lexical_path, accumulator)
|
|
145
141
|
return if node.nil?
|
|
146
142
|
|
|
@@ -151,8 +147,7 @@ module Rigor
|
|
|
151
147
|
|
|
152
148
|
full_name = declared_constant_name(local_name, lexical_path)
|
|
153
149
|
if node.body
|
|
154
|
-
|
|
155
|
-
entries = def_nodes.to_h { |def_node| [def_node.name, build_action_entry(def_node)] }
|
|
150
|
+
entries = collect_action_defs(node.body).first.to_h { |def_node| [def_node.name, build_action_entry(def_node)] }
|
|
156
151
|
# UNIONed, never assigned: a concern module reopened in a second file adds actions rather
|
|
157
152
|
# than replacing the ones the first declaration spelled (same rule as {MailerIndex}'s
|
|
158
153
|
# per-class merge).
|
|
@@ -216,80 +211,102 @@ module Rigor
|
|
|
216
211
|
# - `initialize`
|
|
217
212
|
# - methods starting with `_` (Ruby convention for private/internal)
|
|
218
213
|
# - `def self.<name>` (singleton-side)
|
|
219
|
-
# - methods
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
214
|
+
# - methods that are not public at the end of the class body, read in source order: after a bare
|
|
215
|
+
# `private` / `protected` (until a `public` switches back), named by `private :foo` /
|
|
216
|
+
# `protected :foo`, or defined inline (`private def foo`) — Rails builds `action_methods` from the
|
|
217
|
+
# public instance methods. `public :foo` after a `protected` section makes `foo` an action again.
|
|
218
|
+
# Methods named by a callback or `helper_method` macro (see {#collect_callback_names}) stay actions:
|
|
219
|
+
# Rails dispatches any public instance method, so `Mailer.target` still runs. They are returned as the
|
|
220
|
+
# view-exempt names, which exempt them from the missing-view check only.
|
|
223
221
|
#
|
|
224
222
|
# Pre-fix, Mastodon's `AdminMailer#process_params` / `set_instance` / `set_locale` /
|
|
225
223
|
# `set_important_headers!` all surfaced as missing-view because the bare `private` keyword
|
|
226
224
|
# wasn't honoured. ~19 false positives across Mastodon's mailers.
|
|
227
|
-
|
|
228
|
-
private_constant :
|
|
225
|
+
VISIBILITY_KEYWORDS = %i[public private protected module_function].freeze
|
|
226
|
+
private_constant :VISIBILITY_KEYWORDS
|
|
229
227
|
|
|
228
|
+
# Returns `[action_def_nodes, view_exempt_names]`. The view-exempt names are the callback and
|
|
229
|
+
# `helper_method` targets of the body: they remain actions, and only the missing-view check skips them.
|
|
230
230
|
def collect_action_defs(body)
|
|
231
|
-
return [] if body.nil?
|
|
231
|
+
return [[], Set[]] if body.nil?
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
callback_names = collect_callback_names(body)
|
|
234
|
+
def_nodes, final_visibility = scan_visibility(body)
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
next
|
|
239
|
-
next
|
|
240
|
-
next [] if node.name == :initialize
|
|
241
|
-
next [] if node.name.to_s.start_with?("_")
|
|
242
|
-
next [] if visibility == :private
|
|
243
|
-
next [] if private_names.include?(node.name)
|
|
244
|
-
next [] if callback_names.include?(node.name)
|
|
236
|
+
actions = def_nodes.select do |node|
|
|
237
|
+
next false if node.receiver.is_a?(Prism::SelfNode)
|
|
238
|
+
next false if node.name == :initialize
|
|
239
|
+
next false if node.name.to_s.start_with?("_")
|
|
245
240
|
|
|
246
|
-
|
|
241
|
+
# Rails builds `action_methods` from the PUBLIC instance methods, so only a method whose final
|
|
242
|
+
# visibility is public is an action.
|
|
243
|
+
final_visibility[node.name] == :public
|
|
247
244
|
end
|
|
245
|
+
[actions, callback_names]
|
|
248
246
|
end
|
|
249
247
|
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
def
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
248
|
+
# Symbol arguments to the macros the manifest's `per_invocation_non_action_macros` lists (`before_action
|
|
249
|
+
# :setup`, `after_deliver :record`, `helper_method :fmt`). The `skip_*` forms are left out, since
|
|
250
|
+
# `skip_before_action :x` names a callback a parent defined and does not exempt `x` from anything (#2319).
|
|
251
|
+
def collect_callback_names(body)
|
|
252
|
+
macros = Actionmailer.manifest.per_invocation_non_action_macros.reject { |macro| macro.start_with?("skip_") }
|
|
253
|
+
names = []
|
|
257
254
|
body.rigor_each_child do |node|
|
|
258
|
-
next unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
255
|
+
next unless node.is_a?(Prism::CallNode) && node.receiver.nil? && macros.include?(node.name)
|
|
259
256
|
|
|
260
|
-
|
|
261
|
-
Rigor::Source::Literals.symbol(arg)
|
|
257
|
+
(node.arguments&.arguments || []).each do |arg|
|
|
258
|
+
symbol = Rigor::Source::Literals.symbol(arg)
|
|
259
|
+
names << symbol if symbol
|
|
262
260
|
end
|
|
263
|
-
|
|
261
|
+
end
|
|
262
|
+
names.to_set
|
|
263
|
+
end
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
# Walks the class body in source order and returns `[def_nodes, final_visibility]`: every `def` the body
|
|
266
|
+
# spells (a direct statement or the inline argument of a visibility call, `private def x`), and the
|
|
267
|
+
# visibility each method name ends with. A bare `public` / `private` / `protected` / `module_function` sets
|
|
268
|
+
# the default for the defs after it; `private :x` / `public :x` / `protected def x` set that one method, whichever side of
|
|
269
|
+
# its `def` they fall on in the source, and the last statement to name a method wins.
|
|
270
|
+
def scan_visibility(body)
|
|
271
|
+
def_nodes = []
|
|
272
|
+
final_visibility = {}
|
|
273
|
+
default = :public
|
|
274
|
+
|
|
275
|
+
body.compact_child_nodes.each do |node|
|
|
276
|
+
case node
|
|
277
|
+
when Prism::DefNode
|
|
278
|
+
def_nodes << node
|
|
279
|
+
final_visibility[node.name] = default
|
|
280
|
+
when Prism::CallNode
|
|
281
|
+
default = apply_visibility_call(node, default, def_nodes, final_visibility)
|
|
268
282
|
end
|
|
269
283
|
end
|
|
270
284
|
|
|
271
|
-
[
|
|
285
|
+
[def_nodes, final_visibility]
|
|
272
286
|
end
|
|
273
287
|
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
# Applies one receiverless `public` / `private` / `protected` / `module_function` call and returns the
|
|
289
|
+
# (possibly new) default.
|
|
290
|
+
def apply_visibility_call(node, default, def_nodes, final_visibility)
|
|
291
|
+
return default unless node.receiver.nil? && VISIBILITY_KEYWORDS.include?(node.name)
|
|
292
|
+
|
|
293
|
+
args = node.arguments&.arguments || []
|
|
294
|
+
return node.name if args.empty?
|
|
295
|
+
|
|
296
|
+
args.flat_map { |arg| arg.is_a?(Prism::ArrayNode) ? arg.elements : [arg] }.each do |arg|
|
|
297
|
+
if arg.is_a?(Prism::DefNode)
|
|
298
|
+
def_nodes << arg
|
|
299
|
+
final_visibility[arg.name] = node.name
|
|
300
|
+
elsif (name = Rigor::Source::Literals.symbol_or_string(arg))
|
|
301
|
+
final_visibility[name] = node.name
|
|
302
|
+
end
|
|
287
303
|
end
|
|
304
|
+
default
|
|
288
305
|
end
|
|
289
306
|
|
|
290
|
-
def build_class_entry(class_name, file_path, def_nodes, includes = [], module_actions = {})
|
|
307
|
+
def build_class_entry(class_name, file_path, def_nodes, includes = [], module_actions = {}, view_exempt = Set[])
|
|
291
308
|
actions = def_nodes.to_h do |def_node|
|
|
292
|
-
entry = build_action_entry(def_node)
|
|
309
|
+
entry = build_action_entry(def_node, view_exempt: view_exempt.include?(def_node.name))
|
|
293
310
|
[entry.method_name, entry]
|
|
294
311
|
end
|
|
295
312
|
|
|
@@ -309,7 +326,7 @@ module Rigor
|
|
|
309
326
|
end
|
|
310
327
|
end
|
|
311
328
|
|
|
312
|
-
missing_views = actions.keys.reject { |action| view_exists?(class_name, action) }
|
|
329
|
+
missing_views = actions.reject { |_, entry| entry.view_exempt }.keys.reject { |action| view_exists?(class_name, action) }
|
|
313
330
|
|
|
314
331
|
MailerIndex::ClassEntry.new(
|
|
315
332
|
class_name: class_name,
|
|
@@ -320,7 +337,7 @@ module Rigor
|
|
|
320
337
|
)
|
|
321
338
|
end
|
|
322
339
|
|
|
323
|
-
def build_action_entry(def_node)
|
|
340
|
+
def build_action_entry(def_node, view_exempt: false)
|
|
324
341
|
parameters = def_node.parameters
|
|
325
342
|
location = def_node.name_loc
|
|
326
343
|
|
|
@@ -329,7 +346,8 @@ module Rigor
|
|
|
329
346
|
method_name: def_node.name,
|
|
330
347
|
min_arity: 0, max_arity: 0,
|
|
331
348
|
def_line: location.start_line,
|
|
332
|
-
def_column: location.start_column + 1
|
|
349
|
+
def_column: location.start_column + 1,
|
|
350
|
+
view_exempt: view_exempt
|
|
333
351
|
)
|
|
334
352
|
end
|
|
335
353
|
|
|
@@ -342,7 +360,8 @@ module Rigor
|
|
|
342
360
|
min_arity: required_count,
|
|
343
361
|
max_arity: rest_present ? Float::INFINITY : required_count + optional_count,
|
|
344
362
|
def_line: location.start_line,
|
|
345
|
-
def_column: location.start_column + 1
|
|
363
|
+
def_column: location.start_column + 1,
|
|
364
|
+
view_exempt: view_exempt
|
|
346
365
|
)
|
|
347
366
|
end
|
|
348
367
|
|
|
@@ -11,7 +11,9 @@ module Rigor
|
|
|
11
11
|
# the mailer file)
|
|
12
12
|
# - the list of `(action, location)` pairs whose view templates are missing from `app/views/`
|
|
13
13
|
class MailerIndex
|
|
14
|
-
|
|
14
|
+
# `view_exempt` marks a callback or `helper_method` target: still an action (Rails dispatches it), but
|
|
15
|
+
# left out of the missing-view check.
|
|
16
|
+
ActionEntry = Data.define(:method_name, :min_arity, :max_arity, :def_line, :def_column, :view_exempt) do
|
|
15
17
|
def arity_label
|
|
16
18
|
return "#{min_arity}+" if max_arity == Float::INFINITY
|
|
17
19
|
return min_arity.to_s if min_arity == max_arity
|
|
@@ -67,7 +69,7 @@ module Rigor
|
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
# @param file_path — absolute path of a mailer file (canonicalised — see plugin entry's
|
|
70
|
-
# `
|
|
72
|
+
# `canonical_path`)
|
|
71
73
|
def find_by_file(file_path)
|
|
72
74
|
@entries.find { |entry| entry.file_path == file_path }
|
|
73
75
|
end
|
|
@@ -11,7 +11,7 @@ module Rigor
|
|
|
11
11
|
module Plugin
|
|
12
12
|
# rigor-actionmailer — validates `Mailer.action(args)` call sites and detects missing view templates.
|
|
13
13
|
#
|
|
14
|
-
# Tier 1C of the [Rails plugins roadmap](
|
|
14
|
+
# Tier 1C of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
15
15
|
# Statically discovers mailer classes by walking `mailer_search_paths` and parsing each file with
|
|
16
16
|
# Prism — no `action_mailer` runtime dependency.
|
|
17
17
|
#
|
|
@@ -38,8 +38,6 @@ module Rigor
|
|
|
38
38
|
# - Direct-superclass match only.
|
|
39
39
|
# - Action methods are read from the syntactic instance-side `def` list. `define_method` actions are
|
|
40
40
|
# out of scope.
|
|
41
|
-
# - Adding a brand-new view file does not invalidate the cache until something the mailer file
|
|
42
|
-
# touches changes.
|
|
43
41
|
class Actionmailer < Rigor::Plugin::Base
|
|
44
42
|
manifest(
|
|
45
43
|
id: "actionmailer",
|
|
@@ -52,13 +50,36 @@ module Rigor
|
|
|
52
50
|
# Bumped 2026-09-02 (#621) — mailer keys are de-rooted at the producer and a reopened class's
|
|
53
51
|
# actions are UNIONed rather than clobbered, so a cached 0.4.0 index can be missing actions this
|
|
54
52
|
# version knows about.
|
|
55
|
-
|
|
53
|
+
#
|
|
54
|
+
# Bumped 2026-10-23 (#2308) — a method after a bare `protected` is no longer an action, so a cached 0.5.0
|
|
55
|
+
# index can list protected helpers as actions (and as missing-view candidates).
|
|
56
|
+
#
|
|
57
|
+
# Bumped 2026-10-26 (#2319) — a method named by a callback or `helper_method` macro (`prepend_before_action`,
|
|
58
|
+
# `after_deliver`, `helper_method`, ...) no longer draws a missing-view row, and a method defined after a bare
|
|
59
|
+
# `module_function` is no longer an action, so a cached 0.5.1 index can carry those rows and actions.
|
|
60
|
+
version: "0.5.2",
|
|
56
61
|
description: "Validates ActionMailer call shape and view template existence.",
|
|
57
62
|
config_schema: {
|
|
58
63
|
"mailer_search_paths" => { kind: :array, default: ["app/mailers"] },
|
|
59
64
|
"mailer_base_classes" => { kind: :array, default: %w[ApplicationMailer ActionMailer::Base] },
|
|
60
65
|
"views_root" => { kind: :string, default: "app/views" }
|
|
61
66
|
},
|
|
67
|
+
# ADR-125 (#575, #2308) — a mailer instance serves exactly one action (`Mailer.welcome(...)` builds a
|
|
68
|
+
# fresh instance and runs that one method), so ivar writes in two different public actions are never live
|
|
69
|
+
# together and `def.ivar-write-mismatch` stops comparing them. `ActionMailer::Base` has no signature here;
|
|
70
|
+
# the rule matches an undeclared superclass by its written name.
|
|
71
|
+
per_invocation_classes: ["ActionMailer::Base"],
|
|
72
|
+
# A public method these macros name is a callback or a view helper, not an action: it runs inside
|
|
73
|
+
# whichever action triggers it (AbstractController::Callbacks / Helpers), so its writes stay compared with
|
|
74
|
+
# every action's. The actionpack list, plus the `*_deliver` callbacks (ActionMailer::Callbacks), which run
|
|
75
|
+
# on the action's own instance.
|
|
76
|
+
per_invocation_non_action_macros: %i[
|
|
77
|
+
before_action prepend_before_action append_before_action skip_before_action
|
|
78
|
+
after_action prepend_after_action append_after_action skip_after_action
|
|
79
|
+
around_action prepend_around_action append_around_action skip_around_action
|
|
80
|
+
before_deliver after_deliver around_deliver
|
|
81
|
+
helper_method
|
|
82
|
+
],
|
|
62
83
|
# ADR-103 WD10 (#387) — see {Effects} for what each row is and why.
|
|
63
84
|
effect_root: "rails",
|
|
64
85
|
effect_labels: ["rails.actionmailer.deliver"],
|
|
@@ -102,7 +102,7 @@ module Rigor
|
|
|
102
102
|
|
|
103
103
|
# Phase 2 — filter-chain validation for ONE filter-DSL call (`before_action :name`, …). The
|
|
104
104
|
# enclosing controller is read from the node-rule `NodeContext` ancestors, looked up in the
|
|
105
|
-
# controller index to get the effective method set (including
|
|
105
|
+
# controller index to get the effective method set (including inherited methods), and
|
|
106
106
|
# each filter name reference is validated against it. Calls outside a known controller
|
|
107
107
|
# contribute nothing.
|
|
108
108
|
#
|
|
@@ -6,8 +6,7 @@ module Rigor
|
|
|
6
6
|
# Per-run frozen index of discovered controllers AND concerns (modules) in
|
|
7
7
|
# `controller_search_paths`. Phase 2 (filter-chain validation) consults the index at every call
|
|
8
8
|
# site to check that `before_action :name` references a method that actually exists on the
|
|
9
|
-
# controller,
|
|
10
|
-
# chain transitively `include`s.
|
|
9
|
+
# controller, any ancestor, or any module the controller or an ancestor transitively `include`s.
|
|
11
10
|
#
|
|
12
11
|
# Real-world example (Mastodon):
|
|
13
12
|
# AccountsController < ApplicationController
|
|
@@ -169,9 +168,6 @@ module Rigor
|
|
|
169
168
|
# and returning the first candidate that has an entry in the index. When nothing resolves,
|
|
170
169
|
# return the original name unchanged — the caller treats unresolved entries as "gem-shipped
|
|
171
170
|
# concerns we cannot see" (the `unresolved_include?` predicate).
|
|
172
|
-
#
|
|
173
|
-
# `name` may already be qualified (`Foo::Bar`); we only try lexical prefixing when the
|
|
174
|
-
# unqualified first segment doesn't match a top-level entry.
|
|
175
171
|
def resolve_constant_lexically(name, enclosing)
|
|
176
172
|
return nil if name.nil?
|
|
177
173
|
|
|
@@ -229,7 +229,8 @@ module Rigor
|
|
|
229
229
|
# been measured. Resolved ONCE per process: a failed resolution is as memoised as a successful
|
|
230
230
|
# one, so a project without Erubi does not pay a `$LOAD_PATH` walk per template.
|
|
231
231
|
#
|
|
232
|
-
# `Isolation.require_with_target_bundle` is
|
|
232
|
+
# `Isolation.require_with_target_bundle` is
|
|
233
|
+
# [ADR-90](../../../../../../docs/adr/90-target-library-resolution-from-project-bundle.md)'s
|
|
233
234
|
# path — Rigor's own gem env first, then the analysed project's bundle, `$LOAD_PATH` appended and
|
|
234
235
|
# only on a failed require. Erubi is not a Rigor dependency and is never added to one. Any load
|
|
235
236
|
# failure is equally "no Erubi here"; stdlib `ERB` answers the same question, so nothing is
|
|
@@ -18,7 +18,7 @@ module Rigor
|
|
|
18
18
|
# ## The inference is deliberately narrow
|
|
19
19
|
#
|
|
20
20
|
# Only `@user = Model.find(...)`-shaped assignments contribute, and only for a closed set of
|
|
21
|
-
# constructors and finders that **cannot return nil** — `find`, `find_by!`, `sole`, `first!`,
|
|
21
|
+
# constructors and finders that **cannot return nil** — `find`, `find_by!`, `find_sole_by`, `sole`, `first!`,
|
|
22
22
|
# `last!`, `new`, `create`, `create!`. A seeded type is a claim the engine acts on, and a wrong one
|
|
23
23
|
# is worse than none: seeding `@user` from `find_by` (which returns nil at runtime) would hand the
|
|
24
24
|
# flow rules a non-nil nominal and license folds that draw diagnostics on correct templates — the
|
|
@@ -20,7 +20,7 @@ module Rigor
|
|
|
20
20
|
# fact published by `rigor-rails-routes` (ADR-9 cross-plugin API) and validates every implicit-self
|
|
21
21
|
# `*_path` / `*_url` call inside files under `controller_search_paths` (default `app/controllers`).
|
|
22
22
|
#
|
|
23
|
-
# Tier 2 of the [Rails plugins roadmap](
|
|
23
|
+
# Tier 2 of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
24
24
|
# Phase 1 (strong-parameters → AR column validation), Phase 2 (filter chains), and Phase 3 (render
|
|
25
25
|
# targets) ship as separate slices; each phase composes additively under the same plugin id.
|
|
26
26
|
#
|
|
@@ -37,14 +37,10 @@ module Rigor
|
|
|
37
37
|
#
|
|
38
38
|
# ## What it checks
|
|
39
39
|
#
|
|
40
|
-
# - **
|
|
41
|
-
# the
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# recorded arity (a trailing `KeywordHashNode` like `users_path(format: :json)` is excluded; same
|
|
45
|
-
# convention `rigor-rails-routes` uses). Mismatches emit `wrong-helper-arity`.
|
|
46
|
-
# - **Trace** — recognised helpers also emit a `helper-call` info diagnostic naming the action and
|
|
47
|
-
# path, mirroring the trace shape of the upstream plugin.
|
|
40
|
+
# - **Trace** — recognised `*_path` / `*_url` helpers emit a `helper-call` info diagnostic naming
|
|
41
|
+
# the action and path. Unknown helpers (`unknown-helper`) and arity mismatches (`wrong-arity`)
|
|
42
|
+
# are reported by `rigor-rails-routes`, which owns the helper table, so this plugin does not
|
|
43
|
+
# repeat them.
|
|
48
44
|
#
|
|
49
45
|
# ## Limitations
|
|
50
46
|
#
|
|
@@ -88,7 +84,9 @@ module Rigor
|
|
|
88
84
|
# the engine reads as "a template exists here and has no unit". The bump is belt-and-braces: what
|
|
89
85
|
# actually invalidates is the claim itself, through the glob row in `TemplateUnits#digest` and the
|
|
90
86
|
# `claimed_globs` equality the carry checks. A manifest version is in no cache key of its own.
|
|
91
|
-
|
|
87
|
+
# Bumped 2026-10-27 (#2430) — `params[...]` answers the gradual `Dynamic[Parameters | nil]` rather than
|
|
88
|
+
# the bare union, so a cached 1.5.0 run carries the non-gradual answer at every `params[:k]` argument.
|
|
89
|
+
version: "1.6.0",
|
|
92
90
|
description: "Validates Action Pack route-helper calls and filter chains inside controllers, and types the request-context readers (`params` / `session` / `request` / `flash`) and their chains.",
|
|
93
91
|
config_schema: {
|
|
94
92
|
"controller_search_paths" => { kind: :array, default: ["app/controllers"] },
|
|
@@ -146,6 +144,19 @@ module Rigor
|
|
|
146
144
|
# `sig/action_view.rbs` so the name resolves, open so the helper surface stays lenient.
|
|
147
145
|
"ActionView::Base"
|
|
148
146
|
],
|
|
147
|
+
# ADR-125 (#575) — Rails instantiates a fresh controller per request and runs exactly one action on
|
|
148
|
+
# it, so ivar writes in two different public actions are never live together and
|
|
149
|
+
# `def.ivar-write-mismatch` stops comparing them. Neither root has a signature (see `sig/action_controller.rbs`
|
|
150
|
+
# for why); the rule matches an undeclared superclass by its written name.
|
|
151
|
+
per_invocation_classes: ["ActionController::Base", "ActionController::API"],
|
|
152
|
+
# A public method these macros name is a callback or a view helper, not an action: it runs inside
|
|
153
|
+
# whichever action triggers it, so its writes stay compared with every action's (#575's A1 decision).
|
|
154
|
+
per_invocation_non_action_macros: %i[
|
|
155
|
+
before_action prepend_before_action append_before_action skip_before_action
|
|
156
|
+
after_action prepend_after_action append_after_action skip_after_action
|
|
157
|
+
around_action prepend_around_action append_around_action skip_around_action
|
|
158
|
+
helper_method
|
|
159
|
+
],
|
|
149
160
|
# ADR-103 WD10 / WD14 (#387) — see {Effects} for what each row is and why.
|
|
150
161
|
effect_root: "rails",
|
|
151
162
|
effect_labels: %w[
|
|
@@ -462,7 +473,7 @@ module Rigor
|
|
|
462
473
|
# `ActionController::Parameters` (above), but the chained `params.require(:user).permit(:name)` /
|
|
463
474
|
# `params.permit(...)` calls returned `Dynamic` at the first hop (Parameters ships no bundled RBS,
|
|
464
475
|
# so a call on it resolves lenient-to-Dynamic), leaking every downstream site (`.permit`, `.to_h`,
|
|
465
|
-
# `.each`) to unprotected. These
|
|
476
|
+
# `.each`) to unprotected. These methods return another `Parameters`, so gate on a
|
|
466
477
|
# `Parameters` receiver and re-type the result as the same lenient nominal — the chain stays a
|
|
467
478
|
# concrete receiver end-to-end and `coverage --protection` counts the sites as protected.
|
|
468
479
|
#
|
|
@@ -484,9 +495,9 @@ module Rigor
|
|
|
484
495
|
# entry FP-safe, because the RBS-less nominal buys leniency on the *method surface* only:
|
|
485
496
|
#
|
|
486
497
|
# - Method-surface rules DO decline on it. `call.undefined-method` bails at
|
|
487
|
-
# `Rigor::Reflection.rbs_class_known?` (lib/rigor/analysis/check_rules.rb
|
|
488
|
-
# ships no RBS for Parameters, and the argument / arity rules gate on the same predicate
|
|
489
|
-
#
|
|
498
|
+
# `Rigor::Reflection.rbs_class_known?` (lib/rigor/analysis/check_rules.rb) because Rigor
|
|
499
|
+
# ships no RBS for Parameters, and the argument / arity rules gate on the same predicate. So
|
|
500
|
+
# `params.slice(:a).whatever_rails_adds` never fires, and a scalar-shaped
|
|
490
501
|
# use of a Parameters-typed value (`.to_i`, `.strip`, string interpolation) resolves lenient
|
|
491
502
|
# -to-Dynamic rather than to a diagnostic.
|
|
492
503
|
# - **Flow rules do NOT.** Truthiness folding, ternary arm selection and `.nil?` folding key on
|
|
@@ -529,7 +540,7 @@ module Rigor
|
|
|
529
540
|
# and still carries the chain — `params[:user][:name]` types `Parameters?` and
|
|
530
541
|
# `params[:user].permit(:name)` types `Parameters`, because the dispatcher strips the nil arm
|
|
531
542
|
# for the receiver gate. Idiomatic *chained* reads stay silent too: `call.possible-nil-receiver`
|
|
532
|
-
# restricts itself to `Prism::LocalVariableReadNode` receivers (`CheckRules#nil_receiver_diagnostic`
|
|
543
|
+
# restricts itself to `Prism::LocalVariableReadNode` receivers (`CheckRules#nil_receiver_diagnostic`), so
|
|
533
544
|
# `params[:q].strip` cannot fire. What it does fire on is the assigned form — `q = params[:q];
|
|
534
545
|
# q.strip` — at **error** severity, once per unguarded use. Guarded forms (`return if q.nil?`,
|
|
535
546
|
# `if q`, `q&.strip`, `|| ""`) all narrow correctly and stay silent, so this is not a broken
|
|
@@ -551,7 +562,8 @@ module Rigor
|
|
|
551
562
|
# The control specs stay, and they still bite: they pin the HAZARD (no truthiness fold, no `bot`
|
|
552
563
|
# branch, no possible-nil firing), not the Dynamic answer, so they go red if either typing's
|
|
553
564
|
# hazard ever returns — and they now carry a positive assertion that `params[:k]` types
|
|
554
|
-
# `Parameters
|
|
565
|
+
# `Dynamic[ActionController::Parameters?]` (#2430), so a silent regression to bare `Dynamic[top]`
|
|
566
|
+
# or to the non-nil nominal fails them too.
|
|
555
567
|
STRONG_PARAMS_CHAIN_METHODS = %i[
|
|
556
568
|
require permit permit! expect slice
|
|
557
569
|
except without extract! slice!
|
|
@@ -565,6 +577,16 @@ module Rigor
|
|
|
565
577
|
Rigor::Type::Combinator.nominal_of(REQUEST_CONTEXT_READER_TYPES[:params])
|
|
566
578
|
end
|
|
567
579
|
|
|
580
|
+
# Issue #2430 — the chain on a `params[...]` carrier (`params[:user].permit(:name)`). The result stays
|
|
581
|
+
# a `Dynamic` of the same nominal, so it crosses a declared boundary as the carrier does.
|
|
582
|
+
dynamic_return methods: STRONG_PARAMS_CHAIN_METHODS do |call_node, scope|
|
|
583
|
+
next nil unless call_node.is_a?(Prism::CallNode) && call_node.receiver
|
|
584
|
+
next nil unless controller_scope?(scope)
|
|
585
|
+
next nil unless params_carrier_facet?(scope.type_of(call_node.receiver))
|
|
586
|
+
|
|
587
|
+
Rigor::Type::Combinator.dynamic(Rigor::Type::Combinator.nominal_of(REQUEST_CONTEXT_READER_TYPES[:params]))
|
|
588
|
+
end
|
|
589
|
+
|
|
568
590
|
# Issue #534 item 1, reopened by #574. `#[]` is the single largest named-receiver pair on both survey
|
|
569
591
|
# apps (redmine 581, mastodon ~475). The honest contract is `Parameters | nil` — a missing key really
|
|
570
592
|
# returns nil — and the block comment above records why that typing was REJECTED in 2026-09-01: it put
|
|
@@ -580,15 +602,53 @@ module Rigor
|
|
|
580
602
|
#
|
|
581
603
|
# The non-nil `-> Parameters` typing stays rejected on its own merits, unchanged: it makes every
|
|
582
604
|
# `params[:k]` condition always-truthy and folds a ternary over one to its true arm.
|
|
605
|
+
#
|
|
606
|
+
# Issue #2430 — the answer is a GRADUAL carrier, `Dynamic[Parameters | nil]`, not the bare union.
|
|
607
|
+
# A declared boundary (a `String` parameter, a `-> String` return) rejects the bare union, because
|
|
608
|
+
# `Acceptance` passes only a `Dynamic` through every boundary, and a scalar param is a `String` at
|
|
609
|
+
# runtime. The static facet keeps the strong-params chain typed: the carrier-gated rules below
|
|
610
|
+
# dispatch on the facet, so `params[:user][:name]` and `params[:user].permit(:name)` keep resolving.
|
|
611
|
+
# The control hazards (no truthiness fold, no `bot`, no possible-nil on `q = params[:q]`) are
|
|
612
|
+
# unchanged, because the facet is the same `Parameters | nil` union.
|
|
583
613
|
dynamic_return receivers: [REQUEST_CONTEXT_READER_TYPES[:params]], methods: %i[[]] do |call_node, _scope|
|
|
584
614
|
next nil unless call_node.is_a?(Prism::CallNode)
|
|
585
615
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
616
|
+
params_read_type
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Issue #2430 — `Dynamic` is not a `Nominal`, so the receiver-gated rules never see a carrier
|
|
620
|
+
# receiver. These two rules gate on the carrier's facet instead, and only inside a controller: a
|
|
621
|
+
# carrier is minted by the `params` reader, which is controller-only, so no other scope can hold one.
|
|
622
|
+
dynamic_return methods: %i[[]] do |call_node, scope|
|
|
623
|
+
next nil unless call_node.is_a?(Prism::CallNode) && call_node.receiver
|
|
624
|
+
next nil unless controller_scope?(scope)
|
|
625
|
+
next nil unless params_carrier_facet?(scope.type_of(call_node.receiver))
|
|
626
|
+
|
|
627
|
+
params_read_type
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# The `params` carrier's static facet as a `Dynamic`: `Parameters | nil`, gradual at every boundary.
|
|
631
|
+
def params_read_type
|
|
632
|
+
Rigor::Type::Combinator.dynamic(
|
|
633
|
+
Rigor::Type::Combinator.union(
|
|
634
|
+
Rigor::Type::Combinator.nominal_of(REQUEST_CONTEXT_READER_TYPES[:params]),
|
|
635
|
+
Rigor::Type::Combinator.constant_of(nil)
|
|
636
|
+
)
|
|
589
637
|
)
|
|
590
638
|
end
|
|
591
639
|
|
|
640
|
+
# True when `type` is a `Dynamic` whose facet is `Parameters` or `Parameters | nil`: the carrier a
|
|
641
|
+
# `params[...]` read answers, and the value a chained `[]` / strong-params call receives.
|
|
642
|
+
def params_carrier_facet?(type)
|
|
643
|
+
return false unless type.is_a?(Rigor::Type::Dynamic)
|
|
644
|
+
|
|
645
|
+
facet = type.static_facet
|
|
646
|
+
members = facet.is_a?(Rigor::Type::Union) ? facet.members : [facet]
|
|
647
|
+
non_nil = members.reject { |member| member.is_a?(Rigor::Type::Constant) && member.value.nil? }
|
|
648
|
+
non_nil.size == 1 && non_nil.first.is_a?(Rigor::Type::Nominal) &&
|
|
649
|
+
non_nil.first.class_name == REQUEST_CONTEXT_READER_TYPES[:params]
|
|
650
|
+
end
|
|
651
|
+
|
|
592
652
|
# Phase 5c (2026-09-02, issue #534 "same lane") — the rest of the request-context surface the
|
|
593
653
|
# corpus sweep measured after the #578 / #585 batch: `request.post?` 28 and `request.xhr?` 27 on
|
|
594
654
|
# the two apps, `flash.now` inside the redmine 147 / mastodon 29 flash cluster. Each is typed from
|
|
@@ -35,7 +35,7 @@ module Rigor
|
|
|
35
35
|
|
|
36
36
|
# Where a Rails app declares its adapter. Per-environment files are read too, because an app that
|
|
37
37
|
# sets the adapter only in `config/environments/production.rb` is common; agreement across every
|
|
38
|
-
# file that names one is what licenses the narrowing (see {.
|
|
38
|
+
# file that names one is what licenses the narrowing (see {.detect_adapter}).
|
|
39
39
|
CONFIG_PATHS = ["config/application.rb", "config/environments"].freeze
|
|
40
40
|
|
|
41
41
|
SETTING = /queue_adapter\s*=\s*[:"']([a-z_]+)["']?/
|
|
@@ -13,7 +13,7 @@ module Rigor
|
|
|
13
13
|
# rigor-activejob — validates `Job.perform_later(...)` / `.perform_now(...)` / `.perform(...)`
|
|
14
14
|
# argument arity against the discovered `#perform` definitions.
|
|
15
15
|
#
|
|
16
|
-
# Tier 1D of the [Rails plugins roadmap](
|
|
16
|
+
# Tier 1D of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
17
|
# Statically discovers ActiveJob subclasses by walking the configured `job_search_paths` and
|
|
18
18
|
# parsing each file with Prism — no `active_job` runtime dependency.
|
|
19
19
|
#
|
|
@@ -97,8 +97,11 @@ module Rigor
|
|
|
97
97
|
|
|
98
98
|
# Cached separately from `:job_index` because the two invalidate on different files: editing a
|
|
99
99
|
# schedule changes which jobs are reached without touching `app/jobs` at all. The `watch:` roots the
|
|
100
|
-
# glob at the working directory so that CREATING a schedule file — not just editing one — is seen.
|
|
101
|
-
|
|
100
|
+
# glob at the working directory so that CREATING a schedule file — not just editing one — is seen. Each
|
|
101
|
+
# configured path is escaped to a literal name, so `recurring[1].yml` is watched as that file, and an
|
|
102
|
+
# absolute path is watched under its own directory (`RootedGlob.split_file`, #2222).
|
|
103
|
+
producer :recurring_jobs,
|
|
104
|
+
watch: -> { @recurring_paths.map { |path| Rigor::RootedGlob.split_file(path) } } do |_params|
|
|
102
105
|
RecurringScan.new(
|
|
103
106
|
io_boundary: io_boundary,
|
|
104
107
|
recurring_paths: @recurring_paths
|
|
@@ -20,8 +20,9 @@ module Rigor
|
|
|
20
20
|
#
|
|
21
21
|
# Declined — no root, never a guess: a polymorphic association (no single target), a non-literal
|
|
22
22
|
# `class_name:` / `**opts`, `anonymous_class:`, a `with_options` group with non-literal options, and a
|
|
23
|
-
# `through:` association without a literal `class_name:` / `source_type:`. Without them a `through:`
|
|
24
|
-
#
|
|
23
|
+
# `through:` association without a literal `class_name:` / `source_type:`. Without them a `through:`
|
|
24
|
+
# target is the SOURCE association's class on the through model, and that association (declared
|
|
25
|
+
# there, hence in this same index) roots it itself.
|
|
25
26
|
#
|
|
26
27
|
# A computed name equal to the owner's own demodulized name declines too (Rails 7.2+ tries `::Name`
|
|
27
28
|
# first; the target is then the owner itself or a top-level class).
|