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
|
@@ -2,19 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module TypeNode
|
|
5
|
-
# A bare named-type reference in an RBS::Extended payload. The
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# PascalCase for class-like names (`"String"`, `"Pick"`),
|
|
9
|
-
# `lower_snake` for type-function-shaped names without
|
|
5
|
+
# A bare named-type reference in an RBS::Extended payload. The `name` is the head as the parser
|
|
6
|
+
# saw it — kebab-case for built-in refinement names (`"non-empty-string"`), PascalCase for
|
|
7
|
+
# class-like names (`"String"`, `"Pick"`), `lower_snake` for type-function-shaped names without
|
|
10
8
|
# arguments (rare).
|
|
11
9
|
#
|
|
12
|
-
# The resolver dispatch path treats an `Identifier` as the
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# meaningful with two type arguments. The `Generic` carrier
|
|
17
|
-
# is what plugin resolvers normally key on.
|
|
10
|
+
# The resolver dispatch path treats an `Identifier` as the no-arg form: if a plugin recognises
|
|
11
|
+
# `Pick` as a TS-utility name, it MAY still return `Dynamic[top]` for the bare `Identifier("Pick")`
|
|
12
|
+
# since TypeScript's `Pick` is only meaningful with two type arguments. The `Generic` carrier is
|
|
13
|
+
# what plugin resolvers normally key on.
|
|
18
14
|
class Identifier < Data.define(:name)
|
|
19
15
|
def initialize(name:)
|
|
20
16
|
unless name.is_a?(String) && !name.empty?
|
|
@@ -23,14 +19,11 @@ module Rigor
|
|
|
23
19
|
"got #{name.inspect}"
|
|
24
20
|
end
|
|
25
21
|
|
|
26
|
-
# Freeze the String field so the resulting Data object
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# object — they live in the parser's hot path and are
|
|
32
|
-
# the natural carriers to flow through future Ractor
|
|
33
|
-
# boundaries (see CURRENT_WORK Open Items #8).
|
|
22
|
+
# Freeze the String field so the resulting Data object is `Ractor.shareable?` regardless of
|
|
23
|
+
# whether the caller passed a `# frozen_string_literal: true` constant or a dynamically built
|
|
24
|
+
# String. The same discipline applies to every other TypeNode value object — they live in the
|
|
25
|
+
# parser's hot path and are the natural carriers to flow through future Ractor boundaries
|
|
26
|
+
# (see CURRENT_WORK Open Items #8).
|
|
34
27
|
super(name: name.frozen? ? name : name.dup.freeze)
|
|
35
28
|
end
|
|
36
29
|
end
|
data/lib/rigor/type_node.rb
CHANGED
|
@@ -2,18 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
# Mini-AST passed to plugin-supplied `TypeNode` resolvers (see
|
|
5
|
-
# [ADR-13](../../docs/adr/13-typenode-resolver-plugin.md)). The
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# built-in registry / `ImportedRefinements::Parser` failed to
|
|
9
|
-
# resolve it and the analyzer hands the node off to plugins.
|
|
10
|
-
#
|
|
11
|
-
# Slice 1 of the ADR-13 envelope ships these two value objects
|
|
12
|
-
# only. The `NameScope` companion + plugin manifest hook arrive
|
|
13
|
-
# in slice 2; the parser integration arrives in slice 3. Until
|
|
14
|
-
# those land, the carriers are reachable only through direct
|
|
15
|
-
# instantiation in tests — they are the stable data shape every
|
|
16
|
-
# later slice consumes.
|
|
5
|
+
# [ADR-13](../../docs/adr/13-typenode-resolver-plugin.md)). The two leaf carriers — {Identifier} and
|
|
6
|
+
# {Generic} — describe a node in an `%a{rigor:v1:...}` payload at the point the built-in registry /
|
|
7
|
+
# `ImportedRefinements::Parser` failed to resolve it and the analyzer hands the node off to plugins.
|
|
17
8
|
module TypeNode
|
|
18
9
|
end
|
|
19
10
|
end
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
|
-
# Generates the value-object identity trio (`==` / `eql?` / `hash`)
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# behaviour — a carrier vanishes after its first insertion, or two
|
|
10
|
-
# distinct types compare equal.
|
|
4
|
+
# Generates the value-object identity trio (`==` / `eql?` / `hash`) from a fixed field
|
|
5
|
+
# list. Every `Rigor::Type` carrier and the cache descriptor entries are immutable value
|
|
6
|
+
# objects that each hand-wrote the same three methods; forgetting a field in one of them
|
|
7
|
+
# (or letting `==` and `hash` disagree) silently corrupts `Set` / `Hash`-key behaviour — a
|
|
8
|
+
# carrier vanishes after its first insertion, or two distinct types compare equal.
|
|
11
9
|
#
|
|
12
|
-
# The methods are CODE-GENERATED at class-definition time, not driven
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# would be a real regression.
|
|
10
|
+
# The methods are CODE-GENERATED at class-definition time, not driven by per-call
|
|
11
|
+
# reflection, so they are as fast as the hand-written versions they replace — `hash` and
|
|
12
|
+
# `==` run on the inference hot path (type dedup, fact-store keys, memo caches), where a
|
|
13
|
+
# `send`-per-field would be a real regression.
|
|
17
14
|
#
|
|
18
15
|
# Usage:
|
|
19
16
|
#
|
|
@@ -32,11 +29,10 @@ module Rigor
|
|
|
32
29
|
# [self.class, class_name, type_args].hash
|
|
33
30
|
# end
|
|
34
31
|
#
|
|
35
|
-
# Value objects whose equality is NOT a plain field-wise comparison
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# `
|
|
39
|
-
# field-free singletons.
|
|
32
|
+
# Value objects whose equality is NOT a plain field-wise comparison keep their
|
|
33
|
+
# hand-written `==` / `hash` and do not call `value_fields` — e.g. `Type::Constant` also
|
|
34
|
+
# distinguishes `value.class` (so `Constant[1] != Constant[1.0]`), and `Type::Top` /
|
|
35
|
+
# `Type::Bot` are field-free singletons.
|
|
40
36
|
module ValueSemantics
|
|
41
37
|
def self.included(base)
|
|
42
38
|
base.extend(ClassMethods)
|
|
@@ -49,10 +45,9 @@ module Rigor
|
|
|
49
45
|
guard = comparisons.empty? ? "" : " && #{comparisons.join(' && ')}"
|
|
50
46
|
key = (["self.class"] + fields.map(&:to_s)).join(", ")
|
|
51
47
|
|
|
52
|
-
# Codegen (not per-call reflection) so the generated `==` / `hash`
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# the emitted source is:
|
|
48
|
+
# Codegen (not per-call reflection) so the generated `==` / `hash` read fields
|
|
49
|
+
# directly and run at hand-written speed on the inference hot path. For
|
|
50
|
+
# `value_fields :class_name, :type_args` the emitted source is:
|
|
56
51
|
#
|
|
57
52
|
# def ==(other)
|
|
58
53
|
# other.is_a?(self.class) && class_name == other.class_name && type_args == other.type_args
|
data/lib/rigor/version.rb
CHANGED
|
@@ -6,43 +6,34 @@ require "prism"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class Actioncable < Rigor::Plugin::Base
|
|
9
|
-
# Walks a parsed file's AST looking for ActionCable
|
|
10
|
-
#
|
|
11
|
-
# {ChannelIndex}.
|
|
9
|
+
# Walks a parsed file's AST looking for ActionCable entry-point calls and validates each against
|
|
10
|
+
# the {ChannelIndex}.
|
|
12
11
|
#
|
|
13
12
|
# Recognised shapes:
|
|
14
13
|
#
|
|
15
|
-
# - `<ChannelClass>.broadcast_to(record, data)` —
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# one channel with no dynamic stream registrations,
|
|
22
|
-
# we check that the name appears in
|
|
23
|
-
# `index.all_stream_names`. Otherwise the
|
|
24
|
-
# `unknown-stream` warning is suppressed (we can't
|
|
25
|
-
# prove absence).
|
|
14
|
+
# - `<ChannelClass>.broadcast_to(record, data)` — class-targeted broadcast. The class must exist
|
|
15
|
+
# in the index.
|
|
16
|
+
# - `ActionCable.server.broadcast(stream_name, data)` — string-targeted broadcast. When
|
|
17
|
+
# `stream_name` is a literal string and the index has at least one channel with no dynamic
|
|
18
|
+
# stream registrations, we check that the name appears in `index.all_stream_names`. Otherwise
|
|
19
|
+
# the `unknown-stream` warning is suppressed (we can't prove absence).
|
|
26
20
|
module Analyzer
|
|
27
|
-
# `ActionCable.server.broadcast(...)` — the receiver
|
|
28
|
-
#
|
|
29
|
-
# Single-symbol form (just `broadcast`) is too
|
|
30
|
-
# ambiguous to validate.
|
|
21
|
+
# `ActionCable.server.broadcast(...)` — the receiver path we recognise as a server-targeted
|
|
22
|
+
# broadcast. Single-symbol form (just `broadcast`) is too ambiguous to validate.
|
|
31
23
|
SERVER_BROADCAST_RECEIVER_NAMES = %w[
|
|
32
24
|
ActionCable.server
|
|
33
25
|
::ActionCable.server
|
|
34
26
|
].freeze
|
|
35
27
|
|
|
36
|
-
# One broadcast observation. Carries no path/location — the
|
|
37
|
-
#
|
|
38
|
-
# `Plugin::Base#diagnostic`.
|
|
28
|
+
# One broadcast observation. Carries no path/location — the caller (the `node_rule` block)
|
|
29
|
+
# positions it via `Plugin::Base#diagnostic`.
|
|
39
30
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
40
31
|
|
|
41
32
|
module_function
|
|
42
33
|
|
|
43
|
-
# The broadcast violations for a single call node, or `[]` when
|
|
44
|
-
#
|
|
45
|
-
#
|
|
34
|
+
# The broadcast violations for a single call node, or `[]` when the node is not a
|
|
35
|
+
# `broadcast_to` / `ActionCable.server.broadcast` call this plugin recognises. ADR-37: the
|
|
36
|
+
# engine owns the walk.
|
|
46
37
|
#
|
|
47
38
|
# @param call_node [Prism::Node]
|
|
48
39
|
# @param channel_index [ChannelIndex]
|
|
@@ -61,10 +52,8 @@ module Rigor
|
|
|
61
52
|
class_name = constant_receiver_name(call_node.receiver)
|
|
62
53
|
return [] if class_name.nil?
|
|
63
54
|
|
|
64
|
-
# broadcast_to with a class-name receiver that
|
|
65
|
-
#
|
|
66
|
-
# not ActionCable — pass through silently to
|
|
67
|
-
# avoid flagging unrelated `broadcast_to` methods.
|
|
55
|
+
# broadcast_to with a class-name receiver that doesn't end in "Channel" is almost certainly
|
|
56
|
+
# not ActionCable — pass through silently to avoid flagging unrelated `broadcast_to` methods.
|
|
68
57
|
return [] unless class_name.end_with?("Channel")
|
|
69
58
|
|
|
70
59
|
entry = channel_index.find(class_name) || channel_index.find("::#{class_name}")
|
|
@@ -127,8 +116,7 @@ module Rigor
|
|
|
127
116
|
)
|
|
128
117
|
end
|
|
129
118
|
|
|
130
|
-
# Renders an `A.b.c` chain as a string (used to
|
|
131
|
-
# detect `ActionCable.server`). Returns nil for
|
|
119
|
+
# Renders an `A.b.c` chain as a string (used to detect `ActionCable.server`). Returns nil for
|
|
132
120
|
# non-chained nodes.
|
|
133
121
|
def call_chain_string(node)
|
|
134
122
|
parts = []
|
|
@@ -7,36 +7,27 @@ require_relative "channel_index"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Actioncable < Rigor::Plugin::Base
|
|
10
|
-
# Walks the configured channel-search paths via the
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# superclass is one of the configured base classes.
|
|
10
|
+
# Walks the configured channel-search paths via the plugin's `IoBoundary`, parses each `.rb` file
|
|
11
|
+
# with Prism, and collects classes whose immediate superclass is one of the configured base
|
|
12
|
+
# classes.
|
|
14
13
|
#
|
|
15
14
|
# For each discovered channel, the discoverer:
|
|
16
15
|
#
|
|
17
|
-
# - Records every public instance-side `def` whose
|
|
18
|
-
#
|
|
19
|
-
# (`subscribed`, `unsubscribed`, `_`-prefixed).
|
|
20
|
-
# These are the action methods clients can invoke
|
|
16
|
+
# - Records every public instance-side `def` whose name isn't an ActionCable framework hook
|
|
17
|
+
# (`subscribed`, `unsubscribed`, `_`-prefixed). These are the action methods clients can invoke
|
|
21
18
|
# via `subscription.perform("action_name", data)`.
|
|
22
|
-
# - Records every literal-string `stream_from "name"`
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# ANY non-literal `stream_from` argument (or a
|
|
26
|
-
# `stream_for` call) so the analyzer knows it can't
|
|
27
|
-
# be sure of every stream name.
|
|
19
|
+
# - Records every literal-string `stream_from "name"` call as a registered stream name.
|
|
20
|
+
# - Sets `dynamic_streams: true` when the channel has ANY non-literal `stream_from` argument (or a
|
|
21
|
+
# `stream_for` call) so the analyzer knows it can't be sure of every stream name.
|
|
28
22
|
#
|
|
29
23
|
# Intentional limitations:
|
|
30
24
|
#
|
|
31
25
|
# - Direct-superclass match only.
|
|
32
|
-
# - Public-vs-private is not tracked; the framework
|
|
33
|
-
#
|
|
34
|
-
# by name. Methods marked `private` after a
|
|
35
|
-
# `private` keyword would still appear in the
|
|
26
|
+
# - Public-vs-private is not tracked; the framework hooks (`subscribed`/`unsubscribed`) are
|
|
27
|
+
# excluded by name. Methods marked `private` after a `private` keyword would still appear in the
|
|
36
28
|
# `action_methods` set.
|
|
37
|
-
# - `stream_for(record)` (model-scoped streams) is
|
|
38
|
-
#
|
|
39
|
-
# not introspected further.
|
|
29
|
+
# - `stream_for(record)` (model-scoped streams) is recognised as setting `dynamic_streams: true`
|
|
30
|
+
# but not introspected further.
|
|
40
31
|
class ChannelDiscoverer
|
|
41
32
|
FRAMEWORK_HOOKS = %i[subscribed unsubscribed].to_set.freeze
|
|
42
33
|
|
|
@@ -126,12 +117,9 @@ module Rigor
|
|
|
126
117
|
)
|
|
127
118
|
end
|
|
128
119
|
|
|
129
|
-
# Walks the channel body recursively (so
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
# Returns `[Array<String>, bool]` — the literal
|
|
133
|
-
# stream names + whether any dynamic registration
|
|
134
|
-
# was seen.
|
|
120
|
+
# Walks the channel body recursively (so `stream_from` / `stream_for` calls inside `subscribed`
|
|
121
|
+
# / helper methods are picked up). Returns `[Array<String>, bool]` — the literal stream names +
|
|
122
|
+
# whether any dynamic registration was seen.
|
|
135
123
|
def collect_stream_registrations(node, names: [], dynamic: false)
|
|
136
124
|
return [names, dynamic] if node.nil?
|
|
137
125
|
|
|
@@ -145,8 +133,7 @@ module Rigor
|
|
|
145
133
|
dynamic = true
|
|
146
134
|
end
|
|
147
135
|
when :stream_for
|
|
148
|
-
# Model-scoped stream — name is computed from
|
|
149
|
-
# the record at runtime; treat as dynamic.
|
|
136
|
+
# Model-scoped stream — name is computed from the record at runtime; treat as dynamic.
|
|
150
137
|
dynamic = true
|
|
151
138
|
end
|
|
152
139
|
end
|
|
@@ -3,23 +3,16 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Actioncable < Rigor::Plugin::Base
|
|
6
|
-
# Frozen catalogue of discovered ActionCable channel
|
|
7
|
-
#
|
|
8
|
-
# holds:
|
|
6
|
+
# Frozen catalogue of discovered ActionCable channel classes keyed by qualified class name. Each
|
|
7
|
+
# entry holds:
|
|
9
8
|
#
|
|
10
|
-
# - `action_methods` — the set of public instance
|
|
11
|
-
#
|
|
12
|
-
# (`subscribed` / `unsubscribed`). These are the
|
|
13
|
-
# methods clients invoke via
|
|
9
|
+
# - `action_methods` — the set of public instance methods that aren't ActionCable framework hooks
|
|
10
|
+
# (`subscribed` / `unsubscribed`). These are the methods clients invoke via
|
|
14
11
|
# `subscription.perform("action_name", data)`.
|
|
15
|
-
# - `stream_names` — the set of literal-string stream
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# separately as `dynamic_streams: true` so the
|
|
20
|
-
# analyzer can suppress unknown-stream warnings on
|
|
21
|
-
# any channel that has at least one dynamic
|
|
22
|
-
# registration.
|
|
12
|
+
# - `stream_names` — the set of literal-string stream names registered via `stream_from "name"`
|
|
13
|
+
# calls inside the channel body. Dynamic registrations (`stream_from interpolated_string`) are
|
|
14
|
+
# recorded separately as `dynamic_streams: true` so the analyzer can suppress unknown-stream
|
|
15
|
+
# warnings on any channel that has at least one dynamic registration.
|
|
23
16
|
class ChannelIndex
|
|
24
17
|
Entry = Data.define(:class_name, :file_path, :action_methods, :stream_names, :dynamic_streams) do
|
|
25
18
|
def includes_action?(name)
|
|
@@ -60,18 +53,14 @@ module Rigor
|
|
|
60
53
|
@by_name.keys
|
|
61
54
|
end
|
|
62
55
|
|
|
63
|
-
# All literal stream names registered across every
|
|
64
|
-
# discovered channel.
|
|
56
|
+
# All literal stream names registered across every discovered channel.
|
|
65
57
|
def all_stream_names
|
|
66
58
|
@entries.flat_map { |e| e.stream_names.to_a }.to_set
|
|
67
59
|
end
|
|
68
60
|
|
|
69
|
-
# True when at least one discovered channel uses a
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# missing" and skips the `unknown-stream` warning
|
|
73
|
-
# entirely — absence of a literal match doesn't prove
|
|
74
|
-
# the name is wrong.
|
|
61
|
+
# True when at least one discovered channel uses a dynamic stream registration. The analyzer
|
|
62
|
+
# treats this as "we can't be sure any literal name is missing" and skips the `unknown-stream`
|
|
63
|
+
# warning entirely — absence of a literal match doesn't prove the name is wrong.
|
|
75
64
|
def any_dynamic_streams?
|
|
76
65
|
@entries.any?(&:dynamic_streams)
|
|
77
66
|
end
|
|
@@ -8,14 +8,11 @@ require_relative "actioncable/analyzer"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
|
-
# rigor-actioncable — validates ActionCable
|
|
12
|
-
#
|
|
13
|
-
# `ActionCable.server.broadcast(stream_name, ...)`
|
|
14
|
-
# call sites against the discovered channel index.
|
|
11
|
+
# rigor-actioncable — validates ActionCable `<Channel>.broadcast_to(...)` and
|
|
12
|
+
# `ActionCable.server.broadcast(stream_name, ...)` call sites against the discovered channel index.
|
|
15
13
|
#
|
|
16
14
|
# Tier 3F of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
|
-
# Statically discovers channel classes by walking
|
|
18
|
-
# `channel_search_paths` and parsing each file with
|
|
15
|
+
# Statically discovers channel classes by walking `channel_search_paths` and parsing each file with
|
|
19
16
|
# Prism — no `actioncable` runtime dependency.
|
|
20
17
|
#
|
|
21
18
|
# ## Configuration
|
|
@@ -28,33 +25,22 @@ module Rigor
|
|
|
28
25
|
#
|
|
29
26
|
# ## What it checks
|
|
30
27
|
#
|
|
31
|
-
# 1. **Channel class existence** — `<X>.broadcast_to(...)`
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# registrations. The check is suppressed when ANY
|
|
39
|
-
# discovered channel uses a dynamic registration
|
|
40
|
-
# (`stream_from interpolated_string` or
|
|
41
|
-
# `stream_for record`) — the absence of a literal
|
|
42
|
-
# match doesn't prove absence.
|
|
28
|
+
# 1. **Channel class existence** — `<X>.broadcast_to(...)` where `X` ends in `Channel` must resolve
|
|
29
|
+
# to a discovered channel.
|
|
30
|
+
# 2. **Stream-name registration** — `ActionCable.server.broadcast("stream_name", ...)` with a
|
|
31
|
+
# literal stream name is checked against every discovered channel's `stream_from "..."`
|
|
32
|
+
# registrations. The check is suppressed when ANY discovered channel uses a dynamic registration
|
|
33
|
+
# (`stream_from interpolated_string` or `stream_for record`) — the absence of a literal match
|
|
34
|
+
# doesn't prove absence.
|
|
43
35
|
#
|
|
44
36
|
# ## Limitations
|
|
45
37
|
#
|
|
46
|
-
# - **Direct-superclass match only.** Indirect
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# -
|
|
51
|
-
#
|
|
52
|
-
# `subscription.perform("action_name", data)`; we
|
|
53
|
-
# don't analyse JS so the action-method index is
|
|
54
|
-
# informational only (deferred: cross-plugin handoff
|
|
55
|
-
# to a JS-side analyzer).
|
|
56
|
-
# - **`broadcast_to` arity isn't checked.** The method
|
|
57
|
-
# takes any record + any data hash; there's no
|
|
38
|
+
# - **Direct-superclass match only.** Indirect inheritance (`AdminChannel < BaseChannel <
|
|
39
|
+
# ApplicationCable::Channel`) needs `BaseChannel` listed in `channel_base_classes`.
|
|
40
|
+
# - **Action method invocations are not validated.** ActionCable actions are invoked from JS via
|
|
41
|
+
# `subscription.perform("action_name", data)`; we don't analyse JS so the action-method index is
|
|
42
|
+
# informational only (deferred: cross-plugin handoff to a JS-side analyzer).
|
|
43
|
+
# - **`broadcast_to` arity isn't checked.** The method takes any record + any data hash; there's no
|
|
58
44
|
# useful arity envelope.
|
|
59
45
|
class Actioncable < Rigor::Plugin::Base
|
|
60
46
|
manifest(
|
|
@@ -70,11 +56,9 @@ module Rigor
|
|
|
70
56
|
}
|
|
71
57
|
)
|
|
72
58
|
|
|
73
|
-
# `watch:` covers every `.rb` file under the channel search paths
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# the discoverer runs, so the `io_boundary` reads inside it are
|
|
77
|
-
# captured too.
|
|
59
|
+
# `watch:` covers every `.rb` file under the channel search paths so the cache invalidates when
|
|
60
|
+
# channels are added, removed, or edited (ADR-60 WD3). The dependency descriptor is recorded after
|
|
61
|
+
# the discoverer runs, so the `io_boundary` reads inside it are captured too.
|
|
78
62
|
producer :channel_index, watch: -> { [[@channel_search_paths, "**/*.rb"]] } do |_params|
|
|
79
63
|
ChannelDiscoverer.new(
|
|
80
64
|
io_boundary: io_boundary,
|
|
@@ -88,10 +72,9 @@ module Rigor
|
|
|
88
72
|
@channel_base_classes = Array(config.fetch("channel_base_classes")).map(&:to_s)
|
|
89
73
|
end
|
|
90
74
|
|
|
91
|
-
# File-level only: the load-error emission. Per-call broadcast
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# `producer_value`, shared by both surfaces.
|
|
75
|
+
# File-level only: the load-error emission. Per-call broadcast validation runs over the
|
|
76
|
+
# engine-owned walk via the node_rule below (ADR-37). The channel index is lazily loaded +
|
|
77
|
+
# memoised by `producer_value`, shared by both surfaces.
|
|
95
78
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
96
79
|
index = producer_value(:channel_index)
|
|
97
80
|
return [load_error_diagnostic(path)] if index.nil? && producer_error(:channel_index)
|
|
@@ -5,36 +5,25 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Plugin
|
|
7
7
|
class Actionmailer < Rigor::Plugin::Base
|
|
8
|
-
# Walks a parsed file's AST looking for
|
|
9
|
-
#
|
|
10
|
-
# each against the {MailerIndex}. Recognises both:
|
|
8
|
+
# Walks a parsed file's AST looking for `<MailerClass>.<action>(...)` calls and validates each
|
|
9
|
+
# against the {MailerIndex}. Recognises both:
|
|
11
10
|
#
|
|
12
|
-
# - `UserMailer.welcome(user)` — direct action call
|
|
13
|
-
# (the call returns a `Mail::Message` ready for
|
|
11
|
+
# - `UserMailer.welcome(user)` — direct action call (the call returns a `Mail::Message` ready for
|
|
14
12
|
# `.deliver_now` / `.deliver_later`).
|
|
15
|
-
# - `UserMailer.with(user: u).welcome` — parametrized
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# validated on the trailing `.welcome` invocation
|
|
19
|
-
# even though the receiver is a method-call chain
|
|
20
|
-
# rather than a constant.
|
|
13
|
+
# - `UserMailer.with(user: u).welcome` — parametrized action call. The `.with(...)` call is
|
|
14
|
+
# treated as a pass-through; the action's argument shape is validated on the trailing `.welcome`
|
|
15
|
+
# invocation even though the receiver is a method-call chain rather than a constant.
|
|
21
16
|
#
|
|
22
|
-
# The analyzer is purely syntactic: it does not look
|
|
23
|
-
#
|
|
24
|
-
# in the index are silently ignored — the rule has no
|
|
25
|
-
# opinion on non-mailer call shapes.
|
|
17
|
+
# The analyzer is purely syntactic: it does not look at runtime mailer state. Constants that don't
|
|
18
|
+
# appear in the index are silently ignored — the rule has no opinion on non-mailer call shapes.
|
|
26
19
|
module Analyzer
|
|
27
|
-
# `.with(...)` is recognised as a forwarding step:
|
|
28
|
-
# the
|
|
29
|
-
# so the trailing action-method call's class context
|
|
30
|
-
# is the same.
|
|
20
|
+
# `.with(...)` is recognised as a forwarding step: the receiver of `.with(...)` is the mailer
|
|
21
|
+
# class, so the trailing action-method call's class context is the same.
|
|
31
22
|
WITH_METHODS = %i[with].freeze
|
|
32
23
|
|
|
33
|
-
# Ruby method names that ActionMailer reserves on the
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# — the user almost certainly meant the framework
|
|
37
|
-
# method, not their own action.
|
|
24
|
+
# Ruby method names that ActionMailer reserves on the class itself. We don't validate against
|
|
25
|
+
# these as actions even if a mailer happens to override them — the user almost certainly meant
|
|
26
|
+
# the framework method, not their own action.
|
|
38
27
|
RESERVED_CLASS_METHODS = %i[
|
|
39
28
|
new allocate name superclass class
|
|
40
29
|
deliver_later deliver_now deliver_later! deliver_now!
|
|
@@ -45,16 +34,14 @@ module Rigor
|
|
|
45
34
|
method instance_method methods
|
|
46
35
|
].freeze
|
|
47
36
|
|
|
48
|
-
# One mailer-call observation. Carries no path/location — the
|
|
49
|
-
#
|
|
50
|
-
# `Plugin::Base#diagnostic`.
|
|
37
|
+
# One mailer-call observation. Carries no path/location — the caller (the `node_rule` block)
|
|
38
|
+
# positions it via `Plugin::Base#diagnostic`.
|
|
51
39
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
52
40
|
|
|
53
41
|
module_function
|
|
54
42
|
|
|
55
|
-
# The mailer-call violations for a single call node (0..2), or
|
|
56
|
-
#
|
|
57
|
-
# mailer. ADR-37: the engine owns the walk.
|
|
43
|
+
# The mailer-call violations for a single call node (0..2), or `[]` when it is not a
|
|
44
|
+
# `<Mailer>.action(...)` call on a known mailer. ADR-37: the engine owns the walk.
|
|
58
45
|
#
|
|
59
46
|
# @param call_node [Prism::Node]
|
|
60
47
|
# @param mailer_index [MailerIndex]
|
|
@@ -71,9 +58,8 @@ module Rigor
|
|
|
71
58
|
|
|
72
59
|
action_entry = class_entry.find_action(call_node.name)
|
|
73
60
|
if action_entry.nil?
|
|
74
|
-
# Skip `unknown-action` when the mailer's include set has any
|
|
75
|
-
#
|
|
76
|
-
# (gem-shipped concern, dynamically loaded extension).
|
|
61
|
+
# Skip `unknown-action` when the mailer's include set has any unresolved module — it may
|
|
62
|
+
# legitimately define the action (gem-shipped concern, dynamically loaded extension).
|
|
77
63
|
return [] if class_entry.unresolved_includes?
|
|
78
64
|
|
|
79
65
|
return [unknown_action_violation(call_node, class_entry)]
|
|
@@ -86,19 +72,16 @@ module Rigor
|
|
|
86
72
|
end
|
|
87
73
|
|
|
88
74
|
def action_call_candidate?(node)
|
|
89
|
-
# Skip anything that doesn't look like a mailer
|
|
90
|
-
# action call: no receiver, or a non-constant /
|
|
75
|
+
# Skip anything that doesn't look like a mailer action call: no receiver, or a non-constant /
|
|
91
76
|
# non-`.with(...)` receiver.
|
|
92
77
|
return false if node.receiver.nil?
|
|
93
78
|
|
|
94
79
|
mailer_class_for_call(node) ? true : false
|
|
95
80
|
end
|
|
96
81
|
|
|
97
|
-
# Extracts the mailer class name when the call's
|
|
98
|
-
# receiver is either:
|
|
82
|
+
# Extracts the mailer class name when the call's receiver is either:
|
|
99
83
|
# - A constant (`UserMailer.welcome(...)`), or
|
|
100
|
-
# - A `.with(...)` call whose receiver is a constant
|
|
101
|
-
# (`UserMailer.with(user: u).welcome`).
|
|
84
|
+
# - A `.with(...)` call whose receiver is a constant (`UserMailer.with(user: u).welcome`).
|
|
102
85
|
def mailer_class_for_call(node)
|
|
103
86
|
receiver = node.receiver
|
|
104
87
|
case receiver
|
|
@@ -125,13 +108,10 @@ module Rigor
|
|
|
125
108
|
actual = args.size
|
|
126
109
|
return nil if action_entry.accepts?(actual)
|
|
127
110
|
|
|
128
|
-
# Trailing keyword-hash relaxation. `Notify.foo(uid,
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
# arity 2. When the call's trailing arg is a kwargs
|
|
133
|
-
# hash, allow `(actual - 1) ≤ max_arity` so kwargs-
|
|
134
|
-
# carrying calls don't surface as wrong-arity.
|
|
111
|
+
# Trailing keyword-hash relaxation. `Notify.foo(uid, gid, success_count: 5)` is 3 positional
|
|
112
|
+
# args from Prism's perspective (2 + a KeywordHashNode); the action's `def foo(uid, gid,
|
|
113
|
+
# success_count:)` has arity 2. When the call's trailing arg is a kwargs hash, allow `(actual
|
|
114
|
+
# - 1) ≤ max_arity` so kwargs-carrying calls don't surface as wrong-arity.
|
|
135
115
|
return nil if args.last.is_a?(Prism::KeywordHashNode) && action_entry.accepts?(actual - 1)
|
|
136
116
|
|
|
137
117
|
Violation.new(
|