rigortype 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +112 -14
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -45,8 +45,7 @@ module Rigor
|
|
|
45
45
|
unique_constraint foreign_key primary_keys
|
|
46
46
|
].freeze
|
|
47
47
|
|
|
48
|
-
# @param source
|
|
49
|
-
# @return [SchemaTable]
|
|
48
|
+
# @param source — contents of `db/schema.rb`
|
|
50
49
|
def self.parse(source)
|
|
51
50
|
tree = Prism.parse(source).value
|
|
52
51
|
new.parse(tree)
|
|
@@ -54,8 +54,7 @@ module Rigor
|
|
|
54
54
|
# Uppercase leading keywords that mark a table-level constraint line rather than a column.
|
|
55
55
|
CONSTRAINT_KEYWORDS = %w[CONSTRAINT PRIMARY UNIQUE CHECK FOREIGN EXCLUDE LIKE PARTITION].freeze
|
|
56
56
|
|
|
57
|
-
# @param source
|
|
58
|
-
# @return [SchemaTable]
|
|
57
|
+
# @param source — contents of `db/structure.sql`
|
|
59
58
|
def self.parse(source)
|
|
60
59
|
new.parse(source)
|
|
61
60
|
end
|
|
@@ -63,6 +63,37 @@ module Rigor
|
|
|
63
63
|
class Activerecord < Rigor::Plugin::Base
|
|
64
64
|
manifest(
|
|
65
65
|
id: "activerecord",
|
|
66
|
+
target_gems: ["activerecord"],
|
|
67
|
+
# 0.13.0, 2026-09-24 (#1321) — the class-side `Model.find` with two or more arguments returns
|
|
68
|
+
# `Array[Model]` instead of the model, and its `model-call` note says so. A model that defines its own
|
|
69
|
+
# `self.find` keeps its own answer; the note reads syntax alone and does not see that override. No
|
|
70
|
+
# producer payload changed shape; the bump records the type change as the 0.11.0 and 0.12.0 entries do.
|
|
71
|
+
#
|
|
72
|
+
# 0.12.0, 2026-09-19 (#1089) — a written-receiver read of an enum-backed column returns the enum's
|
|
73
|
+
# KEY type (the key String constants, or `String` for an empty key list) instead of the column's
|
|
74
|
+
# SQL storage type. No producer payload changed shape, but the version is the cache key a project
|
|
75
|
+
# sees and this changes which calls the plugin claims at the written-receiver spelling — a warm
|
|
76
|
+
# run must not serve the pre-change stream (the same call the 0.11.0 bump records).
|
|
77
|
+
#
|
|
78
|
+
# 0.11.0, 2026-09-17 (#963 item 2) — an implicit-self read of a column / association / `column?`
|
|
79
|
+
# predicate inside the model's own `def` is answered `untyped` instead of reaching no path, so the
|
|
80
|
+
# engine's own-method veto sees the member and a same-named top-level `def` stops binding ahead of
|
|
81
|
+
# it. No producer payload changed shape, but the version is the cache key a project sees and this
|
|
82
|
+
# changes which calls the plugin claims — a warm run must not serve the pre-change stream.
|
|
83
|
+
#
|
|
84
|
+
# 0.10.0, 2026-09-17 (#1049) — three macro families join the model entry and the published
|
|
85
|
+
# `:model_index` fact: `delegate`, associations declared in an included concern's `included do`, and
|
|
86
|
+
# the Paperclip / Active Storage attachment macros. `ModelIndex::Entry` gains a `macro_methods`
|
|
87
|
+
# member, so a cached 0.9.0 payload is not Marshal-compatible; and the concern fold WIDENS the
|
|
88
|
+
# association list of models the 0.9.0 payload recorded without it. Both make the bump — part of the
|
|
89
|
+
# producer cache KEY — what stops a warm run serving the pre-change surface to a consumer that fails
|
|
90
|
+
# closed on a name it cannot see.
|
|
91
|
+
#
|
|
92
|
+
# 0.9.0, 2026-09-10 (#534 item 7) — the bundled signatures gained `sig/active_record/framework.rbs`:
|
|
93
|
+
# the Active Record exception hierarchy, the `ActiveModel` namespace and `Arel`. No producer payload
|
|
94
|
+
# changed shape, but the version is the cache key a project sees, and the RBS half of a plugin's
|
|
95
|
+
# contribution rides the same bundle.
|
|
96
|
+
#
|
|
66
97
|
# 0.8.0, 2026-09-02 — a model declared `class ::User` is keyed by its de-rooted name (`"User"`) in
|
|
67
98
|
# the {ModelIndex} and the published `:model_index` fact (#583). The version is part of the
|
|
68
99
|
# producer cache KEY: a cached 0.7.0 index still keys such a model `"::User"`, and `ModelIndex#find`
|
|
@@ -87,7 +118,7 @@ module Rigor
|
|
|
87
118
|
# a scope lambda body / class-method body now contributes `Relation[Model]` via `scope.self_type`
|
|
88
119
|
# instead of falling through to `Kernel#select` (the IO multiplexer, `Array[String]` return). Plus
|
|
89
120
|
# `:select` added to the relation-entry-point list.
|
|
90
|
-
version: "0.
|
|
121
|
+
version: "0.13.0",
|
|
91
122
|
description: "Types ActiveRecord finders against the project's db/schema.rb and AR models.",
|
|
92
123
|
config_schema: {
|
|
93
124
|
"schema_file" => { kind: :string, default: "db/schema.rb" },
|
|
@@ -104,7 +135,28 @@ module Rigor
|
|
|
104
135
|
# ADR-26 — `ActiveRecord::Relation` is an "open" receiver: it delegates an unbounded set of
|
|
105
136
|
# user-defined scopes / class methods to its model, so `call.undefined-method` must not fire for
|
|
106
137
|
# it. `CheckRules` reads this manifest field and skips the rule for the class.
|
|
107
|
-
|
|
138
|
+
#
|
|
139
|
+
# The `sig/active_record/framework.rbs` names (#534 item 7) are open for the narrower reason that
|
|
140
|
+
# file documents: each is declared so the CONSTANT resolves, and none of them enumerates a method
|
|
141
|
+
# surface. `RecordInvalid#record`, `StatementInvalid#sql`, `Arel.star` and every other member left
|
|
142
|
+
# out must stay lenient, so the declaration buys constant resolution and asserts nothing else.
|
|
143
|
+
open_receivers: [
|
|
144
|
+
"ActiveRecord::Relation",
|
|
145
|
+
"ActiveRecord::ActiveRecordError",
|
|
146
|
+
"ActiveRecord::RecordNotFound",
|
|
147
|
+
"ActiveRecord::RecordInvalid",
|
|
148
|
+
"ActiveRecord::RecordNotSaved",
|
|
149
|
+
"ActiveRecord::RecordNotDestroyed",
|
|
150
|
+
"ActiveRecord::ReadOnlyRecord",
|
|
151
|
+
"ActiveRecord::Rollback",
|
|
152
|
+
"ActiveRecord::StaleObjectError",
|
|
153
|
+
"ActiveRecord::ConnectionNotEstablished",
|
|
154
|
+
"ActiveRecord::StatementInvalid",
|
|
155
|
+
"ActiveRecord::WrappedDatabaseException",
|
|
156
|
+
"ActiveRecord::RecordNotUnique",
|
|
157
|
+
"ActiveModel::ValidationError",
|
|
158
|
+
"Arel"
|
|
159
|
+
],
|
|
108
160
|
# ADR-103 WD2 / WD10 (#387) — the effect layer. rigor-activerecord models ActiveRecord, which is
|
|
109
161
|
# part of Rails, so it opens the framework's own `rails.*` root rather than one named after the
|
|
110
162
|
# plugin; `effect_root:` is granted only because the engine bundles this plugin
|
|
@@ -184,6 +236,10 @@ module Rigor
|
|
|
184
236
|
# no-op).
|
|
185
237
|
def prepare(services)
|
|
186
238
|
index = model_index
|
|
239
|
+
# Issue #1051 — registered HERE, on the run's parent, because `#prepare` is where the schema read is
|
|
240
|
+
# forced and therefore where every load error already exists. The pool's one pre-fork session runs
|
|
241
|
+
# this, so the disclosure is in the coordinator's table before a single worker is forked.
|
|
242
|
+
disclose_load_errors
|
|
187
243
|
return if index.nil? || index.empty?
|
|
188
244
|
# Reduced mode stays UNPUBLISHED. Every consumer reads `columns:` as authoritative and fires on a
|
|
189
245
|
# key missing from it — rigor-actionpack's `permit(:title)` check and rigor-shoulda-matchers'
|
|
@@ -200,15 +256,17 @@ module Rigor
|
|
|
200
256
|
)
|
|
201
257
|
end
|
|
202
258
|
|
|
203
|
-
def diagnostics_for_file(path:, scope:, root:)
|
|
259
|
+
def diagnostics_for_file(path:, scope:, root:)
|
|
204
260
|
index = model_index
|
|
205
261
|
# The schema-load disclosure is independent of whether an index was built: reduced mode still
|
|
206
262
|
# produces one (and still analyzes), while a genuine index failure produces one and nothing else.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
263
|
+
# It leaves through the run-scoped channel rather than this method's return, so re-registering here
|
|
264
|
+
# is free — it only matters for a load error appended after `#prepare` already ran.
|
|
265
|
+
disclose_load_errors
|
|
266
|
+
return [] if index.nil? || index.empty?
|
|
267
|
+
return [] if migration_path?(path)
|
|
210
268
|
|
|
211
|
-
|
|
269
|
+
Analyzer.new(path: path, model_index: index, scope: scope).analyze(root).diagnostics
|
|
212
270
|
end
|
|
213
271
|
|
|
214
272
|
# Rails migration files (`db/migrate/<timestamp>_*.rb`) and post-migration files
|
|
@@ -229,6 +287,61 @@ module Rigor
|
|
|
229
287
|
MIGRATION_PATH_PATTERNS.any? { |pattern| path_s.match?(pattern) }
|
|
230
288
|
end
|
|
231
289
|
|
|
290
|
+
# ADR-113 WD4 (#1082) — enumerates the members this plugin synthesizes on `class_name`, for the
|
|
291
|
+
# `rigor lens` declaration map. The rows come straight from the prepared {ModelIndex} — the same
|
|
292
|
+
# fact the `dynamic_return` gate and {#diagnostics_for_file} already read — so the hook adds no
|
|
293
|
+
# discovery of its own and stays off the `check` hot path (ADR-52): `check` never calls it. An
|
|
294
|
+
# unknown class, a failed index, and a non-model class all answer `[]`; a reduced-mode index (no
|
|
295
|
+
# schema) simply lists no columns.
|
|
296
|
+
#
|
|
297
|
+
# `type` is the most conservative of the answers the plugin gives the member at `check` — a row
|
|
298
|
+
# that claimed a type `check` never produces would misstate the member (ADR-113 treats a lens
|
|
299
|
+
# answer differing from `check` as a bug):
|
|
300
|
+
#
|
|
301
|
+
# - `:scope` — `Relation[Model]`, the class-side answer `Post.published` gets.
|
|
302
|
+
# - `:association_reader` — the same object {#association_return_type} contributes at a call
|
|
303
|
+
# site: `Relation[Target]` for a collection, `Nominal[Target]` (`| nil` for `has_one` /
|
|
304
|
+
# `optional: true`) for a singular, and nil for a polymorphic target the plugin cannot type.
|
|
305
|
+
# - `:column_reader` / `:column_predicate` — `Dynamic[top]` ON PURPOSE (#963): that is the
|
|
306
|
+
# answer the bare, receiver-less read gets inside the model's own `def`, the only answer free
|
|
307
|
+
# of receiver-type context. A written `user.name` still narrows to the column's type, but the
|
|
308
|
+
# member-level answer stays dynamic — the precise variant at member level was measured at 57
|
|
309
|
+
# false positives on mastodon.
|
|
310
|
+
# - `:enum` — the enum's attribute reader (`status`), listed ONLY when no column row already
|
|
311
|
+
# carries the name (a non-column enum attribute, or a reduced-mode index with no schema). An
|
|
312
|
+
# enum-backed column appears under `:column_reader` instead, so no member lists twice. The
|
|
313
|
+
# plugin answers nothing for the bare enum attribute at `check`, so `type` is nil — not
|
|
314
|
+
# `untyped`, which would claim an answer `check` does not give. The per-value predicates Rails
|
|
315
|
+
# generates ride in `:macro_method` with their affix-resolved names.
|
|
316
|
+
# - `:macro_method` — `delegate` names, attachment readers, enum predicates (#1049 records names
|
|
317
|
+
# only): the plugin knows the member exists and answers no type for it, so `type` is nil.
|
|
318
|
+
def declared_members(class_name)
|
|
319
|
+
index = model_index
|
|
320
|
+
return [] if index.nil?
|
|
321
|
+
|
|
322
|
+
entry = index.find(class_name)
|
|
323
|
+
return [] if entry.nil?
|
|
324
|
+
|
|
325
|
+
untyped = Rigor::Type::Combinator.untyped
|
|
326
|
+
entry.column_names.flat_map do |name|
|
|
327
|
+
[{ name: name, kind: :column_reader, type: untyped },
|
|
328
|
+
{ name: "#{name}?", kind: :column_predicate, type: untyped }]
|
|
329
|
+
end +
|
|
330
|
+
entry.associations.map do |association|
|
|
331
|
+
{ name: association[:name], kind: :association_reader,
|
|
332
|
+
type: association_return_type(entry, association[:name]) }
|
|
333
|
+
end +
|
|
334
|
+
entry.scopes.map do |name|
|
|
335
|
+
{ name: name, kind: :scope, type: relation_of(entry.class_name) }
|
|
336
|
+
end +
|
|
337
|
+
(entry.enums.keys - entry.column_names).map do |column|
|
|
338
|
+
{ name: column, kind: :enum, type: nil }
|
|
339
|
+
end +
|
|
340
|
+
entry.macro_methods.map do |name|
|
|
341
|
+
{ name: name, kind: :macro_method, type: nil }
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
232
345
|
# The class-side finder / relation entry-point names `finder_return_type` recognises. Static half of
|
|
233
346
|
# the `dynamic_return` name gate; the run-time half comes from the model index (scopes,
|
|
234
347
|
# associations, columns).
|
|
@@ -287,15 +400,16 @@ module Rigor
|
|
|
287
400
|
return nil if index.nil? || index.empty?
|
|
288
401
|
|
|
289
402
|
if call_node.receiver
|
|
290
|
-
class_call_return_type(call_node, index) ||
|
|
403
|
+
class_call_return_type(call_node, scope, index) ||
|
|
291
404
|
relation_call_return_type(call_node, scope, index) ||
|
|
292
405
|
instance_call_return_type(call_node, scope, index)
|
|
293
406
|
else
|
|
294
|
-
implicit_self_class_call_return_type(call_node, scope, index)
|
|
407
|
+
implicit_self_class_call_return_type(call_node, scope, index) ||
|
|
408
|
+
implicit_self_instance_member_type(call_node, scope, index)
|
|
295
409
|
end
|
|
296
410
|
end
|
|
297
411
|
|
|
298
|
-
def class_call_return_type(call_node, index)
|
|
412
|
+
def class_call_return_type(call_node, scope, index)
|
|
299
413
|
model_name = constant_receiver_name(call_node.receiver)
|
|
300
414
|
return nil if model_name.nil?
|
|
301
415
|
|
|
@@ -303,7 +417,7 @@ module Rigor
|
|
|
303
417
|
entry = index.find(model_name)
|
|
304
418
|
return nil if entry.nil?
|
|
305
419
|
|
|
306
|
-
finder_return_type(call_node, entry) ||
|
|
420
|
+
finder_return_type(call_node, entry, scope) ||
|
|
307
421
|
class_scope_return_type(call_node, entry)
|
|
308
422
|
end
|
|
309
423
|
|
|
@@ -329,20 +443,115 @@ module Rigor
|
|
|
329
443
|
entry = index.find(self_type.class_name)
|
|
330
444
|
return nil if entry.nil?
|
|
331
445
|
|
|
332
|
-
finder_return_type(call_node, entry) ||
|
|
446
|
+
finder_return_type(call_node, entry, scope) ||
|
|
333
447
|
class_scope_return_type(call_node, entry)
|
|
334
448
|
end
|
|
335
449
|
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
def
|
|
450
|
+
# Implicit-self INSTANCE member read — `name` / `account` written without a receiver inside the
|
|
451
|
+
# model's own `def`, where `self` is `Nominal[Model]`. Both existing instance paths key on a WRITTEN
|
|
452
|
+
# receiver, so this spelling reached no path at all; issue #963 item 2 is what that costs beyond
|
|
453
|
+
# precision. The engine's own-method veto (#618) asks the dispatcher whether a plugin answers the
|
|
454
|
+
# name before letting a same-named top-level `def` bind, and a member no tier answers is a member the
|
|
455
|
+
# top-level `def` binds ahead of — typing `name.upcase` inside `def shout` as the def's `nil` and
|
|
456
|
+
# firing `undefined method 'upcase' for nil` on working Rails code.
|
|
457
|
+
#
|
|
458
|
+
# **`untyped`, not the column's type**, and the difference is measured rather than assumed. The
|
|
459
|
+
# precise variant (the same `association_return_type` / `column_return_type` pair the written-receiver
|
|
460
|
+
# path uses) was run over the corpus and added 57 diagnostics on mastodon and 5 on redmine, all new:
|
|
461
|
+
# `flow.always-truthy-condition` where a predicate reader now folds a guard to a constant, and
|
|
462
|
+
# `call.possible-nil-receiver` where a singular association's `nil` arm survives a `.compact` the
|
|
463
|
+
# engine does not fold. Those are precision questions about the reader's TYPE, and each is a false
|
|
464
|
+
# positive on correct code. What #963 needs is only that the member EXISTS, so the contribution is the
|
|
465
|
+
# ADR-82 WD4 answer `#ruby_type_to_type`'s decline already documents next door: the plugin knows the
|
|
466
|
+
# reader is there and declines to say more here. The site keeps the `Dynamic` it already had, gains
|
|
467
|
+
# the `framework_dsl_boundary` attribution, and the veto gets its answer.
|
|
468
|
+
#
|
|
469
|
+
# The project's OWN definition wins, and is not merely a precision preference: `def name` on the
|
|
470
|
+
# model — or on an ancestor it declares — is an override Ruby dispatches to, and the plugin tier sits
|
|
471
|
+
# ABOVE the engine's body-inference tiers, so answering here would displace the override's real return
|
|
472
|
+
# type with `Dynamic`. `discovered_method_through_ancestors?` is the same table the veto's own
|
|
473
|
+
# discovery arm reads, and it fails toward declining (a budget-exhausted walk answers true).
|
|
474
|
+
def implicit_self_instance_member_type(call_node, scope, index)
|
|
475
|
+
return nil if scope.nil?
|
|
476
|
+
return nil unless call_node.arguments.nil?
|
|
477
|
+
return nil unless call_node.block.nil?
|
|
478
|
+
|
|
479
|
+
self_type = scope.self_type
|
|
480
|
+
return nil unless self_type.is_a?(Rigor::Type::Nominal)
|
|
481
|
+
|
|
482
|
+
entry = index.find(self_type.class_name)
|
|
483
|
+
return nil if entry.nil?
|
|
484
|
+
return nil unless instance_member_name?(entry, call_node.name)
|
|
485
|
+
return nil if scope.discovered_method_through_ancestors?(self_type.class_name, call_node.name, :instance)
|
|
486
|
+
return nil if rbs_answers_before_object?(self_type.class_name, call_node.name, scope)
|
|
487
|
+
|
|
488
|
+
Rigor::Type::Combinator.untyped
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# A signature the project wrote about the model — or about anything in the model's MRO — is
|
|
492
|
+
# authorship, and this tier sits above `RbsDispatch`, so answering `untyped` for a name RBS declares
|
|
493
|
+
# would displace the declared type at the implicit-self spelling ONLY, leaving `name` and `self.name`
|
|
494
|
+
# typed differently in one method body. Declining restores both: RBS answers the call, and the
|
|
495
|
+
# engine's own veto still sees the member through its pre-`::Object` RBS arm, so a top-level `def` of
|
|
496
|
+
# the name does not bind either.
|
|
497
|
+
#
|
|
498
|
+
# The cut-off is `Inference::ExternalAncestorResolution.declared_before_object?`'s and NOT its
|
|
499
|
+
# own-class sibling's, and the difference is the whole point: RBS's definition builder resolves
|
|
500
|
+
# through ancestors, so a reader declared on a `sig/application_record.rbs` superclass or on an
|
|
501
|
+
# `include`d module comes back with THAT owner, and an own-class test would call it undeclared and
|
|
502
|
+
# displace it one ancestor up. What the cut-off keeps out is an owner at or after a top-level
|
|
503
|
+
# `def`'s own rung — `Object`, `Kernel`,
|
|
504
|
+
# `BasicObject` — whose names say nothing about whether the model answers. Instance-side only, so a
|
|
505
|
+
# sidecar declaring `def self.name` is correctly not counted. Fail-soft: an unreadable environment
|
|
506
|
+
# declines to claim a declaration exists, which leaves the member visible to the veto.
|
|
507
|
+
TOP_LEVEL_DEF_OWNERS = %w[Object Kernel BasicObject].freeze
|
|
508
|
+
private_constant :TOP_LEVEL_DEF_OWNERS
|
|
509
|
+
|
|
510
|
+
def rbs_answers_before_object?(class_name, method_name, scope)
|
|
511
|
+
definition = services.reflection.instance_method_definition(class_name, method_name, scope: scope)
|
|
512
|
+
return false if definition.nil? || !definition.respond_to?(:defined_in)
|
|
513
|
+
|
|
514
|
+
defined_in = definition.defined_in
|
|
515
|
+
return false if defined_in.nil?
|
|
516
|
+
|
|
517
|
+
!TOP_LEVEL_DEF_OWNERS.include?(defined_in.to_s.delete_prefix("::"))
|
|
518
|
+
rescue StandardError
|
|
519
|
+
false
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Whether the model entry declares `method_name` as an instance-side member: an association accessor,
|
|
523
|
+
# a column reader, or the ActiveRecord-generated `column?` predicate. The same three families
|
|
524
|
+
# `#recognised_method_names` puts in the dispatch gate, asked of ONE entry.
|
|
525
|
+
def instance_member_name?(entry, method_name)
|
|
526
|
+
name = method_name.to_s
|
|
527
|
+
return true unless entry.association(method_name).nil?
|
|
528
|
+
|
|
529
|
+
column_name = name.end_with?("?") ? name[0..-2] : name
|
|
530
|
+
!entry.column(column_name).nil?
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# Class-side finders + the class-side relation entry points. `find` returns the model, or an Array of
|
|
534
|
+
# them for two or more ids, or with a block the model or `nil`; `find_by!` returns the model; `find_by` adds the `nil` arm; `where` /
|
|
535
|
+
# `all` / `order` / `limit` / `none` open a relation. The relation then carries its element type
|
|
536
|
+
# through any further chained query method via the bundled `ActiveRecord::Relation` RBS.
|
|
537
|
+
def finder_return_type(call_node, entry, scope)
|
|
341
538
|
case call_node.name
|
|
342
539
|
when :find
|
|
343
|
-
return
|
|
540
|
+
return block_find_return_type(call_node, entry, scope) if call_node.block
|
|
344
541
|
|
|
345
|
-
|
|
542
|
+
arity = call_argument_count(call_node)
|
|
543
|
+
return nil if arity.zero?
|
|
544
|
+
|
|
545
|
+
# `Model.find` is `all.find`, so it answers by arity exactly as the bundled `Relation#find`
|
|
546
|
+
# overloads do, and the comment there says why one argument stays the model.
|
|
547
|
+
model = Rigor::Type::Combinator.nominal_of(entry.class_name)
|
|
548
|
+
return model if arity < 2
|
|
549
|
+
|
|
550
|
+
# A model that defines its own `self.find` (`find(owner, id)`) takes two arguments for its own
|
|
551
|
+
# reasons, so the Array reading of Rails' `find` is withheld and the project's method answers.
|
|
552
|
+
return nil if scope.discovered_method_through_ancestors?(entry.class_name, :find, :singleton)
|
|
553
|
+
|
|
554
|
+
Rigor::Type::Combinator.nominal_of("Array", type_args: [model])
|
|
346
555
|
when :find_by!
|
|
347
556
|
# The bang variant raises `RecordNotFound` instead of returning `nil`, so the result is
|
|
348
557
|
# non-nullable.
|
|
@@ -369,6 +578,29 @@ module Rigor
|
|
|
369
578
|
end
|
|
370
579
|
end
|
|
371
580
|
|
|
581
|
+
# `Model.find { |m| … }` / `Model.find(&:pred?)` never reaches the id lookup: `Core.find` passes a
|
|
582
|
+
# block to `super` (`all.find`), whose `Relation#find` does `return super if block_given?`, landing on
|
|
583
|
+
# `Enumerable#find` over the loaded records. So the answer is the element or `nil`, exactly what the
|
|
584
|
+
# bundled `Relation#find` block overload says and what the engine already answers for `Array#find`.
|
|
585
|
+
# The `nil` arm is the search miss, not a worst-case reading: the block-form `find` sites in the
|
|
586
|
+
# survey corpus, all on Arrays, guard it (`&.`, `||=`, `while (x = …)`).
|
|
587
|
+
#
|
|
588
|
+
# Declining here is not neutral: when the model is RBS-known, a decline falls through to
|
|
589
|
+
# `singleton(Model)`, which declares no `find`, and fires `call.undefined-method` on working code.
|
|
590
|
+
# An argument next to the block is `Enumerable#find`'s `ifnone` callable, whose result joins the
|
|
591
|
+
# union. That form is rare enough that it answers `untyped` rather than typing the callable, as the
|
|
592
|
+
# `Relation#find` overload does. A project `self.find` keeps its own answer, as it does for the
|
|
593
|
+
# multi-id form.
|
|
594
|
+
def block_find_return_type(call_node, entry, scope)
|
|
595
|
+
return nil if scope.discovered_method_through_ancestors?(entry.class_name, :find, :singleton)
|
|
596
|
+
return Rigor::Type::Combinator.untyped unless call_argument_count(call_node).zero?
|
|
597
|
+
|
|
598
|
+
Rigor::Type::Combinator.union(
|
|
599
|
+
Rigor::Type::Combinator.nominal_of(entry.class_name),
|
|
600
|
+
Rigor::Type::Combinator.constant_of(nil)
|
|
601
|
+
)
|
|
602
|
+
end
|
|
603
|
+
|
|
372
604
|
# `Model.table_name` is a String either way; whether it is safe to VALUE-PIN turns on how the name was
|
|
373
605
|
# derived. `Entry#table_name_exact?` holds ONLY for a name the source declared as a String literal
|
|
374
606
|
# (`self.table_name = "…"`, inherited down an STI chain, and not overridden by a computed one). An
|
|
@@ -511,16 +743,51 @@ module Rigor
|
|
|
511
743
|
return nil if column.nil?
|
|
512
744
|
return bool_type if predicate
|
|
513
745
|
|
|
746
|
+
# #1089 — an enum-backed column reads as its KEY (`"active"`), not its SQL storage type
|
|
747
|
+
# (`Integer` for a `t.integer :status` column). Rails returns the configured key as a String from
|
|
748
|
+
# the reader (`user.status # => "active"`), so typing it by `column.ruby_type` makes a correct
|
|
749
|
+
# `user.status.upcase` fire `call.undefined-method` on Integer and lets a wrong `user.status + 1`
|
|
750
|
+
# pass. The writer / `where(status:)` side is out of scope and untouched — it accepts both keys
|
|
751
|
+
# and values.
|
|
752
|
+
return enum_key_type(entry, column_name) if entry.enum?(column_name)
|
|
753
|
+
|
|
514
754
|
inner = ruby_type_to_type(column.ruby_type)
|
|
515
|
-
return
|
|
755
|
+
return Rigor::Type::Combinator.untyped if inner.nil?
|
|
516
756
|
|
|
517
757
|
column.array? ? Rigor::Type::Combinator.nominal_of("Array", type_args: [inner]) : inner
|
|
518
758
|
end
|
|
519
759
|
|
|
520
|
-
#
|
|
521
|
-
#
|
|
522
|
-
#
|
|
523
|
-
#
|
|
760
|
+
# The KEY type of an enum-backed column. {ModelIndex} only records an enum when every key is a
|
|
761
|
+
# static Symbol literal — `parse_enum_call` declines a non-literal values expression outright — so
|
|
762
|
+
# a column in `entry.enums` carries a COMPLETE key set by construction, and the union of the key
|
|
763
|
+
# String constants is exact for the declared enum rather than the wider `String`. The empty-list
|
|
764
|
+
# guard (`enum :status, []` parses) falls back to `String`.
|
|
765
|
+
#
|
|
766
|
+
# Nullability follows `#column_return_type`'s other-column convention: non-nullable, so
|
|
767
|
+
# `user.status.upcase` does not light up `possible-nil-receiver` — Rails code calls enum readers
|
|
768
|
+
# directly as a matter of course, and the same under-report-the-nil trade the parent doc makes
|
|
769
|
+
# applies.
|
|
770
|
+
def enum_key_type(entry, column_name)
|
|
771
|
+
keys = entry.enum_values(column_name)
|
|
772
|
+
return Rigor::Type::Combinator.nominal_of("String") if keys.empty?
|
|
773
|
+
|
|
774
|
+
Rigor::Type::Combinator.union(
|
|
775
|
+
*keys.map { |key| Rigor::Type::Combinator.constant_of(key.to_s) }
|
|
776
|
+
)
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# Maps a `SchemaTable::Column#ruby_type` string to a Rigor type. `"Object"` (json / jsonb / a
|
|
780
|
+
# `serialize`d or `mount_uploader`ed column / an unrecognised SQL type) declines — `Nominal[Object]`
|
|
781
|
+
# would be NARROWER than the RBS-erased envelope and could surface false `call.undefined-method` on a
|
|
782
|
+
# value whose real shape the plugin cannot model.
|
|
783
|
+
#
|
|
784
|
+
# ADR-82 WD4 — the caller turns that decline into an explicit `Dynamic[top]` rather than passing the
|
|
785
|
+
# call on. The two are the same type at the site (dispatch widens an unresolved reader to Dynamic
|
|
786
|
+
# anyway), but only the plugin ANSWERING makes the engine record `framework_dsl_boundary`: this is a
|
|
787
|
+
# macro-generated attribute whose value crosses a DSL boundary and stays dynamic, which is exactly the
|
|
788
|
+
# cause WD4 defined and left unattributed. Answering here is honest in the way WD4 requires — the
|
|
789
|
+
# plugin knows the reader exists (the schema declares the column) and knows it cannot type it — where
|
|
790
|
+
# guessing "framework boundary" for any unresolved receiver would not be.
|
|
524
791
|
def ruby_type_to_type(ruby_type)
|
|
525
792
|
case ruby_type
|
|
526
793
|
when "bool" then bool_type
|
|
@@ -579,7 +846,8 @@ module Rigor
|
|
|
579
846
|
scopes: entry.scopes,
|
|
580
847
|
validations: entry.validated_attributes,
|
|
581
848
|
callbacks: entry.callbacks,
|
|
582
|
-
aliases: entry.aliases
|
|
849
|
+
aliases: entry.aliases,
|
|
850
|
+
macro_methods: entry.macro_methods
|
|
583
851
|
}.freeze
|
|
584
852
|
end.freeze
|
|
585
853
|
end
|
|
@@ -597,7 +865,9 @@ module Rigor
|
|
|
597
865
|
# model-file additions — so no priming walk is needed (it used to run the discover twice).
|
|
598
866
|
@model_index = cache_for(:model_index, params: {}).call
|
|
599
867
|
rescue StandardError => e
|
|
600
|
-
@load_errors << {
|
|
868
|
+
@load_errors << { key: "2-model-index-failed",
|
|
869
|
+
message: "model index build failed: #{e.class}: #{e.message}",
|
|
870
|
+
severity: :warning }
|
|
601
871
|
nil
|
|
602
872
|
end
|
|
603
873
|
|
|
@@ -616,7 +886,8 @@ module Rigor
|
|
|
616
886
|
# here.
|
|
617
887
|
@schema_table = cache_for(:schema_table, params: {}).call
|
|
618
888
|
rescue Plugin::AccessDeniedError => e
|
|
619
|
-
@load_errors << {
|
|
889
|
+
@load_errors << { key: "1-schema-read-refused",
|
|
890
|
+
message: "rigor-activerecord: #{e.message}#{REDUCED_MODE_SUFFIX}",
|
|
620
891
|
severity: :warning }
|
|
621
892
|
nil
|
|
622
893
|
rescue Errno::ENOENT
|
|
@@ -625,14 +896,16 @@ module Rigor
|
|
|
625
896
|
# associations and table names off the discovered models, so this is a disclosure of REDUCED
|
|
626
897
|
# capability rather than a failure — `:info`, the grade the plugins use for "here is what I
|
|
627
898
|
# recognised", not `:warning`, the grade they use for "I could not run".
|
|
628
|
-
@load_errors << {
|
|
899
|
+
@load_errors << { key: "1-schema-file-missing",
|
|
900
|
+
message: "rigor-activerecord: schema file `#{@schema_file}` (or " \
|
|
629
901
|
"`#{@structure_sql_file}`) not found#{REDUCED_MODE_SUFFIX}",
|
|
630
902
|
severity: :info }
|
|
631
903
|
nil
|
|
632
904
|
rescue StandardError => e
|
|
633
905
|
# A schema that EXISTS but does not parse is a real, actionable problem — the user meant it to be
|
|
634
906
|
# read. Reduced mode still applies, but the disclosure stays a warning.
|
|
635
|
-
@load_errors << {
|
|
907
|
+
@load_errors << { key: "1-schema-parse-failed",
|
|
908
|
+
message: "rigor-activerecord: failed to parse `#{@schema_file}`: " \
|
|
636
909
|
"#{e.class}: #{e.message}#{REDUCED_MODE_SUFFIX}",
|
|
637
910
|
severity: :warning }
|
|
638
911
|
nil
|
|
@@ -644,23 +917,31 @@ module Rigor
|
|
|
644
917
|
"(`where(col:)`, column readers) are skipped"
|
|
645
918
|
private_constant :REDUCED_MODE_SUFFIX
|
|
646
919
|
|
|
647
|
-
# Project-global disclosures,
|
|
648
|
-
#
|
|
649
|
-
#
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
920
|
+
# Project-global disclosures, handed to the engine's run-scoped channel ({Plugin::Base#disclose_once},
|
|
921
|
+
# issue #1051). They are facts about the project's INPUTS — "there is no `db/schema.rb`, so column
|
|
922
|
+
# checks are off" — so the engine positions them at `.rigor.yml:1:1` and emits each `key` once per
|
|
923
|
+
# run.
|
|
924
|
+
#
|
|
925
|
+
# This used to be an `@load_errors_emitted` flag consulted from `#diagnostics_for_file`, which put the
|
|
926
|
+
# row on whichever file this instance saw first. On a Redmine-shape project (migrations only, no
|
|
927
|
+
# `schema.rb`) the per-FILE form before that produced 346 identical rows; on a Solidus monorepo, 999.
|
|
928
|
+
# The flag fixed the count per instance, but a fork-pool worker gets its OWN instance, so `--workers
|
|
929
|
+
# N` re-multiplied it by N and each copy landed on that worker's first file — after #393 possibly an
|
|
930
|
+
# `.erb` template unit, where a schema notice reads as a claim about a view.
|
|
931
|
+
# The `key`s carry an ordinal prefix because the engine emits a plugin's disclosures in KEY order
|
|
932
|
+
# (#1051), and the three schema outcomes — mutually exclusive, since `@schema_load_attempted` memoises
|
|
933
|
+
# the attempt — must still precede a model-index failure the way `@load_errors` records them. Keys are
|
|
934
|
+
# identities, never shown to the user, so the prefix costs nothing but the ordering.
|
|
935
|
+
def disclose_load_errors
|
|
936
|
+
@load_errors.each do |error|
|
|
937
|
+
disclose_once(
|
|
938
|
+
error.fetch(:key),
|
|
659
939
|
message: error.fetch(:message),
|
|
660
940
|
severity: error.fetch(:severity),
|
|
661
941
|
rule: "load-error"
|
|
662
942
|
)
|
|
663
943
|
end
|
|
944
|
+
nil
|
|
664
945
|
end
|
|
665
946
|
end
|
|
666
947
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Active Record's framework namespaces — issue #534 item 7.
|
|
2
|
+
#
|
|
3
|
+
# The 2026-09-01 corpus opacity sweep counted `ActiveRecord::RecordNotFound` 115 times on
|
|
4
|
+
# mastodon, modal in `rescue` clauses, and `ActiveModel` 165 / `Arel` 35 — every one of them
|
|
5
|
+
# resolving to `untyped` because nothing in the bundle named the constant. This file names them,
|
|
6
|
+
# and nothing more.
|
|
7
|
+
#
|
|
8
|
+
# ## The admission rule: a namespace or a rescued error, never a base class
|
|
9
|
+
#
|
|
10
|
+
# Declaring a constant makes it RBS-KNOWN, and a known class with an incomplete signature turns
|
|
11
|
+
# every member the signature omits into a false `call.undefined-method`. That risk is not
|
|
12
|
+
# uniform, so this file admits exactly two shapes:
|
|
13
|
+
#
|
|
14
|
+
# - **Bare namespace modules.** Nobody dispatches on a module used only as a constant path
|
|
15
|
+
# (`ActiveModel::Type::Boolean`, `Arel::Nodes`), so an empty declaration asserts nothing.
|
|
16
|
+
# - **Exception classes**, which are dispatched on — as the `=> e` of a rescue — but whose whole
|
|
17
|
+
# inherited surface is `StandardError`'s, which Rigor's core RBS already carries. Each is listed
|
|
18
|
+
# in the manifest's `open_receivers:` (ADR-26) as well, so the framework-specific readers this
|
|
19
|
+
# file does not enumerate (`RecordInvalid#record`, `StatementInvalid#cause`, …) stay lenient.
|
|
20
|
+
#
|
|
21
|
+
# What is deliberately NOT here is every class a project INHERITS from. `ActiveRecord::Base` is
|
|
22
|
+
# the case that matters: declaring it empty would close every model in the project, and turn
|
|
23
|
+
# `Account.where(...)`, `record.save!` and the whole generated column surface into
|
|
24
|
+
# `call.undefined-method` on working Rails code. The plugin types models through its model index
|
|
25
|
+
# instead, and the namespace stays open for it. The same argument keeps `ActiveModel::Model` and
|
|
26
|
+
# the other mixin modules bare rather than populated.
|
|
27
|
+
#
|
|
28
|
+
# The exception hierarchy is transcribed from Rails v8.0.2 `activerecord/lib/active_record/errors.rb`;
|
|
29
|
+
# a class whose superclass could not be stated with certainty was left out rather than guessed,
|
|
30
|
+
# because a wrong superclass is a wrong `rescue` reachability answer.
|
|
31
|
+
|
|
32
|
+
module ActiveRecord
|
|
33
|
+
class ActiveRecordError < StandardError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class RecordNotFound < ActiveRecordError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class RecordInvalid < ActiveRecordError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class RecordNotSaved < ActiveRecordError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RecordNotDestroyed < ActiveRecordError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class ReadOnlyRecord < ActiveRecordError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Rollback < ActiveRecordError
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class StaleObjectError < ActiveRecordError
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class ConnectionNotEstablished < ActiveRecordError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class StatementInvalid < ActiveRecordError
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class WrappedDatabaseException < StatementInvalid
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class RecordNotUnique < WrappedDatabaseException
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Active Model ships as its own gem, but `activerecord` depends on it unconditionally, so a project
|
|
71
|
+
# that enables this plugin always has it. There is no `rigor-activemodel`, and inventing one to hold
|
|
72
|
+
# eleven empty modules would be a plugin whose whole content is this file.
|
|
73
|
+
module ActiveModel
|
|
74
|
+
module Model
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module API
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
module Validations
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Serialization
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
module Conversion
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
module Naming
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module Attributes
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
module Dirty
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
module SecurePassword
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
module Callbacks
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module Type
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class ValidationError < StandardError
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Arel ships INSIDE the activerecord gem, so this plugin is its owner by the "the row lives in the
|
|
112
|
+
# plugin that owns the gem" rule. `Arel.sql` is the one member the corpus calls; the signature is
|
|
113
|
+
# written to accept anything because its extra binds are positional-or-keyword and an arity claim
|
|
114
|
+
# here would be a false `call.wrong-arity` on a shape this file has no reason to police.
|
|
115
|
+
module Arel
|
|
116
|
+
module Nodes
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
module Predications
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.sql: (untyped, *untyped, **untyped) -> untyped
|
|
123
|
+
end
|