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
|
@@ -17,56 +17,44 @@ module Rigor
|
|
|
17
17
|
# include Dry.Types()
|
|
18
18
|
# end
|
|
19
19
|
#
|
|
20
|
-
# and publishes the resulting `{aliased_name => underlying_class}`
|
|
21
|
-
#
|
|
22
|
-
# Other dry-rb adapter plugins consume this fact:
|
|
20
|
+
# and publishes the resulting `{aliased_name => underlying_class}` table as the `:dry_type_aliases`
|
|
21
|
+
# cross-plugin fact (ADR-9). Other dry-rb adapter plugins consume this fact:
|
|
23
22
|
#
|
|
24
|
-
# - `rigor-dry-struct` reads it so `attribute :city, Types::String`
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# per-key type recognition in `schema { … }` / `params { … }`
|
|
29
|
-
# blocks (separate plugin slice).
|
|
23
|
+
# - `rigor-dry-struct` reads it so `attribute :city, Types::String` promotes `address.city` from
|
|
24
|
+
# `Dynamic[Top]` to `Nominal[String]` via ADR-18's `returns_from_arg:` fact lookup.
|
|
25
|
+
# - `rigor-dry-validation` / `rigor-dry-schema` read it for per-key type recognition in `schema { … }`
|
|
26
|
+
# / `params { … }` blocks (separate plugin slice).
|
|
30
27
|
#
|
|
31
28
|
# ## Floor / ceiling (slice 1)
|
|
32
29
|
#
|
|
33
30
|
# Slice 1 ships the **floor**:
|
|
34
31
|
#
|
|
35
|
-
# - Recognises `module X; include Dry.Types(); end` for any
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# - Publishes the table as `{ "<Module>::<Alias>" =>
|
|
42
|
-
# "<UnderlyingClass>" }` so consumers can match on the
|
|
43
|
-
# qualified constant name they see in source.
|
|
32
|
+
# - Recognises `module X; include Dry.Types(); end` for any constant module name `X` (commonly
|
|
33
|
+
# `Types`, sometimes `MyTypes` / `AppTypes`).
|
|
34
|
+
# - Maps the **basic** dry-types constants: `String`, `Integer`, `Float`, `Decimal`, `Symbol`, `Bool`,
|
|
35
|
+
# `True`, `False`, `Nil`, `Date`, `DateTime`, `Time`, `Hash`, `Array`, `Any`.
|
|
36
|
+
# - Publishes the table as `{ "<Module>::<Alias>" => "<UnderlyingClass>" }` so consumers can match on
|
|
37
|
+
# the qualified constant name they see in source.
|
|
44
38
|
#
|
|
45
39
|
# Implemented beyond the floor:
|
|
46
40
|
#
|
|
47
|
-
# - Nested-namespace aliases (`Types::Coercible::Integer`,
|
|
48
|
-
# `Types::
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# transitive resolution through composition chains) — the alias
|
|
53
|
-
# surface extends beyond the 15 canonical names.
|
|
41
|
+
# - Nested-namespace aliases (`Types::Coercible::Integer`, `Types::Strict::Symbol`,
|
|
42
|
+
# `Types::Params::Bool`, `Types::JSON::Date`) — the four coercion categories each map to the same
|
|
43
|
+
# underlying class as their canonical shortcut.
|
|
44
|
+
# - User-authored compositions (`Email = Types::String.constrained(...)`, transitive resolution
|
|
45
|
+
# through composition chains) — the alias surface extends beyond the 15 canonical names.
|
|
54
46
|
#
|
|
55
47
|
# Deferred:
|
|
56
48
|
#
|
|
57
|
-
# - `dry-types.unknown-alias` / `dry-types.alias-shadow`
|
|
58
|
-
#
|
|
59
|
-
# wasn't published.
|
|
49
|
+
# - `dry-types.unknown-alias` / `dry-types.alias-shadow` diagnostics when downstream code references
|
|
50
|
+
# a name that wasn't published.
|
|
60
51
|
#
|
|
61
52
|
# ## Why no `diagnostics_for_file` at the floor?
|
|
62
53
|
#
|
|
63
|
-
# The plugin's user-visible value at slice 1 is the published
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# diagnostics. The diagnostics surface lands when the
|
|
68
|
-
# `dry-types.*` rule family becomes load-bearing for
|
|
69
|
-
# demand-driven cases.
|
|
54
|
+
# The plugin's user-visible value at slice 1 is the published fact — every downstream uplift
|
|
55
|
+
# (precision promotion in `address.city`, schema-key recognition in `rigor-dry-validation`) consumes
|
|
56
|
+
# the fact rather than the plugin itself emitting diagnostics. The diagnostics surface lands when the
|
|
57
|
+
# `dry-types.*` rule family becomes load-bearing for demand-driven cases.
|
|
70
58
|
class DryTypes < Rigor::Plugin::Base
|
|
71
59
|
manifest(
|
|
72
60
|
id: "dry-types",
|
|
@@ -75,12 +63,9 @@ module Rigor
|
|
|
75
63
|
produces: [:dry_type_aliases]
|
|
76
64
|
)
|
|
77
65
|
|
|
78
|
-
# Walks every project file once during `prepare(services)` to
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# surface; each file's parse error degrades to "no
|
|
82
|
-
# contribution" without polluting the user-visible
|
|
83
|
-
# diagnostic stream.
|
|
66
|
+
# Walks every project file once during `prepare(services)` to build the alias table, then publishes
|
|
67
|
+
# via the ADR-9 fact store. The walk is bounded by the configured `paths:` surface; each file's
|
|
68
|
+
# parse error degrades to "no contribution" without polluting the user-visible diagnostic stream.
|
|
84
69
|
def prepare(services)
|
|
85
70
|
aliases = AliasScanner.scan(paths: scannable_paths(services))
|
|
86
71
|
return if aliases.empty?
|
|
@@ -98,11 +83,9 @@ module Rigor
|
|
|
98
83
|
|
|
99
84
|
private
|
|
100
85
|
|
|
101
|
-
# Resolves the project's `paths:` to a flat list of `.rb`
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
# exclude/sort surface because the alias table is a
|
|
105
|
-
# union — any duplicate scan is a no-op.
|
|
86
|
+
# Resolves the project's `paths:` to a flat list of `.rb` files the scanner walks. Mirrors
|
|
87
|
+
# `Analysis::Runner`'s `expand_paths` floor; we don't need the runner's full exclude/sort surface
|
|
88
|
+
# because the alias table is a union — any duplicate scan is a no-op.
|
|
106
89
|
def scannable_paths(services)
|
|
107
90
|
@scannable_paths ||= services.configuration.paths.flat_map do |entry|
|
|
108
91
|
if File.directory?(entry)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Gem entry point. Required by Rigor's plugin loader when
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# `Rigor::Plugin.register`, which the body of
|
|
7
|
-
# `lib/rigor/plugin/dry_types.rb` performs at load time.
|
|
3
|
+
# Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-dry-types` under
|
|
4
|
+
# `plugins:`. The loader expects this `require` to side-effect a call to `Rigor::Plugin.register`,
|
|
5
|
+
# which the body of `lib/rigor/plugin/dry_types.rb` performs at load time.
|
|
8
6
|
require_relative "rigor/plugin/dry_types"
|
|
@@ -5,18 +5,15 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Plugin
|
|
7
7
|
class DryValidation < Rigor::Plugin::Base
|
|
8
|
-
# Walks project source for `class T < Dry::Validation::Contract`
|
|
9
|
-
#
|
|
8
|
+
# Walks project source for `class T < Dry::Validation::Contract` subclasses and returns the
|
|
9
|
+
# contract class FQN set.
|
|
10
10
|
#
|
|
11
|
-
# Recognition tightness: the superclass match accepts EITHER
|
|
12
|
-
#
|
|
13
|
-
# path
|
|
14
|
-
# (
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# under `Dry::Validation` should use the explicit form.
|
|
18
|
-
# Unrelated `< MyApp::Validation::Contract` shapes with the
|
|
19
|
-
# same tail do NOT register.
|
|
11
|
+
# Recognition tightness: the superclass match accepts EITHER the fully-qualified
|
|
12
|
+
# `Dry::Validation::Contract` (3-segment path) OR the lexical-nested `Validation::Contract`
|
|
13
|
+
# (2-segment path, when the class body lives inside `module Dry`). The bare `< Contract` form
|
|
14
|
+
# (1-segment) is NOT recognised — too ambiguous; users who deeply nest under `Dry::Validation`
|
|
15
|
+
# should use the explicit form. Unrelated `< MyApp::Validation::Contract` shapes with the same tail
|
|
16
|
+
# do NOT register.
|
|
20
17
|
module ContractScanner
|
|
21
18
|
CONTRACT_FULL_PATH = %w[Dry Validation Contract].freeze
|
|
22
19
|
CONTRACT_LEXICAL_DRY_PATH = %w[Validation Contract].freeze
|
|
@@ -24,10 +21,8 @@ module Rigor
|
|
|
24
21
|
|
|
25
22
|
module_function
|
|
26
23
|
|
|
27
|
-
# @param paths [Array<String>] absolute paths to `.rb`
|
|
28
|
-
#
|
|
29
|
-
# @return [Array<String>] frozen, sorted list of
|
|
30
|
-
# recognized contract class FQNs (e.g.
|
|
24
|
+
# @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
|
|
25
|
+
# @return [Array<String>] frozen, sorted list of recognized contract class FQNs (e.g.
|
|
31
26
|
# `["App::NewUserContract", "Types::EmailContract"]`).
|
|
32
27
|
def scan(paths:)
|
|
33
28
|
contracts = []
|
|
@@ -77,11 +72,9 @@ module Rigor
|
|
|
77
72
|
end
|
|
78
73
|
private_class_method :collect_module_node
|
|
79
74
|
|
|
80
|
-
# Matches superclasses whose constant chain is EXACTLY
|
|
81
|
-
# `
|
|
82
|
-
# `
|
|
83
|
-
# — including same-tail-but-different-root chains and
|
|
84
|
-
# the ambiguous bare `Contract` — do not match.
|
|
75
|
+
# Matches superclasses whose constant chain is EXACTLY `Dry::Validation::Contract` (full path) OR
|
|
76
|
+
# EXACTLY `Validation::Contract` (lexical-Dry path). Other shapes — including
|
|
77
|
+
# same-tail-but-different-root chains and the ambiguous bare `Contract` — do not match.
|
|
85
78
|
def contract_subclass?(class_node)
|
|
86
79
|
superclass = class_node.superclass
|
|
87
80
|
return false if superclass.nil?
|
|
@@ -15,31 +15,23 @@ module Rigor
|
|
|
15
15
|
#
|
|
16
16
|
# Slice 1 floor:
|
|
17
17
|
#
|
|
18
|
-
# - Walks the project for `class T < Dry::Validation::Contract`
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# -
|
|
23
|
-
# `Dry::Validation::Contract#call` (returns Result) and
|
|
24
|
-
# `Dry::Validation::Result#{success?, failure?, to_h}`.
|
|
25
|
-
# The manifest's `signature_paths: ["sig"]` auto-contributes
|
|
26
|
-
# the overlay (ADR-25) — no project-side wiring needed.
|
|
18
|
+
# - Walks the project for `class T < Dry::Validation::Contract` subclasses and publishes the
|
|
19
|
+
# resulting set of contract class FQNs as the `:dry_validation_contracts` ADR-9 cross-plugin fact.
|
|
20
|
+
# - Ships an RBS overlay (`sig/dry_validation.rbs`) typing `Dry::Validation::Contract#call` (returns
|
|
21
|
+
# Result) and `Dry::Validation::Result#{success?, failure?, to_h}`. The manifest's
|
|
22
|
+
# `signature_paths: ["sig"]` auto-contributes the overlay (ADR-25) — no project-side wiring needed.
|
|
27
23
|
#
|
|
28
24
|
# Slice 2 (deferred, per design note):
|
|
29
25
|
#
|
|
30
|
-
# - Integrate with `:dry_schema_table` (published by
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# schema. Until this lands, `result.to_h` types as
|
|
34
|
-
# `Hash[Symbol, untyped]` (the generic RBS overlay shape).
|
|
26
|
+
# - Integrate with `:dry_schema_table` (published by `rigor-dry-schema`) so the `params { ... }`
|
|
27
|
+
# block inside a Contract contributes a typed `result.to_h` shape per the schema. Until this lands,
|
|
28
|
+
# `result.to_h` types as `Hash[Symbol, untyped]` (the generic RBS overlay shape).
|
|
35
29
|
#
|
|
36
|
-
# Slice 3 (deferred): `json { ... }` adapter parity with
|
|
37
|
-
# `params { ... }`. Same shape as slice 2.
|
|
30
|
+
# Slice 3 (deferred): `json { ... }` adapter parity with `params { ... }`. Same shape as slice 2.
|
|
38
31
|
#
|
|
39
|
-
# No ADR-3 amendment is needed for the validation surface
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# existing bool flow facts).
|
|
32
|
+
# No ADR-3 amendment is needed for the validation surface itself; `Dry::Validation::Result` is a
|
|
33
|
+
# generic class, not a sum type (the `success?` / `failure?` predicates narrow via existing bool flow
|
|
34
|
+
# facts).
|
|
43
35
|
class DryValidation < Rigor::Plugin::Base
|
|
44
36
|
manifest(
|
|
45
37
|
id: "dry-validation",
|
|
@@ -47,9 +39,8 @@ module Rigor
|
|
|
47
39
|
description: "Recognises `class T < Dry::Validation::Contract` subclasses and " \
|
|
48
40
|
"publishes the contract FQN set.",
|
|
49
41
|
produces: [:dry_validation_contracts],
|
|
50
|
-
# Auto-contribute the bundled RBS overlay (Contract#call -> Result,
|
|
51
|
-
#
|
|
52
|
-
# signature_paths wiring is needed.
|
|
42
|
+
# Auto-contribute the bundled RBS overlay (Contract#call -> Result, Result#success?/#to_h/...)
|
|
43
|
+
# per ADR-25, so no project-side signature_paths wiring is needed.
|
|
53
44
|
signature_paths: ["sig"]
|
|
54
45
|
)
|
|
55
46
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Gem entry point. Required by Rigor's plugin loader when
|
|
4
|
-
#
|
|
5
|
-
# Side-effects a `Rigor::Plugin.register` call via the
|
|
3
|
+
# Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-dry-validation`
|
|
4
|
+
# under `plugins:`. Side-effects a `Rigor::Plugin.register` call via the
|
|
6
5
|
# `lib/rigor/plugin/dry_validation.rb` class body.
|
|
7
6
|
require_relative "rigor/plugin/dry_validation"
|
|
@@ -7,34 +7,27 @@ require "rigor/source/literals"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Factorybot < Rigor::Plugin::Base
|
|
10
|
-
# Per-file walker — visits every `FactoryBot.<entry>(...)`
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# against the per-run {FactoryIndex}.
|
|
10
|
+
# Per-file walker — visits every `FactoryBot.<entry>(...)` call (and the `FactoryGirl` legacy alias)
|
|
11
|
+
# and validates the factory name + the keyword-argument attribute keys against the per-run
|
|
12
|
+
# {FactoryIndex}.
|
|
14
13
|
#
|
|
15
|
-
# Recognised entry methods cover the canonical create /
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# differ — one persists, one returns a hash — but the
|
|
19
|
-
# call-site shape is identical from the static check's
|
|
20
|
-
# perspective).
|
|
14
|
+
# Recognised entry methods cover the canonical create / build / build_stubbed / attributes_for
|
|
15
|
+
# family; the same validation applies to every entry (the runtime semantics differ — one persists,
|
|
16
|
+
# one returns a hash — but the call-site shape is identical from the static check's perspective).
|
|
21
17
|
module Analyzer
|
|
22
18
|
ENTRY_METHODS = %i[create build build_stubbed attributes_for create_list build_list build_stubbed_list].freeze
|
|
23
19
|
|
|
24
|
-
# One violation. Carries its own `location` because a single
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# unknown-attribute. The caller positions each via
|
|
29
|
-
# `Plugin::Base#diagnostic(node, location:)`.
|
|
20
|
+
# One violation. Carries its own `location` because a single entry call yields diagnostics at
|
|
21
|
+
# different positions — the matcher/method name (`message_loc`) for factory-call /
|
|
22
|
+
# unknown-factory, and the attribute key node for unknown-attribute. The caller positions each
|
|
23
|
+
# via `Plugin::Base#diagnostic(node, location:)`.
|
|
30
24
|
Violation = Data.define(:location, :message, :severity, :rule)
|
|
31
25
|
|
|
32
26
|
module_function
|
|
33
27
|
|
|
34
|
-
# The violations for a single entry call (`FactoryBot.create(...)`
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# the walk.
|
|
28
|
+
# The violations for a single entry call (`FactoryBot.create(...)` etc.), or `[]` when the node
|
|
29
|
+
# is not an entry call or its first argument is not a literal factory name. ADR-37: the engine
|
|
30
|
+
# owns the walk.
|
|
38
31
|
#
|
|
39
32
|
# @param call_node [Prism::Node]
|
|
40
33
|
# @param factory_index [FactoryIndex]
|
|
@@ -80,20 +73,14 @@ module Rigor
|
|
|
80
73
|
[factory_call_violation(call_node, factory_name, entry)]
|
|
81
74
|
end
|
|
82
75
|
|
|
83
|
-
# The keyword-argument attribute keys come from the
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# a `Prism::SymbolNode` is treated as a literal
|
|
87
|
-
# attribute reference.
|
|
76
|
+
# The keyword-argument attribute keys come from the trailing `Prism::KeywordHashNode` (Ruby's
|
|
77
|
+
# `name: "value"` syntax). Each AssocNode whose key is a `Prism::SymbolNode` is treated as a
|
|
78
|
+
# literal attribute reference.
|
|
88
79
|
#
|
|
89
|
-
# When `model_index` (the cross-plugin `:model_index`
|
|
90
|
-
#
|
|
91
|
-
# the
|
|
92
|
-
# factory
|
|
93
|
-
# model's columns. FactoryBot's runtime accepts any AR
|
|
94
|
-
# attribute regardless of whether the factory declared
|
|
95
|
-
# it, so the cross-check broadens the acceptance
|
|
96
|
-
# accordingly.
|
|
80
|
+
# When `model_index` (the cross-plugin `:model_index` fact published by rigor-activerecord) is
|
|
81
|
+
# present, the effective accepted key set is the UNION of the factory's declared attributes plus
|
|
82
|
+
# the corresponding model's columns. FactoryBot's runtime accepts any AR attribute regardless of
|
|
83
|
+
# whether the factory declared it, so the cross-check broadens the acceptance accordingly.
|
|
97
84
|
def unknown_attribute_violations(call_node, entry, model_index)
|
|
98
85
|
accepted_keys, suggestion_dictionary = effective_keys(entry, model_index)
|
|
99
86
|
attr_spell_checker = DidYouMean::SpellChecker.new(dictionary: suggestion_dictionary)
|
|
@@ -119,10 +106,8 @@ module Rigor
|
|
|
119
106
|
[(factory_keys + model_columns).uniq.freeze, (factory_keys + model_columns).uniq.freeze]
|
|
120
107
|
end
|
|
121
108
|
|
|
122
|
-
# Convention: `:user` → `User`, `:order_item` →
|
|
123
|
-
# `
|
|
124
|
-
# convention; namespaced models (`:admin_user` →
|
|
125
|
-
# `Admin::User`) are deferred.
|
|
109
|
+
# Convention: `:user` → `User`, `:order_item` → `OrderItem`. Mirrors rigor-actionpack Phase 1's
|
|
110
|
+
# convention; namespaced models (`:admin_user` → `Admin::User`) are deferred.
|
|
126
111
|
def model_class_for(factory_name)
|
|
127
112
|
factory_name.to_s.split("_").map(&:capitalize).join
|
|
128
113
|
end
|
|
@@ -8,13 +8,10 @@ require_relative "factory_index"
|
|
|
8
8
|
module Rigor
|
|
9
9
|
module Plugin
|
|
10
10
|
class Factorybot < Rigor::Plugin::Base
|
|
11
|
-
# Walks `factory_search_paths` and parses each `.rb` file
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# `["spec/factories", "spec/factories.rb"]` covers both
|
|
16
|
-
# the multi-file convention RSpec uses today and the
|
|
17
|
-
# legacy single-file form.
|
|
11
|
+
# Walks `factory_search_paths` and parses each `.rb` file into a {FactoryIndex}. The search-path
|
|
12
|
+
# list contains both directory paths (recursively walked) and direct file paths (read once); the
|
|
13
|
+
# typical default `["spec/factories", "spec/factories.rb"]` covers both the multi-file convention
|
|
14
|
+
# RSpec uses today and the legacy single-file form.
|
|
18
15
|
#
|
|
19
16
|
# The walker recognises:
|
|
20
17
|
#
|
|
@@ -22,21 +19,16 @@ module Rigor
|
|
|
22
19
|
# - `factory "users" do ... end` — string form
|
|
23
20
|
# - `factory :users, aliases: [:author] do ... end` — alias form
|
|
24
21
|
#
|
|
25
|
-
# Inside a factory block, attribute declarations come in
|
|
26
|
-
#
|
|
27
|
-
# (Symbol arg / String arg):
|
|
22
|
+
# Inside a factory block, attribute declarations come in several shapes. Only literal-name forms
|
|
23
|
+
# are recognised (Symbol arg / String arg):
|
|
28
24
|
#
|
|
29
|
-
# - `name { "Alice" }` — implicit attribute via
|
|
30
|
-
# `method_missing` with a block (FactoryBot's modern
|
|
25
|
+
# - `name { "Alice" }` — implicit attribute via `method_missing` with a block (FactoryBot's modern
|
|
31
26
|
# syntax)
|
|
32
|
-
# - `name "Alice"` — implicit attribute via
|
|
33
|
-
#
|
|
34
|
-
# - `add_attribute(:name) { "Alice" }` — the explicit
|
|
35
|
-
# form
|
|
27
|
+
# - `name "Alice"` — implicit attribute via `method_missing` with a positional argument (legacy)
|
|
28
|
+
# - `add_attribute(:name) { "Alice" }` — the explicit form
|
|
36
29
|
#
|
|
37
|
-
# Sequences (`sequence(:email) { ... }`), associations
|
|
38
|
-
#
|
|
39
|
-
# relationships are deferred to later slices.
|
|
30
|
+
# Sequences (`sequence(:email) { ... }`), associations (`association :author`), traits, and parent
|
|
31
|
+
# / child relationships are deferred to later slices.
|
|
40
32
|
class FactoryDiscoverer
|
|
41
33
|
def initialize(io_boundary:, search_paths:)
|
|
42
34
|
@io_boundary = io_boundary
|
|
@@ -83,12 +75,10 @@ module Rigor
|
|
|
83
75
|
nil
|
|
84
76
|
end
|
|
85
77
|
|
|
86
|
-
# Yields `(factory_name, [attribute_names])` for every
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# arbitrary container nodes so factories inside `module`
|
|
91
|
-
# / `class` blocks are still picked up.
|
|
78
|
+
# Yields `(factory_name, [attribute_names])` for every `factory :name do ... end` call
|
|
79
|
+
# discovered in the subtree. The walker recurses into top-level wrapping blocks
|
|
80
|
+
# (`FactoryBot.define do ... end`) and into arbitrary container nodes so factories inside
|
|
81
|
+
# `module` / `class` blocks are still picked up.
|
|
92
82
|
def walk_for_factories(node, &)
|
|
93
83
|
return unless node.is_a?(Prism::Node)
|
|
94
84
|
|
|
@@ -116,26 +106,20 @@ module Rigor
|
|
|
116
106
|
Rigor::Source::Literals.symbol_or_string_name(call_node.arguments&.arguments&.first)
|
|
117
107
|
end
|
|
118
108
|
|
|
119
|
-
# Resolves the model class name for the factory.
|
|
120
|
-
# Three sources, in priority order:
|
|
109
|
+
# Resolves the model class name for the factory. Three sources, in priority order:
|
|
121
110
|
#
|
|
122
|
-
# 1. Explicit `class: <Const>` keyword arg —
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
# 3. Inflected from the factory name — `:user` →
|
|
127
|
-
# `"User"`, `:admin_user` → `"AdminUser"`. The
|
|
128
|
-
# factory name is already singular by FactoryBot
|
|
129
|
-
# convention, so we only need camelization.
|
|
111
|
+
# 1. Explicit `class: <Const>` keyword arg — ConstantReadNode / ConstantPathNode value.
|
|
112
|
+
# 2. Explicit `class: "<name>"` keyword arg — String value (supports `"Admin::User"`).
|
|
113
|
+
# 3. Inflected from the factory name — `:user` → `"User"`, `:admin_user` → `"AdminUser"`. The
|
|
114
|
+
# factory name is already singular by FactoryBot convention, so we only need camelization.
|
|
130
115
|
#
|
|
131
116
|
# Returns a String (the canonical class name).
|
|
132
117
|
def factory_model_class(call_node, factory_name)
|
|
133
118
|
explicit = explicit_class_option(call_node)
|
|
134
119
|
return explicit if explicit
|
|
135
120
|
|
|
136
|
-
# ADR-39: the real ActiveSupport::Inflector camelizes the factory
|
|
137
|
-
#
|
|
138
|
-
# `admin/user` → `Admin::User`), so the model-class fallback
|
|
121
|
+
# ADR-39: the real ActiveSupport::Inflector camelizes the factory name to its class
|
|
122
|
+
# (`admin_user` → `AdminUser`, `admin/user` → `Admin::User`), so the model-class fallback
|
|
139
123
|
# matches Rails' real convention rather than an approximation.
|
|
140
124
|
Rigor::Plugin::Inflector.camelize(factory_name)
|
|
141
125
|
end
|
|
@@ -188,10 +172,9 @@ module Rigor
|
|
|
188
172
|
attributes
|
|
189
173
|
end
|
|
190
174
|
|
|
191
|
-
# Walks the block body collecting attribute names. Only
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
# not collected (traits deferred to a follow-up).
|
|
175
|
+
# Walks the block body collecting attribute names. Only top-level statements are examined —
|
|
176
|
+
# attributes inside `trait :admin do ... end` or other nested blocks are not collected (traits
|
|
177
|
+
# deferred to a follow-up).
|
|
195
178
|
def collect_attributes_from(node, accumulator)
|
|
196
179
|
return unless node.is_a?(Prism::Node)
|
|
197
180
|
|
|
@@ -204,15 +187,14 @@ module Rigor
|
|
|
204
187
|
|
|
205
188
|
def record_attribute(node, accumulator)
|
|
206
189
|
return unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
207
|
-
# Skip association / sequence / trait / framework
|
|
208
|
-
#
|
|
190
|
+
# Skip association / sequence / trait / framework methods — only plain attribute declarations
|
|
191
|
+
# are recorded.
|
|
209
192
|
return if SKIPPED_METHODS.include?(node.name)
|
|
210
193
|
|
|
211
194
|
name = if node.name == :add_attribute
|
|
212
195
|
literal_name_arg(node)
|
|
213
196
|
else
|
|
214
|
-
# method_missing form: the call's method
|
|
215
|
-
# name IS the attribute name.
|
|
197
|
+
# method_missing form: the call's method name IS the attribute name.
|
|
216
198
|
node.name.to_s
|
|
217
199
|
end
|
|
218
200
|
accumulator << name if name
|
|
@@ -3,26 +3,22 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Factorybot < Rigor::Plugin::Base
|
|
6
|
-
# Per-run frozen index of discovered FactoryBot factories
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# relationships, traits, and dynamically-named factories
|
|
11
|
-
# are deferred to follow-up slices.
|
|
6
|
+
# Per-run frozen index of discovered FactoryBot factories and the attribute keys each declares.
|
|
7
|
+
# Indexes only **literal symbol/string** factory names + **literal symbol** attribute names;
|
|
8
|
+
# sequences, parent/child relationships, traits, and dynamically-named factories are deferred to
|
|
9
|
+
# follow-up slices.
|
|
12
10
|
#
|
|
13
|
-
# Each entry carries a `model_class` — the inferred or
|
|
14
|
-
#
|
|
11
|
+
# Each entry carries a `model_class` — the inferred or explicit class the factory builds. Resolved
|
|
12
|
+
# from:
|
|
15
13
|
#
|
|
16
|
-
# 1. An explicit `factory :user, class: User do`
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# `:user` → `"User"`, `:admin_user` → `"AdminUser"`.
|
|
14
|
+
# 1. An explicit `factory :user, class: User do` keyword option (ConstantReadNode /
|
|
15
|
+
# ConstantPathNode value).
|
|
16
|
+
# 2. An explicit `factory :user, class: "User"` keyword option (String value, supports
|
|
17
|
+
# `"Admin::User"`).
|
|
18
|
+
# 3. Fallback: inflected from the factory name — `:user` → `"User"`, `:admin_user` →
|
|
19
|
+
# `"AdminUser"`.
|
|
23
20
|
#
|
|
24
|
-
# The structure is intentionally flat: one entry per
|
|
25
|
-
# factory name. Attribute lists deduplicate.
|
|
21
|
+
# The structure is intentionally flat: one entry per factory name. Attribute lists deduplicate.
|
|
26
22
|
class FactoryIndex
|
|
27
23
|
Entry = Data.define(:name, :attribute_names, :model_class)
|
|
28
24
|
|