rigortype 0.4.6 → 0.4.8
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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- 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 +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -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 +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- 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 +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- 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.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- 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 +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- 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 +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- 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/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- 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 +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -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 +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- 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 +26 -18
- 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 +29 -10
- 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 +92 -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 +2 -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 +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- 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 +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -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/hkt_registry_holder.rb +6 -0
- 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_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- 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 +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -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/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- 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 +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -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 +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- 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/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- 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 +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- 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/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -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 +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- 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 +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- 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/singleton_type_params.rb +104 -0
- 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 +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.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/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -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/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- 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 +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- 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/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- 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 +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- 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 +48 -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/mutation_cache.rb +4 -1
- 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 +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -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 +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- 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 +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- 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/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -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 +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- 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 +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- 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/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- 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 +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- 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 +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- 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 +11 -14
- 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 +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- 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 +8 -7
- 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-graphql/lib/rigor/plugin/graphql.rb +12 -2
- 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 +8 -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 +200 -4
- 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 +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- 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 +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- 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/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- 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/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- 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/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- 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/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -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.
|
|
@@ -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 |
|
|
@@ -144,7 +174,10 @@ is left alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
|
144
174
|
those rows are the intended keys and are never flagged, while a bare plugin
|
|
145
175
|
rule, `all`, or a `plugin.`-qualified key for an unconfigured plugin is. The same findings appear in the
|
|
146
176
|
`--format=json` payload under `config_warnings` (each tagged with a
|
|
147
|
-
`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.
|
|
148
181
|
|
|
149
182
|
### Diagnostics
|
|
150
183
|
|
|
@@ -194,12 +227,50 @@ project Ruby's gem home without running your toolchain. If `rigor check`'s
|
|
|
194
227
|
`--stats` shows gems whose RBS it could not find, point it at the bundle
|
|
195
228
|
explicitly with `bundler.bundle_path:`, or supply signatures another way:
|
|
196
229
|
`rbs collection install` (auto-discovered) or `dependencies.source_inference:`.
|
|
197
|
-
| `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`. A `source: { type: stdlib }` entry loads that stdlib library, as `libraries:` would. 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). |
|
|
198
231
|
| `rbs_collection.lockfile` | String | `nil` | Explicit `rbs_collection.lock.yaml` path. |
|
|
199
232
|
| `dependencies.source_inference` | Array | `[]` | Per-gem source-inference modes (ADR-10). |
|
|
200
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. |
|
|
201
234
|
| `dependencies.budget_overrun_strategy` | String | `"walker_cap"` | What happens to calls on a gem that hit `budget_per_gem` (ADR-10 § 5b). `walker_cap` (default) lets methods past the cap fall through to the engine's normal user-class resolution. `dependency_silence` instead resolves any call on a class from a budget-exceeded gem to `Dynamic[top]`, silencing `call.undefined-method` on that gem's unrecorded surface at the cost of weaker static checking there. |
|
|
202
235
|
|
|
236
|
+
#### Running Rigor in a gem_rbs_collection `_test/` directory
|
|
237
|
+
|
|
238
|
+
To check a signature you are writing for
|
|
239
|
+
[`ruby/gem_rbs_collection`](https://github.com/ruby/gem_rbs_collection), run
|
|
240
|
+
Rigor in the gem's `gems/<name>/<version>/_test/` directory after the
|
|
241
|
+
collection's own test setup:
|
|
242
|
+
|
|
243
|
+
1. Run `bin/test gems/<name>/<version>` once from the collection root. It
|
|
244
|
+
writes `rbs_collection.yaml` and `rbs_collection.lock.yaml` into `_test/`
|
|
245
|
+
and installs the gem and its dependencies into `_test/.gem_rbs_collection/`,
|
|
246
|
+
with your working tree linked in as a `local` source.
|
|
247
|
+
2. Put a `.rigor.yml` in `_test/`:
|
|
248
|
+
|
|
249
|
+
```yaml
|
|
250
|
+
paths: ["."]
|
|
251
|
+
signature_paths: ["."]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
This is the shape of the `Steepfile` that `bin/test` writes (`check "."`,
|
|
255
|
+
`signature "."`). Rigor finds the lockfile and loads the gem and its
|
|
256
|
+
dependencies from it, and `signature_paths: ["."]` loads the `.rbs` files in
|
|
257
|
+
`_test/` itself. Without that line, the types those test signatures declare
|
|
258
|
+
are absent and calls into them are not checked.
|
|
259
|
+
|
|
260
|
+
`signature_paths: [".."]` is also safe. It loads the gem from your working
|
|
261
|
+
tree, and Rigor skips the lockfile's copy of the same gem and version, so the
|
|
262
|
+
gem is not loaded twice. Use it when the lockfile's copy is not your working
|
|
263
|
+
tree, for example when `rbs_collection.yaml` names a `git` source instead of
|
|
264
|
+
the collection's `gems/` directory.
|
|
265
|
+
3. Run `rigor check` in `_test/`.
|
|
266
|
+
|
|
267
|
+
Rigor skips every `_`-prefixed directory inside a collected gem, such as another
|
|
268
|
+
gem's `_test/` fixtures, as `rbs` does. Rigor does not read `manifest.yaml`
|
|
269
|
+
or a gemspec to find dependencies. Without a lockfile it loads none of them and
|
|
270
|
+
reports `rbs.coverage.collection-not-installed` (`:info`), which tells you to run
|
|
271
|
+
the `bin/test` setup, or `rbs collection install` where an `rbs_collection.yaml`
|
|
272
|
+
is present.
|
|
273
|
+
|
|
203
274
|
### Execution
|
|
204
275
|
|
|
205
276
|
| Key | Type | Default | Meaning |
|
|
@@ -237,8 +308,8 @@ config with no `effects:` key at all behave as `effects: {}` — collection,
|
|
|
237
308
|
`effects.check`, and everything else on this page all turn on at their
|
|
238
309
|
defaults. It only fills an *absence*: write `effects: false` and you stay
|
|
239
310
|
opted out regardless, and any `effects:` block you do write is left exactly
|
|
240
|
-
as written. It previews what becomes the default at **v0.
|
|
241
|
-
([ADR-103](../adr/103-effect-labels.md) § WD15).
|
|
311
|
+
as written. It previews what becomes the default at **v0.5.0**
|
|
312
|
+
([ADR-103](../adr/103-effect-labels.md) § WD15, moved by § WD18).
|
|
242
313
|
|
|
243
314
|
#### Entry-point presets
|
|
244
315
|
|
|
@@ -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. 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 |
|
|
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. A write in a block whose `self` is provably another object (`Other.new.instance_eval { @x = 1 }`, `Class.new { … }`, `X.class_eval { … }`, a `class << obj` body) sets that object's variable and is not compared. 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 |
|
|
@@ -75,9 +75,10 @@ carries no `documentation_url`.
|
|
|
75
75
|
| <a id="rule-global-write-type-mismatch"></a>`global.write-type-mismatch` | A special global is assigned a literal its setter rejects, so the write raises `TypeError` every time it runs. `$/`, `$-0`, `$,` and `$\` take only a String or nil (`$/ = 1` and `$/ = /x/` report). `$;` and `$-F` take a String, a Regexp, nil, or an object with `to_str`. `$~` takes a MatchData or nil. `$0` and `$PROGRAM_NAME` take a String or an object with `to_str`, so `$0 = nil` and `$0 = :name` report. `$.` takes an Integer, a Float, or an object with `to_int`, so `$. = "3"` reports and `$. = 3r` does not. `$-i` takes a String, nil, false, or an object with `to_str`. `$stdout`, `$>` and `$stderr` take anything that responds to `write`: `$stdout = 1` reports, `$stdout = StringIO.new` does not. Ruby's setter decides, not the global's RBS type. Only a value written as a literal is judged — a number, a string, a symbol, an array, a hash or a regexp literal (interpolated or not), or `nil`, `true` or `false` — so a variable, a method call or a constant never reports, whatever its type. A literal stays silent when your program defines the method the setter asks for (`write`, `to_str`, `to_int`) or a `method_missing` / `respond_to_missing?` / `respond_to?` anywhere, in any spelling and on any class — Rigor cannot always tell which objects such a definition reaches, so it does not try — or when a top-level `include` / `extend` or an ancestor's `include` names a module Rigor has no RBS for. For `$stdout`, `$>` and `$stderr` it also stays silent where a `using` is in effect for a refinement that may add `write`. `$/`, `$-0`, `$,`, `$\` and `$~` accept only their classes, so none of that silences them. A write to `$stdin` is never checked, and a special any file aliases (`alias $stdout $out`, including a `pre_eval:` file) is exempt. `warning` under `lenient`. | high |
|
|
76
76
|
| <a id="rule-global-readonly-write"></a>`global.readonly-write` | A read-only special global is written — `$!`, `$$`, `$?`, `$<`, `$FILENAME`, `$*`, `$:` / `$LOAD_PATH` / `$-I`, `$"` / `$LOADED_FEATURES`, `$-W`, `$-p`, `$-l` or `$-a` — which raises `NameError` whatever the value. Covers `$g = value`, `$g += value` and a multiple-assignment target. `$LOAD_PATH ||= []` never writes and `$LOAD_PATH << dir` is a method call, so neither fires. A special any file aliases (`alias $! $err`) is exempt. An error in every profile. | high |
|
|
77
77
|
| <a id="rule-static-value-use-void"></a>`static.value-use.void` | A value recovered from an author-declared `-> void` return is used in value context (an assignment right-hand side, a call receiver, or a call argument). Off by default; reaches a run only through the `use-of-void-value` bleeding-edge feature (ADR-100). A bare-statement `void` call and a legitimate `top` value both stay silent. | high |
|
|
78
|
+
| <a id="rule-plugin-api-surface"></a>`plugin.api-surface` | Plugin code (inside a class that inherits `Rigor::Plugin::Base`) calls a member that a `Rigor::Plugin` interface (`_Scope`, `_Environment`, `_Services`, `_FactStore`, `_Configuration`) does not declare, on a value that interface types: a hook's `scope`, `scope.environment`, and so on. The engine object answers the call today, but it is outside the plugin API Rigor promises to keep, so call a member the interface declares (`sig/rigor/plugin/interfaces.rbs`) or a public namespace instead. Off by default; reaches a run through the `plugin-api-surface` bleeding-edge feature (ADR-128), or a `severity_overrides:` key naming `plugin.api-surface` itself (a bare `plugin` key does not reach it). Calls of members every object has (`nil?`, `respond_to?`) and values that lost the interface type (a helper parameter with no signature, a join with an untyped value) stay silent; a call a `respond_to?` guard protects still reports. | high |
|
|
78
79
|
| <a id="rule-effect-envelope-exceeded"></a>`effect.envelope-exceeded` | A method performs an effect its declared envelope does not admit — its proven effect labels (its own body plus everything it calls) are not covered by the `%a{pure}` or `%a{rigor:v1:effect …}` bound written on it or on its class. Opt-in twice over: it needs an `effects:` block in `.rigor.yml` and an envelope you wrote. Positioned at the Ruby `def`. Unproven ("and possibly more") effects never fire, and `mutate.local` is tolerated by every envelope. | high |
|
|
79
80
|
| <a id="rule-effect-liskov-widened"></a>`effect.liskov-widened` | An override escapes the envelope written on the method it overrides. A `PgRepo` is usable wherever a `Repo` is, so a `%a{rigor:v1:effect io.db}` on `Repo#find` binds `PgRepo#find` too: an implementation may be purer than the bound it inherits, never less pure. Either what the override *does* exceeds the inherited bound, or the envelope the override *declares for itself* is wider than it. Both sides must be authored — nothing fires unless someone wrote an envelope on the ancestor — and only subclassing counts, not `include`. Positioned at the override's `def`. Needs an `effects:` block. | high |
|
|
80
|
-
| <a id="rule-effect-unknown-label"></a>`effect.unknown-label` | An effect declaration names a label the registry does not know — a typo in an envelope (`%a{rigor:v1:effect io.bd}`), or a member of `effects.tolerated:`. The whole tag then reads as unbounded, so the declaration quietly stops doing anything; this says so. Positioned at the declaration: the `.rbs` line, the `.rb` line for an rbs-inline annotation, or `.rigor.yml` for a config value. `# rigor:disable` comments are not read out of `.rbs` or `.rigor.yml`, so use `disable:` or the baseline there. Only fires where the spelling is evidently meant to be a label (close to a known one, next to a known one, dotted, or retired) — a word nothing resembles stays silent, because you may be opening your own vocabulary. Needs an `effects:` block. | high |
|
|
81
|
+
| <a id="rule-effect-unknown-label"></a>`effect.unknown-label` | An effect declaration names a label the registry does not know — a typo in an envelope (`%a{rigor:v1:effect io.bd}`), or a member of `effects.tolerated:`. The whole tag then reads as unbounded, so the declaration quietly stops doing anything; this says so. Positioned at the declaration: the `.rbs` line, the `.rb` line for an rbs-inline annotation, or the loaded configuration file (`.rigor.yml` when none was loaded) for a config value. `# rigor:disable` comments are not read out of `.rbs` or `.rigor.yml`, so use `disable:` or the baseline there. Only fires where the spelling is evidently meant to be a label (close to a known one, next to a known one, dotted, or retired) — a word nothing resembles stays silent, because you may be opening your own vocabulary. Needs an `effects:` block. | high |
|
|
81
82
|
| <a id="rule-effect-annotations-unchecked"></a>`effect.annotations-unchecked` | Your signatures carry `%a{pure}` / `%a{rigor:v1:effect …}` but `.rigor.yml` has no `effects:` block, so nothing checks them. One `:info` per run, positioned at the first annotation. An annotation never turns effect collection on by itself — that would make one line in one file more expensive for every run — so this is how it tells you instead. Add `effects: {}` to opt in, or `disable:` it to keep the annotations documentary. Reads both annotation lanes — `sig/*.rbs` and rbs-inline comments — on every run, a warm `--incremental` with nothing changed included. | — |
|
|
82
83
|
| <a id="rule-rbs-contradicting-signature"></a>`rbs.contradicting-signature` | A method is declared both in your `sig/` and by an inline `# @rbs` / `#:` annotation, and the two provably contradict — no value or call satisfies both: in the return or a parameter every call must pass, the two types share no value (`::String` against `::Integer`). Only absolutely written Ruby core or stdlib classes count: a module such as `Comparable`, a class your project declares, a gem's class, or a relative name like plain `String` never does, or two keyword-free declarations accept positional counts that cannot meet, or one requires a keyword the other cannot take in any form. Overloads are paired by correspondence, not order. Also fires when a member-level `%a{rigor:v1:return: …}` / `%a{rigor:v1:param: …}` refinement shares no value with the type its own member declares. Positioned at the `.rbs` member; Rigor reads that declaration. A stale generated signature is the usual cause: regenerate it, or fix the annotation. Two declarations where one refines the other merge to the more precise one without a word, and a pair Rigor cannot rank drops the inline side with a `source-rbs-annotation-not-honoured` `:info` instead ([Precedence](plugins/rigor-rbs-inline.md#precedence)). An error in every profile. | high |
|
|
83
84
|
| <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
|
|
@@ -130,7 +131,7 @@ re-stamps it for the run. Three profiles, set with the
|
|
|
130
131
|
| --- | --- |
|
|
131
132
|
| `lenient` | Only proven diagnostics are errors; uncertain ones drop to `warning` / `info`. For incremental adoption on legacy code. |
|
|
132
133
|
| `balanced` *(default)* | Most rules `error`; `dump.type` `info`; uncertain rules `warning`. |
|
|
133
|
-
| `strict` | Nearly every rule is an `error`. The exceptions: `call.self-undefined-method
|
|
134
|
+
| `strict` | Nearly every rule is an `error`. The exceptions: `call.self-undefined-method`, `static.value-use.void` and `plugin.api-surface` stay `off` (all three opt-in only), `flow.unreachable-clause` stays `warning` pending its false-positive gate, and the three `suppression.*` rules stay `warning` — a stale suppression comment is worth telling you about, but it is not a reason to fail a build. CI-friendly. |
|
|
134
135
|
|
|
135
136
|
Under `balanced`, the rules that do **not** emit as `error`
|
|
136
137
|
are:
|
|
@@ -139,7 +140,7 @@ are:
|
|
|
139
140
|
| --- | --- |
|
|
140
141
|
| `warning` | `call.unresolved-toplevel`, `def.ivar-write-mismatch`, `def.return-type-mismatch`, `def.override-visibility-reduced`, `def.override-return-widened`, `def.override-param-narrowed`, `flow.unreachable-branch`, `flow.always-truthy-condition`, `flow.dead-assignment`, `flow.duplicate-hash-key`, `flow.return-in-ensure`, `flow.shadowed-rescue-clause`, `suppression.unknown-rule`, `suppression.empty`, `suppression.unknown-marker`, `effect.envelope-exceeded`, `effect.liskov-widened` |
|
|
141
142
|
| `info` | `flow.unreachable-clause`, `dump.type`, `effect.unknown-label`, `effect.annotations-unchecked` |
|
|
142
|
-
| `off` | `call.self-undefined-method`, `static.value-use.void` |
|
|
143
|
+
| `off` | `call.self-undefined-method`, `static.value-use.void`, `plugin.api-surface` |
|
|
143
144
|
|
|
144
145
|
Everything else emits as `error`. For one rule under all three
|
|
145
146
|
profiles, `rigor explain <rule>` prints `Authored severity:` and
|
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`
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -80,6 +80,17 @@ is not repeated here.)
|
|
|
80
80
|
minimal hand-RBS residual, under a double gate (the site becomes
|
|
81
81
|
protected *and* `rigor check` gains no new diagnostic). See
|
|
82
82
|
[Type-protection coverage](15-type-protection-coverage.md).
|
|
83
|
+
- **`rigor-rbs-contract`** — audits, repairs, and extends RBS
|
|
84
|
+
declarations as *contracts*: a gem's own `sig/`, an application's
|
|
85
|
+
`sig/`, or an entry contributed to `ruby/gem_rbs_collection`. It finds
|
|
86
|
+
where a declaration and the code disagree (`rigor check`'s
|
|
87
|
+
`def.return-type-mismatch`, `rigor sig-gen --diff`, use-site checks,
|
|
88
|
+
the library's documentation), changes a declaration only on cited
|
|
89
|
+
evidence, keeps a declared `void` unless the library documents a
|
|
90
|
+
value, and proves each change with a use-site test. On your own code
|
|
91
|
+
it asks you the contract questions the evidence leaves open; on a gem
|
|
92
|
+
you do not own, the gem's documentation decides. See
|
|
93
|
+
[ADR-131](../adr/131-declarations-are-contracts.md).
|
|
83
94
|
- **`rigor-baseline-reduce`** — works an existing `.rigor-baseline.yml`
|
|
84
95
|
down rule by rule. It prioritises with `rigor triage`, classifies each
|
|
85
96
|
site as a real bug / safe stylistic finding / false positive, and
|
|
@@ -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
|
|
|
@@ -34,6 +35,14 @@ install channels in [Installing Rigor](01-installation.md) provide it;
|
|
|
34
35
|
`mise` is the recommended one because its shims make `rigor` available
|
|
35
36
|
to GUI-launched editors that do not inherit your shell environment.
|
|
36
37
|
|
|
38
|
+
The [VS Code extension](../../editors/vscode/README.md) also finds a
|
|
39
|
+
mise or asdf shim when `rigor` is not on `PATH`. When it finds no
|
|
40
|
+
`rigor` at all, it offers to install a **managed Rigor** into its own
|
|
41
|
+
storage with a Ruby 4.0 already on the machine, and uses it as its
|
|
42
|
+
last resort; a project that pins `rigortype` in `Gemfile.lock`,
|
|
43
|
+
`mise.toml` or `.tool-versions` is never served by it. See the
|
|
44
|
+
extension README's *Server discovery*.
|
|
45
|
+
|
|
37
46
|
Do **not** add `rigortype` to your project's `Gemfile`. Rigor is a
|
|
38
47
|
tool, not a library — installing it standalone keeps its Ruby version
|
|
39
48
|
and its dependencies out of your application's resolution. See
|
|
@@ -51,8 +60,10 @@ rigor lsp [--transport=stdio] [--log=PATH] [--config=PATH]
|
|
|
51
60
|
|
|
52
61
|
- `--transport=stdio` — default; only value accepted in v1. TCP /
|
|
53
62
|
Unix-socket transports are queued.
|
|
54
|
-
- `--log=PATH` — wire log
|
|
55
|
-
server
|
|
63
|
+
- `--log=PATH` — append the wire log (each message in and out, one
|
|
64
|
+
line each) and the server's debug output from the start of the loop to
|
|
65
|
+
a file. Output before the loop starts still goes to stderr. Without
|
|
66
|
+
it, no wire log is written and server-side logs go to stderr.
|
|
56
67
|
- `--config=PATH` — mirrors `rigor check --config=PATH`. Without
|
|
57
68
|
it, `Configuration.discover` walks `.rigor.yml` / `.rigor.dist.yml`
|
|
58
69
|
from the project root.
|
|
@@ -194,6 +205,14 @@ For a legacy bundler-based install, swap the connection list to
|
|
|
194
205
|
there, the LSP should too. If `rigor check` fails, fix that
|
|
195
206
|
first.
|
|
196
207
|
|
|
208
|
+
**A rule still fires although `.rigor.yml` turns it off.**
|
|
209
|
+
|
|
210
|
+
- Look for a `rigor:` warning the server showed when it started (or
|
|
211
|
+
after the save that changed `.rigor.yml`): a misspelt key such as
|
|
212
|
+
`undefined-method: "off"` at the top level, or a `disable:` token
|
|
213
|
+
that names no rule, has no effect, and the server says so once.
|
|
214
|
+
The same warnings appear on STDERR from `rigor check`.
|
|
215
|
+
|
|
197
216
|
**Completion popup is empty.**
|
|
198
217
|
|
|
199
218
|
- Completion only fires on a node with a known type. Receivers
|
|
@@ -193,6 +193,28 @@ All messages except `notifications/*` require a response.
|
|
|
193
193
|
|
|
194
194
|
## Tool reference
|
|
195
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
|
+
|
|
196
218
|
### rigor_check
|
|
197
219
|
|
|
198
220
|
Analyze one or more Ruby files or directories for type errors,
|
|
@@ -222,9 +244,16 @@ undefined methods, arity mismatches, and nil-receiver risks.
|
|
|
222
244
|
}
|
|
223
245
|
```
|
|
224
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
|
+
|
|
225
251
|
`isError` is `false` when the run completes normally, even if
|
|
226
|
-
diagnostics were found. `isError: true` signals a
|
|
227
|
-
|
|
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`.
|
|
228
257
|
|
|
229
258
|
**Typical agent use:** call `rigor_check` on the files an agent is
|
|
230
259
|
about to edit, record the baseline, apply the edit, call again, diff
|
|
@@ -484,6 +513,13 @@ as `Constant` / `Nominal` / shaped / refined (precise) versus
|
|
|
484
513
|
|
|
485
514
|
**Returns:** JSON — the same as `rigor coverage --format json`.
|
|
486
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
|
+
|
|
487
523
|
```json
|
|
488
524
|
{
|
|
489
525
|
"summary": {
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -190,9 +190,10 @@ unchanged tree, so it cannot see an edit that fails to re-check a
|
|
|
190
190
|
file it affects. `--verify-incremental-edits=N` also edits `N`
|
|
191
191
|
files one at a time, in memory and not on disk, and compares each
|
|
192
192
|
warm recheck with a full analysis of the edited tree. Each file is
|
|
193
|
-
edited
|
|
194
|
-
`module` declarations with every body dropped
|
|
195
|
-
|
|
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.
|
|
196
197
|
|
|
197
198
|
Rigor is also precise about *what* an edit changed. Re-checking a
|
|
198
199
|
file that many others depend on — a base class, a widely-used
|
|
@@ -61,7 +61,7 @@ mise use gem:rigortype
|
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
`mise use` records the chosen versions in a `mise.toml` in the
|
|
64
|
-
current directory and installs them in one step. Verify:
|
|
64
|
+
current directory and installs them in one step. Rails projects with Steep or `rbs_rails` list `rbs` in the bundle, and the `rbs` that `gem:rigortype` puts on `PATH` can shadow it; see the [install guide's note on `rbs` shadowing](../install.md#case-a--mise-is-available-recommended) before running `bundle exec rbs`. Verify:
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
67
|
rigor --version
|
|
@@ -108,10 +108,9 @@ annotations and no block gets told so, once per run:
|
|
|
108
108
|
sig/slug.rbs:2:1: info: Effect annotations (`%a{pure}` / `%a{rigor:v1:effect …}`) are present in your project's signatures, but `.rigor.yml` carries no `effects:` block, so effect collection never runs and nothing checks them — they are documentation, not a contract. Add `effects: {}` to have Rigor prove what your methods do and check these bounds against it (ADR-103); an annotation alone never turns collection on, because that would make one line in one signature file more expensive for every run of the project.
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
From **v0.
|
|
112
|
-
all behaves as `effects: {}`, and `effects: false` is how you opt out.
|
|
113
|
-
|
|
114
|
-
`bleeding_edge: [effects-on-by-default]`.
|
|
111
|
+
From **v0.5.0** collection is the default: a config with no `effects:` key at
|
|
112
|
+
all behaves as `effects: {}`, and `effects: false` is how you opt out. Until
|
|
113
|
+
then you can preview that with `bleeding_edge: [effects-on-by-default]`.
|
|
115
114
|
|
|
116
115
|
One exception to all of this: `rigor effects` — the bare report — runs under an
|
|
117
116
|
implicit empty block whether or not you configured one, so you can look before
|
|
@@ -397,21 +397,38 @@ diff — not a policy scoped to `app/views/**/*`. See
|
|
|
397
397
|
knows what `rigor-rails-routes` published, and `permit`
|
|
398
398
|
validation only what `rigor-activerecord` published — enabling
|
|
399
399
|
those producers widens what this plugin can check.
|
|
400
|
-
- **`params[:key]`
|
|
401
|
-
types as `ActionController::Parameters`, and so does the
|
|
402
|
-
of every builder method that always returns one — `
|
|
403
|
-
`permit`, `
|
|
404
|
-
`
|
|
405
|
-
`
|
|
406
|
-
`
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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 — `permit`,
|
|
403
|
+
`permit!`, `slice`, `slice!`, `except`, `without`, `extract!`,
|
|
404
|
+
`merge`, `merge!`, `reverse_merge`, `reverse_merge!`,
|
|
405
|
+
`with_defaults`, `with_defaults!`, `compact`, `compact_blank`,
|
|
406
|
+
`deep_dup` — when the receiver is `params` itself. A carrier
|
|
407
|
+
(`params[:tags]`) may hold an Array or a String at runtime, so a
|
|
408
|
+
builder call on one answers the carrier too, except `permit` and
|
|
409
|
+
`permit!`, which no core class defines and which answer `Parameters`.
|
|
410
|
+
`require`
|
|
411
|
+
and `expect` are different: Rails returns the value itself, which
|
|
412
|
+
can be a scalar (`params.require(:id).to_sym`), so they answer the
|
|
413
|
+
same `Dynamic[ActionController::Parameters]` carrier (#2947). A
|
|
414
|
+
`permit` or `permit!` after one (`params.require(:user).permit(:name)`)
|
|
415
|
+
still types as `Parameters`; other chain methods off a carrier stay
|
|
416
|
+
the carrier. A subscript read answers
|
|
417
|
+
`Dynamic[ActionController::Parameters | nil]` (#2430): the static
|
|
418
|
+
facet is the nilable `Parameters`, so `params[:user][:name]` and
|
|
419
|
+
`params[:user].permit(:name)` keep dispatching on that facet, and
|
|
420
|
+
the `Dynamic` wrapper lets the value cross a declared boundary such
|
|
421
|
+
as a `String` parameter, as a scalar param does at runtime. The
|
|
422
|
+
facet is nilable rather than the non-nil nominal on purpose:
|
|
423
|
+
`params[:missing]` is `nil` at runtime, and a non-nil type would
|
|
424
|
+
let the flow rules fold live conditions (`if params[:q]`,
|
|
425
|
+
`url.nil?`) to a constant and report working code. Methods on the
|
|
426
|
+
carrier outside the chain (`params[:q].to_s`) answer `Dynamic[top]`,
|
|
427
|
+
because the engine dispatches a `Dynamic` receiver without projecting
|
|
428
|
+
its facet: a loss of precision, not a false positive. Methods whose
|
|
429
|
+
result depends on the call — `dig`, `fetch`, `compact!`, and the
|
|
430
|
+
block-less `select` / `reject` / `transform_keys` /
|
|
431
|
+
`transform_values` — are untyped for the same reason.
|
|
415
432
|
- **`flash[:key]` and `session[:key]` stay untyped too**, for that
|
|
416
433
|
same reason and measured the same way. Both are leaf reads that
|
|
417
434
|
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:
|
|
@@ -42,6 +53,9 @@ Roughly the top ~40 selectors plus their close neighbours, across:
|
|
|
42
53
|
`TrueClass` / `FalseClass`).
|
|
43
54
|
- **Integer / Float** — Duration multipliers (`#days`, `#hours`,
|
|
44
55
|
`#minutes`, …) and Bytes multipliers (`#megabytes`, `#gigabytes`, …).
|
|
56
|
+
- **Integer / Float / BigDecimal** — `#to_fs` / `#to_formatted_s`
|
|
57
|
+
(`NumericWithFormat`: a Symbol format, an Integer radix or a String
|
|
58
|
+
format, and the no-argument `to_s` form).
|
|
45
59
|
- **String** — inflections (`#underscore`, `#camelize`, `#classify`,
|
|
46
60
|
`#constantize`, `#pluralize`, …), filters (`#squish`, `#truncate`),
|
|
47
61
|
`#html_safe`, `#starts_with?` / `#ends_with?` (also on **Symbol**),
|
|
@@ -55,7 +69,15 @@ Roughly the top ~40 selectors plus their close neighbours, across:
|
|
|
55
69
|
- **Hash** — `#symbolize_keys` / `#stringify_keys` (+ deep / bang) and
|
|
56
70
|
their `#to_options` alias, `#deep_merge`, `#reverse_merge` /
|
|
57
71
|
`#with_defaults` (+ bang), `#with_indifferent_access`, `#except!`,
|
|
58
|
-
`#extract!`.
|
|
72
|
+
`#extract!`. `#symbolize_keys` / `#to_options` map the key type per
|
|
73
|
+
member, as ActiveSupport does (`key.to_sym rescue key`): a `String` or
|
|
74
|
+
`Symbol` key reads as `Symbol`, and every other key (an `Integer`, a
|
|
75
|
+
`Float`, a project class) reads as `untyped`, because a mixin or a
|
|
76
|
+
project file can give any class a `to_sym`. So
|
|
77
|
+
`Hash[String, Integer]` gives `Hash[Symbol, Integer]` and
|
|
78
|
+
`Hash[Integer, Integer]` gives `Hash[untyped, Integer]`. Without the
|
|
79
|
+
plugin, on the activesupport overlay alone, the key reads `untyped`.
|
|
80
|
+
The bang forms answer the receiver's own type.
|
|
59
81
|
- **Range** — `#overlaps?`, `#to_fs` / `#to_formatted_s`.
|
|
60
82
|
- **Enumerable** — `#index_by`, `#index_with`, `#pluck`, `#exclude?`.
|
|
61
83
|
|
|
@@ -61,9 +61,23 @@ The plugin also types the annotation DSL's own expressions
|
|
|
61
61
|
`T::Struct` subclass still needs the subclass's own `extend T::Sig`,
|
|
62
62
|
matching the runtime) through a bundled
|
|
63
63
|
`sorbet-runtime` RBS surface — so the sig DSL no longer reads as
|
|
64
|
-
`Dynamic[top]` on a Sorbet codebase.
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
`Dynamic[top]` on a Sorbet codebase.
|
|
65
|
+
|
|
66
|
+
On a `T::Struct`, `T::InexactStruct` or `T::ImmutableStruct`
|
|
67
|
+
subclass, `prop :name, Type` and `const :name, Type` at the top
|
|
68
|
+
level of the class body give `obj.name` the translated `Type`
|
|
69
|
+
(`T.nilable(String)` reads `String?`; a form outside the table
|
|
70
|
+
reads `Dynamic[top]`), and `prop` also answers `obj.name = v`.
|
|
71
|
+
A relative constant in `Type` resolves from the class body's
|
|
72
|
+
namespaces outward, and one that names no known class reads
|
|
73
|
+
`Dynamic[top]`.
|
|
74
|
+
A `const` has no writer, so `obj.name = v` still reports.
|
|
75
|
+
Subclasses inherit the accessors, a `def` of the same name in
|
|
76
|
+
the class body wins over the macro, and a `prop` nested under an
|
|
77
|
+
`if` or a block reads `Dynamic[top]`. A `prop` call in a class
|
|
78
|
+
outside the struct family defines nothing.
|
|
79
|
+
|
|
80
|
+
What it does not model: `include T::Props` does
|
|
67
81
|
not confer the `ClassMethods` surface (only the `< T::Struct`
|
|
68
82
|
subclass path does), and the deeper type-member machinery
|
|
69
83
|
(`T.attached_class` variance, `type_member` bounds) is not
|
|
@@ -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,
|