rigortype 0.3.8 → 0.3.9
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 +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -19,7 +19,7 @@ specification](../type-specification/README.md), the spec binds.
|
|
|
19
19
|
| What about types that may or may not match? | Gradual consistency (`~`) | The `Dynamic[T]` carrier and the trinary certainty `yes / no / maybe` |
|
|
20
20
|
| How are user types identified? | Nominal vs structural | **Nominal-first hybrid** — classes by name, plus structural facets (`interface`, `HashShape`, capability roles) |
|
|
21
21
|
| How are generics expressed? | Parametric polymorphism (System F-style, but predicative) | RBS generics `class Array[Elem]`, method generics `def map: [U] () { (Elem) -> U } -> Array[U]` |
|
|
22
|
-
| How is "x is a non-empty string" expressed? | Refinement / predicate subtyping | First-class refinement carriers (`non-empty-string`, `
|
|
22
|
+
| How is "x is a non-empty string" expressed? | Refinement / predicate subtyping | First-class refinement carriers (`non-empty-string`, `Integer[min..max]`, …) |
|
|
23
23
|
| How does `if x.is_a?(String)` change `x`'s type? | Occurrence typing / flow-sensitive narrowing | Edge-aware narrowing with trinary certainty |
|
|
24
24
|
| What about side effects? | Effect systems | The engine's effect model (mutation, exception, escape) — internal, not user-visible |
|
|
25
25
|
| Soundness or completeness? | Pick one (or neither) | **Neither in full** — Rigor optimises for no-false-positives, with a robustness-principle bias |
|
|
@@ -528,7 +528,7 @@ refinements with reserved names:
|
|
|
528
528
|
| `non-empty-string` | `s : String, s.size >= 1` | refinement on `String` |
|
|
529
529
|
| `numeric-string` | `s : String, s =~ /\A[+-]?\d+(\.\d+)?\z/` | refinement on `String` |
|
|
530
530
|
| `literal-string` | "provably built from literals" | refinement on `String` |
|
|
531
|
-
| `
|
|
531
|
+
| `Integer[min..max]` | `n : Integer, min <= n <= max` | range carrier |
|
|
532
532
|
| `non-zero-int` | `n : Integer, n != 0` | refinement on `Integer` |
|
|
533
533
|
| `positive-int` | `n : Integer, n > 0` | refinement on `Integer` |
|
|
534
534
|
| `non-empty-array[T]` | `arr : Array[T], arr.size >= 1` | refinement on `Array[T]` |
|
|
@@ -1491,7 +1491,7 @@ here so you can stop looking:
|
|
|
1491
1491
|
and Rigor does not synthesise it.
|
|
1492
1492
|
- **Full dependent types.** No `Vec[n, T]` with `n : Integer`.
|
|
1493
1493
|
Type-checking is decidable but inference is not; integer-range
|
|
1494
|
-
refinements (`
|
|
1494
|
+
refinements (`Integer[min..max]`) cover the most common practical
|
|
1495
1495
|
need without crossing the line.
|
|
1496
1496
|
- **Row polymorphism as a user-quantifiable axis.** `HashShape`
|
|
1497
1497
|
carries open-vs-closed semantics internally but does not
|
|
@@ -1581,7 +1581,7 @@ they map to the sections of this appendix:
|
|
|
1581
1581
|
matching and exhaustiveness."
|
|
1582
1582
|
- Rondon, Kawaguchi & Jhala. "Liquid Types." *PLDI 2008.* The
|
|
1583
1583
|
refinement-types-with-SMT framework that informs the
|
|
1584
|
-
`
|
|
1584
|
+
`Integer[min..max]` carrier (Rigor uses a much weaker, decidable
|
|
1585
1585
|
fragment).
|
|
1586
1586
|
- Lucassen & Gifford. "Polymorphic Effect Systems."
|
|
1587
1587
|
*POPL 1988.* Origin of effect systems.
|
|
@@ -101,7 +101,7 @@ positive, an array that is provably non-empty.
|
|
|
101
101
|
| --- | --- | --- |
|
|
102
102
|
| `non-empty-string` | `\`${string}${string}\`` (template literal trick) or branded `NonEmptyString` | Awkward in TS; Rigor produces it from `unless s.empty?` automatically. |
|
|
103
103
|
| `positive-int` | branded `PositiveInt` | TS users tend to skip the brand — Rigor narrows from `n > 0`. |
|
|
104
|
-
| `
|
|
104
|
+
| `Integer[1..9]` | union of literal types `1 \| 2 \| 3 \| ... \| 9` | Rigor's range carrier handles arbitrary bounds without exploding. |
|
|
105
105
|
| `numeric-string` | (none useful) | TS has no equivalent; Rigor narrows from regex matches against numeric patterns. |
|
|
106
106
|
| `non-empty-array[T]` | `[T, ...T[]]` (tuple-with-rest) | TS has the encoding but few APIs use it; Rigor produces it from `unless arr.empty?`. |
|
|
107
107
|
|
data/docs/llms.txt
CHANGED
|
@@ -42,6 +42,8 @@ Skill with `rigor skill <name>`. (The canonical web copy is
|
|
|
42
42
|
- `15-type-protection-coverage` — `rigor coverage --protection`.
|
|
43
43
|
- `16-rbs-extended-annotations` — the `%a{rigor:v1:…}` annotations.
|
|
44
44
|
- `17-driving-improvement` — the `rigor-next-steps`-driven improvement loop.
|
|
45
|
+
- `18-removing-dead-code` — working `rigor unused` as a campaign on an old codebase.
|
|
46
|
+
- `19-effect-labels` — `rigor effects`, the `.rigor-effects.yml` snapshot, and `%a{pure}`.
|
|
45
47
|
|
|
46
48
|
## Handbook (read with `rigor docs <name>`)
|
|
47
49
|
|
|
@@ -45,6 +45,7 @@ the `paths:` list from the configuration file.
|
|
|
45
45
|
| `--baseline=PATH` | Load a baseline file, overriding config. |
|
|
46
46
|
| `--no-baseline` | Ignore any configured baseline. |
|
|
47
47
|
| `--baseline-strict` | Fail the run on any baseline drift — a CI gate. |
|
|
48
|
+
| `--fail-on=SEVERITY` | Exit non-zero when a diagnostic at or above `SEVERITY` (`error`, the default; `warning`; or `info`) survives baseline filtering — raises the exit-status bar above the default `:error`-only reading for CI gates that want it, without changing `--format json`'s `success` / `error_count` fields. |
|
|
48
49
|
| `--treat-all-as-inline-rbs` | Force-load `rigor-rbs-inline` with `require_magic_comment: false`, so every analysed file is treated as inline-RBS without the `# rbs_inline: enabled` comment (ADR-32). |
|
|
49
50
|
| `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids. Inspect it with [`rigor show-bleedingedge`](#rigor-show-bleedingedge). |
|
|
50
51
|
| `--no-bleeding-edge` | Ignore any configured `bleeding_edge:` selection for this run (adopt none). |
|
|
@@ -52,7 +53,9 @@ the `paths:` list from the configuration file.
|
|
|
52
53
|
| `--tmp-file=PATH --instead-of=PATH` | Editor mode: analyse `PATH` using the buffer in `--tmp-file`. Both required together. Alone, only the buffer's own file produces diagnostics; add `--incremental` for whole-project scope (see below). |
|
|
53
54
|
|
|
54
55
|
Exit `0` when no error-severity diagnostics remain, `1` when
|
|
55
|
-
any are reported, `64` on a usage error.
|
|
56
|
+
any are reported, `64` on a usage error. `--fail-on` raises that
|
|
57
|
+
bar to `warning` or `info` for callers (CI gates, `make check`)
|
|
58
|
+
that want the stricter reading.
|
|
56
59
|
|
|
57
60
|
### Editor mode scope
|
|
58
61
|
|
|
@@ -135,6 +138,17 @@ truncation explicit. `--trace` records fail-soft fallbacks,
|
|
|
135
138
|
after the rows of a line table in text output. The editor-mode
|
|
136
139
|
`--tmp-file` / `--instead-of` pair is accepted as on `check`.
|
|
137
140
|
|
|
141
|
+
The four probe commands — `type-of`, `type-scan`, `trace` and
|
|
142
|
+
`annotate` — build their environment fresh on every invocation
|
|
143
|
+
and never read or write the persistent cache. That is why none
|
|
144
|
+
of them takes `--no-cache`: the flag would have nothing to skip.
|
|
145
|
+
A probe therefore types against the environment `rigor check
|
|
146
|
+
--no-cache` analyses with. The environment a cached (default)
|
|
147
|
+
`rigor check` builds is meant to be identical, and Rigor gates
|
|
148
|
+
the two builds against each other — but if you are chasing a
|
|
149
|
+
disagreement between a probe and a `check` run, comparing
|
|
150
|
+
against `rigor check --no-cache` removes that variable.
|
|
151
|
+
|
|
138
152
|
## `rigor trace`
|
|
139
153
|
|
|
140
154
|
Replay HOW the engine typed a file, step by step, as a
|
|
@@ -429,6 +443,12 @@ rigor explain [rule]
|
|
|
429
443
|
or a family wildcard (`call`, `flow`, `def`, `assert`, `dump`).
|
|
430
444
|
`--format=json` is available. Exit `64` for an unknown rule.
|
|
431
445
|
|
|
446
|
+
It also answers the `sig.skipped.*` identifiers `rigor sig-gen`
|
|
447
|
+
prints when it declines to write a signature — `rigor explain
|
|
448
|
+
sig.skipped.untyped-return` says what the skip means and what to do
|
|
449
|
+
instead. Those are not diagnostic rules, so they are rendered without
|
|
450
|
+
a severity or a suppression line.
|
|
451
|
+
|
|
432
452
|
## `rigor diff`
|
|
433
453
|
|
|
434
454
|
Compare the current diagnostics against a saved baseline JSON
|
|
@@ -481,6 +501,8 @@ not overwrite) is never a silent absence: under `--format=json`
|
|
|
481
501
|
it is a `skipped` row of the `candidates` array with its
|
|
482
502
|
`sig.skipped.*` identifier in `skip_reason`, and in text mode a
|
|
483
503
|
one-line stderr summary counts the skipped methods per reason.
|
|
504
|
+
`rigor explain sig.skipped.untyped-return` (or any of the other skip
|
|
505
|
+
identifiers) explains what the reason means and what to do about it.
|
|
484
506
|
|
|
485
507
|
## `rigor lsp`
|
|
486
508
|
|
|
@@ -577,6 +599,12 @@ see. That is why this is a separate command and never a `rigor check`
|
|
|
577
599
|
diagnostic — see
|
|
578
600
|
[ADR-102](../adr/102-unused-code-reachability-report.md).
|
|
579
601
|
|
|
602
|
+
`rigor unused` refuses `--incremental` and exits non-zero rather
|
|
603
|
+
than quietly running a full pass. Reachability is only sound over a
|
|
604
|
+
whole-project run: with files served from the incremental cache a
|
|
605
|
+
constant would be reported as unused merely because the file that
|
|
606
|
+
references it was not re-scanned. Re-run without the flag.
|
|
607
|
+
|
|
580
608
|
Reachability is computed from **roots**, not by counting references,
|
|
581
609
|
so a cluster of classes that only reference each other is still
|
|
582
610
|
reported. Roots are the declarations in files matching
|
|
@@ -1112,7 +1140,7 @@ operational knobs read the environment instead.
|
|
|
1112
1140
|
| `RIGOR_RACTOR_WORKERS=N` | Worker count for parallel analysis. Sits between the CLI flag and the config key in precedence: `--workers=N` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > `0` (sequential). |
|
|
1113
1141
|
| `RIGOR_POOL_BACKEND=ractor` | Opt back into the (off-by-default) Ractor worker pool instead of the active fork-based pool ([ADR-15](../adr/15-ractor-concurrency.md)). Only relevant with a non-zero worker count; the fork pool is the supported backend. |
|
|
1114
1142
|
| `RIGOR_LSP_POOL_MIN_BATCH=N` | Fewest buffers an [`rigor lsp`](#rigor-lsp) batch must carry before analysis is dispatched across the worker pool rather than run in-process (default `16`). Lower it if your project's per-file analysis is expensive enough that pooling pays off sooner. |
|
|
1115
|
-
| `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
|
|
1143
|
+
| `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Overrides the `plugins_isolation:` configuration key. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
|
|
1116
1144
|
| `RIGOR_STRICT_VALIDATION=1` | Force full-content cache validation for one run (the same as `cache.validation: digest`, and winning over it) — re-hash every file's content instead of trusting its stat metadata. Use it if a filesystem's timestamps or inode numbers cannot be trusted. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). |
|
|
1117
1145
|
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through any long run so short runs never pay the JIT warm-up; this variable leaves it off entirely. Diagnostics and allocations are identical either way — the effect is wall-time only. |
|
|
1118
1146
|
| `RIGOR_YJIT_DEADLINE=<seconds>` | Advanced: tune how long a run must last before deferred YJIT enables (default `5.0`). Lower it if your runs are long and you want the JIT sooner; raise it to protect short runs. Ignored when `RIGOR_DISABLE_YJIT=1` is set or YJIT is unavailable. |
|
|
@@ -1126,8 +1154,8 @@ diagnostics about Rigor's own inference cutoffs and memory — see
|
|
|
1126
1154
|
|
|
1127
1155
|
| Code | Meaning |
|
|
1128
1156
|
| --- | --- |
|
|
1129
|
-
| `0` | Success — no error-
|
|
1130
|
-
| `1` | Diagnostics found, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
|
|
1157
|
+
| `0` | Success — no diagnostic at or above the exit threshold (`error` by default; `rigor check --fail-on=SEVERITY` lowers it to `warning` or `info`). |
|
|
1158
|
+
| `1` | Diagnostics found at or above the threshold, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
|
|
1131
1159
|
| `64` | Usage error — unknown command, bad flag, malformed argument, or a value in `.rigor.yml` the loader cannot proceed on. |
|
|
1132
1160
|
|
|
1133
1161
|
`rigor triage` is the exception: it is advisory and always
|
|
@@ -93,6 +93,16 @@ rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the overrid
|
|
|
93
93
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
One warning covers the mirror-image mistake — a path that loads, but only
|
|
97
|
+
half of what you wanted. A bundled plugin ships its RBS *and* a manifest
|
|
98
|
+
recording which of those classes it declares only partially; `plugins:`
|
|
99
|
+
loads both, while pointing `signature_paths:` at the plugin's `sig/` loads
|
|
100
|
+
only the RBS, so calls your own code defines get reported as undefined:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
rigor: signature_paths: "…/plugins/rigor-activerecord/sig" loads the signatures of the bundled plugin "rigor-activerecord", which `plugins:` does not name — … Add "rigor-activerecord" to `plugins:` instead of naming its `sig/` in `signature_paths:`.
|
|
104
|
+
```
|
|
105
|
+
|
|
96
106
|
The unrecognised-key check covers **top-level** keys, and skips
|
|
97
107
|
the namespaces reserved for other implementations (see below).
|
|
98
108
|
A typo *inside* a group — `cache: { pth: … }` — is caught by
|
|
@@ -170,6 +180,7 @@ explicitly with `bundler.bundle_path:`, or supply signatures another way:
|
|
|
170
180
|
| `cache.max_bytes` | Integer or `null` | `268435456` (256 MB) | LRU eviction cap for the cache directory; `null` disables eviction. See [Caching § Size and eviction](12-caching.md#size-and-eviction). |
|
|
171
181
|
| `cache.validation` | String | `"auto"` | How the cache checks whether a file is unchanged: `auto` behaves as `digest` when a CI environment is detected and as `stat` otherwise; `stat` compares size + nanosecond timestamps + inode and only re-hashes a file whose stat moved; `digest` re-hashes every file's content every run. Both keep the content hash as the sole change authority — `stat` just skips the hash when the stat proves a file untouched. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). The `RIGOR_STRICT_VALIDATION=1` environment variable forces `digest` for one run and wins over this key; `RIGOR_CI_DETECT=0` disables the CI detection. |
|
|
172
182
|
| `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. Applies to `--incremental` re-checks as well as full runs. |
|
|
183
|
+
| `plugins_isolation` | String | `null` | How a plugin's call into its target library is isolated — `process` (default) or `none`. `RIGOR_PLUGIN_ISOLATION` overrides it for one invocation; `ruby_box` is that variable only. See [Using plugins](07-plugins.md). |
|
|
173
184
|
| `plugins_io.network` | String | `"disabled"` | Plugin network policy — `disabled` or `allowlist`. |
|
|
174
185
|
| `plugins_io.allowed_paths` | Array | `[]` | Filesystem paths plugins may read. |
|
|
175
186
|
| `plugins_io.allowed_url_hosts` | Array | `[]` | URL hosts plugins may fetch from when `network: allowlist`. |
|
|
@@ -85,6 +85,17 @@ carries no `documentation_url`.
|
|
|
85
85
|
Plugins may contribute further families and rules; `rigor
|
|
86
86
|
explain` lists whatever the active configuration loads.
|
|
87
87
|
|
|
88
|
+
`flow.unreachable-branch` and `flow.always-truthy-condition` fold
|
|
89
|
+
version guards — `RUBY_VERSION` / `RUBY_ENGINE` comparisons, and
|
|
90
|
+
`X::VERSION` for a default gem of the running Ruby — against the
|
|
91
|
+
Ruby interpreter running `rigor`, never `target_ruby`. The
|
|
92
|
+
diagnostic set is therefore host-dependent: the same file can fold
|
|
93
|
+
a different arm on Ruby 3.3 than on Ruby 4.0, and a project whose
|
|
94
|
+
CI pins a different Ruby than your workstation should expect the CI
|
|
95
|
+
run's result, not yours. See
|
|
96
|
+
[Version-guard condition folding](../type-specification/control-flow-analysis.md#version-guard-condition-folding)
|
|
97
|
+
for the exact foldable set.
|
|
98
|
+
|
|
88
99
|
## Evidence tier
|
|
89
100
|
|
|
90
101
|
Every rule in the catalogue above carries an **evidence tier** —
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -74,23 +74,33 @@ A plugin may want to read a file (a schema dump) or reach the
|
|
|
74
74
|
network. Those are gated by the `plugins_io:` config keys —
|
|
75
75
|
the network is `disabled` by default, and a plugin can read
|
|
76
76
|
only the paths you list. See
|
|
77
|
-
[Configuration](03-configuration.md).
|
|
77
|
+
[Configuration](03-configuration.md). If a plugin's read falls
|
|
78
|
+
outside every configured path — a path spelled through a symlink
|
|
79
|
+
alias where the read roots hold the real path (macOS' `/tmp` is
|
|
80
|
+
one), or a genuinely out-of-tree file — Rigor surfaces a
|
|
81
|
+
`plugin_trust.read-refused` `:info` diagnostic naming the plugin,
|
|
82
|
+
the refused path and the nearest read root instead of failing
|
|
83
|
+
silently. Spell the path the way the diagnostic's read root spells
|
|
84
|
+
it, or add it under `plugins_io.allowed_paths:`.
|
|
78
85
|
|
|
79
86
|
### Isolation strategy
|
|
80
87
|
|
|
81
88
|
A few plugins call into their target library directly (for
|
|
82
89
|
example to ask ActiveSupport's real inflector how to pluralise a
|
|
83
90
|
class name). That call runs under an **isolation strategy**, set
|
|
84
|
-
with the `
|
|
91
|
+
with the `plugins_isolation:` configuration key or the
|
|
92
|
+
`RIGOR_PLUGIN_ISOLATION` environment variable:
|
|
85
93
|
|
|
86
94
|
| Value | Behaviour |
|
|
87
95
|
| --- | --- |
|
|
88
96
|
| `process` (default) | Run the call in a forked, crash-contained worker, so the target library's monkey-patches and any crash never contaminate Rigor. Falls back to `none` where `fork` is unavailable (Windows / JRuby). |
|
|
89
97
|
| `none` | Load the library into Rigor's own process and call it directly. |
|
|
90
|
-
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. |
|
|
98
|
+
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
|
|
91
99
|
|
|
92
|
-
The
|
|
93
|
-
|
|
100
|
+
The environment variable wins over `plugins_isolation:`, so you can
|
|
101
|
+
override a project's committed choice for one invocation. The legacy
|
|
102
|
+
`RIGOR_BOX` environment variable is a back-compat alias for
|
|
103
|
+
`RIGOR_PLUGIN_ISOLATION=ruby_box`. The default
|
|
94
104
|
(`process`) is the right choice for almost everyone; the variable
|
|
95
105
|
exists for the rare platform where forking is unavailable or
|
|
96
106
|
where you want stronger containment.
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -8,9 +8,9 @@ agent works inside a project that has Rigor available.
|
|
|
8
8
|
Skills are optional. Everything they do, you can do by hand with the
|
|
9
9
|
commands in this manual; a skill drives the workflow end to end.
|
|
10
10
|
|
|
11
|
-
## Start here —
|
|
11
|
+
## Start here — three skills to remember
|
|
12
12
|
|
|
13
|
-
You only ever need to remember
|
|
13
|
+
You only ever need to remember three skills; the rest are reached through
|
|
14
14
|
them.
|
|
15
15
|
|
|
16
16
|
- **`rigor-next-steps`** — *"what should we do next?"* The single entry
|
|
@@ -31,6 +31,20 @@ them.
|
|
|
31
31
|
code, runs `rigor check` / `annotate` / `type-of` — then answers from
|
|
32
32
|
the page or the inferred type. You never have to remember the command,
|
|
33
33
|
just the question. Available at any point.
|
|
34
|
+
- **`rigor-type-oracle`** — *"before you write a type, ask Rigor."* The
|
|
35
|
+
one to remember while **writing** rather than while planning. Any time
|
|
36
|
+
a type is about to be written or asserted — RBS under `sig/`, an inline
|
|
37
|
+
`#:` / `# @rbs` annotation, a Sorbet `sig`, a YARD `@param` /
|
|
38
|
+
`@return`, a type named in a doc sentence or a review comment, a nil
|
|
39
|
+
check justified by "this should be an `X`" — the type comes from
|
|
40
|
+
[`rigor type-of`](02-cli-reference.md#rigor-type-of) /
|
|
41
|
+
[`annotate`](02-cli-reference.md#rigor-annotate) /
|
|
42
|
+
[`sig-gen`](02-cli-reference.md#rigor-sig-gen), not from reading the
|
|
43
|
+
code. A type nobody obtained from Rigor is a guess, and where Rigor has
|
|
44
|
+
no answer (`Dynamic[top]`, `untyped`, a skipped method) the gap is
|
|
45
|
+
reported rather than filled in. It also ships the paragraph to keep in
|
|
46
|
+
your `AGENTS.md` / `CLAUDE.md`, so the rule holds in every agent
|
|
47
|
+
session and not only when the skill triggers.
|
|
34
48
|
|
|
35
49
|
If you do not know which skill you need, start with `rigor-next-steps`.
|
|
36
50
|
|
|
@@ -72,6 +86,13 @@ is not repeated here.)
|
|
|
72
86
|
- **`rigor-monkeypatch-resolve`** — resolves an `undefined-method`
|
|
73
87
|
cluster that is really your project's own monkey-patches by wiring the
|
|
74
88
|
defining files into `pre_eval:`.
|
|
89
|
+
- **`rigor-type-oracle`** — sources every type an agent writes from
|
|
90
|
+
Rigor (`type-of` / `annotate` / `sig-gen`) instead of from reading the
|
|
91
|
+
code, and reports the gaps rather than filling them. It is triggered by
|
|
92
|
+
the *event* of a type being about to be written, so `rigor skill
|
|
93
|
+
describe` lists it but never routes to it — reach for it (or install
|
|
94
|
+
its `AGENTS.md` paragraph) whenever an agent documents or annotates
|
|
95
|
+
your code. Introduced above under "Start here".
|
|
75
96
|
|
|
76
97
|
### Integration and operations
|
|
77
98
|
|
data/docs/manual/11-ci.md
CHANGED
|
@@ -187,6 +187,15 @@ Rigor's severities map per format ([ADR-51](../adr/51-ci-diagnostic-output-forma
|
|
|
187
187
|
The exit code is unchanged by `--format` — `0` when there are no errors,
|
|
188
188
|
`1` otherwise — so the job still gates the pipeline. `--format json`
|
|
189
189
|
remains available for any other tool that wants the raw diagnostic stream.
|
|
190
|
+
A job that wants warnings (or `info` notes) to gate the pipeline too, not
|
|
191
|
+
just errors, raises the bar with `rigor check --fail-on=warning` (or
|
|
192
|
+
`--fail-on=info`) instead of parsing the diagnostic stream itself.
|
|
193
|
+
|
|
194
|
+
A third status, `70`, means Rigor itself died mid-run — a
|
|
195
|
+
`SystemStackError` or `NoMemoryError` inside the analysis — and the report
|
|
196
|
+
it left behind (an empty `triage.json`, say) is not an account of your
|
|
197
|
+
code. Treat it as a failed job and report it as a Rigor defect, never as a
|
|
198
|
+
clean check.
|
|
190
199
|
|
|
191
200
|
## Gating effect drift
|
|
192
201
|
|
|
@@ -337,6 +337,14 @@ ones a type actually catches.
|
|
|
337
337
|
> point Rigor at it with `bundler.bundle_path:`. Until you do, these
|
|
338
338
|
> holes keep the generic `engine_gap` cause instead of `add_rbs` —
|
|
339
339
|
> the label is missing, never wrong.
|
|
340
|
+
>
|
|
341
|
+
> A project with **no `Gemfile.lock`** is not left out: Rigor falls
|
|
342
|
+
> back to the gems it can see installed — the project's Bundler
|
|
343
|
+
> install tree if one resolves, otherwise the running Ruby's gems —
|
|
344
|
+
> and attributes constants against those. Ownership is still
|
|
345
|
+
> established by reading the gem's own entry file, so the fallback
|
|
346
|
+
> widens which gems can be claimed and never whether an unowned
|
|
347
|
+
> constant is.
|
|
340
348
|
|
|
341
349
|
Provenance is precision-additive only: it never changes a type, fires
|
|
342
350
|
no diagnostic, and never affects severity or the protection ratio.
|
|
@@ -122,7 +122,12 @@ The right-hand side of `return:`, `param:`, `assert*`, and
|
|
|
122
122
|
|
|
123
123
|
Refinement payloads support the parameterised forms
|
|
124
124
|
`non-empty-array[Integer]`, `non-empty-hash[Symbol, Integer]`,
|
|
125
|
-
and the bounded
|
|
125
|
+
and the bounded numeric forms `Integer[1..10]` and
|
|
126
|
+
`Float[0.0...1.0]`, written with a Ruby range literal (`1...10`,
|
|
127
|
+
`1..`, `..10`; the PHPStan-style `int<1, 10>` still parses but is
|
|
128
|
+
deprecated and reports `dynamic.rbs-extended.deprecated-form` with
|
|
129
|
+
the spelling to write), plus the Float names `non-nan-float` and
|
|
130
|
+
`finite-float`. Type-argument
|
|
126
131
|
positions also accept Symbol / String literal tokens and unions
|
|
127
132
|
of them — `pick_of[T, :name | :email]`,
|
|
128
133
|
`Pick[T, "name" | "email"]` — each lifted to a `Constant<value>`.
|
|
@@ -80,14 +80,16 @@ partition `declared` — they sum to 1497. **`reachable only from tests`
|
|
|
80
80
|
is a subset of `reachable`**, not a fifth bucket, so do not add it in.
|
|
81
81
|
|
|
82
82
|
Read `roots` first. A root is a declaration something outside your code
|
|
83
|
-
reaches — an entry point, a route, a framework convention.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
reaches — an entry point, a route, a framework convention. The
|
|
84
|
+
`(… from plugins, …)` parenthetical is omitted entirely when no plugin
|
|
85
|
+
contributed a root, so a bare `roots: N` with no such note is itself
|
|
86
|
+
the signal. Reachability is computed *from* roots, so a thin root set
|
|
87
|
+
inflates everything below it. The pathological case is a Rails
|
|
88
|
+
application with no `from plugins` note at all: nothing in Ruby source
|
|
89
|
+
names a controller, so every controller you own appears dead. There is
|
|
90
|
+
no published healthy ratio to compare against; what you are checking
|
|
91
|
+
is whether the number is plausible for your framework, and a missing
|
|
92
|
+
`from plugins` note on a framework app never is.
|
|
91
93
|
|
|
92
94
|
## Getting the number down
|
|
93
95
|
|
|
@@ -93,6 +93,24 @@ so *can* fold a condition — and `Mime::NullType`, the value `format`
|
|
|
93
93
|
returns when there is no format, answers `nil?` with `true` while
|
|
94
94
|
being a real object. Typing it needs a nil-aware answer.
|
|
95
95
|
|
|
96
|
+
## Framework constants resolve
|
|
97
|
+
|
|
98
|
+
The plugin ships a small bundled signature naming the
|
|
99
|
+
`ActionController` namespace and the errors controllers rescue —
|
|
100
|
+
`ParameterMissing`, `UnpermittedParameters`, `RoutingError`,
|
|
101
|
+
`BadRequest`, `UnknownFormat`, `InvalidAuthenticityToken` and their
|
|
102
|
+
siblings. A `rescue ActionController::ParameterMissing => e` now types
|
|
103
|
+
`e` as that class instead of leaving it opaque.
|
|
104
|
+
|
|
105
|
+
The signature names **only** those. `ActionController::Base` and
|
|
106
|
+
`ActionController::API` are left undeclared on purpose: every
|
|
107
|
+
controller in the app inherits from one of them, and an incomplete
|
|
108
|
+
declaration of a superclass turns every member it omits — `render`,
|
|
109
|
+
`before_action`, `head` — into a report on working code. For the same
|
|
110
|
+
reason `ActionController::Parameters` and the `ActionDispatch` readers
|
|
111
|
+
above stay undeclared; their leniency is what makes the `params`
|
|
112
|
+
typing safe.
|
|
113
|
+
|
|
96
114
|
## Limitations
|
|
97
115
|
|
|
98
116
|
- **Implicit-self helpers only.** `*_path` / `*_url` calls with an
|
|
@@ -76,6 +76,19 @@ Chained query methods keep the element type, and iteration
|
|
|
76
76
|
scope invoked on a typed relation (`User.where(...).published`)
|
|
77
77
|
never surfaces a false `call.undefined-method`.
|
|
78
78
|
|
|
79
|
+
If the project also installs `activerecord` through
|
|
80
|
+
`rbs collection install`, the collection declares
|
|
81
|
+
`ActiveRecord::Relation` without a type parameter while the
|
|
82
|
+
plugin declares `ActiveRecord::Relation[Elem]`, and RBS cannot
|
|
83
|
+
hold both. The plugin's declaration stands down: relation call
|
|
84
|
+
sites still type as `ActiveRecord::Relation[Model]`, but calls
|
|
85
|
+
into a relation resolve against the collection's declaration,
|
|
86
|
+
so the plugin's element typing (`.first` as `Model?`, for
|
|
87
|
+
example) is unavailable, and the run reports one
|
|
88
|
+
`rbs.coverage.plugin-signature-stood-down` info row naming both
|
|
89
|
+
files. Nothing is broken; the plugin's typing returns only when
|
|
90
|
+
the collection stops declaring the class.
|
|
91
|
+
|
|
79
92
|
`User.table_name` types as `String`, and as the exact string
|
|
80
93
|
only when your source says the name: a literal
|
|
81
94
|
`self.table_name = "people"` on the class or on an STI ancestor,
|
|
@@ -118,6 +131,21 @@ than none, so `Blog::Post`'s column, alias and association checks
|
|
|
118
131
|
stand down entirely rather than run against a table that might not
|
|
119
132
|
be the real one.
|
|
120
133
|
|
|
134
|
+
## Framework constants resolve
|
|
135
|
+
|
|
136
|
+
The bundled signatures also name Active Record's exception hierarchy
|
|
137
|
+
(`ActiveRecordError` and the classes apps rescue: `RecordNotFound`,
|
|
138
|
+
`RecordInvalid`, `RecordNotSaved`, `StatementInvalid`,
|
|
139
|
+
`RecordNotUnique`, `StaleObjectError`, …), the `ActiveModel`
|
|
140
|
+
namespace, and `Arel`. `rescue ActiveRecord::RecordNotFound => e` types
|
|
141
|
+
`e` instead of leaving it opaque.
|
|
142
|
+
|
|
143
|
+
None of them declares a method surface — the declaration buys constant
|
|
144
|
+
resolution and asserts nothing else, so `e.record` and every other
|
|
145
|
+
member left out stays lenient rather than reported.
|
|
146
|
+
`ActiveRecord::Base` is deliberately **not** declared: closing it would
|
|
147
|
+
close every model in the project.
|
|
148
|
+
|
|
121
149
|
## Limitations
|
|
122
150
|
|
|
123
151
|
- **Direct-superclass match only.** `class Admin < User` where
|
|
@@ -129,8 +129,29 @@ singletons `.days_in_month`, `.days_in_year`, `.rfc3339`, `.use_zone`,
|
|
|
129
129
|
`.find_zone` / `.find_zone!` and `.zone_default`.
|
|
130
130
|
|
|
131
131
|
Where a return cannot honestly be named it is widened rather than
|
|
132
|
-
guessed: `#in_time_zone` answers an `ActiveSupport::TimeWithZone
|
|
133
|
-
|
|
132
|
+
guessed: `#in_time_zone` answers an `ActiveSupport::TimeWithZone` under a
|
|
133
|
+
zone and the receiver's own class when none is configured, so it reads
|
|
134
|
+
`untyped` rather than picking one of the two.
|
|
135
|
+
|
|
136
|
+
`ActiveSupport::TimeWithZone` itself **is** modelled, as a subclass of
|
|
137
|
+
`Time` — which is what `Time.current` and the whole `1.hour.ago` family
|
|
138
|
+
answer:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
Time.current.time_zone # the zone (untyped)
|
|
142
|
+
Time.current.comparable_time # Time
|
|
143
|
+
Time.current.to_fs(:db) # String — Time's own surface, inherited
|
|
144
|
+
1.hour.ago.time_zone # the same class from the Duration family
|
|
145
|
+
Time.now.time_zone # still call.undefined-method — a plain
|
|
146
|
+
# Time really does not have it
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Rails overrides `TimeWithZone#is_a?` to answer true for `::Time` and
|
|
150
|
+
forwards everything it does not define to the wrapped `Time`, so the
|
|
151
|
+
subclass says what a `Time` return said and adds the four readers a TWZ
|
|
152
|
+
has of its own. A `Time | TimeWithZone` union was measured instead and
|
|
153
|
+
rejected: it fires nothing, but types the whole downstream chain
|
|
154
|
+
`Dynamic[top]`.
|
|
134
155
|
|
|
135
156
|
What is left out is twelve names, measured against a real
|
|
136
157
|
`require "active_support/all"`: ten instance and two singleton, every one
|
|
@@ -145,6 +166,15 @@ code that does will see them reported.
|
|
|
145
166
|
`Date` and `DateTime` are extended by the same ActiveSupport modules and
|
|
146
167
|
do **not** carry this yet — `Date.current.past?` still reports.
|
|
147
168
|
|
|
169
|
+
## `ActiveSupport::Concern` resolves
|
|
170
|
+
|
|
171
|
+
`extend ActiveSupport::Concern` is the first line of every concern in a
|
|
172
|
+
Rails app, and the constant used to resolve to nothing. The bundle now
|
|
173
|
+
names the module, along with the three members `extend` puts on the
|
|
174
|
+
extender — `included`, `prepended` and `class_methods` — so
|
|
175
|
+
`included do … end` keeps resolving rather than becoming a report on the
|
|
176
|
+
newly-named module. Everything else `Concern` responds to stays lenient.
|
|
177
|
+
|
|
148
178
|
## No diagnostics, no config
|
|
149
179
|
|
|
150
180
|
The plugin emits no diagnostics and has no configuration knobs. It
|
|
@@ -45,7 +45,48 @@ plugin id/version + config), so an unchanged second run skips the parse.
|
|
|
45
45
|
| Rule | Severity | Fires when |
|
|
46
46
|
| --- | --- | --- |
|
|
47
47
|
| `plugin.rbs-inline.source-rbs-synthesis-failed` | info | rbs-inline could not parse a file; analysis falls back to no inline-RBS contribution and the diagnostic carries the upstream error |
|
|
48
|
-
| `plugin.rbs-inline.source-rbs-annotation-not-honoured` | info | an annotation parsed successfully but contributed nothing — the file's other annotations still apply.
|
|
48
|
+
| `plugin.rbs-inline.source-rbs-annotation-not-honoured` | info | an annotation parsed successfully but contributed nothing — the file's other annotations still apply. Two causes: a member your `sig/` also declares (see [Precedence](#precedence)), and the `# @rbs module-self: Foo` spelling (see below) |
|
|
49
|
+
|
|
50
|
+
## Precedence
|
|
51
|
+
|
|
52
|
+
When a method is declared **both** in `sig/` and by an inline
|
|
53
|
+
annotation, **the `.rbs` wins, per member.** The inline signature for
|
|
54
|
+
that one method is dropped; every other annotation in the file still
|
|
55
|
+
binds, and the class keeps its method surface.
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# lib/demo.rb # sig/demo.rbs
|
|
59
|
+
class Demo # class Demo
|
|
60
|
+
# @rbs (Integer) -> String # def shared: (String) -> Integer ← this one wins
|
|
61
|
+
def shared(v) = v.to_s # def only_sig: () -> String
|
|
62
|
+
# end
|
|
63
|
+
# @rbs (Integer) -> Integer
|
|
64
|
+
def only_inline(v) = v + 1 # ← inline-only: still binds
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Each dropped member is reported once as
|
|
69
|
+
`plugin.rbs-inline.source-rbs-annotation-not-honoured`, naming the
|
|
70
|
+
member and the `.rbs` that won. Delete one of the two declarations to
|
|
71
|
+
make the inline annotation take effect.
|
|
72
|
+
|
|
73
|
+
`sig/` wins because it is the reviewed artefact — the one you diff in
|
|
74
|
+
review and the one `rigor sig-gen --diff` reasons about. There is no
|
|
75
|
+
upstream rule to defer to: rbs merges an inline `.rb` declaration and a
|
|
76
|
+
`.rbs` one into a single class entry and ranks neither, so Steep reports
|
|
77
|
+
the same overlap as a signature error and the class still fails to
|
|
78
|
+
build. Rigor keeps the reporting and drops the degradation
|
|
79
|
+
([ADR-32](../../adr/32-rbs-inline-comment-ingestion.md) WD13) — left to
|
|
80
|
+
collide, one duplicated method costs the class every other method, and
|
|
81
|
+
each call on it, real methods and typos alike, reads `Dynamic[top]`.
|
|
82
|
+
|
|
83
|
+
Two overlaps this does **not** cover: a `.rbs` that collides with
|
|
84
|
+
*bundled* RBS (Ruby core, stdlib, a gem's signatures) is quarantined
|
|
85
|
+
file-by-file instead, reported as `rbs.coverage.quarantined-signature`;
|
|
86
|
+
and two `.rbs` files declaring the same member still fail the class's
|
|
87
|
+
definition build and surface as `rbs.coverage.definition-build-failed` —
|
|
88
|
+
neither side of that pair is more reviewed than the other, so there is
|
|
89
|
+
nothing to prefer.
|
|
49
90
|
|
|
50
91
|
## Which inline-RBS dialect Rigor reads
|
|
51
92
|
|
|
@@ -236,9 +236,9 @@ module Rigor
|
|
|
236
236
|
# Walk the current diagnostic stream and report bucket-level drift. Each baseline bucket becomes one
|
|
237
237
|
# DriftRow regardless of whether the current run still matches it.
|
|
238
238
|
#
|
|
239
|
-
# @param diagnostics
|
|
239
|
+
# @param diagnostics — current run's diagnostic stream (PRE-filter — pass the raw
|
|
240
240
|
# `result.diagnostics` from `Runner#run`, not the post-baseline surface).
|
|
241
|
-
# @return
|
|
241
|
+
# @return one entry per baseline bucket, in baseline-file order.
|
|
242
242
|
def audit(diagnostics)
|
|
243
243
|
counts = Hash.new(0)
|
|
244
244
|
diagnostics.each do |diag|
|
|
@@ -53,7 +53,7 @@ module Rigor
|
|
|
53
53
|
NODE_CLASSES = [Prism::IfNode, Prism::UnlessNode].freeze
|
|
54
54
|
RULE_WALK_GATES = [:loop_or_block].freeze
|
|
55
55
|
|
|
56
|
-
# @return
|
|
56
|
+
# @return one entry per qualifying predicate. Empty when the tree carries no firing
|
|
57
57
|
# predicates.
|
|
58
58
|
def initialize(scope_index)
|
|
59
59
|
@scope_index = scope_index
|
|
@@ -30,9 +30,6 @@ module Rigor
|
|
|
30
30
|
module DeadVersionGuardArms
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
|
-
# @param diagnostics [Array<Rigor::Analysis::Diagnostic>]
|
|
34
|
-
# @param root [Prism::Node]
|
|
35
|
-
# @return [Array<Rigor::Analysis::Diagnostic>]
|
|
36
33
|
def filter(diagnostics, root)
|
|
37
34
|
# The scan is a whole-file walk, so it is paid only when there is something to drop. A file with
|
|
38
35
|
# no diagnostics — the overwhelming majority — never walks.
|
|
@@ -44,8 +41,7 @@ module Rigor
|
|
|
44
41
|
diagnostics.reject { |diagnostic| arms.any? { |arm| covers?(arm, diagnostic) } }
|
|
45
42
|
end
|
|
46
43
|
|
|
47
|
-
# @
|
|
48
|
-
# @return [Array<Prism::Location>] the source ranges of every dead version-guard arm
|
|
44
|
+
# @return the source ranges of every dead version-guard arm
|
|
49
45
|
def scan(root)
|
|
50
46
|
arms = []
|
|
51
47
|
collect(root, arms)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "rule_walk"
|
|
5
6
|
require_relative "../../source/constant_path"
|
|
6
7
|
require_relative "../../source/node_children"
|
|
7
8
|
|
|
@@ -17,7 +18,11 @@ module Rigor
|
|
|
17
18
|
#
|
|
18
19
|
# Skipped on purpose:
|
|
19
20
|
#
|
|
20
|
-
# - Singleton-method bodies
|
|
21
|
+
# - Singleton-method bodies, in either spelling: `def self.foo`, and (issue #909) any `def` lexically
|
|
22
|
+
# inside a `class << self` body. Their ivars live on the class object, not on instances.
|
|
23
|
+
# - Bodies of an anonymous-class factory block (`Class.new do … end`, `Module.new`, `Struct.new`,
|
|
24
|
+
# `Data.define`). The class they define is not the enclosing one, so its ivars are a third store
|
|
25
|
+
# again (issue #909).
|
|
21
26
|
# - Class-body ivar writes outside any def — the `Module#@var` surface is a separate slice the engine
|
|
22
27
|
# doesn't yet model.
|
|
23
28
|
# - Nested classes / modules / defs inside a method body are barriers, mirroring the indexer's
|
|
@@ -33,7 +38,7 @@ module Rigor
|
|
|
33
38
|
# shared full DFS that prune becomes the `:inside_def` gate; the enclosing class / module name stack
|
|
34
39
|
# the legacy walk threaded as `qualified_prefix` is now `context.qualified_prefix`.
|
|
35
40
|
NODE_CLASSES = [Prism::DefNode].freeze
|
|
36
|
-
RULE_WALK_GATES = [
|
|
41
|
+
RULE_WALK_GATES = %i[inside_def detached_ivar_facet].freeze
|
|
37
42
|
|
|
38
43
|
# Returns `Hash[class_name (String) => Hash[ivar_name (Symbol) => Array<{node:, type:}>]]`. Empty
|
|
39
44
|
# when the tree has no qualifying writes.
|
|
@@ -66,6 +71,9 @@ module Rigor
|
|
|
66
71
|
|
|
67
72
|
def walk(node, qualified_prefix)
|
|
68
73
|
return unless node.is_a?(Prism::Node)
|
|
74
|
+
# The legacy walk's mirror of {RuleWalk}'s `detached_ivar_facet` context (issue #909); the two
|
|
75
|
+
# walks share the predicate so the permanent equivalence spec keeps holding.
|
|
76
|
+
return if RuleWalk.detached_ivar_facet?(node)
|
|
69
77
|
|
|
70
78
|
case node
|
|
71
79
|
when Prism::ClassNode, Prism::ModuleNode
|
|
@@ -25,7 +25,7 @@ module Rigor
|
|
|
25
25
|
Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode
|
|
26
26
|
].freeze
|
|
27
27
|
|
|
28
|
-
# @param node_diagnostics
|
|
28
|
+
# @param node_diagnostics — maps a `Prism::Node` to the array of diagnostics the main pass emits
|
|
29
29
|
# for it.
|
|
30
30
|
def initialize(node_diagnostics)
|
|
31
31
|
@node_diagnostics = node_diagnostics
|