rigortype 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/08-understanding-errors.md +3 -3
- data/docs/manual/01-installation.md +1 -0
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +27 -4
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +248 -186
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +62 -48
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +46 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +34 -18
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +225 -304
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +794 -1154
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/fork_map.rb +87 -0
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +627 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -821
- data/lib/rigor/inference/statement_evaluator.rb +754 -1114
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/environment.rbs +1 -0
- data/sig/rigor/scope.rbs +6 -0
- metadata +9 -1
|
@@ -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
|
|
|
@@ -8,17 +8,13 @@ require_relative "factorybot/factory_index"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
|
-
# rigor-factorybot — validates `FactoryBot.create(:name,
|
|
12
|
-
#
|
|
13
|
-
# attributes_for / *_list family against a per-run index
|
|
14
|
-
# built from `factory_search_paths`.
|
|
11
|
+
# rigor-factorybot — validates `FactoryBot.create(:name, key: ...)` and the build / build_stubbed /
|
|
12
|
+
# attributes_for / *_list family against a per-run index built from `factory_search_paths`.
|
|
15
13
|
#
|
|
16
|
-
# Recognises factory NAMES + literal ATTRIBUTE KEYS in
|
|
17
|
-
# the
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# Traits, sequences, parent / child factories, and dynamic
|
|
21
|
-
# factory names are deferred to follow-up slices.
|
|
14
|
+
# Recognises factory NAMES + literal ATTRIBUTE KEYS in the call's keyword hash. The AR column
|
|
15
|
+
# cross-check uses the `rigor-activerecord` `:model_index` ADR-9 fact when that plugin is loaded
|
|
16
|
+
# (optional `consumes:`). Traits, sequences, parent / child factories, and dynamic factory names are
|
|
17
|
+
# deferred to follow-up slices.
|
|
22
18
|
#
|
|
23
19
|
# ## Configuration
|
|
24
20
|
#
|
|
@@ -33,43 +29,32 @@ module Rigor
|
|
|
33
29
|
#
|
|
34
30
|
# ## What it checks
|
|
35
31
|
#
|
|
36
|
-
# - **Factory existence** — every entry call's first
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# with a `DidYouMean`
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# declared attribute
|
|
43
|
-
# `unknown-attribute` with a `DidYouMean` suggestion.
|
|
44
|
-
# - **Trace** — recognised entry calls also emit a
|
|
45
|
-
# `factory-call` info diagnostic listing the factory's
|
|
46
|
-
# declared attribute set.
|
|
32
|
+
# - **Factory existence** — every entry call's first positional Symbol / String literal is looked up
|
|
33
|
+
# in the index. Missing factories emit `unknown-factory` with a `DidYouMean` suggestion.
|
|
34
|
+
# - **Attribute key existence** — every literal-Symbol keyword-argument key is matched against the
|
|
35
|
+
# factory's declared attribute names. Missing keys emit `unknown-attribute` with a `DidYouMean`
|
|
36
|
+
# suggestion.
|
|
37
|
+
# - **Trace** — recognised entry calls also emit a `factory-call` info diagnostic listing the
|
|
38
|
+
# factory's declared attribute set.
|
|
47
39
|
#
|
|
48
40
|
# ## Recognised entry methods
|
|
49
41
|
#
|
|
50
|
-
# `FactoryBot.create`, `.build`, `.build_stubbed`,
|
|
51
|
-
# `.
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# context) are NOT recognised — too many false positives on
|
|
56
|
-
# plain `create` calls outside test files; deferred until
|
|
57
|
-
# receiver-type inference can disambiguate.
|
|
42
|
+
# `FactoryBot.create`, `.build`, `.build_stubbed`, `.attributes_for`, `.create_list`, `.build_list`,
|
|
43
|
+
# `.build_stubbed_list`. The legacy `FactoryGirl` constant is recognised identically. Implicit-receiver
|
|
44
|
+
# calls (`create(:name)` inside an `include FactoryBot::Syntax::Methods` context) are NOT recognised —
|
|
45
|
+
# too many false positives on plain `create` calls outside test files; deferred until receiver-type
|
|
46
|
+
# inference can disambiguate.
|
|
58
47
|
#
|
|
59
48
|
# ## What's recognised inside `factory :name do ... end`
|
|
60
49
|
#
|
|
61
|
-
# - `name { "Alice" }` — implicit attribute via
|
|
62
|
-
#
|
|
63
|
-
# - `name "Alice"` — implicit attribute with a positional
|
|
64
|
-
# argument (legacy syntax).
|
|
50
|
+
# - `name { "Alice" }` — implicit attribute via `method_missing` with a block (modern syntax).
|
|
51
|
+
# - `name "Alice"` — implicit attribute with a positional argument (legacy syntax).
|
|
65
52
|
# - `add_attribute(:name) { "Alice" }` — explicit form.
|
|
66
53
|
#
|
|
67
|
-
# Sequences (`sequence(:email) { ... }`), associations
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# slices. Factories whose name is a non-literal expression
|
|
72
|
-
# (`factory FACTORY_NAME do ... end`) are silently skipped.
|
|
54
|
+
# Sequences (`sequence(:email) { ... }`), associations (`association :author`), traits (`trait :admin
|
|
55
|
+
# do ... end`), and parent / child relationships (`factory :admin, parent: :user do ... end`) are
|
|
56
|
+
# deferred to follow-up slices. Factories whose name is a non-literal expression (`factory
|
|
57
|
+
# FACTORY_NAME do ... end`) are silently skipped.
|
|
73
58
|
class Factorybot < Rigor::Plugin::Base
|
|
74
59
|
manifest(
|
|
75
60
|
id: "factorybot",
|
|
@@ -96,18 +81,16 @@ module Rigor
|
|
|
96
81
|
@factory_search_paths = Array(config.fetch("factory_search_paths")).map(&:to_s)
|
|
97
82
|
end
|
|
98
83
|
|
|
99
|
-
# ADR-37 — per-call factory/attribute validation over the
|
|
100
|
-
#
|
|
101
|
-
# (
|
|
102
|
-
#
|
|
103
|
-
# diagnostic remains, so there is no `diagnostics_for_file`.
|
|
84
|
+
# ADR-37 — per-call factory/attribute validation over the engine-owned walk. Each violation carries
|
|
85
|
+
# its own location (the call's message_loc, or the offending attribute key), so it is positioned
|
|
86
|
+
# via `diagnostic(node, location:)`. No file-level diagnostic remains, so there is no
|
|
87
|
+
# `diagnostics_for_file`.
|
|
104
88
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
105
89
|
index = producer_value(:factory_index)
|
|
106
90
|
next [] if index.nil? || index.empty?
|
|
107
91
|
|
|
108
|
-
# `:model_index` is rigor-activerecord's published fact (ADR-9);
|
|
109
|
-
#
|
|
110
|
-
# falls back to factory-attributes-only checking.
|
|
92
|
+
# `:model_index` is rigor-activerecord's published fact (ADR-9); nil when that plugin isn't
|
|
93
|
+
# loaded, in which case the analyzer falls back to factory-attributes-only checking.
|
|
111
94
|
violations = Analyzer.violations_for(
|
|
112
95
|
call_node: node, factory_index: index,
|
|
113
96
|
model_index: read_fact(plugin_id: "activerecord", name: :model_index)
|
|
@@ -6,17 +6,13 @@ require "rigor/source/literals"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class Graphql < Rigor::Plugin::Base
|
|
9
|
-
# Walks project source for `class T < GraphQL::Schema::Object`
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# inside the class body.
|
|
9
|
+
# Walks project source for `class T < GraphQL::Schema::Object` subclasses and emits a
|
|
10
|
+
# `{type_class_fqn => {field_name => {type:, nullable:}}}` table covering every `field :name, Type,
|
|
11
|
+
# null: ...` declaration inside the class body.
|
|
13
12
|
module TypeScanner
|
|
14
|
-
# The canonical GraphQL scalar type names accepted as
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# consumers can cross-reference against Ruby types
|
|
18
|
-
# without re-implementing the GraphQL→Ruby coercion
|
|
19
|
-
# table.
|
|
13
|
+
# The canonical GraphQL scalar type names accepted as `field`'s second positional argument. The
|
|
14
|
+
# plugin maps each to the underlying Ruby class name so downstream consumers can cross-reference
|
|
15
|
+
# against Ruby types without re-implementing the GraphQL→Ruby coercion table.
|
|
20
16
|
CANONICAL_TYPES = {
|
|
21
17
|
"String" => "String",
|
|
22
18
|
"Integer" => "Integer",
|
|
@@ -26,21 +22,17 @@ module Rigor
|
|
|
26
22
|
"ID" => "String"
|
|
27
23
|
}.freeze
|
|
28
24
|
|
|
29
|
-
# The base class name a Schema::Object subclass MUST
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# `BaseObject = GraphQL::Schema::Object` shape work when
|
|
34
|
-
# the alias's RHS is the canonical path.
|
|
25
|
+
# The base class name a Schema::Object subclass MUST inherit from to be recognised. Match is on
|
|
26
|
+
# the rightmost segment of the superclass constant chain so both `GraphQL::Schema::Object` and
|
|
27
|
+
# the locally-aliased `BaseObject = GraphQL::Schema::Object` shape work when the alias's RHS is
|
|
28
|
+
# the canonical path.
|
|
35
29
|
SCHEMA_OBJECT_TAIL = "Object"
|
|
36
30
|
SCHEMA_ENUM_TAIL = "Enum"
|
|
37
31
|
SCHEMA_INPUT_OBJECT_TAIL = "InputObject"
|
|
38
32
|
SCHEMA_MUTATION_TAIL = "Mutation"
|
|
39
|
-
# Common path-segment for `Schema::Object` / `Schema::Enum`
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# fully-qualified `GraphQL::Schema::X` or lexically nested
|
|
43
|
-
# `Schema::X` inside `module GraphQL`).
|
|
33
|
+
# Common path-segment for `Schema::Object` / `Schema::Enum` / `Schema::InputObject` /
|
|
34
|
+
# `Schema::Mutation`; the second-to-last segment must be `Schema` (either fully-qualified
|
|
35
|
+
# `GraphQL::Schema::X` or lexically nested `Schema::X` inside `module GraphQL`).
|
|
44
36
|
SCHEMA_PARENT_SEGMENTS = %w[Schema GraphQL].freeze
|
|
45
37
|
private_constant :SCHEMA_OBJECT_TAIL, :SCHEMA_ENUM_TAIL,
|
|
46
38
|
:SCHEMA_INPUT_OBJECT_TAIL, :SCHEMA_MUTATION_TAIL,
|
|
@@ -48,15 +40,12 @@ module Rigor
|
|
|
48
40
|
|
|
49
41
|
module_function
|
|
50
42
|
|
|
51
|
-
# @param paths [Array<String>] absolute paths to `.rb` files
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# `:mutations` (per-`Schema::Mutation` arguments+fields table).
|
|
58
|
-
# Any subset may be empty when no recognisable declaration
|
|
59
|
-
# of that kind is found.
|
|
43
|
+
# @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
|
|
44
|
+
# @return [Hash{Symbol => Hash}] frozen 4-key result: `:types` (per-`Schema::Object` field
|
|
45
|
+
# table), `:enums` (per-`Schema::Enum` value list), `:input_objects`
|
|
46
|
+
# (per-`Schema::InputObject` argument table), `:mutations` (per-`Schema::Mutation`
|
|
47
|
+
# arguments+fields table). Any subset may be empty when no recognisable declaration of that
|
|
48
|
+
# kind is found.
|
|
60
49
|
def scan(paths:)
|
|
61
50
|
acc = empty_accumulator
|
|
62
51
|
paths.each do |path|
|
|
@@ -97,10 +86,9 @@ module Rigor
|
|
|
97
86
|
end
|
|
98
87
|
private_class_method :scan_file
|
|
99
88
|
|
|
100
|
-
# Walks the AST collecting `class X < GraphQL::Schema::Object`,
|
|
101
|
-
# `Schema::
|
|
102
|
-
#
|
|
103
|
-
# caller can publish multiple cross-plugin facts from one walk.
|
|
89
|
+
# Walks the AST collecting `class X < GraphQL::Schema::Object`, `Schema::Enum`,
|
|
90
|
+
# `Schema::InputObject`, and `Schema::Mutation` decls at any nesting level. Returns a 4-key hash
|
|
91
|
+
# so the caller can publish multiple cross-plugin facts from one walk.
|
|
104
92
|
def collect_definitions(node, qualified_prefix)
|
|
105
93
|
return empty_accumulator if node.nil?
|
|
106
94
|
|
|
@@ -154,10 +142,9 @@ module Rigor
|
|
|
154
142
|
end
|
|
155
143
|
private_class_method :collect_module_node
|
|
156
144
|
|
|
157
|
-
# `class X < GraphQL::Schema::<Tail>` matches when the
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# and `< Schema::<Tail>` (lexically inside `module GraphQL`).
|
|
145
|
+
# `class X < GraphQL::Schema::<Tail>` matches when the superclass's last two path segments are
|
|
146
|
+
# `Schema::<Tail>`. Matches both `< GraphQL::Schema::<Tail>` (fully qualified) and `<
|
|
147
|
+
# Schema::<Tail>` (lexically inside `module GraphQL`).
|
|
161
148
|
def schema_subclass?(class_node, tail)
|
|
162
149
|
superclass = class_node.superclass
|
|
163
150
|
return false if superclass.nil?
|
|
@@ -193,19 +180,16 @@ module Rigor
|
|
|
193
180
|
end
|
|
194
181
|
private_class_method :statement_nodes
|
|
195
182
|
|
|
196
|
-
# Walks every top-level `value "..."` call inside an
|
|
197
|
-
#
|
|
198
|
-
# Array<String>. Both shapes graphql-ruby accepts work:
|
|
183
|
+
# Walks every top-level `value "..."` call inside an enum subclass body and returns the value
|
|
184
|
+
# names as an Array<String>. Both shapes graphql-ruby accepts work:
|
|
199
185
|
#
|
|
200
186
|
# value "ACTIVE"
|
|
201
187
|
# value "DISABLED", value: :off, description: "..."
|
|
202
188
|
#
|
|
203
|
-
# The first positional must be a String literal — the
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# `value:` kwarg (Ruby-side override) and `description:`
|
|
208
|
-
# are omitted from the published table.
|
|
189
|
+
# The first positional must be a String literal — the graphql-ruby `value` API also accepts a
|
|
190
|
+
# Symbol form (`value :ACTIVE`) but the documented idiom is String. Only the GraphQL-side value
|
|
191
|
+
# name is stored; the optional `value:` kwarg (Ruby-side override) and `description:` are
|
|
192
|
+
# omitted from the published table.
|
|
209
193
|
def collect_values(body)
|
|
210
194
|
return [] if body.nil?
|
|
211
195
|
|
|
@@ -220,13 +204,10 @@ module Rigor
|
|
|
220
204
|
end
|
|
221
205
|
private_class_method :collect_values
|
|
222
206
|
|
|
223
|
-
# Walks every top-level `argument :name, Type, required: ...`
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
# `required:` (default `false` — per graphql-ruby's
|
|
228
|
-
# `argument` default; the OPPOSITE polarity of `field`'s
|
|
229
|
-
# `null:`).
|
|
207
|
+
# Walks every top-level `argument :name, Type, required: ...` call inside an InputObject (or
|
|
208
|
+
# Mutation) subclass body and returns the per-argument shape table. Argument syntax mirrors
|
|
209
|
+
# `field` except the nullability axis is named `required:` (default `false` — per graphql-ruby's
|
|
210
|
+
# `argument` default; the OPPOSITE polarity of `field`'s `null:`).
|
|
230
211
|
#
|
|
231
212
|
# argument :name, String, required: true
|
|
232
213
|
# argument :tags, [String], required: false
|
|
@@ -270,10 +251,9 @@ module Rigor
|
|
|
270
251
|
end
|
|
271
252
|
private_class_method :parse_argument_call
|
|
272
253
|
|
|
273
|
-
# Mirror of `extract_nullability` but reads the `required:`
|
|
274
|
-
#
|
|
275
|
-
# default
|
|
276
|
-
# nullability default).
|
|
254
|
+
# Mirror of `extract_nullability` but reads the `required:` kwarg, defaulting to `false`
|
|
255
|
+
# (graphql-ruby's argument default — the OPPOSITE polarity of `field`'s `null:` / nullability
|
|
256
|
+
# default).
|
|
277
257
|
# rubocop:disable Naming/PredicateMethod -- extractor returns the literal required value
|
|
278
258
|
def extract_required_flag(args)
|
|
279
259
|
kwargs = args.last
|
|
@@ -293,12 +273,10 @@ module Rigor
|
|
|
293
273
|
# rubocop:enable Naming/PredicateMethod
|
|
294
274
|
private_class_method :extract_required_flag
|
|
295
275
|
|
|
296
|
-
# `field :name, Type, null: false` shape. The first
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
# the nullability keyword (defaults to TRUE per
|
|
301
|
-
# graphql-ruby's field defaults so we mirror that).
|
|
276
|
+
# `field :name, Type, null: false` shape. The first positional is a Symbol (field name); the
|
|
277
|
+
# second is a constant reference (GraphQL type) OR a single-element ArrayNode (`[Type]`) for
|
|
278
|
+
# GraphQL list types; `null:` is the nullability keyword (defaults to TRUE per graphql-ruby's
|
|
279
|
+
# field defaults so we mirror that).
|
|
302
280
|
def parse_field_call(node)
|
|
303
281
|
args = node.arguments&.arguments
|
|
304
282
|
return nil if args.nil? || args.size < 2
|
|
@@ -320,13 +298,10 @@ module Rigor
|
|
|
320
298
|
end
|
|
321
299
|
private_class_method :parse_field_call
|
|
322
300
|
|
|
323
|
-
# Resolves the `Type` positional argument to a
|
|
324
|
-
# `
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
# not lists. Returns nil for unrecognised shapes (string
|
|
328
|
-
# types `"User"`, Proc lazy types, etc.) so callers drop
|
|
329
|
-
# the field.
|
|
301
|
+
# Resolves the `Type` positional argument to a `{type: "ClassName", list: bool}` tuple.
|
|
302
|
+
# ArrayNode forms (`[String]` / `[Types::User]`) unwrap the single element and mark `list: true`.
|
|
303
|
+
# Bare constant refs are not lists. Returns nil for unrecognised shapes (string types `"User"`,
|
|
304
|
+
# Proc lazy types, etc.) so callers drop the field.
|
|
330
305
|
def resolve_field_type(node)
|
|
331
306
|
if node.is_a?(Prism::ArrayNode)
|
|
332
307
|
element = node.elements.first
|
|
@@ -354,9 +329,8 @@ module Rigor
|
|
|
354
329
|
end
|
|
355
330
|
private_class_method :resolve_constant_type
|
|
356
331
|
|
|
357
|
-
# Defaults to `true` (matches graphql-ruby's `field`
|
|
358
|
-
#
|
|
359
|
-
# keyword and reads its boolean literal.
|
|
332
|
+
# Defaults to `true` (matches graphql-ruby's `field` default nullability). Looks for an explicit
|
|
333
|
+
# `null:` keyword and reads its boolean literal.
|
|
360
334
|
# rubocop:disable Naming/PredicateMethod -- extractor returns the literal nullability value
|
|
361
335
|
def extract_nullability(args)
|
|
362
336
|
kwargs = args.last
|
|
@@ -376,9 +350,8 @@ module Rigor
|
|
|
376
350
|
# rubocop:enable Naming/PredicateMethod
|
|
377
351
|
private_class_method :extract_nullability
|
|
378
352
|
|
|
379
|
-
# Returns the constant chain as an Array of String
|
|
380
|
-
#
|
|
381
|
-
# array for unrecognised node kinds.
|
|
353
|
+
# Returns the constant chain as an Array of String segments (`["GraphQL", "Schema", "Object"]`).
|
|
354
|
+
# Empty array for unrecognised node kinds.
|
|
382
355
|
def constant_path_segments(node)
|
|
383
356
|
case node
|
|
384
357
|
when Prism::ConstantReadNode then [node.name.to_s]
|
|
@@ -397,9 +370,8 @@ module Rigor
|
|
|
397
370
|
end
|
|
398
371
|
private_class_method :constant_path_segments
|
|
399
372
|
|
|
400
|
-
# Joined `::`-form of {.constant_path_segments}. Returns
|
|
401
|
-
#
|
|
402
|
-
# circuit).
|
|
373
|
+
# Joined `::`-form of {.constant_path_segments}. Returns nil for unrecognised node kinds (so
|
|
374
|
+
# callers can short-circuit).
|
|
403
375
|
def constant_name_for(node)
|
|
404
376
|
segments = constant_path_segments(node)
|
|
405
377
|
segments.empty? ? nil : segments.join("::")
|