rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -3,22 +3,17 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Activejob < Rigor::Plugin::Base
|
|
6
|
-
# Frozen catalogue of discovered ActiveJob subclasses
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# validate `Job.perform_later(...)` call sites.
|
|
6
|
+
# Frozen catalogue of discovered ActiveJob subclasses keyed by qualified class name. Each entry
|
|
7
|
+
# holds the `#perform` method's arity envelope so the analyzer can validate
|
|
8
|
+
# `Job.perform_later(...)` call sites.
|
|
10
9
|
#
|
|
11
|
-
# `min_arity` / `max_arity` form a closed range
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# keyword
|
|
15
|
-
# but they're rare in user code, so the analyzer
|
|
16
|
-
# validates positional arity only (keyword arity
|
|
17
|
-
# validation is deferred).
|
|
10
|
+
# `min_arity` / `max_arity` form a closed range (`Float::INFINITY` for the upper bound when
|
|
11
|
+
# `*args` is present). `keyword_required` lists any required keyword arguments — Active Job
|
|
12
|
+
# supports keyword args but they're rare in user code, so the analyzer validates positional arity
|
|
13
|
+
# only (keyword arity validation is deferred).
|
|
18
14
|
class JobIndex
|
|
19
15
|
Entry = Data.define(:class_name, :min_arity, :max_arity, :keyword_required) do
|
|
20
|
-
# Flexible-friendly textual form of the arity for
|
|
21
|
-
# error messages: `1`, `1..2`, `2+`.
|
|
16
|
+
# Flexible-friendly textual form of the arity for error messages: `1`, `1..2`, `2+`.
|
|
22
17
|
def arity_label
|
|
23
18
|
return "#{min_arity}+" if max_arity == Float::INFINITY
|
|
24
19
|
return min_arity.to_s if min_arity == max_arity
|
|
@@ -8,14 +8,12 @@ require_relative "activejob/analyzer"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
|
-
# rigor-activejob — validates `Job.perform_later(...)` /
|
|
12
|
-
#
|
|
13
|
-
# against the discovered `#perform` definitions.
|
|
11
|
+
# rigor-activejob — validates `Job.perform_later(...)` / `.perform_now(...)` / `.perform(...)`
|
|
12
|
+
# argument arity against the discovered `#perform` definitions.
|
|
14
13
|
#
|
|
15
14
|
# Tier 1D of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
16
|
-
# Statically discovers ActiveJob subclasses by walking
|
|
17
|
-
#
|
|
18
|
-
# file with Prism — no `active_job` runtime dependency.
|
|
15
|
+
# Statically discovers ActiveJob subclasses by walking the configured `job_search_paths` and
|
|
16
|
+
# parsing each file with Prism — no `active_job` runtime dependency.
|
|
19
17
|
#
|
|
20
18
|
# ## Configuration
|
|
21
19
|
#
|
|
@@ -27,16 +25,12 @@ module Rigor
|
|
|
27
25
|
#
|
|
28
26
|
# ## Limitations (v0.1.0)
|
|
29
27
|
#
|
|
30
|
-
# - Direct-superclass match only. `class WelcomeJob <
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# -
|
|
35
|
-
#
|
|
36
|
-
# are out of scope.
|
|
37
|
-
# - Required keyword arguments are recognised but not
|
|
38
|
-
# validated at the call site (positional arity only
|
|
39
|
-
# for v0.1.0).
|
|
28
|
+
# - Direct-superclass match only. `class WelcomeJob < BaseJob` where `BaseJob < ApplicationJob` is
|
|
29
|
+
# NOT discovered. Add `BaseJob` to `job_base_classes` if needed.
|
|
30
|
+
# - The `#perform` arity is read from the syntactic parameter list. Methods built via
|
|
31
|
+
# `define_method` are out of scope.
|
|
32
|
+
# - Required keyword arguments are recognised but not validated at the call site (positional arity
|
|
33
|
+
# only for v0.1.0).
|
|
40
34
|
class Activejob < Rigor::Plugin::Base
|
|
41
35
|
manifest(
|
|
42
36
|
id: "activejob",
|
|
@@ -48,11 +42,10 @@ module Rigor
|
|
|
48
42
|
}
|
|
49
43
|
)
|
|
50
44
|
|
|
51
|
-
# Cached: discovered job index. `watch:` (ADR-60 WD3) covers
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# validate dependency descriptor after the block runs.
|
|
45
|
+
# Cached: discovered job index. `watch:` (ADR-60 WD3) covers every `.rb` under `job_search_paths`
|
|
46
|
+
# so the cache invalidates when a job is added, removed, or edited; the discoverer's in-block
|
|
47
|
+
# `IoBoundary` reads are captured into the record-and-validate dependency descriptor after the
|
|
48
|
+
# block runs.
|
|
56
49
|
producer :job_index, watch: -> { [[@job_search_paths, "**/*.rb"]] } do |_params|
|
|
57
50
|
JobDiscoverer.new(
|
|
58
51
|
io_boundary: io_boundary,
|
|
@@ -66,9 +59,8 @@ module Rigor
|
|
|
66
59
|
@job_base_classes = Array(config.fetch("job_base_classes")).map(&:to_s)
|
|
67
60
|
end
|
|
68
61
|
|
|
69
|
-
# File-level only: the load-error emission. Per-call arity
|
|
70
|
-
#
|
|
71
|
-
# below (ADR-37). The job index is lazily loaded + memoised by
|
|
62
|
+
# File-level only: the load-error emission. Per-call arity validation runs over the engine-owned
|
|
63
|
+
# walk via the node_rule below (ADR-37). The job index is lazily loaded + memoised by
|
|
72
64
|
# `producer_value`, shared by both surfaces.
|
|
73
65
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
74
66
|
index = producer_value(:job_index)
|
|
@@ -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,14 +83,11 @@ 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
93
|
unknown = keyword_pairs.reject { |pair| valid_query_key?(entry, pair[: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,10 @@ module Rigor
|
|
|
140
129
|
assoc && assoc[:kind] == :singular
|
|
141
130
|
end
|
|
142
131
|
|
|
143
|
-
# When the column is an enum-bearing column AND the
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
# sites stay silent. Sequences (`status: [:active,
|
|
148
|
-
# :archived]`) are intentionally NOT walked here —
|
|
149
|
-
# the relation-shape check belongs in a future track.
|
|
132
|
+
# When the column is an enum-bearing column AND the passed value is a Symbol literal, the value
|
|
133
|
+
# MUST be one of the declared enum values. Non-Symbol values (variables, expressions) decline so
|
|
134
|
+
# dynamic call sites stay silent. Sequences (`status: [:active, :archived]`) are intentionally NOT
|
|
135
|
+
# walked here — the relation-shape check belongs in a future track.
|
|
150
136
|
def validate_enum_value(node, entry, pair)
|
|
151
137
|
return unless entry.enum?(pair[:key])
|
|
152
138
|
return unless pair[:value].is_a?(Prism::SymbolNode)
|
|
@@ -199,13 +185,10 @@ module Rigor
|
|
|
199
185
|
node.arguments.arguments.size
|
|
200
186
|
end
|
|
201
187
|
|
|
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.
|
|
188
|
+
# Returns the symbol-keyed pairs of any KeywordHashNode argument as `{ key:, value: }` rows. Plain
|
|
189
|
+
# hash-literal arguments (`Model.where({a: 1})`) are NOT walked — Rails accepts both, but the
|
|
190
|
+
# keyword form is the idiomatic one. The `value` Prism node is preserved so the enum check
|
|
191
|
+
# downstream can recognise Symbol literals without re-walking the call.
|
|
209
192
|
def keyword_argument_pairs(node)
|
|
210
193
|
return [] if node.arguments.nil?
|
|
211
194
|
|
|
@@ -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,21 +23,16 @@ 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
|
-
# @param base_classes [Array<String>] superclass names that
|
|
45
|
-
# identify a class as an AR model.
|
|
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.
|
|
46
36
|
def initialize(io_boundary:, search_paths:, base_classes:)
|
|
47
37
|
@io_boundary = io_boundary
|
|
48
38
|
@search_paths = search_paths
|
|
@@ -64,17 +54,13 @@ module Rigor
|
|
|
64
54
|
|
|
65
55
|
private
|
|
66
56
|
|
|
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.
|
|
57
|
+
# Fixpoint over the captured class candidates: a candidate is a model when its superclass is a
|
|
58
|
+
# configured base class, or — transitively — the class name of an already-known model. The
|
|
59
|
+
# second arm is what discovers STI subclasses; the matched parent name is stamped onto the row as
|
|
60
|
+
# `sti_parent:` so the {ModelIndex} can inherit the root model's table and association surface.
|
|
74
61
|
#
|
|
75
|
-
# Non-model classes (POROs, service objects that happen to
|
|
76
|
-
#
|
|
77
|
-
# are dropped.
|
|
62
|
+
# Non-model classes (POROs, service objects that happen to live under `app/models/`) never enter
|
|
63
|
+
# `model_names` and are dropped.
|
|
78
64
|
def resolve_models(candidates)
|
|
79
65
|
model_names = {}
|
|
80
66
|
sti_parent = {}
|
|
@@ -108,9 +94,8 @@ module Rigor
|
|
|
108
94
|
end
|
|
109
95
|
end
|
|
110
96
|
|
|
111
|
-
# Resolves a superclass NAME against the set of known
|
|
112
|
-
# model class
|
|
113
|
-
# the matched model class name, or nil.
|
|
97
|
+
# Resolves a superclass NAME against the set of known model class names, tolerating a leading
|
|
98
|
+
# `::`. Returns the matched model class name, or nil.
|
|
114
99
|
def model_match(superclass_name, model_names)
|
|
115
100
|
return superclass_name if model_names.key?(superclass_name)
|
|
116
101
|
|
|
@@ -146,11 +131,9 @@ module Rigor
|
|
|
146
131
|
end
|
|
147
132
|
end
|
|
148
133
|
|
|
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.
|
|
134
|
+
# Captures EVERY class declaration as a candidate — the `resolve_models` fixpoint decides
|
|
135
|
+
# afterwards which ones are models. The DSL metadata is extracted eagerly; for a non-model class
|
|
136
|
+
# it is simply discarded when the candidate is dropped.
|
|
154
137
|
def visit_class(node, lexical_path, &)
|
|
155
138
|
class_local_name = constant_path_name(node.constant_path)
|
|
156
139
|
return if class_local_name.nil?
|
|
@@ -183,8 +166,7 @@ module Rigor
|
|
|
183
166
|
walk_for_classes(node.body, inner_path, &) if node.body
|
|
184
167
|
end
|
|
185
168
|
|
|
186
|
-
# Renders a constant-path node (`Admin::User`,
|
|
187
|
-
# `::ApplicationRecord`) as a String. Returns nil for
|
|
169
|
+
# Renders a constant-path node (`Admin::User`, `::ApplicationRecord`) as a String. Returns nil for
|
|
188
170
|
# shapes the discoverer chooses not to handle.
|
|
189
171
|
def constant_path_name(node)
|
|
190
172
|
return nil if node.nil?
|
|
@@ -208,9 +190,8 @@ module Rigor
|
|
|
208
190
|
end
|
|
209
191
|
end
|
|
210
192
|
|
|
211
|
-
# Looks for `self.table_name = "..."` at the top level of
|
|
212
|
-
#
|
|
213
|
-
# nil otherwise.
|
|
193
|
+
# Looks for `self.table_name = "..."` at the top level of the class body. Returns the literal
|
|
194
|
+
# String when found, nil otherwise.
|
|
214
195
|
def lookup_table_name_override(body)
|
|
215
196
|
return nil if body.nil?
|
|
216
197
|
|
|
@@ -224,19 +205,14 @@ module Rigor
|
|
|
224
205
|
nil
|
|
225
206
|
end
|
|
226
207
|
|
|
227
|
-
# Recognised single-instance and collection association
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
# `Nominal[Target] | nil`, plural ones narrow to
|
|
231
|
-
# `ActiveRecord::Relation[Target]`.
|
|
208
|
+
# Recognised single-instance and collection association DSL methods. The kind drives the eventual
|
|
209
|
+
# return-type contribution: singular associations narrow to `Nominal[Target] | nil`, plural ones
|
|
210
|
+
# narrow to `ActiveRecord::Relation[Target]`.
|
|
232
211
|
#
|
|
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).
|
|
212
|
+
# `composed_of` value-object aggregations and `delegated_type` roles are folded in here too — both
|
|
213
|
+
# accept the association name as a `where` / `find_by` query key, so omitting them turns every
|
|
214
|
+
# such query into a false `unknown-column`. `composed_of` resolves to its value class (a real
|
|
215
|
+
# target); `delegated_type` is polymorphic (no single target).
|
|
240
216
|
ASSOCIATION_METHODS = {
|
|
241
217
|
belongs_to: :singular,
|
|
242
218
|
has_one: :singular,
|
|
@@ -247,28 +223,21 @@ module Rigor
|
|
|
247
223
|
}.freeze
|
|
248
224
|
private_constant :ASSOCIATION_METHODS
|
|
249
225
|
|
|
250
|
-
# Association DSL methods that are ALWAYS polymorphic —
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
# an explicit `polymorphic: true` option.
|
|
226
|
+
# Association DSL methods that are ALWAYS polymorphic — the accessor has no single static target
|
|
227
|
+
# class. `belongs_to` / `has_one` become polymorphic only with an explicit `polymorphic: true`
|
|
228
|
+
# option.
|
|
254
229
|
POLYMORPHIC_BY_DEFAULT = %i[delegated_type].freeze
|
|
255
230
|
private_constant :POLYMORPHIC_BY_DEFAULT
|
|
256
231
|
|
|
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.
|
|
232
|
+
# Class-body declaration calls — the top-level `CallNode`s PLUS those nested inside a
|
|
233
|
+
# `with_options(...) do … end` block. `with_options` is Rails' idiom for sharing options across a
|
|
234
|
+
# group of `belongs_to` / `validates` / etc. declarations; without descending into it every
|
|
235
|
+
# association / enum / validation declared inside is invisible to the discoverer, turning
|
|
236
|
+
# `where(<assoc>: ...)` into a false `unknown-column`. Nested `with_options` blocks recurse.
|
|
266
237
|
#
|
|
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.
|
|
238
|
+
# The options the `with_options` call itself carries (e.g. `with_options class_name: 'Account'`)
|
|
239
|
+
# are NOT merged into the nested calls — discovering the declaration name is what clears the
|
|
240
|
+
# false positive; the merged-option target precision is a separate refinement.
|
|
272
241
|
def declaration_calls(body)
|
|
273
242
|
return [] if body.nil?
|
|
274
243
|
|
|
@@ -283,17 +252,13 @@ module Rigor
|
|
|
283
252
|
end
|
|
284
253
|
end
|
|
285
254
|
|
|
286
|
-
# Walks the class body for association DSL calls and
|
|
287
|
-
# returns a list of rows shaped:
|
|
255
|
+
# Walks the class body for association DSL calls and returns a list of rows shaped:
|
|
288
256
|
#
|
|
289
257
|
# { name: "user", kind: :singular, target: "User" }
|
|
290
258
|
#
|
|
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.
|
|
259
|
+
# The `target` is resolved from an explicit `class_name: "Foo"` option when supplied, otherwise
|
|
260
|
+
# inferred from the association name via {Inflector.classify}. Calls whose first arg is not a
|
|
261
|
+
# Symbol literal (or whose `class_name:` is a non-literal expression) decline rather than guess.
|
|
297
262
|
def lookup_associations(body)
|
|
298
263
|
return [] if body.nil?
|
|
299
264
|
|
|
@@ -319,10 +284,8 @@ module Rigor
|
|
|
319
284
|
polymorphic = POLYMORPHIC_BY_DEFAULT.include?(node.name) ||
|
|
320
285
|
association_option(args, "polymorphic") == true
|
|
321
286
|
|
|
322
|
-
# A polymorphic association has no single static target
|
|
323
|
-
#
|
|
324
|
-
# declines to narrow rather than inventing a wrong
|
|
325
|
-
# `Nominal[<classified-name>]`.
|
|
287
|
+
# A polymorphic association has no single static target class — `target` is nil and the flow
|
|
288
|
+
# contribution declines to narrow rather than inventing a wrong `Nominal[<classified-name>]`.
|
|
326
289
|
if polymorphic
|
|
327
290
|
target = nil
|
|
328
291
|
else
|
|
@@ -334,15 +297,12 @@ module Rigor
|
|
|
334
297
|
nullable: association_nullable?(node.name, args) }
|
|
335
298
|
end
|
|
336
299
|
|
|
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.
|
|
300
|
+
# Whether a `:singular` association's accessor can return `nil`. `has_one` genuinely can (no
|
|
301
|
+
# associated record → `nil`). `belongs_to` is **required (non-`nil`) by default since Rails 5**
|
|
302
|
+
# (`belongs_to_required_by_default`); it becomes nullable only when the call passes
|
|
303
|
+
# `optional: true` or `required: false`. `composed_of` is non-nullable unless `allow_nil: true`.
|
|
304
|
+
# `delegated_type` roles are required. A non-literal option value declines to the default rather
|
|
305
|
+
# than guessing.
|
|
346
306
|
def association_nullable?(method_name, args)
|
|
347
307
|
case method_name
|
|
348
308
|
when :has_one
|
|
@@ -357,9 +317,8 @@ module Rigor
|
|
|
357
317
|
end
|
|
358
318
|
end
|
|
359
319
|
|
|
360
|
-
# Reads a literal boolean association option (`optional:` /
|
|
361
|
-
#
|
|
362
|
-
# `nil` when the key is absent or its value is non-literal.
|
|
320
|
+
# Reads a literal boolean association option (`optional:` / `required:`). Returns `true` / `false`
|
|
321
|
+
# for a literal, or `nil` when the key is absent or its value is non-literal.
|
|
363
322
|
def association_option(args, key)
|
|
364
323
|
args.each do |arg|
|
|
365
324
|
next unless arg.is_a?(Prism::KeywordHashNode)
|
|
@@ -388,10 +347,9 @@ module Rigor
|
|
|
388
347
|
nil
|
|
389
348
|
end
|
|
390
349
|
|
|
391
|
-
# `enum status: { active: 0, archived: 1 }` (Rails ≤6)
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
# Non-literal forms decline rather than guess.
|
|
350
|
+
# `enum status: { active: 0, archived: 1 }` (Rails ≤6) and `enum :status, [:active, :archived]`
|
|
351
|
+
# (Rails 7+). Returns `Hash<column_name => Array<Symbol>>`. Non-literal forms decline rather than
|
|
352
|
+
# guess.
|
|
395
353
|
def lookup_enums(body)
|
|
396
354
|
return {} if body.nil?
|
|
397
355
|
|
|
@@ -445,10 +403,9 @@ module Rigor
|
|
|
445
403
|
end
|
|
446
404
|
end
|
|
447
405
|
|
|
448
|
-
# `scope :active, -> { ... }`. Records the scope name
|
|
449
|
-
#
|
|
450
|
-
#
|
|
451
|
-
# based on the name alone via `class_scope_return_type`).
|
|
406
|
+
# `scope :active, -> { ... }`. Records the scope name only (the body is intentionally NOT
|
|
407
|
+
# introspected — the caller contributes `ActiveRecord::Relation[Model]` based on the name alone
|
|
408
|
+
# via `class_scope_return_type`).
|
|
452
409
|
def lookup_scopes(body)
|
|
453
410
|
return [] if body.nil?
|
|
454
411
|
|
|
@@ -468,11 +425,9 @@ module Rigor
|
|
|
468
425
|
scopes.freeze
|
|
469
426
|
end
|
|
470
427
|
|
|
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).
|
|
428
|
+
# `validates :name, presence: true, length: { maximum: 100 }`. Records the attribute name (the
|
|
429
|
+
# validator option set is ignored — the value here is the diagnostic `validates :unknown_attr`
|
|
430
|
+
# surfacing when the attribute isn't a column on the table).
|
|
476
431
|
def lookup_validations(body)
|
|
477
432
|
return [] if body.nil?
|
|
478
433
|
|
|
@@ -487,10 +442,9 @@ module Rigor
|
|
|
487
442
|
attrs.uniq.freeze
|
|
488
443
|
end
|
|
489
444
|
|
|
490
|
-
# `before_save :foo`, `after_create :bar`, etc. Records
|
|
491
|
-
#
|
|
492
|
-
#
|
|
493
|
-
# Block callbacks (`before_save { ... }`) decline.
|
|
445
|
+
# `before_save :foo`, `after_create :bar`, etc. Records the referenced method name (a Symbol
|
|
446
|
+
# literal). The diagnostic value is "did you forget to `def` this?". Block callbacks
|
|
447
|
+
# (`before_save { ... }`) decline.
|
|
494
448
|
CALLBACK_METHODS = %i[
|
|
495
449
|
before_validation after_validation
|
|
496
450
|
before_save after_save around_save
|
|
@@ -517,12 +471,10 @@ module Rigor
|
|
|
517
471
|
targets.freeze
|
|
518
472
|
end
|
|
519
473
|
|
|
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.
|
|
474
|
+
# `alias_attribute :new_name, :old_name`. Records the mapping so the analyzer accepts the alias
|
|
475
|
+
# as a query key — without it every `where(<alias>: ...)` / `find_by(<alias>: ...)` call surfaces
|
|
476
|
+
# as a false `unknown-column`. Returns `Hash<alias => target>`; non-Symbol-literal forms decline
|
|
477
|
+
# rather than guess.
|
|
526
478
|
def lookup_aliases(body)
|
|
527
479
|
return {} if body.nil?
|
|
528
480
|
|
|
@@ -543,10 +495,8 @@ module Rigor
|
|
|
543
495
|
aliases.freeze
|
|
544
496
|
end
|
|
545
497
|
|
|
546
|
-
# Collects every Symbol-literal positional argument
|
|
547
|
-
#
|
|
548
|
-
# and `lookup_callbacks` to extract the attribute /
|
|
549
|
-
# method name list.
|
|
498
|
+
# Collects every Symbol-literal positional argument from a CallNode. Used by both
|
|
499
|
+
# `lookup_validations` and `lookup_callbacks` to extract the attribute / method name list.
|
|
550
500
|
def symbol_args(node)
|
|
551
501
|
args = node.arguments&.arguments
|
|
552
502
|
return [] if args.nil?
|