rigortype 0.2.7 → 0.2.9
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/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/08-understanding-errors.md +3 -3
- data/docs/manual/01-installation.md +1 -0
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +27 -4
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- 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 +248 -186
- 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 +62 -48
- 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 +46 -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_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +34 -18
- 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/missing_gem_constant_index.rb +128 -0
- 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 +225 -304
- 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 +794 -1154
- 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/fork_map.rb +87 -0
- 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 +627 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
- 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 -821
- data/lib/rigor/inference/statement_evaluator.rb +754 -1114
- 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 +141 -184
- 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 +92 -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 +40 -48
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
- 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/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
- 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-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- 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/grape_api_discoverer.rb +189 -0
- 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 +61 -75
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
- 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/environment.rbs +1 -0
- data/sig/rigor/scope.rbs +6 -0
- metadata +9 -1
|
@@ -5,9 +5,8 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Plugin
|
|
7
7
|
class Activerecord < Rigor::Plugin::Base
|
|
8
|
-
# Per-file AST walker. For each `Model.find(...)` /
|
|
9
|
-
# `Model
|
|
10
|
-
# `Model` is a name in the {ModelIndex}, emits diagnostics:
|
|
8
|
+
# Per-file AST walker. For each `Model.find(...)` / `Model.find_by(...)` / `Model.where(...)` call
|
|
9
|
+
# where `Model` is a name in the {ModelIndex}, emits diagnostics:
|
|
11
10
|
#
|
|
12
11
|
# | Method | Recognised arg shape | Validation |
|
|
13
12
|
# | --- | --- | --- |
|
|
@@ -16,17 +15,13 @@ module Rigor
|
|
|
16
15
|
# | `Model.where(col: v, ...)` | keyword args | each key must be a column |
|
|
17
16
|
# | `Model.where(string)` | String literal | parser-side; not validated |
|
|
18
17
|
#
|
|
19
|
-
# Successful matches surface as `:info` diagnostics naming
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# calls with non-keyword arguments to `where` / `find_by`,
|
|
23
|
-
# stay silent.
|
|
18
|
+
# Successful matches surface as `:info` diagnostics naming the resolved table; unknown columns
|
|
19
|
+
# surface as `:error`. Calls whose receiver is not a model in the index, and calls with
|
|
20
|
+
# non-keyword arguments to `where` / `find_by`, stay silent.
|
|
24
21
|
class Analyzer
|
|
25
|
-
# Methods that take a column → value Hash and need each key
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# `find_or_create_by!` raises on a validation failure) and
|
|
29
|
-
# `create_or_find_by` / `create_or_find_by!` take the
|
|
22
|
+
# Methods that take a column → value Hash and need each key validated against the receiver's
|
|
23
|
+
# column set. The bang variants (`find_by!` raises instead of returning nil; `find_or_create_by!`
|
|
24
|
+
# raises on a validation failure) and `create_or_find_by` / `create_or_find_by!` take the
|
|
30
25
|
# identical column-hash argument shape.
|
|
31
26
|
COLUMN_HASH_METHODS = %i[
|
|
32
27
|
where
|
|
@@ -88,17 +83,14 @@ module Rigor
|
|
|
88
83
|
keyword_pairs = keyword_argument_pairs(node)
|
|
89
84
|
return push_recognised(node, entry) if keyword_pairs.empty?
|
|
90
85
|
|
|
91
|
-
# Models with no schema-side columns are virtual — backed
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# check query keys; surface the call as recognised and
|
|
97
|
-
# skip column validation entirely rather than firing a
|
|
98
|
-
# false `unknown-column` against every key.
|
|
86
|
+
# Models with no schema-side columns are virtual — backed by a database VIEW (Mastodon's
|
|
87
|
+
# `Instance` model wraps a SQL view that isn't in `db/schema.rb`), seeded from an external
|
|
88
|
+
# source, or otherwise opaque to our schema parser. Without a column set we cannot meaningfully
|
|
89
|
+
# check query keys; surface the call as recognised and skip column validation entirely rather
|
|
90
|
+
# than firing a false `unknown-column` against every key.
|
|
99
91
|
return push_recognised(node, entry, keyword_pairs.map { |p| p[:key] }) if entry.column_names.empty?
|
|
100
92
|
|
|
101
|
-
unknown = keyword_pairs.reject { |pair| valid_query_key?(entry, pair[:key]) }
|
|
93
|
+
unknown = keyword_pairs.reject { |pair| nested_condition?(pair[:value]) || valid_query_key?(entry, pair[:key]) }
|
|
102
94
|
if unknown.empty?
|
|
103
95
|
keyword_pairs.each { |pair| validate_enum_value(node, entry, pair) }
|
|
104
96
|
push_recognised(node, entry, keyword_pairs.map { |p| p[:key] })
|
|
@@ -114,23 +106,20 @@ module Rigor
|
|
|
114
106
|
end
|
|
115
107
|
end
|
|
116
108
|
|
|
117
|
-
# ActiveRecord's `where` / `find_by` / `find_or_initialize_by`
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# association's FK column behind the scenes:
|
|
109
|
+
# ActiveRecord's `where` / `find_by` / `find_or_initialize_by` accept either a column name *or* a
|
|
110
|
+
# singular association name (belongs_to / has_one). The latter resolves to the association's FK
|
|
111
|
+
# column behind the scenes:
|
|
121
112
|
#
|
|
122
113
|
# AccountPin.find_by(account: x)
|
|
123
114
|
# # → SELECT … WHERE account_id = x.id
|
|
124
115
|
#
|
|
125
|
-
# Without this allowance, every `find_by(<assoc>:)` call
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
# which were the canonical Rails idiom rather than typos.
|
|
116
|
+
# Without this allowance, every `find_by(<assoc>:)` call surfaces as an `unknown-column` false
|
|
117
|
+
# positive — Mastodon saw ~100 such hits across the API controllers, all of which were the
|
|
118
|
+
# canonical Rails idiom rather than typos.
|
|
129
119
|
#
|
|
130
|
-
# `alias_attribute` names resolve to their target before
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
# an `unknown-column`.
|
|
120
|
+
# `alias_attribute` names resolve to their target before the column / association check —
|
|
121
|
+
# querying by an aliased attribute is a valid Rails idiom and must not surface as an
|
|
122
|
+
# `unknown-column`.
|
|
134
123
|
def valid_query_key?(entry, key)
|
|
135
124
|
key = entry.resolve_alias(key) if entry.alias?(key)
|
|
136
125
|
|
|
@@ -140,13 +129,19 @@ module Rigor
|
|
|
140
129
|
assoc && assoc[:kind] == :singular
|
|
141
130
|
end
|
|
142
131
|
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# (
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
132
|
+
# A keyword pair with a Hash value is a nested / joined-table condition (`Approval.where(approvals:
|
|
133
|
+
# { user_id: 1 })`, `where(users: { name: 'x' })`), where the key names an association or a joined
|
|
134
|
+
# table, not a column on the receiver. Rails resolves it through the join, so it must not be checked
|
|
135
|
+
# against the receiver's own columns. (An Array value — `where(status: [:a, :b])` — is an IN query on
|
|
136
|
+
# a real column, not nested, so only a Hash is skipped.)
|
|
137
|
+
def nested_condition?(value_node)
|
|
138
|
+
value_node.is_a?(Prism::HashNode) || value_node.is_a?(Prism::KeywordHashNode)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# When the column is an enum-bearing column AND the passed value is a Symbol literal, the value
|
|
142
|
+
# MUST be one of the declared enum values. Non-Symbol values (variables, expressions) decline so
|
|
143
|
+
# dynamic call sites stay silent. Sequences (`status: [:active, :archived]`) are intentionally NOT
|
|
144
|
+
# walked here — the relation-shape check belongs in a future track.
|
|
150
145
|
def validate_enum_value(node, entry, pair)
|
|
151
146
|
return unless entry.enum?(pair[:key])
|
|
152
147
|
return unless pair[:value].is_a?(Prism::SymbolNode)
|
|
@@ -199,13 +194,10 @@ module Rigor
|
|
|
199
194
|
node.arguments.arguments.size
|
|
200
195
|
end
|
|
201
196
|
|
|
202
|
-
# Returns the symbol-keyed pairs of any KeywordHashNode
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
# idiomatic one. The `value` Prism node is preserved so
|
|
207
|
-
# the enum check downstream can recognise Symbol
|
|
208
|
-
# literals without re-walking the call.
|
|
197
|
+
# Returns the symbol-keyed pairs of any KeywordHashNode argument as `{ key:, value: }` rows. Plain
|
|
198
|
+
# hash-literal arguments (`Model.where({a: 1})`) are NOT walked — Rails accepts both, but the
|
|
199
|
+
# keyword form is the idiomatic one. The `value` Prism node is preserved so the enum check
|
|
200
|
+
# downstream can recognise Symbol literals without re-walking the call.
|
|
209
201
|
def keyword_argument_pairs(node)
|
|
210
202
|
return [] if node.arguments.nil?
|
|
211
203
|
|
|
@@ -6,20 +6,15 @@ require "rigor/source/literals"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class Activerecord < Rigor::Plugin::Base
|
|
9
|
-
# Walks the configured model search paths via the plugin's
|
|
10
|
-
#
|
|
11
|
-
# collects class declarations that resolve to ActiveRecord
|
|
12
|
-
# models.
|
|
9
|
+
# Walks the configured model search paths via the plugin's `IoBoundary`, parses each `.rb` file with
|
|
10
|
+
# Prism, and collects class declarations that resolve to ActiveRecord models.
|
|
13
11
|
#
|
|
14
|
-
# Discovery is a two-step pass. First every class declaration
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# subclasses (`class Admin < User`) discoverable. Each STI
|
|
21
|
-
# child carries an `sti_parent:` pointer the {ModelIndex}
|
|
22
|
-
# uses to inherit the root model's table and DSL surface.
|
|
12
|
+
# Discovery is a two-step pass. First every class declaration is captured as a *candidate* (its
|
|
13
|
+
# name, its superclass name, and its DSL metadata). Then a fixpoint marks a candidate as a model
|
|
14
|
+
# when its superclass is a configured base class OR (transitively) the class name of another model
|
|
15
|
+
# — this is what makes single-table-inheritance subclasses (`class Admin < User`) discoverable.
|
|
16
|
+
# Each STI child carries an `sti_parent:` pointer the {ModelIndex} uses to inherit the root model's
|
|
17
|
+
# table and DSL surface.
|
|
23
18
|
#
|
|
24
19
|
# Returns rows the {ModelIndex} consumes:
|
|
25
20
|
#
|
|
@@ -28,27 +23,35 @@ module Rigor
|
|
|
28
23
|
#
|
|
29
24
|
# Limitations (intentional for v0.1.0 of the plugin):
|
|
30
25
|
#
|
|
31
|
-
# - `self.table_name = "..."` recognised only when the RHS
|
|
32
|
-
# is a String literal. Computed names
|
|
26
|
+
# - `self.table_name = "..."` recognised only when the RHS is a String literal. Computed names
|
|
33
27
|
# (`self.table_name = "#{tenant}_users"`) are skipped.
|
|
34
|
-
# - Modules (`class Admin::User < ApplicationRecord`) are
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# class names tolerating a leading `::`; richer constant
|
|
39
|
-
# resolution (relative namespacing) is not modelled.
|
|
28
|
+
# - Modules (`class Admin::User < ApplicationRecord`) are recognised; the resulting class name is
|
|
29
|
+
# the lexical path (`Admin::User`).
|
|
30
|
+
# - The STI fixpoint matches a superclass name against model class names tolerating a leading `::`;
|
|
31
|
+
# richer constant resolution (relative namespacing) is not modelled.
|
|
40
32
|
class ModelDiscoverer
|
|
41
33
|
# @param io_boundary [Rigor::Plugin::IoBoundary]
|
|
42
|
-
# @param search_paths [Array<String>] absolute or
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
34
|
+
# @param search_paths [Array<String>] absolute or project-relative paths.
|
|
35
|
+
# @param base_classes [Array<String>] superclass names that identify a class as an AR model.
|
|
36
|
+
# Declaration macros whose column's runtime value is a rich object, not the SQL scalar. Their column
|
|
37
|
+
# must NOT be narrowed to the schema type (see {ModelIndex.build}'s type-override remap).
|
|
38
|
+
TYPE_OVERRIDE_METHODS = %i[serialize mount_uploader mount_uploaders].freeze
|
|
39
|
+
|
|
46
40
|
def initialize(io_boundary:, search_paths:, base_classes:)
|
|
47
41
|
@io_boundary = io_boundary
|
|
48
42
|
@search_paths = search_paths
|
|
49
43
|
@base_classes = base_classes.to_set
|
|
44
|
+
# Global set of column names whose declared runtime type overrides the schema scalar
|
|
45
|
+
# (`serialize` / `mount_uploader` / `attribute :x, CustomType`). Collected across every class AND
|
|
46
|
+
# concern module walked — a serialize inside a concern's `included do … end` is invisible to the
|
|
47
|
+
# per-model view (the discoverer doesn't follow `include`), so overrides are tracked globally by
|
|
48
|
+
# column name and applied wherever that column appears. Over-suppresses a same-named scalar
|
|
49
|
+
# column elsewhere (a precision cost, never a false positive).
|
|
50
|
+
@type_override_columns = Set.new
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
attr_reader :type_override_columns
|
|
54
|
+
|
|
52
55
|
# @return [Array<Hash>] rows of { class_name:, table_name_override:, sti_parent:, ... }
|
|
53
56
|
def discover
|
|
54
57
|
candidates = []
|
|
@@ -64,17 +67,13 @@ module Rigor
|
|
|
64
67
|
|
|
65
68
|
private
|
|
66
69
|
|
|
67
|
-
# Fixpoint over the captured class candidates: a candidate
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# STI subclasses; the matched parent name is stamped onto
|
|
72
|
-
# the row as `sti_parent:` so the {ModelIndex} can inherit
|
|
73
|
-
# the root model's table and association surface.
|
|
70
|
+
# Fixpoint over the captured class candidates: a candidate is a model when its superclass is a
|
|
71
|
+
# configured base class, or — transitively — the class name of an already-known model. The
|
|
72
|
+
# second arm is what discovers STI subclasses; the matched parent name is stamped onto the row as
|
|
73
|
+
# `sti_parent:` so the {ModelIndex} can inherit the root model's table and association surface.
|
|
74
74
|
#
|
|
75
|
-
# Non-model classes (POROs, service objects that happen to
|
|
76
|
-
#
|
|
77
|
-
# are dropped.
|
|
75
|
+
# Non-model classes (POROs, service objects that happen to live under `app/models/`) never enter
|
|
76
|
+
# `model_names` and are dropped.
|
|
78
77
|
def resolve_models(candidates)
|
|
79
78
|
model_names = {}
|
|
80
79
|
sti_parent = {}
|
|
@@ -108,9 +107,8 @@ module Rigor
|
|
|
108
107
|
end
|
|
109
108
|
end
|
|
110
109
|
|
|
111
|
-
# Resolves a superclass NAME against the set of known
|
|
112
|
-
# model class
|
|
113
|
-
# the matched model class name, or nil.
|
|
110
|
+
# Resolves a superclass NAME against the set of known model class names, tolerating a leading
|
|
111
|
+
# `::`. Returns the matched model class name, or nil.
|
|
114
112
|
def model_match(superclass_name, model_names)
|
|
115
113
|
return superclass_name if model_names.key?(superclass_name)
|
|
116
114
|
|
|
@@ -146,11 +144,9 @@ module Rigor
|
|
|
146
144
|
end
|
|
147
145
|
end
|
|
148
146
|
|
|
149
|
-
# Captures EVERY class declaration as a candidate — the
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# non-model class it is simply discarded when the candidate
|
|
153
|
-
# is dropped.
|
|
147
|
+
# Captures EVERY class declaration as a candidate — the `resolve_models` fixpoint decides
|
|
148
|
+
# afterwards which ones are models. The DSL metadata is extracted eagerly; for a non-model class
|
|
149
|
+
# it is simply discarded when the candidate is dropped.
|
|
154
150
|
def visit_class(node, lexical_path, &)
|
|
155
151
|
class_local_name = constant_path_name(node.constant_path)
|
|
156
152
|
return if class_local_name.nil?
|
|
@@ -158,6 +154,8 @@ module Rigor
|
|
|
158
154
|
full_name = (lexical_path + [class_local_name]).join("::")
|
|
159
155
|
superclass = constant_path_name(node.superclass) if node.superclass
|
|
160
156
|
|
|
157
|
+
collect_type_overrides(node.body)
|
|
158
|
+
|
|
161
159
|
yield({
|
|
162
160
|
class_name: full_name,
|
|
163
161
|
superclass_name: superclass,
|
|
@@ -179,12 +177,57 @@ module Rigor
|
|
|
179
177
|
module_local_name = constant_path_name(node.constant_path)
|
|
180
178
|
return if module_local_name.nil?
|
|
181
179
|
|
|
180
|
+
# Concerns (`module DiffPositionableNote`) carry `serialize` / `mount_uploader` inside an
|
|
181
|
+
# `included do … end` block; collect their overrides even though the module itself is not a model.
|
|
182
|
+
collect_type_overrides(node.body)
|
|
183
|
+
|
|
182
184
|
inner_path = lexical_path + [module_local_name]
|
|
183
185
|
walk_for_classes(node.body, inner_path, &) if node.body
|
|
184
186
|
end
|
|
185
187
|
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
+
# Records the column name of every `serialize :col` / `mount_uploader(s) :col` / `attribute :col,
|
|
189
|
+
# CustomType` in `body` (descending into `with_options` and concern `included do` blocks) into the
|
|
190
|
+
# global {#type_override_columns} set. `attribute :col, :symbol_type` (a built-in scalar type) is
|
|
191
|
+
# NOT an override — only a custom type CONSTANT is.
|
|
192
|
+
def collect_type_overrides(body)
|
|
193
|
+
type_override_declaration_calls(body).each do |node|
|
|
194
|
+
next if node.receiver
|
|
195
|
+
|
|
196
|
+
column = Rigor::Source::Literals.symbol_name(node.arguments&.arguments&.first)
|
|
197
|
+
next if column.nil?
|
|
198
|
+
|
|
199
|
+
if TYPE_OVERRIDE_METHODS.include?(node.name)
|
|
200
|
+
@type_override_columns << column
|
|
201
|
+
elsif node.name == :attribute && custom_type_attribute?(node)
|
|
202
|
+
@type_override_columns << column
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# `declaration_calls` variant that also descends into a concern's `included do … end` block (the
|
|
208
|
+
# ActiveSupport::Concern idiom where models' shared `serialize` declarations live).
|
|
209
|
+
def type_override_declaration_calls(body)
|
|
210
|
+
return [] if body.nil?
|
|
211
|
+
|
|
212
|
+
body.compact_child_nodes.flat_map do |node|
|
|
213
|
+
next [] unless node.is_a?(Prism::CallNode)
|
|
214
|
+
|
|
215
|
+
if %i[with_options included].include?(node.name) && node.block.is_a?(Prism::BlockNode)
|
|
216
|
+
type_override_declaration_calls(node.block.body)
|
|
217
|
+
else
|
|
218
|
+
[node]
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# True when an `attribute :col, <type>` call's type argument is a custom type CLASS (a constant),
|
|
224
|
+
# whose runtime value is a rich object — not a built-in `:symbol` type, which stays a scalar.
|
|
225
|
+
def custom_type_attribute?(node)
|
|
226
|
+
type_arg = node.arguments&.arguments&.[](1)
|
|
227
|
+
type_arg.is_a?(Prism::ConstantReadNode) || type_arg.is_a?(Prism::ConstantPathNode)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Renders a constant-path node (`Admin::User`, `::ApplicationRecord`) as a String. Returns nil for
|
|
188
231
|
# shapes the discoverer chooses not to handle.
|
|
189
232
|
def constant_path_name(node)
|
|
190
233
|
return nil if node.nil?
|
|
@@ -208,9 +251,8 @@ module Rigor
|
|
|
208
251
|
end
|
|
209
252
|
end
|
|
210
253
|
|
|
211
|
-
# Looks for `self.table_name = "..."` at the top level of
|
|
212
|
-
#
|
|
213
|
-
# nil otherwise.
|
|
254
|
+
# Looks for `self.table_name = "..."` at the top level of the class body. Returns the literal
|
|
255
|
+
# String when found, nil otherwise.
|
|
214
256
|
def lookup_table_name_override(body)
|
|
215
257
|
return nil if body.nil?
|
|
216
258
|
|
|
@@ -224,19 +266,14 @@ module Rigor
|
|
|
224
266
|
nil
|
|
225
267
|
end
|
|
226
268
|
|
|
227
|
-
# Recognised single-instance and collection association
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
# `Nominal[Target] | nil`, plural ones narrow to
|
|
231
|
-
# `ActiveRecord::Relation[Target]`.
|
|
269
|
+
# Recognised single-instance and collection association DSL methods. The kind drives the eventual
|
|
270
|
+
# return-type contribution: singular associations narrow to `Nominal[Target] | nil`, plural ones
|
|
271
|
+
# narrow to `ActiveRecord::Relation[Target]`.
|
|
232
272
|
#
|
|
233
|
-
# `composed_of` value-object aggregations and
|
|
234
|
-
# `
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
# a false `unknown-column`. `composed_of` resolves to its
|
|
238
|
-
# value class (a real target); `delegated_type` is
|
|
239
|
-
# polymorphic (no single target).
|
|
273
|
+
# `composed_of` value-object aggregations and `delegated_type` roles are folded in here too — both
|
|
274
|
+
# accept the association name as a `where` / `find_by` query key, so omitting them turns every
|
|
275
|
+
# such query into a false `unknown-column`. `composed_of` resolves to its value class (a real
|
|
276
|
+
# target); `delegated_type` is polymorphic (no single target).
|
|
240
277
|
ASSOCIATION_METHODS = {
|
|
241
278
|
belongs_to: :singular,
|
|
242
279
|
has_one: :singular,
|
|
@@ -247,28 +284,21 @@ module Rigor
|
|
|
247
284
|
}.freeze
|
|
248
285
|
private_constant :ASSOCIATION_METHODS
|
|
249
286
|
|
|
250
|
-
# Association DSL methods that are ALWAYS polymorphic —
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
# an explicit `polymorphic: true` option.
|
|
287
|
+
# Association DSL methods that are ALWAYS polymorphic — the accessor has no single static target
|
|
288
|
+
# class. `belongs_to` / `has_one` become polymorphic only with an explicit `polymorphic: true`
|
|
289
|
+
# option.
|
|
254
290
|
POLYMORPHIC_BY_DEFAULT = %i[delegated_type].freeze
|
|
255
291
|
private_constant :POLYMORPHIC_BY_DEFAULT
|
|
256
292
|
|
|
257
|
-
# Class-body declaration calls — the top-level `CallNode`s
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
# association / enum / validation declared inside is
|
|
263
|
-
# invisible to the discoverer, turning `where(<assoc>:
|
|
264
|
-
# ...)` into a false `unknown-column`. Nested
|
|
265
|
-
# `with_options` blocks recurse.
|
|
293
|
+
# Class-body declaration calls — the top-level `CallNode`s PLUS those nested inside a
|
|
294
|
+
# `with_options(...) do … end` block. `with_options` is Rails' idiom for sharing options across a
|
|
295
|
+
# group of `belongs_to` / `validates` / etc. declarations; without descending into it every
|
|
296
|
+
# association / enum / validation declared inside is invisible to the discoverer, turning
|
|
297
|
+
# `where(<assoc>: ...)` into a false `unknown-column`. Nested `with_options` blocks recurse.
|
|
266
298
|
#
|
|
267
|
-
# The options the `with_options` call itself carries (e.g.
|
|
268
|
-
#
|
|
269
|
-
# the
|
|
270
|
-
# what clears the false positive; the merged-option target
|
|
271
|
-
# precision is a separate refinement.
|
|
299
|
+
# The options the `with_options` call itself carries (e.g. `with_options class_name: 'Account'`)
|
|
300
|
+
# are NOT merged into the nested calls — discovering the declaration name is what clears the
|
|
301
|
+
# false positive; the merged-option target precision is a separate refinement.
|
|
272
302
|
def declaration_calls(body)
|
|
273
303
|
return [] if body.nil?
|
|
274
304
|
|
|
@@ -283,17 +313,13 @@ module Rigor
|
|
|
283
313
|
end
|
|
284
314
|
end
|
|
285
315
|
|
|
286
|
-
# Walks the class body for association DSL calls and
|
|
287
|
-
# returns a list of rows shaped:
|
|
316
|
+
# Walks the class body for association DSL calls and returns a list of rows shaped:
|
|
288
317
|
#
|
|
289
318
|
# { name: "user", kind: :singular, target: "User" }
|
|
290
319
|
#
|
|
291
|
-
# The `target` is resolved from an explicit
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
# {Inflector.classify}. Calls whose first arg is not a
|
|
295
|
-
# Symbol literal (or whose `class_name:` is a non-literal
|
|
296
|
-
# expression) decline rather than guess.
|
|
320
|
+
# The `target` is resolved from an explicit `class_name: "Foo"` option when supplied, otherwise
|
|
321
|
+
# inferred from the association name via {Inflector.classify}. Calls whose first arg is not a
|
|
322
|
+
# Symbol literal (or whose `class_name:` is a non-literal expression) decline rather than guess.
|
|
297
323
|
def lookup_associations(body)
|
|
298
324
|
return [] if body.nil?
|
|
299
325
|
|
|
@@ -319,10 +345,8 @@ module Rigor
|
|
|
319
345
|
polymorphic = POLYMORPHIC_BY_DEFAULT.include?(node.name) ||
|
|
320
346
|
association_option(args, "polymorphic") == true
|
|
321
347
|
|
|
322
|
-
# A polymorphic association has no single static target
|
|
323
|
-
#
|
|
324
|
-
# declines to narrow rather than inventing a wrong
|
|
325
|
-
# `Nominal[<classified-name>]`.
|
|
348
|
+
# A polymorphic association has no single static target class — `target` is nil and the flow
|
|
349
|
+
# contribution declines to narrow rather than inventing a wrong `Nominal[<classified-name>]`.
|
|
326
350
|
if polymorphic
|
|
327
351
|
target = nil
|
|
328
352
|
else
|
|
@@ -334,15 +358,12 @@ module Rigor
|
|
|
334
358
|
nullable: association_nullable?(node.name, args) }
|
|
335
359
|
end
|
|
336
360
|
|
|
337
|
-
# Whether a `:singular` association's accessor can return
|
|
338
|
-
# `nil
|
|
339
|
-
# `
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
# unless `allow_nil: true`. `delegated_type` roles are
|
|
344
|
-
# required. A non-literal option value declines to the
|
|
345
|
-
# default rather than guessing.
|
|
361
|
+
# Whether a `:singular` association's accessor can return `nil`. `has_one` genuinely can (no
|
|
362
|
+
# associated record → `nil`). `belongs_to` is **required (non-`nil`) by default since Rails 5**
|
|
363
|
+
# (`belongs_to_required_by_default`); it becomes nullable only when the call passes
|
|
364
|
+
# `optional: true` or `required: false`. `composed_of` is non-nullable unless `allow_nil: true`.
|
|
365
|
+
# `delegated_type` roles are required. A non-literal option value declines to the default rather
|
|
366
|
+
# than guessing.
|
|
346
367
|
def association_nullable?(method_name, args)
|
|
347
368
|
case method_name
|
|
348
369
|
when :has_one
|
|
@@ -357,9 +378,8 @@ module Rigor
|
|
|
357
378
|
end
|
|
358
379
|
end
|
|
359
380
|
|
|
360
|
-
# Reads a literal boolean association option (`optional:` /
|
|
361
|
-
#
|
|
362
|
-
# `nil` when the key is absent or its value is non-literal.
|
|
381
|
+
# Reads a literal boolean association option (`optional:` / `required:`). Returns `true` / `false`
|
|
382
|
+
# for a literal, or `nil` when the key is absent or its value is non-literal.
|
|
363
383
|
def association_option(args, key)
|
|
364
384
|
args.each do |arg|
|
|
365
385
|
next unless arg.is_a?(Prism::KeywordHashNode)
|
|
@@ -388,10 +408,9 @@ module Rigor
|
|
|
388
408
|
nil
|
|
389
409
|
end
|
|
390
410
|
|
|
391
|
-
# `enum status: { active: 0, archived: 1 }` (Rails ≤6)
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
# Non-literal forms decline rather than guess.
|
|
411
|
+
# `enum status: { active: 0, archived: 1 }` (Rails ≤6) and `enum :status, [:active, :archived]`
|
|
412
|
+
# (Rails 7+). Returns `Hash<column_name => Array<Symbol>>`. Non-literal forms decline rather than
|
|
413
|
+
# guess.
|
|
395
414
|
def lookup_enums(body)
|
|
396
415
|
return {} if body.nil?
|
|
397
416
|
|
|
@@ -445,10 +464,9 @@ module Rigor
|
|
|
445
464
|
end
|
|
446
465
|
end
|
|
447
466
|
|
|
448
|
-
# `scope :active, -> { ... }`. Records the scope name
|
|
449
|
-
#
|
|
450
|
-
#
|
|
451
|
-
# based on the name alone via `class_scope_return_type`).
|
|
467
|
+
# `scope :active, -> { ... }`. Records the scope name only (the body is intentionally NOT
|
|
468
|
+
# introspected — the caller contributes `ActiveRecord::Relation[Model]` based on the name alone
|
|
469
|
+
# via `class_scope_return_type`).
|
|
452
470
|
def lookup_scopes(body)
|
|
453
471
|
return [] if body.nil?
|
|
454
472
|
|
|
@@ -468,11 +486,9 @@ module Rigor
|
|
|
468
486
|
scopes.freeze
|
|
469
487
|
end
|
|
470
488
|
|
|
471
|
-
# `validates :name, presence: true, length: { maximum: 100 }`.
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
# `validates :unknown_attr` surfacing when the attribute
|
|
475
|
-
# isn't a column on the table).
|
|
489
|
+
# `validates :name, presence: true, length: { maximum: 100 }`. Records the attribute name (the
|
|
490
|
+
# validator option set is ignored — the value here is the diagnostic `validates :unknown_attr`
|
|
491
|
+
# surfacing when the attribute isn't a column on the table).
|
|
476
492
|
def lookup_validations(body)
|
|
477
493
|
return [] if body.nil?
|
|
478
494
|
|
|
@@ -487,10 +503,9 @@ module Rigor
|
|
|
487
503
|
attrs.uniq.freeze
|
|
488
504
|
end
|
|
489
505
|
|
|
490
|
-
# `before_save :foo`, `after_create :bar`, etc. Records
|
|
491
|
-
#
|
|
492
|
-
#
|
|
493
|
-
# Block callbacks (`before_save { ... }`) decline.
|
|
506
|
+
# `before_save :foo`, `after_create :bar`, etc. Records the referenced method name (a Symbol
|
|
507
|
+
# literal). The diagnostic value is "did you forget to `def` this?". Block callbacks
|
|
508
|
+
# (`before_save { ... }`) decline.
|
|
494
509
|
CALLBACK_METHODS = %i[
|
|
495
510
|
before_validation after_validation
|
|
496
511
|
before_save after_save around_save
|
|
@@ -517,12 +532,10 @@ module Rigor
|
|
|
517
532
|
targets.freeze
|
|
518
533
|
end
|
|
519
534
|
|
|
520
|
-
# `alias_attribute :new_name, :old_name`. Records the
|
|
521
|
-
#
|
|
522
|
-
#
|
|
523
|
-
#
|
|
524
|
-
# `unknown-column`. Returns `Hash<alias => target>`;
|
|
525
|
-
# non-Symbol-literal forms decline rather than guess.
|
|
535
|
+
# `alias_attribute :new_name, :old_name`. Records the mapping so the analyzer accepts the alias
|
|
536
|
+
# as a query key — without it every `where(<alias>: ...)` / `find_by(<alias>: ...)` call surfaces
|
|
537
|
+
# as a false `unknown-column`. Returns `Hash<alias => target>`; non-Symbol-literal forms decline
|
|
538
|
+
# rather than guess.
|
|
526
539
|
def lookup_aliases(body)
|
|
527
540
|
return {} if body.nil?
|
|
528
541
|
|
|
@@ -543,10 +556,8 @@ module Rigor
|
|
|
543
556
|
aliases.freeze
|
|
544
557
|
end
|
|
545
558
|
|
|
546
|
-
# Collects every Symbol-literal positional argument
|
|
547
|
-
#
|
|
548
|
-
# and `lookup_callbacks` to extract the attribute /
|
|
549
|
-
# method name list.
|
|
559
|
+
# Collects every Symbol-literal positional argument from a CallNode. Used by both
|
|
560
|
+
# `lookup_validations` and `lookup_callbacks` to extract the attribute / method name list.
|
|
550
561
|
def symbol_args(node)
|
|
551
562
|
args = node.arguments&.arguments
|
|
552
563
|
return [] if args.nil?
|