rigortype 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +29 -10
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +92 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +48 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
|
@@ -161,9 +161,8 @@ module Rigor
|
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def project_defines_object?(serializer_name, scope)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
!scope.user_def_through_ancestors(serializer_name, :object).first.nil?
|
|
164
|
+
Rigor::Reflection.project_method_defined(serializer_name, :object, kind: :instance, source: :def,
|
|
165
|
+
scope: scope).yes?
|
|
167
166
|
end
|
|
168
167
|
|
|
169
168
|
# The model a serializer serializes, or nil where nothing corroborates it. Memoised per serializer
|
|
@@ -237,13 +236,11 @@ module Rigor
|
|
|
237
236
|
# the check would decline nearly every real serializer. On the serializer side it is a base
|
|
238
237
|
# serializer's own rendering method, which is not a read of the resource at all.
|
|
239
238
|
def project_defines?(class_name, method_name, scope)
|
|
240
|
-
return false unless scope.respond_to?(:user_def_through_ancestors)
|
|
241
|
-
|
|
242
239
|
# A name no Symbol can hold cannot be a method the project defines (#1831): decline, do not raise.
|
|
243
240
|
method = Rigor::Source::SymbolName.of(method_name.to_s)
|
|
244
241
|
return false if method.nil?
|
|
245
242
|
|
|
246
|
-
|
|
243
|
+
Rigor::Reflection.project_method_defined(class_name, method, kind: :instance, source: :def, scope: scope).yes?
|
|
247
244
|
end
|
|
248
245
|
|
|
249
246
|
# `REST::AccountSerializer` offers two readings — the namespaced `REST::Account` and the
|
|
@@ -55,8 +55,24 @@ module Rigor
|
|
|
55
55
|
)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Ruby binds a trailing `k: v` / `**opts` group to keyword parameters, never to positional ones, so
|
|
59
|
+
# when `#perform` takes keywords the group is not counted (the message's "got N" is then the
|
|
60
|
+
# positional count too). A job without keyword parameters keeps counting the group as one positional
|
|
61
|
+
# argument — `NoArgsJob.perform_later(name: 1)` raises at runtime. A `Hash` literal / local stays
|
|
62
|
+
# positional either way (Ruby 3 does not convert it).
|
|
63
|
+
#
|
|
64
|
+
# The call is declined (no report) when its shape is unknown: a `*splat` or `...` argument may supply
|
|
65
|
+
# any number of positionals (or a trailing keyword group), and a keyword group made only of `**opts`
|
|
66
|
+
# arguments sent to a job without keywords may be empty — declined only when the verdict differs
|
|
67
|
+
# between "empty" and "one positional hash" (`NoArgsJob.perform_later(**opts)`).
|
|
58
68
|
def arity_violation(call_node, entry)
|
|
59
|
-
|
|
69
|
+
args = call_node.arguments&.arguments || []
|
|
70
|
+
return nil if args.any? { |arg| arg.is_a?(Prism::SplatNode) || arg.is_a?(Prism::ForwardingArgumentsNode) }
|
|
71
|
+
|
|
72
|
+
keyword_group = args.last.is_a?(Prism::KeywordHashNode) ? args.last : nil
|
|
73
|
+
return nil if keyword_group && !entry.accepts_keywords && empty_group_decides?(args, keyword_group, entry)
|
|
74
|
+
|
|
75
|
+
actual = entry.accepts_keywords && keyword_group ? args.size - 1 : args.size
|
|
60
76
|
return nil if entry.accepts?(actual)
|
|
61
77
|
|
|
62
78
|
Violation.new(
|
|
@@ -66,6 +82,14 @@ module Rigor
|
|
|
66
82
|
)
|
|
67
83
|
end
|
|
68
84
|
|
|
85
|
+
# True when the group is only `**splat` arguments (so it may be empty) AND the entry's verdict changes
|
|
86
|
+
# between counting it and not counting it.
|
|
87
|
+
def empty_group_decides?(args, keyword_group, entry)
|
|
88
|
+
return false unless keyword_group.elements.all?(Prism::AssocSplatNode)
|
|
89
|
+
|
|
90
|
+
entry.accepts?(args.size - 1) != entry.accepts?(args.size)
|
|
91
|
+
end
|
|
92
|
+
|
|
69
93
|
# Renders a constant-path receiver as a String. Mirrors the helpers in rigor-activerecord /
|
|
70
94
|
# rigor-rails-routes for parity.
|
|
71
95
|
def constant_receiver_name(node)
|
|
@@ -35,7 +35,7 @@ module Rigor
|
|
|
35
35
|
|
|
36
36
|
# Where a Rails app declares its adapter. Per-environment files are read too, because an app that
|
|
37
37
|
# sets the adapter only in `config/environments/production.rb` is common; agreement across every
|
|
38
|
-
# file that names one is what licenses the narrowing (see {.
|
|
38
|
+
# file that names one is what licenses the narrowing (see {.detect_adapter}).
|
|
39
39
|
CONFIG_PATHS = ["config/application.rb", "config/environments"].freeze
|
|
40
40
|
|
|
41
41
|
SETTING = /queue_adapter\s*=\s*[:"']([a-z_]+)["']?/
|
|
@@ -76,7 +76,8 @@ module Rigor
|
|
|
76
76
|
base.with(
|
|
77
77
|
min_arity: [base.min_arity, addition.min_arity].min,
|
|
78
78
|
max_arity: [base.max_arity, addition.max_arity].max,
|
|
79
|
-
keyword_required: base.keyword_required & addition.keyword_required
|
|
79
|
+
keyword_required: base.keyword_required & addition.keyword_required,
|
|
80
|
+
accepts_keywords: base.accepts_keywords || addition.accepts_keywords
|
|
80
81
|
)
|
|
81
82
|
end
|
|
82
83
|
|
|
@@ -187,7 +188,7 @@ module Rigor
|
|
|
187
188
|
if perform_def.nil?
|
|
188
189
|
return JobIndex::Entry.new(
|
|
189
190
|
class_name: class_name, min_arity: 0,
|
|
190
|
-
max_arity: Float::INFINITY, keyword_required: []
|
|
191
|
+
max_arity: Float::INFINITY, keyword_required: [], accepts_keywords: true
|
|
191
192
|
)
|
|
192
193
|
end
|
|
193
194
|
|
|
@@ -195,22 +196,31 @@ module Rigor
|
|
|
195
196
|
if parameters.nil?
|
|
196
197
|
return JobIndex::Entry.new(
|
|
197
198
|
class_name: class_name, min_arity: 0,
|
|
198
|
-
max_arity: 0, keyword_required: []
|
|
199
|
+
max_arity: 0, keyword_required: [], accepts_keywords: false
|
|
199
200
|
)
|
|
200
201
|
end
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
# Parameters after an optional or rest one (`def perform(a, b = 1, c)`) are required too.
|
|
204
|
+
required_count = (parameters.requireds || []).size + (parameters.posts || []).size
|
|
203
205
|
optional_count = (parameters.optionals || []).size
|
|
204
206
|
rest_present = !parameters.rest.nil?
|
|
205
207
|
keyword_required = (parameters.keywords || []).filter_map do |kw|
|
|
206
208
|
kw.name if kw.is_a?(Prism::RequiredKeywordParameterNode)
|
|
207
209
|
end
|
|
208
210
|
|
|
211
|
+
keyword_rest = parameters.keyword_rest
|
|
212
|
+
# `...` lives in `keyword_rest` and forwards every argument, so it widens the envelope; `**nil`
|
|
213
|
+
# also lives there but declares that NO keywords are taken (ActiveJob then hands them over as a
|
|
214
|
+
# positional hash, exactly like a method with no keyword parameters).
|
|
215
|
+
forwards_all = keyword_rest.is_a?(Prism::ForwardingParameterNode)
|
|
216
|
+
takes_keywords = !(parameters.keywords || []).empty? || keyword_rest.is_a?(Prism::KeywordRestParameterNode)
|
|
217
|
+
|
|
209
218
|
JobIndex::Entry.new(
|
|
210
219
|
class_name: class_name,
|
|
211
220
|
min_arity: required_count,
|
|
212
|
-
max_arity: rest_present ? Float::INFINITY : required_count + optional_count,
|
|
213
|
-
keyword_required: keyword_required.map(&:to_sym)
|
|
221
|
+
max_arity: rest_present || forwards_all ? Float::INFINITY : required_count + optional_count,
|
|
222
|
+
keyword_required: keyword_required.map(&:to_sym),
|
|
223
|
+
accepts_keywords: takes_keywords || forwards_all
|
|
214
224
|
)
|
|
215
225
|
end
|
|
216
226
|
end
|
|
@@ -10,9 +10,11 @@ module Rigor
|
|
|
10
10
|
# `min_arity` / `max_arity` form a closed range (`Float::INFINITY` for the upper bound when
|
|
11
11
|
# `*args` is present). `keyword_required` lists any required keyword arguments — Active Job
|
|
12
12
|
# supports keyword args but they're rare in user code, so the analyzer validates positional arity
|
|
13
|
-
# only (keyword
|
|
13
|
+
# only (keyword name validation is deferred). `accepts_keywords` records whether `#perform` binds
|
|
14
|
+
# keywords at all (`k:`, `k: 1` or `**opts`), so the analyzer can keep a call's keyword arguments out
|
|
15
|
+
# of the positional count.
|
|
14
16
|
class JobIndex
|
|
15
|
-
Entry = Data.define(:class_name, :min_arity, :max_arity, :keyword_required) do
|
|
17
|
+
Entry = Data.define(:class_name, :min_arity, :max_arity, :keyword_required, :accepts_keywords) do
|
|
16
18
|
# Flexible-friendly textual form of the arity for error messages: `1`, `1..2`, `2+`.
|
|
17
19
|
def arity_label
|
|
18
20
|
return "#{min_arity}+" if max_arity == Float::INFINITY
|
|
@@ -13,7 +13,7 @@ module Rigor
|
|
|
13
13
|
# rigor-activejob — validates `Job.perform_later(...)` / `.perform_now(...)` / `.perform(...)`
|
|
14
14
|
# argument arity against the discovered `#perform` definitions.
|
|
15
15
|
#
|
|
16
|
-
# Tier 1D of the [Rails plugins roadmap](
|
|
16
|
+
# Tier 1D of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
17
|
# Statically discovers ActiveJob subclasses by walking the configured `job_search_paths` and
|
|
18
18
|
# parsing each file with Prism — no `active_job` runtime dependency.
|
|
19
19
|
#
|
|
@@ -98,9 +98,10 @@ module Rigor
|
|
|
98
98
|
# Cached separately from `:job_index` because the two invalidate on different files: editing a
|
|
99
99
|
# schedule changes which jobs are reached without touching `app/jobs` at all. The `watch:` roots the
|
|
100
100
|
# glob at the working directory so that CREATING a schedule file — not just editing one — is seen. Each
|
|
101
|
-
# configured path is escaped to a literal name, so `recurring[1].yml` is watched as that file
|
|
101
|
+
# configured path is escaped to a literal name, so `recurring[1].yml` is watched as that file, and an
|
|
102
|
+
# absolute path is watched under its own directory (`RootedGlob.split_file`, #2222).
|
|
102
103
|
producer :recurring_jobs,
|
|
103
|
-
watch: -> {
|
|
104
|
+
watch: -> { @recurring_paths.map { |path| Rigor::RootedGlob.split_file(path) } } do |_params|
|
|
104
105
|
RecurringScan.new(
|
|
105
106
|
io_boundary: io_boundary,
|
|
106
107
|
recurring_paths: @recurring_paths
|
|
@@ -110,7 +110,8 @@ module Rigor
|
|
|
110
110
|
# The predicate `Activerecord#finder_return_type` declines on, so the note and the type agree in a
|
|
111
111
|
# CLI run. In an editor run this scope lacks the file's own methods (#1329).
|
|
112
112
|
def own_find?(entry)
|
|
113
|
-
|
|
113
|
+
!Rigor::Reflection.project_method_defined(entry.class_name, :find, kind: :singleton, source: :discovered,
|
|
114
|
+
scope: @scope).no?
|
|
114
115
|
end
|
|
115
116
|
|
|
116
117
|
def validate_column_hash_call(node, entry)
|
|
@@ -20,8 +20,9 @@ module Rigor
|
|
|
20
20
|
#
|
|
21
21
|
# Declined — no root, never a guess: a polymorphic association (no single target), a non-literal
|
|
22
22
|
# `class_name:` / `**opts`, `anonymous_class:`, a `with_options` group with non-literal options, and a
|
|
23
|
-
# `through:` association without a literal `class_name:` / `source_type:`. Without them a `through:`
|
|
24
|
-
#
|
|
23
|
+
# `through:` association without a literal `class_name:` / `source_type:`. Without them a `through:`
|
|
24
|
+
# target is the SOURCE association's class on the through model, and that association (declared
|
|
25
|
+
# there, hence in this same index) roots it itself.
|
|
25
26
|
#
|
|
26
27
|
# A computed name equal to the owner's own demodulized name declines too (Rails 7.2+ tries `::Name`
|
|
27
28
|
# first; the target is then the owner itself or a top-level class).
|
|
@@ -28,8 +28,8 @@ module Rigor
|
|
|
28
28
|
#
|
|
29
29
|
# An `ActiveRecord::Base` row reaches `User.find` through the project's own
|
|
30
30
|
# `User < ApplicationRecord < ActiveRecord::Base` lines, so one row covers every model in the app.
|
|
31
|
-
# That is the whole economy of the framework layer:
|
|
32
|
-
# database touches a Rails app performs, and none of them needed a line of application code.
|
|
31
|
+
# That is the whole economy of the framework layer: a fixed set of rows here colours the several
|
|
32
|
+
# thousand database touches a Rails app performs, and none of them needed a line of application code.
|
|
33
33
|
module Effects
|
|
34
34
|
BASE = "ActiveRecord::Base"
|
|
35
35
|
RELATION = "ActiveRecord::Relation"
|
|
@@ -186,27 +186,20 @@ module Rigor
|
|
|
186
186
|
merge_redeclarations(rows)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
# Collapses the rows that declare the SAME constant into one, so {ModelIndex.build}
|
|
190
|
-
#
|
|
189
|
+
# Collapses the rows that declare the SAME constant into one, so {ModelIndex.build} (keyed by
|
|
190
|
+
# `class_name`) receives at most one row per model.
|
|
191
191
|
#
|
|
192
|
-
# A model is routinely declared more than once
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
# rows are UNIONed here. Taking the last row instead dropped the real declaration's associations,
|
|
197
|
-
# scopes, enums, validations, callbacks and `alias_attribute`s whenever the reopen sorted later in
|
|
198
|
-
# the glob, and `where(<a declared alias>: …)` then surfaced a false `unknown-column` on correct
|
|
199
|
-
# code; taking the first dropped whatever the reopen added. Neither order loses anything now.
|
|
192
|
+
# A model is routinely declared more than once (`app/models/user.rb` plus a reopening file). Ruby's
|
|
193
|
+
# semantics are ADDITIVE, so the rows are UNIONed. Taking the last row dropped the real declaration's
|
|
194
|
+
# associations, scopes, enums, validations, callbacks and `alias_attribute`s whenever the reopen sorted
|
|
195
|
+
# later, producing false `unknown-column`s; taking the first dropped what the reopen added.
|
|
200
196
|
#
|
|
201
|
-
# Merge is by field: the first non-nil `superclass_name` wins
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# inexact); name-keyed rows (associations, enums, aliases) let the LAST declaration override an
|
|
208
|
-
# earlier same-name row exactly as {ModelIndex.merge_named_rows} does across an STI chain, and the
|
|
209
|
-
# plain lists union.
|
|
197
|
+
# Merge is by field: the first non-nil `superclass_name` wins. `self.table_name =` is an ASSIGNMENT, so
|
|
198
|
+
# the later `table_name_override` wins, but when two declarations disagree the name is marked computed
|
|
199
|
+
# so no value is pinned: glob order is not load order, and a pinned wrong literal would fold
|
|
200
|
+
# `Model.table_name == "…"` on correct code. `table_name_computed` is otherwise an OR. Name-keyed rows
|
|
201
|
+
# let the LAST declaration override an earlier same-name row, as {ModelIndex.merge_named_rows} does
|
|
202
|
+
# across an STI chain; plain lists union.
|
|
210
203
|
def merge_redeclarations(rows)
|
|
211
204
|
return rows if rows.length < 2
|
|
212
205
|
|
|
@@ -459,7 +452,6 @@ module Rigor
|
|
|
459
452
|
macro_methods: lookup_macro_methods(node.body)
|
|
460
453
|
})
|
|
461
454
|
|
|
462
|
-
# Recurse into the body in case nested classes exist.
|
|
463
455
|
walk_for_classes(node.body, [full_name], &) if node.body
|
|
464
456
|
end
|
|
465
457
|
|
|
@@ -869,8 +861,8 @@ module Rigor
|
|
|
869
861
|
|
|
870
862
|
# Walks the namespace body's TOP-LEVEL statements only — a decorator declared inside a nested
|
|
871
863
|
# `module`/`class` belongs to THAT namespace, not this one, and must not be attributed here.
|
|
872
|
-
# Returns
|
|
873
|
-
#
|
|
864
|
+
# Returns the decorators keyed by `:prefix` / `:suffix`, only for the names this body actually mentions.
|
|
865
|
+
# A later statement overrides an earlier one within the SAME body — real Ruby
|
|
874
866
|
# assignment order, not a guess: `mattr_accessor :table_name_prefix` (no `default:`, so `computed:
|
|
875
867
|
# true`) followed two lines down by a real `self.table_name_prefix = "blog_"` folds to the literal.
|
|
876
868
|
#
|
|
@@ -1234,8 +1226,8 @@ module Rigor
|
|
|
1234
1226
|
end
|
|
1235
1227
|
|
|
1236
1228
|
# Recognised single-instance and collection association DSL methods. The kind drives the eventual
|
|
1237
|
-
# return-type contribution: singular associations narrow to
|
|
1238
|
-
#
|
|
1229
|
+
# return-type contribution: singular associations narrow to the target or nil, plural ones to a
|
|
1230
|
+
# relation of the target.
|
|
1239
1231
|
#
|
|
1240
1232
|
# `composed_of` value-object aggregations and `delegated_type` roles are folded in here too — both
|
|
1241
1233
|
# accept the association name as a `where` / `find_by` query key, so omitting them turns every
|
|
@@ -1336,7 +1328,7 @@ module Rigor
|
|
|
1336
1328
|
association_option(args, "polymorphic") == true
|
|
1337
1329
|
|
|
1338
1330
|
# A polymorphic association has no single static target class — `target` is nil and the flow
|
|
1339
|
-
# contribution declines to narrow rather than inventing a wrong
|
|
1331
|
+
# contribution declines to narrow rather than inventing a wrong class-name target.
|
|
1340
1332
|
if polymorphic
|
|
1341
1333
|
target = nil
|
|
1342
1334
|
else
|
|
@@ -1460,8 +1452,7 @@ module Rigor
|
|
|
1460
1452
|
end
|
|
1461
1453
|
|
|
1462
1454
|
# `enum status: { active: 0, archived: 1 }` (Rails ≤6) and `enum :status, [:active, :archived]`
|
|
1463
|
-
# (Rails 7+).
|
|
1464
|
-
# guess.
|
|
1455
|
+
# (Rails 7+). Non-literal forms decline rather than guess.
|
|
1465
1456
|
def lookup_enums(body)
|
|
1466
1457
|
return {} if body.nil?
|
|
1467
1458
|
|
|
@@ -1516,7 +1507,7 @@ module Rigor
|
|
|
1516
1507
|
end
|
|
1517
1508
|
|
|
1518
1509
|
# `scope :active, -> { ... }`. Records the scope name only (the body is intentionally NOT
|
|
1519
|
-
# introspected — the caller contributes
|
|
1510
|
+
# introspected — the caller contributes a relation of the model based on the name alone
|
|
1520
1511
|
# via `class_scope_return_type`).
|
|
1521
1512
|
def lookup_scopes(body)
|
|
1522
1513
|
return [] if body.nil?
|
|
@@ -1585,8 +1576,7 @@ module Rigor
|
|
|
1585
1576
|
|
|
1586
1577
|
# `alias_attribute :new_name, :old_name`. Records the mapping so the analyzer accepts the alias
|
|
1587
1578
|
# as a query key — without it every `where(<alias>: ...)` / `find_by(<alias>: ...)` call surfaces
|
|
1588
|
-
# as a false `unknown-column`.
|
|
1589
|
-
# rather than guess.
|
|
1579
|
+
# as a false `unknown-column`. Non-Symbol-literal forms decline rather than guess.
|
|
1590
1580
|
def lookup_aliases(body)
|
|
1591
1581
|
return {} if body.nil?
|
|
1592
1582
|
|
|
@@ -1626,7 +1616,7 @@ module Rigor
|
|
|
1626
1616
|
# macro and `images`, `images=`, `images_attachments`, `images_blobs` for the collection one — the
|
|
1627
1617
|
# `has_one_attached` / `has_many_attached` generated-methods surface, none of it column-backed.
|
|
1628
1618
|
# This is name MEMBERSHIP only, and does not overlap `rigor-activestorage`, which contributes the
|
|
1629
|
-
# accessor's
|
|
1619
|
+
# accessor's type and publishes no fact: a consumer that reads
|
|
1630
1620
|
# `:model_index` to ask "does this model answer `avatar`?" gets nothing from that plugin, and gets
|
|
1631
1621
|
# nothing at all when the project does not load it.
|
|
1632
1622
|
ATTACHMENT_MACROS = {
|
|
@@ -29,14 +29,12 @@ module Rigor
|
|
|
29
29
|
# {Entry} resolves its table from the root model and inherits the chain's declared associations /
|
|
30
30
|
# enums / aliases / scopes / validations / callbacks / macro methods.
|
|
31
31
|
class ModelIndex
|
|
32
|
-
# `associations` is a frozen
|
|
33
|
-
# nullable: }`:
|
|
32
|
+
# `associations` is a frozen list of rows `{ name:, kind:, target:, nullable: }`:
|
|
34
33
|
#
|
|
35
|
-
# - `name` —
|
|
34
|
+
# - `name` — the association method name as the user invokes it (`"posts"`).
|
|
36
35
|
# - `kind` — `:singular` (`belongs_to` / `has_one`) or `:collection` (`has_many`).
|
|
37
|
-
# - `target` —
|
|
38
|
-
#
|
|
39
|
-
# - `nullable` — Boolean; whether a `:singular` accessor can return `nil`. `has_one` → `true`;
|
|
36
|
+
# - `target` — the target class name, from an explicit `class_name:` option or via {Inflector.classify}.
|
|
37
|
+
# - `nullable` — whether a `:singular` accessor can return `nil`. `has_one` → `true`;
|
|
40
38
|
# `belongs_to` → `false` (required by default since Rails 5) unless `optional: true`
|
|
41
39
|
# / `required: false`. Meaningless for `:collection` rows.
|
|
42
40
|
#
|
|
@@ -71,27 +69,23 @@ module Rigor
|
|
|
71
69
|
|
|
72
70
|
def association_names = associations.map { |a| a[:name] }
|
|
73
71
|
|
|
74
|
-
# `enums`
|
|
72
|
+
# `enums` maps a column name to its value names.
|
|
75
73
|
def enum?(name) = enums.key?(name.to_s)
|
|
76
74
|
def enum_values(name) = enums.fetch(name.to_s, [])
|
|
77
75
|
|
|
78
|
-
# `scopes` is `Array<scope_name>`.
|
|
79
76
|
def scope?(name) = scopes.include?(name.to_s)
|
|
80
77
|
|
|
81
|
-
# `validations`
|
|
82
|
-
# `validates_*_of :name, ...` shorthand families.
|
|
78
|
+
# `validations` covers both `validates :name, ...` and the `validates_*_of :name, ...` shorthand families.
|
|
83
79
|
def validation?(name) = validations.include?(name.to_s)
|
|
84
80
|
def validated_attributes = validations
|
|
85
81
|
|
|
86
|
-
# `callbacks` is `Array<{ name:, callback: }>`.
|
|
87
82
|
def callback_targets = callbacks.map { |c| c[:name] }
|
|
88
83
|
|
|
89
|
-
# `aliases`
|
|
90
|
-
# declarations.
|
|
84
|
+
# `aliases` maps an alias name to its target attribute, from `alias_attribute` declarations.
|
|
91
85
|
def alias?(name) = aliases.key?(name.to_s)
|
|
92
86
|
def resolve_alias(name) = aliases[name.to_s]
|
|
93
87
|
|
|
94
|
-
# `macro_methods`
|
|
88
|
+
# `macro_methods` lists the instance methods a declaration macro installs that
|
|
95
89
|
# none of the sets above already carries (#1049): `delegate`'s names, the Paperclip / Active
|
|
96
90
|
# Storage attachment readers, and an `enum`'s per-value predicates. Names only, with no type: a
|
|
97
91
|
# consumer asks "does this model answer `followers_count`?", which is what a fail-closed check
|
|
@@ -312,7 +306,7 @@ module Rigor
|
|
|
312
306
|
seen.values.map(&:freeze).freeze
|
|
313
307
|
end
|
|
314
308
|
|
|
315
|
-
#
|
|
309
|
+
# Enum value lists merged across the STI chain; a child redeclaration of the same
|
|
316
310
|
# enum column overrides the ancestor's value list.
|
|
317
311
|
def self.merge_enums(chain)
|
|
318
312
|
chain.each_with_object({}) do |row, acc|
|
|
@@ -320,7 +314,7 @@ module Rigor
|
|
|
320
314
|
end.freeze
|
|
321
315
|
end
|
|
322
316
|
|
|
323
|
-
#
|
|
317
|
+
# Alias map merged across the STI chain.
|
|
324
318
|
def self.merge_aliases(chain)
|
|
325
319
|
chain.each_with_object({}) do |row, acc|
|
|
326
320
|
(row[:aliases] || {}).each { |name, target| acc[name.to_s] = target.to_s }
|
|
@@ -31,10 +31,17 @@ module Rigor
|
|
|
31
31
|
# `Object` per `SchemaTable.ruby_type_for` rather than being dropped. Only the structural calls in
|
|
32
32
|
# {NON_COLUMN_METHODS} (indexes, constraints, foreign keys) are skipped.
|
|
33
33
|
#
|
|
34
|
+
# `create_table "x", primary_key: "name"` adds that column, typed from the `id:` option when the call is
|
|
35
|
+
# read completely and untyped otherwise. `record.id` reads the primary key column's value, so the `id`
|
|
36
|
+
# column takes the same type; a composite or unreadable `primary_key:` leaves it untyped. `id: false` or
|
|
37
|
+
# `id: nil` removes it and adds neither.
|
|
38
|
+
#
|
|
34
39
|
# Designed for the Prism interpretation pattern from rigor-lisp-eval — recursive descent on the AST,
|
|
35
40
|
# no eval.
|
|
36
41
|
class SchemaParser
|
|
37
42
|
TIMESTAMPS_COLUMNS = %w[created_at updated_at].freeze
|
|
43
|
+
DEFAULT_PRIMARY_KEY_TYPE = :bigint
|
|
44
|
+
UNKNOWN_PRIMARY_KEY_TYPE = :unknown
|
|
38
45
|
|
|
39
46
|
# Structural `t.<method>` calls inside a `create_table` block that declare indexes / constraints
|
|
40
47
|
# rather than columns. Everything NOT in this set that carries a literal column name is treated as
|
|
@@ -75,8 +82,10 @@ module Rigor
|
|
|
75
82
|
return [nil, nil] if table_name.nil?
|
|
76
83
|
|
|
77
84
|
block_node = call_node.block
|
|
78
|
-
columns = { "id" =>
|
|
85
|
+
columns = { "id" => implicit_id_column(call_node) }
|
|
79
86
|
columns.delete("id") if id_disabled?(call_node)
|
|
87
|
+
primary_key = named_primary_key_column(call_node)
|
|
88
|
+
columns[primary_key.name] = primary_key if primary_key
|
|
80
89
|
|
|
81
90
|
if block_node.is_a?(Prism::BlockNode) && block_node.body
|
|
82
91
|
collect_column_calls(block_node.body) do |column_call|
|
|
@@ -92,25 +101,133 @@ module Rigor
|
|
|
92
101
|
[table_name, columns.freeze]
|
|
93
102
|
end
|
|
94
103
|
|
|
104
|
+
# `record.id` reads the primary key column's value, so the `id` reader takes the key's type — the
|
|
105
|
+
# same shape the `id:` option gives the column `primary_key:` names or the implicit `id` column.
|
|
106
|
+
# A composite `primary_key:` makes `id` an Array this parser does not type, and a `primary_key:`
|
|
107
|
+
# it cannot read leaves `id` untyped as well.
|
|
108
|
+
def implicit_id_column(call_node)
|
|
109
|
+
unless single_primary_key?(call_node)
|
|
110
|
+
return SchemaTable::Column.new(name: "id", type: UNKNOWN_PRIMARY_KEY_TYPE, ruby_type: "Object")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
SchemaTable::Column.new(name: "id", **primary_key_shape(call_node))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# `primary_key:` names one key column when it is a String or Symbol literal; `nil` or `false` falls
|
|
117
|
+
# back to the default `id` column the way `primary_key || ...` does in Rails.
|
|
118
|
+
def single_primary_key?(call_node)
|
|
119
|
+
primary_key = keyword_value(call_node, :primary_key)
|
|
120
|
+
primary_key.nil? || primary_key.is_a?(Prism::NilNode) || primary_key.is_a?(Prism::FalseNode) ||
|
|
121
|
+
!literal_name(primary_key).nil?
|
|
122
|
+
end
|
|
123
|
+
|
|
95
124
|
def id_disabled?(call_node)
|
|
96
|
-
|
|
125
|
+
id = keyword_value(call_node, :id)
|
|
126
|
+
(id.is_a?(Prism::FalseNode) || id.is_a?(Prism::NilNode)) && keywords_read?(call_keywords(call_node))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# `create_table "settings", primary_key: "name", id: { type: :string }` creates the column `name`
|
|
130
|
+
# without the block declaring it, so the option is the only place the schema names it.
|
|
131
|
+
def named_primary_key_column(call_node)
|
|
132
|
+
return nil if id_disabled?(call_node)
|
|
133
|
+
|
|
134
|
+
name = literal_name(keyword_value(call_node, :primary_key))
|
|
135
|
+
return nil if name.nil?
|
|
136
|
+
|
|
137
|
+
SchemaTable::Column.new(name: name, **primary_key_shape(call_node))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The `id:` option describes the primary key column's type whether the key column is `id` itself or
|
|
141
|
+
# the column `primary_key:` names. An unreadable call still yields a shape — the column stays
|
|
142
|
+
# queryable, just untyped.
|
|
143
|
+
def primary_key_shape(call_node)
|
|
144
|
+
id = keyword_value(call_node, :id)
|
|
145
|
+
options = id.is_a?(Prism::HashNode) ? id.elements : []
|
|
146
|
+
array = keyword_in(options, :array).is_a?(Prism::TrueNode)
|
|
147
|
+
unless primary_key_type_stated?(call_node, options)
|
|
148
|
+
return { type: UNKNOWN_PRIMARY_KEY_TYPE, ruby_type: "Object", array: array }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
type = primary_key_type(id, options)
|
|
152
|
+
{ type: type, ruby_type: primary_key_ruby_type(type), array: array }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# `id: :string` or `id: { type: :string, ... }`. `id: true` asks for the default key, as does an
|
|
156
|
+
# `id:` hash that names no `type:`. A type this parser cannot read as a literal is left unknown
|
|
157
|
+
# rather than assumed to be bigint.
|
|
158
|
+
def primary_key_type(id, options)
|
|
159
|
+
return DEFAULT_PRIMARY_KEY_TYPE if id.is_a?(Prism::TrueNode)
|
|
160
|
+
|
|
161
|
+
declared = id.is_a?(Prism::HashNode) ? keyword_in(options, :type) : id
|
|
162
|
+
return DEFAULT_PRIMARY_KEY_TYPE if declared.nil?
|
|
163
|
+
|
|
164
|
+
literal_name(declared)&.to_sym || UNKNOWN_PRIMARY_KEY_TYPE
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The key is typed only from a call this parser reads completely. `as:` is excluded because the query
|
|
168
|
+
# may define the column, and a non-literal `array:` because it decides the reader's shape.
|
|
169
|
+
def primary_key_type_stated?(call_node, options)
|
|
170
|
+
array = keyword_in(options, :array)
|
|
171
|
+
keywords_read?(call_keywords(call_node)) && keywords_read?(options) &&
|
|
172
|
+
keyword_value(call_node, :as).nil? &&
|
|
173
|
+
(array.nil? || array.is_a?(Prism::TrueNode) || array.is_a?(Prism::FalseNode))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def call_keywords(call_node)
|
|
177
|
+
call_node.arguments.arguments.grep(Prism::KeywordHashNode).flat_map(&:elements)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Every element has a static Symbol key and no key repeats, so `keyword_in` sees what Ruby would pass.
|
|
181
|
+
def keywords_read?(elements)
|
|
182
|
+
keys = elements.map { |element| symbol_key(element.key) if element.is_a?(Prism::AssocNode) }
|
|
183
|
+
keys.none?(&:nil?) && keys.uniq.size == keys.size
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# A decimal key stays untyped, because its class depends on the scale: `Type::DecimalWithoutScale`
|
|
187
|
+
# at scale 0, `Type::Decimal` otherwise.
|
|
188
|
+
def primary_key_ruby_type(type)
|
|
189
|
+
type == :decimal ? "Object" : SchemaTable.ruby_type_for(type)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# A `decimal` column's class follows its literal `scale:`. Any other type takes its SchemaTable mapping.
|
|
193
|
+
# A call this parser does not read completely may still hold a different scale, so it reads untyped.
|
|
194
|
+
def column_ruby_type(type, call_node)
|
|
195
|
+
return SchemaTable.ruby_type_for(type) unless type == :decimal
|
|
196
|
+
return "Object" unless keywords_read?(call_keywords(call_node))
|
|
197
|
+
|
|
198
|
+
scale = keyword_value(call_node, :scale)
|
|
199
|
+
SchemaTable.decimal_ruby_type(scale.is_a?(Prism::IntegerNode) ? scale.value : nil)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def literal_name(node)
|
|
203
|
+
case node
|
|
204
|
+
when Prism::StringNode, Prism::SymbolNode then node.unescaped
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def keyword_value(call_node, name)
|
|
209
|
+
return nil if call_node.arguments.nil?
|
|
97
210
|
|
|
98
211
|
call_node.arguments.arguments.each do |arg|
|
|
99
212
|
next unless arg.is_a?(Prism::KeywordHashNode)
|
|
100
213
|
|
|
101
|
-
arg.elements
|
|
102
|
-
|
|
214
|
+
value = keyword_in(arg.elements, name)
|
|
215
|
+
return value if value
|
|
216
|
+
end
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
103
219
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
220
|
+
def keyword_in(elements, name)
|
|
221
|
+
elements.each do |pair|
|
|
222
|
+
next unless pair.is_a?(Prism::AssocNode)
|
|
223
|
+
|
|
224
|
+
return pair.value if symbol_key(pair.key) == name
|
|
107
225
|
end
|
|
108
|
-
|
|
226
|
+
nil
|
|
109
227
|
end
|
|
110
228
|
|
|
111
|
-
# Walks the block body collecting `t.<method>(...)` calls.
|
|
112
|
-
# `if
|
|
113
|
-
# back silently.
|
|
229
|
+
# Walks the block body collecting `t.<method>(...)` calls. It descends through nested constructs
|
|
230
|
+
# (e.g. `if` branches) but does not recurse into a matched call.
|
|
114
231
|
def collect_column_calls(node, &)
|
|
115
232
|
return if node.nil?
|
|
116
233
|
|
|
@@ -148,7 +265,7 @@ module Rigor
|
|
|
148
265
|
SchemaTable::Column.new(
|
|
149
266
|
name: name,
|
|
150
267
|
type: type,
|
|
151
|
-
ruby_type:
|
|
268
|
+
ruby_type: column_ruby_type(type, call_node),
|
|
152
269
|
array: keyword_true?(call_node, :array)
|
|
153
270
|
)
|
|
154
271
|
end
|
|
@@ -177,19 +294,7 @@ module Rigor
|
|
|
177
294
|
# modifiers like `t.bigint "status_ids", array: true` (Postgres array column) and `t.references
|
|
178
295
|
# "x", polymorphic: true`.
|
|
179
296
|
def keyword_true?(call_node, name)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
call_node.arguments.arguments.each do |arg|
|
|
183
|
-
next unless arg.is_a?(Prism::KeywordHashNode)
|
|
184
|
-
|
|
185
|
-
arg.elements.each do |pair|
|
|
186
|
-
next unless pair.is_a?(Prism::AssocNode)
|
|
187
|
-
next unless symbol_key(pair.key) == name
|
|
188
|
-
|
|
189
|
-
return pair.value.is_a?(Prism::TrueNode)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
false
|
|
297
|
+
keyword_value(call_node, name).is_a?(Prism::TrueNode)
|
|
193
298
|
end
|
|
194
299
|
|
|
195
300
|
# `t.column "name", "type"` / `t.column "name", :type` — the explicit generic column form. The
|
|
@@ -203,7 +308,7 @@ module Rigor
|
|
|
203
308
|
SchemaTable::Column.new(
|
|
204
309
|
name: name,
|
|
205
310
|
type: type_sym,
|
|
206
|
-
ruby_type:
|
|
311
|
+
ruby_type: column_ruby_type(type_sym, call_node),
|
|
207
312
|
array: keyword_true?(call_node, :array)
|
|
208
313
|
)
|
|
209
314
|
end
|
|
@@ -217,13 +322,7 @@ module Rigor
|
|
|
217
322
|
def string_argument(call_node, index)
|
|
218
323
|
return nil if call_node.arguments.nil?
|
|
219
324
|
|
|
220
|
-
|
|
221
|
-
return nil if arg.nil?
|
|
222
|
-
|
|
223
|
-
case arg
|
|
224
|
-
when Prism::StringNode then arg.unescaped
|
|
225
|
-
when Prism::SymbolNode then arg.unescaped
|
|
226
|
-
end
|
|
325
|
+
literal_name(call_node.arguments.arguments[index])
|
|
227
326
|
end
|
|
228
327
|
|
|
229
328
|
def symbol_key(node)
|