rigortype 0.2.7 → 0.2.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 +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -10,34 +10,26 @@ require_relative "activerecord/analyzer"
|
|
|
10
10
|
|
|
11
11
|
module Rigor
|
|
12
12
|
module Plugin
|
|
13
|
-
# rigor-activerecord — types ActiveRecord finder + relation
|
|
14
|
-
#
|
|
15
|
-
# AR model classes.
|
|
13
|
+
# rigor-activerecord — types ActiveRecord finder + relation calls against the project's `db/schema.rb`
|
|
14
|
+
# and discovered AR model classes.
|
|
16
15
|
#
|
|
17
16
|
# ## Architecture
|
|
18
17
|
#
|
|
19
18
|
# Two cached producers per plugin run:
|
|
20
19
|
#
|
|
21
|
-
# 1. `:schema_table` reads `db/schema.rb` via the `IoBoundary`
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# configured `model_search_paths`, finds class declarations
|
|
27
|
-
# whose direct superclass is in `model_base_classes`, and
|
|
28
|
-
# composes them with the schema table into a {ModelIndex}.
|
|
20
|
+
# 1. `:schema_table` reads `db/schema.rb` via the `IoBoundary` and parses it through {SchemaParser} into
|
|
21
|
+
# a {SchemaTable} mapping `table_name → { column_name → Column }`.
|
|
22
|
+
# 2. `:model_index` walks every `.rb` file under the configured `model_search_paths`, finds class
|
|
23
|
+
# declarations whose direct superclass is in `model_base_classes`, and composes them with the schema
|
|
24
|
+
# table into a {ModelIndex}.
|
|
29
25
|
#
|
|
30
|
-
# Both producers ride `Plugin::Base#cache_for` (ADR-60 WD3
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# so editing `db/schema.rb`, editing any model, or adding a new
|
|
35
|
-
# model file invalidates exactly the right cache entry.
|
|
26
|
+
# Both producers ride `Plugin::Base#cache_for` (ADR-60 WD3 record-and-validate): each producer's
|
|
27
|
+
# in-block boundary reads are captured into its dependency descriptor after the block runs, and
|
|
28
|
+
# `model_index`'s `watch:` covers model-file additions, so editing `db/schema.rb`, editing any model, or
|
|
29
|
+
# adding a new model file invalidates exactly the right cache entry.
|
|
36
30
|
#
|
|
37
|
-
# The per-file `#diagnostics_for_file` hook delegates to
|
|
38
|
-
#
|
|
39
|
-
# `Model.find` / `Model.find_by` / `Model.where` calls
|
|
40
|
-
# against the index.
|
|
31
|
+
# The per-file `#diagnostics_for_file` hook delegates to {Analyzer}, which walks Prism and emits
|
|
32
|
+
# diagnostics for `Model.find` / `Model.find_by` / `Model.where` calls against the index.
|
|
41
33
|
#
|
|
42
34
|
# ## Configuration
|
|
43
35
|
#
|
|
@@ -48,25 +40,19 @@ module Rigor
|
|
|
48
40
|
# model_search_paths: ["app/models"]
|
|
49
41
|
# model_base_classes: ["ApplicationRecord", "ActiveRecord::Base"]
|
|
50
42
|
#
|
|
51
|
-
# All three keys default to the values shown above. The class
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# `::ActiveRecord` even though the gem name is hyphenated.
|
|
43
|
+
# All three keys default to the values shown above. The class name `Rigor::Plugin::Activerecord` (single
|
|
44
|
+
# capital R) is intentional — keeps the constant lookup distinct from `::ActiveRecord` even though the
|
|
45
|
+
# gem name is hyphenated.
|
|
55
46
|
#
|
|
56
|
-
# Note: this plugin is the seventh worked example. It does NOT
|
|
57
|
-
#
|
|
58
|
-
# source, the same way the other examples do. Rigor stays
|
|
59
|
-
# decoupled from Rails.
|
|
47
|
+
# Note: this plugin is the seventh worked example. It does NOT require `active_record` at runtime — it
|
|
48
|
+
# only reads project source, the same way the other examples do. Rigor stays decoupled from Rails.
|
|
60
49
|
class Activerecord < Rigor::Plugin::Base
|
|
61
50
|
manifest(
|
|
62
51
|
id: "activerecord",
|
|
63
|
-
# Bumped 2026-05-28 — implicit-self class-side AR call
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# `
|
|
67
|
-
# falling through to `Kernel#select` (the IO multiplexer,
|
|
68
|
-
# `Array[String]` return). Plus `:select` added to the
|
|
69
|
-
# relation-entry-point list.
|
|
52
|
+
# Bumped 2026-05-28 — implicit-self class-side AR call resolution: `select(:uri).group(:uri)` inside
|
|
53
|
+
# a scope lambda body / class-method body now contributes `Relation[Model]` via `scope.self_type`
|
|
54
|
+
# instead of falling through to `Kernel#select` (the IO multiplexer, `Array[String]` return). Plus
|
|
55
|
+
# `:select` added to the relation-entry-point list.
|
|
70
56
|
version: "0.5.0",
|
|
71
57
|
description: "Types ActiveRecord finders against the project's db/schema.rb and AR models.",
|
|
72
58
|
config_schema: {
|
|
@@ -75,37 +61,30 @@ module Rigor
|
|
|
75
61
|
"model_base_classes" => { kind: :array, default: %w[ApplicationRecord ActiveRecord::Base] }
|
|
76
62
|
},
|
|
77
63
|
produces: [:model_index],
|
|
78
|
-
# ADR-25 — the bundled `ActiveRecord::Relation` RBS that
|
|
79
|
-
#
|
|
80
|
-
# `Model.where`, scopes) dispatch against.
|
|
64
|
+
# ADR-25 — the bundled `ActiveRecord::Relation` RBS that relation-typed call sites (`has_many`
|
|
65
|
+
# accessors, `Model.where`, scopes) dispatch against.
|
|
81
66
|
signature_paths: ["sig"],
|
|
82
|
-
# ADR-26 — `ActiveRecord::Relation` is an "open" receiver:
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# must not fire for it. `CheckRules` reads this manifest
|
|
86
|
-
# field and skips the rule for the class.
|
|
67
|
+
# ADR-26 — `ActiveRecord::Relation` is an "open" receiver: it delegates an unbounded set of
|
|
68
|
+
# user-defined scopes / class methods to its model, so `call.undefined-method` must not fire for
|
|
69
|
+
# it. `CheckRules` reads this manifest field and skips the rule for the class.
|
|
87
70
|
open_receivers: ["ActiveRecord::Relation"]
|
|
88
71
|
)
|
|
89
72
|
|
|
90
|
-
# The class the bundled `sig/active_record/relation.rbs`
|
|
91
|
-
#
|
|
92
|
-
# `
|
|
93
|
-
# call sites (`has_many` accessors, `Model.where`, scopes).
|
|
73
|
+
# The class the bundled `sig/active_record/relation.rbs` describes; `dynamic_return` contributes
|
|
74
|
+
# `ActiveRecord::Relation[Model]` for relation-returning call sites (`has_many` accessors,
|
|
75
|
+
# `Model.where`, scopes).
|
|
94
76
|
RELATION_CLASS_NAME = "ActiveRecord::Relation"
|
|
95
77
|
|
|
96
|
-
# Cached: parsed schema table. The producer reads `@schema_file`
|
|
97
|
-
#
|
|
98
|
-
# digest, then parses through {SchemaParser}.
|
|
78
|
+
# Cached: parsed schema table. The producer reads `@schema_file` via `io_boundary.read_file` so the
|
|
79
|
+
# descriptor picks up the digest, then parses through {SchemaParser}.
|
|
99
80
|
producer :schema_table do |_params|
|
|
100
81
|
contents = io_boundary.read_file(@schema_file)
|
|
101
82
|
SchemaParser.parse(contents)
|
|
102
83
|
end
|
|
103
84
|
|
|
104
|
-
# Cached: model index. Walks every model file, then composes
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
# are captured into the record-and-validate dependency
|
|
108
|
-
# descriptor after the block runs.
|
|
85
|
+
# Cached: model index. Walks every model file, then composes the rows with the cached schema table.
|
|
86
|
+
# `watch:` (ADR-60 WD3) covers model-file additions; the discoverer's in-block reads are captured
|
|
87
|
+
# into the record-and-validate dependency descriptor after the block runs.
|
|
109
88
|
producer :model_index, watch: -> { [[@model_search_paths, "**/*.rb"]] } do |_params|
|
|
110
89
|
rows = ModelDiscoverer.new(
|
|
111
90
|
io_boundary: io_boundary,
|
|
@@ -125,15 +104,11 @@ module Rigor
|
|
|
125
104
|
@load_errors = []
|
|
126
105
|
end
|
|
127
106
|
|
|
128
|
-
# ADR-9 cross-plugin publication. Builds the model index
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
# (c) attribute → column cross-check, future plugins
|
|
134
|
-
# that need to know "what columns does class `User`
|
|
135
|
-
# expose?") can read it without coupling to this
|
|
136
|
-
# plugin's carrier classes.
|
|
107
|
+
# ADR-9 cross-plugin publication. Builds the model index eagerly during the per-run
|
|
108
|
+
# `prepare(services)` pass and publishes a flat Hash form to the shared fact store so downstream
|
|
109
|
+
# Tier-2 consumers (rigor-actionpack Phase 1 strong-parameter validation, rigor-factorybot Phase 1
|
|
110
|
+
# (c) attribute → column cross-check, future plugins that need to know "what columns does class
|
|
111
|
+
# `User` expose?") can read it without coupling to this plugin's carrier classes.
|
|
137
112
|
#
|
|
138
113
|
# The published shape:
|
|
139
114
|
#
|
|
@@ -143,11 +118,9 @@ module Rigor
|
|
|
143
118
|
# ...
|
|
144
119
|
# }
|
|
145
120
|
#
|
|
146
|
-
# Consumers do `services.fact_store.read(plugin_id:
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
# unparseable models) leave the fact unpublished — the
|
|
150
|
-
# consumer's own degrade path runs (typically a no-op).
|
|
121
|
+
# Consumers do `services.fact_store.read(plugin_id: "activerecord", name: :model_index)` and look up
|
|
122
|
+
# by class name. Discovery failures (missing schema, unparseable models) leave the fact unpublished —
|
|
123
|
+
# the consumer's own degrade path runs (typically a no-op).
|
|
151
124
|
def prepare(services)
|
|
152
125
|
index = model_index
|
|
153
126
|
return if index.nil? || index.empty?
|
|
@@ -162,12 +135,10 @@ module Rigor
|
|
|
162
135
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
163
136
|
index = model_index
|
|
164
137
|
if index.nil?
|
|
165
|
-
# Project-global error (missing `db/schema.rb`, parse
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
# produced 346 identical load-errors; on a Solidus
|
|
170
|
-
# monorepo (no top-level `schema.rb`), 999.
|
|
138
|
+
# Project-global error (missing `db/schema.rb`, parse failure, etc.) — emit once per run rather
|
|
139
|
+
# than once per analyzed file. On a Redmine-shape project that uses migrations only (no
|
|
140
|
+
# `schema.rb`), the old path produced 346 identical load-errors; on a Solidus monorepo (no
|
|
141
|
+
# top-level `schema.rb`), 999.
|
|
171
142
|
return [] if @load_errors_emitted
|
|
172
143
|
|
|
173
144
|
@load_errors_emitted = true
|
|
@@ -179,15 +150,11 @@ module Rigor
|
|
|
179
150
|
Analyzer.new(path: path, model_index: index).analyze(root).diagnostics
|
|
180
151
|
end
|
|
181
152
|
|
|
182
|
-
# Rails migration files (`db/migrate/<timestamp>_*.rb`)
|
|
183
|
-
#
|
|
184
|
-
# the
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
# column, even though the current `db/schema.rb` no
|
|
188
|
-
# longer carries it. Validating these files against the
|
|
189
|
-
# CURRENT schema is a category error; the column
|
|
190
|
-
# diagnostics MUST stay silent.
|
|
153
|
+
# Rails migration files (`db/migrate/<timestamp>_*.rb`) and post-migration files
|
|
154
|
+
# (`db/post_migrate/`) reference the EVOLVING schema at the time the migration was written —
|
|
155
|
+
# `User.where(admin: ...)` is valid when the migration ran on a schema that still had the `admin`
|
|
156
|
+
# column, even though the current `db/schema.rb` no longer carries it. Validating these files
|
|
157
|
+
# against the CURRENT schema is a category error; the column diagnostics MUST stay silent.
|
|
191
158
|
MIGRATION_PATH_PATTERNS = [
|
|
192
159
|
%r{(\A|/)db/migrate/},
|
|
193
160
|
%r{(\A|/)db/post_migrate/}
|
|
@@ -201,48 +168,36 @@ module Rigor
|
|
|
201
168
|
MIGRATION_PATH_PATTERNS.any? { |pattern| path_s.match?(pattern) }
|
|
202
169
|
end
|
|
203
170
|
|
|
204
|
-
# The class-side finder / relation entry-point names
|
|
205
|
-
# `
|
|
206
|
-
#
|
|
207
|
-
# the model index (scopes, associations, columns).
|
|
171
|
+
# The class-side finder / relation entry-point names `finder_return_type` recognises. Static half of
|
|
172
|
+
# the `dynamic_return` name gate; the run-time half comes from the model index (scopes,
|
|
173
|
+
# associations, columns).
|
|
208
174
|
FINDER_METHOD_NAMES = %i[find find_by! find_by where all order limit none select].freeze
|
|
209
175
|
private_constant :FINDER_METHOD_NAMES
|
|
210
176
|
|
|
211
|
-
# Return-type contribution via the run-time `methods:` name
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
# normal dispatch instead of the RBS-level untyped
|
|
216
|
-
# fall-back; scopes, association accessors, and column
|
|
217
|
-
# readers narrow per the paths below.
|
|
177
|
+
# Return-type contribution via the run-time `methods:` name gate (ADR-52 slice 5b). `Model.find(id)`
|
|
178
|
+
# narrows the call site's return type to `Nominal[Model]`, so chained calls (`User.find(1).name`)
|
|
179
|
+
# resolve through the analyzer's normal dispatch instead of the RBS-level untyped fall-back; scopes,
|
|
180
|
+
# association accessors, and column readers narrow per the paths below.
|
|
218
181
|
#
|
|
219
|
-
# WHY a method-name gate and not `receivers:` — the ADR-52
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
# the
|
|
228
|
-
# column name (plus `column?` predicate forms) the model
|
|
229
|
-
# index discovered — is exactly the union of names the four
|
|
230
|
-
# resolution paths below can return a type for, so gating on
|
|
231
|
-
# it is byte-identical to the old ungated hook. It is broad
|
|
232
|
-
# (`name`, `id`, …), but membership is one Set probe and the
|
|
233
|
-
# expensive block runs only on candidate hits.
|
|
182
|
+
# WHY a method-name gate and not `receivers:` — the ADR-52 "rigor-activerecord blocker": a project
|
|
183
|
+
# model not in RBS types its constant as `Dynamic[top]`, so a receiver-type gate declines exactly the
|
|
184
|
+
# calls this plugin exists for. A *name* gate never reads the receiver type; the block keeps the
|
|
185
|
+
# plugin's own AST-constant / `self_type` / `type_of` resolution, so the Dynamic-constant case still
|
|
186
|
+
# reaches it (the same shape as rigor-sorbet's catalog path). The set — the static finder names ∪
|
|
187
|
+
# every scope, association, and column name (plus `column?` predicate forms) the model index
|
|
188
|
+
# discovered — is exactly the union of names the four resolution paths below can return a type for,
|
|
189
|
+
# so gating on it is byte-identical to the old ungated hook. It is broad (`name`, `id`, …), but
|
|
190
|
+
# membership is one Set probe and the expensive block runs only on candidate hits.
|
|
234
191
|
dynamic_return methods: -> { recognised_method_names } do |call_node, scope|
|
|
235
192
|
contribution_return_type(call_node, scope)
|
|
236
193
|
end
|
|
237
194
|
|
|
238
195
|
private
|
|
239
196
|
|
|
240
|
-
# The run-time name gate: finders ∪ scopes ∪ associations ∪
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
# then declines every call, matching the old hook's
|
|
245
|
-
# `index.nil? || index.empty?` early return.
|
|
197
|
+
# The run-time name gate: finders ∪ scopes ∪ associations ∪ column readers (+ `?` predicates).
|
|
198
|
+
# Resolved lazily on first dispatch (after `#prepare` built the index), memoised by the engine.
|
|
199
|
+
# Returns [] when discovery found nothing — the gate then declines every call, matching the old
|
|
200
|
+
# hook's `index.nil? || index.empty?` early return.
|
|
246
201
|
def recognised_method_names
|
|
247
202
|
index = model_index
|
|
248
203
|
return [] if index.nil? || index.empty?
|
|
@@ -259,8 +214,8 @@ module Rigor
|
|
|
259
214
|
names
|
|
260
215
|
end
|
|
261
216
|
|
|
262
|
-
# Resolution body for `dynamic_return` — same four-path
|
|
263
|
-
#
|
|
217
|
+
# Resolution body for `dynamic_return` — same four-path order, returning the bare type the contract
|
|
218
|
+
# expects.
|
|
264
219
|
def contribution_return_type(call_node, scope)
|
|
265
220
|
return nil unless call_node.is_a?(Prism::CallNode)
|
|
266
221
|
|
|
@@ -287,24 +242,19 @@ module Rigor
|
|
|
287
242
|
class_scope_return_type(call_node, entry)
|
|
288
243
|
end
|
|
289
244
|
|
|
290
|
-
# Implicit-self class-side call: `select(:uri)` /
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
# three cases, so the same finder / scope / relation entry-
|
|
295
|
-
# point resolution that handles `Model.where(...)` applies.
|
|
245
|
+
# Implicit-self class-side call: `select(:uri)` / `where(active: true)` inside a `def
|
|
246
|
+
# self.<method>` body, a class body, or a scope lambda body (`scope :x, -> { ... }`). The
|
|
247
|
+
# surrounding `self_type` is `Singleton[Model]` in all three cases, so the same finder / scope /
|
|
248
|
+
# relation entry-point resolution that handles `Model.where(...)` applies.
|
|
296
249
|
#
|
|
297
|
-
# Without this, `select(:uri)` inside a class body falls
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
#
|
|
301
|
-
# AR class-side `select`'s relation return type, so the
|
|
302
|
-
# canonical scope-body idiom
|
|
250
|
+
# Without this, `select(:uri)` inside a class body falls through to RBS dispatch on
|
|
251
|
+
# `Singleton[Account]`, which finds `Kernel#select` (the IO multiplexer) at `core/kernel.rbs` — its
|
|
252
|
+
# `Array[String]` return masks the AR class-side `select`'s relation return type, so the canonical
|
|
253
|
+
# scope-body idiom
|
|
303
254
|
#
|
|
304
255
|
# scope :duplicate_uris, -> { select(:uri).group(:uri) }
|
|
305
256
|
#
|
|
306
|
-
# types `select(:uri)` as `Array[String]` and the chained
|
|
307
|
-
# `.group` as `undefined-method`.
|
|
257
|
+
# types `select(:uri)` as `Array[String]` and the chained `.group` as `undefined-method`.
|
|
308
258
|
def implicit_self_class_call_return_type(call_node, scope, index)
|
|
309
259
|
return nil if scope.nil?
|
|
310
260
|
|
|
@@ -318,12 +268,10 @@ module Rigor
|
|
|
318
268
|
class_scope_return_type(call_node, entry)
|
|
319
269
|
end
|
|
320
270
|
|
|
321
|
-
# Class-side finders + the class-side relation entry points.
|
|
322
|
-
# `
|
|
323
|
-
#
|
|
324
|
-
#
|
|
325
|
-
# type through any further chained query method via the
|
|
326
|
-
# bundled `ActiveRecord::Relation` RBS.
|
|
271
|
+
# Class-side finders + the class-side relation entry points. `find` / `find_by!` return the model;
|
|
272
|
+
# `find_by` adds the `nil` arm; `where` / `all` / `order` / `limit` / `none` open a relation. The
|
|
273
|
+
# relation then carries its element type through any further chained query method via the bundled
|
|
274
|
+
# `ActiveRecord::Relation` RBS.
|
|
327
275
|
def finder_return_type(call_node, entry)
|
|
328
276
|
case call_node.name
|
|
329
277
|
when :find
|
|
@@ -331,8 +279,8 @@ module Rigor
|
|
|
331
279
|
|
|
332
280
|
Rigor::Type::Combinator.nominal_of(entry.class_name)
|
|
333
281
|
when :find_by!
|
|
334
|
-
# The bang variant raises `RecordNotFound` instead of
|
|
335
|
-
#
|
|
282
|
+
# The bang variant raises `RecordNotFound` instead of returning `nil`, so the result is
|
|
283
|
+
# non-nullable.
|
|
336
284
|
Rigor::Type::Combinator.nominal_of(entry.class_name)
|
|
337
285
|
when :find_by
|
|
338
286
|
Rigor::Type::Combinator.union(
|
|
@@ -341,31 +289,26 @@ module Rigor
|
|
|
341
289
|
)
|
|
342
290
|
when :where, :all, :order, :limit, :none, :select
|
|
343
291
|
# `:select` was added to close Mastodon's
|
|
344
|
-
# `scope :duplicate_uris, -> { select(:uri).group(:uri).having(...) }`
|
|
345
|
-
#
|
|
346
|
-
#
|
|
347
|
-
#
|
|
348
|
-
#
|
|
349
|
-
# query DSL chains through the bundled `ActiveRecord::Relation`
|
|
350
|
-
# RBS once a relation is open.
|
|
292
|
+
# `scope :duplicate_uris, -> { select(:uri).group(:uri).having(...) }` shape: the implicit-self
|
|
293
|
+
# `select(:uri)` inside the scope lambda body had been resolving to `Kernel#select` (IO
|
|
294
|
+
# multiplexer, return `Array[String]`), masking the AR class-side relation entry point. The rest
|
|
295
|
+
# of the query DSL chains through the bundled `ActiveRecord::Relation` RBS once a relation is
|
|
296
|
+
# open.
|
|
351
297
|
relation_of(entry.class_name)
|
|
352
298
|
end
|
|
353
299
|
end
|
|
354
300
|
|
|
355
|
-
# `Post.published` / `Post.recent(5)` — a user-declared
|
|
356
|
-
#
|
|
357
|
-
# arguments it takes.
|
|
301
|
+
# `Post.published` / `Post.recent(5)` — a user-declared `scope` returns a relation of the model
|
|
302
|
+
# regardless of the arguments it takes.
|
|
358
303
|
def class_scope_return_type(call_node, entry)
|
|
359
304
|
return nil unless entry.scope?(call_node.name)
|
|
360
305
|
|
|
361
306
|
relation_of(entry.class_name)
|
|
362
307
|
end
|
|
363
308
|
|
|
364
|
-
# `ActiveRecord::Relation[Model]` — the type the bundled
|
|
365
|
-
# `
|
|
366
|
-
#
|
|
367
|
-
# scope call the bundled RBS cannot enumerate does not
|
|
368
|
-
# surface as `call.undefined-method` (ADR-26).
|
|
309
|
+
# `ActiveRecord::Relation[Model]` — the type the bundled `sig/active_record/relation.rbs` describes.
|
|
310
|
+
# The class is declared `open_receivers` in the manifest, so a chained scope call the bundled RBS
|
|
311
|
+
# cannot enumerate does not surface as `call.undefined-method` (ADR-26).
|
|
369
312
|
def relation_of(model_class_name)
|
|
370
313
|
Rigor::Type::Combinator.nominal_of(
|
|
371
314
|
RELATION_CLASS_NAME,
|
|
@@ -373,24 +316,17 @@ module Rigor
|
|
|
373
316
|
)
|
|
374
317
|
end
|
|
375
318
|
|
|
376
|
-
# A scope invoked on an already-typed relation
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
# RBS
|
|
380
|
-
#
|
|
381
|
-
#
|
|
382
|
-
# resolves `where` / `order` / `each` / `first` precisely.
|
|
383
|
-
# Scopes may take arguments (`relation.recent(5)`), so —
|
|
384
|
-
# unlike `instance_call_return_type` — argument calls are
|
|
385
|
-
# not skipped.
|
|
319
|
+
# A scope invoked on an already-typed relation (`User.where(active: true).published`) keeps the
|
|
320
|
+
# relation type through the chain. The bundled `ActiveRecord::Relation` RBS cannot enumerate
|
|
321
|
+
# user-defined scopes, so without this the chain would lose its element type after the first scope
|
|
322
|
+
# call. Non-scope methods decline — the RBS tier resolves `where` / `order` / `each` / `first`
|
|
323
|
+
# precisely. Scopes may take arguments (`relation.recent(5)`), so — unlike `instance_call_return_type`
|
|
324
|
+
# — argument calls are not skipped.
|
|
386
325
|
#
|
|
387
|
-
# The cheap `scope_name?` pre-check is load-bearing: it
|
|
388
|
-
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
391
|
-
# and calling it for every call node in a long method chain
|
|
392
|
-
# is pathologically expensive — the pre-check keeps the
|
|
393
|
-
# cost off the hot path.
|
|
326
|
+
# The cheap `scope_name?` pre-check is load-bearing: it gates the `scope.type_of(receiver)` call so
|
|
327
|
+
# the receiver type is computed ONLY when the method name could be a scope. `type_of` on a call
|
|
328
|
+
# receiver re-enters dispatch, and calling it for every call node in a long method chain is
|
|
329
|
+
# pathologically expensive — the pre-check keeps the cost off the hot path.
|
|
394
330
|
def relation_call_return_type(call_node, scope, index)
|
|
395
331
|
return nil if call_node.receiver.nil?
|
|
396
332
|
return nil unless scope_name?(call_node.name, index)
|
|
@@ -405,16 +341,15 @@ module Rigor
|
|
|
405
341
|
relation_of(model_name)
|
|
406
342
|
end
|
|
407
343
|
|
|
408
|
-
# Whether `name` is a declared `scope` on ANY model in the
|
|
409
|
-
#
|
|
410
|
-
# in `relation_call_return_type` stays O(1).
|
|
344
|
+
# Whether `name` is a declared `scope` on ANY model in the index. A run-lifetime memoised Set so the
|
|
345
|
+
# per-call check in `relation_call_return_type` stays O(1).
|
|
411
346
|
def scope_name?(name, index)
|
|
412
347
|
@all_scope_names ||= index.entries.each_value.flat_map(&:scopes).to_set
|
|
413
348
|
@all_scope_names.include?(name.to_s)
|
|
414
349
|
end
|
|
415
350
|
|
|
416
|
-
# When `type` is `ActiveRecord::Relation[Nominal[Model]]`,
|
|
417
|
-
#
|
|
351
|
+
# When `type` is `ActiveRecord::Relation[Nominal[Model]]`, returns the model class name; nil for
|
|
352
|
+
# any other type.
|
|
418
353
|
def relation_element_class_name(type)
|
|
419
354
|
return nil unless type.is_a?(Rigor::Type::Nominal)
|
|
420
355
|
return nil unless type.class_name == RELATION_CLASS_NAME
|
|
@@ -423,13 +358,10 @@ module Rigor
|
|
|
423
358
|
element.class_name if element.is_a?(Rigor::Type::Nominal)
|
|
424
359
|
end
|
|
425
360
|
|
|
426
|
-
# Instance-side navigation: when the call's receiver
|
|
427
|
-
#
|
|
428
|
-
#
|
|
429
|
-
#
|
|
430
|
-
# skipped — accessor / association calls take no args, and
|
|
431
|
-
# argument forms (`user.posts(limit: 10)`, `user.name = x`)
|
|
432
|
-
# route through Rails APIs this slice does not model.
|
|
361
|
+
# Instance-side navigation: when the call's receiver resolves to `Nominal[Model]` and the method name
|
|
362
|
+
# matches a discovered association OR a table column, the call site gets a precise return type.
|
|
363
|
+
# Calls with arguments are skipped — accessor / association calls take no args, and argument forms
|
|
364
|
+
# (`user.posts(limit: 10)`, `user.name = x`) route through Rails APIs this slice does not model.
|
|
433
365
|
def instance_call_return_type(call_node, scope, index)
|
|
434
366
|
return nil unless call_node.arguments.nil?
|
|
435
367
|
|
|
@@ -444,16 +376,12 @@ module Rigor
|
|
|
444
376
|
column_return_type(entry, call_node.name)
|
|
445
377
|
end
|
|
446
378
|
|
|
447
|
-
# The return type for an association accessor. A `belongs_to`
|
|
448
|
-
#
|
|
449
|
-
#
|
|
450
|
-
#
|
|
451
|
-
#
|
|
452
|
-
#
|
|
453
|
-
# collection narrows to `ActiveRecord::Relation[Target]` so
|
|
454
|
-
# chained query / iteration calls resolve. A polymorphic
|
|
455
|
-
# association has no single static target and declines
|
|
456
|
-
# rather than inventing a wrong type.
|
|
379
|
+
# The return type for an association accessor. A `belongs_to` / `has_one` singular association
|
|
380
|
+
# narrows to the target model — `belongs_to` is required (non-`nil`) by default since Rails 5 so it
|
|
381
|
+
# is `Nominal[Target]`, while `has_one` (and an `optional: true` / `required: false` `belongs_to`)
|
|
382
|
+
# adds the `nil` arm. A `has_many` / `has_and_belongs_to_many` collection narrows to
|
|
383
|
+
# `ActiveRecord::Relation[Target]` so chained query / iteration calls resolve. A polymorphic
|
|
384
|
+
# association has no single static target and declines rather than inventing a wrong type.
|
|
457
385
|
def association_return_type(entry, method_name)
|
|
458
386
|
association = entry.association(method_name)
|
|
459
387
|
return nil if association.nil?
|
|
@@ -470,19 +398,14 @@ module Rigor
|
|
|
470
398
|
end
|
|
471
399
|
end
|
|
472
400
|
|
|
473
|
-
# Instance-side column access. `user.name` on a
|
|
474
|
-
# `
|
|
475
|
-
# type; `user.name?` (the ActiveRecord-generated predicate)
|
|
476
|
-
# narrows to `bool`.
|
|
401
|
+
# Instance-side column access. `user.name` on a `Nominal[User]` receiver narrows to the column's
|
|
402
|
+
# value type; `user.name?` (the ActiveRecord-generated predicate) narrows to `bool`.
|
|
477
403
|
#
|
|
478
|
-
# The contributed type is deliberately NON-nullable even
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
#
|
|
482
|
-
#
|
|
483
|
-
# entire codebase. Under-reporting a nil column is a false
|
|
484
|
-
# negative; over-reporting it is a false positive — and the
|
|
485
|
-
# project ranks the latter as the worse failure.
|
|
404
|
+
# The contributed type is deliberately NON-nullable even though the DB column may permit `NULL`:
|
|
405
|
+
# Rails code calls column accessors directly (`user.email.downcase`) as a matter of course, and
|
|
406
|
+
# contributing `T | nil` would light up that idiom with `possible-nil-receiver` across an entire
|
|
407
|
+
# codebase. Under-reporting a nil column is a false negative; over-reporting it is a false positive
|
|
408
|
+
# — and the project ranks the latter as the worse failure.
|
|
486
409
|
def column_return_type(entry, method_name)
|
|
487
410
|
name = method_name.to_s
|
|
488
411
|
predicate = name.end_with?("?")
|
|
@@ -498,12 +421,10 @@ module Rigor
|
|
|
498
421
|
column.array? ? Rigor::Type::Combinator.nominal_of("Array", type_args: [inner]) : inner
|
|
499
422
|
end
|
|
500
423
|
|
|
501
|
-
# Maps a `SchemaTable::Column#ruby_type` string to a Rigor
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
#
|
|
505
|
-
# `call.undefined-method` on a value whose real shape the
|
|
506
|
-
# plugin cannot model.
|
|
424
|
+
# Maps a `SchemaTable::Column#ruby_type` string to a Rigor type. `"Object"` (json / jsonb /
|
|
425
|
+
# unrecognised column types) declines — `Nominal[Object]` would be NARROWER than the RBS-erased
|
|
426
|
+
# envelope and could surface false `call.undefined-method` on a value whose real shape the plugin
|
|
427
|
+
# cannot model.
|
|
507
428
|
def ruby_type_to_type(ruby_type)
|
|
508
429
|
case ruby_type
|
|
509
430
|
when "bool" then bool_type
|
|
@@ -546,12 +467,9 @@ module Rigor
|
|
|
546
467
|
node.arguments.arguments.size
|
|
547
468
|
end
|
|
548
469
|
|
|
549
|
-
# Marshal-clean Hash form for the cross-plugin fact
|
|
550
|
-
#
|
|
551
|
-
#
|
|
552
|
-
# `class_name → { table:, columns: }` map without
|
|
553
|
-
# depending on this plugin's `ModelIndex` /
|
|
554
|
-
# `SchemaTable::Column` carrier classes.
|
|
470
|
+
# Marshal-clean Hash form for the cross-plugin fact store. Consumers (rigor-actionpack Phase 1,
|
|
471
|
+
# rigor-factorybot Phase 1 (c), ...) get a flat `class_name → { table:, columns: }` map without
|
|
472
|
+
# depending on this plugin's `ModelIndex` / `SchemaTable::Column` carrier classes.
|
|
555
473
|
def index_to_published_hash(index)
|
|
556
474
|
index.entries.transform_values do |entry|
|
|
557
475
|
{
|
|
@@ -573,11 +491,9 @@ module Rigor
|
|
|
573
491
|
table = schema_table_or_nil
|
|
574
492
|
return nil if table.nil?
|
|
575
493
|
|
|
576
|
-
# ADR-60 WD3 record-and-validate: the producer's own in-block
|
|
577
|
-
#
|
|
578
|
-
#
|
|
579
|
-
# covers model-file additions — so no priming walk is needed
|
|
580
|
-
# (it used to run the discover twice).
|
|
494
|
+
# ADR-60 WD3 record-and-validate: the producer's own in-block `ModelDiscoverer` reads are
|
|
495
|
+
# captured into the dependency descriptor after the block runs, and the producer's `watch:` covers
|
|
496
|
+
# model-file additions — so no priming walk is needed (it used to run the discover twice).
|
|
581
497
|
@model_index = cache_for(:model_index, params: {}).call
|
|
582
498
|
rescue StandardError => e
|
|
583
499
|
@load_errors << "model index build failed: #{e.class}: #{e.message}"
|
|
@@ -586,20 +502,17 @@ module Rigor
|
|
|
586
502
|
|
|
587
503
|
def schema_table_or_nil
|
|
588
504
|
return @schema_table if @schema_table
|
|
589
|
-
# Memoize the *failure*, not just the success: `model_index`
|
|
590
|
-
#
|
|
591
|
-
#
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
# `@load_errors` to millions of retained strings (measured: 4.2 M
|
|
595
|
-
# strings / ~1.5 GB on Redmine). One attempt is enough.
|
|
505
|
+
# Memoize the *failure*, not just the success: `model_index` (and thus this method) is invoked per
|
|
506
|
+
# AR call site, so a missing / unreadable schema file would otherwise re-attempt the read and
|
|
507
|
+
# append a fresh interpolated error string to `@load_errors` on every call. On a large Rails app
|
|
508
|
+
# that grew `@load_errors` to millions of retained strings (measured: 4.2 M strings / ~1.5 GB on
|
|
509
|
+
# Redmine). One attempt is enough.
|
|
596
510
|
return nil if @schema_load_attempted
|
|
597
511
|
|
|
598
512
|
@schema_load_attempted = true
|
|
599
|
-
# ADR-60 WD3 record-and-validate: the producer reads
|
|
600
|
-
#
|
|
601
|
-
#
|
|
602
|
-
# read is needed here.
|
|
513
|
+
# ADR-60 WD3 record-and-validate: the producer reads `@schema_file` in-block, and that read is
|
|
514
|
+
# captured into the dependency descriptor after the block runs — so no priming read is needed
|
|
515
|
+
# here.
|
|
603
516
|
@schema_table = cache_for(:schema_table, params: {}).call
|
|
604
517
|
rescue Plugin::AccessDeniedError => e
|
|
605
518
|
@load_errors << "rigor-activerecord: #{e.message}"
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Gem entry point. Required by Rigor's plugin loader when
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# `Rigor::Plugin.register`, which the body of
|
|
7
|
-
# `lib/rigor/plugin/activerecord.rb` performs at load time.
|
|
3
|
+
# Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-activerecord` under
|
|
4
|
+
# `plugins:`. The loader expects this `require` to side-effect a call to `Rigor::Plugin.register`, which
|
|
5
|
+
# the body of `lib/rigor/plugin/activerecord.rb` performs at load time.
|
|
8
6
|
require_relative "rigor/plugin/activerecord"
|