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
|
@@ -24,7 +24,7 @@ Relative paths in a config file resolve against that file's own directory, with
|
|
|
24
24
|
`rigor` runs from. They match only when you run `rigor` from the directory that holds the config file, which is
|
|
25
25
|
the usual case; with `--config path/to/.rigor.yml` from elsewhere, give these three as absolute paths.
|
|
26
26
|
A leading `~/` (or a bare `~`) in a path setting expands to your home directory; `~name` is an ordinary
|
|
27
|
-
directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `test_paths:`,
|
|
27
|
+
directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `core_root:`, `test_paths:`,
|
|
28
28
|
`pre_eval:`, `plugins_io.allowed_paths:`, `includes:`, `bundler.bundle_path:`, `bundler.lockfile:` and
|
|
29
29
|
`rbs_collection.lockfile:`. It does not apply to `cache.path:`, `baseline:` or `effects.snapshot.path:`, which stay
|
|
30
30
|
literal and relative to the working directory.
|
|
@@ -67,7 +67,7 @@ cache:
|
|
|
67
67
|
| `target_ruby` | String | `"4.0"` | The lowest Ruby version *your* project runs on — `"X.Y"`, `"X.Y.Z"`, or `"latest"`. Independent of the Ruby Rigor itself runs on. Every value selects the syntax Rigor parses. A version you set explicitly also turns on the Ruby-deprecation rules for that version ([`call.deprecated-ruby2-keywords`](04-diagnostics.md#rule-call-deprecated-ruby2-keywords) from `"4.1"`); the default and `"latest"` never do, which is why `rigor init` writes the key commented out. |
|
|
68
68
|
| `paths` | Array | `["lib"]` | Directories or files to analyse. |
|
|
69
69
|
| `exclude` | Array | `[]` | Glob patterns to skip. A relative pattern is matched against the file's path relative to the directory of the config file in use (the working directory when no config file is used), so it excludes the same files whether the path came from `paths:` or from a command-line argument, and it never matches on a directory above that base (a file outside it is seen as `../…`, so `../shared/lib/gen/**` reaches a sibling directory that `paths:` names). A leading `./` is ignored, and a pattern starting with `.` (such as `.*`) also matches files outside the base, which appear as `../…`. A symlink to a file is matched only under its own name: the realpath pass resolves directories, not files. `*` and `**` also cross `/`, so `lib/*.rb` matches nested files too. An absolute pattern is matched against the absolute path. Patterns from an `includes:` file resolve against the top-level config's directory, not the included file's. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
70
|
-
| `test_paths` | Array | `nil` | The project's test roots: the directories (or files) holding its tests. Relative entries resolve against the config file's directory. Unset auto-detects whichever of `spec/` and `test/` exist; `[]` declares none. `rigor sig-gen --params=observed` reads call sites there to type parameters, and names on stderr a declared root that does not exist. Test roots are not analysed unless `paths:` also lists them, and changing them invalidates no cache. |
|
|
70
|
+
| `test_paths` | Array | `nil` | The project's test roots: the directories (or files) holding its tests. Relative entries resolve against the config file's directory. Unset auto-detects whichever of `spec/` and `test/` exist in the config file's directory, or in the working directory when the config file's directory has neither or no config file is used; `[]` declares none. `rigor sig-gen --params=observed` reads call sites there to type parameters, and names on stderr a declared root that does not exist. Test roots are not analysed unless `paths:` also lists them, and changing them invalidates no cache. |
|
|
71
71
|
| `includes` | Array | `[]` | Other config files to layer underneath this one. |
|
|
72
72
|
| `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
|
|
73
73
|
| `parameter_inference` | Boolean | `false` | Opt-in call-site parameter type inference on the `check` walk ([ADR-67](../adr/67-parameter-type-inference.md) WD6). When `true`, an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved call-site argument types, sharpening downstream ivar reads, folds, and protection coverage. Precision-additive only — the negative rules never fire against an inferred parameter. Cannot be combined with `--incremental`. |
|
|
@@ -78,9 +78,39 @@ cache:
|
|
|
78
78
|
| --- | --- | --- | --- |
|
|
79
79
|
| `libraries` | Array | `[]` | Standard-library / gem names whose bundled RBS to load. |
|
|
80
80
|
| `signature_paths` | Array | `nil` | Extra directories of `.rbs` files. Relative entries resolve against the config file's directory. |
|
|
81
|
+
| `core_root` | String | `nil` | A directory of core `.rbs` files loaded **instead of** the `rbs` gem's own core, such as one [rbs-shifter](https://github.com/rigortype/rbs-shifter) generates for an older CRuby. A relative path resolves against the config file's directory. `nil` loads the gem's core. See [Analysing for an older Ruby](#analysing-for-an-older-ruby). |
|
|
81
82
|
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. The walker does not follow `require` / `require_relative`, so list the files in load order: a method declared in two files takes the last-listed one, and a file that requires a later-listed one is still read in list order. `type-of`, `type-scan`, `trace`, `annotate` and `sig-gen` read the patches too, so they type a patched method as `rigor check` does. |
|
|
82
83
|
| `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
|
|
83
84
|
|
|
85
|
+
#### Analysing for an older Ruby
|
|
86
|
+
|
|
87
|
+
Rigor runs on Ruby 4.0, and its bundled core signatures describe Ruby 4.0. A project that runs on an older
|
|
88
|
+
Ruby can call a method that version does not have, and Rigor accepts the call. To check against the older
|
|
89
|
+
API, point `core_root:` at a core signature directory written for it:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
target_ruby: "3.3"
|
|
93
|
+
core_root: vendor/rbs-shifter/3.3/core
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`core_root:` replaces the core, while `signature_paths:` only adds to it. A method that a signature directory
|
|
97
|
+
omits stays declared by the default core, so `signature_paths:` cannot remove one. With the core above,
|
|
98
|
+
`[1, 2].rfind { ... }` reports `call.undefined-method` and `" text ".strip(" ")` reports `call.wrong-arity`.
|
|
99
|
+
Project signatures, plugins and `libraries:` still load on top of it. `target_ruby:` sets the syntax Rigor
|
|
100
|
+
parses; it does not choose a core.
|
|
101
|
+
|
|
102
|
+
A `core_root:` that does not exist, is not a directory, holds no `.rbs` file, lies inside (or contains) one of
|
|
103
|
+
your `signature_paths:`, or does not build an RBS environment stops the run with exit code 64. A core class
|
|
104
|
+
whose definition fails to build because of the core's own files is reported as an
|
|
105
|
+
`rbs.coverage.definition-build-failed` error:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
rigor: core_root: "/path/to/vendor/rbs-shifter/3.3/core" does not exist
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The cache keys on the directory and on the contents of its files, so switching cores or regenerating one
|
|
112
|
+
invalidates cached results, including in a running language server.
|
|
113
|
+
|
|
84
114
|
### Signature generation
|
|
85
115
|
|
|
86
116
|
| Key | Type | Default | Meaning |
|
|
@@ -106,6 +136,7 @@ rigor: disable: "call.undefined-methdo" is not a recognized rule id; the suppres
|
|
|
106
136
|
rigor: disable: "plugin_loader.load-error" names a run-level row, which `disable:` does not reach; use `severity_overrides:` or a baseline
|
|
107
137
|
rigor: disable: "plugin.shoulda-matchers.unknown-column" names no plugin `plugins:` configures; the suppression has no effect
|
|
108
138
|
rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the override has no effect
|
|
139
|
+
rigor: severity_overrides: "undefined-method" is a legacy alias of `call.undefined-method`; the override has no effect, so write `call.undefined-method`
|
|
109
140
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
110
141
|
```
|
|
111
142
|
|
|
@@ -143,7 +174,10 @@ is left alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
|
143
174
|
those rows are the intended keys and are never flagged, while a bare plugin
|
|
144
175
|
rule, `all`, or a `plugin.`-qualified key for an unconfigured plugin is. The same findings appear in the
|
|
145
176
|
`--format=json` payload under `config_warnings` (each tagged with a
|
|
146
|
-
`kind`), so CI can assert on them.
|
|
177
|
+
`kind`), so CI can assert on them. The [language server](09-editor-integration.md)
|
|
178
|
+
shows each one as a `window/showMessage` warning, and every
|
|
179
|
+
[MCP tool](10-mcp-server.md#tool-reference) that takes a `config`
|
|
180
|
+
argument includes them in its result.
|
|
147
181
|
|
|
148
182
|
### Diagnostics
|
|
149
183
|
|
|
@@ -193,7 +227,7 @@ project Ruby's gem home without running your toolchain. If `rigor check`'s
|
|
|
193
227
|
`--stats` shows gems whose RBS it could not find, point it at the bundle
|
|
194
228
|
explicitly with `bundler.bundle_path:`, or supply signatures another way:
|
|
195
229
|
`rbs collection install` (auto-discovered) or `dependencies.source_inference:`.
|
|
196
|
-
| `rbs_collection.auto_detect` | Boolean | `true` | Auto-discover `rbs_collection.lock.yaml`. |
|
|
230
|
+
| `rbs_collection.auto_detect` | Boolean | `true` | Auto-discover `rbs_collection.lock.yaml`. An entry is not loaded for a gem your `Gemfile.lock` sources from a `path:` directory that is at or under one of your `paths:` entries, or contains one, and is not left out by `exclude:` (the gem is code Rigor analyses, which your `sig/` declares). |
|
|
197
231
|
| `rbs_collection.lockfile` | String | `nil` | Explicit `rbs_collection.lock.yaml` path. |
|
|
198
232
|
| `dependencies.source_inference` | Array | `[]` | Per-gem source-inference modes (ADR-10). |
|
|
199
233
|
| `dependencies.budget_per_gem` | Integer | `5000` | Per-gem source-walk cap, counted in **method definitions** (not time): the walker stops harvesting a gem's catalog once it reaches this many `def`s, then emits `dynamic.dependency-source.budget-exceeded` and degrades the rest to `Dynamic[top]`. Range 1250–20000. |
|
|
@@ -67,7 +67,7 @@ carries no `documentation_url`.
|
|
|
67
67
|
| <a id="rule-flow-return-in-ensure"></a>`flow.return-in-ensure` | An explicit `return` inside an `ensure` clause — it overrides the method's in-flight return value and silently swallows any in-flight exception. A `return` in a nested `def`, lambda, or `define_method` block inside the `ensure` does not fire (it exits that inner frame). | high |
|
|
68
68
|
| <a id="rule-flow-shadowed-rescue-clause"></a>`flow.shadowed-rescue-clause` | A `rescue` clause can never run because an earlier clause of the same chain already catches a superclass (or the same class) of every exception class it names. | high |
|
|
69
69
|
| <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
|
|
70
|
-
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. | high |
|
|
70
|
+
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. With `rigor-actionpack` or `rigor-actionmailer` loaded, writes in two different actions of a controller or mailer are not compared, since Rails runs one action per instance. A write in a private or protected helper, `initialize` or the same action still is, and so is one in a callback target or `helper_method` that a macro in the same file names. A public method named only by a macro in another file or a concern, or reached through a lambda callback, counts as an action, and so does an action another action calls. | high |
|
|
71
71
|
| <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
|
|
72
72
|
| <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
|
|
73
73
|
| <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
|
data/docs/manual/06-baseline.md
CHANGED
|
@@ -77,7 +77,9 @@ a diagnostic another layer has already suppressed.
|
|
|
77
77
|
| `rigor baseline drift` | Show how buckets have moved — `--only=over` for buckets that grew, `reducible` for ones you have already improved past, `cleared` for empty ones. |
|
|
78
78
|
| `rigor baseline prune` | Drop buckets that match nothing any more. `--dry-run` previews. |
|
|
79
79
|
|
|
80
|
-
`generate` and `regenerate` analyse with your loaded configuration unchanged except that `baseline:` is off, so options such as `bleeding_edge`, `target_ruby` or `severity_overrides` shape the recorded set exactly as they shape `rigor check`.
|
|
80
|
+
`generate` and `regenerate` analyse with your loaded configuration unchanged except that `baseline:` is off, so options such as `bleeding_edge`, `target_ruby` or `severity_overrides` shape the recorded set exactly as they shape `rigor check`. They analyse the configured `paths:` only: a positional path argument is rejected with a usage error (exit `64`), since a baseline over a subset of files would silently drop the entries of every other file. The other three subcommands reject a positional path the same way.
|
|
81
|
+
|
|
82
|
+
The summary counts only the diagnostics the written buckets cover. A diagnostic with no rule — a **parse error** in a file Rigor could not parse, a missing `paths:` entry, or an internal error — or no path cannot be keyed, so it never enters the baseline. The command prints a second line saying how many, why, and which files. The exit status stays 0, but the next `rigor check` with `baseline:` wired still reports them and exits non-zero until you fix those files, exclude them, or remove a bad `paths:` entry.
|
|
81
83
|
|
|
82
84
|
`generate` and `regenerate` take `--match-mode=rule` (the
|
|
83
85
|
default — one bucket per file × rule) or `--match-mode=message`
|
|
@@ -103,11 +105,11 @@ snapshot in the repository and compares against it explicitly:
|
|
|
103
105
|
|
|
104
106
|
```sh
|
|
105
107
|
# Once: capture the current diagnostic surface.
|
|
106
|
-
rigor check --format=json > rigor.
|
|
107
|
-
git add rigor.
|
|
108
|
+
rigor check --format=json > rigor-check.json
|
|
109
|
+
git add rigor-check.json
|
|
108
110
|
|
|
109
111
|
# Per PR: compare against the committed snapshot.
|
|
110
|
-
rigor diff rigor.
|
|
112
|
+
rigor diff rigor-check.json
|
|
111
113
|
```
|
|
112
114
|
|
|
113
115
|
[`rigor diff`](02-cli-reference.md#rigor-diff) prints a `+ NEW`
|
|
@@ -25,6 +25,7 @@ design + capability matrix lives in
|
|
|
25
25
|
| `textDocument/documentSymbol` | Outline tree from Prism AST: `class` / `module` / `def` with nesting. |
|
|
26
26
|
| `workspace/didChangeWatchedFiles` | Invalidates the per-session `Environment` + `Cache::Store` cache so saved files repropagate. |
|
|
27
27
|
| `workspace/didChangeConfiguration` | Same — re-reads `.rigor.yml` / `Gemfile.lock` etc. |
|
|
28
|
+
| `window/showMessage` | One warning per [configuration warning](03-configuration.md) — the lines `rigor check` prints on STDERR, such as a key that has no effect or a `disable:` token that names no rule. Sent on `initialized`, and again after a save only when re-reading `.rigor.yml` changed the set of warnings. |
|
|
28
29
|
|
|
29
30
|
## Prerequisites
|
|
30
31
|
|
|
@@ -51,8 +52,10 @@ rigor lsp [--transport=stdio] [--log=PATH] [--config=PATH]
|
|
|
51
52
|
|
|
52
53
|
- `--transport=stdio` — default; only value accepted in v1. TCP /
|
|
53
54
|
Unix-socket transports are queued.
|
|
54
|
-
- `--log=PATH` — wire log
|
|
55
|
-
server
|
|
55
|
+
- `--log=PATH` — append the wire log (each message in and out, one
|
|
56
|
+
line each) and the server's debug output from the start of the loop to
|
|
57
|
+
a file. Output before the loop starts still goes to stderr. Without
|
|
58
|
+
it, no wire log is written and server-side logs go to stderr.
|
|
56
59
|
- `--config=PATH` — mirrors `rigor check --config=PATH`. Without
|
|
57
60
|
it, `Configuration.discover` walks `.rigor.yml` / `.rigor.dist.yml`
|
|
58
61
|
from the project root.
|
|
@@ -194,6 +197,14 @@ For a legacy bundler-based install, swap the connection list to
|
|
|
194
197
|
there, the LSP should too. If `rigor check` fails, fix that
|
|
195
198
|
first.
|
|
196
199
|
|
|
200
|
+
**A rule still fires although `.rigor.yml` turns it off.**
|
|
201
|
+
|
|
202
|
+
- Look for a `rigor:` warning the server showed when it started (or
|
|
203
|
+
after the save that changed `.rigor.yml`): a misspelt key such as
|
|
204
|
+
`undefined-method: "off"` at the top level, or a `disable:` token
|
|
205
|
+
that names no rule, has no effect, and the server says so once.
|
|
206
|
+
The same warnings appear on STDERR from `rigor check`.
|
|
207
|
+
|
|
197
208
|
**Completion popup is empty.**
|
|
198
209
|
|
|
199
210
|
- Completion only fires on a node with a known type. Receivers
|
|
@@ -66,7 +66,9 @@ rigor mcp [--transport=stdio] [--config=PATH]
|
|
|
66
66
|
transport is queued for v2.
|
|
67
67
|
- `--config=PATH` — session-level default config path. Individual tool
|
|
68
68
|
calls may supply their own `config` argument, which takes precedence.
|
|
69
|
-
|
|
69
|
+
A path that names no file is an error, not a silent switch to the
|
|
70
|
+
defaults, for the session default and for a per-call `config` alike:
|
|
71
|
+
the call returns `isError: true`. Without either, `Configuration.discover` walks `.rigor.yml` /
|
|
70
72
|
`.rigor.dist.yml` from the working directory.
|
|
71
73
|
|
|
72
74
|
Exit codes: 0 (clean shutdown — stdin EOF), 64 (unknown
|
|
@@ -191,6 +193,28 @@ All messages except `notifications/*` require a response.
|
|
|
191
193
|
|
|
192
194
|
## Tool reference
|
|
193
195
|
|
|
196
|
+
Every tool that takes a `config` argument reports the configuration's
|
|
197
|
+
warnings — the ones `rigor check` prints on STDERR, such as a key that
|
|
198
|
+
has no effect or a `disable:` token that names no rule. `rigor_check`
|
|
199
|
+
carries them in its own document; every other such tool appends a
|
|
200
|
+
second `text` content block holding one JSON object, after the tool's
|
|
201
|
+
own output, which is left unchanged:
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{"config_warnings": [{"kind": "unknown_key", "message": "`undefined-method` is not a recognized configuration key; it has no effect.", "key": "undefined-method", "suggestion": null}]}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Each entry has the shape of `rigor check --format json`'s
|
|
208
|
+
`config_warnings` entries. There is no such block when the
|
|
209
|
+
configuration has no warnings, or when it fails to load (the tool
|
|
210
|
+
reports that error itself).
|
|
211
|
+
|
|
212
|
+
`rigor_type_of` and `rigor_explain` print JSON whose top-level shape changes
|
|
213
|
+
in the next minor release (`{ "schema_version": 1, "results" | "rules" |
|
|
214
|
+
"skip_reasons": [...] }`). Until then, a successful call carries a final
|
|
215
|
+
`text` content block, after any `config_warnings` block, with the one-line
|
|
216
|
+
notice the CLI prints on STDERR. The tool's own document is left unchanged.
|
|
217
|
+
|
|
194
218
|
### rigor_check
|
|
195
219
|
|
|
196
220
|
Analyze one or more Ruby files or directories for type errors,
|
|
@@ -220,9 +244,16 @@ undefined methods, arity mismatches, and nil-receiver risks.
|
|
|
220
244
|
}
|
|
221
245
|
```
|
|
222
246
|
|
|
247
|
+
When `.rigor.yml` has [configuration warnings](03-configuration.md)
|
|
248
|
+
(a key or a `disable:` token that has no effect), the document carries
|
|
249
|
+
them under `config_warnings`, as `rigor check --format json` does.
|
|
250
|
+
|
|
223
251
|
`isError` is `false` when the run completes normally, even if
|
|
224
|
-
diagnostics were found. `isError: true` signals a
|
|
225
|
-
|
|
252
|
+
diagnostics were found. `isError: true` signals a usage or runtime
|
|
253
|
+
failure: an unreadable `config`, or a `rigor_coverage` call whose
|
|
254
|
+
`paths` leave nothing to measure. A missing path given to `rigor_check`
|
|
255
|
+
is not one of these: it is reported in the diagnostics, and the call
|
|
256
|
+
returns `isError: false`.
|
|
226
257
|
|
|
227
258
|
**Typical agent use:** call `rigor_check` on the files an agent is
|
|
228
259
|
about to edit, record the baseline, apply the edit, call again, diff
|
|
@@ -417,6 +448,12 @@ The key is absent under the default `untyped` policy.
|
|
|
417
448
|
Classifications: `new-file`, `new-method`, `tighter-return`,
|
|
418
449
|
`equivalent`, `skipped`.
|
|
419
450
|
|
|
451
|
+
A `paths` entry that is neither a directory nor a `.rb` file is reported
|
|
452
|
+
under a top-level `diagnostics` array, in the shape `rigor_check` uses
|
|
453
|
+
(`"message": "no such file or directory"`). It is a warning beside
|
|
454
|
+
paths that were read, and an error when none was. An empty `candidates`
|
|
455
|
+
list with no `diagnostics` means nothing was found to generate.
|
|
456
|
+
|
|
420
457
|
**Typical agent use:** call `rigor_sig_gen` to see which methods have
|
|
421
458
|
precise enough returns to be worth writing into `sig/`, then generate
|
|
422
459
|
and insert the RBS. The agent should NOT write signatures
|
|
@@ -476,6 +513,13 @@ as `Constant` / `Nominal` / shaped / refined (precise) versus
|
|
|
476
513
|
|
|
477
514
|
**Returns:** JSON — the same as `rigor coverage --format json`.
|
|
478
515
|
|
|
516
|
+
A `paths` entry that is missing, or is not a Ruby file, is reported as
|
|
517
|
+
`rigor coverage` reports it. Beside files that are measured it is skipped. The result
|
|
518
|
+
then carries a second text block with the CLI's stderr, unfiltered: path
|
|
519
|
+
warnings such as `missing.rb:1:1: warning: no such file or directory (skipped)`,
|
|
520
|
+
and any other warnings. When no entry holds a Ruby file, the call is
|
|
521
|
+
`isError: true` and the block says so.
|
|
522
|
+
|
|
479
523
|
```json
|
|
480
524
|
{
|
|
481
525
|
"summary": {
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -185,7 +185,15 @@ exactly which files an edit can affect. A continuous-integration
|
|
|
185
185
|
gate, `rigor check --verify-incremental`, asserts this on every
|
|
186
186
|
build: it runs the incremental analyzer and a full analysis and
|
|
187
187
|
fails if they disagree on a single diagnostic, or list the same
|
|
188
|
-
diagnostics in a different order.
|
|
188
|
+
diagnostics in a different order. That gate re-analyses an
|
|
189
|
+
unchanged tree, so it cannot see an edit that fails to re-check a
|
|
190
|
+
file it affects. `--verify-incremental-edits=N` also edits `N`
|
|
191
|
+
files one at a time, in memory and not on disk, and compares each
|
|
192
|
+
warm recheck with a full analysis of the edited tree. Each file is
|
|
193
|
+
edited up to four times: once emptied, once reduced to its
|
|
194
|
+
`class` / `module` declarations with every body dropped, and up
|
|
195
|
+
to twice with one statement of a `class` / `module` body removed
|
|
196
|
+
and the rest kept. It replays only removals, never an addition.
|
|
189
197
|
|
|
190
198
|
Rigor is also precise about *what* an edit changed. Re-checking a
|
|
191
199
|
file that many others depend on — a base class, a widely-used
|
|
@@ -77,6 +77,36 @@ plugins:
|
|
|
77
77
|
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
78
78
|
regenerate`.
|
|
79
79
|
|
|
80
|
+
## Instance variable writes across actions
|
|
81
|
+
|
|
82
|
+
The plugin tells the engine that a mailer runs one action per instance
|
|
83
|
+
([ADR-125](../../adr/125-per-invocation-classes.md)). Its manifest lists
|
|
84
|
+
`ActionMailer::Base` in `per_invocation_classes:`, and the callback macros
|
|
85
|
+
(`before_action` and the rest) and `helper_method` in
|
|
86
|
+
`per_invocation_non_action_macros:`, so a method those name is not an action.
|
|
87
|
+
|
|
88
|
+
So [`def.ivar-write-mismatch`](../04-diagnostics.md#rule-def-ivar-write-mismatch)
|
|
89
|
+
stays silent when `welcome` assigns `@items = [1, 2]` and `digest` assigns
|
|
90
|
+
`@items = { a: 1 }`. It still reports two divergent writes in the same action,
|
|
91
|
+
and a divergence that involves a private or protected helper, `initialize`, or a
|
|
92
|
+
callback target or `helper_method` that a macro in the same file names.
|
|
93
|
+
|
|
94
|
+
The callback macros include `before_deliver`, `after_deliver` and
|
|
95
|
+
`around_deliver`, which run on the action's own instance.
|
|
96
|
+
|
|
97
|
+
Two shapes stay silent because their macros are not listed: a public
|
|
98
|
+
method named by `rescue_from X, with: :handler` or by `layout :method_name`
|
|
99
|
+
counts as an action, as does one a lambda callback
|
|
100
|
+
(`before_action -> { load_roles }`) reaches ([#2320](https://github.com/rigortype/rigor/issues/2320) tracks the fix).
|
|
101
|
+
|
|
102
|
+
Visibility is read in source order, as Rails builds `action_methods` from the
|
|
103
|
+
public instance methods. A method that ends up protected or private is not an
|
|
104
|
+
action: it follows a bare `protected` or `private`, is named by `private :x` or
|
|
105
|
+
`protected :x`, or is defined inline (`private def x`). `public :x` or
|
|
106
|
+
`public def x` makes it one again. So it is no longer checked for a view
|
|
107
|
+
template, and calling it (`UserMailer.helper`) reports `unknown-action` at
|
|
108
|
+
error severity, as Rails raises `NoMethodError` for it.
|
|
109
|
+
|
|
80
110
|
## Plugin internals
|
|
81
111
|
|
|
82
112
|
The mailer/concern discoverer, the cached `:mailer_index` producer,
|
|
@@ -41,6 +41,36 @@ no-op rather than erroring.
|
|
|
41
41
|
| `plugin.actionpack.permit-call` | info | a `params.require(:m).permit(:key, …)` chain resolved to a known model; keys matched against its columns |
|
|
42
42
|
| `plugin.actionpack.unknown-permit-key` | error | a literal `permit(:key)` is a near-miss (edit distance ≤ 2) of a real column but not one — a likely typo (with a did-you-mean). A key nothing like any column (a legitimate virtual attribute) does not fire |
|
|
43
43
|
|
|
44
|
+
The plugin also tells the engine that a controller runs one action
|
|
45
|
+
per instance ([ADR-125](../../adr/125-per-invocation-classes.md)).
|
|
46
|
+
Its manifest lists `ActionController::Base` and
|
|
47
|
+
`ActionController::API` in `per_invocation_classes:`. It also lists
|
|
48
|
+
the callback macros (`before_action` and the rest) and
|
|
49
|
+
`helper_method` in `per_invocation_non_action_macros:`, so a method
|
|
50
|
+
those name is not an action.
|
|
51
|
+
|
|
52
|
+
So `def.ivar-write-mismatch` stays silent when `index` assigns
|
|
53
|
+
`@roles = Role.sorted.to_a` and `permissions` assigns
|
|
54
|
+
`@roles = Role.sorted`. It still reports a divergence that involves
|
|
55
|
+
a private or protected helper, `initialize`, or two writes in the
|
|
56
|
+
same action. It also still reports one that involves a callback
|
|
57
|
+
target or `helper_method`, when the macro naming it sits in the same
|
|
58
|
+
file as the method. Actions defined in a concern module and
|
|
59
|
+
`ActionController::Metal` controllers keep reporting too.
|
|
60
|
+
|
|
61
|
+
Two shapes stay silent because their macros are not listed: a public
|
|
62
|
+
method named by `rescue_from X, with: :handler` or by `layout :method_name`
|
|
63
|
+
counts as an action, as does one a lambda callback
|
|
64
|
+
(`before_action -> { load_roles }`) reaches ([#2320](https://github.com/rigortype/rigor/issues/2320) tracks the fix).
|
|
65
|
+
|
|
66
|
+
These shapes stay silent:
|
|
67
|
+
|
|
68
|
+
- a public method named only by a macro in another file or a
|
|
69
|
+
concern, such as `ApplicationController`;
|
|
70
|
+
- a public method a lambda callback reaches
|
|
71
|
+
(`before_action -> { load_roles }`);
|
|
72
|
+
- an action that calls another action.
|
|
73
|
+
|
|
44
74
|
Filter and render resolution honours nested-module controller
|
|
45
75
|
qualification (`module Admin; class WidgetsController` resolves
|
|
46
76
|
views under `admin/widgets/…`) and silences gem-shipped parent
|
|
@@ -367,21 +397,29 @@ diff — not a policy scoped to `app/views/**/*`. See
|
|
|
367
397
|
knows what `rigor-rails-routes` published, and `permit`
|
|
368
398
|
validation only what `rigor-activerecord` published — enabling
|
|
369
399
|
those producers widens what this plugin can check.
|
|
370
|
-
- **`params[:key]`
|
|
371
|
-
types as `ActionController::Parameters`, and so does the
|
|
372
|
-
of every builder method that always returns one — `require`,
|
|
400
|
+
- **`params[:key]` is a gradual carrier.** Inside a controller,
|
|
401
|
+
`params` types as `ActionController::Parameters`, and so does the
|
|
402
|
+
result of every builder method that always returns one — `require`,
|
|
373
403
|
`permit`, `permit!`, `expect`, `slice`, `slice!`, `except`,
|
|
374
404
|
`without`, `extract!`, `merge`, `merge!`, `reverse_merge`,
|
|
375
405
|
`reverse_merge!`, `with_defaults`, `with_defaults!`, `compact`,
|
|
376
406
|
`compact_blank`, `deep_dup` — so a chain built from them keeps a
|
|
377
|
-
concrete receiver throughout. A subscript read
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
407
|
+
concrete receiver throughout. A subscript read answers
|
|
408
|
+
`Dynamic[ActionController::Parameters | nil]` (#2430): the static
|
|
409
|
+
facet is the nilable `Parameters`, so `params[:user][:name]` and
|
|
410
|
+
`params[:user].permit(:name)` keep dispatching on that facet, and
|
|
411
|
+
the `Dynamic` wrapper lets the value cross a declared boundary such
|
|
412
|
+
as a `String` parameter, as a scalar param does at runtime. The
|
|
413
|
+
facet is nilable rather than the non-nil nominal on purpose:
|
|
414
|
+
`params[:missing]` is `nil` at runtime, and a non-nil type would
|
|
415
|
+
let the flow rules fold live conditions (`if params[:q]`,
|
|
416
|
+
`url.nil?`) to a constant and report working code. Methods on the
|
|
417
|
+
carrier outside the chain (`params[:q].to_s`) answer `Dynamic[top]`,
|
|
418
|
+
because the engine dispatches a `Dynamic` receiver without projecting
|
|
419
|
+
its facet: a loss of precision, not a false positive. Methods whose
|
|
420
|
+
result depends on the call — `dig`, `fetch`, `compact!`, and the
|
|
421
|
+
block-less `select` / `reject` / `transform_keys` /
|
|
422
|
+
`transform_values` — are untyped for the same reason.
|
|
385
423
|
- **`flash[:key]` and `session[:key]` stay untyped too**, for that
|
|
386
424
|
same reason and measured the same way. Both are leaf reads that
|
|
387
425
|
return whatever was stored — or `nil` for a key that is not set.
|
|
@@ -32,6 +32,17 @@ path, no vendoring, no `signature_paths:` wiring.
|
|
|
32
32
|
> surface. When it is loaded, the auto overlay stands down so the two
|
|
33
33
|
> never double-declare.
|
|
34
34
|
|
|
35
|
+
If the project also runs `rbs collection install` for `activesupport`,
|
|
36
|
+
some methods are declared twice — `Object#acts_like?`,
|
|
37
|
+
`Object#html_safe?`, `Time#to_fs` among them. Rigor merges the two:
|
|
38
|
+
the plugin's signatures are tried first and the collection's still
|
|
39
|
+
apply, so a call either one accepts type-checks. When your own `sig/`
|
|
40
|
+
or inline RBS declares one of the plugin's methods, yours is used
|
|
41
|
+
instead and the plugin's stands down; the run reports one
|
|
42
|
+
`rbs.coverage.plugin-signature-stood-down` info row for the plugin's
|
|
43
|
+
file, naming those methods and the file whose declaration is used for
|
|
44
|
+
each.
|
|
45
|
+
|
|
35
46
|
## What it covers
|
|
36
47
|
|
|
37
48
|
Roughly the top ~40 selectors plus their close neighbours, across:
|
|
@@ -55,7 +66,15 @@ Roughly the top ~40 selectors plus their close neighbours, across:
|
|
|
55
66
|
- **Hash** — `#symbolize_keys` / `#stringify_keys` (+ deep / bang) and
|
|
56
67
|
their `#to_options` alias, `#deep_merge`, `#reverse_merge` /
|
|
57
68
|
`#with_defaults` (+ bang), `#with_indifferent_access`, `#except!`,
|
|
58
|
-
`#extract!`.
|
|
69
|
+
`#extract!`. `#symbolize_keys` / `#to_options` map the key type per
|
|
70
|
+
member, as ActiveSupport does (`key.to_sym rescue key`): a `String` or
|
|
71
|
+
`Symbol` key reads as `Symbol`, and every other key (an `Integer`, a
|
|
72
|
+
`Float`, a project class) reads as `untyped`, because a mixin or a
|
|
73
|
+
project file can give any class a `to_sym`. So
|
|
74
|
+
`Hash[String, Integer]` gives `Hash[Symbol, Integer]` and
|
|
75
|
+
`Hash[Integer, Integer]` gives `Hash[untyped, Integer]`. Without the
|
|
76
|
+
plugin, on the activesupport overlay alone, the key reads `untyped`.
|
|
77
|
+
The bang forms answer the receiver's own type.
|
|
59
78
|
- **Range** — `#overlaps?`, `#to_fs` / `#to_formatted_s`.
|
|
60
79
|
- **Enumerable** — `#index_by`, `#index_with`, `#pluck`, `#exclude?`.
|
|
61
80
|
|
|
@@ -48,6 +48,21 @@ whatever class includes the concern.
|
|
|
48
48
|
|
|
49
49
|
## Limitations
|
|
50
50
|
|
|
51
|
+
- **Needs RBS for the Devise modules.** The methods come from the
|
|
52
|
+
strategy modules' signatures (`Devise::Models::DatabaseAuthenticatable`
|
|
53
|
+
and the rest), which Rigor does not bundle and the `devise` gem does
|
|
54
|
+
not ship. Supply them through `signature_paths:` (or a
|
|
55
|
+
`rbs collection`); a strategy module with no signature contributes
|
|
56
|
+
nothing, and the model's `valid_password?` and friends stay as they
|
|
57
|
+
were. `plugins/rigor-devise/demo/sig/devise.rbs` is a minimal
|
|
58
|
+
example.
|
|
59
|
+
- **Your own definitions win.** A method the model defines itself
|
|
60
|
+
(`def`, `alias_method`, `attr_*`, `define_method`, `delegate`), or
|
|
61
|
+
that a module of your project it includes or prepends defines, is
|
|
62
|
+
left to that definition rather than typed from the Devise module.
|
|
63
|
+
This holds even for a module included before the `devise` call,
|
|
64
|
+
which Devise's module would override at runtime. Only the module's
|
|
65
|
+
own public methods are contributed.
|
|
51
66
|
- **Instance methods only.** Per-module `ClassMethods` (e.g.
|
|
52
67
|
`User.reset_password_by_token`) are not yet synthesised.
|
|
53
68
|
- **Controller helpers deferred.** `current_user` /
|
|
@@ -73,10 +73,11 @@ end
|
|
|
73
73
|
| `eq(literal)` / `eql(literal)` | meet with `Constant<literal>` (see below) |
|
|
74
74
|
| `be_a(T)` / `be_kind_of(T)` / `be_instance_of(T)` / `be_an_instance_of(T)` | meet with `T` |
|
|
75
75
|
| `be_nil` | narrow to `Constant<nil>` |
|
|
76
|
-
| `match(/regex/)` |
|
|
76
|
+
| `match(/regex/)` | keep the `String` and `Symbol` members, read an `Object`, `BasicObject` or module member as `String \| Symbol`, and remove `nil`, `true`, `false` and numeric members (unless the project defines `match`, `to_str` or `==` on them); unchanged when a member may be anything else |
|
|
77
77
|
| `not_to be_nil` / `to_not be_nil` | narrow away from `nil` |
|
|
78
78
|
| `not_to be_a(T)` / `not_to be_kind_of(T)` / `not_to be_instance_of(T)` / `not_to be_an_instance_of(T)` | narrow away from `T` |
|
|
79
79
|
| `not_to eq(literal)` / `not_to eql(literal)` (and `to_not`) | unchanged (see below) |
|
|
80
|
+
| `not_to match(/regex/)` (and `to_not`) | unchanged: a `String` that does not match passes |
|
|
80
81
|
|
|
81
82
|
Other matchers emit nothing. `be_truthy` is not narrowed, nor is
|
|
82
83
|
`expect { ... }.to raise_error(...)` (the block form), nor an
|
|
@@ -97,6 +97,13 @@ module Rigor
|
|
|
97
97
|
new(buckets, project_root: project_root)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
# The diagnostics a baseline cannot address: no rule (a Prism parse error, a path or internal error) or no path.
|
|
101
|
+
# `#filter` passes these through and `from_diagnostics` leaves them out of every bucket; this is the one
|
|
102
|
+
# definition of both.
|
|
103
|
+
def unkeyable(diagnostics)
|
|
104
|
+
diagnostics.reject { |d| d.qualified_rule && d.path }
|
|
105
|
+
end
|
|
106
|
+
|
|
100
107
|
private
|
|
101
108
|
|
|
102
109
|
def parse_loaded(raw, path:, project_root:)
|
|
@@ -138,17 +145,12 @@ module Rigor
|
|
|
138
145
|
raise LoadError, "#{path}: ignored[#{index}] `message:` is not a valid Regexp: #{e.message}"
|
|
139
146
|
end
|
|
140
147
|
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# contributes to a single bucket regardless of message.
|
|
145
|
-
# File paths are normalised to relative before keying.
|
|
148
|
+
# Groups diagnostics by [file, rule, regex_or_nil]. In message mode, each unique message gets its own
|
|
149
|
+
# bucket; in rule mode, every diagnostic for a (file, rule) pair contributes to a single bucket regardless
|
|
150
|
+
# of message. File paths are normalised to relative before keying.
|
|
146
151
|
def group_for_baseline(diagnostics, match_mode, project_root)
|
|
147
152
|
root = Pathname.new(project_root)
|
|
148
|
-
diagnostics.each_with_object({}) do |diag, into|
|
|
149
|
-
next if diag.qualified_rule.nil?
|
|
150
|
-
next if diag.path.nil?
|
|
151
|
-
|
|
153
|
+
(diagnostics - unkeyable(diagnostics)).each_with_object({}) do |diag, into|
|
|
152
154
|
rel = relative_path(diag.path, root)
|
|
153
155
|
key = case match_mode
|
|
154
156
|
when :rule
|
|
@@ -160,18 +162,18 @@ module Rigor
|
|
|
160
162
|
end
|
|
161
163
|
end
|
|
162
164
|
|
|
165
|
+
def relative_path(path, root_pathname)
|
|
166
|
+
Pathname.new(path).relative_path_from(root_pathname).to_s
|
|
167
|
+
rescue ArgumentError
|
|
168
|
+
path # different drive letter on Windows or non-child path
|
|
169
|
+
end
|
|
170
|
+
|
|
163
171
|
# Generates a Regexp source string for the baseline row. The string is `Regexp.escape`d so the YAML
|
|
164
172
|
# round-trip produces a regex that matches the literal message. Users hand-editing the row can replace
|
|
165
173
|
# the escaped form with a pattern.
|
|
166
174
|
def message_pattern_for(message)
|
|
167
175
|
Regexp.new(Regexp.escape(message.to_s))
|
|
168
176
|
end
|
|
169
|
-
|
|
170
|
-
def relative_path(path, root_pathname)
|
|
171
|
-
Pathname.new(path).relative_path_from(root_pathname).to_s
|
|
172
|
-
rescue ArgumentError
|
|
173
|
-
path # different drive letter on Windows or non-child path
|
|
174
|
-
end
|
|
175
177
|
end
|
|
176
178
|
|
|
177
179
|
class LoadError < StandardError; end
|
|
@@ -181,11 +183,8 @@ module Rigor
|
|
|
181
183
|
def initialize(buckets, project_root: Dir.pwd)
|
|
182
184
|
@project_root = Pathname.new(project_root)
|
|
183
185
|
@buckets = buckets.freeze
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
# - message-pattern rows (message_regex != nil)
|
|
187
|
-
# The matcher walks message-pattern rows first (tighter match takes precedence); diagnostics that
|
|
188
|
-
# don't match any message row fall through to the rule-ID row if one exists.
|
|
186
|
+
# Buckets grouped by (file, rule). `claim_bucket_for` splits each group into message-pattern rows, tried
|
|
187
|
+
# first as the tighter match, and the rule-ID row (message_regex == nil) as the fallback.
|
|
189
188
|
@by_pair = buckets.group_by { |b| [b.file, b.rule] }.freeze
|
|
190
189
|
freeze
|
|
191
190
|
end
|
|
@@ -216,7 +215,7 @@ module Rigor
|
|
|
216
215
|
|
|
217
216
|
# Diagnostics that lacked a rule or a path bypass the baseline entirely (the baseline can't address
|
|
218
217
|
# them).
|
|
219
|
-
unkeyable =
|
|
218
|
+
unkeyable = self.class.unkeyable(diagnostics)
|
|
220
219
|
[surfaced + unkeyable, silenced_count]
|
|
221
220
|
end
|
|
222
221
|
|
|
@@ -225,7 +224,7 @@ module Rigor
|
|
|
225
224
|
# - `:within` — `actual <= count` (silenced by the filter).
|
|
226
225
|
# - `:over` — `actual > count` (over threshold; surfaced in the regular `rigor check` output).
|
|
227
226
|
# - `:cleared` — `actual == 0` (the bucket can be pruned).
|
|
228
|
-
# - `:reducible` — `0 < actual < count` (the bucket's count can be tightened;
|
|
227
|
+
# - `:reducible` — `0 < actual < count` (the bucket's count can be tightened; `rigor baseline regenerate`
|
|
229
228
|
# handles this).
|
|
230
229
|
DriftRow = Struct.new(:bucket, :actual_count, :status, keyword_init: true) do
|
|
231
230
|
def delta
|
|
@@ -254,8 +253,8 @@ module Rigor
|
|
|
254
253
|
end
|
|
255
254
|
end
|
|
256
255
|
|
|
257
|
-
# Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune`
|
|
258
|
-
#
|
|
256
|
+
# Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune` to remove cleared
|
|
257
|
+
# buckets (`actual == 0`) from the on-disk file.
|
|
259
258
|
def without(buckets_to_drop)
|
|
260
259
|
dropset = buckets_to_drop.to_set
|
|
261
260
|
self.class.new(buckets.reject { |b| dropset.include?(b) }, project_root: @project_root)
|
|
@@ -311,9 +310,7 @@ module Rigor
|
|
|
311
310
|
# over rule-ID rows because they're more specific. A diagnostic that matches no row goes into a
|
|
312
311
|
# synthetic "no-bucket" bin keyed by (file, rule).
|
|
313
312
|
bins = {}
|
|
314
|
-
diagnostics.each do |diag|
|
|
315
|
-
next if diag.qualified_rule.nil? || diag.path.nil?
|
|
316
|
-
|
|
313
|
+
(diagnostics - self.class.unkeyable(diagnostics)).each do |diag|
|
|
317
314
|
bucket = claim_bucket_for(diag)
|
|
318
315
|
key = if bucket
|
|
319
316
|
[bucket.file, bucket.rule,
|
|
@@ -24,6 +24,20 @@ module Rigor
|
|
|
24
24
|
def display_path(path)
|
|
25
25
|
path == physical_path ? logical_path : path
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
# The file's identity for comparing two spellings of one path: its real path, resolved through its directory
|
|
29
|
+
# when the file is absent from disk (a new buffer's file), so a symlinked root or parent spells it as it spells
|
|
30
|
+
# an existing file. Two accepted limits: a missing name is compared as written, so a name that differs only in
|
|
31
|
+
# case is not matched; and a `..` in a missing path is resolved textually, not through the filesystem.
|
|
32
|
+
def self.real_path(path)
|
|
33
|
+
File.realpath(path)
|
|
34
|
+
rescue SystemCallError
|
|
35
|
+
absolute = File.absolute_path(path)
|
|
36
|
+
parent = File.dirname(absolute)
|
|
37
|
+
return absolute if parent == absolute
|
|
38
|
+
|
|
39
|
+
File.join(real_path(parent), File.basename(absolute))
|
|
40
|
+
end
|
|
27
41
|
end
|
|
28
42
|
end
|
|
29
43
|
end
|
|
@@ -23,10 +23,11 @@ module Rigor
|
|
|
23
23
|
# `BlockNode` ancestors. Mutation tracking through loop bodies is incomplete (`shift = 0; loop {
|
|
24
24
|
# shift += 7 }` keeps `shift` at `Constant<7>` per the analyzer), so a `Constant<bool>` predicate
|
|
25
25
|
# inside the loop body is suspect.
|
|
26
|
-
# - **Defensive predicate calls**: `.nil?` / `.empty?` / `.zero?` / `.any?` / `.none?` / `.all
|
|
27
|
-
# read like the user explicitly checking for a runtime condition that the type
|
|
28
|
-
# can't happen — Rigor's strict-on-returns RBS often disagrees with the user's
|
|
29
|
-
# `Module#name` returns `String` per RBS but anonymous classes really do return
|
|
26
|
+
# - **Defensive predicate calls**: `.nil?` / `.empty?` / `.zero?` / `.any?` / `.none?` / `.all?` /
|
|
27
|
+
# `.respond_to?`. These read like the user explicitly checking for a runtime condition that the type
|
|
28
|
+
# system already proves can't happen — Rigor's strict-on-returns RBS often disagrees with the user's
|
|
29
|
+
# defensive check (e.g. `Module#name` returns `String` per RBS but anonymous classes really do return
|
|
30
|
+
# `nil`). Skipping
|
|
30
31
|
# these forms keeps the rule useful for genuine logic errors without faulting defensive code.
|
|
31
32
|
#
|
|
32
33
|
# Also skipped (so the literal-only `unreachable-branch` rule doesn't double-fire alongside this one):
|
|
@@ -53,8 +54,6 @@ module Rigor
|
|
|
53
54
|
NODE_CLASSES = [Prism::IfNode, Prism::UnlessNode].freeze
|
|
54
55
|
RULE_WALK_GATES = [:loop_or_block].freeze
|
|
55
56
|
|
|
56
|
-
# @return one entry per qualifying predicate. Empty when the tree carries no firing
|
|
57
|
-
# predicates.
|
|
58
57
|
def initialize(scope_index)
|
|
59
58
|
@scope_index = scope_index
|
|
60
59
|
@results = []
|
|
@@ -35,8 +35,8 @@ module Rigor
|
|
|
35
35
|
# exactly like the legacy walk.
|
|
36
36
|
NODE_CLASSES = [Prism::DefNode].freeze
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
38
|
+
# One entry per dead-assignment write, as `{def_node:, write_node:}`. Empty when the tree has no
|
|
39
|
+
# qualifying writes.
|
|
40
40
|
def initialize(scope_index)
|
|
41
41
|
@scope_index = scope_index
|
|
42
42
|
@results = []
|