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
|
@@ -5,7 +5,9 @@ require "prism"
|
|
|
5
5
|
require_relative "../source/constant_path"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
7
|
require_relative "attribution"
|
|
8
|
+
require_relative "callee_rule"
|
|
8
9
|
require_relative "catalog"
|
|
10
|
+
require_relative "definition_context"
|
|
9
11
|
require_relative "envelope_index"
|
|
10
12
|
require_relative "file_collection"
|
|
11
13
|
require_relative "label_set"
|
|
@@ -35,12 +37,62 @@ module Rigor
|
|
|
35
37
|
# Long by construction: the walk carries one `when` per Ruby construct that originates an effect, and
|
|
36
38
|
# splitting that table across classes would put the vocabulary in one file and the reasons in another.
|
|
37
39
|
class UnitScan # rubocop:disable Metrics/ClassLength
|
|
38
|
-
# `$~` and
|
|
39
|
-
#
|
|
40
|
-
|
|
40
|
+
# `$~` and `$_` are frame-local, not global state (#1363): Ruby keeps them in the special-variable slot
|
|
41
|
+
# of the body that runs them. The blocks that body creates reach the same slot, except the root block
|
|
42
|
+
# of a `Thread.new`, `Fiber.new` or `Ractor.new`, which has its own; a call into a method defined with
|
|
43
|
+
# `def` does not reach it. A read of one is not `global.read`, and a write (`$_ = line`, `$~ = nil`)
|
|
44
|
+
# binds only that slot, so it earns no label, as a local-variable write earns none — except in a
|
|
45
|
+
# `define_method` body (`@shared_slot`), which runs on the slot of the body that defined it and so
|
|
46
|
+
# shares it with every sibling defined there. The rest of the match family (`$&`, `` $` ``, `$'`, `$+`,
|
|
47
|
+
# `$1`…) are Prism nodes of their own that this scan does not colour, and none can be assigned.
|
|
48
|
+
FRAME_LOCAL_GLOBALS = %i[$~ $_].to_set.freeze
|
|
49
|
+
|
|
50
|
+
# `$!` and `$@` are the exception being rescued and its backtrace. They are not frame-local: a read
|
|
51
|
+
# reaches the dynamically enclosing rescue clause, whichever frame runs it — a caller's, or a callee's
|
|
52
|
+
# that yields to a block written here. So `$!` is an implicit argument of the running call rather than
|
|
53
|
+
# program state, and a read of it is not `global.read`. A read of `$@` reads that exception's backtrace
|
|
54
|
+
# as `e.backtrace` would, and a read of an object's state is never labelled. A write is another matter
|
|
55
|
+
# and stays `gvar-write`: `$@ = bt` changes that object, which the rescuing frame observes
|
|
56
|
+
# (`rescue => e` sees the new backtrace). Ruby refuses `$! = x`.
|
|
57
|
+
RESCUED_EXCEPTION_GLOBALS = %i[$! $@].to_set.freeze
|
|
58
|
+
|
|
59
|
+
# The globals whose read is not `global.read`.
|
|
60
|
+
UNCOLOURED_READS = (FRAME_LOCAL_GLOBALS | RESCUED_EXCEPTION_GLOBALS).freeze
|
|
41
61
|
|
|
42
62
|
REFLECTIVE_SEND = %i[send public_send __send__].to_set.freeze
|
|
43
63
|
|
|
64
|
+
# The constructs under which a call may not run (#1048). Only the `responds:` bit reads this, and
|
|
65
|
+
# only to refuse to record a response the body might not perform: `render :edit if x` leaves the
|
|
66
|
+
# other path taking Rails' implicit render.
|
|
67
|
+
#
|
|
68
|
+
# The modifier forms need no entry of their own — Prism spells `render :x if y` as an ordinary
|
|
69
|
+
# `IfNode` — and `RescueNode` is the rescue CLAUSE rather than the body it guards, so a `render` in
|
|
70
|
+
# the `begin` half of `begin … rescue … end` is at depth zero, which is right: that half runs.
|
|
71
|
+
#
|
|
72
|
+
# A `LambdaNode` is here for the same reason and needs no exemption: `@after = -> { redirect_to
|
|
73
|
+
# "/" }` stores a response rather than performing one, and the body may never be called at all.
|
|
74
|
+
#
|
|
75
|
+
# A **block** is branching too, but is answered by {#branching?} rather than listed here, because
|
|
76
|
+
# only the CALL that owns it can say whether it is one. `User.transaction { redirect_to "/" }`,
|
|
77
|
+
# `[1].each { redirect_to "/" }` and `x.presence&.then { … }` all contain a call the body may not
|
|
78
|
+
# make, and recording a response from one would drop the implicit-render edge AND leave the unit
|
|
79
|
+
# reading exhaustive — the combination this bit exists to prevent.
|
|
80
|
+
BRANCHING = [
|
|
81
|
+
Prism::IfNode, Prism::UnlessNode, Prism::CaseNode, Prism::CaseMatchNode,
|
|
82
|
+
Prism::WhileNode, Prism::UntilNode, Prism::ForNode,
|
|
83
|
+
Prism::RescueNode, Prism::RescueModifierNode, Prism::AndNode, Prism::OrNode,
|
|
84
|
+
Prism::LambdaNode
|
|
85
|
+
].to_set.freeze
|
|
86
|
+
|
|
87
|
+
# The one block a response may be recorded through: `respond_to`'s own. It is the format dispatcher
|
|
88
|
+
# rather than a conditional, and it always runs — but its ARMS do not, so `f.html { … }` is an
|
|
89
|
+
# ordinary branching block and a `render json:` in the `f.json` arm no longer stands the `f.html`
|
|
90
|
+
# arm's implicit render down (#1048). The dispatcher's other half is the per-arm conventional edge
|
|
91
|
+
# (#1071): each `format.<fmt>` arm the body spells renders `<action>.<fmt>` where the arm's block
|
|
92
|
+
# does not answer on every path of its own body, exactly as an uninstrumented action renders
|
|
93
|
+
# `<action>.html`. The arms are read off the same syntax that marks the block transparent.
|
|
94
|
+
DISPATCH_SELECTORS = %i[respond_to respond_with].to_set.freeze
|
|
95
|
+
|
|
44
96
|
# Selectors a per-class POSTURE default must never answer for, because a more specific reading of
|
|
45
97
|
# the same site exists and would be swallowed: `send` and friends are the `dynamic-send` taint, and
|
|
46
98
|
# `call` is the `opaque-callable` one. An explicit ROW still wins (`Fiddle::Function#call` is
|
|
@@ -85,9 +137,10 @@ module Rigor
|
|
|
85
137
|
# declaration wherever it appears: in a class body it is the only way that method exists, and inside
|
|
86
138
|
# another method it is a definition the enclosing method performs (`mutate.static`) rather than code
|
|
87
139
|
# the enclosing method contains. A non-literal name has no key to file the block under, so it stays
|
|
88
|
-
# contained in the enclosing method and this returns nil.
|
|
140
|
+
# contained in the enclosing method and this returns nil. Which side the method lands on is the
|
|
141
|
+
# {DefinitionContext}'s to say.
|
|
89
142
|
#
|
|
90
|
-
# @return
|
|
143
|
+
# @return `[name, body, parameters]`
|
|
91
144
|
def self.define_method_unit(node)
|
|
92
145
|
return nil unless node.name == :define_method && node.receiver.nil?
|
|
93
146
|
|
|
@@ -97,26 +150,42 @@ module Rigor
|
|
|
97
150
|
block = node.block
|
|
98
151
|
return nil unless block.is_a?(Prism::BlockNode)
|
|
99
152
|
|
|
100
|
-
[first.unescaped,
|
|
153
|
+
[first.unescaped, block.body, block.parameters]
|
|
101
154
|
end
|
|
102
155
|
|
|
103
|
-
# @param
|
|
104
|
-
# `
|
|
105
|
-
#
|
|
156
|
+
# @param context — the {DefinitionContext} the body runs under. Its singleton bit — whether the
|
|
157
|
+
# unit's `self` is a class object (`def self.x`, a `def` in `class << self`) — is the axis that
|
|
158
|
+
# separates `mutate.self` from `mutate.static` on an ivar write; the rest says where the units
|
|
159
|
+
# nested in the body land
|
|
160
|
+
# @param block_parameter — the unit's `&blk` parameter name, if any; a call on it is
|
|
106
161
|
# forwarding, not an opaque callable
|
|
107
|
-
# @param calls
|
|
108
|
-
# @param attribution
|
|
109
|
-
# @param envelopes
|
|
110
|
-
# @param plugin_facts
|
|
111
|
-
# @param owner_class
|
|
162
|
+
# @param calls — node-identity table of {Collector::CallRecord}s
|
|
163
|
+
# @param attribution — the project's `effects.attribution:` table
|
|
164
|
+
# @param envelopes — the envelopes a call site may import as a `≤` bound (#386)
|
|
165
|
+
# @param plugin_facts — the loaded plugins' `effect_attributions:` (#387)
|
|
166
|
+
# @param owner_class — the class this unit is defined on — the carrier an
|
|
112
167
|
# implicit-self call's envelope is looked up under, since the syntax spells `Kernel#name`
|
|
113
|
-
# @param method_name
|
|
168
|
+
# @param method_name — this unit's own selector — what a `super` in its body names as
|
|
114
169
|
# the target the propagator resolves above `owner_class` (#446). With no name to state, a `super`
|
|
115
170
|
# taints instead.
|
|
116
|
-
|
|
171
|
+
# @param non_public — whether the class body declared this unit `private` or `protected`
|
|
172
|
+
# (#1048). Only a UNIT callee rule reads it, and only to decline: Rails' `action_methods` is a
|
|
173
|
+
# controller's PUBLIC instance methods, so a private helper is never implicitly rendered — while
|
|
174
|
+
# a project that happens to ship a template of the same name would otherwise hand that
|
|
175
|
+
# template's effects to the helper.
|
|
176
|
+
# @param shared_slot — whether the body is a `define_method` block, which runs on the special-variable
|
|
177
|
+
# slot of the body that defined it rather than on one of its own (#1363). A write to a
|
|
178
|
+
# {FRAME_LOCAL_GLOBALS} name there reaches every sibling defined in that body, so it stays
|
|
179
|
+
# `global.write`.
|
|
180
|
+
def initialize(context:, parameters:, block_parameter:, owned_locals:, calls:, # rubocop:disable Metrics/ParameterLists
|
|
117
181
|
attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
|
|
118
|
-
plugin_facts: PluginFacts.empty, owner_class: nil, method_name: nil
|
|
182
|
+
plugin_facts: PluginFacts.empty, owner_class: nil, method_name: nil,
|
|
183
|
+
non_public: false, shared_slot: false)
|
|
184
|
+
singleton = context.singleton?
|
|
119
185
|
@singleton = singleton
|
|
186
|
+
# The context at the walk's current position. It starts as the body's own and moves only inside a
|
|
187
|
+
# block or `class << self` body that rebinds `self`; the unit's singleton bit does not move with it.
|
|
188
|
+
@context = context
|
|
120
189
|
@block_parameter = block_parameter
|
|
121
190
|
@calls = calls
|
|
122
191
|
@attribution = attribution
|
|
@@ -124,6 +193,8 @@ module Rigor
|
|
|
124
193
|
@plugin_facts = plugin_facts
|
|
125
194
|
@owner_class = owner_class
|
|
126
195
|
@method_name = method_name
|
|
196
|
+
@non_public = non_public ? true : false
|
|
197
|
+
@shared_slot = shared_slot ? true : false
|
|
127
198
|
@mutation = MutationClassifier.new(
|
|
128
199
|
singleton: singleton, parameters: parameters, owned_locals: owned_locals
|
|
129
200
|
)
|
|
@@ -133,10 +204,44 @@ module Rigor
|
|
|
133
204
|
@edges = []
|
|
134
205
|
@nested = []
|
|
135
206
|
@delegates_upward = false
|
|
207
|
+
# #1048 — whether a plugin row marked `responds:` fired **unconditionally** in this unit, i.e.
|
|
208
|
+
# the body supplied the framework's answer on every path through it. It is what stands a UNIT
|
|
209
|
+
# callee rule down: an action that called `render :edit` or `redirect_to` outright did not take
|
|
210
|
+
# Rails' implicit render.
|
|
211
|
+
#
|
|
212
|
+
# Conditional does not count, and that is the whole of the bit's care. `redirect_to root_path if
|
|
213
|
+
# @user.nil?` leaves the other path taking the implicit render, so a unit that recorded the
|
|
214
|
+
# response there would drop the template edge AND read exhaustive — the one combination an
|
|
215
|
+
# effect summary may never produce. {@conditional} counts the branching ancestors the walk is
|
|
216
|
+
# inside; only a depth of zero answers.
|
|
217
|
+
@responded = false
|
|
218
|
+
@conditional = 0
|
|
219
|
+
@transparent_blocks = Set.new.compare_by_identity
|
|
220
|
+
# #1071 — a `respond_to` dispatcher's format arms, read for the per-arm conventional edge that
|
|
221
|
+
# replaced the single `html` unit edge. Each arm is one `format.<fmt>` call made on the
|
|
222
|
+
# dispatcher's block parameter, and an arm's own block is a branch the scan walks with the same
|
|
223
|
+
# {@conditional} bit scoped to it: a `responds:` row fired at the arm's top level stands THAT
|
|
224
|
+
# arm's conventional template down, exactly as a unit-level response stands the implicit render
|
|
225
|
+
# down. `@arms_by_block` maps an arm block node to its arm so the walk can open the scope;
|
|
226
|
+
# `@arm_stack` holds the open ones with the depth their body's top level sits at;
|
|
227
|
+
# `@block_stack` is every open block node, so an arm call is recognised by its enclosing block
|
|
228
|
+
# being one of {@transparent_blocks}; `@dispatch_top_level` remembers whether a dispatcher was
|
|
229
|
+
# spelled at the unit's top level, which is what lets the unit rule stand down for the standard
|
|
230
|
+
# responder while an action whose `respond_to` is inside a branch still keeps its plain implicit
|
|
231
|
+
# render.
|
|
232
|
+
@format_arms = []
|
|
233
|
+
@arms_by_block = {}.compare_by_identity
|
|
234
|
+
@arm_stack = []
|
|
235
|
+
@block_stack = []
|
|
236
|
+
@dispatch_top_level = false
|
|
237
|
+
# #391 — set only where a site could not carry the bit on an edge; see {#record_edge}.
|
|
238
|
+
@unclaimed = false
|
|
136
239
|
end
|
|
137
240
|
|
|
138
241
|
# Units discovered inside this one — a nested `def`, or a `define_method` with a literal name whose
|
|
139
|
-
# block becomes that method's body. Each is `[name,
|
|
242
|
+
# block becomes that method's body. Each is `[name, body context, body_node, parameters_node,
|
|
243
|
+
# shared_slot]`, the context being the {DefinitionContext}'s answer at the definition and `shared_slot`
|
|
244
|
+
# true for the `define_method` block (see {#initialize}). One it cannot place is omitted.
|
|
140
245
|
attr_reader :nested
|
|
141
246
|
|
|
142
247
|
# Whether this body reaches `super` — an override that delegates upward still runs whatever the
|
|
@@ -153,13 +258,22 @@ module Rigor
|
|
|
153
258
|
# Walks `body` and returns `[Summary, edges]`.
|
|
154
259
|
def run(body)
|
|
155
260
|
walk(body)
|
|
261
|
+
apply_unit_callees
|
|
156
262
|
summary = Summary.new(
|
|
157
263
|
bundles: @bundles, declared_bundles: @declared_bundles,
|
|
158
|
-
exhaustive: @causes.empty?, causes: @causes
|
|
264
|
+
exhaustive: @causes.empty?, causes: @causes, unclaimed: @unclaimed
|
|
159
265
|
)
|
|
160
266
|
[summary, @edges]
|
|
161
267
|
end
|
|
162
268
|
|
|
269
|
+
# One `format.<fmt>` arm of a `respond_to` dispatcher (#1071): the format it spells, and whether a
|
|
270
|
+
# plugin `responds:` row fired unconditionally in the arm's own block. `responded` is mutated by the
|
|
271
|
+
# walk (a Struct, unlike the Data rows the rules produce) and `conventional?` is the any/all
|
|
272
|
+
# exclusion — those two arms serve every format and can name no single template.
|
|
273
|
+
FormatArm = Struct.new(:format, :responded, keyword_init: true) do
|
|
274
|
+
def conventional? = format != "any" && format != "all"
|
|
275
|
+
end
|
|
276
|
+
|
|
163
277
|
private
|
|
164
278
|
|
|
165
279
|
def add(origin, labels)
|
|
@@ -183,21 +297,66 @@ module Rigor
|
|
|
183
297
|
return if unit_boundary?(node)
|
|
184
298
|
|
|
185
299
|
visit(node)
|
|
186
|
-
node
|
|
300
|
+
return walk_children(node) unless branching?(node)
|
|
301
|
+
|
|
302
|
+
@conditional += 1
|
|
303
|
+
arm = enter_arm_block(node)
|
|
304
|
+
walk_children(node)
|
|
305
|
+
@arm_stack.pop if arm
|
|
306
|
+
@conditional -= 1
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The children, with the block stack maintained: an arm call is recognised by its innermost
|
|
310
|
+
# enclosing block being one of the dispatcher's transparent ones, so every `BlockNode` the walk
|
|
311
|
+
# descends into has to be on the stack while its body runs (#1071).
|
|
312
|
+
def walk_children(node)
|
|
313
|
+
if node.is_a?(Prism::BlockNode)
|
|
314
|
+
@block_stack.push(node)
|
|
315
|
+
node.rigor_each_child { |child| walk(child) }
|
|
316
|
+
@block_stack.pop
|
|
317
|
+
elsif DefinitionContext.rebinds?(node)
|
|
318
|
+
walk_rebinding(node)
|
|
319
|
+
else
|
|
320
|
+
node.rigor_each_child { |child| walk(child) }
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# The children of a node one of which may run under another {DefinitionContext} — a `class_eval`
|
|
325
|
+
# block, a `class << self` body — so a unit nested there is keyed where Ruby defines it.
|
|
326
|
+
def walk_rebinding(node)
|
|
327
|
+
outer = @context
|
|
328
|
+
node.rigor_each_child do |child|
|
|
329
|
+
@context = outer.for_child(node, child)
|
|
330
|
+
walk(child)
|
|
331
|
+
end
|
|
332
|
+
@context = outer
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Whether the walk is entering a construct whose body may not run. A `BlockNode` answers from the
|
|
336
|
+
# call that owns it, which {#visit_call} has already marked — `walk` visits a node before its
|
|
337
|
+
# children, so the mark is always in place by the time the block is reached.
|
|
338
|
+
def branching?(node)
|
|
339
|
+
return !@transparent_blocks.include?(node) if node.is_a?(Prism::BlockNode)
|
|
340
|
+
|
|
341
|
+
BRANCHING.include?(node.class)
|
|
187
342
|
end
|
|
188
343
|
|
|
189
344
|
# A nested unit is recorded and NOT descended into: its body belongs to its own summary, and the
|
|
190
|
-
# enclosing method gets only the `mutate.static` of having defined it.
|
|
345
|
+
# enclosing method gets only the `mutate.static` of having defined it. One the {DefinitionContext}
|
|
346
|
+
# cannot place is not descended into either, and belongs to no summary.
|
|
191
347
|
def unit_boundary?(node)
|
|
192
348
|
case node
|
|
193
349
|
when Prism::DefNode
|
|
194
|
-
|
|
350
|
+
context = @context.def_body(node)
|
|
351
|
+
@nested << [node.name.to_s, context, node.body, node.parameters, false] if context
|
|
195
352
|
true
|
|
196
353
|
when Prism::CallNode
|
|
197
354
|
declared = self.class.define_method_unit(node)
|
|
198
355
|
return false unless declared
|
|
199
356
|
|
|
200
|
-
|
|
357
|
+
name, body, parameters = declared
|
|
358
|
+
context = @context.module_call_body
|
|
359
|
+
@nested << [name, context, body, parameters, true] if context
|
|
201
360
|
add(DEFINE_METHOD, MUTATE_STATIC)
|
|
202
361
|
true
|
|
203
362
|
else
|
|
@@ -211,10 +370,12 @@ module Rigor
|
|
|
211
370
|
when Prism::XStringNode, Prism::InterpolatedXStringNode
|
|
212
371
|
add(XSTRING, IO_PROCESS)
|
|
213
372
|
when Prism::GlobalVariableReadNode
|
|
214
|
-
add(GVAR_READ, GLOBAL_READ) unless
|
|
373
|
+
add(GVAR_READ, GLOBAL_READ) unless UNCOLOURED_READS.include?(node.name)
|
|
215
374
|
when Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode,
|
|
216
|
-
Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode
|
|
217
|
-
|
|
375
|
+
Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
|
|
376
|
+
Prism::GlobalVariableTargetNode
|
|
377
|
+
# A target is a multiple assignment's, a `for` loop's or a `rescue =>` clause's.
|
|
378
|
+
add(GVAR_WRITE, GLOBAL_WRITE) unless !@shared_slot && FRAME_LOCAL_GLOBALS.include?(node.name)
|
|
218
379
|
when Prism::ClassVariableReadNode
|
|
219
380
|
add(CVAR_READ, GLOBAL_READ)
|
|
220
381
|
when Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode,
|
|
@@ -256,6 +417,8 @@ module Rigor
|
|
|
256
417
|
end
|
|
257
418
|
|
|
258
419
|
def visit_call(node)
|
|
420
|
+
mark_transparent_block(node)
|
|
421
|
+
record_format_arm(node)
|
|
259
422
|
record = @calls[node]
|
|
260
423
|
attribute(node, record)
|
|
261
424
|
plugin = attribute_plugin(node, record)
|
|
@@ -270,6 +433,64 @@ module Rigor
|
|
|
270
433
|
visit_block_argument(node)
|
|
271
434
|
end
|
|
272
435
|
|
|
436
|
+
# `respond_to do |format| … end` — the block that is a dispatcher rather than a branch. Marked by
|
|
437
|
+
# identity, because a `BlockNode` cannot name the call it belongs to and two structurally equal
|
|
438
|
+
# blocks in one body are two blocks (#1048). A dispatcher spelled at the unit's top level is also
|
|
439
|
+
# remembered (#1071): its arms answer for the whole body, so the conventional `<action>.html` unit
|
|
440
|
+
# edge stands down; a dispatcher inside a branch does not cover the fall-through path.
|
|
441
|
+
def mark_transparent_block(node)
|
|
442
|
+
return unless node.receiver.nil? && DISPATCH_SELECTORS.include?(node.name)
|
|
443
|
+
|
|
444
|
+
block = node.block
|
|
445
|
+
@transparent_blocks << block if block.is_a?(Prism::BlockNode)
|
|
446
|
+
@dispatch_top_level ||= true if @conditional.zero?
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# #1071 — one `format.<fmt>` arm of the enclosing `respond_to`, when the formatter is the block's
|
|
450
|
+
# own parameter: the edge its arm answers for is the conventional `<action>.<fmt>` template, and the
|
|
451
|
+
# walk needs the arm registered and its block (if any) mapped before it descends into that block's
|
|
452
|
+
# body. A call made on anything else in the block is not an arm; a formatter nobody assigned a
|
|
453
|
+
# parameter is a dispatcher whose arms cannot be read, and the unit rule simply keeps its old
|
|
454
|
+
# single answer.
|
|
455
|
+
def record_format_arm(node)
|
|
456
|
+
enclosing = @block_stack.last
|
|
457
|
+
return unless enclosing.is_a?(Prism::BlockNode) && @transparent_blocks.include?(enclosing)
|
|
458
|
+
|
|
459
|
+
parameter = block_parameter(enclosing)
|
|
460
|
+
return if parameter.nil?
|
|
461
|
+
return unless node.receiver.is_a?(Prism::LocalVariableReadNode) && node.receiver.name.to_s == parameter
|
|
462
|
+
|
|
463
|
+
arm = FormatArm.new(format: node.name.to_s)
|
|
464
|
+
@format_arms << arm
|
|
465
|
+
block = node.block
|
|
466
|
+
@arms_by_block[block] = arm if block.is_a?(Prism::BlockNode)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# The name of a dispatcher block's first required parameter — `|format|` in the ordinary shape. A
|
|
470
|
+
# `respond_to` whose block spells no parameter (or a splat, or a destructure) has no formatter the
|
|
471
|
+
# walk can name arms on.
|
|
472
|
+
def block_parameter(block)
|
|
473
|
+
params = block.parameters
|
|
474
|
+
return nil unless params.is_a?(Prism::BlockParametersNode)
|
|
475
|
+
|
|
476
|
+
required = params.parameters&.requireds
|
|
477
|
+
return nil if required.nil? || required.empty?
|
|
478
|
+
|
|
479
|
+
first = required.first
|
|
480
|
+
first.name.to_s if first.respond_to?(:name)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Opens an arm scope for the walk: the arm's own block is a branch like any other, and its body's
|
|
484
|
+
# top level sits at the current {@conditional} depth — the value a `responds:` row must fire at for
|
|
485
|
+
# the ARM to count as answered outright.
|
|
486
|
+
def enter_arm_block(node)
|
|
487
|
+
arm = @arms_by_block[node]
|
|
488
|
+
return nil unless arm
|
|
489
|
+
|
|
490
|
+
@arm_stack << [arm, @conditional]
|
|
491
|
+
arm
|
|
492
|
+
end
|
|
493
|
+
|
|
273
494
|
# The **plugin stratum** (#387; ADR-103 WD6 / WD10): what the plugin that models a framework says
|
|
274
495
|
# this call does.
|
|
275
496
|
#
|
|
@@ -283,22 +504,129 @@ module Rigor
|
|
|
283
504
|
# Three receiver shapes, tried nearest-syntax first — a written receiver path (`Rails.cache.read`),
|
|
284
505
|
# a receiver rooted at implicit self inside a framework class (`session[:x] = 1`), and the receiver's
|
|
285
506
|
# class through the project's inheritance chain (`user.save`).
|
|
286
|
-
# @return
|
|
507
|
+
# @return the row that claimed this call, for {#visit_call} to read as a bound
|
|
287
508
|
def attribute_plugin(node, record)
|
|
288
509
|
return nil unless @plugin_facts.attributions?
|
|
289
510
|
|
|
290
511
|
row = plugin_row(node, record)
|
|
291
512
|
return nil if row.nil?
|
|
292
513
|
|
|
514
|
+
@responded = true if row.responds && @conditional.zero?
|
|
515
|
+
mark_arm_responded(row)
|
|
516
|
+
edged = callee_edge_taken?(node, row)
|
|
293
517
|
labels = row.narrow ? Narrowing.apply(row.narrow, node) : row.labels
|
|
294
|
-
|
|
518
|
+
# A `narrow:` that narrowed to nothing says the call does nothing, and a call that does nothing
|
|
519
|
+
# taints nothing. A row that declares no labels at all is a different statement — since #1048 a
|
|
520
|
+
# row may contribute an EDGE and no label — so it falls through to the taints rather than
|
|
521
|
+
# returning here.
|
|
522
|
+
return row if row.narrow && (labels.nil? || labels.empty?)
|
|
523
|
+
|
|
524
|
+
add_declared(Origin.plugin(row.key), labels) unless labels.nil? || labels.empty?
|
|
525
|
+
record_plugin_taints(row, edged)
|
|
526
|
+
row
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# The `responds:` bit scoped to an OPEN format arm (#1071): a row fired at the arm's body top
|
|
530
|
+
# level, so the arm never falls through to its conventional `<action>.<fmt>` template. A deeper
|
|
531
|
+
# response does not stand the arm's own fall-through down, exactly as a conditional response does
|
|
532
|
+
# not stand a unit's implicit render down.
|
|
533
|
+
def mark_arm_responded(row)
|
|
534
|
+
return unless row.responds
|
|
295
535
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
536
|
+
arm, depth = @arm_stack.last
|
|
537
|
+
return if arm.nil?
|
|
538
|
+
|
|
539
|
+
arm.responded = true if @conditional == depth
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# A row may discharge AND still taint: `render` states exactly what the CONTROLLER does and says
|
|
543
|
+
# nothing about the template. Where a {CalleeRule} named the template the taint rides the EDGE
|
|
544
|
+
# instead (#1048), so a render that reaches a real unit clears it and one that reaches nothing
|
|
545
|
+
# keeps it — decided by the propagator, which is the only thing that knows.
|
|
546
|
+
def record_plugin_taints(row, edged)
|
|
547
|
+
taint(row.taint, row.key) if row.taint && !edged
|
|
300
548
|
taint("plugin-attribution", row.key) unless row.discharge?
|
|
301
|
-
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# #1048 — the call-graph edge a framework method IS, when the plugin's row named a {CalleeRule}.
|
|
552
|
+
# `render :show` inside `UsersController` runs `view:users/show.html`, which is a unit in the same
|
|
553
|
+
# table; the rule reads the call's argument literals and nothing else, and answers nil for anything
|
|
554
|
+
# it cannot settle — a computed target, an unmodelled option, a receiver whose class names no view
|
|
555
|
+
# directory. Nil leaves the site exactly as it was, taint included.
|
|
556
|
+
#
|
|
557
|
+
# @return whether an edge took the row's taint with it
|
|
558
|
+
def callee_edge_taken?(node, row)
|
|
559
|
+
return false if row.callee.nil? || !CalleeRule.site_rule?(row.callee)
|
|
560
|
+
|
|
561
|
+
callee = CalleeRule.site(row.callee, node, owner_class: @owner_class, unit_key: @method_name,
|
|
562
|
+
fallbacks: row.callee_fallbacks)
|
|
563
|
+
return false if callee.nil?
|
|
564
|
+
|
|
565
|
+
@edges << FileCollection::Edge.new(
|
|
566
|
+
receiver_class: callee.receiver, kind: :singleton, selector: callee.selector, self_call: false,
|
|
567
|
+
taint_if_unresolved: row.taint ? [row.taint, row.key].freeze : nil,
|
|
568
|
+
fallback_selectors: callee.fallbacks
|
|
569
|
+
)
|
|
570
|
+
!row.taint.nil?
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# The UNIT callee rules (#1048): an edge a plugin declares for a body that made no call at all.
|
|
574
|
+
# Rails' implicit render is the whole of the case — an action that falls off its end renders
|
|
575
|
+
# `<controller>/<action>` — so the producing fact is the ABSENCE of a `responds:` row, which only a
|
|
576
|
+
# finished unit scan can observe.
|
|
577
|
+
#
|
|
578
|
+
# Contributes an edge and nothing else — no label, no taint — which is what keeps it from being
|
|
579
|
+
# wrong about a method that is not an action at all.
|
|
580
|
+
#
|
|
581
|
+
# Two units it never applies to, and both are the same false positive twice: a `private` /
|
|
582
|
+
# `protected` member (Rails' `action_methods` is public only) and a `def` nested inside another
|
|
583
|
+
# method. A project with `app/views/users/card.html.erb` and a `private def card` would otherwise
|
|
584
|
+
# hand that template's `io.db.write` to the helper.
|
|
585
|
+
#
|
|
586
|
+
# #1071 widens the answer per `respond_to` arm: a unit that spelled a dispatcher renders one
|
|
587
|
+
# conventional `<action>.<fmt>` template per arm rather than one `<action>.html`, so the row is
|
|
588
|
+
# applied once per arm the walk read, each answered outright (a `responds:` row at the arm's own
|
|
589
|
+
# top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html`
|
|
590
|
+
# answer survives only for a dispatcher the walk could not name arms on, or one nested under a
|
|
591
|
+
# branch — the plain fall-through still exists on the path that skips it.
|
|
592
|
+
def apply_unit_callees
|
|
593
|
+
return if @responded || @singleton || @non_public
|
|
594
|
+
return if @owner_class.nil? || @method_name.nil?
|
|
595
|
+
|
|
596
|
+
rows = @plugin_facts.unit_callee_rows
|
|
597
|
+
return if rows.empty?
|
|
598
|
+
|
|
599
|
+
rows.each do |row|
|
|
600
|
+
next unless @plugin_facts.descends_from?(@owner_class, row.receiver)
|
|
601
|
+
|
|
602
|
+
apply_unit_callee_row(row)
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# One unit callee row across the unit's format arms: nil targets the plain `<action>.html` implicit
|
|
607
|
+
# render, and one application per arm reaches that arm's own `<action>.<fmt>` convention (#1071) —
|
|
608
|
+
# an arm that answered outright, or one serving every format (`any` / `all`), names nothing.
|
|
609
|
+
def apply_unit_callee_row(row)
|
|
610
|
+
return apply_unit_callee(row, nil) if @format_arms.empty?
|
|
611
|
+
|
|
612
|
+
apply_unit_callee(row, nil) unless @dispatch_top_level
|
|
613
|
+
@format_arms.each { |arm| apply_unit_callee(row, arm) }
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# Applies one unit callee row's rule for one target format.
|
|
617
|
+
# @return the {Callee} the edge was recorded for, or nil when the arm (or the format) names nothing
|
|
618
|
+
def apply_unit_callee(row, arm)
|
|
619
|
+
return nil if arm && !arm.conventional?
|
|
620
|
+
return nil if arm&.responded
|
|
621
|
+
|
|
622
|
+
format = arm&.format
|
|
623
|
+
callee = CalleeRule.unit(row.callee, owner_class: @owner_class, unit_key: @method_name, format: format)
|
|
624
|
+
return nil if callee.nil?
|
|
625
|
+
|
|
626
|
+
@edges << FileCollection::Edge.new(
|
|
627
|
+
receiver_class: callee.receiver, kind: :singleton, selector: callee.selector, self_call: false
|
|
628
|
+
)
|
|
629
|
+
callee
|
|
302
630
|
end
|
|
303
631
|
|
|
304
632
|
def plugin_row(node, record)
|
|
@@ -545,7 +873,11 @@ module Rigor
|
|
|
545
873
|
selector = literal_selector(node.arguments&.arguments&.first)
|
|
546
874
|
return taint("dynamic-send") unless selector
|
|
547
875
|
|
|
548
|
-
|
|
876
|
+
# A literal `send` is an ordinary call and nothing bounded it, so it is unclaimed on the same
|
|
877
|
+
# terms as {#record_edge}'s.
|
|
878
|
+
push_edge(record, selector, node.receiver.nil?,
|
|
879
|
+
unclaimed: true, constant_receiver: constant_receiver?(node.receiver))
|
|
880
|
+
@unclaimed = true if !node.receiver.nil? && !edge_recordable?(record)
|
|
549
881
|
end
|
|
550
882
|
|
|
551
883
|
def literal_selector(node)
|
|
@@ -571,7 +903,14 @@ module Rigor
|
|
|
571
903
|
# such calls are ordinary inherited ones the catalogue simply has no row for.
|
|
572
904
|
def record_edge(node, record, bound = nil)
|
|
573
905
|
self_call = node.receiver.nil?
|
|
574
|
-
|
|
906
|
+
# #391 — an uncatalogued, unbounded site is a site whose callee nobody described. Whether that
|
|
907
|
+
# matters is the propagator's to decide: if the edge lands on a project definition the closure
|
|
908
|
+
# reads that definition's own summary and the site is fully accounted for. So the bit travels ON
|
|
909
|
+
# the edge, and the unit is marked directly only where there is no edge to carry it.
|
|
910
|
+
unclaimed = bound.nil?
|
|
911
|
+
push_edge(record, node.name.to_s, self_call,
|
|
912
|
+
unclaimed: unclaimed, constant_receiver: constant_receiver?(node.receiver))
|
|
913
|
+
@unclaimed = true if unclaimed && !self_call && !edge_recordable?(record)
|
|
575
914
|
return unless self_call && (record.nil? || !record.resolved)
|
|
576
915
|
# An envelope on this unit's own class for the very selector the dispatcher declined is the
|
|
577
916
|
# project declaring the method and stating its bound; a discharging plugin row on the framework
|
|
@@ -583,14 +922,30 @@ module Rigor
|
|
|
583
922
|
taint("unresolved-self-call", node.name.to_s)
|
|
584
923
|
end
|
|
585
924
|
|
|
586
|
-
def push_edge(record, selector, self_call)
|
|
587
|
-
return
|
|
925
|
+
def push_edge(record, selector, self_call, unclaimed: false, constant_receiver: false)
|
|
926
|
+
return unless edge_recordable?(record)
|
|
588
927
|
|
|
589
928
|
@edges << FileCollection::Edge.new(
|
|
590
|
-
receiver_class: record.receiver_class, kind: record.kind, selector: selector,
|
|
929
|
+
receiver_class: record.receiver_class, kind: record.kind, selector: selector,
|
|
930
|
+
self_call: self_call, unclaimed: unclaimed, constant_receiver: constant_receiver
|
|
591
931
|
)
|
|
592
932
|
end
|
|
593
933
|
|
|
934
|
+
# Whether the AUTHOR wrote this receiver as a constant path (#1039). The edge is otherwise keyed on
|
|
935
|
+
# the receiver's type, which cannot tell `Base.new` from `self.class.new` — the same `Singleton[Base]`
|
|
936
|
+
# in both — and the two reach different constructors. Everything else, a bare `self` included, is a
|
|
937
|
+
# receiver whose run-time class may be a subclass.
|
|
938
|
+
def constant_receiver?(receiver)
|
|
939
|
+
receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
# Whether {#push_edge} has a receiver to key an edge on. A call whose receiver the typer never
|
|
943
|
+
# named carries nothing the propagator can resolve, so an unclaimed site of that shape marks the
|
|
944
|
+
# unit directly instead of handing the bit to an edge that will not exist (#391).
|
|
945
|
+
def edge_recordable?(record)
|
|
946
|
+
!record.nil? && !record.receiver_class.nil?
|
|
947
|
+
end
|
|
948
|
+
|
|
594
949
|
def visit_block_argument(node)
|
|
595
950
|
block = node.block
|
|
596
951
|
return unless block.is_a?(Prism::BlockArgumentNode)
|
|
@@ -30,10 +30,6 @@ module Rigor
|
|
|
30
30
|
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
|
-
# @param method_envelopes [Hash{String => Envelope}]
|
|
34
|
-
# @param class_envelopes [Hash{String => Envelope}]
|
|
35
|
-
# @param registry [Rigor::Effects::Registry]
|
|
36
|
-
# @return [Array<Finding>]
|
|
37
33
|
def for_envelopes(method_envelopes:, class_envelopes:, registry:)
|
|
38
34
|
seen = {}
|
|
39
35
|
declarations(method_envelopes, class_envelopes).each do |envelope|
|
|
@@ -49,10 +45,9 @@ module Rigor
|
|
|
49
45
|
seen.values
|
|
50
46
|
end
|
|
51
47
|
|
|
52
|
-
# @param labels
|
|
53
|
-
# @param key_path
|
|
54
|
-
# @param consequence
|
|
55
|
-
# @return [Array<Finding>]
|
|
48
|
+
# @param labels — the list as written, in source order.
|
|
49
|
+
# @param key_path — the `.rigor.yml` key, for the message (`effects.tolerated`).
|
|
50
|
+
# @param consequence — what the degradation cost, for the message.
|
|
56
51
|
def for_config(labels:, key_path:, consequence:, registry:)
|
|
57
52
|
tokens = Array(labels).map(&:to_s)
|
|
58
53
|
tokens.filter_map do |token|
|
|
@@ -17,10 +17,9 @@ module Rigor
|
|
|
17
17
|
# {.for} answers `nil` unless {LabelIntent} says the spelling is evidently a label — the whole
|
|
18
18
|
# point of the diagnostic is that it fires where intent is evident and nowhere else.
|
|
19
19
|
class UnknownLabelReport < Data.define(:token, :suggestion, :retirement)
|
|
20
|
-
# @param token
|
|
21
|
-
# @param registry
|
|
22
|
-
# @param siblings
|
|
23
|
-
# @return [UnknownLabelReport, nil]
|
|
20
|
+
# @param token — the spelling as written.
|
|
21
|
+
# @param registry — the vocabulary after plugin load.
|
|
22
|
+
# @param siblings — the other tokens written alongside it.
|
|
24
23
|
def self.for(token:, registry:, siblings: [])
|
|
25
24
|
return nil unless LabelIntent.evident?(token, registry, siblings: siblings)
|
|
26
25
|
|