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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../configuration/severity_profile"
|
|
6
|
+
require_relative "../../reflection"
|
|
7
|
+
require_relative "../../source/node_walker"
|
|
8
|
+
require_relative "../../scope/resolution_chain"
|
|
9
|
+
require_relative "../../type"
|
|
10
|
+
require_relative "../diagnostic"
|
|
11
|
+
require_relative "rule_ids"
|
|
12
|
+
|
|
13
|
+
module Rigor
|
|
14
|
+
module Analysis
|
|
15
|
+
module CheckRules
|
|
16
|
+
# ADR-128 WD6 / ADR-130 WD3 — `plugin.api-surface`. A call in plugin source on a value typed by a
|
|
17
|
+
# `Rigor::Plugin` interface (`Dynamic[Rigor::Plugin::_Scope]`, ...) to a member that interface does not
|
|
18
|
+
# declare. The method exists on the backing engine class, so the call works today; it is outside what Rigor
|
|
19
|
+
# promises to keep, so `call.undefined-method` would be wrong and this rule says that instead.
|
|
20
|
+
#
|
|
21
|
+
# Authored `:warning`, resolved `:off` by every profile and promoted only by the `plugin-api-surface`
|
|
22
|
+
# bleeding-edge feature (ADR-50 Decision 3, WD2), as `static.value-use.void` is.
|
|
23
|
+
#
|
|
24
|
+
# It fires only where all of these hold:
|
|
25
|
+
#
|
|
26
|
+
# - the call has an explicit receiver, typed `Dynamic[<Rigor::Plugin interface>]`, or that plus `nil` (the
|
|
27
|
+
# facet survives only where ADR-130 WD1 keeps it; a value that lost it at a join reads `Dynamic[top]`);
|
|
28
|
+
# - the interface declares no member of the name, and `Object` (with `Kernel` and `BasicObject`) declares
|
|
29
|
+
# none either: `nil?`, `respond_to?` and `is_a?` are every object's surface, not the engine's;
|
|
30
|
+
# - the call sits lexically inside a class whose discovered superclass chain reaches `Rigor::Plugin::Base`.
|
|
31
|
+
# `Base` itself is not such a class, so its own hooks, which hand interface values to engine helpers
|
|
32
|
+
# (`environment.class_ordering`), are not judged: they are engine code.
|
|
33
|
+
#
|
|
34
|
+
# The plugin-source test reads the class declarations, not a path: a third-party plugin gem keeps its plugin
|
|
35
|
+
# under `lib/` as Rigor's own engine does.
|
|
36
|
+
#
|
|
37
|
+
# The rule runs only when its resolved severity is not `:off` ({.active?}): the runner then adds its
|
|
38
|
+
# {Collector} to the shared {RuleWalk}. A default run builds no collector, so it types no extra receiver and
|
|
39
|
+
# files no ADR-46 dependency for the rule.
|
|
40
|
+
module PluginApiSurface
|
|
41
|
+
PLUGIN_BASE = "Rigor::Plugin::Base"
|
|
42
|
+
|
|
43
|
+
# One call the rule reports: the call and the first interface its receiver carries.
|
|
44
|
+
Result = Data.define(:call_node, :interface)
|
|
45
|
+
|
|
46
|
+
# The {RuleWalk} collector: one {Result} per reported call, in walk order.
|
|
47
|
+
class Collector
|
|
48
|
+
NODE_CLASSES = [Prism::CallNode].freeze
|
|
49
|
+
|
|
50
|
+
def initialize(scope_index)
|
|
51
|
+
@scope_index = scope_index
|
|
52
|
+
@results = []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The legacy single-collector walk, the oracle the ADR-53 equivalence harness compares {RuleWalk} with.
|
|
56
|
+
def collect(root)
|
|
57
|
+
Source::NodeWalker.each(root) { |node| visit(node) if node.is_a?(Prism::CallNode) }
|
|
58
|
+
@results.freeze
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def visit(node, _context = nil)
|
|
62
|
+
interface = PluginApiSurface.outside_interface(node, @scope_index[node])
|
|
63
|
+
@results << Result.new(call_node: node, interface: interface) if interface
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def results
|
|
67
|
+
@results.freeze
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module_function
|
|
72
|
+
|
|
73
|
+
# Whether the rule can report in a run under `configuration`: it is not in `disable:`, and its resolved
|
|
74
|
+
# severity is not `:off`. Default profiles resolve it `:off`; the `plugin-api-surface` bleeding-edge feature
|
|
75
|
+
# or an explicit `severity_overrides:` entry for the id turns it on.
|
|
76
|
+
def active?(configuration)
|
|
77
|
+
disabled = configuration.disabled_rules
|
|
78
|
+
return false if disabled.include?(RULE_PLUGIN_API_SURFACE) || disabled.include?("all")
|
|
79
|
+
|
|
80
|
+
Configuration::SeverityProfile.resolve(
|
|
81
|
+
rule: RULE_PLUGIN_API_SURFACE, authored_severity: :warning,
|
|
82
|
+
profile: configuration.severity_profile, overrides: configuration.severity_overrides,
|
|
83
|
+
bleeding_edge_overrides: configuration.bleeding_edge_severity_overrides
|
|
84
|
+
) != :off
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The interface the call's receiver carries when the call is outside it, or nil.
|
|
88
|
+
def outside_interface(call_node, scope)
|
|
89
|
+
return nil if call_node.receiver.nil? || scope.nil?
|
|
90
|
+
|
|
91
|
+
interfaces = receiver_interfaces(scope.type_of(call_node.receiver))
|
|
92
|
+
return nil if interfaces.nil?
|
|
93
|
+
|
|
94
|
+
environment = scope.environment
|
|
95
|
+
method_name = call_node.name
|
|
96
|
+
return nil if interfaces.any? { |interface| declares?(environment, interface, method_name) }
|
|
97
|
+
return nil if object_member?(environment, method_name)
|
|
98
|
+
return nil unless plugin_source?(scope)
|
|
99
|
+
|
|
100
|
+
interfaces.first
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def diagnostics(path, results)
|
|
104
|
+
results.map { |result| build(path, result.call_node, result.interface, result.call_node.name) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The interface facets the receiver carries, or nil when it is anything but `Dynamic[Interface]` arms and
|
|
108
|
+
# `nil`. A union whose non-nil arms include another type has a member the call may resolve on.
|
|
109
|
+
def receiver_interfaces(type)
|
|
110
|
+
case type
|
|
111
|
+
when Type::Dynamic
|
|
112
|
+
facet = type.static_facet
|
|
113
|
+
[facet] if facet.is_a?(Type::Interface)
|
|
114
|
+
when Type::Union
|
|
115
|
+
union_interfaces(type)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def union_interfaces(union)
|
|
120
|
+
interfaces = []
|
|
121
|
+
union.members.each do |member|
|
|
122
|
+
next if nil_type?(member)
|
|
123
|
+
return nil unless member.is_a?(Type::Dynamic) && member.static_facet.is_a?(Type::Interface)
|
|
124
|
+
|
|
125
|
+
interfaces << member.static_facet
|
|
126
|
+
end
|
|
127
|
+
interfaces.empty? ? nil : interfaces
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def nil_type?(type)
|
|
131
|
+
(type.is_a?(Type::Constant) && type.value.nil?) ||
|
|
132
|
+
(type.is_a?(Type::Nominal) && type.class_name == "NilClass")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# An interface that does not resolve declares nothing the rule can read, so it is treated as declaring
|
|
136
|
+
# every member. A guard: the carrier exists only for an interface the loader translated, so no fixture
|
|
137
|
+
# reaches it.
|
|
138
|
+
def declares?(environment, interface, method_name)
|
|
139
|
+
definition = environment.rbs_loader.interface_definition(interface.name)
|
|
140
|
+
return true if definition.nil?
|
|
141
|
+
|
|
142
|
+
definition.methods.key?(method_name.to_sym)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def object_member?(environment, method_name)
|
|
146
|
+
!Rigor::Reflection.instance_method_definition("Object", method_name, environment: environment).nil?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Whether the call is plugin source: lexically inside a class that inherits `Rigor::Plugin::Base`. A
|
|
150
|
+
# `Rigor::Plugin::Base` body is not: its chain holds `Base` only as itself. An unrecorded nesting (a scope
|
|
151
|
+
# built outside a declaration walk) is not either.
|
|
152
|
+
def plugin_source?(scope)
|
|
153
|
+
nesting = scope.lexical_nesting
|
|
154
|
+
return false if nesting.nil? || nesting.empty?
|
|
155
|
+
|
|
156
|
+
nesting.any? { |name| inherits_plugin_base?(scope, name) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Whether `class_name`'s instance chain ({Scope::ResolutionChain}, the one ancestry reader) holds
|
|
160
|
+
# `Rigor::Plugin::Base` below the class itself. Reading it files the ADR-46 class dependencies, so
|
|
161
|
+
# an incremental recheck re-judges the call when a declaration on the chain changes in another file. A
|
|
162
|
+
# truncated chain reads as no plugin: the rule withholds where it cannot see the whole chain.
|
|
163
|
+
def inherits_plugin_base?(scope, class_name)
|
|
164
|
+
root = class_name.to_s
|
|
165
|
+
chain = Scope::ResolutionChain.for(scope, root, :instance, :methods)
|
|
166
|
+
chain.record(scope)
|
|
167
|
+
return false if chain.truncated?
|
|
168
|
+
|
|
169
|
+
chain.entries.any? { |entry| entry.name != root && plugin_base_entry?(entry) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# `Rigor::Plugin::Base` is external where only its RBS and the gem declare it, and a project entry where the
|
|
173
|
+
# analysed tree holds its source (Rigor's own `lib`, or a reopen).
|
|
174
|
+
def plugin_base_entry?(entry)
|
|
175
|
+
entry.external? ? entry.candidates.include?(PLUGIN_BASE) : entry.name == PLUGIN_BASE
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def build(path, call_node, interface, method_name)
|
|
179
|
+
Diagnostic.from_message_loc(
|
|
180
|
+
call_node,
|
|
181
|
+
rule: RULE_PLUGIN_API_SURFACE,
|
|
182
|
+
path: path,
|
|
183
|
+
message: "`#{method_name}' is not part of the plugin API: `#{interface.describe}' does not declare it. " \
|
|
184
|
+
"The engine object answers it today, but Rigor does not promise to keep it; call a member the " \
|
|
185
|
+
"interface declares (sig/rigor/plugin/interfaces.rbs) or a public namespace " \
|
|
186
|
+
"(docs/internal-spec/public-api.md)",
|
|
187
|
+
severity: :warning,
|
|
188
|
+
receiver_type: nil,
|
|
189
|
+
method_name: method_name
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -19,7 +19,7 @@ module Rigor
|
|
|
19
19
|
# branched on, whose declaration the reader's author never opened. A configuration constant read in ten
|
|
20
20
|
# files would put a warning in all ten.
|
|
21
21
|
#
|
|
22
|
-
# This is [ADR-58](../../../../docs/adr/58-
|
|
22
|
+
# This is [ADR-58](../../../../docs/adr/58-ivar-field-typing.md)'s discipline applied to a
|
|
23
23
|
# different carrier: "a consumer may only ever *withhold* a firing it would otherwise make, so the mark
|
|
24
24
|
# can lose precision but can never manufacture a false positive"
|
|
25
25
|
# (`docs/internal-spec/inference-engine.md` § "Carrier and discipline"). The carrier is deliberately NOT
|
|
@@ -115,15 +115,19 @@ module Rigor
|
|
|
115
115
|
# exemption exactly rather than on the last segment. A path that renders no qualified name falls back
|
|
116
116
|
# to its own last segment, which is all there is to ask about.
|
|
117
117
|
def published_path?(node, scope)
|
|
118
|
-
rendered =
|
|
118
|
+
rendered = constant_spelling(node)
|
|
119
119
|
!rendered.nil? && scope.published_constant?(rendered)
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# The one hop. Resolves the callee through the ordinary `Scope` accessors (so the ADR-46 recorder sees
|
|
123
123
|
# the read and the caller gains an edge to the callee — the conservative direction) and scans its
|
|
124
124
|
# body. Fails soft to false: a resolution this cannot make is not a reason to withhold.
|
|
125
|
+
#
|
|
126
|
+
# Issue #3199 — the empty-table shortcut is taken only when no ADR-46 recorder is watching: the scan's
|
|
127
|
+
# `Scope#published_constant?` questions are what record the `constant:` edges, and a reader that skipped
|
|
128
|
+
# them would not be re-checked when another file publishes a constant the callee's body names.
|
|
125
129
|
def callee_reads_published_constant?(node, scope)
|
|
126
|
-
return false if scope.published_constant_names.empty?
|
|
130
|
+
return false if scope.published_constant_names.empty? && !Analysis::DependencyRecorder.recording?
|
|
127
131
|
|
|
128
132
|
def_node = resolve_callee(node, scope)
|
|
129
133
|
return false if def_node.nil?
|
|
@@ -172,10 +176,30 @@ module Rigor
|
|
|
172
176
|
end
|
|
173
177
|
end
|
|
174
178
|
|
|
175
|
-
# An explicit worklist rather than recursion, so the node budget is one loop-carried local instead of
|
|
176
|
-
# a shared mutable cell (the engine value-pins a one-element Array counter and then self-flags the
|
|
177
|
-
# guard's own `negative?` as an always-falsey condition — this rule's own medicine).
|
|
178
179
|
def body_reads_published_constant?(root, scope)
|
|
180
|
+
scan_body(root) { |node| return true if published_constant_node?(node, scope) }
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Issue #3186 — the constant spellings the one hop's body scan would ask `Scope#published_constant?` about,
|
|
185
|
+
# sorted and de-duplicated: every node {#body_reads_published_constant?} visits within its budget, in its
|
|
186
|
+
# order. The hop's answer for any reader is a function of this list and of that reader's publication
|
|
187
|
+
# tables, so the ADR-89 WD2 gate stores it beside a def's return summary and compares it on an edit: a
|
|
188
|
+
# reader the gate skips would otherwise keep a withholding (or a firing) the edited body no longer earns.
|
|
189
|
+
def body_constant_spellings(root)
|
|
190
|
+
spellings = []
|
|
191
|
+
scan_body(root) do |node|
|
|
192
|
+
spelling = constant_spelling(node)
|
|
193
|
+
spellings << spelling unless spelling.nil?
|
|
194
|
+
end
|
|
195
|
+
spellings.uniq.sort
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Yields each node of `root` the hop's scan visits, stopping once {BODY_SCAN_BUDGET} is spent. An explicit
|
|
199
|
+
# worklist rather than recursion, so the node budget is one loop-carried local instead of a shared mutable
|
|
200
|
+
# cell (the engine value-pins a one-element Array counter and then self-flags the guard's own `negative?`
|
|
201
|
+
# as an always-falsey condition — this rule's own medicine).
|
|
202
|
+
def scan_body(root)
|
|
179
203
|
stack = [root]
|
|
180
204
|
budget = BODY_SCAN_BUDGET
|
|
181
205
|
until stack.empty?
|
|
@@ -183,19 +207,24 @@ module Rigor
|
|
|
183
207
|
next unless node.is_a?(Prism::Node)
|
|
184
208
|
|
|
185
209
|
budget -= 1
|
|
186
|
-
return
|
|
187
|
-
return true if published_constant_node?(node, scope)
|
|
210
|
+
return if budget.negative?
|
|
188
211
|
|
|
212
|
+
yield node
|
|
189
213
|
node.rigor_each_child { |child| stack << child }
|
|
190
214
|
end
|
|
191
|
-
false
|
|
192
215
|
end
|
|
193
216
|
|
|
194
217
|
def published_constant_node?(node, scope)
|
|
218
|
+
spelling = constant_spelling(node)
|
|
219
|
+
!spelling.nil? && scope.published_constant?(spelling)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# The name a constant reference asks the publication tables about ({#published_path?}'s rendering for a
|
|
223
|
+
# path), or nil for any other node and for a path that renders no name.
|
|
224
|
+
def constant_spelling(node)
|
|
195
225
|
case node
|
|
196
|
-
when Prism::ConstantReadNode then
|
|
197
|
-
when Prism::ConstantPathNode then
|
|
198
|
-
else false
|
|
226
|
+
when Prism::ConstantReadNode then node.name.to_s
|
|
227
|
+
when Prism::ConstantPathNode then Source::ConstantPath.qualified_name_or_nil(node) || node.name&.to_s
|
|
199
228
|
end
|
|
200
229
|
end
|
|
201
230
|
|
|
@@ -36,10 +36,36 @@ module Rigor
|
|
|
36
36
|
MISSING_HOOK = :method_missing
|
|
37
37
|
# The roots whose project mixins `Inference::ObjectMixins` answers for.
|
|
38
38
|
OBJECT_ROOTS = %w[Object Kernel BasicObject].to_set.freeze
|
|
39
|
-
|
|
39
|
+
# Issue #2792 — the owners of a `method_missing` every object (and every class object) reaches. Core's
|
|
40
|
+
# `BasicObject#method_missing` raises `NoMethodError`, so it opens nothing; core declares none on the others,
|
|
41
|
+
# which are here so that a project or gem RBS reopening `Object`, `Kernel`, `Module` or `Class` with one cannot
|
|
42
|
+
# silence the rule for every receiver.
|
|
43
|
+
UNIVERSAL_HOOK_OWNERS = {
|
|
44
|
+
instance: OBJECT_ROOTS,
|
|
45
|
+
singleton: (OBJECT_ROOTS | %w[Module Class]).freeze
|
|
46
|
+
}.freeze
|
|
47
|
+
private_constant :MISSING_HOOK, :OBJECT_ROOTS, :UNIVERSAL_HOOK_OWNERS
|
|
40
48
|
|
|
41
49
|
module_function
|
|
42
50
|
|
|
51
|
+
# Issue #2792 — whether RBS gives `class_name`'s `side` a `method_missing` from somewhere other than the
|
|
52
|
+
# universal roots: the class itself, an RBS ancestor or an included module (`Delegator` for a
|
|
53
|
+
# `SimpleDelegator`, `class W < SimpleDelegator`, `OpenStruct`). Ruby sends any name the receiver lacks there,
|
|
54
|
+
# private or public, so no name is provably missing. Only the absence verdict reads it: the call's type stays
|
|
55
|
+
# what an unresolved call answers.
|
|
56
|
+
def rbs_missing_hook?(scope, class_name, side)
|
|
57
|
+
definition =
|
|
58
|
+
if side == :singleton
|
|
59
|
+
Rigor::Reflection.singleton_method_definition(class_name, MISSING_HOOK, scope: scope)
|
|
60
|
+
else
|
|
61
|
+
Rigor::Reflection.instance_method_definition(class_name, MISSING_HOOK, scope: scope)
|
|
62
|
+
end
|
|
63
|
+
owner = definition&.defined_in
|
|
64
|
+
return false if owner.nil?
|
|
65
|
+
|
|
66
|
+
!UNIVERSAL_HOOK_OWNERS.fetch(side).include?(owner.to_s.delete_prefix("::"))
|
|
67
|
+
end
|
|
68
|
+
|
|
43
69
|
# Whether the project may answer `method_name` on an instance of `class_name` through one of its RBS
|
|
44
70
|
# ancestors.
|
|
45
71
|
def ancestor_may_answer?(scope, class_name, method_name)
|
|
@@ -39,8 +39,8 @@ module Rigor
|
|
|
39
39
|
SEND_CALLS = %i[send __send__ public_send].freeze
|
|
40
40
|
# Reading the block through these runs it with its own `self`, or does not run it.
|
|
41
41
|
PLAIN_PROC_CALLS = %i[call yield [] === ! arity lambda? parameters nil?].freeze
|
|
42
|
-
# Calls, on any receiver, that can reach a local by name:
|
|
43
|
-
# the `*_eval` family (with an argument)
|
|
42
|
+
# Calls, on any receiver, that can reach a local by name: `binding`, `eval`, `local_variable_get`. The string
|
|
43
|
+
# forms of the `*_eval` family (`STRING_EVAL_CALLS`, with an argument) run in the caller's scope too.
|
|
44
44
|
BINDING_CALLS = %i[binding eval local_variable_get].freeze
|
|
45
45
|
STRING_EVAL_CALLS = %i[instance_eval class_eval module_eval].freeze
|
|
46
46
|
private_constant :STRING_EVAL_CALLS
|
|
@@ -96,7 +96,7 @@ module Rigor
|
|
|
96
96
|
found
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
# `
|
|
99
|
+
# The project's named `def` row counts, keyed by method name and then by owner and side; built once per file.
|
|
100
100
|
def def_rows(scope)
|
|
101
101
|
@def_rows ||= RebindingBlockCallees.def_row_counts(scope)
|
|
102
102
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../inference/scope_indexer"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Analysis
|
|
9
|
+
module CheckRules
|
|
10
|
+
# Issue #3155 — the singleton defs of one file that a later def of the same name certainly replaces, which
|
|
11
|
+
# `def.return-type-mismatch` does not check against the class's signature: Ruby keeps the later body.
|
|
12
|
+
#
|
|
13
|
+
# Both defs must be certain singleton defs of the same class or module body: a direct statement of that body
|
|
14
|
+
# (`def self.x`, or `def K.x` where `K` names the class), or a direct statement of a `class << self` there
|
|
15
|
+
# (`def x`, or `def K.x`). A def in a block (`Class.new do … end`, `Struct.new(…) do … end`), under a condition,
|
|
16
|
+
# loop or `begin`/`rescue`, or in a method body may not run, or runs on another object, so it replaces nothing,
|
|
17
|
+
# and a def written there is never read as replaced either. A reopening in another file is not read.
|
|
18
|
+
#
|
|
19
|
+
# The walk groups defs by the class or module body node only; whether a constant receiver names the class is
|
|
20
|
+
# read from the def's own Scope (`self_type`, the indexer's declaration context), so it derives none itself.
|
|
21
|
+
class ReplacedSingletonDefs
|
|
22
|
+
def initialize(root, scope_index)
|
|
23
|
+
@root = root
|
|
24
|
+
@scope_index = scope_index
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def replaced?(def_node) = offsets.include?(def_node.location.start_offset)
|
|
28
|
+
|
|
29
|
+
# `def (self).x` and `def (K).x` name the receiver inside parentheses.
|
|
30
|
+
def self.unparenthesized(node)
|
|
31
|
+
while node.is_a?(Prism::ParenthesesNode)
|
|
32
|
+
body = node.body
|
|
33
|
+
node = body.is_a?(Prism::StatementsNode) && body.body.size == 1 ? body.body.first : body
|
|
34
|
+
end
|
|
35
|
+
node
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def offsets
|
|
41
|
+
@offsets ||= collect
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def collect
|
|
45
|
+
replaced = Set.new
|
|
46
|
+
each_class_body(@root) do |body|
|
|
47
|
+
last = {}
|
|
48
|
+
certain_singleton_defs(body).each do |def_node|
|
|
49
|
+
replaced << last[def_node.name].location.start_offset if last.key?(def_node.name)
|
|
50
|
+
last[def_node.name] = def_node
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
replaced
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def each_class_body(node, &)
|
|
57
|
+
return if node.nil?
|
|
58
|
+
|
|
59
|
+
yield node.body if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
|
|
60
|
+
node.rigor_each_child { |child| each_class_body(child, &) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def certain_singleton_defs(body)
|
|
64
|
+
statements(body).flat_map do |stmt|
|
|
65
|
+
case stmt
|
|
66
|
+
when Prism::DefNode
|
|
67
|
+
lexical_self?(stmt.receiver, stmt, self_allowed: true) ? [stmt] : []
|
|
68
|
+
when Prism::SingletonClassNode
|
|
69
|
+
next [] unless lexical_self?(stmt.expression, stmt, self_allowed: true)
|
|
70
|
+
|
|
71
|
+
statements(stmt.body).select do |inner|
|
|
72
|
+
inner.is_a?(Prism::DefNode) &&
|
|
73
|
+
(inner.receiver.nil? || lexical_self?(inner.receiver, inner, self_allowed: false))
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
[]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# `at` is the node whose Scope names the class: its `self_type` is the class or its singleton.
|
|
82
|
+
def lexical_self?(node, at, self_allowed:)
|
|
83
|
+
node = self.class.unparenthesized(node)
|
|
84
|
+
return self_allowed if node.is_a?(Prism::SelfNode)
|
|
85
|
+
|
|
86
|
+
self_type = @scope_index[at]&.self_type
|
|
87
|
+
return false unless self_type.respond_to?(:class_name)
|
|
88
|
+
|
|
89
|
+
Inference::ScopeIndexer.def_receiver_targets_lexical_self?(node, self_type.class_name.split("::"))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def statements(body) = body.is_a?(Prism::StatementsNode) ? body.body : []
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -55,8 +55,7 @@ module Rigor
|
|
|
55
55
|
collect_ensure_returns(begin_node)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
#
|
|
59
|
-
# `#collect` returns it.
|
|
58
|
+
# One `{return_node:}` entry per offending `return`, frozen the same way `#collect` returns them.
|
|
60
59
|
def results
|
|
61
60
|
@results.freeze
|
|
62
61
|
end
|
|
@@ -81,6 +81,12 @@ module Rigor
|
|
|
81
81
|
# Issue #1692 (the ADR-47 WD5 amendment) — a call to the `ruby2_keywords` family Ruby 4.1 deprecates. Runs only
|
|
82
82
|
# under an explicit `target_ruby` of 4.1 or later; see {RubyDeprecations}.
|
|
83
83
|
RULE_DEPRECATED_RUBY2_KEYWORDS = "call.deprecated-ruby2-keywords"
|
|
84
|
+
# ADR-128 WD6 / ADR-130 slice 2 (#2686) — a call in plugin source on a `Rigor::Plugin` interface value to a
|
|
85
|
+
# member the interface does not declare. Authored `:warning`, resolved `:off` by every profile and promoted
|
|
86
|
+
# only by the `plugin-api-surface` bleeding-edge feature. The one built-in id under `plugin.`, the prefix a
|
|
87
|
+
# plugin's own rows carry as `plugin.<id>.<rule>`: it is in {ALL_RULES}, so `disable:`, markers and
|
|
88
|
+
# `severity_overrides:` resolve it as a catalogue id before any plugin-id reading.
|
|
89
|
+
RULE_PLUGIN_API_SURFACE = "plugin.api-surface"
|
|
84
90
|
|
|
85
91
|
ALL_RULES = [
|
|
86
92
|
RULE_UNDEFINED_METHOD,
|
|
@@ -117,7 +123,8 @@ module Rigor
|
|
|
117
123
|
RULE_EFFECT_LISKOV_WIDENED,
|
|
118
124
|
RULE_EFFECT_UNKNOWN_LABEL,
|
|
119
125
|
RULE_EFFECT_ANNOTATIONS_UNCHECKED,
|
|
120
|
-
RULE_CONTRADICTING_SIGNATURE
|
|
126
|
+
RULE_CONTRADICTING_SIGNATURE,
|
|
127
|
+
RULE_PLUGIN_API_SURFACE
|
|
121
128
|
].freeze
|
|
122
129
|
|
|
123
130
|
# Backward-compat alias table (ADR-8 § "Backward compatibility"). Existing user code with
|
|
@@ -178,7 +185,7 @@ module Rigor
|
|
|
178
185
|
|
|
179
186
|
# Resolves a user-supplied rule token (`undefined-method`, `call.undefined-method`, or the family wildcard
|
|
180
187
|
# `call`) to the set of canonical rule identifiers it disables. Returns `nil` for `"all"` (the existing
|
|
181
|
-
# wildcard meaning "every rule"),
|
|
188
|
+
# wildcard meaning "every rule"), and an empty array for an unknown token.
|
|
182
189
|
def self.resolve_rule_token(token)
|
|
183
190
|
return nil if token == "all"
|
|
184
191
|
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
@@ -10,7 +10,7 @@ module Rigor
|
|
|
10
10
|
module CheckRules
|
|
11
11
|
# ADR-53 Track B — the engine-owned AST walk that hosts CheckRules' per-node collectors, so a file is
|
|
12
12
|
# traversed once for all of them instead of once per collector (the ADR-52 WD4 model applied to the
|
|
13
|
-
# built-in rules;
|
|
13
|
+
# built-in rules; {Plugin::NodeRuleWalk} drives the same collectors, ADR-53 B4).
|
|
14
14
|
#
|
|
15
15
|
# The walk is a single visit-before-descend DFS over `compact_child_nodes`. It threads the union of the
|
|
16
16
|
# context the hosted collectors need — the per-collector hand-rolled walks each tracked some subset of
|
|
@@ -146,12 +146,14 @@ module Rigor
|
|
|
146
146
|
qualified_prefix = extend_prefix(node, context.qualified_prefix)
|
|
147
147
|
detached_ivar_facet = context.detached_ivar_facet || detached_ivar_facet?(node)
|
|
148
148
|
|
|
149
|
-
Context
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
# Nearly every node leaves all four fields as they were, and a {Context} is immutable, so the parent's
|
|
150
|
+
# is the child's. This runs once per node and was a top allocation site (a Data per node).
|
|
151
|
+
if in_loop_or_block == context.in_loop_or_block && inside_def == context.inside_def &&
|
|
152
|
+
qualified_prefix.equal?(context.qualified_prefix) && detached_ivar_facet == context.detached_ivar_facet
|
|
153
|
+
return context
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
Context.new(in_loop_or_block, qualified_prefix, inside_def, detached_ivar_facet)
|
|
155
157
|
end
|
|
156
158
|
|
|
157
159
|
# True for a node whose descendants write ivars to a store the enclosing class name does not name:
|
|
@@ -123,10 +123,9 @@ module Rigor
|
|
|
123
123
|
@scope_index[node] || @scope_index[node.statements] || @scope_index[node.rescue_clause]
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
# Resolves one clause to `{clause:, names:
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# `StandardError`.
|
|
126
|
+
# Resolves one clause to `{clause:, names:}`, where `names` are the certified exception CLASS names it
|
|
127
|
+
# rescues, or nil when the clause is opaque (any entry unresolved / dynamic / splat / module — the
|
|
128
|
+
# comparisons involving it stay silent). A bare `rescue` names the implicit `StandardError`.
|
|
130
129
|
def clause_info(clause, prefix, scope)
|
|
131
130
|
exceptions = clause.exceptions
|
|
132
131
|
if exceptions.empty?
|
|
@@ -9,9 +9,9 @@ module Rigor
|
|
|
9
9
|
# the envelope `global.write-type-mismatch` and `global.readonly-write` judge a write against.
|
|
10
10
|
#
|
|
11
11
|
# The setter is the authority, not the RBS declaration of the global. Where the two differ, the setter wins:
|
|
12
|
-
# `$;` is declared
|
|
13
|
-
# declared
|
|
14
|
-
# that enforces it and was confirmed on Ruby 4.0.5, whose error the comment quotes.
|
|
12
|
+
# `$;` is declared as a Regexp, a String or nil, but its setter also takes any object with `to_str`, and
|
|
13
|
+
# `$stdout` is declared as an IO, but its setter takes anything that responds to `write`. Each entry names
|
|
14
|
+
# the CRuby function that enforces it and was confirmed on Ruby 4.0.5, whose error the comment quotes.
|
|
15
15
|
#
|
|
16
16
|
# A special whose setter accepts every value is absent, so a write to it is never checked: `$stdin`
|
|
17
17
|
# (ADR-117 WD2), `$_`, `$VERBOSE` / `$-v` / `$-w`, `$DEBUG` / `$-d` and `$=`. So is `$@`, whose setter raises
|
|
@@ -280,8 +280,8 @@ module Rigor
|
|
|
280
280
|
|
|
281
281
|
# The trailing `else` is dead when every subject value is covered by the `when` clauses — the final
|
|
282
282
|
# falsey scope (recorded on the `else` node) narrows the subject to `bot`. Defensive `else` bodies (a
|
|
283
|
-
# bare `raise` / `fail` / `throw`) are deliberate guards, not removable dead code, so
|
|
284
|
-
# skipped.
|
|
283
|
+
# bare `raise` / `fail` / `throw` / `abort` / `exit`) are deliberate guards, not removable dead code, so
|
|
284
|
+
# they are skipped.
|
|
285
285
|
def collect_else(else_clause, subject_name, keyword)
|
|
286
286
|
return if else_clause.nil? || else_clause.statements.nil?
|
|
287
287
|
return if defensive_else?(else_clause)
|
|
@@ -29,7 +29,7 @@ module Rigor
|
|
|
29
29
|
class VoidValueUseCollector
|
|
30
30
|
# The assignment forms whose `value` slot is a value position. `MultiWriteNode` (destructuring) is
|
|
31
31
|
# intentionally excluded — its `value` is a container the void call would have to be spread through,
|
|
32
|
-
# not a direct use, so it is
|
|
32
|
+
# not a direct use, so it is outside the FP-narrow scope.
|
|
33
33
|
WRITE_NODE_CLASSES = [
|
|
34
34
|
Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode,
|
|
35
35
|
Prism::ClassVariableWriteNode, Prism::GlobalVariableWriteNode,
|
|
@@ -100,7 +100,7 @@ module Rigor
|
|
|
100
100
|
return unless value_node.is_a?(Prism::CallNode)
|
|
101
101
|
|
|
102
102
|
scope = @scope_index[value_node]
|
|
103
|
-
origin = scope&.
|
|
103
|
+
origin = scope&.void_origin(value_node)
|
|
104
104
|
return if origin.nil?
|
|
105
105
|
|
|
106
106
|
@results << Result.new(void_node: value_node, origin: origin)
|