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
|
@@ -7,49 +7,40 @@ require "rigor/source/literals"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Actionpack < Rigor::Plugin::Base
|
|
10
|
-
# Per-node validators for the Action Pack DSL inside controller
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# filter / render phases need is read from the node-rule
|
|
17
|
-
# `NodeContext` ancestors rather than re-derived by a forward walk.
|
|
10
|
+
# Per-node validators for the Action Pack DSL inside controller files. ADR-37: the engine owns the
|
|
11
|
+
# single AST walk (the plugin declares `node_rule(Prism::CallNode)`), so each method here
|
|
12
|
+
# validates ONE call node and returns location-bearing {Violation}s — the `node_rule` block
|
|
13
|
+
# positions them via `Plugin::Base#diagnostic`. The enclosing-controller context the filter /
|
|
14
|
+
# render phases need is read from the node-rule `NodeContext` ancestors rather than re-derived by
|
|
15
|
+
# a forward walk.
|
|
18
16
|
#
|
|
19
17
|
# The recogniser keys on call-method-name suffix:
|
|
20
18
|
#
|
|
21
19
|
# - `users_path`, `edit_user_path(@user)` → `_path` family.
|
|
22
20
|
# - `users_url`, `edit_user_url(@user)` → `_url` family.
|
|
23
21
|
#
|
|
24
|
-
# Any call whose name doesn't end in `_path` / `_url` is
|
|
25
|
-
#
|
|
26
|
-
# receiver
|
|
27
|
-
# the helper is implicit-self in real controllers, and a
|
|
28
|
-
# custom-receiver call is almost certainly someone's own
|
|
22
|
+
# Any call whose name doesn't end in `_path` / `_url` is silently passed through. Calls with an
|
|
23
|
+
# explicit non-self receiver (`other_helper.users_path`) are also skipped — the helper is
|
|
24
|
+
# implicit-self in real controllers, and a custom-receiver call is almost certainly someone's own
|
|
29
25
|
# method that happens to share the suffix.
|
|
30
26
|
module Analyzer
|
|
31
27
|
SUFFIXES = %w[_path _url].freeze
|
|
32
28
|
|
|
33
|
-
# Phase 2 — filter-chain DSL methods. Each takes a
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# modifiers. Only the filter NAMES are validated; the
|
|
37
|
-
# `only:`/`except:` action-name arguments are not (deferred).
|
|
29
|
+
# Phase 2 — filter-chain DSL methods. Each takes a variadic list of filter names (Symbols /
|
|
30
|
+
# Strings) plus optional `only:` / `except:` / `if:` / `unless:` modifiers. Only the filter
|
|
31
|
+
# NAMES are validated; the `only:`/`except:` action-name arguments are not (deferred).
|
|
38
32
|
FILTER_DSL_METHODS = %i[
|
|
39
33
|
before_action after_action around_action
|
|
40
34
|
skip_before_action skip_after_action skip_around_action
|
|
41
35
|
prepend_before_action prepend_after_action prepend_around_action
|
|
42
36
|
].freeze
|
|
43
37
|
|
|
44
|
-
# Phase 3 — render-target template extensions checked in
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# extensions, the missing-template diagnostic stays silent.
|
|
51
|
-
# A configurable extension list is deferred; this set is wide
|
|
52
|
-
# enough to cover surveyed real-world projects without FPs.
|
|
38
|
+
# Phase 3 — render-target template extensions checked in priority order. Covers the engines used
|
|
39
|
+
# by surveyed projects: ERB (Rails default — `.html.erb`, `.text.erb`), HAML (Mastodon, Solidus
|
|
40
|
+
# admin — `.html.haml`), Slim, and JSON (`.json.jbuilder` plus `.json.erb` for hand-rolled API
|
|
41
|
+
# responses). When a template exists under any of these extensions, the missing-template
|
|
42
|
+
# diagnostic stays silent. A configurable extension list is deferred; this set is wide enough to
|
|
43
|
+
# cover surveyed real-world projects without FPs.
|
|
53
44
|
RENDER_TEMPLATE_EXTENSIONS = %w[
|
|
54
45
|
.html.erb
|
|
55
46
|
.text.erb
|
|
@@ -62,39 +53,31 @@ module Rigor
|
|
|
62
53
|
.xml.erb
|
|
63
54
|
].freeze
|
|
64
55
|
|
|
65
|
-
# Phase 1 — strong-parameter call shapes that begin a
|
|
66
|
-
#
|
|
67
|
-
# `params.require(:
|
|
68
|
-
# by looking for `:permit` call sites whose receiver is
|
|
69
|
-
# `params.require(:symbol)`.
|
|
56
|
+
# Phase 1 — strong-parameter call shapes that begin a validatable chain. The walker matches the
|
|
57
|
+
# `params.require(:user).permit(:name, :email)` chain by looking for `:permit` call sites whose
|
|
58
|
+
# receiver is `params.require(:symbol)`.
|
|
70
59
|
STRONG_PARAMS_RECEIVER_NAMES = %i[require permit_params strong_params].freeze
|
|
71
60
|
|
|
72
|
-
# One Action Pack observation. Carries no path — the caller
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# itself, or a sub-argument's location for `unknown-*-key`-style
|
|
77
|
-
# diagnostics that point at the offending Symbol).
|
|
61
|
+
# One Action Pack observation. Carries no path — the caller (the `node_rule` block) positions
|
|
62
|
+
# it via `Plugin::Base#diagnostic`. `location` is the Prism location the diagnostic should point
|
|
63
|
+
# at (a call's `message_loc` for the call itself, or a sub-argument's location for
|
|
64
|
+
# `unknown-*-key`-style diagnostics that point at the offending Symbol).
|
|
78
65
|
Violation = Data.define(:location, :message, :severity, :rule)
|
|
79
66
|
|
|
80
67
|
module_function
|
|
81
68
|
|
|
82
|
-
# Phase 4 — route-helper consumption. The helper-existence /
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# error. Real-world impact pre-fix: ~301 duplicates on Mastodon and
|
|
91
|
-
# ~119 on Redmine, exactly stacked with the rails-routes
|
|
92
|
-
# diagnostics. Returns `[]` for unknown / arity-mismatch shapes.
|
|
69
|
+
# Phase 4 — route-helper consumption. The helper-existence / arity check for ONE call node.
|
|
70
|
+
# Only the **info-level** route resolution (`helper-call`) is the value this plugin adds — it
|
|
71
|
+
# surfaces the resolved HTTP method + path + action alongside the call site, which
|
|
72
|
+
# `rigor-rails-routes` does not. Unknown-helper and wrong-arity diagnostics are produced
|
|
73
|
+
# canonically by `rigor-rails-routes`'s own analyzer (same `:helper_table` source of truth), so
|
|
74
|
+
# emitting them here would double every call-site error. Real-world impact pre-fix: ~301
|
|
75
|
+
# duplicates on Mastodon and ~119 on Redmine, exactly stacked with the rails-routes diagnostics.
|
|
76
|
+
# Returns `[]` for unknown / arity-mismatch shapes.
|
|
93
77
|
#
|
|
94
78
|
# @param call_node [Prism::Node]
|
|
95
|
-
# @param helper_table [Hash{String => Hash}] each entry carries
|
|
96
|
-
# `
|
|
97
|
-
# `action`.
|
|
79
|
+
# @param helper_table [Hash{String => Hash}] each entry carries `name`, `arity`,
|
|
80
|
+
# `acceptable_arities`, `path`, `http_method`, `action`.
|
|
98
81
|
# @return [Array<Violation>]
|
|
99
82
|
def helper_violations_for(call_node:, helper_table:)
|
|
100
83
|
return [] unless implicit_helper_call?(call_node)
|
|
@@ -103,24 +86,20 @@ module Rigor
|
|
|
103
86
|
return [] if entry.nil?
|
|
104
87
|
|
|
105
88
|
actual_arity = positional_arg_count(call_node)
|
|
106
|
-
# The `:helper_table` fact entry carries both `:arity` (the
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
# index/show). Honour the full set; fall back to the single
|
|
111
|
-
# arity for consumers compiled against an older fact shape.
|
|
89
|
+
# The `:helper_table` fact entry carries both `:arity` (the first registered entry's arity)
|
|
90
|
+
# and `:acceptable_arities` (the full Array — populated for uncountable-noun resources like
|
|
91
|
+
# `resources :news` which share a helper name across index/show). Honour the full set; fall
|
|
92
|
+
# back to the single arity for consumers compiled against an older fact shape.
|
|
112
93
|
acceptable = entry[:acceptable_arities] || [entry[:arity]]
|
|
113
94
|
return [] unless acceptable.include?(actual_arity)
|
|
114
95
|
|
|
115
96
|
[helper_call_violation(call_node, entry)]
|
|
116
97
|
end
|
|
117
98
|
|
|
118
|
-
# Phase 2 — filter-chain validation for ONE filter-DSL call
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# level of inheritance), and each filter name reference is
|
|
123
|
-
# validated against it. Calls outside a known controller
|
|
99
|
+
# Phase 2 — filter-chain validation for ONE filter-DSL call (`before_action :name`, …). The
|
|
100
|
+
# enclosing controller is read from the node-rule `NodeContext` ancestors, looked up in the
|
|
101
|
+
# controller index to get the effective method set (including one level of inheritance), and
|
|
102
|
+
# each filter name reference is validated against it. Calls outside a known controller
|
|
124
103
|
# contribute nothing.
|
|
125
104
|
#
|
|
126
105
|
# @param call_node [Prism::Node]
|
|
@@ -136,13 +115,11 @@ module Rigor
|
|
|
136
115
|
|
|
137
116
|
methods = controller_index.effective_methods_for(class_name)
|
|
138
117
|
spell_checker = DidYouMean::SpellChecker.new(dictionary: methods.map(&:to_s))
|
|
139
|
-
# When the controller (or its parent) `include`s a module the
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
# `
|
|
143
|
-
#
|
|
144
|
-
# `unknown-filter-method` in that case rather than FPing on
|
|
145
|
-
# legitimate gem-provided callback names.
|
|
118
|
+
# When the controller (or its parent) `include`s a module the discoverer couldn't resolve —
|
|
119
|
+
# typically a gem-shipped concern such as `Devise::Controllers::Helpers` or
|
|
120
|
+
# `Pundit::Authorization` — any `before_action :name` MIGHT be defined in that unresolved
|
|
121
|
+
# module. Suppress `unknown-filter-method` in that case rather than FPing on legitimate
|
|
122
|
+
# gem-provided callback names.
|
|
146
123
|
ambiguous_filters = controller_index.unresolved_include?(class_name)
|
|
147
124
|
|
|
148
125
|
filter_name_args(call_node).filter_map do |arg_node|
|
|
@@ -156,12 +133,10 @@ module Rigor
|
|
|
156
133
|
end
|
|
157
134
|
end
|
|
158
135
|
|
|
159
|
-
# Phase 1 — strong-parameter validation for ONE `permit` call.
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
# `rigor-activerecord`). Calls whose `:require` argument is a
|
|
164
|
-
# non-literal Symbol are passed through; namespaced models
|
|
136
|
+
# Phase 1 — strong-parameter validation for ONE `permit` call. Recognises the
|
|
137
|
+
# `params.require(:symbol).permit(:key, :key, ...)` chain and validates each `:key` against the
|
|
138
|
+
# AR model's column list (looked up via the model_index fact published by `rigor-activerecord`).
|
|
139
|
+
# Calls whose `:require` argument is a non-literal Symbol are passed through; namespaced models
|
|
165
140
|
# (`params.require(:admin_user)` → `Admin::User`) are deferred.
|
|
166
141
|
#
|
|
167
142
|
# @param call_node [Prism::Node]
|
|
@@ -185,23 +160,19 @@ module Rigor
|
|
|
185
160
|
end
|
|
186
161
|
end
|
|
187
162
|
|
|
188
|
-
# Phase 3 — render-target validation for ONE `render` call. Derive
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
# configured `view_search_paths` (default `["app/views"]`).
|
|
192
|
-
# Recognised call shapes:
|
|
163
|
+
# Phase 3 — render-target validation for ONE `render` call. Derive the candidate view template
|
|
164
|
+
# path(s) from the controller class name + the render argument shape, then check existence
|
|
165
|
+
# under the configured `view_search_paths` (default `["app/views"]`). Recognised call shapes:
|
|
193
166
|
#
|
|
194
167
|
# - `render :symbol` — `<views>/<controller_path>/<symbol>.html.erb`
|
|
195
168
|
# - `render "string/path"` — `<views>/<string_path>.html.erb`
|
|
196
169
|
# - `render partial: "name"` — `<views>/<controller_path>/_<name>.html.erb`
|
|
197
170
|
# - `render partial: "string/path"` — `<views>/<string_path with _ prefix>.html.erb`
|
|
198
171
|
#
|
|
199
|
-
# `render layout:`, `render plain:`, `render json:`, `render
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
# validates explicit renders only, since the implicit path would
|
|
204
|
-
# false-positive on `redirect_to` / `head` / early returns.
|
|
172
|
+
# `render layout:`, `render plain:`, `render json:`, `render text:`, `render inline:`, `render
|
|
173
|
+
# :nothing => true`, etc. are pass-through (no template lookup). Implicit-render (a controller
|
|
174
|
+
# method that doesn't call `render`) is also skipped — Phase 3 validates explicit renders only,
|
|
175
|
+
# since the implicit path would false-positive on `redirect_to` / `head` / early returns.
|
|
205
176
|
#
|
|
206
177
|
# @param call_node [Prism::Node]
|
|
207
178
|
# @param ancestors [Array<Prism::Node>] the lexical ancestor chain
|
|
@@ -215,41 +186,32 @@ module Rigor
|
|
|
215
186
|
class_name = enclosing_controller_name(ancestors)
|
|
216
187
|
return [] if class_name.nil?
|
|
217
188
|
|
|
218
|
-
# Prefer the file-path-derived controller path when the source
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
# `Users
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
# disambiguator Rails picks). Fall back to the AST-derived class
|
|
225
|
-
# name for files outside `app/controllers/` (libraries, test
|
|
226
|
-
# fixtures).
|
|
189
|
+
# Prefer the file-path-derived controller path when the source lives under `app/controllers/`
|
|
190
|
+
# — Rails autoload is the runtime authority on `Admin::Users::RolesController` vs
|
|
191
|
+
# `Users::RolesController` (a declaration like `module Admin; class Users::RolesController`
|
|
192
|
+
# resolves to whichever `Users` constant Ruby finds first, and the file path is the
|
|
193
|
+
# disambiguator Rails picks). Fall back to the AST-derived class name for files outside
|
|
194
|
+
# `app/controllers/` (libraries, test fixtures).
|
|
227
195
|
controller_path = controller_path_from_file(path) || controller_path_for(class_name)
|
|
228
196
|
return [] if controller_path.nil?
|
|
229
197
|
|
|
230
|
-
# Render checks silence when the controller (or its ancestor
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
# views; the local subclass calling `render :show` resolves
|
|
235
|
-
# through the gem's view path, which our static analyser doesn't
|
|
236
|
-
# know about.
|
|
198
|
+
# Render checks silence when the controller (or its ancestor chain) inherits from a
|
|
199
|
+
# gem-shipped parent (Devise::ConfirmationsController, Doorkeeper::ApplicationsController, …).
|
|
200
|
+
# The gem ships its own views; the local subclass calling `render :show` resolves through the
|
|
201
|
+
# gem's view path, which our static analyser doesn't know about.
|
|
237
202
|
if controller_index&.known?(class_name) &&
|
|
238
203
|
controller_index.unresolved_include?(class_name)
|
|
239
204
|
return []
|
|
240
205
|
end
|
|
241
206
|
|
|
242
|
-
# Abstract base controllers: a `*BaseController` (`Admin::
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
# … but no top-level templates). Skip render checks for these —
|
|
251
|
-
# the diagnostic would be a false positive against intentional
|
|
252
|
-
# Rails abstract-base layouts.
|
|
207
|
+
# Abstract base controllers: a `*BaseController` (`Admin::BaseController`,
|
|
208
|
+
# `Settings::Preferences::BaseController`, …) typically has no view of its own; its `render
|
|
209
|
+
# :show` bodies are resolved by Rails against the calling subclass's controller path at
|
|
210
|
+
# request time, NOT the base's. Same for parent controllers whose view directory exists but
|
|
211
|
+
# contains only subdirectories (Mastodon's `Admin::SettingsController` whose
|
|
212
|
+
# `app/views/admin/settings/` holds about/, appearance/, … but no top-level templates). Skip
|
|
213
|
+
# render checks for these — the diagnostic would be a false positive against intentional Rails
|
|
214
|
+
# abstract-base layouts.
|
|
253
215
|
return [] if abstract_base_controller?(class_name, controller_path, view_search_roots)
|
|
254
216
|
|
|
255
217
|
target = render_target_for(call_node, controller_path)
|
|
@@ -259,27 +221,22 @@ module Rigor
|
|
|
259
221
|
violation ? [violation] : []
|
|
260
222
|
end
|
|
261
223
|
|
|
262
|
-
# Resolves the qualified name of the controller class the node
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
# root and validated nested-class call sites against it. Enclosing
|
|
268
|
-
# `ModuleNode`s above it contribute the namespace, so the
|
|
269
|
-
# nested-module declaration shape
|
|
224
|
+
# Resolves the qualified name of the controller class the node sits in from its lexical
|
|
225
|
+
# ancestors (the node-rule `NodeContext#ancestors`, outermost first). The OUTERMOST `ClassNode`
|
|
226
|
+
# is the controller — matching the old forward walk, which returned the first `ClassNode`
|
|
227
|
+
# reachable from the file root and validated nested-class call sites against it. Enclosing
|
|
228
|
+
# `ModuleNode`s above it contribute the namespace, so the nested-module declaration shape
|
|
270
229
|
#
|
|
271
230
|
# module Admin
|
|
272
231
|
# class DomainBlocksController < BaseController
|
|
273
232
|
# end
|
|
274
233
|
# end
|
|
275
234
|
#
|
|
276
|
-
# is recovered as `Admin::DomainBlocksController` — the same name
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# `
|
|
280
|
-
#
|
|
281
|
-
# (Mastodon's `admin/domain_blocks` rendered as bare
|
|
282
|
-
# `domain_blocks`). Returns nil when no enclosing class exists.
|
|
235
|
+
# is recovered as `Admin::DomainBlocksController` — the same name the `ControllerDiscoverer`
|
|
236
|
+
# registers under. Without this, a bare inner-class name would be used for index lookups +
|
|
237
|
+
# `controller_path_for`, silently skipping filter validation and pointing render template paths
|
|
238
|
+
# at the wrong directory (Mastodon's `admin/domain_blocks` rendered as bare `domain_blocks`).
|
|
239
|
+
# Returns nil when no enclosing class exists.
|
|
283
240
|
def enclosing_controller_name(ancestors)
|
|
284
241
|
class_index = ancestors.index { |n| n.is_a?(Prism::ClassNode) }
|
|
285
242
|
return nil if class_index.nil?
|
|
@@ -296,10 +253,9 @@ module Rigor
|
|
|
296
253
|
path ? path.split("::") : []
|
|
297
254
|
end
|
|
298
255
|
|
|
299
|
-
# Resolves a class-name AST node against an enclosing namespace
|
|
300
|
-
#
|
|
301
|
-
#
|
|
302
|
-
# `class Foo` inside `module Admin`) is qualified against it.
|
|
256
|
+
# Resolves a class-name AST node against an enclosing namespace chain. A `ConstantPathNode`
|
|
257
|
+
# (e.g. `class Admin::Foo`) is already absolute and ignores the chain; a `ConstantReadNode`
|
|
258
|
+
# (bare `class Foo` inside `module Admin`) is qualified against it.
|
|
303
259
|
def qualified_name_with_enclosing(node, enclosing)
|
|
304
260
|
return nil unless node.is_a?(Prism::Node)
|
|
305
261
|
|
|
@@ -334,9 +290,8 @@ module Rigor
|
|
|
334
290
|
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :render
|
|
335
291
|
end
|
|
336
292
|
|
|
337
|
-
# Drops the trailing keyword hash (`only:` / `except:` / `if:` /
|
|
338
|
-
#
|
|
339
|
-
# names.
|
|
293
|
+
# Drops the trailing keyword hash (`only:` / `except:` / `if:` / `unless:`) so the modifier args
|
|
294
|
+
# don't get treated as filter names.
|
|
340
295
|
def filter_name_args(call_node)
|
|
341
296
|
args = call_node.arguments&.arguments || []
|
|
342
297
|
args = args[0..-2] if args.last.is_a?(Prism::KeywordHashNode)
|
|
@@ -351,9 +306,8 @@ module Rigor
|
|
|
351
306
|
if methods.include?(filter_name.to_sym)
|
|
352
307
|
filter_call_violation(call_node, filter_name)
|
|
353
308
|
elsif ambiguous_filters
|
|
354
|
-
# An unresolved include shadows our judgment — emit the
|
|
355
|
-
#
|
|
356
|
-
# indexed, but skip the error.
|
|
309
|
+
# An unresolved include shadows our judgment — emit the recognized-filter info anyway so
|
|
310
|
+
# the call site is still indexed, but skip the error.
|
|
357
311
|
filter_call_violation(call_node, filter_name)
|
|
358
312
|
else
|
|
359
313
|
unknown_filter_violation(arg_node, call_node, filter_name, spell_checker)
|
|
@@ -373,11 +327,9 @@ module Rigor
|
|
|
373
327
|
def model_class_for_permit(permit_call)
|
|
374
328
|
require_call = permit_call.receiver
|
|
375
329
|
symbol_node = require_call.arguments.arguments.first
|
|
376
|
-
# `:user` → `User`, `:order_item` → `OrderItem`. ADR-39: the
|
|
377
|
-
#
|
|
378
|
-
# `
|
|
379
|
-
# case the former `split("_").map(&:capitalize)` only
|
|
380
|
-
# approximated is authoritative), falling back otherwise.
|
|
330
|
+
# `:user` → `User`, `:order_item` → `OrderItem`. ADR-39: the shared inflector camelizes
|
|
331
|
+
# through the real `ActiveSupport::Inflector` when present (so the multi-word case the former
|
|
332
|
+
# `split("_").map(&:capitalize)` only approximated is authoritative), falling back otherwise.
|
|
381
333
|
Rigor::Plugin::Inflector.camelize(symbol_node.value.to_s)
|
|
382
334
|
end
|
|
383
335
|
|
|
@@ -405,29 +357,24 @@ module Rigor
|
|
|
405
357
|
)
|
|
406
358
|
end
|
|
407
359
|
|
|
408
|
-
# Convert `<root>/app/controllers/admin/users/roles_controller.rb`
|
|
409
|
-
#
|
|
410
|
-
# `app/controllers/` segment.
|
|
360
|
+
# Convert `<root>/app/controllers/admin/users/roles_controller.rb` to `admin/users/roles`.
|
|
361
|
+
# Returns nil if the path is not under an `app/controllers/` segment.
|
|
411
362
|
def controller_path_from_file(path)
|
|
412
363
|
match = path.match(%r{(?:\A|/)app/controllers/(.+)_controller\.rb\z})
|
|
413
364
|
match&.[](1)
|
|
414
365
|
end
|
|
415
366
|
|
|
416
|
-
# An abstract base controller — its `render` bodies don't validate
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
#
|
|
423
|
-
#
|
|
424
|
-
# subdirectories (no template files at its top). Mastodon's
|
|
425
|
-
# Admin::SettingsController whose app/views/admin/settings/
|
|
426
|
-
# holds only about/, appearance/, … — the parent of nested
|
|
367
|
+
# An abstract base controller — its `render` bodies don't validate against its own
|
|
368
|
+
# controller_path because Rails resolves at request time against the actual subclass. Two
|
|
369
|
+
# conservative heuristics:
|
|
370
|
+
# (1) The class name ends with `BaseController`. Strong Rails-convention signal
|
|
371
|
+
# (Settings::Preferences::BaseController, Admin::BaseController, …).
|
|
372
|
+
# (2) The controller's view directory exists but contains ONLY subdirectories (no template
|
|
373
|
+
# files at its top). Mastodon's Admin::SettingsController whose
|
|
374
|
+
# app/views/admin/settings/ holds only about/, appearance/, … — the parent of nested
|
|
427
375
|
# controllers.
|
|
428
|
-
# Deliberately NOT triggered by "no view directory at all" because
|
|
429
|
-
#
|
|
430
|
-
# (the typo / forgot-to-create case).
|
|
376
|
+
# Deliberately NOT triggered by "no view directory at all" because that fires the diagnostic we
|
|
377
|
+
# DO want for genuinely-missing views (the typo / forgot-to-create case).
|
|
431
378
|
def abstract_base_controller?(class_name, controller_path, view_search_roots)
|
|
432
379
|
return true if class_name.end_with?("BaseController")
|
|
433
380
|
|
|
@@ -440,12 +387,10 @@ module Rigor
|
|
|
440
387
|
end
|
|
441
388
|
end
|
|
442
389
|
|
|
443
|
-
# Returns `[kind, view_relative_path]` where kind is `:template`
|
|
444
|
-
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
# validate (`layout:` / `plain:` / `json:` / `text:` / `inline:` /
|
|
448
|
-
# `:nothing` / no parseable target).
|
|
390
|
+
# Returns `[kind, view_relative_path]` where kind is `:template` or `:partial`, and
|
|
391
|
+
# view_relative_path is the path under view_search_roots WITHOUT extension (the extension
|
|
392
|
+
# family is appended at lookup time). Returns nil for shapes Phase 3 doesn't validate (`layout:`
|
|
393
|
+
# / `plain:` / `json:` / `text:` / `inline:` / `:nothing` / no parseable target).
|
|
449
394
|
def render_target_for(call_node, controller_path)
|
|
450
395
|
args = call_node.arguments&.arguments || []
|
|
451
396
|
return nil if args.empty?
|
|
@@ -454,9 +399,8 @@ module Rigor
|
|
|
454
399
|
# `render partial: "..."` — the keyword form.
|
|
455
400
|
return partial_target_from_kwargs(first, controller_path) if first.is_a?(Prism::KeywordHashNode)
|
|
456
401
|
|
|
457
|
-
# `render :symbol` / `render "path"`. A trailing KeywordHashNode
|
|
458
|
-
#
|
|
459
|
-
# positional carries the template name.
|
|
402
|
+
# `render :symbol` / `render "path"`. A trailing KeywordHashNode is allowed (e.g. `render
|
|
403
|
+
# :show, status: :ok`); the leading positional carries the template name.
|
|
460
404
|
template_target_from_positional(first, controller_path)
|
|
461
405
|
end
|
|
462
406
|
|
|
@@ -487,15 +431,13 @@ module Rigor
|
|
|
487
431
|
|
|
488
432
|
# `UsersController` → "users".
|
|
489
433
|
# `Admin::WidgetsController` → "admin/widgets".
|
|
490
|
-
# Returns nil for class names that don't end with the `Controller`
|
|
491
|
-
# suffix.
|
|
434
|
+
# Returns nil for class names that don't end with the `Controller` suffix.
|
|
492
435
|
def controller_path_for(class_name)
|
|
493
436
|
return nil unless class_name.end_with?("Controller")
|
|
494
437
|
|
|
495
438
|
stripped = class_name.delete_suffix("Controller")
|
|
496
|
-
# ADR-39: the shared inflector flattens `::` → `/` and applies
|
|
497
|
-
#
|
|
498
|
-
# produced the same result for the common case but missed
|
|
439
|
+
# ADR-39: the shared inflector flattens `::` → `/` and applies Rails' real underscore (the
|
|
440
|
+
# per-segment hand-rolled version produced the same result for the common case but missed
|
|
499
441
|
# acronym handling). `Admin::Widgets` → `admin/widgets`.
|
|
500
442
|
Rigor::Plugin::Inflector.underscore(stripped)
|
|
501
443
|
end
|
|
@@ -7,15 +7,11 @@ require_relative "controller_index"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Actionpack < Rigor::Plugin::Base
|
|
10
|
-
# Walks `controller_search_paths` building a
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# chains) to validate that `before_action :name`
|
|
14
|
-
# references a method defined on the controller or its
|
|
15
|
-
# ancestor chain.
|
|
10
|
+
# Walks `controller_search_paths` building a {ControllerIndex} of `(class_name, methods,
|
|
11
|
+
# parent_class_name)` triples. Used by Phase 2 (filter chains) to validate that `before_action
|
|
12
|
+
# :name` references a method defined on the controller or its ancestor chain.
|
|
16
13
|
#
|
|
17
|
-
# Two declaration shapes are recognised and qualified
|
|
18
|
-
# the same way:
|
|
14
|
+
# Two declaration shapes are recognised and qualified the same way:
|
|
19
15
|
#
|
|
20
16
|
# class Admin::AccountsController < BaseController
|
|
21
17
|
# # → registered as "Admin::AccountsController"
|
|
@@ -26,25 +22,18 @@ module Rigor
|
|
|
26
22
|
# end
|
|
27
23
|
# end
|
|
28
24
|
#
|
|
29
|
-
# Pre-fix the nested form ignored the enclosing `module
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# `
|
|
33
|
-
#
|
|
34
|
-
# `AccountsController` + `Admin::AccountsController` +
|
|
35
|
-
# `Api::V1::AccountsController`) and the overwrite caused
|
|
36
|
-
# the wrong inheritance / method set to flow downstream,
|
|
37
|
-
# producing dozens of false `unknown-filter-method`
|
|
25
|
+
# Pre-fix the nested form ignored the enclosing `module Admin` and registered the inner class as
|
|
26
|
+
# bare `"AccountsController"`, overwriting the top-level `app/controllers/accounts_controller.rb`
|
|
27
|
+
# entry. Mastodon has both shapes for the same logical name (top-level `AccountsController` +
|
|
28
|
+
# `Admin::AccountsController` + `Api::V1::AccountsController`) and the overwrite caused the wrong
|
|
29
|
+
# inheritance / method set to flow downstream, producing dozens of false `unknown-filter-method`
|
|
38
30
|
# diagnostics.
|
|
39
31
|
#
|
|
40
|
-
# Multi-level inheritance is also walked: `Admin::AccountsController
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# tries `Admin::BaseController`, then falls through to
|
|
46
|
-
# top-level `BaseController`, matching Ruby's constant-
|
|
47
|
-
# resolution semantics.
|
|
32
|
+
# Multi-level inheritance is also walked: `Admin::AccountsController < BaseController <
|
|
33
|
+
# ApplicationController` resolves methods from all three. Cycle-safe via a visited set. The
|
|
34
|
+
# parent-name lookup is **lexically scoped** — a bare `BaseController` reference inside `module
|
|
35
|
+
# Admin` first tries `Admin::BaseController`, then falls through to top-level `BaseController`,
|
|
36
|
+
# matching Ruby's constant-resolution semantics.
|
|
48
37
|
class ControllerDiscoverer
|
|
49
38
|
def initialize(io_boundary:, search_paths:)
|
|
50
39
|
@io_boundary = io_boundary
|
|
@@ -84,15 +73,12 @@ module Rigor
|
|
|
84
73
|
nil
|
|
85
74
|
end
|
|
86
75
|
|
|
87
|
-
# Walks every `ClassNode` / `ModuleNode` in the AST,
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
# `["Admin"]`). The yielded declaration's own segment
|
|
94
|
-
# is added to the chain for its body's recursion so
|
|
95
|
-
# constants nested two levels deep qualify correctly.
|
|
76
|
+
# Walks every `ClassNode` / `ModuleNode` in the AST, yielding `[declaration_node,
|
|
77
|
+
# enclosing_namespace_array]` for each. `enclosing` is the chain of module / class qualifiers
|
|
78
|
+
# OUTSIDE the yielded declaration (e.g. for `module Admin; class AccountsController; end; end`
|
|
79
|
+
# the inner class yields with enclosing `["Admin"]`). The yielded declaration's own segment is
|
|
80
|
+
# added to the chain for its body's recursion so constants nested two levels deep qualify
|
|
81
|
+
# correctly.
|
|
96
82
|
def walk_declarations(node, namespace:, &)
|
|
97
83
|
return unless node.is_a?(Prism::Node)
|
|
98
84
|
|
|
@@ -109,11 +95,9 @@ module Rigor
|
|
|
109
95
|
end
|
|
110
96
|
end
|
|
111
97
|
|
|
112
|
-
# The segments contributed by a declaration to the
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# Returns `[]` for an anonymous declaration the walker
|
|
116
|
-
# cannot qualify.
|
|
98
|
+
# The segments contributed by a declaration to the qualifier chain of its body. For `class A::B`
|
|
99
|
+
# this is `["A", "B"]`; for `module Outer` it is `["Outer"]`. Returns `[]` for an anonymous
|
|
100
|
+
# declaration the walker cannot qualify.
|
|
117
101
|
def namespace_segments_for(declaration_node)
|
|
118
102
|
path = qualified_name_for(declaration_node.constant_path)
|
|
119
103
|
path ? path.split("::") : []
|
|
@@ -133,13 +117,10 @@ module Rigor
|
|
|
133
117
|
)
|
|
134
118
|
end
|
|
135
119
|
|
|
136
|
-
# Resolves the declared name against the enclosing
|
|
137
|
-
#
|
|
138
|
-
# `
|
|
139
|
-
#
|
|
140
|
-
# `ConstantReadNode` (bare `Foo` in `module Admin;
|
|
141
|
-
# class Foo`) is qualified against the chain so the
|
|
142
|
-
# name becomes `Admin::Foo`.
|
|
120
|
+
# Resolves the declared name against the enclosing namespace chain. A `ConstantPathNode` (e.g.
|
|
121
|
+
# `Admin::Foo` in `class Admin::Foo`) is already absolute and ignores the enclosing chain. A
|
|
122
|
+
# `ConstantReadNode` (bare `Foo` in `module Admin; class Foo`) is qualified against the chain so
|
|
123
|
+
# the name becomes `Admin::Foo`.
|
|
143
124
|
def qualified_name_with_enclosing(node, enclosing)
|
|
144
125
|
return nil unless node.is_a?(Prism::Node)
|
|
145
126
|
|
|
@@ -165,11 +146,9 @@ module Rigor
|
|
|
165
146
|
accumulator
|
|
166
147
|
end
|
|
167
148
|
|
|
168
|
-
# Collects the qualified-constant targets passed to
|
|
169
|
-
# `
|
|
170
|
-
#
|
|
171
|
-
# declared inside a concern doesn't pull the concern's
|
|
172
|
-
# includes into itself.
|
|
149
|
+
# Collects the qualified-constant targets passed to `include` calls inside the body. Stops at
|
|
150
|
+
# nested `ClassNode` / `ModuleNode` boundaries so a class declared inside a concern doesn't pull
|
|
151
|
+
# the concern's includes into itself.
|
|
173
152
|
def collect_include_targets(node, accumulator = [])
|
|
174
153
|
return accumulator unless node.is_a?(Prism::Node)
|
|
175
154
|
return accumulator if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
|