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
|
@@ -6,49 +6,36 @@ require_relative "rspec_rails/have_http_status_analyzer"
|
|
|
6
6
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
|
-
# rigor-rspec-rails — validates rspec-rails matchers whose
|
|
10
|
-
# arguments are statically checkable.
|
|
9
|
+
# rigor-rspec-rails — validates rspec-rails matchers whose arguments are statically checkable.
|
|
11
10
|
#
|
|
12
11
|
# v0.1.0 covers `have_http_status(int_or_symbol)`:
|
|
13
12
|
#
|
|
14
13
|
# - Integer: must be in `100..599`.
|
|
15
|
-
# - Symbol: must be one of the
|
|
16
|
-
#
|
|
17
|
-
# Rails' status-group aliases (`:success`, `:successful`,
|
|
18
|
-
# `:missing`, `:redirect`, `:error`, `:client_error`,
|
|
14
|
+
# - Symbol: must be one of the `Rack::Utils::SYMBOL_TO_STATUS_CODE` keys or one of Rails' status-group
|
|
15
|
+
# aliases (`:success`, `:successful`, `:missing`, `:redirect`, `:error`, `:client_error`,
|
|
19
16
|
# `:server_error`, `:informational`).
|
|
20
17
|
#
|
|
21
18
|
# ## Why this is a separate plugin from rigor-rspec
|
|
22
19
|
#
|
|
23
|
-
# `rigor-rspec` handles the **type-narrowing** matchers
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# matchers — ones that assert runtime state (HTTP status,
|
|
29
|
-
# rendered template, route shape) without narrowing a
|
|
30
|
-
# type. The two are activated independently in `.rigor.yml`.
|
|
20
|
+
# `rigor-rspec` handles the **type-narrowing** matchers (`be_a`, `be_kind_of`, `be_nil`, `eq(literal)`,
|
|
21
|
+
# etc.) — ones that refine a local's static type so downstream calls in the same `it` body resolve at
|
|
22
|
+
# the narrowed type. `rigor-rspec-rails` handles the **behavioral** matchers — ones that assert runtime
|
|
23
|
+
# state (HTTP status, rendered template, route shape) without narrowing a type. The two are activated
|
|
24
|
+
# independently in `.rigor.yml`.
|
|
31
25
|
#
|
|
32
26
|
# ## Deferred matchers
|
|
33
27
|
#
|
|
34
|
-
# - `render_template(...)` — overlaps with
|
|
35
|
-
# `
|
|
36
|
-
#
|
|
37
|
-
# slice would coordinate the two to avoid
|
|
38
|
-
# double-firing.
|
|
39
|
-
# - `route_to(...)` / `redirect_to(...)` — needs the
|
|
40
|
-
# routes table from `rigor-rails-routes`
|
|
28
|
+
# - `render_template(...)` — overlaps with `rigor-actionpack`'s render-target validation (`render
|
|
29
|
+
# :show` against the view tree). A separate slice would coordinate the two to avoid double-firing.
|
|
30
|
+
# - `route_to(...)` / `redirect_to(...)` — needs the routes table from `rigor-rails-routes`
|
|
41
31
|
# (`:helper_table` cross-plugin fact). Future slice.
|
|
42
|
-
# - `have_enqueued_job(JobClass)` /
|
|
43
|
-
# `
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# - `have_received(:method)` — overlaps with engine's
|
|
48
|
-
# `call.undefined-method`; same coordination question.
|
|
32
|
+
# - `have_enqueued_job(JobClass)` / `have_enqueued_mail(MailerClass)` — class-existence check overlaps
|
|
33
|
+
# with engine's `inference.unresolved-constant`; queued behind a decision on which surface owns the
|
|
34
|
+
# diagnostic.
|
|
35
|
+
# - `have_received(:method)` — overlaps with engine's `call.undefined-method`; same coordination
|
|
36
|
+
# question.
|
|
49
37
|
# - `be_routable` — needs routes table.
|
|
50
|
-
# - `match_response_schema(...)` (rswag / OpenAPI) — out
|
|
51
|
-
# of scope, separate plugin.
|
|
38
|
+
# - `match_response_schema(...)` (rswag / OpenAPI) — out of scope, separate plugin.
|
|
52
39
|
class RspecRails < Rigor::Plugin::Base
|
|
53
40
|
manifest(
|
|
54
41
|
id: "rspec-rails",
|
|
@@ -56,10 +43,9 @@ module Rigor
|
|
|
56
43
|
description: "Validates rspec-rails behavioral matchers (have_http_status floor)."
|
|
57
44
|
)
|
|
58
45
|
|
|
59
|
-
# ADR-37 — the engine walks each file and hands us every
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# reproducing the previous `message_loc || location` positioning.
|
|
46
|
+
# ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is now pure per-node
|
|
47
|
+
# logic. The diagnostic points at the matcher name (`message_loc`), reproducing the previous
|
|
48
|
+
# `message_loc || location` positioning.
|
|
63
49
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
64
50
|
violation = HaveHttpStatusAnalyzer.violation_for(node)
|
|
65
51
|
next [] unless violation
|
|
@@ -5,17 +5,13 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Plugin
|
|
7
7
|
class ShouldaMatchers < Rigor::Plugin::Base
|
|
8
|
-
# Walks every `RSpec.describe <ModelConst> do ... end` /
|
|
9
|
-
#
|
|
10
|
-
# the
|
|
11
|
-
# of nested `describe` / `context`) against the
|
|
12
|
-
# `:model_index` published by `rigor-activerecord`.
|
|
8
|
+
# Walks every `RSpec.describe <ModelConst> do ... end` / `describe <ModelConst> do ... end` block and
|
|
9
|
+
# validates the shoulda-matchers calls inside its body (any depth of nested `describe` / `context`)
|
|
10
|
+
# against the `:model_index` published by `rigor-activerecord`.
|
|
13
11
|
#
|
|
14
|
-
# The "anchor" for cross-checking is the OUTERMOST
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# `describe ".some_method"` (String / Symbol args) does
|
|
18
|
-
# NOT change the anchor.
|
|
12
|
+
# The "anchor" for cross-checking is the OUTERMOST describe block whose argument is a constant —
|
|
13
|
+
# that constant names the model being specced. Nested `describe ".some_method"` (String / Symbol
|
|
14
|
+
# args) does NOT change the anchor.
|
|
19
15
|
#
|
|
20
16
|
# ## Recognised matcher calls (v0.1.0)
|
|
21
17
|
#
|
|
@@ -34,8 +30,7 @@ module Rigor
|
|
|
34
30
|
# have_db_column(:col)
|
|
35
31
|
# have_db_index(:col)
|
|
36
32
|
#
|
|
37
|
-
# All look up `:col` against the model's columns
|
|
38
|
-
# (`Entry#column?`). Unknown columns fire
|
|
33
|
+
# All look up `:col` against the model's columns (`Entry#column?`). Unknown columns fire
|
|
39
34
|
# `unknown-column`.
|
|
40
35
|
#
|
|
41
36
|
# ### Association matchers
|
|
@@ -45,19 +40,15 @@ module Rigor
|
|
|
45
40
|
# have_many(:assoc) ← expects :collection
|
|
46
41
|
# have_and_belong_to_many(:assoc) ← expects :collection
|
|
47
42
|
#
|
|
48
|
-
# Unknown associations fire
|
|
49
|
-
# `
|
|
50
|
-
# associations with mismatched kind (`should belong_to(:posts)`
|
|
51
|
-
# where `:posts` is `has_many`) fire
|
|
52
|
-
# `association-kind-mismatch`.
|
|
43
|
+
# Unknown associations fire `unknown-association`. Known associations with mismatched kind (`should
|
|
44
|
+
# belong_to(:posts)` where `:posts` is `has_many`) fire `association-kind-mismatch`.
|
|
53
45
|
module Analyzer
|
|
54
|
-
# One matcher violation (always `:warning`). Carries no
|
|
55
|
-
#
|
|
56
|
-
# at the matcher name (`message_loc`) via `Plugin::Base#diagnostic`.
|
|
46
|
+
# One matcher violation (always `:warning`). Carries no path/location — the caller (the
|
|
47
|
+
# `node_rule` block) positions it at the matcher name (`message_loc`) via `Plugin::Base#diagnostic`.
|
|
57
48
|
Violation = Struct.new(:rule, :message, keyword_init: true)
|
|
58
49
|
|
|
59
|
-
# `(matcher_name) => (:column | :association_singular | :association_collection)`
|
|
60
|
-
#
|
|
50
|
+
# `(matcher_name) => (:column | :association_singular | :association_collection)` — the
|
|
51
|
+
# validation lane each matcher routes to.
|
|
61
52
|
MATCHER_TABLE = {
|
|
62
53
|
# Column matchers — validate the named column exists on the model.
|
|
63
54
|
validate_presence_of: :column,
|
|
@@ -72,8 +63,7 @@ module Rigor
|
|
|
72
63
|
validate_confirmation_of: :column,
|
|
73
64
|
have_db_column: :column,
|
|
74
65
|
have_db_index: :column,
|
|
75
|
-
# Association matchers — validate the association
|
|
76
|
-
# exists AND its kind matches the matcher.
|
|
66
|
+
# Association matchers — validate the association exists AND its kind matches the matcher.
|
|
77
67
|
belong_to: :association_singular,
|
|
78
68
|
have_one: :association_singular,
|
|
79
69
|
have_many: :association_collection,
|
|
@@ -84,17 +74,13 @@ module Rigor
|
|
|
84
74
|
|
|
85
75
|
# @param path [String]
|
|
86
76
|
# @param root [Prism::Node]
|
|
87
|
-
# @param model_index [Object, nil] the `:model_index`
|
|
88
|
-
# fact value. When nil the analyzer falls silent.
|
|
77
|
+
# @param model_index [Object, nil] the `:model_index` fact value. When nil the analyzer falls silent.
|
|
89
78
|
# @return [Array<Diagnostic>]
|
|
90
|
-
# The matcher violations for a single call node (0..1), or `[]`
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# `describe`-with-constant — nested const describes override,
|
|
96
|
-
# `describe ".method"` string blocks inherit), exactly as the
|
|
97
|
-
# former recursive anchor propagation did.
|
|
79
|
+
# The matcher violations for a single call node (0..1), or `[]` when it is not a shoulda matcher,
|
|
80
|
+
# sits outside any `describe <ModelConst>` block, or the model is unknown. ADR-37: the engine owns
|
|
81
|
+
# the walk; the model anchor comes from the node-rule `NodeContext` ancestors (the innermost
|
|
82
|
+
# enclosing `describe`-with-constant — nested const describes override, `describe ".method"`
|
|
83
|
+
# string blocks inherit), exactly as the former recursive anchor propagation did.
|
|
98
84
|
#
|
|
99
85
|
# @param matcher_call [Prism::Node]
|
|
100
86
|
# @param ancestors [Array<Prism::Node>]
|
|
@@ -114,20 +100,17 @@ module Rigor
|
|
|
114
100
|
violation ? [violation] : []
|
|
115
101
|
end
|
|
116
102
|
|
|
117
|
-
# The model anchor in effect at a node: the constant named by the
|
|
118
|
-
#
|
|
103
|
+
# The model anchor in effect at a node: the constant named by the innermost enclosing `describe
|
|
104
|
+
# <Const> do … end`, or nil.
|
|
119
105
|
def anchor_for(ancestors)
|
|
120
106
|
describe = ancestors.rfind { |n| describe_with_constant?(n) }
|
|
121
107
|
describe && describe_const_name(describe)
|
|
122
108
|
end
|
|
123
109
|
|
|
124
|
-
# A direct matcher invocation is a `CallNode` whose
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
# we always recurse to the inner matcher, so a
|
|
129
|
-
# diagnostic fires on the matcher regardless of the
|
|
130
|
-
# surrounding chain.
|
|
110
|
+
# A direct matcher invocation is a `CallNode` whose `name` is in `MATCHER_TABLE` and whose first
|
|
111
|
+
# argument is a `SymbolNode`. The chain shape (`should`, `expect(...).to`, `is_expected.to`) is
|
|
112
|
+
# irrelevant — we always recurse to the inner matcher, so a diagnostic fires on the matcher
|
|
113
|
+
# regardless of the surrounding chain.
|
|
131
114
|
def matcher_invocation?(node)
|
|
132
115
|
node.is_a?(Prism::CallNode) && MATCHER_TABLE.key?(node.name) && symbol_first_arg?(node)
|
|
133
116
|
end
|
|
@@ -137,10 +120,8 @@ module Rigor
|
|
|
137
120
|
!args.empty? && args.first.is_a?(Prism::SymbolNode)
|
|
138
121
|
end
|
|
139
122
|
|
|
140
|
-
# Detects `RSpec.describe(Const) do ... end` and
|
|
141
|
-
# `
|
|
142
|
-
# scope whose anchor is `Const`. The receiver shape
|
|
143
|
-
# (RSpec vs nil) is allowed in both cases.
|
|
123
|
+
# Detects `RSpec.describe(Const) do ... end` and `describe(Const) do ... end`. Either form opens a
|
|
124
|
+
# scope whose anchor is `Const`. The receiver shape (RSpec vs nil) is allowed in both cases.
|
|
144
125
|
def describe_with_constant?(node)
|
|
145
126
|
return false unless node.is_a?(Prism::CallNode)
|
|
146
127
|
return false unless node.name == :describe
|
|
@@ -6,18 +6,15 @@ require_relative "shoulda_matchers/analyzer"
|
|
|
6
6
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
|
-
# rigor-shoulda-matchers — validates shoulda-matchers
|
|
10
|
-
#
|
|
11
|
-
# (ADR-9) published by `rigor-activerecord`.
|
|
9
|
+
# rigor-shoulda-matchers — validates shoulda-matchers matchers against the `:model_index` cross-plugin
|
|
10
|
+
# fact (ADR-9) published by `rigor-activerecord`.
|
|
12
11
|
#
|
|
13
|
-
# The plugin walks every `RSpec.describe <ModelConst> do
|
|
14
|
-
#
|
|
15
|
-
# against the model's known columns / associations.
|
|
12
|
+
# The plugin walks every `RSpec.describe <ModelConst> do ... end` block and validates the matchers
|
|
13
|
+
# inside against the model's known columns / associations.
|
|
16
14
|
#
|
|
17
15
|
# ## Recognised matchers (v0.1.0)
|
|
18
16
|
#
|
|
19
|
-
# **Column matchers** (validate the named column exists on
|
|
20
|
-
# the model):
|
|
17
|
+
# **Column matchers** (validate the named column exists on the model):
|
|
21
18
|
#
|
|
22
19
|
# validate_presence_of(:col), validate_uniqueness_of(:col),
|
|
23
20
|
# validate_length_of(:col), validate_numericality_of(:col),
|
|
@@ -26,38 +23,27 @@ module Rigor
|
|
|
26
23
|
# validate_format_of(:col), validate_confirmation_of(:col),
|
|
27
24
|
# have_db_column(:col), have_db_index(:col)
|
|
28
25
|
#
|
|
29
|
-
# **Association matchers** (validate the association exists
|
|
30
|
-
# AND its kind matches):
|
|
26
|
+
# **Association matchers** (validate the association exists AND its kind matches):
|
|
31
27
|
#
|
|
32
28
|
# belong_to(:assoc), have_one(:assoc) ← :singular
|
|
33
29
|
# have_many(:assoc), have_and_belong_to_many(:assoc) ← :collection
|
|
34
30
|
#
|
|
35
31
|
# ## Cross-plugin dependency
|
|
36
32
|
#
|
|
37
|
-
# The plugin consumes `:model_index` from `rigor-activerecord`.
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# falls silent — the cross-check is opt-in. Adding
|
|
41
|
-
# `rigor-activerecord` to `.rigor.yml` unlocks the
|
|
42
|
-
# diagnostics.
|
|
33
|
+
# The plugin consumes `:model_index` from `rigor-activerecord`. When `rigor-activerecord` is NOT
|
|
34
|
+
# loaded (or hasn't published an index for the analysed model), the plugin falls silent — the
|
|
35
|
+
# cross-check is opt-in. Adding `rigor-activerecord` to `.rigor.yml` unlocks the diagnostics.
|
|
43
36
|
#
|
|
44
37
|
# ## Limitations (v0.1.0)
|
|
45
38
|
#
|
|
46
|
-
# - **No chained-matcher arg validation.** The chain
|
|
47
|
-
#
|
|
48
|
-
# `
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# - **No
|
|
52
|
-
#
|
|
53
|
-
# the
|
|
54
|
-
# ignored.
|
|
55
|
-
# - **No nested-attribute matchers.** `accept_nested_attributes_for(:posts)`
|
|
56
|
-
# not yet covered.
|
|
57
|
-
# - **No callback matchers.** `callback(:before_save).before(:save)`
|
|
58
|
-
# would need a separate slice (overlaps with the
|
|
59
|
-
# model_index's `callbacks` column already exposed but
|
|
60
|
-
# no rspec-side recogniser yet).
|
|
39
|
+
# - **No chained-matcher arg validation.** The chain options on `validate_length_of(:col).is_at_most(50)`,
|
|
40
|
+
# `validate_inclusion_of(:col).in_array([...])`, `allow_value("foo").for(:col)`, etc. are NOT
|
|
41
|
+
# validated (the `.is_at_most` etc. terminals are runtime-only).
|
|
42
|
+
# - **No polymorphic / through validation.** `belong_to(:user).polymorphic`, `have_many(:posts,
|
|
43
|
+
# through: :memberships)` only check the named association; the chain modifiers are ignored.
|
|
44
|
+
# - **No nested-attribute matchers.** `accept_nested_attributes_for(:posts)` not yet covered.
|
|
45
|
+
# - **No callback matchers.** `callback(:before_save).before(:save)` would need a separate slice
|
|
46
|
+
# (overlaps with the model_index's `callbacks` column already exposed but no rspec-side recogniser yet).
|
|
61
47
|
class ShouldaMatchers < Rigor::Plugin::Base
|
|
62
48
|
manifest(
|
|
63
49
|
id: "shoulda-matchers",
|
|
@@ -70,12 +56,10 @@ module Rigor
|
|
|
70
56
|
]
|
|
71
57
|
)
|
|
72
58
|
|
|
73
|
-
# ADR-37 — per-matcher validation over the engine-owned walk. The
|
|
74
|
-
#
|
|
75
|
-
# the
|
|
76
|
-
#
|
|
77
|
-
# rigor-activerecord) is read lazily via `read_fact`; without it
|
|
78
|
-
# the rule is silent.
|
|
59
|
+
# ADR-37 — per-matcher validation over the engine-owned walk. The model anchor (the enclosing
|
|
60
|
+
# `describe <Model>` const) comes from the node-rule NodeContext ancestors; the diagnostic points at
|
|
61
|
+
# the matcher name (message_loc). The :model_index fact (from rigor-activerecord) is read lazily via
|
|
62
|
+
# `read_fact`; without it the rule is silent.
|
|
79
63
|
node_rule Prism::CallNode do |node, _scope, path, _fc, context|
|
|
80
64
|
index = read_fact(plugin_id: "activerecord", name: :model_index)
|
|
81
65
|
next [] if index.nil?
|
|
@@ -5,43 +5,35 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Plugin
|
|
7
7
|
class Sidekiq < Rigor::Plugin::Base
|
|
8
|
-
# Walks a parsed file's AST looking for
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# `.perform_at(time, ...)` calls and validates each
|
|
12
|
-
# against the {WorkerIndex}.
|
|
8
|
+
# Walks a parsed file's AST looking for `<WorkerClass>.perform_async(...)` / `.perform_inline(...)` /
|
|
9
|
+
# `.perform_in(time, ...)` / `.perform_at(time, ...)` calls and validates each against the
|
|
10
|
+
# {WorkerIndex}.
|
|
13
11
|
#
|
|
14
12
|
# Argument-shape rules:
|
|
15
13
|
#
|
|
16
|
-
# - `perform_async` / `perform_inline` — every
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# `perform_at(time, ...args)` — the FIRST argument
|
|
21
|
-
# is the schedule (a Time / Integer / ActiveSupport
|
|
22
|
-
# duration); the rest are forwarded to `#perform`.
|
|
14
|
+
# - `perform_async` / `perform_inline` — every argument is forwarded to `#perform`. Validate `actual
|
|
15
|
+
# == #perform.arity`.
|
|
16
|
+
# - `perform_in(interval, ...args)` / `perform_at(time, ...args)` — the FIRST argument is the
|
|
17
|
+
# schedule (a Time / Integer / ActiveSupport duration); the rest are forwarded to `#perform`.
|
|
23
18
|
# Validate `actual_args - 1 == #perform.arity`.
|
|
24
19
|
module Analyzer
|
|
25
20
|
# Methods that delegate to `#perform` 1:1.
|
|
26
21
|
DIRECT_ENTRY_METHODS = %i[perform_async perform_inline].freeze
|
|
27
22
|
|
|
28
|
-
# Methods whose first argument is a schedule (the
|
|
29
|
-
# remaining args are forwarded to `#perform`).
|
|
23
|
+
# Methods whose first argument is a schedule (the remaining args are forwarded to `#perform`).
|
|
30
24
|
SCHEDULED_ENTRY_METHODS = %i[perform_in perform_at].freeze
|
|
31
25
|
|
|
32
26
|
ENTRY_METHODS = (DIRECT_ENTRY_METHODS + SCHEDULED_ENTRY_METHODS).freeze
|
|
33
27
|
|
|
34
|
-
# One worker-call observation: the kebab-case `rule`, `severity`,
|
|
35
|
-
#
|
|
36
|
-
# caller (the `node_rule` block) positions it via
|
|
28
|
+
# One worker-call observation: the kebab-case `rule`, `severity`, and human-readable `message`.
|
|
29
|
+
# Carries no path/location — the caller (the `node_rule` block) positions it via
|
|
37
30
|
# `Plugin::Base#diagnostic`.
|
|
38
31
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
39
32
|
|
|
40
33
|
module_function
|
|
41
34
|
|
|
42
|
-
# The worker-call violations for a single call node (0..2), or
|
|
43
|
-
#
|
|
44
|
-
# known worker. ADR-37: the engine owns the walk.
|
|
35
|
+
# The worker-call violations for a single call node (0..2), or `[]` when the node is not a
|
|
36
|
+
# `<Worker>.perform_*` entry call on a known worker. ADR-37: the engine owns the walk.
|
|
45
37
|
#
|
|
46
38
|
# @param call_node [Prism::Node]
|
|
47
39
|
# @param worker_index [WorkerIndex]
|
|
@@ -77,8 +69,7 @@ module Rigor
|
|
|
77
69
|
|
|
78
70
|
def arity_violation(call_node, entry)
|
|
79
71
|
all_args = (call_node.arguments&.arguments || []).size
|
|
80
|
-
# Scheduled entries consume the first arg as the
|
|
81
|
-
# schedule; the rest are forwarded.
|
|
72
|
+
# Scheduled entries consume the first arg as the schedule; the rest are forwarded.
|
|
82
73
|
forwarded_count = SCHEDULED_ENTRY_METHODS.include?(call_node.name) ? all_args - 1 : all_args
|
|
83
74
|
|
|
84
75
|
if SCHEDULED_ENTRY_METHODS.include?(call_node.name) && all_args.zero?
|
|
@@ -7,28 +7,20 @@ require_relative "worker_index"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Sidekiq < Rigor::Plugin::Base
|
|
10
|
-
# Walks the configured worker-search paths via the
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# modules). For each discovered class, the discoverer
|
|
15
|
-
# also reads the `#perform` method's parameter list
|
|
16
|
-
# and computes the arity envelope.
|
|
10
|
+
# Walks the configured worker-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
|
|
11
|
+
# Prism, and collects classes that `include Sidekiq::Job` (or one of the configured marker modules).
|
|
12
|
+
# For each discovered class, the discoverer also reads the `#perform` method's parameter list and
|
|
13
|
+
# computes the arity envelope.
|
|
17
14
|
#
|
|
18
15
|
# Limitations (intentional for v0.1.0):
|
|
19
16
|
#
|
|
20
|
-
# - Only direct `include` matches against the
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# (`Admin::WelcomeWorker` for a class declared
|
|
28
|
-
# inside `module Admin`).
|
|
29
|
-
# - `#perform` arity is read from the syntactic
|
|
30
|
-
# parameter list. Methods built via
|
|
31
|
-
# `define_method` are out of scope.
|
|
17
|
+
# - Only direct `include` matches against the configured marker modules. `class MyWorker; include
|
|
18
|
+
# Concerns::Sidekiqable; end` where `Concerns::Sidekiqable` re-includes `Sidekiq::Job` is NOT
|
|
19
|
+
# discovered. Add the intermediate module to `worker_marker_modules` if needed.
|
|
20
|
+
# - The qualified class name is the lexical path (`Admin::WelcomeWorker` for a class declared inside
|
|
21
|
+
# `module Admin`).
|
|
22
|
+
# - `#perform` arity is read from the syntactic parameter list. Methods built via `define_method`
|
|
23
|
+
# are out of scope.
|
|
32
24
|
class WorkerDiscoverer
|
|
33
25
|
def initialize(io_boundary:, search_paths:, marker_modules:)
|
|
34
26
|
@io_boundary = io_boundary
|
|
@@ -101,9 +93,8 @@ module Rigor
|
|
|
101
93
|
walk_for_workers(node.body, inner_path, &) if node.body
|
|
102
94
|
end
|
|
103
95
|
|
|
104
|
-
# Returns true if the class body contains a top-level
|
|
105
|
-
#
|
|
106
|
-
# one of the configured marker modules.
|
|
96
|
+
# Returns true if the class body contains a top-level `include <Module>` call where `<Module>`
|
|
97
|
+
# matches one of the configured marker modules.
|
|
107
98
|
def includes_marker_module?(body)
|
|
108
99
|
return false if body.nil?
|
|
109
100
|
|
|
@@ -137,9 +128,8 @@ module Rigor
|
|
|
137
128
|
end
|
|
138
129
|
end
|
|
139
130
|
|
|
140
|
-
# Returns the instance-side `def perform(...)` node
|
|
141
|
-
#
|
|
142
|
-
# override `#perform`.
|
|
131
|
+
# Returns the instance-side `def perform(...)` node from a class body, or `nil` when the class
|
|
132
|
+
# doesn't override `#perform`.
|
|
143
133
|
def lookup_perform_def(body)
|
|
144
134
|
return nil if body.nil?
|
|
145
135
|
|
|
@@ -152,12 +142,9 @@ module Rigor
|
|
|
152
142
|
nil
|
|
153
143
|
end
|
|
154
144
|
|
|
155
|
-
# Builds a `WorkerIndex::Entry` from the discovered
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
# entry — Sidekiq itself doesn't supply a default
|
|
159
|
-
# `#perform`, so calling `perform_async` on a
|
|
160
|
-
# worker without one is the user's bug, not the
|
|
145
|
+
# Builds a `WorkerIndex::Entry` from the discovered class's `#perform` def. When the class doesn't
|
|
146
|
+
# override `#perform`, we record an "any-arity" entry — Sidekiq itself doesn't supply a default
|
|
147
|
+
# `#perform`, so calling `perform_async` on a worker without one is the user's bug, not the
|
|
161
148
|
# plugin's call to flag without runtime context.
|
|
162
149
|
def build_entry(class_name, perform_def)
|
|
163
150
|
if perform_def.nil?
|
|
@@ -3,17 +3,13 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Sidekiq < Rigor::Plugin::Base
|
|
6
|
-
# Frozen catalogue of discovered Sidekiq worker
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# analyzer can validate `Worker.perform_async(...)`
|
|
10
|
-
# call sites.
|
|
6
|
+
# Frozen catalogue of discovered Sidekiq worker classes keyed by qualified class name. Each entry
|
|
7
|
+
# holds the `#perform` method's arity envelope so the analyzer can validate
|
|
8
|
+
# `Worker.perform_async(...)` call sites.
|
|
11
9
|
#
|
|
12
|
-
# Uses the same `min_arity` / `max_arity` closed-range
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# Sidekiq workers serialize args to JSON so keyword arity
|
|
16
|
-
# is not tracked here.
|
|
10
|
+
# Uses the same `min_arity` / `max_arity` closed-range envelope as `rigor-activejob`'s
|
|
11
|
+
# `JobIndex::Entry` (`Float::INFINITY` upper bound when `*args` is present); Sidekiq workers
|
|
12
|
+
# serialize args to JSON so keyword arity is not tracked here.
|
|
17
13
|
class WorkerIndex
|
|
18
14
|
Entry = Data.define(:class_name, :min_arity, :max_arity) do
|
|
19
15
|
def arity_label
|
|
@@ -8,14 +8,11 @@ require_relative "sidekiq/analyzer"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
|
-
# rigor-sidekiq — validates `Worker.perform_async(...)`
|
|
12
|
-
#
|
|
13
|
-
# `.perform_inline(...)` argument arity against the
|
|
14
|
-
# discovered `#perform` definitions.
|
|
11
|
+
# rigor-sidekiq — validates `Worker.perform_async(...)` / `.perform_in(...)` / `.perform_at(...)` /
|
|
12
|
+
# `.perform_inline(...)` argument arity against the discovered `#perform` definitions.
|
|
15
13
|
#
|
|
16
14
|
# Tier 3C of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
|
-
# Statically discovers Sidekiq workers by walking
|
|
18
|
-
# `worker_search_paths` and parsing each file with
|
|
15
|
+
# Statically discovers Sidekiq workers by walking `worker_search_paths` and parsing each file with
|
|
19
16
|
# Prism — no `sidekiq` runtime dependency.
|
|
20
17
|
#
|
|
21
18
|
# ## Configuration
|
|
@@ -28,30 +25,19 @@ module Rigor
|
|
|
28
25
|
#
|
|
29
26
|
# ## What it checks
|
|
30
27
|
#
|
|
31
|
-
# 1. **Argument arity** — `perform_async(args)` /
|
|
32
|
-
# `
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
# as the schedule and forward the rest. Mismatches
|
|
36
|
-
# emit `wrong-arity`.
|
|
37
|
-
# 2. **Missing schedule** — `perform_in()` /
|
|
38
|
-
# `perform_at()` with zero arguments emit
|
|
39
|
-
# `missing-schedule`.
|
|
28
|
+
# 1. **Argument arity** — `perform_async(args)` / `perform_inline(args)` forward every argument to
|
|
29
|
+
# `#perform`; `perform_in(t, args)` / `perform_at(t, args)` consume the first argument as the
|
|
30
|
+
# schedule and forward the rest. Mismatches emit `wrong-arity`.
|
|
31
|
+
# 2. **Missing schedule** — `perform_in()` / `perform_at()` with zero arguments emit `missing-schedule`.
|
|
40
32
|
#
|
|
41
33
|
# ## Limitations (v0.1.0)
|
|
42
34
|
#
|
|
43
|
-
# - Direct `include` matches only against the
|
|
44
|
-
# configured marker modules. Indirect includes via a
|
|
35
|
+
# - Direct `include` matches only against the configured marker modules. Indirect includes via a
|
|
45
36
|
# custom concern are out of scope.
|
|
46
|
-
# - `#perform` arity is read from the syntactic
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# -
|
|
50
|
-
# the call site (positional-only for v0.1.0). Sidekiq
|
|
51
|
-
# serialises arguments to JSON, so keyword args are
|
|
52
|
-
# uncommon in practice.
|
|
53
|
-
# - The schedule argument's type isn't validated (no
|
|
54
|
-
# "is this a Time?" check); we just consume it.
|
|
37
|
+
# - `#perform` arity is read from the syntactic parameter list. `define_method` actions are out of scope.
|
|
38
|
+
# - Required keyword arguments are not validated at the call site (positional-only for v0.1.0).
|
|
39
|
+
# Sidekiq serialises arguments to JSON, so keyword args are uncommon in practice.
|
|
40
|
+
# - The schedule argument's type isn't validated (no "is this a Time?" check); we just consume it.
|
|
55
41
|
class Sidekiq < Rigor::Plugin::Base
|
|
56
42
|
manifest(
|
|
57
43
|
id: "sidekiq",
|
|
@@ -76,9 +62,8 @@ module Rigor
|
|
|
76
62
|
@worker_marker_modules = Array(config.fetch("worker_marker_modules")).map(&:to_s)
|
|
77
63
|
end
|
|
78
64
|
|
|
79
|
-
# File-level only: the load-error emission. The per-call arity
|
|
80
|
-
#
|
|
81
|
-
# below (ADR-37). The worker index is lazily loaded + memoised by
|
|
65
|
+
# File-level only: the load-error emission. The per-call arity validation runs over the engine-owned
|
|
66
|
+
# walk via the node_rule below (ADR-37). The worker index is lazily loaded + memoised by
|
|
82
67
|
# `producer_value`, shared by both surfaces.
|
|
83
68
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
84
69
|
index = producer_value(:worker_index)
|