rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Actionpack < Rigor::Plugin::Base
|
|
6
|
-
# Per-run frozen index of discovered controllers AND
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# parent (one level of inheritance), or any module the
|
|
12
|
-
# controller or its ancestor chain transitively `include`s.
|
|
6
|
+
# Per-run frozen index of discovered controllers AND concerns (modules) in
|
|
7
|
+
# `controller_search_paths`. Phase 2 (filter-chain validation) consults the index at every call
|
|
8
|
+
# site to check that `before_action :name` references a method that actually exists on the
|
|
9
|
+
# controller, its parent (one level of inheritance), or any module the controller or its ancestor
|
|
10
|
+
# chain transitively `include`s.
|
|
13
11
|
#
|
|
14
12
|
# Real-world example (Mastodon):
|
|
15
13
|
# AccountsController < ApplicationController
|
|
@@ -19,23 +17,17 @@ module Rigor
|
|
|
19
17
|
# SignatureVerification < module
|
|
20
18
|
# def require_account_signature! # ← the filter target
|
|
21
19
|
#
|
|
22
|
-
# The include chain spans three concern modules.
|
|
23
|
-
#
|
|
24
|
-
# of them transitively to collect `require_account_signature!`.
|
|
20
|
+
# The include chain spans three concern modules. `effective_methods_for("AccountsController")`
|
|
21
|
+
# walks all of them transitively to collect `require_account_signature!`.
|
|
25
22
|
class ControllerIndex
|
|
26
|
-
# `defined_methods` carries the discovered method names
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# `module Admin; class Foo; end; end`). Used by the
|
|
35
|
-
# index's parent / module lookup to try lexically-scoped
|
|
36
|
-
# constant resolution before falling through to the
|
|
37
|
-
# top-level form — Ruby's constant lookup walks the
|
|
38
|
-
# enclosing scope chain before `Object`.
|
|
23
|
+
# `defined_methods` carries the discovered method names (Symbols). `included_module_names`
|
|
24
|
+
# carries the constant names passed to `include X` calls inside the class / module body
|
|
25
|
+
# (Strings). `parent_class_name` is the immediate superclass (nil for plain modules).
|
|
26
|
+
# `enclosing_namespace` is the qualifier chain of the declaration's *lexical* enclosing scope
|
|
27
|
+
# (e.g. `["Admin"]` for an `Admin::Foo` declared via `module Admin; class Foo; end; end`). Used
|
|
28
|
+
# by the index's parent / module lookup to try lexically-scoped constant resolution before
|
|
29
|
+
# falling through to the top-level form — Ruby's constant lookup walks the enclosing scope chain
|
|
30
|
+
# before `Object`.
|
|
39
31
|
Entry = Data.define(:class_name, :defined_methods, :parent_class_name, :included_module_names,
|
|
40
32
|
:enclosing_namespace)
|
|
41
33
|
|
|
@@ -51,20 +43,15 @@ module Rigor
|
|
|
51
43
|
@entries[class_name]
|
|
52
44
|
end
|
|
53
45
|
|
|
54
|
-
# Resolves the **effective** method set for a controller,
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# methods inherited from the ancestor chain (unbounded
|
|
58
|
-
# depth, cycle-safe via a visited set).
|
|
46
|
+
# Resolves the **effective** method set for a controller, including methods contributed by
|
|
47
|
+
# every module the controller / any ancestor transitively `include`s AND methods inherited from
|
|
48
|
+
# the ancestor chain (unbounded depth, cycle-safe via a visited set).
|
|
59
49
|
#
|
|
60
|
-
# Parent / module lookups are **lexically scoped** — a
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# `Admin::Foo < BaseController` declaration would
|
|
66
|
-
# incorrectly walk the top-level `BaseController` even
|
|
67
|
-
# when an `Admin::BaseController` exists.
|
|
50
|
+
# Parent / module lookups are **lexically scoped** — a bare `BaseController` reference inside
|
|
51
|
+
# `module Admin` first tries `Admin::BaseController`, then falls through to the top-level
|
|
52
|
+
# `BaseController`. Matches Ruby's constant-resolution semantics. Without this an `Admin::Foo <
|
|
53
|
+
# BaseController` declaration would incorrectly walk the top-level `BaseController` even when an
|
|
54
|
+
# `Admin::BaseController` exists.
|
|
68
55
|
def effective_methods_for(class_name)
|
|
69
56
|
seen_classes = {}
|
|
70
57
|
seen_modules = {}
|
|
@@ -81,11 +68,9 @@ module Rigor
|
|
|
81
68
|
collect_methods(resolved_include, seen_modules, methods)
|
|
82
69
|
end
|
|
83
70
|
next_parent = entry.parent_class_name
|
|
84
|
-
# Same self-reference guard as `unresolved_include?`:
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# `ApplicationController` to itself; fall back to
|
|
88
|
-
# the unprefixed top-level name in that case.
|
|
71
|
+
# Same self-reference guard as `unresolved_include?`: `class ActivityPub::ApplicationController
|
|
72
|
+
# < ::ApplicationController` lexically resolves `ApplicationController` to itself; fall back
|
|
73
|
+
# to the unprefixed top-level name in that case.
|
|
89
74
|
current = if next_parent
|
|
90
75
|
resolved = resolve_constant_lexically(next_parent, entry.enclosing_namespace)
|
|
91
76
|
resolved == current ? next_parent.sub(/\A::/, "") : resolved
|
|
@@ -94,30 +79,23 @@ module Rigor
|
|
|
94
79
|
methods.uniq.freeze
|
|
95
80
|
end
|
|
96
81
|
|
|
97
|
-
# @return [Boolean] true when the class has at least one
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# `Devise::
|
|
101
|
-
#
|
|
102
|
-
# or
|
|
103
|
-
#
|
|
104
|
-
# to silence — the unresolved module / parent may
|
|
105
|
-
# legitimately contribute the filter (either directly,
|
|
106
|
-
# or via its own ancestor chain which the static
|
|
107
|
-
# analyzer cannot follow), and there's no way to verify.
|
|
82
|
+
# @return [Boolean] true when the class has at least one include OR parent class we couldn't
|
|
83
|
+
# resolve in the index (typically a gem-shipped concern such as Devise's
|
|
84
|
+
# `Devise::Controllers::Helpers`, or a gem-shipped parent controller such as
|
|
85
|
+
# `Devise::ConfirmationsController` or `Doorkeeper::AuthorizedApplicationsController`). Phase
|
|
86
|
+
# 2 uses this to downgrade `unknown-filter-method` to silence — the unresolved module / parent
|
|
87
|
+
# may legitimately contribute the filter (either directly, or via its own ancestor chain which
|
|
88
|
+
# the static analyzer cannot follow), and there's no way to verify.
|
|
108
89
|
def unresolved_include?(class_name)
|
|
109
90
|
entry = @entries[class_name]
|
|
110
91
|
return false if entry.nil?
|
|
111
92
|
|
|
112
|
-
# Walk the full ancestor chain. `Admin::Foo <
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# a
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# `current_entry` on a subsequent iteration means the
|
|
119
|
-
# AST-side `< Parent` reached a gem-shipped class
|
|
120
|
-
# whose ancestor methods are invisible to us.
|
|
93
|
+
# Walk the full ancestor chain. `Admin::Foo < BaseController < ApplicationController` should
|
|
94
|
+
# report an unresolved include if ANY of the three references a gem-shipped concern OR a
|
|
95
|
+
# gem-shipped parent class we cannot index. The first iteration's `current_entry` is always
|
|
96
|
+
# resolved (caller verified `known?`); a nil `current_entry` on a subsequent iteration means
|
|
97
|
+
# the AST-side `< Parent` reached a gem-shipped class whose ancestor methods are invisible to
|
|
98
|
+
# us.
|
|
121
99
|
seen_classes = {}
|
|
122
100
|
current = class_name
|
|
123
101
|
first = true
|
|
@@ -136,15 +114,11 @@ module Rigor
|
|
|
136
114
|
return true if resolved.nil? || !@entries.key?(resolved)
|
|
137
115
|
end
|
|
138
116
|
next_parent = current_entry.parent_class_name
|
|
139
|
-
# Avoid lexically resolving to ourselves. `class
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
# the lexical
|
|
143
|
-
#
|
|
144
|
-
# circuit the walk before reaching the top-level
|
|
145
|
-
# parent. When the lexical match is the current
|
|
146
|
-
# class itself, fall back to the unprefixed
|
|
147
|
-
# top-level name.
|
|
117
|
+
# Avoid lexically resolving to ourselves. `class ActivityPub::ApplicationController <
|
|
118
|
+
# ::ApplicationController` would otherwise resolve `ApplicationController` (in the lexical
|
|
119
|
+
# scope `["ActivityPub"]`) to `ActivityPub::ApplicationController` and short-circuit the walk
|
|
120
|
+
# before reaching the top-level parent. When the lexical match is the current class itself,
|
|
121
|
+
# fall back to the unprefixed top-level name.
|
|
148
122
|
current = if next_parent
|
|
149
123
|
resolved = resolve_constant_lexically(next_parent, current_entry.enclosing_namespace)
|
|
150
124
|
resolved == current ? next_parent.sub(/\A::/, "") : resolved
|
|
@@ -181,37 +155,28 @@ module Rigor
|
|
|
181
155
|
end
|
|
182
156
|
end
|
|
183
157
|
|
|
184
|
-
# Ruby's constant-lookup walk: a bare constant name
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
# the
|
|
189
|
-
#
|
|
190
|
-
# candidate that has an entry in the index. When
|
|
191
|
-
# nothing resolves, return the original name
|
|
192
|
-
# unchanged — the caller treats unresolved entries as
|
|
193
|
-
# "gem-shipped concerns we cannot see" (the
|
|
194
|
-
# `unresolved_include?` predicate).
|
|
158
|
+
# Ruby's constant-lookup walk: a bare constant name inside `module Admin` first tries
|
|
159
|
+
# `Admin::Const`, then walks outward, and finally falls through to top-level `Const`. We
|
|
160
|
+
# approximate that by trying the candidate `enclosing + name` chains from deepest to shallowest,
|
|
161
|
+
# and returning the first candidate that has an entry in the index. When nothing resolves,
|
|
162
|
+
# return the original name unchanged — the caller treats unresolved entries as "gem-shipped
|
|
163
|
+
# concerns we cannot see" (the `unresolved_include?` predicate).
|
|
195
164
|
#
|
|
196
|
-
# `name` may already be qualified (`Foo::Bar`); we
|
|
197
|
-
#
|
|
198
|
-
# first segment doesn't match a top-level entry.
|
|
165
|
+
# `name` may already be qualified (`Foo::Bar`); we only try lexical prefixing when the
|
|
166
|
+
# unqualified first segment doesn't match a top-level entry.
|
|
199
167
|
def resolve_constant_lexically(name, enclosing)
|
|
200
168
|
return nil if name.nil?
|
|
201
169
|
|
|
202
|
-
# A leading `::` denotes the top-level constant
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
# `class ActivityPub::ApplicationController <
|
|
207
|
-
# ::ApplicationController` parent never resolved.
|
|
170
|
+
# A leading `::` denotes the top-level constant explicitly (`< ::ApplicationController`).
|
|
171
|
+
# Strip it for index lookup — the discoverer registers entries under their unprefixed name.
|
|
172
|
+
# Without this strip a `class ActivityPub::ApplicationController < ::ApplicationController`
|
|
173
|
+
# parent never resolved.
|
|
208
174
|
name = name.sub(/\A::/, "")
|
|
209
175
|
|
|
210
176
|
# Constant already absolute or no enclosing scope.
|
|
211
177
|
return name if enclosing.nil? || enclosing.empty?
|
|
212
178
|
|
|
213
|
-
# Try the deepest enclosing scope first, walking
|
|
214
|
-
# outward. `enclosing = ["A", "B"]` produces
|
|
179
|
+
# Try the deepest enclosing scope first, walking outward. `enclosing = ["A", "B"]` produces
|
|
215
180
|
# candidates `["A::B::name", "A::name", "name"]`.
|
|
216
181
|
enclosing.length.downto(0).each do |depth|
|
|
217
182
|
prefix = enclosing[0, depth]
|
|
@@ -8,20 +8,15 @@ require_relative "actionpack/controller_index"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
|
-
# rigor-actionpack — validates Action Pack DSL calls in
|
|
12
|
-
# controller files.
|
|
11
|
+
# rigor-actionpack — validates Action Pack DSL calls in controller files.
|
|
13
12
|
#
|
|
14
|
-
# **Phase 4 of the Action Pack plugin family** (route-helper
|
|
15
|
-
#
|
|
16
|
-
# `
|
|
17
|
-
# every implicit-self `*_path` / `*_url` call inside files
|
|
18
|
-
# under `controller_search_paths` (default `app/controllers`).
|
|
13
|
+
# **Phase 4 of the Action Pack plugin family** (route-helper consumption). Reads the `:helper_table`
|
|
14
|
+
# fact published by `rigor-rails-routes` (ADR-9 cross-plugin API) and validates every implicit-self
|
|
15
|
+
# `*_path` / `*_url` call inside files under `controller_search_paths` (default `app/controllers`).
|
|
19
16
|
#
|
|
20
17
|
# Tier 2 of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
21
|
-
# Phase 1 (strong-parameters → AR column validation), Phase 2
|
|
22
|
-
#
|
|
23
|
-
# separate slices; each phase composes additively under the
|
|
24
|
-
# same plugin id.
|
|
18
|
+
# Phase 1 (strong-parameters → AR column validation), Phase 2 (filter chains), and Phase 3 (render
|
|
19
|
+
# targets) ship as separate slices; each phase composes additively under the same plugin id.
|
|
25
20
|
#
|
|
26
21
|
# ## Configuration
|
|
27
22
|
#
|
|
@@ -36,46 +31,35 @@ module Rigor
|
|
|
36
31
|
#
|
|
37
32
|
# ## What it checks
|
|
38
33
|
#
|
|
39
|
-
# - **Helper existence** — every `*_path` / `*_url` call
|
|
40
|
-
#
|
|
41
|
-
# table.
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# is excluded; same convention `rigor-rails-routes` uses).
|
|
47
|
-
# Mismatches emit `wrong-helper-arity`.
|
|
48
|
-
# - **Trace** — recognised helpers also emit a
|
|
49
|
-
# `helper-call` info diagnostic naming the action and
|
|
34
|
+
# - **Helper existence** — every `*_path` / `*_url` call inside a controller file is looked up in
|
|
35
|
+
# the helper table. Missing entries emit `unknown-helper` with a `DidYouMean` suggestion drawn
|
|
36
|
+
# from the table.
|
|
37
|
+
# - **Helper arity** — the call's positional-argument count is matched against the helper's
|
|
38
|
+
# recorded arity (a trailing `KeywordHashNode` like `users_path(format: :json)` is excluded; same
|
|
39
|
+
# convention `rigor-rails-routes` uses). Mismatches emit `wrong-helper-arity`.
|
|
40
|
+
# - **Trace** — recognised helpers also emit a `helper-call` info diagnostic naming the action and
|
|
50
41
|
# path, mirroring the trace shape of the upstream plugin.
|
|
51
42
|
#
|
|
52
43
|
# ## Limitations
|
|
53
44
|
#
|
|
54
|
-
# - Implicit-self calls only. `Rails.application.routes.url_helpers.users_path`
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# -
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# - When `rigor-rails-routes` is not installed (or its
|
|
64
|
-
# helper table is empty), Phase 4 silently degrades to a
|
|
65
|
-
# no-op. No load-error diagnostic is emitted; the user
|
|
66
|
-
# gets the "no checks happened" failure mode rather than
|
|
67
|
-
# a wall of "is this configured right?" warnings.
|
|
45
|
+
# - Implicit-self calls only. `Rails.application.routes.url_helpers.users_path` and other
|
|
46
|
+
# explicit-receiver shapes are passed through; they're rare in controller code and the helper
|
|
47
|
+
# table doesn't include any extra context to validate them.
|
|
48
|
+
# - Files outside `controller_search_paths` are skipped. The plugin doesn't try to detect "is this
|
|
49
|
+
# a controller?" by class hierarchy — Phase 1's strong-parameters work needs that, so it lives
|
|
50
|
+
# there. Phase 4's job is the single-purpose helper check.
|
|
51
|
+
# - When `rigor-rails-routes` is not installed (or its helper table is empty), Phase 4 silently
|
|
52
|
+
# degrades to a no-op. No load-error diagnostic is emitted; the user gets the "no checks
|
|
53
|
+
# happened" failure mode rather than a wall of "is this configured right?" warnings.
|
|
68
54
|
class Actionpack < Rigor::Plugin::Base
|
|
69
55
|
manifest(
|
|
70
56
|
id: "actionpack",
|
|
71
|
-
# ADR-37: the four phases (helper / filter / render /
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# `
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# `ControllerDiscoverer`), so render paths and filter-chain
|
|
78
|
-
# validation on nested controllers are correct.
|
|
57
|
+
# ADR-37: the four phases (helper / filter / render / strong-params) run per-call over the
|
|
58
|
+
# engine-owned walk; the enclosing controller is read from the node-rule `NodeContext`
|
|
59
|
+
# ancestors. Nested-module qualification is preserved — `module Admin; class
|
|
60
|
+
# DomainBlocksController` resolves as `Admin::DomainBlocksController` (matching the
|
|
61
|
+
# `ControllerDiscoverer`), so render paths and filter-chain validation on nested controllers are
|
|
62
|
+
# correct.
|
|
79
63
|
version: "0.9.0",
|
|
80
64
|
description: "Validates Action Pack route-helper calls and filter chains inside controllers, and types the request-context readers (`params` / `session` / `request`).",
|
|
81
65
|
config_schema: {
|
|
@@ -88,12 +72,10 @@ module Rigor
|
|
|
88
72
|
]
|
|
89
73
|
)
|
|
90
74
|
|
|
91
|
-
# Phase 2 cached producer — the controller index built from
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# in-block `io_boundary` reads are captured into the dependency
|
|
96
|
-
# descriptor too, so no explicit priming is needed.
|
|
75
|
+
# Phase 2 cached producer — the controller index built from `controller_search_paths`. `watch:`
|
|
76
|
+
# (ADR-60 WD3) covers every `.rb` file under those roots so the cache invalidates when a
|
|
77
|
+
# controller is added, removed, or edited; the discoverer's in-block `io_boundary` reads are
|
|
78
|
+
# captured into the dependency descriptor too, so no explicit priming is needed.
|
|
97
79
|
producer :controller_index, watch: -> { [[@controller_search_paths, "**/*.rb"]] } do |_params|
|
|
98
80
|
ControllerDiscoverer.new(
|
|
99
81
|
io_boundary: io_boundary,
|
|
@@ -106,18 +88,15 @@ module Rigor
|
|
|
106
88
|
@view_search_paths = Array(config.fetch("view_search_paths")).map(&:to_s)
|
|
107
89
|
end
|
|
108
90
|
|
|
109
|
-
# ADR-37 — the four Action Pack phases run per-call over the
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
# Phase 4 — route-helper consumption. `:helper_table` is
|
|
119
|
-
# rigor-rails-routes's published fact (ADR-9), read lazily via
|
|
120
|
-
# `read_fact`.
|
|
91
|
+
# ADR-37 — the four Action Pack phases run per-call over the engine-owned walk. Each rule gates on
|
|
92
|
+
# `controller_file?(path)` (the plugin only validates files under `controller_search_paths`,
|
|
93
|
+
# exactly as the former `diagnostics_for_file` top-level guard did), then delegates to a per-node
|
|
94
|
+
# `Analyzer.*_violations_for` and positions each location-free `Violation` with `Base#diagnostic`.
|
|
95
|
+
# The filter / render phases read the enclosing controller from the node-rule `NodeContext`
|
|
96
|
+
# ancestors (its fifth block argument).
|
|
97
|
+
|
|
98
|
+
# Phase 4 — route-helper consumption. `:helper_table` is rigor-rails-routes's published fact
|
|
99
|
+
# (ADR-9), read lazily via `read_fact`.
|
|
121
100
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
122
101
|
next [] unless controller_file?(path)
|
|
123
102
|
|
|
@@ -127,9 +106,8 @@ module Rigor
|
|
|
127
106
|
diagnostics_for(Analyzer.helper_violations_for(call_node: node, helper_table: table), path: path, node: node)
|
|
128
107
|
end
|
|
129
108
|
|
|
130
|
-
# Phase 2 — filter-chain validation. Skips silently when the
|
|
131
|
-
#
|
|
132
|
-
# class.
|
|
109
|
+
# Phase 2 — filter-chain validation. Skips silently when the controller index is absent or
|
|
110
|
+
# doesn't recognise the enclosing class.
|
|
133
111
|
node_rule Prism::CallNode do |node, _scope, path, _fc, context|
|
|
134
112
|
next [] unless controller_file?(path)
|
|
135
113
|
|
|
@@ -142,11 +120,9 @@ module Rigor
|
|
|
142
120
|
)
|
|
143
121
|
end
|
|
144
122
|
|
|
145
|
-
# Phase 3 — render-target validation against the configured
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
# needed; the controller index is consulted only to suppress
|
|
149
|
-
# gem-shipped-view false positives.
|
|
123
|
+
# Phase 3 — render-target validation against the configured `view_search_paths`. Recognised purely
|
|
124
|
+
# from the call site + the enclosing controller name, so no per-controller pre-discovery is
|
|
125
|
+
# needed; the controller index is consulted only to suppress gem-shipped-view false positives.
|
|
150
126
|
node_rule Prism::CallNode do |node, _scope, path, _fc, context|
|
|
151
127
|
next [] unless controller_file?(path)
|
|
152
128
|
|
|
@@ -159,11 +135,9 @@ module Rigor
|
|
|
159
135
|
)
|
|
160
136
|
end
|
|
161
137
|
|
|
162
|
-
# Phase 1 — strong-parameter validation. Reads the `:model_index`
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
# `params.require(:user).permit(:name, :email)` chain against the
|
|
166
|
-
# User model's column list.
|
|
138
|
+
# Phase 1 — strong-parameter validation. Reads the `:model_index` fact from the cross-plugin fact
|
|
139
|
+
# store (published by rigor-activerecord) and validates every `params.require(:user).permit(:name,
|
|
140
|
+
# :email)` chain against the User model's column list.
|
|
167
141
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
168
142
|
next [] unless controller_file?(path)
|
|
169
143
|
|
|
@@ -173,28 +147,21 @@ module Rigor
|
|
|
173
147
|
diagnostics_for(Analyzer.permit_violations_for(call_node: node, model_index: index), path: path, node: node)
|
|
174
148
|
end
|
|
175
149
|
|
|
176
|
-
# Phase 5 (2026-07-04) — type the implicit-self request-context
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
# typing-
|
|
180
|
-
# (
|
|
181
|
-
#
|
|
182
|
-
# largest protection-coverage hole on real Rails apps: `params[:x]`
|
|
183
|
-
# is the #1 dispatch cluster (redmine app+lib: `[]` 2378 sites) and
|
|
184
|
-
# `session[:x] =` a large share of the `[]=` cluster, all
|
|
185
|
-
# unprotected because the receiver is Dynamic.
|
|
150
|
+
# Phase 5 (2026-07-04) — type the implicit-self request-context readers (`params`, `session`,
|
|
151
|
+
# `request`, `flash`, `cookies`) inside controllers. The typing-obstacle probe
|
|
152
|
+
# (docs/notes/20260704-rails-coverage-onboarding-carrier-trap.md, obstacle O3) found `params`
|
|
153
|
+
# typing to `Dynamic[top]` the single largest protection-coverage hole on real Rails apps:
|
|
154
|
+
# `params[:x]` is the #1 dispatch cluster (redmine app+lib: `[]` 2378 sites) and `session[:x] =` a
|
|
155
|
+
# large share of the `[]=` cluster, all unprotected because the receiver is Dynamic.
|
|
186
156
|
#
|
|
187
|
-
# Each returns a bare nominal with NO bundled RBS on purpose. That
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
# `
|
|
194
|
-
#
|
|
195
|
-
# trap (a declared class drops every member the RBS omits → false
|
|
196
|
-
# `undefined-method`). ADR-5: this types the container, never the
|
|
197
|
-
# caller's argument, so the values stay lenient.
|
|
157
|
+
# Each returns a bare nominal with NO bundled RBS on purpose. That makes the reader a *concrete*
|
|
158
|
+
# receiver (so `coverage --protection` counts the site as protected and the dispatch resolves
|
|
159
|
+
# against a named class) while its method surface stays engine-lenient — Rigor does not fire
|
|
160
|
+
# `undefined-method` on a class it has no RBS for, so `params.require(...).permit(...)`,
|
|
161
|
+
# `session.delete(:x)`, `request.xhr?`, and every other method on these stay FP-safe. Shipping a
|
|
162
|
+
# partial RBS would re-introduce the carrier-additivity trap (a declared class drops every member
|
|
163
|
+
# the RBS omits → false `undefined-method`). ADR-5: this types the container, never the caller's
|
|
164
|
+
# argument, so the values stay lenient.
|
|
198
165
|
REQUEST_CONTEXT_READER_TYPES = {
|
|
199
166
|
params: "ActionController::Parameters",
|
|
200
167
|
session: "ActionDispatch::Request::Session",
|
|
@@ -217,12 +184,10 @@ module Rigor
|
|
|
217
184
|
|
|
218
185
|
private
|
|
219
186
|
|
|
220
|
-
# True when the current `self` is a controller — the enclosing
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
# Typing `params` is precision-additive, so the name-convention
|
|
225
|
-
# fallback is FP-safe.
|
|
187
|
+
# True when the current `self` is a controller — the enclosing class is one the discoverer
|
|
188
|
+
# indexed, or its name follows the Rails `*Controller` convention (covering controllers outside
|
|
189
|
+
# `controller_search_paths`, e.g. one shipped by an engine). Typing `params` is precision-additive,
|
|
190
|
+
# so the name-convention fallback is FP-safe.
|
|
226
191
|
def controller_scope?(scope)
|
|
227
192
|
self_type = scope&.self_type
|
|
228
193
|
return false unless self_type.respond_to?(:class_name)
|
|
@@ -238,12 +203,10 @@ module Rigor
|
|
|
238
203
|
|
|
239
204
|
def controller_file?(path)
|
|
240
205
|
@controller_search_paths.any? do |root|
|
|
241
|
-
# The runner may pass `path` as either an absolute
|
|
242
|
-
#
|
|
243
|
-
# relative
|
|
244
|
-
#
|
|
245
|
-
# root-relative. Match the configured root as a
|
|
246
|
-
# /-bracketed substring so both shapes resolve.
|
|
206
|
+
# The runner may pass `path` as either an absolute path (when `paths:` was configured
|
|
207
|
+
# absolutely) or a relative one (when configured relatively). The `controller_search_paths`
|
|
208
|
+
# knob is always project-root-relative. Match the configured root as a /-bracketed substring
|
|
209
|
+
# so both shapes resolve.
|
|
247
210
|
path.include?("/#{root}/") || path.start_with?("#{root}/") || path == root
|
|
248
211
|
end
|
|
249
212
|
end
|
|
@@ -5,34 +5,26 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Plugin
|
|
7
7
|
class Activejob < Rigor::Plugin::Base
|
|
8
|
-
# Walks a parsed file's AST looking for
|
|
9
|
-
#
|
|
10
|
-
# `.perform_now(...)` / `.perform(...)` calls and
|
|
11
|
-
# validates each against the {JobIndex}.
|
|
8
|
+
# Walks a parsed file's AST looking for `<JobClass>.perform_later(...)` / `.perform_now(...)` /
|
|
9
|
+
# `.perform(...)` calls and validates each against the {JobIndex}.
|
|
12
10
|
#
|
|
13
|
-
# The plugin recognises a call as job-shaped when the
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# method name is one of the three ActiveJob entry
|
|
17
|
-
# points.
|
|
11
|
+
# The plugin recognises a call as job-shaped when the receiver is a `ConstantReadNode` /
|
|
12
|
+
# `ConstantPathNode` whose resolved name appears in the index, and the method name is one of the
|
|
13
|
+
# three ActiveJob entry points.
|
|
18
14
|
module Analyzer
|
|
19
|
-
# Methods that delegate to the job's `#perform`. All
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# `perform_now` runs synchronously, and `perform` is
|
|
23
|
-
# the bare execution path.
|
|
15
|
+
# Methods that delegate to the job's `#perform`. All three accept the same argument shape —
|
|
16
|
+
# `perform_later` is the most common (queues for later execution), `perform_now` runs
|
|
17
|
+
# synchronously, and `perform` is the bare execution path.
|
|
24
18
|
ENTRY_METHODS = %i[perform_later perform_now perform].freeze
|
|
25
19
|
|
|
26
|
-
# One job-call observation. Carries no path/location — the
|
|
27
|
-
#
|
|
28
|
-
# `Plugin::Base#diagnostic`.
|
|
20
|
+
# One job-call observation. Carries no path/location — the caller (the `node_rule` block)
|
|
21
|
+
# positions it via `Plugin::Base#diagnostic`.
|
|
29
22
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
30
23
|
|
|
31
24
|
module_function
|
|
32
25
|
|
|
33
|
-
# The job-call violations for a single call node (0..2), or `[]`
|
|
34
|
-
#
|
|
35
|
-
# job. ADR-37: the engine owns the walk.
|
|
26
|
+
# The job-call violations for a single call node (0..2), or `[]` when the node is not a
|
|
27
|
+
# `<Job>.perform_*` entry call on a known job. ADR-37: the engine owns the walk.
|
|
36
28
|
#
|
|
37
29
|
# @param call_node [Prism::Node]
|
|
38
30
|
# @param job_index [JobIndex]
|
|
@@ -76,8 +68,7 @@ module Rigor
|
|
|
76
68
|
)
|
|
77
69
|
end
|
|
78
70
|
|
|
79
|
-
# Renders a constant-path receiver as a String.
|
|
80
|
-
# Mirrors the helpers in rigor-activerecord /
|
|
71
|
+
# Renders a constant-path receiver as a String. Mirrors the helpers in rigor-activerecord /
|
|
81
72
|
# rigor-rails-routes for parity.
|
|
82
73
|
def constant_receiver_name(node)
|
|
83
74
|
case node
|
|
@@ -7,25 +7,19 @@ require_relative "job_index"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Activejob < Rigor::Plugin::Base
|
|
10
|
-
# Walks the configured job-search paths via the plugin's
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# the
|
|
14
|
-
# the discoverer also reads the `#perform` method's
|
|
15
|
-
# parameter list and computes the arity envelope.
|
|
10
|
+
# Walks the configured job-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
|
|
11
|
+
# Prism, and collects classes whose immediate superclass is one of the configured base classes.
|
|
12
|
+
# For each discovered class, the discoverer also reads the `#perform` method's parameter list and
|
|
13
|
+
# computes the arity envelope.
|
|
16
14
|
#
|
|
17
15
|
# Limitations (intentional for v0.1.0):
|
|
18
16
|
#
|
|
19
|
-
# - Only direct-superclass matches. `class WelcomeJob <
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# if needed.
|
|
23
|
-
# - The qualified class name is the lexical path
|
|
24
|
-
# (`Admin::WelcomeJob` for a class declared inside
|
|
17
|
+
# - Only direct-superclass matches. `class WelcomeJob < BaseJob` where `BaseJob < ApplicationJob`
|
|
18
|
+
# is NOT discovered. List `BaseJob` in `job_base_classes` if needed.
|
|
19
|
+
# - The qualified class name is the lexical path (`Admin::WelcomeJob` for a class declared inside
|
|
25
20
|
# `module Admin`).
|
|
26
|
-
# - The `#perform` arity is read from the syntactic
|
|
27
|
-
#
|
|
28
|
-
# are out of scope.
|
|
21
|
+
# - The `#perform` arity is read from the syntactic parameter list. Methods built via
|
|
22
|
+
# `define_method` are out of scope.
|
|
29
23
|
class JobDiscoverer
|
|
30
24
|
def initialize(io_boundary:, search_paths:, base_classes:)
|
|
31
25
|
@io_boundary = io_boundary
|
|
@@ -119,9 +113,8 @@ module Rigor
|
|
|
119
113
|
end
|
|
120
114
|
end
|
|
121
115
|
|
|
122
|
-
# Returns the `def perform(...)` node from a class
|
|
123
|
-
#
|
|
124
|
-
# `#perform`. Only matches instance-side `def perform`.
|
|
116
|
+
# Returns the `def perform(...)` node from a class body, or `nil` when the class doesn't
|
|
117
|
+
# override `#perform`. Only matches instance-side `def perform`.
|
|
125
118
|
def lookup_perform_def(body)
|
|
126
119
|
return nil if body.nil?
|
|
127
120
|
|
|
@@ -134,13 +127,10 @@ module Rigor
|
|
|
134
127
|
nil
|
|
135
128
|
end
|
|
136
129
|
|
|
137
|
-
# Builds a JobIndex::Entry from the discovered class's
|
|
138
|
-
# `#perform
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
# `perform_later` on a job that didn't override it is
|
|
142
|
-
# itself a bug, but it's the user's bug, not the
|
|
143
|
-
# plugin's call to flag without runtime context.
|
|
130
|
+
# Builds a JobIndex::Entry from the discovered class's `#perform` def. When the class doesn't
|
|
131
|
+
# override `#perform`, we record an "any-arity" entry — Active Job's default `#perform` is
|
|
132
|
+
# abstract; calling `perform_later` on a job that didn't override it is itself a bug, but it's
|
|
133
|
+
# the user's bug, not the plugin's call to flag without runtime context.
|
|
144
134
|
def build_entry(class_name, perform_def)
|
|
145
135
|
if perform_def.nil?
|
|
146
136
|
return JobIndex::Entry.new(
|