rigortype 0.3.8 → 0.4.0
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 +2 -2
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +112 -14
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -39,8 +39,6 @@ module Rigor
|
|
|
39
39
|
# `"::ChatChannel"`; see {ChannelDiscoverer}), while a QUERY may legitimately arrive rooted:
|
|
40
40
|
# `::ChatChannel.broadcast_to(...)` renders its receiver as `"::ChatChannel"`. The root marker is
|
|
41
41
|
# dropped here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
|
|
42
|
-
#
|
|
43
|
-
# @return [Entry, nil]
|
|
44
42
|
def find(class_name)
|
|
45
43
|
@by_name[strip_leading_namespace(class_name.to_s)]
|
|
46
44
|
end
|
|
@@ -29,6 +29,7 @@ module Rigor
|
|
|
29
29
|
|
|
30
30
|
# The `_later` twins enqueue an ActiveJob that broadcasts, so they carry the enqueue meaning too.
|
|
31
31
|
TURBO_LATER_SELECTORS = TURBO_SELECTORS.map { |name| "#{name.delete_suffix('_to')}_later_to" }.freeze
|
|
32
|
+
Ractor.make_shareable(TURBO_LATER_SELECTORS)
|
|
32
33
|
|
|
33
34
|
LATER = (BROADCASTING + ["rails.activejob.enqueue", "job.enqueue"]).freeze
|
|
34
35
|
|
|
@@ -65,6 +65,7 @@ module Rigor
|
|
|
65
65
|
class Actioncable < Rigor::Plugin::Base
|
|
66
66
|
manifest(
|
|
67
67
|
id: "actioncable",
|
|
68
|
+
target_gems: ["actioncable"],
|
|
68
69
|
# Bumped 2026-09-02 (#621) — channel keys and base-class names are de-rooted at the producer and a
|
|
69
70
|
# reopened class's actions and streams are UNIONed rather than clobbered, so a cached 0.1.0 index
|
|
70
71
|
# can be missing a whole channel (`< ::ApplicationCable::Channel`) or half its stream names.
|
|
@@ -122,12 +123,12 @@ module Rigor
|
|
|
122
123
|
@protocol_contracts || manifest.protocol_contracts
|
|
123
124
|
end
|
|
124
125
|
|
|
125
|
-
# File-level only: the load-error
|
|
126
|
+
# File-level only: the load-error disclosure. Per-call broadcast validation runs over the
|
|
126
127
|
# engine-owned walk via the node_rule below (ADR-37). The channel index is lazily loaded +
|
|
127
128
|
# memoised by `producer_value`, shared by both surfaces.
|
|
128
129
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
129
130
|
index = producer_value(:channel_index)
|
|
130
|
-
|
|
131
|
+
disclose_load_error if index.nil? && producer_error(:channel_index)
|
|
131
132
|
|
|
132
133
|
[]
|
|
133
134
|
end
|
|
@@ -150,10 +151,14 @@ module Rigor
|
|
|
150
151
|
"#{base}/**/*.rb"
|
|
151
152
|
end
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
# Issue #1056 — "the channel index did not load" is a fact about the run's INPUTS, not about the file being
|
|
155
|
+
# analysed, so it is registered rather than returned: see {Plugin::Base#disclose_once} for the
|
|
156
|
+
# channel, the `(plugin id, key)` de-duplication and why the engine positions it at `.rigor.yml:1:1`.
|
|
157
|
+
# Returned from the per-file hook it carried no once-guard at all and repeated on every file.
|
|
158
|
+
def disclose_load_error
|
|
154
159
|
error = producer_error(:channel_index)
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
disclose_once(
|
|
161
|
+
:channel_index_load_failed,
|
|
157
162
|
message: "rigor-actioncable: failed to discover channels: #{error.class}: #{error.message}",
|
|
158
163
|
severity: :warning,
|
|
159
164
|
rule: "load-error"
|
|
@@ -42,10 +42,6 @@ module Rigor
|
|
|
42
42
|
|
|
43
43
|
# The mailer-call violations for a single call node (0..2), or `[]` when it is not a
|
|
44
44
|
# `<Mailer>.action(...)` call on a known mailer. ADR-37: the engine owns the walk.
|
|
45
|
-
#
|
|
46
|
-
# @param call_node [Prism::Node]
|
|
47
|
-
# @param mailer_index [MailerIndex]
|
|
48
|
-
# @return [Array<Violation>]
|
|
49
45
|
def violations_for(call_node:, mailer_index:)
|
|
50
46
|
return [] unless call_node.is_a?(Prism::CallNode) && action_call_candidate?(call_node)
|
|
51
47
|
|
|
@@ -38,10 +38,9 @@ module Rigor
|
|
|
38
38
|
VIEW_FORMATS = %w[html text].freeze
|
|
39
39
|
VIEW_EXTENSIONS = %w[erb haml slim].freeze
|
|
40
40
|
|
|
41
|
-
# @param
|
|
42
|
-
# @param
|
|
43
|
-
# @param
|
|
44
|
-
# @param views_root [String] absolute or project-relative path to the views directory (typically
|
|
41
|
+
# @param search_paths — absolute or project-relative paths to scan for mailers.
|
|
42
|
+
# @param base_classes — direct superclasses that mark a class as a mailer.
|
|
43
|
+
# @param views_root — absolute or project-relative path to the views directory (typically
|
|
45
44
|
# `app/views`).
|
|
46
45
|
def initialize(io_boundary:, search_paths:, base_classes:, views_root: DEFAULT_VIEWS_ROOT)
|
|
47
46
|
@io_boundary = io_boundary
|
|
@@ -53,7 +52,6 @@ module Rigor
|
|
|
53
52
|
@views_root = views_root
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
# @return [MailerIndex]
|
|
57
55
|
def discover
|
|
58
56
|
# Two-pass: first collect every module's defs (for the include-following step), then build
|
|
59
57
|
# per-class entries that pull in actions from include'd modules. GitLab's `Notify` mailer
|
|
@@ -58,8 +58,6 @@ module Rigor
|
|
|
58
58
|
# `"::UserMailer"`; see {MailerDiscoverer}), while a QUERY may legitimately arrive rooted:
|
|
59
59
|
# `::UserMailer.welcome(u)` renders its receiver as `"::UserMailer"`. The root marker is dropped
|
|
60
60
|
# here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
|
|
61
|
-
#
|
|
62
|
-
# @return [ClassEntry, nil]
|
|
63
61
|
def find(class_name)
|
|
64
62
|
@by_name[strip_leading_namespace(class_name.to_s)]
|
|
65
63
|
end
|
|
@@ -68,9 +66,8 @@ module Rigor
|
|
|
68
66
|
@by_name.key?(strip_leading_namespace(class_name.to_s))
|
|
69
67
|
end
|
|
70
68
|
|
|
71
|
-
# @param file_path
|
|
69
|
+
# @param file_path — absolute path of a mailer file (canonicalised — see plugin entry's
|
|
72
70
|
# `harvest`)
|
|
73
|
-
# @return [ClassEntry, nil]
|
|
74
71
|
def find_by_file(file_path)
|
|
75
72
|
@entries.find { |entry| entry.file_path == file_path }
|
|
76
73
|
end
|
|
@@ -43,6 +43,7 @@ module Rigor
|
|
|
43
43
|
class Actionmailer < Rigor::Plugin::Base
|
|
44
44
|
manifest(
|
|
45
45
|
id: "actionmailer",
|
|
46
|
+
target_gems: ["actionmailer"],
|
|
46
47
|
# Bumped 2026-05-28 — extended RESERVED_CLASS_METHODS to include `respond_to?` / `public_send` /
|
|
47
48
|
# `send` / `__send__` / `method` and friends so dynamic-dispatch idioms
|
|
48
49
|
# (`Mailer.respond_to?(action)` / `Mailer.public_send(action)`) stop firing `unknown-action`
|
|
@@ -92,7 +93,10 @@ module Rigor
|
|
|
92
93
|
# loaded + memoised, shared.
|
|
93
94
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
94
95
|
index = producer_value(:mailer_index)
|
|
95
|
-
|
|
96
|
+
if index.nil? && producer_error(:mailer_index)
|
|
97
|
+
disclose_load_error
|
|
98
|
+
return []
|
|
99
|
+
end
|
|
96
100
|
return [] if index.nil? || index.empty?
|
|
97
101
|
|
|
98
102
|
missing_view_diagnostics(path, index)
|
|
@@ -135,10 +139,16 @@ module Rigor
|
|
|
135
139
|
File.expand_path(path)
|
|
136
140
|
end
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
# Issue #1056 — "the mailer index did not load" is a fact about the run's INPUTS, not about the file being
|
|
143
|
+
# analysed, so it is registered rather than returned: see {Plugin::Base#disclose_once} for the
|
|
144
|
+
# channel, the `(plugin id, key)` de-duplication and why the engine positions it at `.rigor.yml:1:1`.
|
|
145
|
+
# Returned from the per-file hook it carried no once-guard at all and repeated on every file.
|
|
146
|
+
# The missing-view rows this hook still returns are NOT disclosures: they name the mailer's own
|
|
147
|
+
# source file.
|
|
148
|
+
def disclose_load_error
|
|
139
149
|
error = producer_error(:mailer_index)
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
disclose_once(
|
|
151
|
+
:mailer_index_load_failed,
|
|
142
152
|
message: "rigor-actionmailer: failed to discover mailers: #{error.class}: #{error.message}",
|
|
143
153
|
severity: :warning,
|
|
144
154
|
rule: "load-error"
|
|
@@ -81,10 +81,8 @@ module Rigor
|
|
|
81
81
|
# duplicates on Mastodon and ~119 on Redmine, exactly stacked with the rails-routes diagnostics.
|
|
82
82
|
# Returns `[]` for unknown / arity-mismatch shapes.
|
|
83
83
|
#
|
|
84
|
-
# @param
|
|
85
|
-
# @param helper_table [Hash{String => Hash}] each entry carries `name`, `arity`,
|
|
84
|
+
# @param helper_table — each entry carries `name`, `arity`,
|
|
86
85
|
# `acceptable_arities`, `path`, `http_method`, `action`.
|
|
87
|
-
# @return [Array<Violation>]
|
|
88
86
|
def helper_violations_for(call_node:, helper_table:)
|
|
89
87
|
return [] unless implicit_helper_call?(call_node)
|
|
90
88
|
|
|
@@ -108,10 +106,7 @@ module Rigor
|
|
|
108
106
|
# each filter name reference is validated against it. Calls outside a known controller
|
|
109
107
|
# contribute nothing.
|
|
110
108
|
#
|
|
111
|
-
# @param
|
|
112
|
-
# @param ancestors [Array<Prism::Node>] the lexical ancestor chain
|
|
113
|
-
# @param controller_index [ControllerIndex]
|
|
114
|
-
# @return [Array<Violation>]
|
|
109
|
+
# @param ancestors — the lexical ancestor chain
|
|
115
110
|
def filter_violations_for(call_node:, ancestors:, controller_index:)
|
|
116
111
|
return [] unless filter_call?(call_node)
|
|
117
112
|
|
|
@@ -144,10 +139,6 @@ module Rigor
|
|
|
144
139
|
# AR model's column list (looked up via the model_index fact published by `rigor-activerecord`).
|
|
145
140
|
# Calls whose `:require` argument is a non-literal Symbol are passed through; namespaced models
|
|
146
141
|
# (`params.require(:admin_user)` → `Admin::User`) are deferred.
|
|
147
|
-
#
|
|
148
|
-
# @param call_node [Prism::Node]
|
|
149
|
-
# @param model_index [Hash{String => Hash}]
|
|
150
|
-
# @return [Array<Violation>]
|
|
151
142
|
def permit_violations_for(call_node:, model_index:)
|
|
152
143
|
return [] unless permit_chain?(call_node)
|
|
153
144
|
|
|
@@ -179,13 +170,12 @@ module Rigor
|
|
|
179
170
|
# method that doesn't call `render`) is also skipped — Phase 3 validates explicit renders only,
|
|
180
171
|
# since the implicit path would false-positive on `redirect_to` / `head` / early returns.
|
|
181
172
|
#
|
|
182
|
-
# @param
|
|
183
|
-
# @param
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def render_violations_for(call_node:, ancestors:, path:, view_search_roots:, controller_index: nil)
|
|
173
|
+
# @param ancestors — the lexical ancestor chain
|
|
174
|
+
# @param io_boundary — the plugin's {Rigor::Plugin::IoBoundary}; #629 — every filesystem
|
|
175
|
+
# question this phase asks goes through it, so "no such template" is a recorded dependency and
|
|
176
|
+
# the run-result cache invalidates once the template appears
|
|
177
|
+
def render_violations_for(call_node:, ancestors:, path:, view_search_roots:, io_boundary:,
|
|
178
|
+
controller_index: nil)
|
|
189
179
|
return [] unless render_call?(call_node)
|
|
190
180
|
|
|
191
181
|
class_name = enclosing_controller_name(ancestors)
|
|
@@ -217,12 +207,12 @@ module Rigor
|
|
|
217
207
|
# `app/views/admin/settings/` holds about/, appearance/, … but no top-level templates). Skip
|
|
218
208
|
# render checks for these — the diagnostic would be a false positive against intentional Rails
|
|
219
209
|
# abstract-base layouts.
|
|
220
|
-
return [] if abstract_base_controller?(class_name, controller_path, view_search_roots)
|
|
210
|
+
return [] if abstract_base_controller?(class_name, controller_path, view_search_roots, io_boundary)
|
|
221
211
|
|
|
222
212
|
target = render_target_for(call_node, controller_path)
|
|
223
213
|
return [] if target.nil?
|
|
224
214
|
|
|
225
|
-
violation = render_violation(call_node, target, view_search_roots)
|
|
215
|
+
violation = render_violation(call_node, target, view_search_roots, io_boundary)
|
|
226
216
|
violation ? [violation] : []
|
|
227
217
|
end
|
|
228
218
|
|
|
@@ -406,15 +396,16 @@ module Rigor
|
|
|
406
396
|
# controllers.
|
|
407
397
|
# Deliberately NOT triggered by "no view directory at all" because that fires the diagnostic we
|
|
408
398
|
# DO want for genuinely-missing views (the typo / forgot-to-create case).
|
|
409
|
-
def abstract_base_controller?(class_name, controller_path, view_search_roots)
|
|
399
|
+
def abstract_base_controller?(class_name, controller_path, view_search_roots, io_boundary)
|
|
410
400
|
return true if class_name.end_with?("BaseController")
|
|
411
401
|
|
|
412
402
|
view_search_roots.any? do |root|
|
|
413
403
|
dir = File.join(root, controller_path)
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
entries
|
|
404
|
+
# #629 — the listing row this records is the same one {#locate_template} needs, and the
|
|
405
|
+
# boundary deduplicates it per directory, so the whole render phase costs one glob
|
|
406
|
+
# validation per consulted view directory on a warm run.
|
|
407
|
+
entries = io_boundary.list_directory(dir)
|
|
408
|
+
entries.any? && entries.all? { |e| File.directory?(e) }
|
|
418
409
|
end
|
|
419
410
|
end
|
|
420
411
|
|
|
@@ -473,9 +464,9 @@ module Rigor
|
|
|
473
464
|
Rigor::Plugin::Inflector.underscore(stripped)
|
|
474
465
|
end
|
|
475
466
|
|
|
476
|
-
def render_violation(call_node, target, view_search_roots)
|
|
467
|
+
def render_violation(call_node, target, view_search_roots, io_boundary)
|
|
477
468
|
kind, relative = target
|
|
478
|
-
existing = locate_template(relative, view_search_roots)
|
|
469
|
+
existing = locate_template(relative, view_search_roots, io_boundary)
|
|
479
470
|
if existing
|
|
480
471
|
render_target_violation(call_node, kind, relative, existing)
|
|
481
472
|
else
|
|
@@ -483,11 +474,22 @@ module Rigor
|
|
|
483
474
|
end
|
|
484
475
|
end
|
|
485
476
|
|
|
486
|
-
|
|
477
|
+
# #629 — resolved from the DIRECTORY LISTING, not from a probe per candidate path. The old shape
|
|
478
|
+
# asked `File.file?` up to `extensions × roots` times per `render` call and recorded nothing, so a
|
|
479
|
+
# cached run kept reporting `plugin.actionpack.missing-template` after the template was written.
|
|
480
|
+
# One {Rigor::Plugin::IoBoundary#list_directory} row per `app/views/<controller>` directory carries
|
|
481
|
+
# the dependency instead: any file added or removed there invalidates the run, and the warm-run
|
|
482
|
+
# validation count is the number of view directories the run consulted rather than the number of
|
|
483
|
+
# candidate paths it tried.
|
|
484
|
+
def locate_template(relative, view_search_roots, io_boundary)
|
|
485
|
+
basename = File.basename(relative)
|
|
487
486
|
view_search_roots.each do |root|
|
|
487
|
+
# Names, not paths: the boundary answers in absolute paths while `root` may be the relative
|
|
488
|
+
# `app/views` the user configured, and the reported candidate keeps the configured spelling.
|
|
489
|
+
listing = io_boundary.list_directory(File.join(root, File.dirname(relative)))
|
|
490
|
+
names = listing.map { |entry| File.basename(entry) }
|
|
488
491
|
RENDER_TEMPLATE_EXTENSIONS.each do |ext|
|
|
489
|
-
|
|
490
|
-
return candidate if File.file?(candidate)
|
|
492
|
+
return File.join(root, "#{relative}#{ext}") if names.include?("#{basename}#{ext}")
|
|
491
493
|
end
|
|
492
494
|
end
|
|
493
495
|
nil
|
|
@@ -42,8 +42,6 @@ module Rigor
|
|
|
42
42
|
# `"Admin::AccountsController"` — never `"::AccountsController"`; see {ControllerDiscoverer}),
|
|
43
43
|
# while a QUERY may legitimately arrive rooted. The root marker is dropped at every lookup entry
|
|
44
44
|
# point, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
|
|
45
|
-
#
|
|
46
|
-
# @return [Entry, nil]
|
|
47
45
|
def find(class_name)
|
|
48
46
|
@entries[strip_leading_namespace(class_name)]
|
|
49
47
|
end
|
|
@@ -84,7 +82,7 @@ module Rigor
|
|
|
84
82
|
methods.uniq.freeze
|
|
85
83
|
end
|
|
86
84
|
|
|
87
|
-
# @return
|
|
85
|
+
# @return true when the class has at least one include OR parent class we couldn't
|
|
88
86
|
# resolve in the index (typically a gem-shipped concern such as Devise's
|
|
89
87
|
# `Devise::Controllers::Helpers`, or a gem-shipped parent controller such as
|
|
90
88
|
# `Devise::ConfirmationsController` or `Doorkeeper::AuthorizedApplicationsController`). Phase
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/source/node_children"
|
|
4
|
+
|
|
5
|
+
require "prism"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Plugin
|
|
9
|
+
class Actionpack < Rigor::Plugin::Base
|
|
10
|
+
# The syntactic controller walk two parent-side indexes share: {ViewAssigns}, which reads what a
|
|
11
|
+
# controller action ASSIGNED, and {RenderLocals} (#1047), which reads what a render site PASSED.
|
|
12
|
+
#
|
|
13
|
+
# Both run on the parent before any analysis — `#template_units_for_file` is called before the
|
|
14
|
+
# engine has indexed anything — so neither may ask the typer a question, and both need the same four
|
|
15
|
+
# answers off a Prism tree: which class declarations in this file are controllers, what their
|
|
16
|
+
# qualified names are, which view directory each one owns, and which `def`s sit directly in the body.
|
|
17
|
+
# Keeping one copy is what stops the two indexes from disagreeing about, say, whether
|
|
18
|
+
# `module Admin; class UsersController` is `admin/users` — a disagreement that would be silent, since
|
|
19
|
+
# each side would simply find nothing.
|
|
20
|
+
#
|
|
21
|
+
# It deliberately stops at the shapes {ControllerDiscoverer} already recognises. Anything a
|
|
22
|
+
# controller does at runtime — `define_method`, a concern that adds actions on include — is not read
|
|
23
|
+
# here and is not read there either.
|
|
24
|
+
module ControllerScan
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Yields `[class_node, enclosing_namespace_segments]` for every `ClassNode` in the tree, with the
|
|
28
|
+
# enclosing `module` chain accumulated. Mirrors {ControllerDiscoverer#walk_declarations}'s
|
|
29
|
+
# qualification reduced to the two shapes that can carry an action: `class Admin::UsersController`
|
|
30
|
+
# and `module Admin; class UsersController`.
|
|
31
|
+
def each_controller(node, namespace, &)
|
|
32
|
+
return unless node.is_a?(Prism::Node)
|
|
33
|
+
|
|
34
|
+
if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
|
|
35
|
+
yield node, namespace if node.is_a?(Prism::ClassNode)
|
|
36
|
+
inner = namespace + constant_segments(node.constant_path)
|
|
37
|
+
each_controller(node.body, inner, &) if node.body
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
node.rigor_each_child { |child| each_controller(child, namespace, &) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def constant_segments(path)
|
|
45
|
+
case path
|
|
46
|
+
when Prism::ConstantReadNode then [path.name.to_s]
|
|
47
|
+
when Prism::ConstantPathNode then constant_segments(path.parent) + [path.name.to_s]
|
|
48
|
+
else []
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# `["Admin", "UsersController"]` → `"admin/users"`. `ApplicationController` and the other abstract
|
|
53
|
+
# bases are not excluded: an action they define really is inherited, and a template under their own
|
|
54
|
+
# path simply never exists. nil for a class whose name is not a controller's.
|
|
55
|
+
def controller_path(segments)
|
|
56
|
+
return nil unless segments.last&.end_with?("Controller")
|
|
57
|
+
|
|
58
|
+
parts = segments.map { |segment| underscore(segment) }
|
|
59
|
+
parts[-1] = parts[-1].sub(/_controller\z/, "")
|
|
60
|
+
return nil if parts[-1].empty?
|
|
61
|
+
|
|
62
|
+
parts.join("/")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The ASCII-only inflection this needs: a controller constant is a CamelCase identifier, and
|
|
66
|
+
# nothing here has to invert an irregular plural (`ActiveSupport::Inflector` is the analysed
|
|
67
|
+
# project's, not Rigor's).
|
|
68
|
+
def underscore(segment)
|
|
69
|
+
segment.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
70
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
71
|
+
.downcase
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# `{ :show => body_node }` for every `def` directly in the class body.
|
|
75
|
+
def method_bodies(node)
|
|
76
|
+
body = node.body
|
|
77
|
+
return {} if body.nil?
|
|
78
|
+
|
|
79
|
+
body.child_nodes.compact.filter_map do |child|
|
|
80
|
+
next nil unless child.is_a?(Prism::DefNode) && child.receiver.nil?
|
|
81
|
+
|
|
82
|
+
[child.name, child.body]
|
|
83
|
+
end.to_h
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Every implicit-self `render` call node under `node`, at any depth.
|
|
87
|
+
def each_render(node, &)
|
|
88
|
+
return unless node.is_a?(Prism::Node)
|
|
89
|
+
|
|
90
|
+
yield node if node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :render
|
|
91
|
+
node.rigor_each_child { |child| each_render(child, &) }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -31,6 +31,11 @@ module Rigor
|
|
|
31
31
|
module Effects
|
|
32
32
|
CONTROLLER = "ActionController::Base"
|
|
33
33
|
|
|
34
|
+
# The view context a template unit's body runs under — the `self_type:` the plugin declares on
|
|
35
|
+
# every {Rigor::Plugin::TemplateUnit}. A `render` inside a template is an implicit-self call on
|
|
36
|
+
# it, which is what lets one row colour the template → partial edge (#1048).
|
|
37
|
+
VIEW = "ActionView::Base"
|
|
38
|
+
|
|
34
39
|
RESPONSE = ["mutate.self", "rails.response.write"].freeze
|
|
35
40
|
SESSION_WRITE = ["mutate", "rails.session.write"].freeze
|
|
36
41
|
SESSION_READ = ["io", "rails.session.read"].freeze
|
|
@@ -46,6 +51,26 @@ module Rigor
|
|
|
46
51
|
# the one genuinely misleading row in the whole Rails layer.
|
|
47
52
|
RENDERERS = %w[render render_to_string render_to_body].freeze
|
|
48
53
|
|
|
54
|
+
# Rails' own name for the implicit render — what `ActionController::Base` calls when an action
|
|
55
|
+
# returns without having answered. The project never writes it, which is exactly the point: the
|
|
56
|
+
# row exists so the engine's unit-level {Rigor::Effects::CalleeRule} has a declaration to read,
|
|
57
|
+
# and it carries no labels because an action that implicitly renders has already been coloured by
|
|
58
|
+
# nothing at all and must not be coloured by a guess (see `why:`).
|
|
59
|
+
IMPLICIT_RENDER = "default_render"
|
|
60
|
+
|
|
61
|
+
# Action View's own format lookup order for a template-side render, as DATA the engine's
|
|
62
|
+
# `rails_render_partial` rule copies onto the edge (#1065). While a `.js.erb` template renders, the
|
|
63
|
+
# lookup context holds `[:js, :html]` — `LookupContext#formats=` appends `:html` to a lone `:js` —
|
|
64
|
+
# so `render "watchers"` runs `_watchers.js.erb` where it exists and `_watchers.html.erb`
|
|
65
|
+
# otherwise. The propagator takes the first key a unit answers.
|
|
66
|
+
#
|
|
67
|
+
# `js` is the only entry, because it is the only fallback Action View itself hard-codes. What a
|
|
68
|
+
# `.json`, `.xml` or `.turbo_stream` template falls back to is whatever the REQUEST's formats
|
|
69
|
+
# happened to be (`prepend_formats` puts the template's format in front of them) — an `Accept`
|
|
70
|
+
# header the source never states, Turbo's included — so those keep their taint rather than guess.
|
|
71
|
+
FORMAT_FALLBACKS = { "js" => ["html"] }.freeze
|
|
72
|
+
Ractor.make_shareable(FORMAT_FALLBACKS)
|
|
73
|
+
|
|
49
74
|
# The cookie jars a Rails app writes through.
|
|
50
75
|
COOKIE_JARS = ["self.cookies", "self.cookies.signed", "self.cookies.encrypted",
|
|
51
76
|
"self.cookies.permanent"].freeze
|
|
@@ -59,35 +84,67 @@ module Rigor
|
|
|
59
84
|
def response_rows
|
|
60
85
|
RESPONSE_WRITERS.map do |selector|
|
|
61
86
|
EffectAttribution.new(
|
|
62
|
-
receiver: CONTROLLER, method: selector, labels: RESPONSE, discharge: true,
|
|
87
|
+
receiver: CONTROLLER, method: selector, labels: RESPONSE, discharge: true, responds: true,
|
|
63
88
|
why: "sets the response on the controller instance — Rack writes the socket later, outside " \
|
|
64
89
|
"any project method, so this is `mutate.self` and deliberately not `io`"
|
|
65
90
|
)
|
|
66
|
-
end + render_rows
|
|
91
|
+
end + render_rows + [implicit_render_row]
|
|
67
92
|
end
|
|
68
93
|
|
|
94
|
+
# `render` and its two `*_to_string` twins. Each carries a `callee:` so the render site edges to
|
|
95
|
+
# the template's own effect unit (#1048), and each keeps the `template-not-analysed` taint — which
|
|
96
|
+
# now rides the edge: a render whose template produced a unit discharges it, and one whose
|
|
97
|
+
# template the plugin declined (a layout, #1047) or whose target is computed keeps it.
|
|
98
|
+
#
|
|
99
|
+
# Only `render` sets `responds:`. `render_to_string` builds a string and leaves the response
|
|
100
|
+
# unanswered, so an action that calls it and returns still takes Rails' implicit render.
|
|
69
101
|
def render_rows
|
|
70
102
|
RENDERERS.map do |selector|
|
|
71
103
|
EffectAttribution.new(
|
|
72
104
|
receiver: CONTROLLER, method: selector, labels: RESPONSE, discharge: true,
|
|
73
|
-
taint: "template-not-analysed",
|
|
105
|
+
taint: "template-not-analysed", callee: "rails_render", responds: selector == "render",
|
|
74
106
|
why: "sets the response body from a template. The controller half is fully stated; the " \
|
|
75
|
-
"template's own effects
|
|
76
|
-
"
|
|
107
|
+
"template's own effects reach it through the `rails_render` callee edge, and the " \
|
|
108
|
+
"taint survives on the edge for a target the rule cannot resolve"
|
|
77
109
|
)
|
|
78
|
-
end
|
|
110
|
+
end + [
|
|
111
|
+
EffectAttribution.new(
|
|
112
|
+
receiver: VIEW, method: :render, labels: [], discharge: true,
|
|
113
|
+
taint: "template-not-analysed", callee: "rails_render_partial",
|
|
114
|
+
callee_fallbacks: FORMAT_FALLBACKS,
|
|
115
|
+
why: "a `render` inside a template runs another template. The edge is the whole " \
|
|
116
|
+
"contribution — what a partial render DOES is what the partial does, and the row " \
|
|
117
|
+
"reaches it now — while a target the rule cannot resolve keeps the taint. In a view " \
|
|
118
|
+
"a bare argument names a PARTIAL and `layout:` names one too, which is why this is a " \
|
|
119
|
+
"separate rule from the controller's"
|
|
120
|
+
)
|
|
121
|
+
]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The implicit render, as an EDGE and nothing else. An action that never answered still renders
|
|
125
|
+
# `<controller>/<action>`, so its summary must include that template's effects — but the fact is
|
|
126
|
+
# the absence of a call, so there is no site to colour, and inventing a `rails.response.write`
|
|
127
|
+
# here would put it on every private helper a controller defines as well.
|
|
128
|
+
def implicit_render_row
|
|
129
|
+
EffectAttribution.new(
|
|
130
|
+
receiver: CONTROLLER, method: IMPLICIT_RENDER, labels: [], discharge: true,
|
|
131
|
+
callee: "rails_implicit_render",
|
|
132
|
+
why: "Rails renders `<controller>/<action>` for an action that answered nothing. The edge " \
|
|
133
|
+
"is the whole contribution: no label, because an implicit render is observed from a " \
|
|
134
|
+
"body that made no call, and a label read off an absence would colour every helper"
|
|
135
|
+
)
|
|
79
136
|
end
|
|
80
137
|
|
|
81
138
|
# `send_file` streams from disk; `send_data` does not.
|
|
82
139
|
def file_rows
|
|
83
140
|
[
|
|
84
141
|
EffectAttribution.new(
|
|
85
|
-
receiver: CONTROLLER, method: :send_data, labels: RESPONSE, discharge: true,
|
|
142
|
+
receiver: CONTROLLER, method: :send_data, labels: RESPONSE, discharge: true, responds: true,
|
|
86
143
|
why: "sets the response body from an in-memory string"
|
|
87
144
|
),
|
|
88
145
|
EffectAttribution.new(
|
|
89
146
|
receiver: CONTROLLER, method: :send_file, labels: RESPONSE + ["io.fs.read"], discharge: true,
|
|
90
|
-
why: "sets the response AND reads the named file off disk"
|
|
147
|
+
responds: true, why: "sets the response AND reads the named file off disk"
|
|
91
148
|
)
|
|
92
149
|
]
|
|
93
150
|
end
|