rigortype 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
data/lib/rigor/cli.rb
CHANGED
|
@@ -10,6 +10,9 @@ require_relative "version"
|
|
|
10
10
|
require_relative "analysis/diagnostic"
|
|
11
11
|
require_relative "analysis/result"
|
|
12
12
|
require_relative "cli/options"
|
|
13
|
+
require "did_you_mean"
|
|
14
|
+
|
|
15
|
+
require_relative "cli/catalogue"
|
|
13
16
|
require_relative "cli/diagnostic_formats"
|
|
14
17
|
require_relative "ci_detector"
|
|
15
18
|
require_relative "analysis/check_rules/rule_ids"
|
|
@@ -46,6 +49,17 @@ module Rigor
|
|
|
46
49
|
# 2026-07-17 and nothing noticed. See `docs/internal-spec/config.md`.
|
|
47
50
|
CONFIG_SCHEMA_URL = "https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json"
|
|
48
51
|
|
|
52
|
+
# The comment `rigor init` writes under `plugins: []` when Gemfile.lock names a gem a bundled plugin models. The
|
|
53
|
+
# plugins are suggested, not enabled: ADR-96 Criterion 2 keeps a lockfile's presence from opting a project into a
|
|
54
|
+
# plugin's code, so the user uncomments the ones they want (#2536).
|
|
55
|
+
DETECTED_PLUGINS_HEADER = [
|
|
56
|
+
"# Detected from Gemfile.lock (suggested, not enabled). To load them, delete the `[]` above, uncomment the",
|
|
57
|
+
"# entries you want, then run `rigor plugins --strict`."
|
|
58
|
+
].freeze
|
|
59
|
+
|
|
60
|
+
# The file whose presence marks a Rails application for `rigor init`'s `paths:` suggestion (#2560).
|
|
61
|
+
RAILS_APPLICATION_FILE = "config/application.rb"
|
|
62
|
+
|
|
49
63
|
HANDLERS = {
|
|
50
64
|
"check" => :run_check,
|
|
51
65
|
"init" => :run_init,
|
|
@@ -79,6 +93,26 @@ module Rigor
|
|
|
79
93
|
new(argv.dup, out: out, err: err).run
|
|
80
94
|
end
|
|
81
95
|
|
|
96
|
+
# The parser `rigor init` parses with, built without parsing anything (the catalogue reads it, ADR-126 WD3).
|
|
97
|
+
def self.init_option_parser(options = {})
|
|
98
|
+
OptionParser.new do |opts|
|
|
99
|
+
opts.banner = "Usage: rigor init [options]"
|
|
100
|
+
opts.on("--force", "Overwrite an existing configuration file") { options[:force] = true }
|
|
101
|
+
opts.on("--path=PATH", "Configuration file path") { |value| options[:path] = value }
|
|
102
|
+
opts.on("--no-plugins", "Write no commented plugin suggestions from Gemfile.lock") do
|
|
103
|
+
options[:plugins] = false
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The parser `rigor help` parses with, built without parsing anything (the catalogue reads it, ADR-126 WD3).
|
|
109
|
+
def self.help_option_parser(options = {})
|
|
110
|
+
OptionParser.new do |opts|
|
|
111
|
+
opts.banner = "Usage: rigor help [--format=text|json] [<command>]"
|
|
112
|
+
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
82
116
|
def initialize(argv = ARGV.dup, out: $stdout, err: $stderr)
|
|
83
117
|
@argv = argv
|
|
84
118
|
@out = out
|
|
@@ -89,12 +123,14 @@ module Rigor
|
|
|
89
123
|
command = @argv.shift
|
|
90
124
|
|
|
91
125
|
case command
|
|
92
|
-
when nil, "
|
|
126
|
+
when nil, "-h", "--help"
|
|
93
127
|
@out.puts(help)
|
|
94
128
|
0
|
|
129
|
+
when "help"
|
|
130
|
+
run_help
|
|
95
131
|
when "version", "-v", "--version"
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
run_version
|
|
133
|
+
|
|
98
134
|
else
|
|
99
135
|
dispatch(command)
|
|
100
136
|
end
|
|
@@ -139,6 +175,8 @@ module Rigor
|
|
|
139
175
|
return arm_jit_deadline { send(handler) } if handler
|
|
140
176
|
|
|
141
177
|
@err.puts("Unknown command: #{command}")
|
|
178
|
+
nearest = ::DidYouMean::SpellChecker.new(dictionary: Catalogue.names).correct(command).first
|
|
179
|
+
@err.puts("Did you mean `rigor #{nearest}`?") if nearest
|
|
142
180
|
@err.puts(help)
|
|
143
181
|
EXIT_USAGE
|
|
144
182
|
end
|
|
@@ -156,6 +194,16 @@ module Rigor
|
|
|
156
194
|
yield
|
|
157
195
|
end
|
|
158
196
|
|
|
197
|
+
# `rigor version` and `-v`/`--version` print the version; `rigor version --help` prints the usage instead.
|
|
198
|
+
def run_version
|
|
199
|
+
if @argv.intersect?(%w[-h --help])
|
|
200
|
+
@out.puts("Usage: rigor version")
|
|
201
|
+
else
|
|
202
|
+
@out.puts("rigor #{Rigor::VERSION}")
|
|
203
|
+
end
|
|
204
|
+
0
|
|
205
|
+
end
|
|
206
|
+
|
|
159
207
|
def run_check
|
|
160
208
|
require_relative "cli/check_command"
|
|
161
209
|
|
|
@@ -168,15 +216,11 @@ module Rigor
|
|
|
168
216
|
# present; no implicit merge).
|
|
169
217
|
options = {
|
|
170
218
|
force: false,
|
|
171
|
-
path: ".rigor.dist.yml"
|
|
219
|
+
path: ".rigor.dist.yml",
|
|
220
|
+
plugins: true
|
|
172
221
|
}
|
|
173
222
|
|
|
174
|
-
|
|
175
|
-
opts.banner = "Usage: rigor init [options]"
|
|
176
|
-
opts.on("--force", "Overwrite an existing configuration file") { options[:force] = true }
|
|
177
|
-
opts.on("--path=PATH", "Configuration file path") { |value| options[:path] = value }
|
|
178
|
-
end
|
|
179
|
-
parser.parse!(@argv)
|
|
223
|
+
self.class.init_option_parser(options).parse!(@argv)
|
|
180
224
|
|
|
181
225
|
path = options.fetch(:path)
|
|
182
226
|
if File.exist?(path) && !options.fetch(:force)
|
|
@@ -184,24 +228,57 @@ module Rigor
|
|
|
184
228
|
return 1
|
|
185
229
|
end
|
|
186
230
|
|
|
187
|
-
|
|
231
|
+
plugins = options.fetch(:plugins) ? detected_plugins : []
|
|
232
|
+
paths_suggestion = detected_paths
|
|
233
|
+
begin
|
|
234
|
+
File.write(path, init_template(path, plugins: plugins, paths_suggestion: paths_suggestion))
|
|
235
|
+
rescue SystemCallError => e
|
|
236
|
+
# A destination whose directory is missing or not writable is a bad path argument, the same usage error
|
|
237
|
+
# `--config=PATH` gives for a file that does not exist (exit 64, docs/manual/02-cli-reference.md). The bare
|
|
238
|
+
# Errno::ENOENT backtrace read as an internal crash.
|
|
239
|
+
reason = e.message.sub(/ @ .*\z/m, "")
|
|
240
|
+
@err.puts("rigor init: cannot write #{path}: #{reason}")
|
|
241
|
+
return EXIT_USAGE
|
|
242
|
+
end
|
|
188
243
|
@out.puts("Created #{path}")
|
|
189
|
-
print_init_next_steps(path)
|
|
244
|
+
print_init_next_steps(path, plugins, paths_suggestion)
|
|
190
245
|
0
|
|
191
246
|
end
|
|
192
247
|
|
|
193
|
-
#
|
|
194
|
-
#
|
|
248
|
+
# The bundled plugins whose target gems the project's `Gemfile.lock` declares: the read `rigor doctor` makes,
|
|
249
|
+
# through {PluginGapAdvisory}, so the two cannot disagree about what the project depends on. Empty without a
|
|
250
|
+
# lockfile, which leaves the template as it was before detection existed. The advisory is required lazily:
|
|
251
|
+
# it loads every bundled plugin's entry file, a cost a plain `rigor init` in a directory with no lockfile never
|
|
252
|
+
# pays.
|
|
253
|
+
def detected_plugins
|
|
254
|
+
require_relative "plugin_gap_advisory"
|
|
255
|
+
|
|
256
|
+
PluginGapAdvisory.gaps(project_root: Dir.pwd, plugins: []).map(&:plugin_gem)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# `rigor init`'s template lists the plugins Gemfile.lock detected, or ships empty `plugins:` so a fresh init has
|
|
260
|
+
# nothing to validate — but the moment the user adds any plugin entry, the activation-failure surfaces enumerated
|
|
261
|
+
# in `rigor plugins`'s docstring become real.
|
|
195
262
|
# Point them at the verification command + the canonical readiness flow so silent failures (the cwd / Gemfile /
|
|
196
263
|
# signature_paths mismatches that surfaced during the Mastodon trial) get caught the first time the user wires a
|
|
197
264
|
# plugin, not the first time `rigor check` reports false positives that should have been covered.
|
|
198
|
-
def print_init_next_steps(path)
|
|
265
|
+
def print_init_next_steps(path, plugins = [], paths_suggestion = nil)
|
|
199
266
|
@out.puts ""
|
|
200
267
|
@out.puts "Next steps:"
|
|
201
|
-
|
|
268
|
+
if plugins.empty?
|
|
269
|
+
@out.puts " 1. Edit #{path} — add the `plugins:` your project needs."
|
|
270
|
+
else
|
|
271
|
+
@out.puts " 1. Edit #{path} — to load a detected plugin, delete the `[]` on the `plugins:` line and"
|
|
272
|
+
@out.puts " uncomment the entries you want:"
|
|
273
|
+
plugins.each { |gem| @out.puts " #{gem}" }
|
|
274
|
+
end
|
|
202
275
|
@out.puts " 2. Run `rigor plugins` to verify every configured plugin loads."
|
|
203
276
|
@out.puts " (`--strict` exits 1 on failure; ideal CI gate.)"
|
|
204
277
|
@out.puts " 3. Run `rigor check` to analyse your code."
|
|
278
|
+
return if paths_suggestion.nil?
|
|
279
|
+
|
|
280
|
+
@out.puts " `rigor check` analyses only `lib` until you edit `paths:` in #{path}. " \
|
|
281
|
+
"The suggestion is commented beneath it."
|
|
205
282
|
end
|
|
206
283
|
|
|
207
284
|
# Word-wraps `CheckRules::ALL_RULES` into a comma-separated, `#`-commented block for `init_template`, so the
|
|
@@ -230,8 +307,9 @@ module Rigor
|
|
|
230
307
|
|
|
231
308
|
# Renders the starter `.rigor.yml` body. The template serialises `Configuration::DEFAULTS` (so the on-disk file
|
|
232
309
|
# round-trips through `Configuration.load`) and prepends a short header that points the user at the keys they are
|
|
233
|
-
# most likely to want to edit.
|
|
234
|
-
|
|
310
|
+
# most likely to want to edit. `plugins:` stays the empty list the defaults ship; detected gems appear beneath it as
|
|
311
|
+
# comments, so a project with no lockfile gets the template it always did.
|
|
312
|
+
def init_template(path = ".rigor.dist.yml", plugins: [], paths_suggestion: nil)
|
|
235
313
|
<<~YAML
|
|
236
314
|
# yaml-language-server: $schema=#{CONFIG_SCHEMA_URL}
|
|
237
315
|
# Rigor configuration. The full key reference is
|
|
@@ -287,10 +365,49 @@ module Rigor
|
|
|
287
365
|
# `target_ruby` is left commented: the default only picks the syntax Rigor parses. Set it to the lowest
|
|
288
366
|
# Ruby your project runs on, and Rigor also reports what that Ruby deprecates.
|
|
289
367
|
# target_ruby: #{Configuration::DEFAULTS.fetch('target_ruby').dump}
|
|
290
|
-
#{
|
|
368
|
+
#{settings_yaml(path, plugins, paths_suggestion)}
|
|
291
369
|
YAML
|
|
292
370
|
end
|
|
293
371
|
|
|
372
|
+
# The serialised settings. `plugins: []` stays valid YAML and schema-clean; when `Gemfile.lock` named a modelled
|
|
373
|
+
# gem, the suggestion block sits under it as comments, so nothing loads until the user uncomments a line. The
|
|
374
|
+
# `paths:` suggestion sits under the active `paths:` list the same way, and the active list stays `lib`.
|
|
375
|
+
def settings_yaml(path, plugins, paths_suggestion = nil)
|
|
376
|
+
yaml = YAML.dump(init_settings(path)).sub(/\A---\n/, "")
|
|
377
|
+
yaml = yaml.sub(/^plugins: \[\]\n/) { "plugins: []\n#{detected_plugins_block(plugins)}" } unless plugins.empty?
|
|
378
|
+
return yaml if paths_suggestion.nil?
|
|
379
|
+
|
|
380
|
+
yaml.sub(/^paths:\n(?:- .*\n)+/) { |active| active + paths_suggestion_block(paths_suggestion, path) }
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# The commented `paths:` suggestion: one line naming what was detected and why, then the value to uncomment, written
|
|
384
|
+
# relative to the config file's directory like the active list (#1399).
|
|
385
|
+
def paths_suggestion_block(suggestion, path)
|
|
386
|
+
named = suggestion.fetch(:dirs).join(", ")
|
|
387
|
+
written = roots_relative_to(suggestion.fetch(:dirs), config_dir_of(path)).join(", ")
|
|
388
|
+
pronoun = suggestion.fetch(:dirs).one? ? "it" : "them"
|
|
389
|
+
"# #{suggestion.fetch(:reason)}: #{named}. To scan #{pronoun}, delete the `paths:` list above and " \
|
|
390
|
+
"uncomment the line below:\n# paths: [#{written}]\n"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# The source directories `rigor init` suggests for `paths:`, read from the project root (the cwd, like the plugin
|
|
394
|
+
# detection). A Rails application (config/application.rb) has app/ and lib/, each listed only if it exists. Any
|
|
395
|
+
# other layout gets no suggestion: the template's `lib` already covers it, and no other directory is guessed
|
|
396
|
+
# (#2560). Nil when there is nothing to add to today's `paths: [lib]`.
|
|
397
|
+
def detected_paths
|
|
398
|
+
return nil unless File.file?(RAILS_APPLICATION_FILE)
|
|
399
|
+
|
|
400
|
+
dirs = %w[app lib].select { |dir| File.directory?(dir) }
|
|
401
|
+
return nil if dirs.empty? || dirs == ["lib"]
|
|
402
|
+
|
|
403
|
+
{ dirs: dirs, reason: "Detected a Rails app (#{RAILS_APPLICATION_FILE})" }
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def detected_plugins_block(plugins)
|
|
407
|
+
lines = DETECTED_PLUGINS_HEADER + plugins.map { |gem| "# - #{gem}" }
|
|
408
|
+
lines.map { |line| "#{line}\n" }.join
|
|
409
|
+
end
|
|
410
|
+
|
|
294
411
|
# The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
|
|
295
412
|
# the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
|
|
296
413
|
# relative to the config file's directory, which is what {Configuration.load} resolves them against. `paths:`
|
|
@@ -320,7 +437,7 @@ module Rigor
|
|
|
320
437
|
|
|
321
438
|
# Each root is resolved against the physical cwd (where `rigor` reads it from), then written relative to the config
|
|
322
439
|
# dir. With the config at the project root, both sides are the same physical directory, so the roots come out as
|
|
323
|
-
# they are spelled from it (`lib`, `spec`)
|
|
440
|
+
# they are spelled from it (`lib`, `spec`).
|
|
324
441
|
def roots_relative_to(roots, config_dir)
|
|
325
442
|
roots.map { |root| Pathname(File.absolute_path(root)).relative_path_from(config_dir).to_s }
|
|
326
443
|
end
|
|
@@ -445,11 +562,18 @@ module Rigor
|
|
|
445
562
|
# Surfaced because a bare `rigor describe` is the intuitive guess (the onboarding field trial saw it tried and met
|
|
446
563
|
# "Unknown command").
|
|
447
564
|
def run_describe
|
|
565
|
+
return print_describe_usage if @argv.intersect?(%w[-h --help])
|
|
566
|
+
|
|
448
567
|
require_relative "cli/skill_command"
|
|
449
568
|
|
|
450
569
|
CLI::SkillCommand.new(argv: ["describe", *@argv], out: @out, err: @err).run
|
|
451
570
|
end
|
|
452
571
|
|
|
572
|
+
def print_describe_usage
|
|
573
|
+
@out.puts("Usage: rigor describe [--deep]")
|
|
574
|
+
0
|
|
575
|
+
end
|
|
576
|
+
|
|
453
577
|
def run_docs
|
|
454
578
|
require_relative "cli/docs_command"
|
|
455
579
|
|
|
@@ -480,41 +604,72 @@ module Rigor
|
|
|
480
604
|
CLI::UpgradeCommand.new(argv: @argv, out: @out, err: @err).run
|
|
481
605
|
end
|
|
482
606
|
|
|
607
|
+
# The text help is rendered from {Catalogue}, so the command list cannot drift from the dispatch table.
|
|
483
608
|
def help
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
609
|
+
Catalogue.help_text
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# `rigor help [--format=text|json] [<command>]`. `help <command>` behaves as `rigor <command> --help`;
|
|
613
|
+
# `--format=json` prints the whole {Catalogue} document.
|
|
614
|
+
def run_help
|
|
615
|
+
return print_command_list if @argv.intersect?(%w[-h --help])
|
|
616
|
+
|
|
617
|
+
options = parse_help_options
|
|
618
|
+
topics = @argv
|
|
619
|
+
return run_help_json(topics) if options[:format] == "json"
|
|
620
|
+
|
|
621
|
+
topic = topics.first
|
|
622
|
+
return run_help_topic(topic, topics.drop(1)) if topic
|
|
623
|
+
|
|
624
|
+
print_command_list
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def print_command_list
|
|
628
|
+
@out.puts(help)
|
|
629
|
+
0
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def parse_help_options
|
|
633
|
+
options = Hash.new("text")
|
|
634
|
+
# `order!` stops at the first positional, so the flags of the command being asked about are never parsed here.
|
|
635
|
+
self.class.help_option_parser(options).order!(@argv)
|
|
636
|
+
options
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def run_help_json(topics)
|
|
640
|
+
if topics.any?
|
|
641
|
+
@err.puts("rigor help --format=json takes no command; it prints every command")
|
|
642
|
+
return EXIT_USAGE
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
@out.puts(JSON.pretty_generate(Catalogue.document))
|
|
646
|
+
0
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def run_help_topic(topic, rest)
|
|
650
|
+
# Flags after the command name (`help type-of --workers=2`) are not parsed; a second word is not a subcommand's
|
|
651
|
+
# help, so it is refused rather than answered with the parent's.
|
|
652
|
+
extra = rest.reject { |word| word.start_with?("-") }
|
|
653
|
+
unless extra.empty?
|
|
654
|
+
@err.puts("rigor help takes one command, got: #{[topic, *extra].join(' ')}")
|
|
655
|
+
return EXIT_USAGE
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
case topic
|
|
659
|
+
when "help"
|
|
660
|
+
@out.puts(self.class.help_option_parser.help)
|
|
661
|
+
0
|
|
662
|
+
when "version"
|
|
663
|
+
@out.puts("Usage: rigor version")
|
|
664
|
+
0
|
|
665
|
+
when *HANDLERS.keys
|
|
666
|
+
@argv = ["--help"]
|
|
667
|
+
dispatch(topic)
|
|
668
|
+
else
|
|
669
|
+
@err.puts("Unknown command: #{topic}")
|
|
670
|
+
@err.puts(help)
|
|
671
|
+
EXIT_USAGE
|
|
672
|
+
end
|
|
518
673
|
end
|
|
519
674
|
end
|
|
520
675
|
end
|
data/lib/rigor/config_audit.rb
CHANGED
|
@@ -35,7 +35,7 @@ module Rigor
|
|
|
35
35
|
# (`rbs_extended.*`, `rbs.*`, `dynamic.*`) is left alone, since the engine emits those ids
|
|
36
36
|
# outside the catalogue.
|
|
37
37
|
module ConfigAudit
|
|
38
|
-
# One config-level finding. `kind` discriminates the source key (`:signature_path`,
|
|
38
|
+
# One config-level finding. `kind` discriminates the source key (`:unknown_key`, `:signature_path`,
|
|
39
39
|
# `:bundled_plugin_signature_path`, `:library`, `:disabled_rule`, `:severity_override`,
|
|
40
40
|
# `:bundler_bundle_path`, `:bundler_lockfile`, `:rbs_collection_lockfile`); `fields` carries the kind-specific
|
|
41
41
|
# structured data merged into {#to_h} for JSON consumers.
|
|
@@ -15,12 +15,12 @@ module Rigor
|
|
|
15
15
|
# rules `:warning`.
|
|
16
16
|
# - `strict`: Every rule is `:error`. CI-friendly.
|
|
17
17
|
#
|
|
18
|
-
# The
|
|
18
|
+
# The resolution order, first match wins:
|
|
19
19
|
#
|
|
20
|
-
# 1.
|
|
21
|
-
# 2.
|
|
22
|
-
# 3.
|
|
23
|
-
#
|
|
20
|
+
# 1. An explicit `severity_overrides:` entry for the row (`resolve`'s `overrides:`).
|
|
21
|
+
# 2. A bleeding-edge severity override.
|
|
22
|
+
# 3. The profile's entry for the canonical rule id.
|
|
23
|
+
# 4. The diagnostic's own authored severity (the rule's default); an unrecognised rule keeps it.
|
|
24
24
|
module SeverityProfile
|
|
25
25
|
VALID_PROFILES = %i[lenient balanced strict].freeze
|
|
26
26
|
VALID_SEVERITIES = %i[error warning info off].freeze
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -62,6 +62,10 @@ module Rigor
|
|
|
62
62
|
"disable" => [],
|
|
63
63
|
"libraries" => [],
|
|
64
64
|
"signature_paths" => nil,
|
|
65
|
+
# Issue #2517 — a core signature directory loaded IN PLACE OF the `rbs` gem's `core/` (a provider such as
|
|
66
|
+
# rbs-shifter writes one for an older CRuby). `nil` (the default) loads the gem's core. Unlike
|
|
67
|
+
# `signature_paths:`, which only adds, this replaces: the default core is not loaded beside it.
|
|
68
|
+
"core_root" => nil,
|
|
65
69
|
# The project's test roots: the directories (or files) holding its tests. `nil` (the default) auto-detects
|
|
66
70
|
# whichever of `spec/` and `test/` exist, the same way `signature_paths: nil` auto-detects `sig/`; `[]`
|
|
67
71
|
# declares that the project has none. `rigor sig-gen --params=observed` harvests call-site observations
|
|
@@ -77,10 +81,9 @@ module Rigor
|
|
|
77
81
|
"inline_declared" => "write"
|
|
78
82
|
},
|
|
79
83
|
# ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit
|
|
80
|
-
# files that the analyzer walks before per-file inference so patched-method declarations are
|
|
81
|
-
# across the project (e.g. `lib/core_ext/string_extensions.rb`).
|
|
82
|
-
#
|
|
83
|
-
# tier consuming the registry lands in slice 2.
|
|
84
|
+
# files (or globs) that the analyzer walks before per-file inference so patched-method declarations are
|
|
85
|
+
# visible across the project (e.g. `lib/core_ext/string_extensions.rb`). A missing literal path surfaces
|
|
86
|
+
# as `pre-eval.file-not-found`.
|
|
84
87
|
"pre_eval" => [],
|
|
85
88
|
# ADR-22 — baseline file path. nil (default) means no baseline is loaded; the `false` literal is
|
|
86
89
|
# treated as the explicit-disable form for `.rigor.yml`-side override of an upstream `.rigor.dist.yml`
|
|
@@ -229,6 +232,11 @@ module Rigor
|
|
|
229
232
|
PATH_KEYS = %w[paths signature_paths test_paths pre_eval].freeze
|
|
230
233
|
private_constant :PATH_KEYS
|
|
231
234
|
|
|
235
|
+
# The single-path sibling of {PATH_KEYS}: resolved the same way, but kept a String rather than wrapped in an
|
|
236
|
+
# Array.
|
|
237
|
+
SCALAR_PATH_KEYS = %w[core_root].freeze
|
|
238
|
+
private_constant :SCALAR_PATH_KEYS
|
|
239
|
+
|
|
232
240
|
# Top-level keys this implementation DECLARES but never reads — see `docs/internal-spec/config.md`
|
|
233
241
|
# § "Reserved namespaces" and ADR-99.
|
|
234
242
|
#
|
|
@@ -271,7 +279,8 @@ module Rigor
|
|
|
271
279
|
|
|
272
280
|
attr_reader :target_ruby, :paths, :exclude_patterns, :plugins, :cache_path, :cache_max_bytes,
|
|
273
281
|
:cache_validation, :disabled_rules,
|
|
274
|
-
:libraries, :signature_paths, :
|
|
282
|
+
:libraries, :signature_paths, :core_root, :test_paths, :fold_platform_specific_paths,
|
|
283
|
+
:parameter_inference,
|
|
275
284
|
:plugins_isolation,
|
|
276
285
|
:plugins_io_network, :plugins_io_allowed_paths,
|
|
277
286
|
:plugins_io_allowed_url_hosts,
|
|
@@ -490,6 +499,7 @@ module Rigor
|
|
|
490
499
|
|
|
491
500
|
out = data.dup
|
|
492
501
|
PATH_KEYS.each { |key| resolve_path_key!(out, key, base_dir) }
|
|
502
|
+
SCALAR_PATH_KEYS.each { |key| resolve_scalar_path_key!(out, key, base_dir) }
|
|
493
503
|
resolve_plugins_io_paths!(out, base_dir)
|
|
494
504
|
out
|
|
495
505
|
end
|
|
@@ -500,6 +510,12 @@ module Rigor
|
|
|
500
510
|
out[key] = Array(out[key]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
|
|
501
511
|
end
|
|
502
512
|
|
|
513
|
+
def self.resolve_scalar_path_key!(out, key, base_dir)
|
|
514
|
+
return unless out.key?(key) && out[key].is_a?(String)
|
|
515
|
+
|
|
516
|
+
out[key] = ConfigPath.absolute(out[key], base_dir)
|
|
517
|
+
end
|
|
518
|
+
|
|
503
519
|
def self.resolve_plugins_io_paths!(out, base_dir)
|
|
504
520
|
plugins_io = out["plugins_io"]
|
|
505
521
|
return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"]
|
|
@@ -582,6 +598,7 @@ module Rigor
|
|
|
582
598
|
@libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
|
|
583
599
|
sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
|
|
584
600
|
@signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
|
|
601
|
+
@core_root = coerce_core_root(data.fetch("core_root", DEFAULTS.fetch("core_root")))
|
|
585
602
|
test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
|
|
586
603
|
@test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
|
|
587
604
|
@sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
|
|
@@ -595,18 +612,13 @@ module Rigor
|
|
|
595
612
|
# ADR-67 WD6a — resolve to a strict Boolean so a truthy non-`true` value (e.g. a stray String) does not
|
|
596
613
|
# silently enable the gate; only the literal `true` activates the check-walk collector pre-pass.
|
|
597
614
|
@parameter_inference = data.fetch("parameter_inference", DEFAULTS.fetch("parameter_inference")) == true
|
|
598
|
-
# Resolved before `@effects` — ADR-103 WD15's "effects-on-by-default" reads {#bleeding_edge_active?},
|
|
599
|
-
# which reads `@bleeding_edge_active_ids`.
|
|
600
|
-
@bleeding_edge = coerce_bleeding_edge(
|
|
601
|
-
data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge"))
|
|
602
|
-
)
|
|
603
|
-
@bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
|
|
604
|
-
@bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
|
|
605
615
|
# ADR-103 WD13 — presence, not truthiness: `effects:` written with no body parses as nil and still
|
|
606
|
-
# means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
616
|
+
# means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads. The raw
|
|
617
|
+
# input is kept so {#with_bleeding_edge} re-derives the effects block from the same source.
|
|
618
|
+
@effects_key_present = effects_key_present
|
|
619
|
+
@effects_written = Ractor.make_shareable(data.fetch("effects", false))
|
|
620
|
+
derive_bleeding_edge(data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge")))
|
|
621
|
+
derive_effects
|
|
610
622
|
@cache_path = cache.fetch("path").to_s
|
|
611
623
|
raw_max = cache.fetch("max_bytes")
|
|
612
624
|
@cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max)
|
|
@@ -672,6 +684,7 @@ module Rigor
|
|
|
672
684
|
"disable" => disabled_rules,
|
|
673
685
|
"libraries" => libraries,
|
|
674
686
|
"signature_paths" => signature_paths,
|
|
687
|
+
"core_root" => core_root,
|
|
675
688
|
# `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
|
|
676
689
|
# incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
|
|
677
690
|
# invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
|
|
@@ -752,15 +765,14 @@ module Rigor
|
|
|
752
765
|
# It is a frozen `dup` with the bleeding-edge ivars re-set: `dup` returns an unfrozen shallow copy
|
|
753
766
|
# (every other ivar is the receiver's deeply-frozen value, safe to share read-only), the replacements
|
|
754
767
|
# are themselves deeply frozen, and the re-`freeze` keeps the result `Ractor.shareable?` for the worker
|
|
755
|
-
# path. **Every ivar `#initialize` derives from
|
|
756
|
-
#
|
|
768
|
+
# path. **Every ivar `#initialize` derives from the bleeding-edge selector has to be re-derived here** — it
|
|
769
|
+
# calls the same {#derive_bleeding_edge} and {#derive_effects} that `#initialize` does, so the selector,
|
|
770
|
+
# the severity map, the active-id set and the `effects:` default (ADR-103 WD15's "effects-on-by-default")
|
|
771
|
+
# all follow the new value, and a derived ivar added to `#initialize` is picked up without a second edit.
|
|
757
772
|
def with_bleeding_edge(value)
|
|
758
|
-
selector = coerce_bleeding_edge(value)
|
|
759
773
|
copy = dup
|
|
760
|
-
copy.
|
|
761
|
-
copy.
|
|
762
|
-
BleedingEdge.severity_overrides_for(selector))
|
|
763
|
-
copy.instance_variable_set(:@bleeding_edge_active_ids, BleedingEdge.active_ids_for(selector))
|
|
774
|
+
copy.send(:derive_bleeding_edge, value)
|
|
775
|
+
copy.send(:derive_effects)
|
|
764
776
|
copy.freeze
|
|
765
777
|
end
|
|
766
778
|
|
|
@@ -794,19 +806,38 @@ module Rigor
|
|
|
794
806
|
#
|
|
795
807
|
# Same `dup` + re-`freeze` shape as {#with_bleeding_edge}: every other ivar is the receiver's deeply
|
|
796
808
|
# frozen value, safe to share read-only, and the result stays `Ractor.shareable?` for the worker path.
|
|
809
|
+
# The implicit block is recorded as a written empty one, so a later {#with_bleeding_edge} re-derives
|
|
810
|
+
# it instead of falling back to the absent-key default and dropping it.
|
|
797
811
|
def with_effects_enabled
|
|
798
812
|
return self if effects_enabled?
|
|
799
813
|
|
|
800
814
|
copy = dup
|
|
801
|
-
copy.instance_variable_set(:@
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
copy.instance_variable_set(:@effects_check, true)
|
|
815
|
+
copy.instance_variable_set(:@effects_key_present, true)
|
|
816
|
+
copy.instance_variable_set(:@effects_written, {}.freeze)
|
|
817
|
+
copy.send(:derive_effects)
|
|
805
818
|
copy.freeze
|
|
806
819
|
end
|
|
807
820
|
|
|
808
821
|
private
|
|
809
822
|
|
|
823
|
+
# ADR-50 § WD2 / ADR-103 WD15 — derives the bleeding-edge selector and the two ivars the
|
|
824
|
+
# {#bleeding_edge_active?} predicate and the severity resolution read. `#initialize` and
|
|
825
|
+
# {#with_bleeding_edge} both call this, so there is one derivation to keep in step.
|
|
826
|
+
def derive_bleeding_edge(value)
|
|
827
|
+
@bleeding_edge = coerce_bleeding_edge(value)
|
|
828
|
+
@bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
|
|
829
|
+
@bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
# ADR-103 WD13 / WD15 — derives the `effects:` block and its snapshot and policy ivars from the raw
|
|
833
|
+
# `effects:` input the instance kept (`@effects_written`, `@effects_key_present`). The effects-on-by-default
|
|
834
|
+
# default reads the active-id set, so {#derive_bleeding_edge} must run first.
|
|
835
|
+
def derive_effects
|
|
836
|
+
@effects = coerce_effects(@effects_written, effects_key_present: @effects_key_present)
|
|
837
|
+
coerce_effects_snapshot(@effects)
|
|
838
|
+
coerce_effects_policy(@effects)
|
|
839
|
+
end
|
|
840
|
+
|
|
810
841
|
# ADR-103 WD13 — the `effects:` block, or nil when the key is absent or explicitly `false`. A
|
|
811
842
|
# present-but-empty key (`effects:` alone, which YAML parses as nil) and `effects: {}` are the same
|
|
812
843
|
# thing: collection on, every sub-key defaulted. A non-Hash body is normalised to `{}` rather than
|
|
@@ -818,8 +849,7 @@ module Rigor
|
|
|
818
849
|
# as `effects: {}` when the "effects-on-by-default" bleeding-edge feature is adopted. An explicit
|
|
819
850
|
# `effects: false` — `effects_key_present == true` — always stays off regardless of the feature, and a
|
|
820
851
|
# written block always wins outright: the feature can only fill an *absence*.
|
|
821
|
-
def coerce_effects(
|
|
822
|
-
value = data.fetch("effects", false)
|
|
852
|
+
def coerce_effects(value, effects_key_present:)
|
|
823
853
|
return {}.freeze if value == false && !effects_key_present && effects_forced_on_by_bleeding_edge?
|
|
824
854
|
return nil if value == false
|
|
825
855
|
|
|
@@ -1054,7 +1084,7 @@ module Rigor
|
|
|
1054
1084
|
end
|
|
1055
1085
|
end
|
|
1056
1086
|
|
|
1057
|
-
# `target_ruby` is passed
|
|
1087
|
+
# `target_ruby` is passed as Prism's `version:` option at the analyser's parse sites
|
|
1058
1088
|
# (`Analysis::Runner`, `CLI::TypeOfCommand`, `CLI::TypeScanCommand`) so projects that target an older
|
|
1059
1089
|
# Ruby get parse errors for syntax their target doesn't support. Format validation here is loose —
|
|
1060
1090
|
# accepts any `<major>.<minor>` or `<major>.<minor>.<patch>` form, plus the literal `"latest"`. Prism
|
|
@@ -1097,6 +1127,18 @@ module Rigor
|
|
|
1097
1127
|
# ADR-22 WD2 (b) — `baseline: <path>` activates the file; `baseline: false` is the explicit-disable form
|
|
1098
1128
|
# (useful in `.rigor.yml` to override an upstream `.rigor.dist.yml` that names a baseline). `nil`
|
|
1099
1129
|
# (default / absent key) is also "no baseline".
|
|
1130
|
+
# Issue #2517 — the directory's existence and contents are checked where the environment is built
|
|
1131
|
+
# ({Environment::CoreRoot.validate!}), not here: `rigor init`, `rigor doctor` and the language server's reload
|
|
1132
|
+
# load a configuration without building an environment, and must still be able to read one naming a core that
|
|
1133
|
+
# is not generated yet.
|
|
1134
|
+
def coerce_core_root(value)
|
|
1135
|
+
return nil if value.nil?
|
|
1136
|
+
raise ConfigurationError, "core_root must be a directory path, got #{value.inspect}" unless value.is_a?(String)
|
|
1137
|
+
raise ConfigurationError, "core_root must not be empty" if value.strip.empty?
|
|
1138
|
+
|
|
1139
|
+
value.dup.freeze
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1100
1142
|
def coerce_baseline_path(value)
|
|
1101
1143
|
return nil if value.nil? || value == false
|
|
1102
1144
|
|
|
@@ -58,11 +58,9 @@ module Rigor
|
|
|
58
58
|
node.is_a?(Prism::SingletonClassNode) || (node.is_a?(Prism::CallNode) && node.block.is_a?(Prism::BlockNode))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
# A class body.
|
|
62
61
|
CLASS_BODY = of(:class, false)
|
|
63
62
|
# A `class << self` body: `self` and the definee are both the singleton class.
|
|
64
63
|
SINGLETON_CLASS_BODY = of(:singleton_class, true)
|
|
65
|
-
# The body of an instance method of the class.
|
|
66
64
|
INSTANCE_METHOD_BODY = of(:instance, false)
|
|
67
65
|
# The top level, where `self` is `main`, an instance of `Object`, and a `def` defines `Object#m`.
|
|
68
66
|
TOP_LEVEL = INSTANCE_METHOD_BODY
|
|
@@ -8,7 +8,7 @@ module Rigor
|
|
|
8
8
|
# An author-declared upper bound on one method's effect labels (ADR-103 WD1; normative in
|
|
9
9
|
# `docs/type-specification/effect-labels.md` § Effect envelopes).
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# Its facts:
|
|
12
12
|
#
|
|
13
13
|
# - {#owner_key} — the method key the envelope binds (`Class#m` / `Class.m`). A class-level
|
|
14
14
|
# envelope is read once and {#rebind}ed onto each method of that class discovery knows.
|
|
@@ -69,7 +69,7 @@ module Rigor
|
|
|
69
69
|
VALIDATION_TRIGGERS = %w[valid? invalid? validate!].freeze
|
|
70
70
|
SINGLETON_SAVE_TRIGGERS = %w[create create!].freeze
|
|
71
71
|
|
|
72
|
-
# `initialize` is not a mailer action
|
|
72
|
+
# `initialize` is not a mailer action.
|
|
73
73
|
NON_ACTION_METHODS = %w[initialize].to_set.freeze
|
|
74
74
|
|
|
75
75
|
module_function
|