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
|
@@ -17,8 +17,8 @@ module Rigor
|
|
|
17
17
|
#
|
|
18
18
|
# assert_kind_of(T, x) → narrow x to T
|
|
19
19
|
# assert_instance_of(T, x) → narrow x to T
|
|
20
|
-
# assert_nil(x) → narrow x to
|
|
21
|
-
# assert_equal(literal, x) → meet x with
|
|
20
|
+
# assert_nil(x) → narrow x to nil
|
|
21
|
+
# assert_equal(literal, x) → meet x with the literal (see "Equality is not identity")
|
|
22
22
|
# assert_match(regex, x) → narrow x to String
|
|
23
23
|
#
|
|
24
24
|
# ### (2) Minitest / Test::Unit `refute_*` / `assert_not_*` (negative) ###
|
|
@@ -26,7 +26,7 @@ module Rigor
|
|
|
26
26
|
# refute_kind_of(T, x) → narrow x AWAY from T
|
|
27
27
|
# refute_instance_of(T, x) → narrow x AWAY from T
|
|
28
28
|
# refute_nil(x) → narrow x AWAY from nil
|
|
29
|
-
# refute_equal(literal, x) → negative
|
|
29
|
+
# refute_equal(literal, x) → negative fact on the literal (the engine keeps x unchanged)
|
|
30
30
|
#
|
|
31
31
|
# Test::Unit's `assert_not_nil(x)` / `assert_not_equal(...)` / `assert_not_kind_of(...)` /
|
|
32
32
|
# `assert_not_instance_of(...)` share the recognizer with `refute_*` (they're aliases).
|
|
@@ -35,8 +35,8 @@ module Rigor
|
|
|
35
35
|
#
|
|
36
36
|
# _(x).must_be_kind_of(T) → narrow x to T
|
|
37
37
|
# _(x).must_be_instance_of(T) → narrow x to T
|
|
38
|
-
# _(x).must_be_nil → narrow x to
|
|
39
|
-
# _(x).must_equal(literal) → meet x with
|
|
38
|
+
# _(x).must_be_nil → narrow x to nil
|
|
39
|
+
# _(x).must_equal(literal) → meet x with the literal (see "Equality is not identity")
|
|
40
40
|
# _(x).must_match(regex) → narrow x to String
|
|
41
41
|
#
|
|
42
42
|
# The legacy bare `x.must_be_kind_of(T)` form (Minitest < 6.0 monkey-patched onto Object) is
|
|
@@ -48,7 +48,7 @@ module Rigor
|
|
|
48
48
|
#
|
|
49
49
|
# _(x).wont_be_kind_of(T) → narrow x AWAY from T
|
|
50
50
|
# _(x).wont_be_nil → narrow x AWAY from nil
|
|
51
|
-
# _(x).wont_equal(literal) → negative
|
|
51
|
+
# _(x).wont_equal(literal) → negative fact on the literal (the engine keeps x unchanged)
|
|
52
52
|
#
|
|
53
53
|
# ## Equality is not identity
|
|
54
54
|
#
|
|
@@ -10,7 +10,8 @@ module Rigor
|
|
|
10
10
|
module Plugin
|
|
11
11
|
class Pundit < Rigor::Plugin::Base
|
|
12
12
|
# Walks the configured policy-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
|
|
13
|
-
# Prism, and collects classes whose immediate superclass is one of the configured base classes
|
|
13
|
+
# Prism, and collects classes whose immediate superclass is one of the configured base classes and
|
|
14
|
+
# whose name ends in `Policy`.
|
|
14
15
|
#
|
|
15
16
|
# For each discovered policy class, the discoverer collects every instance-side `def name?` predicate
|
|
16
17
|
# method. Non-predicate methods (`initialize`, `resolve`, helper methods) are ignored — Pundit's
|
|
@@ -192,15 +192,10 @@ module Rigor
|
|
|
192
192
|
first.unescaped
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
# Pulls the interpolation hash from the call's arguments. The trailing
|
|
195
|
+
# Pulls the interpolation hash from the call's arguments. The trailing hash argument (or
|
|
196
196
|
# `Prism::KeywordHashNode`) carries both reserved I18n options (`default:`, `scope:`, …) and
|
|
197
|
-
# interpolation variables. Returns
|
|
198
|
-
#
|
|
199
|
-
# :has_default => bool,
|
|
200
|
-
# :all_keys => Set<Symbol> (every assoc key in the hash),
|
|
201
|
-
# :non_reserved => Set<Symbol> (keys NOT in RESERVED_OPTION_KEYS),
|
|
202
|
-
# :hash_node => Prism::Node (or nil)
|
|
203
|
-
# }
|
|
197
|
+
# interpolation variables. Returns `:has_default`, `:all_keys` (every assoc key), `:non_reserved`
|
|
198
|
+
# (keys outside RESERVED_OPTION_KEYS) and `:hash_node` (nil when there is no hash argument).
|
|
204
199
|
#
|
|
205
200
|
# Note: a reserved option key (e.g. `count:`) can also serve as an interpolation value when the
|
|
206
201
|
# locale's leaf string has `%{count}`. The analyzer therefore checks the missing-placeholder set
|
|
@@ -11,9 +11,8 @@ module Rigor
|
|
|
11
11
|
# placeholder names. The actual translated values are not retained — the analyzer doesn't need them
|
|
12
12
|
# and keeping them would bloat the cache slice.
|
|
13
13
|
class LocaleIndex
|
|
14
|
-
# `placeholders`
|
|
15
|
-
# locale
|
|
16
|
-
# `locale_name => Symbol` (`:string` / `:array` / `:hash`).
|
|
14
|
+
# `placeholders` maps each locale name to the `%{var}` placeholder names of its leaf. `value_kinds`
|
|
15
|
+
# maps each locale name to the leaf's kind (`:string` / `:array` / `:hash` / `:scalar`).
|
|
17
16
|
Entry = Data.define(:dotted_key, :placeholders, :value_kinds) do
|
|
18
17
|
def locales
|
|
19
18
|
placeholders.keys
|
|
@@ -36,8 +36,8 @@ module Rigor
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def load
|
|
39
|
-
per_key = {} # dotted_key => { locale =>
|
|
40
|
-
per_key_kinds = {} # dotted_key => { locale =>
|
|
39
|
+
per_key = {} # dotted_key => { locale => placeholder names }
|
|
40
|
+
per_key_kinds = {} # dotted_key => { locale => leaf kind }
|
|
41
41
|
locales = Set.new
|
|
42
42
|
|
|
43
43
|
locale_files.each do |path|
|
|
@@ -98,12 +98,9 @@ module Rigor
|
|
|
98
98
|
# leaves are *not* recorded as entries themselves — only their descendants — but every leaf scalar
|
|
99
99
|
# / array IS recorded.
|
|
100
100
|
#
|
|
101
|
-
# `breadcrumbs` is
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
# every node plus an intermediate result Array at every level — millions of short-lived objects and
|
|
105
|
-
# the run's top allocation site. The dotted key is still materialised once per leaf (it has to
|
|
106
|
-
# be); everything else is now allocation-free traversal.
|
|
101
|
+
# `breadcrumbs` is one mutable stack reused across the walk (push before recursing, pop after). A
|
|
102
|
+
# per-node Array allocation was the run's top allocation site on the 530-file Mastodon corpus; only
|
|
103
|
+
# the dotted key is materialised, once per leaf.
|
|
107
104
|
def each_flattened(node, breadcrumbs, &)
|
|
108
105
|
if node.is_a?(Hash)
|
|
109
106
|
node.each do |k, v|
|
|
@@ -65,7 +65,7 @@ module Rigor
|
|
|
65
65
|
# implicit `*_path` / `*_url` helper call, is shadowed by a same-file binding, or is in a
|
|
66
66
|
# suppressed directory. ADR-37: the engine owns the walk; the same-file `shadowing` set is built
|
|
67
67
|
# once as the node-rule file context.
|
|
68
|
-
def violations_for(call_node:, helper_table:, shadowing:, path:)
|
|
68
|
+
def violations_for(call_node:, helper_table:, shadowing:, path:, scope: nil, context: nil)
|
|
69
69
|
return [] unless call_node.is_a?(Prism::CallNode) && implicit_helper_call?(call_node)
|
|
70
70
|
|
|
71
71
|
name = call_node.name.to_s
|
|
@@ -88,11 +88,209 @@ module Rigor
|
|
|
88
88
|
# Recognised custom / dynamic helper (no arity to check), or a directory where helpers aren't
|
|
89
89
|
# customarily resolved.
|
|
90
90
|
[]
|
|
91
|
+
elsif self_resolves_method?(name, scope, context)
|
|
92
|
+
[]
|
|
91
93
|
else
|
|
92
94
|
[unknown_helper_violation(name, helper_table)]
|
|
93
95
|
end
|
|
94
96
|
end
|
|
95
97
|
|
|
98
|
+
# True when `name` is already a method on the call's `self`, asked of the engine rather than a
|
|
99
|
+
# plugin-side name list: the project's discovered methods (`def`, `attr_*`, the singleton side) through
|
|
100
|
+
# the project's own ancestry, then the RBS ancestry. A truncated walk (`maybe`) declines too — a definer
|
|
101
|
+
# past the budget cannot be ruled out, and a false positive outranks a missed typo.
|
|
102
|
+
#
|
|
103
|
+
# `self` is read from the node's lexical ancestors (`NodeContext#ancestors`), because the scope a
|
|
104
|
+
# `node_rule` receives is the file's, not the call site's. {#call_site} names the enclosing class or
|
|
105
|
+
# module and the kinds `self` may be; where the kind is not decidable (a block in a class body, a
|
|
106
|
+
# `def obj.m`) both are asked, so a definer on either side declines and a typo on neither still reports.
|
|
107
|
+
def self_resolves_method?(name, scope, context)
|
|
108
|
+
return false if scope.nil? || context.nil?
|
|
109
|
+
|
|
110
|
+
site = call_site(context.ancestors)
|
|
111
|
+
return false if site.nil?
|
|
112
|
+
# An owner no constant spelling names (`class foo::Bar`) cannot be asked about.
|
|
113
|
+
return true if site.names.empty?
|
|
114
|
+
# A module is mixed into classes that cannot be seen from where it is defined (a controller concern).
|
|
115
|
+
return true if site.module_owner && FRAMEWORK_METHODS.key?(name)
|
|
116
|
+
|
|
117
|
+
site.names.product(site.kinds).any? { |class_name, kind| resolves_on?(class_name, name, kind, scope) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def resolves_on?(class_name, name, kind, scope)
|
|
121
|
+
return true unless Rigor::Reflection.project_method_defined(class_name, name, kind: kind, source: :either,
|
|
122
|
+
scope: scope).no?
|
|
123
|
+
return true unless Rigor::Reflection.rbs_method_defined(class_name, name, kind: kind, scope: scope).no?
|
|
124
|
+
|
|
125
|
+
bases = FRAMEWORK_METHODS[name]
|
|
126
|
+
!bases.nil? && !Rigor::Reflection.ancestry_reaches(class_name, bases, kind: kind, scope: scope).no?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Rails' own `*_path` / `*_url` methods on a controller or mailer, read from the Rails source (rails/rails
|
|
130
|
+
# main, 1105756f42), each with the framework classes a project class names directly as its superclass. A
|
|
131
|
+
# project without that framework's RBS cannot see these through the ancestry, so a class whose ancestry
|
|
132
|
+
# reaches one of the listed bases declines for that name; a module declines for every name, being mixed
|
|
133
|
+
# into classes that cannot be seen.
|
|
134
|
+
#
|
|
135
|
+
# - `controller_path`: AbstractController::Base, actionpack/lib/abstract_controller/base.rb:157 (instance),
|
|
136
|
+
# :117 (class); every framework base below inherits it (metal.rb:185, base.rb:206, api.rb:91,
|
|
137
|
+
# actionmailer/lib/action_mailer/base.rb:479).
|
|
138
|
+
# - `append_view_path` / `prepend_view_path`: `ActionView::ViewPaths` (actionview/lib/action_view/
|
|
139
|
+
# view_paths.rb:113, :124 instance, :46, :57 class), which `AbstractController::Rendering` includes
|
|
140
|
+
# (abstract_controller/rendering.rb:18); `ActionController::Base` (base.rb:270), `::API` (api.rb:115) and
|
|
141
|
+
# `ActionMailer::Base` (action_mailer/base.rb:489) include that.
|
|
142
|
+
# - `helpers_path`: a class attribute of `ActionController::Helpers` (metal/helpers.rb:68), included by
|
|
143
|
+
# `ActionController::Base` (base.rb:273).
|
|
144
|
+
# - `edit_` / `new_` `polymorphic_path` / `polymorphic_url`: actionpack/lib/action_dispatch/routing/
|
|
145
|
+
# polymorphic_routes.rb:163-166, through `UrlFor` in `ActionController::Base` (base.rb:274) and `::API`
|
|
146
|
+
# (api.rb:117), and in `ActionMailer::Base`, which actionmailer/lib/action_mailer/railtie.rb:40
|
|
147
|
+
# includes `AbstractController::UrlFor` into, whose `ActionDispatch::Routing::UrlFor` includes
|
|
148
|
+
# `PolymorphicRoutes` (action_dispatch/routing/url_for.rb:92). (`polymorphic_path` / `polymorphic_url` are in {BUILTIN_PASSTHROUGH}.)
|
|
149
|
+
FRAMEWORK_CONTROLLERS = %w[
|
|
150
|
+
AbstractController::Base ActionController::Metal ActionController::Base ActionController::API
|
|
151
|
+
ActionMailer::Base
|
|
152
|
+
].freeze
|
|
153
|
+
VIEW_PATH_BASES = %w[ActionController::Base ActionController::API ActionMailer::Base].freeze
|
|
154
|
+
URL_FOR_BASES = %w[ActionController::Base ActionController::API ActionMailer::Base].freeze
|
|
155
|
+
FRAMEWORK_METHODS = {
|
|
156
|
+
"controller_path" => FRAMEWORK_CONTROLLERS,
|
|
157
|
+
"append_view_path" => VIEW_PATH_BASES,
|
|
158
|
+
"prepend_view_path" => VIEW_PATH_BASES,
|
|
159
|
+
"helpers_path" => %w[ActionController::Base].freeze,
|
|
160
|
+
"edit_polymorphic_path" => URL_FOR_BASES,
|
|
161
|
+
"edit_polymorphic_url" => URL_FOR_BASES,
|
|
162
|
+
"new_polymorphic_path" => URL_FOR_BASES,
|
|
163
|
+
"new_polymorphic_url" => URL_FOR_BASES
|
|
164
|
+
}.freeze
|
|
165
|
+
|
|
166
|
+
# What a call site's lexical position says about `self`: the names the enclosing class or module can be
|
|
167
|
+
# known by, whether the owner is a module, and the kinds (`:instance`, `:singleton`) `self` may be.
|
|
168
|
+
CallSite = Struct.new(:names, :module_owner, :kinds, keyword_init: true)
|
|
169
|
+
|
|
170
|
+
# The {CallSite} of a node under `ancestors` (outermost first), or nil when it is in no class or module,
|
|
171
|
+
# which leaves the route-table verdict as it was.
|
|
172
|
+
def call_site(ancestors)
|
|
173
|
+
owner_index = ancestors.rindex { |n| n.is_a?(Prism::ClassNode) || n.is_a?(Prism::ModuleNode) }
|
|
174
|
+
member_site = member_class_site(ancestors, owner_index)
|
|
175
|
+
return member_site if member_site
|
|
176
|
+
return nil if owner_index.nil?
|
|
177
|
+
|
|
178
|
+
CallSite.new(names: owner_names(ancestors, owner_index),
|
|
179
|
+
module_owner: ancestors[owner_index].is_a?(Prism::ModuleNode),
|
|
180
|
+
kinds: self_kinds(ancestors[(owner_index + 1)..]))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
BOTH_KINDS = %i[instance singleton].freeze
|
|
184
|
+
MEMBER_CLASS_CONSTRUCTORS = { Struct: :new, Data: :define }.freeze
|
|
185
|
+
|
|
186
|
+
# The call site of a node inside the block of `Struct.new(...) { }` / `Data.define(...) { }`, which is the
|
|
187
|
+
# body of a class the constructor creates: `self` is that class, not the lexical `class` around it (or
|
|
188
|
+
# none). A plain constant write (`Point = …`, `Outer::Point = …`, nested under the lexical owner) names it,
|
|
189
|
+
# and the engine knows its members. Everything else declines (`names` empty): an unassigned block, `P ||= …`,
|
|
190
|
+
# `… end.freeze`, a dynamic path. nil when the node is not in such a block below its lexical owner.
|
|
191
|
+
def member_class_site(ancestors, owner_index)
|
|
192
|
+
index = ancestors.rindex { |n| member_class_call?(n) && ancestors[ancestors.index(n) + 1].equal?(n.block) }
|
|
193
|
+
return nil if index.nil? || (owner_index && index < owner_index)
|
|
194
|
+
|
|
195
|
+
kinds = self_kinds(ancestors[(index + 1)..])
|
|
196
|
+
names = index.positive? ? written_class_names(ancestors, owner_index, ancestors[index - 1], ancestors[index]) : []
|
|
197
|
+
names = struct_class_names(ancestors[index]) if names.empty?
|
|
198
|
+
CallSite.new(names: names, module_owner: false, kinds: kinds)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Issue #2968 — `Struct.new("Named", :a) { }` names its class `Struct::Named` without a constant write.
|
|
202
|
+
def struct_class_names(call)
|
|
203
|
+
first = call.arguments&.arguments&.first
|
|
204
|
+
return [] unless call.name == :new && first.is_a?(Prism::StringNode)
|
|
205
|
+
|
|
206
|
+
name = first.unescaped
|
|
207
|
+
name.match?(/\A[A-Z][A-Za-z0-9_]*\z/) ? ["Struct::#{name}"] : []
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def written_class_names(ancestors, owner_index, written, call)
|
|
211
|
+
local =
|
|
212
|
+
case written
|
|
213
|
+
when Prism::ConstantWriteNode then written.value.equal?(call) ? written.name.to_s : nil
|
|
214
|
+
when Prism::ConstantPathWriteNode then written.value.equal?(call) ? declared_name(written.target) : nil
|
|
215
|
+
end
|
|
216
|
+
return [] if local.nil?
|
|
217
|
+
return [local.delete_prefix("::")] if local.start_with?("::")
|
|
218
|
+
|
|
219
|
+
prefixes = owner_index ? owner_names(ancestors, owner_index) : [""]
|
|
220
|
+
nested = prefixes.map { |prefix| prefix.empty? ? local : "#{prefix}::#{local}" }
|
|
221
|
+
# `Outer::R = …` resolves `Outer` lexically or at the root, as a compact `class Foo::Bar` does.
|
|
222
|
+
written.is_a?(Prism::ConstantPathWriteNode) ? (nested + [local]).uniq : nested
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def member_class_call?(node)
|
|
226
|
+
return false unless node.is_a?(Prism::CallNode) && node.block.is_a?(Prism::BlockNode)
|
|
227
|
+
|
|
228
|
+
receiver = node.receiver
|
|
229
|
+
receiver_name =
|
|
230
|
+
case receiver
|
|
231
|
+
when Prism::ConstantReadNode then receiver.name
|
|
232
|
+
when Prism::ConstantPathNode then receiver.parent.nil? ? receiver.name : nil
|
|
233
|
+
end
|
|
234
|
+
!receiver_name.nil? && MEMBER_CLASS_CONSTRUCTORS[receiver_name] == node.name
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# The kinds `self` may be for a node under the nodes between its owner and itself. The nearest `def`
|
|
238
|
+
# decides (blocks inside a method keep its `self`); with no `def`, a bare class body is the class object
|
|
239
|
+
# itself, while a block or lambda there may run on another object (`before_action { }`, `included do`,
|
|
240
|
+
# `define_method`, `Class.new`), so both kinds are asked.
|
|
241
|
+
def self_kinds(inner)
|
|
242
|
+
def_index = inner.rindex { |n| n.is_a?(Prism::DefNode) }
|
|
243
|
+
if def_index
|
|
244
|
+
node = inner[def_index]
|
|
245
|
+
return BOTH_KINDS if node.receiver && !node.receiver.is_a?(Prism::SelfNode)
|
|
246
|
+
return [:singleton] if node.receiver
|
|
247
|
+
|
|
248
|
+
return inner[0...def_index].any?(Prism::SingletonClassNode) ? [:singleton] : [:instance]
|
|
249
|
+
end
|
|
250
|
+
return BOTH_KINDS if inner.any? { |n| block_scope?(n) }
|
|
251
|
+
if inner.any? { |n| n.is_a?(Prism::SingletonClassNode) && !n.expression.is_a?(Prism::SelfNode) }
|
|
252
|
+
return BOTH_KINDS
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
[:singleton]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def block_scope?(node)
|
|
259
|
+
node.is_a?(Prism::BlockNode) || node.is_a?(Prism::LambdaNode)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# The names the enclosing class or module can be known by, `[]` when a declaration's name is dynamic. A
|
|
263
|
+
# bare `class Foo` is exactly `Enclosing::Foo`. A compact `class Foo::Bar` may resolve `Foo` lexically or
|
|
264
|
+
# at the root, so both spellings are tried. A rooted declaration (`class ::Foo`) names the root constant
|
|
265
|
+
# alone.
|
|
266
|
+
def owner_names(ancestors, owner_index)
|
|
267
|
+
chain = ancestors[0..owner_index].select { |n| n.is_a?(Prism::ClassNode) || n.is_a?(Prism::ModuleNode) }
|
|
268
|
+
names = [""]
|
|
269
|
+
chain.each do |decl|
|
|
270
|
+
local = declared_name(decl.constant_path)
|
|
271
|
+
return [] if local.nil?
|
|
272
|
+
|
|
273
|
+
names = if local.start_with?("::")
|
|
274
|
+
[local.delete_prefix("::")]
|
|
275
|
+
else
|
|
276
|
+
nested = names.map { |prefix| prefix.empty? ? local : "#{prefix}::#{local}" }
|
|
277
|
+
decl.constant_path.is_a?(Prism::ConstantPathNode) ? (nested + [local]).uniq : nested
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
names
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def declared_name(node)
|
|
284
|
+
case node
|
|
285
|
+
when Prism::ConstantReadNode then node.name.to_s
|
|
286
|
+
when Prism::ConstantPathNode
|
|
287
|
+
return "::#{node.name}" if node.parent.nil?
|
|
288
|
+
|
|
289
|
+
parent = declared_name(node.parent)
|
|
290
|
+
parent && "#{parent}::#{node.name}"
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
96
294
|
# Walks the AST once and returns the Set of names that shadow a route helper for this file. Includes:
|
|
97
295
|
#
|
|
98
296
|
# - `def name` declarations (any level — the per-method-scope visibility model Ruby uses means a
|
|
@@ -124,7 +322,6 @@ module Rigor
|
|
|
124
322
|
if name
|
|
125
323
|
names << name
|
|
126
324
|
elsif call_node.name == :subject && call_node.arguments.nil?
|
|
127
|
-
# Bare `subject { ... }` defines `:subject` itself.
|
|
128
325
|
names << "subject"
|
|
129
326
|
end
|
|
130
327
|
end
|
|
@@ -201,8 +398,7 @@ module Rigor
|
|
|
201
398
|
end
|
|
202
399
|
|
|
203
400
|
def unknown_helper_violation(name, helper_table)
|
|
204
|
-
# ADR-39 / boilerplate 0c — the shared DidYouMean-backed suggester
|
|
205
|
-
# Levenshtein this module used to carry.
|
|
401
|
+
# ADR-39 / boilerplate 0c — the shared DidYouMean-backed suggester.
|
|
206
402
|
suggestion = Rigor::Plugin::Base.suggest(name, helper_table.names)
|
|
207
403
|
message = "no route helper `#{name}`"
|
|
208
404
|
message += " (did you mean `#{suggestion}`?)" if suggestion
|
|
@@ -16,7 +16,7 @@ module Rigor
|
|
|
16
16
|
#
|
|
17
17
|
# Scope:
|
|
18
18
|
#
|
|
19
|
-
# - Recognises the standard
|
|
19
|
+
# - Recognises the standard controllers (`:sessions`, `:passwords`, `:confirmations`, `:unlocks`,
|
|
20
20
|
# `:registrations`, `:omniauth_callbacks`).
|
|
21
21
|
# - Honours `skip: [...]` to suppress controllers the project disables.
|
|
22
22
|
# - Honours `path: "..."` to remap the URL path (does NOT remap the helper-name prefix — Devise uses
|
|
@@ -162,8 +162,7 @@ module Rigor
|
|
|
162
162
|
OMNIAUTH_SUFFIXES
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
# ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available)
|
|
166
|
-
# replacing the tiny AS-replica this module used to duplicate from `RoutesParser`.
|
|
165
|
+
# ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available).
|
|
167
166
|
def singularize(word)
|
|
168
167
|
Rigor::Plugin::Inflector.singularize(word)
|
|
169
168
|
end
|
|
@@ -7,7 +7,7 @@ require "prism"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class RailsRoutes < Rigor::Plugin::Base
|
|
10
|
-
# Walks
|
|
10
|
+
# Walks the configured `helper_paths:` directories (default `app/`) and extracts every public
|
|
11
11
|
# module-level method name so the analyzer's `unknown-helper` rule does not false-fire on calls to
|
|
12
12
|
# project-defined `*_path` / `*_url` helpers (the canonical Rails pattern: a `UrlHelper` module
|
|
13
13
|
# exposing `full_asset_url`, `host_to_url`, `frontend_asset_url`, and so on).
|
|
@@ -409,14 +409,9 @@ module Rigor
|
|
|
409
409
|
end
|
|
410
410
|
end
|
|
411
411
|
|
|
412
|
-
# ADR-39: inflection delegates to the shared `Rigor::Plugin::Inflector`, which
|
|
413
|
-
# `ActiveSupport::Inflector` (the authority Rails
|
|
414
|
-
#
|
|
415
|
-
# AS-replica these methods used to carry — every special case they accreted (`news` uncountable,
|
|
416
|
-
# `media`→`medium`, `databases`→`database`, `custom_css` ss-preservation,
|
|
417
|
-
# `async_refresh`→`async_refreshes`) was reverse-engineered `ActiveSupport::Inflector` behaviour,
|
|
418
|
-
# so the real inflector produces the same result for those and the correct one for the words the
|
|
419
|
-
# replica never covered.
|
|
412
|
+
# ADR-39: inflection delegates to the shared `Rigor::Plugin::Inflector`, which uses the real
|
|
413
|
+
# `ActiveSupport::Inflector` (the authority Rails uses to generate helper names) when available,
|
|
414
|
+
# and a built-in approximation otherwise.
|
|
420
415
|
def singularize(word)
|
|
421
416
|
Rigor::Plugin::Inflector.singularize(word)
|
|
422
417
|
end
|
|
@@ -1286,7 +1281,7 @@ module Rigor
|
|
|
1286
1281
|
return nil unless plural
|
|
1287
1282
|
|
|
1288
1283
|
# Rails appends `_index_path` to the index helper name when the singular form of the resource
|
|
1289
|
-
# matches the plural form
|
|
1284
|
+
# matches the plural form (uncountable nouns included). The collision
|
|
1290
1285
|
# would otherwise put both the index (`:id`-less) and show (`:id`-bearing) helpers under the same
|
|
1291
1286
|
# name, and Rails disambiguates by suffixing the index form. Mastodon's `resources
|
|
1292
1287
|
# :reblogged_by, controller: :reblogged_by_accounts, only: :index` and similar rely on this —
|
|
@@ -1432,8 +1427,6 @@ module Rigor
|
|
|
1432
1427
|
end
|
|
1433
1428
|
|
|
1434
1429
|
# ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available).
|
|
1435
|
-
# Previously a hand-tuned AS-replica kept in sync with `Context#singularize`; both now share the
|
|
1436
|
-
# one authoritative implementation.
|
|
1437
1430
|
def singularize_word(word)
|
|
1438
1431
|
Rigor::Plugin::Inflector.singularize(word)
|
|
1439
1432
|
end
|
|
@@ -14,7 +14,7 @@ module Rigor
|
|
|
14
14
|
# rigor-rails-routes — validates Rails route-helper calls (`users_path`, `edit_user_path(@user)`, …)
|
|
15
15
|
# against the project's `config/routes.rb`.
|
|
16
16
|
#
|
|
17
|
-
# Tier 1A of the [Rails plugins roadmap](
|
|
17
|
+
# Tier 1A of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
18
18
|
# Statically interprets the routes DSL via Prism — no `rails` runtime dependency. Recognised v0.1.0
|
|
19
19
|
# surface:
|
|
20
20
|
#
|
|
@@ -205,11 +205,12 @@ module Rigor
|
|
|
205
205
|
Analyzer.collect_shadowing_names(root)
|
|
206
206
|
end
|
|
207
207
|
|
|
208
|
-
node_rule Prism::CallNode do |node,
|
|
208
|
+
node_rule Prism::CallNode do |node, scope, path, shadowing, context|
|
|
209
209
|
table = helper_table_or_nil
|
|
210
210
|
next [] if table.nil? || table.empty?
|
|
211
211
|
|
|
212
|
-
Analyzer.violations_for(call_node: node, helper_table: table, shadowing: shadowing, path: path
|
|
212
|
+
Analyzer.violations_for(call_node: node, helper_table: table, shadowing: shadowing, path: path,
|
|
213
|
+
scope: scope, context: context).map do |violation|
|
|
213
214
|
diagnostic(node, path: path, message: violation.message, severity: violation.severity, rule: violation.rule)
|
|
214
215
|
end
|
|
215
216
|
end
|
|
@@ -35,7 +35,8 @@ module Rigor
|
|
|
35
35
|
class RbsInline < Rigor::Plugin::Base
|
|
36
36
|
# Synthesizer callable invoked once per project Ruby source file by `Environment.for_project` at
|
|
37
37
|
# env-build time. Returns the synthesised RBS source as a String, or `nil` when the file contributes
|
|
38
|
-
# nothing (no
|
|
38
|
+
# nothing (no annotation in the default mode, no magic comment under `require_magic_comment: true`,
|
|
39
|
+
# or a parse error per WD6).
|
|
39
40
|
class Synthesizer
|
|
40
41
|
# An RDoc directive comment: `#:` followed by a bare word (RDoc allows a hyphen, as in `:call-seq:`)
|
|
41
42
|
# and a closing colon. RDoc predates rbs-inline by about two decades and `#:<name>:` is
|
|
@@ -286,8 +286,8 @@ module Rigor
|
|
|
286
286
|
# project class, which says nothing about who wrote `==`.
|
|
287
287
|
def project_defines?(class_name, operator, scope)
|
|
288
288
|
OPERATOR_OWNER_CHAINS.fetch([class_name, operator]).any? do |name|
|
|
289
|
-
!
|
|
290
|
-
|
|
289
|
+
!Rigor::Reflection.project_method_defined(name, operator, kind: :instance, source: :either,
|
|
290
|
+
scope: scope).no?
|
|
291
291
|
end
|
|
292
292
|
end
|
|
293
293
|
|
|
@@ -358,7 +358,7 @@ module Rigor
|
|
|
358
358
|
# signature, a core ancestor, or the project's source) is kept whole. `Object`, `BasicObject` and a module
|
|
359
359
|
# String or Symbol includes otherwise read `String | Symbol`, as the String they most often are, so a String
|
|
360
360
|
# method called after the matcher reports nothing. Any other module is UNDECIDED: a passing value may include
|
|
361
|
-
# it without being a String. (
|
|
361
|
+
# it without being a String. (Decision shared with #2256.)
|
|
362
362
|
def module_match_member(member, scope)
|
|
363
363
|
return nil unless member.is_a?(Rigor::Type::Nominal) && member.type_args.empty?
|
|
364
364
|
|
|
@@ -390,8 +390,8 @@ module Rigor
|
|
|
390
390
|
# `def`, an included module, or a `define_method`.
|
|
391
391
|
def project_defines_on_chain?(class_name, method_name, scope)
|
|
392
392
|
CORE_ANCESTOR_CHAINS.fetch(class_name, [class_name]).any? do |name|
|
|
393
|
-
!
|
|
394
|
-
|
|
393
|
+
!Rigor::Reflection.project_method_defined(name, method_name, kind: :instance, source: :either,
|
|
394
|
+
scope: scope).no?
|
|
395
395
|
end
|
|
396
396
|
end
|
|
397
397
|
|
|
@@ -14,7 +14,7 @@ module Rigor
|
|
|
14
14
|
# narrows `expect(x).to MATCHER` assertions downstream, and binds `let`/`subject` locals to their
|
|
15
15
|
# inferred return types inside `it` bodies.
|
|
16
16
|
#
|
|
17
|
-
# Tier 3A of the [Rails plugins roadmap](
|
|
17
|
+
# Tier 3A of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
18
18
|
# Ships four checks:
|
|
19
19
|
#
|
|
20
20
|
# 1. **Duplicate `let` / `subject` declarations** in the same scope (`warning`). RSpec's runtime lets
|
|
@@ -44,7 +44,7 @@ module Rigor
|
|
|
44
44
|
# - **No shared-context resolution.** `include_context`, `shared_context`, and `it_behaves_like` are
|
|
45
45
|
# recognised as scope-opening calls but their declarations are not pulled into the host scope.
|
|
46
46
|
# - **Constant validation is not done here.** `RSpec.describe SomeClass do` does not validate
|
|
47
|
-
# `SomeClass`; the engine's
|
|
47
|
+
# `SomeClass`; the engine's constant-resolution diagnostics already catch that.
|
|
48
48
|
class Rspec < Rigor::Plugin::Base
|
|
49
49
|
manifest(
|
|
50
50
|
id: "rspec",
|
|
@@ -30,7 +30,7 @@ module Rigor
|
|
|
30
30
|
# - `route_to(...)` / `redirect_to(...)` — needs the routes table from `rigor-rails-routes`
|
|
31
31
|
# (`:helper_table` cross-plugin fact). Future slice.
|
|
32
32
|
# - `have_enqueued_job(JobClass)` / `have_enqueued_mail(MailerClass)` — class-existence check overlaps
|
|
33
|
-
# with engine's
|
|
33
|
+
# with the engine's constant-resolution diagnostics; queued behind a decision on which surface owns the
|
|
34
34
|
# diagnostic.
|
|
35
35
|
# - `have_received(:method)` — overlaps with engine's `call.undefined-method`; same coordination
|
|
36
36
|
# question.
|
|
@@ -44,9 +44,8 @@ module Rigor
|
|
|
44
44
|
description: "Validates rspec-rails behavioral matchers (have_http_status floor)."
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
# ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is
|
|
48
|
-
# logic. The diagnostic points at the matcher name (`message_loc`)
|
|
49
|
-
# `message_loc || location` positioning.
|
|
47
|
+
# ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is pure per-node
|
|
48
|
+
# logic. The diagnostic points at the matcher name (`message_loc`).
|
|
50
49
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
51
50
|
violation = HaveHttpStatusAnalyzer.violation_for(node)
|
|
52
51
|
next [] unless violation
|
|
@@ -31,7 +31,7 @@ module Rigor
|
|
|
31
31
|
# have_db_index(:col)
|
|
32
32
|
#
|
|
33
33
|
# All look up `:col` against the model's `columns:` (the published `:model_index` fact's flat
|
|
34
|
-
#
|
|
34
|
+
# name list per model — see `Analyzer#column_names`). Unknown columns fire `unknown-column`.
|
|
35
35
|
#
|
|
36
36
|
# ### Association matchers
|
|
37
37
|
#
|
|
@@ -107,8 +107,7 @@ module Rigor
|
|
|
107
107
|
|
|
108
108
|
# A direct matcher invocation is a `CallNode` whose `name` is in `MATCHER_TABLE` and whose first
|
|
109
109
|
# argument is a `SymbolNode`. The chain shape (`should`, `expect(...).to`, `is_expected.to`) is
|
|
110
|
-
# irrelevant
|
|
111
|
-
# regardless of the surrounding chain.
|
|
110
|
+
# irrelevant: the engine visits the inner matcher call, so the diagnostic fires on it regardless.
|
|
112
111
|
def matcher_invocation?(node)
|
|
113
112
|
node.is_a?(Prism::CallNode) && MATCHER_TABLE.key?(node.name) && symbol_first_arg?(node)
|
|
114
113
|
end
|
|
@@ -172,11 +171,10 @@ module Rigor
|
|
|
172
171
|
end
|
|
173
172
|
|
|
174
173
|
# `entry` is the published fact's per-model Hash: `{ table:, columns:, associations:, ... }`.
|
|
175
|
-
# `columns:` is a flat
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
# read here.
|
|
174
|
+
# `columns:` is a flat list of column names (`Activerecord#index_to_published_hash`); `associations:`
|
|
175
|
+
# is a list of rows with `:name`, `:kind` (`:singular` | `:collection`), `:nullable`, `:polymorphic`,
|
|
176
|
+
# and a `:target` that is nil on polymorphic rows — the row shape `ModelDiscoverer#build_association_row`
|
|
177
|
+
# publishes. Only `:name` and `:kind` are read here.
|
|
180
178
|
def column_names(entry) = entry[:columns] || []
|
|
181
179
|
def association_rows(entry) = entry[:associations] || []
|
|
182
180
|
def find_association(entry, name) = association_rows(entry).find { |a| a[:name] == name.to_s }
|
|
@@ -98,9 +98,10 @@ module Rigor
|
|
|
98
98
|
# Cached separately from `:worker_index` because the two invalidate on different files: editing a
|
|
99
99
|
# schedule changes which workers are reached without touching `app/workers` at all. The `watch:` roots
|
|
100
100
|
# the glob at the working directory so that CREATING a schedule file — not just editing one — is seen. Each
|
|
101
|
-
# configured path is escaped to a literal name, so `sidekiq[1].yml` is watched as that file
|
|
101
|
+
# configured path is escaped to a literal name, so `sidekiq[1].yml` is watched as that file, and an
|
|
102
|
+
# absolute path is watched under its own directory (`RootedGlob.split_file`, #2222).
|
|
102
103
|
producer :scheduled_workers,
|
|
103
|
-
watch: -> {
|
|
104
|
+
watch: -> { @schedule_paths.map { |path| Rigor::RootedGlob.split_file(path) } } do |_params|
|
|
104
105
|
ScheduleScan.new(
|
|
105
106
|
io_boundary: io_boundary,
|
|
106
107
|
schedule_paths: @schedule_paths
|
|
@@ -31,11 +31,10 @@ module Rigor
|
|
|
31
31
|
# behaviour: it always raises). This lets the engine's existing flow analysis treat code after
|
|
32
32
|
# `T.absurd` as unreachable, matching what users of Sorbet expect.
|
|
33
33
|
# - When the branch is REACHABLE (the discriminant's type isn't `bot`), the recogniser also records
|
|
34
|
-
# the call node in a per-plugin set. The plugin's
|
|
35
|
-
# `
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# `diagnostics_for_file`.
|
|
34
|
+
# the call node in a per-plugin set. The plugin's node rule later emits a
|
|
35
|
+
# `plugin.sorbet.absurd-reachable` warning for each recorded call node, matched by object identity.
|
|
36
|
+
# We rely on the runner only parsing each file once per run, so the same Prism node object is seen
|
|
37
|
+
# in both the `dynamic_return` rule and the node rule.
|
|
39
38
|
module AbsurdRecognizer
|
|
40
39
|
# @return true when `call_node` is `T.absurd(x)`.
|
|
41
40
|
def self.absurd_call?(call_node)
|
|
@@ -21,7 +21,7 @@ module Rigor
|
|
|
21
21
|
# | `T.unsafe(x)` | return type ← `Dynamic[top]` |
|
|
22
22
|
# | `T.reveal_type(expr)` | return type ← `inferred(expr)` (passes through) |
|
|
23
23
|
# | `T.assert_type!(expr, T)` | return type ← translated `T` + static subtype check |
|
|
24
|
-
# | `T.bind(self, T)` | return type ← `Constant[nil]` +
|
|
24
|
+
# | `T.bind(self, T)` | return type ← `Constant[nil]` + `self` narrowing fact |
|
|
25
25
|
#
|
|
26
26
|
# The Sorbet runtime's `T.let` / `T.cast` actually return the inner expression unchanged at runtime;
|
|
27
27
|
# their job is purely to *assert* a static type. From Rigor's static perspective the simplest
|
|
@@ -36,7 +36,7 @@ module Rigor
|
|
|
36
36
|
# `T.reveal_type` is "diagnostic-only" in Sorbet: it passes the value through unchanged at runtime
|
|
37
37
|
# AND surfaces the inferred static type as a build-time message. The recogniser contributes the
|
|
38
38
|
# inferred type (so chained call sites still resolve as if the `T.reveal_type` wrapper weren't
|
|
39
|
-
# there); the plugin's
|
|
39
|
+
# there); the plugin's node rule surfaces the `plugin.sorbet.reveal-type` `:info`
|
|
40
40
|
# message for human consumption.
|
|
41
41
|
#
|
|
42
42
|
# `T.bind(self, T)` is recognised as block-scope self narrowing. The recogniser returns a
|
|
@@ -113,9 +113,9 @@ module Rigor
|
|
|
113
113
|
|
|
114
114
|
# `T.assert_type!(expr, T)` shares the typed-assertion contribution shape with `T.cast` (return is
|
|
115
115
|
# the asserted type), so the recogniser delegates the return-type half through
|
|
116
|
-
# `resolve_typed_assertion`. The static subtype check that distinguishes `assert_type!` from
|
|
117
|
-
#
|
|
118
|
-
#
|
|
116
|
+
# `resolve_typed_assertion`. The static subtype check that distinguishes `assert_type!` from `cast`
|
|
117
|
+
# is run by the plugin's `record_assert_type_check`, which records a mismatch for the node rule to
|
|
118
|
+
# emit (the absurd-recognizer pattern).
|
|
119
119
|
def assert_type_check(call_node, scope)
|
|
120
120
|
return nil if scope.nil?
|
|
121
121
|
|
|
@@ -135,8 +135,7 @@ module Rigor
|
|
|
135
135
|
# `T.reveal_type(expr)` returns `expr` unchanged at runtime; the Sorbet-side semantics is "make
|
|
136
136
|
# the inferred static type visible to the user." The contribution mirrors `T.must` minus the
|
|
137
137
|
# nil-stripping: the call's return type is the inner expression's inferred type. The companion
|
|
138
|
-
# diagnostic is emitted by
|
|
139
|
-
# contribution only.
|
|
138
|
+
# diagnostic is emitted by a node rule; this method handles the contribution only.
|
|
140
139
|
def resolve_reveal_type(call_node, scope)
|
|
141
140
|
inner = nth_argument(call_node, 0)
|
|
142
141
|
return Rigor::Type::Combinator.untyped if inner.nil? || scope.nil?
|