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
|
@@ -6,6 +6,11 @@ require_relative "actionpack/analyzer"
|
|
|
6
6
|
require_relative "actionpack/effects"
|
|
7
7
|
require_relative "actionpack/controller_discoverer"
|
|
8
8
|
require_relative "actionpack/controller_index"
|
|
9
|
+
require_relative "actionpack/controller_scan"
|
|
10
|
+
require_relative "actionpack/erb_compiler"
|
|
11
|
+
require_relative "actionpack/render_locals"
|
|
12
|
+
require_relative "actionpack/view_assigns"
|
|
13
|
+
require_relative "actionpack/view_units"
|
|
9
14
|
|
|
10
15
|
module Rigor
|
|
11
16
|
module Plugin
|
|
@@ -55,6 +60,7 @@ module Rigor
|
|
|
55
60
|
class Actionpack < Rigor::Plugin::Base
|
|
56
61
|
manifest(
|
|
57
62
|
id: "actionpack",
|
|
63
|
+
target_gems: ["actionpack"],
|
|
58
64
|
# ADR-37: the four phases (helper / filter / render / strong-params) run per-call over the
|
|
59
65
|
# engine-owned walk; the enclosing controller is read from the node-rule `NodeContext`
|
|
60
66
|
# ancestors. Nested-module qualification is preserved — `module Admin; class
|
|
@@ -68,16 +74,78 @@ module Rigor
|
|
|
68
74
|
# Bumped 2026-09-02 (#621) — a reopened controller's `def`s are UNIONed rather than clobbered by
|
|
69
75
|
# the later file in the glob, so a cached 1.0.0 index can be missing the filter targets the merge
|
|
70
76
|
# restores.
|
|
71
|
-
|
|
77
|
+
# Bumped 2026-09-17 (#393) — the plugin gained `template_globs:` and compiles `app/views/**/*.erb`
|
|
78
|
+
# into template units, so the answer for a project with views is a different answer entirely and
|
|
79
|
+
# a cached 1.2.0 run has no view rows in it.
|
|
80
|
+
# Bumped 2026-09-10 (#534 item 7) — the plugin gained a bundled `sig/` (ADR-25) naming the
|
|
81
|
+
# `ActionController` namespace and the errors controllers rescue, so the constants stop resolving
|
|
82
|
+
# to `untyped`. `ActionController::Parameters` and the `ActionDispatch` readers stay undeclared;
|
|
83
|
+
# `sig/action_controller.rbs` documents why that absence is load-bearing.
|
|
84
|
+
# Bumped 2026-09-17 (#1047) — the plugin now traces render-site `locals:` into a partial's unit and
|
|
85
|
+
# compiles LAYOUTS (a `yield` rewrite), so a cached 1.3.0 run is missing both the layout units and
|
|
86
|
+
# every seeded local.
|
|
87
|
+
# Bumped 2026-09-18 (#1065) — the claim grew the non-ERB handlers, which the plugin declines and
|
|
88
|
+
# the engine reads as "a template exists here and has no unit". The bump is belt-and-braces: what
|
|
89
|
+
# actually invalidates is the claim itself, through the glob row in `TemplateUnits#digest` and the
|
|
90
|
+
# `claimed_globs` equality the carry checks. A manifest version is in no cache key of its own.
|
|
91
|
+
version: "1.5.0",
|
|
72
92
|
description: "Validates Action Pack route-helper calls and filter chains inside controllers, and types the request-context readers (`params` / `session` / `request` / `flash`) and their chains.",
|
|
73
93
|
config_schema: {
|
|
74
94
|
"controller_search_paths" => { kind: :array, default: ["app/controllers"] },
|
|
75
|
-
"view_search_paths" => { kind: :array, default: ["app/views"] }
|
|
95
|
+
"view_search_paths" => { kind: :array, default: ["app/views"] },
|
|
96
|
+
# #393 — whether the ordinary `call.*` type checks are reported INSIDE a compiled template. The
|
|
97
|
+
# `flow.*` family is no longer gated on this knob: #1047 removed the binding gap that made it
|
|
98
|
+
# unsound in a view, and it reports by default like every other family.
|
|
99
|
+
# Off by default: the synthesised `self` is an open receiver and the seeds are deliberately
|
|
100
|
+
# narrow, so the typing half of a view unit is not yet precise enough to be worth a diagnostic
|
|
101
|
+
# per template line, while the EFFECTS half needs no receiver precision at all and is what the
|
|
102
|
+
# view layer is analysed for first. A project that wants `@user.nmae` in `show.html.erb` can
|
|
103
|
+
# turn it on; see `docs/manual/plugins/rigor-actionpack.md`.
|
|
104
|
+
"view_type_checks" => { kind: :boolean, default: false }
|
|
76
105
|
},
|
|
77
106
|
consumes: [
|
|
78
107
|
{ plugin_id: "rails-routes", name: :helper_table, optional: true },
|
|
79
108
|
{ plugin_id: "activerecord", name: :model_index, optional: true }
|
|
80
109
|
],
|
|
110
|
+
# ADR-25 (#534 item 7) — the framework-namespace signatures. See `sig/action_controller.rbs` for
|
|
111
|
+
# the admission rule and, more importantly, for what it refuses to declare, and
|
|
112
|
+
# `sig/action_view.rbs` for the one class a template unit's `self` is typed as.
|
|
113
|
+
signature_paths: ["sig"],
|
|
114
|
+
# #393 — the ERB templates this plugin compiles into {Plugin::TemplateUnit}s. A manifest row is
|
|
115
|
+
# read without running plugin code, so it cannot consult `view_search_paths:`; a project that
|
|
116
|
+
# moves its views away from `app/views` gets no units, which is the quiet answer rather than a
|
|
117
|
+
# wrong one. Both `show.html.erb` and the bare `show.erb` shape are claimed.
|
|
118
|
+
# #1065 — the non-ERB handlers are claimed and DECLINED. Nothing here is compiled, and
|
|
119
|
+
# {#template_units_for_file} returns `[]` for every one of them; what the claim buys is the
|
|
120
|
+
# engine knowing a template exists at that logical name, which is what stops a `.js` render's
|
|
121
|
+
# format fallback from resolving past `_row.js.haml` onto `_row.html.erb`. Action View runs the
|
|
122
|
+
# Haml file. The list is the handlers a Rails app actually keeps under `app/views` — the common
|
|
123
|
+
# third-party template gems, plus Action View's own `builder` and `ruby`. It is NOT exhaustive:
|
|
124
|
+
# `raw` and `html` are Action View handlers too and are deliberately left out, because a claim on
|
|
125
|
+
# `*.html` would key a file whose name carries no format at all. A handler nobody claims is
|
|
126
|
+
# invisible, and its fallback behaves as it did before this claim existed.
|
|
127
|
+
template_globs: ["app/views/**/*.erb",
|
|
128
|
+
"app/views/**/*.{haml,slim,jbuilder,builder,rabl,ruby}"],
|
|
129
|
+
# ADR-26 — every class the bundled signature names is declared so the CONSTANT resolves; none of
|
|
130
|
+
# them enumerates a method surface, so `ParameterMissing#param` and its siblings must stay
|
|
131
|
+
# lenient rather than becoming `call.undefined-method` on a rescue body.
|
|
132
|
+
open_receivers: [
|
|
133
|
+
"ActionController::ActionControllerError",
|
|
134
|
+
"ActionController::BadRequest",
|
|
135
|
+
"ActionController::RoutingError",
|
|
136
|
+
"ActionController::UrlGenerationError",
|
|
137
|
+
"ActionController::MethodNotAllowed",
|
|
138
|
+
"ActionController::NotImplemented",
|
|
139
|
+
"ActionController::UnknownFormat",
|
|
140
|
+
"ActionController::InvalidAuthenticityToken",
|
|
141
|
+
"ActionController::InvalidCrossOriginRequest",
|
|
142
|
+
"ActionController::MissingExactTemplate",
|
|
143
|
+
"ActionController::ParameterMissing",
|
|
144
|
+
"ActionController::UnpermittedParameters",
|
|
145
|
+
# #393 — the view context every template unit's `self` is typed as. Declared in
|
|
146
|
+
# `sig/action_view.rbs` so the name resolves, open so the helper surface stays lenient.
|
|
147
|
+
"ActionView::Base"
|
|
148
|
+
],
|
|
81
149
|
# ADR-103 WD10 / WD14 (#387) — see {Effects} for what each row is and why.
|
|
82
150
|
effect_root: "rails",
|
|
83
151
|
effect_labels: %w[
|
|
@@ -98,10 +166,202 @@ module Rigor
|
|
|
98
166
|
).discover
|
|
99
167
|
end
|
|
100
168
|
|
|
169
|
+
# #393 / #1047 — the diagnostic family a compiled template does not report while `view_type_checks:`
|
|
170
|
+
# is off (its default). Measured rather than assumed, on the private corpus copies recorded in
|
|
171
|
+
# `docs/notes/20260917-erb-template-units.md` and `docs/notes/20260917-render-locals-and-layouts.md`.
|
|
172
|
+
#
|
|
173
|
+
# `call.` is the receiver-precision family. `self` is an open `ActionView::Base` and the seeds are
|
|
174
|
+
# deliberately narrow, so what this family would report about a view is mostly a statement about what
|
|
175
|
+
# the synthesis does not know yet.
|
|
176
|
+
#
|
|
177
|
+
# **`flow.` was here and is not any more (#1047).** It was suppressed for one measured reason: with
|
|
178
|
+
# no render-site `locals:`, redmine's standard optional-local preamble
|
|
179
|
+
# (`<% path = nil unless defined? path %>` in `app/views/common/_other.html.erb`) really did assign
|
|
180
|
+
# nil in the compiled Ruby, and the flow rules folded three live branches on a partial Rails renders
|
|
181
|
+
# correctly. {RenderLocals} binds those names from the three sites that render that partial, the
|
|
182
|
+
# preamble stops being a fresh assignment, and the corpus re-measured with `flow.` UNSUPPRESSED is
|
|
183
|
+
# byte-identical to the corpus with it suppressed — on redmine (506 templates, layouts included) and
|
|
184
|
+
# on mastodon (46). The suppression was a stand-in for a missing binding, so it goes when the binding
|
|
185
|
+
# arrives rather than staying as a habit.
|
|
186
|
+
SUPPRESSED_VIEW_RULES = ["call."].freeze
|
|
187
|
+
|
|
188
|
+
# #1047 — where the project's view helpers live, read only for the names they `def`: a template's
|
|
189
|
+
# `defined?(current_user)` tests a HELPER, not an optional local, and must not seed one
|
|
190
|
+
# ({ViewUnits.self_declared_locals}). Rails' own convention and not a config knob, for the reason
|
|
191
|
+
# `template_globs:` is not one either.
|
|
192
|
+
HELPER_SEARCH_PATHS = ["app/helpers"].freeze
|
|
193
|
+
|
|
101
194
|
def init(_services)
|
|
102
195
|
@controller_search_paths = Array(config.fetch("controller_search_paths")).map(&:to_s)
|
|
103
196
|
@view_search_paths = Array(config.fetch("view_search_paths")).map(&:to_s)
|
|
197
|
+
@view_type_checks = config.fetch("view_type_checks") ? true : false
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# #393 — one ERB template compiled into one {Plugin::TemplateUnit}. Called once per matched file, on
|
|
201
|
+
# the parent, before any analysis (ADR-16 Tier D as revived by #392).
|
|
202
|
+
#
|
|
203
|
+
# The three synthesised bindings and where each comes from:
|
|
204
|
+
#
|
|
205
|
+
# - `self_type:` — {ViewUnits::SELF_TYPE}, a declared-but-open `ActionView::Base`, so every helper
|
|
206
|
+
# call resolves lenient rather than drawing a finding per line.
|
|
207
|
+
# - `locals:` — the names every RENDER SITE passes ({RenderLocals}, #1047), overlaid with the Rails
|
|
208
|
+
# 7.1 strict-locals comment where a template carries one. The seed is what makes Prism read `user`
|
|
209
|
+
# as a local rather than as a method call, which is what a `defined? user` preamble needs.
|
|
210
|
+
# - `ivar_seeds:` — the assigns of the controller actions that render this template, through
|
|
211
|
+
# {ViewAssigns}. A partial has none: its bindings belong to the render site.
|
|
212
|
+
#
|
|
213
|
+
# A compiler whose line numbering could not be measured declines the file rather than reporting at
|
|
214
|
+
# lines it cannot vouch for — {ErbCompiler::Unmappable} propagates, and the seam turns it into one
|
|
215
|
+
# `:plugin_loader` `runtime-error` row naming the template.
|
|
216
|
+
#
|
|
217
|
+
# ## Compiled Ruby that does not parse declines the file, silently
|
|
218
|
+
#
|
|
219
|
+
# A template is not Ruby, and the compiled form of one is not always Ruby either: a `case` split
|
|
220
|
+
# across tags with markup between the `case` and its first `when` is invalid anywhere. Handing such a
|
|
221
|
+
# body to the engine would put TWO parse diagnostics on a template Rails renders perfectly — a false
|
|
222
|
+
# positive per file, which is the one outcome this feature may not have (AGENTS.md). So the compiled
|
|
223
|
+
# Ruby is parsed and a body that does not parse declines the file through the seam's own `[]` door:
|
|
224
|
+
# no unit, no diagnostic, no effects.
|
|
225
|
+
#
|
|
226
|
+
# A LAYOUT used to be the bulk of that residue — `<%= yield %>` is legal ERB and illegal Ruby outside
|
|
227
|
+
# a method — and since #1047 it is not: {ErbCompiler::YIELD_METHOD} rewrites the keyword into a call
|
|
228
|
+
# on the view context, so all four of redmine's layouts and both of mastodon's compile. The residue
|
|
229
|
+
# is one `case` under stdlib ERB and none under Erubi.
|
|
230
|
+
#
|
|
231
|
+
# The cost is one Prism parse per template on the parent, and since #1047 both that parse and the
|
|
232
|
+
# compile are the ones {RenderLocals} already performed while building its index.
|
|
233
|
+
def template_units_for_file(path:, source:)
|
|
234
|
+
# #1065 — a claimed non-ERB handler is read for its NAME and nothing else: this plugin compiles
|
|
235
|
+
# ERB, and a `.haml` body handed to an ERB compiler would be garbage rather than a decline.
|
|
236
|
+
return [] unless path.end_with?(ERB_SUFFIX)
|
|
237
|
+
|
|
238
|
+
name = ViewUnits.logical_name(path, @view_search_paths)
|
|
239
|
+
# Scrubbed ONCE, here, and handed to both readers. Scrubbing inside the compiler alone left
|
|
240
|
+
# `ViewUnits.strict_locals` matching a Regexp against the raw bytes, and a single invalid byte
|
|
241
|
+
# anywhere in a template carrying a strict-locals comment raised out of the hook — which the seam
|
|
242
|
+
# correctly turns into an `error`-severity `:plugin_loader` row, so one mis-encoded view failed
|
|
243
|
+
# the whole run.
|
|
244
|
+
text = ErbCompiler.scrub(source)
|
|
245
|
+
revalidate_indexes
|
|
246
|
+
compiled, line_map, transform, parsed = render_locals.compiled_for(path, text) || compile_now(text)
|
|
247
|
+
return [] unless parsed
|
|
248
|
+
|
|
249
|
+
[
|
|
250
|
+
Rigor::Plugin::TemplateUnit.new(
|
|
251
|
+
logical_name: name, path: path, ruby_source: compiled, line_map: line_map,
|
|
252
|
+
self_type: ViewUnits::SELF_TYPE,
|
|
253
|
+
locals: template_locals(name, text),
|
|
254
|
+
ivar_seeds: view_assigns.seeds_for(name), transform_id: transform,
|
|
255
|
+
suppressed_rules: @view_type_checks ? [] : SUPPRESSED_VIEW_RULES
|
|
256
|
+
)
|
|
257
|
+
]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# The compile the {RenderLocals} cache could not answer — an editor's in-flight bytes, or a template
|
|
261
|
+
# the index never globbed. Same four values the cache carries.
|
|
262
|
+
# The one handler this plugin compiles; every other claimed template is declined for its name.
|
|
263
|
+
ERB_SUFFIX = ".erb"
|
|
264
|
+
|
|
265
|
+
def compile_now(text)
|
|
266
|
+
compiled, line_map, transform = ErbCompiler.compile(text)
|
|
267
|
+
[compiled, line_map, transform, Prism.parse(compiled).errors.empty?]
|
|
268
|
+
end
|
|
269
|
+
private :compile_now
|
|
270
|
+
|
|
271
|
+
# A template's locals, from three sources that each say a name is bound, weakest first so the
|
|
272
|
+
# strongest wins a type: every name the template itself tests for with `defined?` or `local_assigns`
|
|
273
|
+
# (its author's own optional-local declaration, which needs no render site — #1047's review found
|
|
274
|
+
# the render-site index alone misses a `locals: opts` site, a helper-side `render` and a local no
|
|
275
|
+
# site passes at all), the render sites {RenderLocals} could read, and the strict-locals comment.
|
|
276
|
+
def template_locals(name, text)
|
|
277
|
+
ViewUnits.self_declared_locals(text, helpers: render_locals.helper_methods)
|
|
278
|
+
.merge(render_locals.seeds_for(name))
|
|
279
|
+
.merge(ViewUnits.strict_locals(text))
|
|
280
|
+
end
|
|
281
|
+
private :template_locals
|
|
282
|
+
|
|
283
|
+
# #1047 — the two parent-side indexes are memoised on this instance, and a long-lived
|
|
284
|
+
# `LanguageServer::ProjectContext` keeps the instance across publishes. A memo that is never
|
|
285
|
+
# revalidated keeps a render site's locals after the site changed on disk, which is a `flow.` row
|
|
286
|
+
# on a correct partial until the process restarts.
|
|
287
|
+
#
|
|
288
|
+
# So the indexes are revalidated once per COLLECTION PASS, against a fingerprint of every controller,
|
|
289
|
+
# helper and template they read ({RenderLocals::Builder.fingerprint}: one glob and one `stat` per
|
|
290
|
+
# file). The collector announces a pass through {#template_units_pass_started}; the check itself is
|
|
291
|
+
# deferred to the pass's first `#template_units_for_file`, so a warm pass that carries every unit and
|
|
292
|
+
# compiles nothing pays nothing. Inferring the pass from the order of paths was tried and is not
|
|
293
|
+
# enough: a warm pass offers only the editor's buffer, so switching buffers after a render site
|
|
294
|
+
# changed on disk was served from the stale index whichever way the two paths sorted.
|
|
295
|
+
#
|
|
296
|
+
# Revalidating at the FIRST call of the pass rather than when an edited template's own bytes arrive is
|
|
297
|
+
# load-bearing too: `_card.html.erb` globs before `show.html.erb`, so a reset triggered by `show` would
|
|
298
|
+
# come after `_card`'s unit had already been built from the stale index. The collector's whole-claim
|
|
299
|
+
# carry decision ({Analysis::TemplateUnitCollector}) is the other half: it is what re-offers `_card`.
|
|
300
|
+
def template_units_pass_started
|
|
301
|
+
@index_pass_pending = true
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def revalidate_indexes
|
|
305
|
+
return unless @index_pass_pending
|
|
306
|
+
|
|
307
|
+
@index_pass_pending = false
|
|
308
|
+
drop_indexes unless indexes_fresh?
|
|
309
|
+
end
|
|
310
|
+
private :revalidate_indexes
|
|
311
|
+
|
|
312
|
+
def indexes_fresh?
|
|
313
|
+
return true if @render_locals.nil?
|
|
314
|
+
|
|
315
|
+
@render_locals.fingerprint == index_fingerprint
|
|
316
|
+
end
|
|
317
|
+
private :indexes_fresh?
|
|
318
|
+
|
|
319
|
+
def drop_indexes
|
|
320
|
+
@render_locals = nil
|
|
321
|
+
@view_assigns = nil
|
|
322
|
+
end
|
|
323
|
+
private :drop_indexes
|
|
324
|
+
|
|
325
|
+
def index_fingerprint
|
|
326
|
+
RenderLocals::Builder.fingerprint(
|
|
327
|
+
io_boundary: io_boundary, controller_search_paths: @controller_search_paths,
|
|
328
|
+
view_search_paths: @view_search_paths, helper_search_paths: HELPER_SEARCH_PATHS
|
|
329
|
+
)
|
|
330
|
+
rescue StandardError
|
|
331
|
+
nil
|
|
332
|
+
end
|
|
333
|
+
private :index_fingerprint
|
|
334
|
+
|
|
335
|
+
# The controller-assigns index, built once per run on the parent. Deliberately NOT a `producer` —
|
|
336
|
+
# a producer's value is cached against the run key, and this is consulted from the template-unit
|
|
337
|
+
# hook, which runs before the analysis a producer belongs to.
|
|
338
|
+
def view_assigns
|
|
339
|
+
@view_assigns ||= begin
|
|
340
|
+
ViewAssigns::Builder.new(io_boundary: io_boundary, search_paths: @controller_search_paths).build
|
|
341
|
+
rescue StandardError
|
|
342
|
+
ViewAssigns.empty
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
private :view_assigns
|
|
346
|
+
|
|
347
|
+
# #1047 — the render-site `locals:` index, and the compiled source every template was read through
|
|
348
|
+
# while it was built. Lazy and parent-only for the same reason {#view_assigns} is: it answers a
|
|
349
|
+
# question the analysis has not begun to ask yet. See {RenderLocals} for what a site contributes, and
|
|
350
|
+
# {#revalidate_indexes} for when a memoised index is dropped. A builder that raised is memoised as
|
|
351
|
+
# the empty index too — otherwise every hook call re-globbed and recompiled the project to fail
|
|
352
|
+
# again — and its nil fingerprint never equals a real one, so the next pass retries it.
|
|
353
|
+
def render_locals
|
|
354
|
+
@render_locals ||= begin
|
|
355
|
+
RenderLocals::Builder.new(
|
|
356
|
+
io_boundary: io_boundary, controller_search_paths: @controller_search_paths,
|
|
357
|
+
view_search_paths: @view_search_paths, helper_search_paths: HELPER_SEARCH_PATHS,
|
|
358
|
+
view_assigns: view_assigns
|
|
359
|
+
).build
|
|
360
|
+
rescue StandardError
|
|
361
|
+
RenderLocals.empty
|
|
362
|
+
end
|
|
104
363
|
end
|
|
364
|
+
private :render_locals
|
|
105
365
|
|
|
106
366
|
# ADR-37 — the four Action Pack phases run per-call over the engine-owned walk. Each rule gates on
|
|
107
367
|
# `controller_file?(path)` (the plugin only validates files under `controller_search_paths`,
|
|
@@ -144,7 +404,8 @@ module Rigor
|
|
|
144
404
|
diagnostics_for(
|
|
145
405
|
Analyzer.render_violations_for(
|
|
146
406
|
call_node: node, ancestors: context.ancestors, path: path,
|
|
147
|
-
view_search_roots: @view_search_paths,
|
|
407
|
+
view_search_roots: @view_search_paths, io_boundary: io_boundary,
|
|
408
|
+
controller_index: producer_value(:controller_index)
|
|
148
409
|
),
|
|
149
410
|
path: path, node: node
|
|
150
411
|
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Action Pack's framework namespaces — issue #534 item 7.
|
|
2
|
+
#
|
|
3
|
+
# The 2026-09-01 corpus sweep counted `ActionController` 37 times on mastodon as an unresolved
|
|
4
|
+
# constant. This file names the namespace and the errors controllers rescue, and nothing else.
|
|
5
|
+
#
|
|
6
|
+
# ## What must NOT be declared here, and why the list is so short
|
|
7
|
+
#
|
|
8
|
+
# `ActionController::Parameters` is absent on purpose, and it is the most important absence in this
|
|
9
|
+
# plugin. `Rigor::Plugin::Actionpack::REQUEST_CONTEXT_READER_TYPES` types `params` as a *lenient*
|
|
10
|
+
# nominal — a real class name Rigor ships no RBS for — precisely so that `params.require(...)`,
|
|
11
|
+
# `params.to_unsafe_h` and every other member of a surface no signature can complete resolve
|
|
12
|
+
# lenient-to-`Dynamic` instead of drawing `call.undefined-method`. Declaring the class here, even
|
|
13
|
+
# empty, would make it RBS-known and undo that. The same argument covers `ActionDispatch::Request`,
|
|
14
|
+
# `Request::Session`, `Flash::FlashHash`, `Flash::FlashNow` and `Cookies::CookieJar`.
|
|
15
|
+
#
|
|
16
|
+
# `ActionController::Base` and `ActionController::API` are absent for the stronger reason: every
|
|
17
|
+
# controller in a Rails app inherits from one of them, so declaring either empty would close the
|
|
18
|
+
# whole project's controller hierarchy and turn `render`, `before_action`, `head` and the rest into
|
|
19
|
+
# `call.undefined-method` on working code.
|
|
20
|
+
#
|
|
21
|
+
# What is left is exactly the shape that is safe: bare namespace modules nobody dispatches on, and
|
|
22
|
+
# exception classes whose inherited surface is a core Ruby one Rigor already carries. Each error
|
|
23
|
+
# class is listed in the manifest's `open_receivers:` (ADR-26), so the framework-specific readers
|
|
24
|
+
# this file does not enumerate (`ParameterMissing#param`, `UrlGenerationError#routes`, …) stay
|
|
25
|
+
# lenient too.
|
|
26
|
+
#
|
|
27
|
+
# Superclasses transcribed from Rails v8.0.2 (`actionpack/lib/action_controller/metal/exceptions.rb`
|
|
28
|
+
# and `strong_parameters.rb`); `ParameterMissing < KeyError` and `UnpermittedParameters < IndexError`
|
|
29
|
+
# are the two that are NOT under `ActionControllerError`, and getting that wrong would be a wrong
|
|
30
|
+
# `rescue` reachability answer, so both are stated from the source rather than from the pattern.
|
|
31
|
+
|
|
32
|
+
module ActionController
|
|
33
|
+
class ActionControllerError < StandardError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class BadRequest < ActionControllerError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class RoutingError < ActionControllerError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class UrlGenerationError < ActionControllerError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class MethodNotAllowed < ActionControllerError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class NotImplemented < MethodNotAllowed
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class UnknownFormat < ActionControllerError
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class InvalidAuthenticityToken < ActionControllerError
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class InvalidCrossOriginRequest < ActionControllerError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class MissingExactTemplate < UnknownFormat
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# `::KeyError` is generic in core RBS (`KeyError[K, R]`, the receiver and key of the failed lookup),
|
|
64
|
+
# so the supertype has to be applied. `untyped` on both: Rails raises it from a Parameters lookup and
|
|
65
|
+
# this signature has no business claiming what `#receiver` / `#key` answer.
|
|
66
|
+
class ParameterMissing < ::KeyError[untyped, untyped]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class UnpermittedParameters < ::IndexError
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Action View's namespace — issue #393.
|
|
2
|
+
#
|
|
3
|
+
# `ActionView::Base` is the `self_type:` of every ERB template unit the plugin compiles. It is declared
|
|
4
|
+
# here for one reason: so that the NAME resolves. `Analysis::TemplateUnits#seed` binds an unresolvable
|
|
5
|
+
# `self_type:` to `Dynamic[top]`, which is silent but also tells the engine nothing, and a declared
|
|
6
|
+
# receiver is what lets a later slice put a real helper surface behind it.
|
|
7
|
+
#
|
|
8
|
+
# ## Why it enumerates nothing
|
|
9
|
+
#
|
|
10
|
+
# The same argument `action_controller.rbs` makes about `ActionController::Parameters`, one step stronger.
|
|
11
|
+
# A template's implicit-self calls are `link_to`, `form_with`, `t`, `l`, `cache`, `content_for`,
|
|
12
|
+
# `image_tag`, `turbo_frame_tag`, every `*_path` / `*_url` route helper, every method in the project's own
|
|
13
|
+
# `ApplicationHelper` and `<Controller>Helper`, every `helper_method` a controller exported, and whatever
|
|
14
|
+
# the project's gems mixed in. No signature completes that surface, and a declared class DROPS every
|
|
15
|
+
# member its signature omits — so enumerating the ten helpers the design note names would turn the other
|
|
16
|
+
# thousand into `call.undefined-method` on working templates.
|
|
17
|
+
#
|
|
18
|
+
# So the class is named and left empty, and `open_receivers:` in the manifest (ADR-26) keeps its whole
|
|
19
|
+
# method surface lenient. That is the "lenient / open receivers, never closed" posture stated as code.
|
|
20
|
+
#
|
|
21
|
+
# `ActionView::UnknownLocal` is deliberately ABSENT — see `ViewUnits::UNKNOWN_LOCAL`. It is the name a
|
|
22
|
+
# strict-locals parameter is seeded with precisely because nothing resolves it, so the engine binds
|
|
23
|
+
# `Dynamic[top]` while the local's NAME still reaches the parse.
|
|
24
|
+
|
|
25
|
+
# ## The one declared member, and why it is not a helper
|
|
26
|
+
#
|
|
27
|
+
# `__rigor_yield` is issue #1047's reading of a LAYOUT's `yield`. `<%= yield %>` is legal ERB and illegal
|
|
28
|
+
# Ruby outside a method body, so `ErbCompiler` rewrites the keyword into a call on this receiver before
|
|
29
|
+
# either compiler runs — the template-unit seam does not wrap a unit's body in a synthesised method, so the
|
|
30
|
+
# body has to parse as written. The name is not Rails', is not reachable from any template a user writes,
|
|
31
|
+
# and exists only so the rewritten body resolves.
|
|
32
|
+
#
|
|
33
|
+
# It returns `String` and nothing narrower. Rails' `yield` hands back whatever the inner template's output
|
|
34
|
+
# buffer holds and `yield :sidebar` hands back a `content_for` buffer (an empty `SafeBuffer`, never nil,
|
|
35
|
+
# when nothing was provided); a lenient `String` is the
|
|
36
|
+
# widest honest reading of that, and it is what keeps a layout from seeding a type the analyzer could not
|
|
37
|
+
# justify. `*untyped` covers `yield`, `yield :sidebar` and `yield(:sidebar)` alike.
|
|
38
|
+
|
|
39
|
+
module ActionView
|
|
40
|
+
class Base
|
|
41
|
+
def __rigor_yield: (*untyped) -> String
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/source/node_children"
|
|
4
|
+
|
|
5
|
+
require "prism"
|
|
6
|
+
|
|
7
|
+
require_relative "serializer_index"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Plugin
|
|
11
|
+
class ActiveModelSerializers < Rigor::Plugin::Base
|
|
12
|
+
# Walks the configured serializer-search paths through the plugin's `IoBoundary`, parses each `.rb`
|
|
13
|
+
# file with Prism, records every `class X < Y` it finds with the facts the derivation needs, and
|
|
14
|
+
# keeps the classes whose ancestry closes back to a configured root:
|
|
15
|
+
#
|
|
16
|
+
# class ApplicationSerializer < ActiveModel::Serializer # root child
|
|
17
|
+
# class REST::AccountSerializer < ApplicationSerializer # reached through it
|
|
18
|
+
#
|
|
19
|
+
# The closure is what a direct-superclass match (the shape `rigor-pundit`'s policy discoverer uses)
|
|
20
|
+
# would miss, and a project base serializer is the common case rather than the exotic one. It is
|
|
21
|
+
# also the ONLY gate: a class the closure does not reach is not a serializer here, whatever it is
|
|
22
|
+
# called, because `object` is an ordinary name that any class may define.
|
|
23
|
+
#
|
|
24
|
+
# Every class in the scanned tree is recorded first and filtered afterwards, because the file that
|
|
25
|
+
# defines a base serializer may be parsed after the files that inherit from it.
|
|
26
|
+
class SerializerDiscoverer
|
|
27
|
+
# A `class << self` body is class-level for the same reason a `def self.x` body is, and it is a
|
|
28
|
+
# separate node type: without this arm a receiver-less `def object` inside one would register as
|
|
29
|
+
# an instance method, and an `object.<name>` there as a read of the resource.
|
|
30
|
+
SKIPPED_SCOPES = [Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode].freeze
|
|
31
|
+
|
|
32
|
+
# AMS's resource-backed declaration macros. Each names something the serializer renders by
|
|
33
|
+
# calling it on the resource, unless the serializer defines a method of that name itself.
|
|
34
|
+
DECLARATION_MACROS = %i[attributes attribute has_many has_one belongs_to].freeze
|
|
35
|
+
|
|
36
|
+
# Macros that DEFINE a method on the serializer rather than declaring one to read off the
|
|
37
|
+
# resource. `delegate :object, to: :wrapper` and `attr_reader :object` are both real ways to
|
|
38
|
+
# override AMS's reader, and neither writes a `def` for an ancestor walk to find; a name defined
|
|
39
|
+
# this way is also not a name the resource has to answer.
|
|
40
|
+
DEFINITION_MACROS = %i[attr_reader attr_accessor attr_writer delegate alias_method].freeze
|
|
41
|
+
|
|
42
|
+
# `object.tap`, `object.nil?`, `object.is_a?` and their siblings say nothing about which class the
|
|
43
|
+
# resource is, so they are not evidence.
|
|
44
|
+
#
|
|
45
|
+
# Listed rather than read from `Object.instance_methods`, which was the first shape and is
|
|
46
|
+
# process-dependent: `to_json`, `to_yaml` and `pretty_print` are on Object only because some
|
|
47
|
+
# library in the ANALYSER's process required them, so whether a serializer derived could change
|
|
48
|
+
# with Rigor's own load order. These are the public instance methods every Ruby object has from
|
|
49
|
+
# `Object` / `Kernel` alone.
|
|
50
|
+
UNIVERSAL_METHODS = %w[
|
|
51
|
+
! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup
|
|
52
|
+
enum_for eql? equal? extend freeze frozen? hash inspect instance_of? instance_variable_defined?
|
|
53
|
+
instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method
|
|
54
|
+
methods nil? object_id private_methods public_method public_methods public_send
|
|
55
|
+
remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods
|
|
56
|
+
taint tainted? tap then to_enum to_s trust untaint untrust untrusted? yield_self
|
|
57
|
+
].to_set.freeze
|
|
58
|
+
|
|
59
|
+
def initialize(io_boundary:, search_paths:, base_classes:)
|
|
60
|
+
@io_boundary = io_boundary
|
|
61
|
+
@search_paths = search_paths
|
|
62
|
+
@base_classes = base_classes.map { |name| name.to_s.delete_prefix("::") }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def discover
|
|
66
|
+
candidates = []
|
|
67
|
+
ruby_files_under(@search_paths).each do |path|
|
|
68
|
+
contents = read_safely(path)
|
|
69
|
+
next if contents.nil?
|
|
70
|
+
|
|
71
|
+
walk(Prism.parse(contents).value, []) do |entry_fields|
|
|
72
|
+
candidates << SerializerIndex::Entry.new(file_path: path, **entry_fields)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
SerializerIndex.new(close_over_roots(candidates))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Keeps only the classes whose declared-superclass chain reaches one of the roots. Iterating to a
|
|
81
|
+
# fixed point rather than recursing keeps a cyclic `class A < B; class B < A` source — which Ruby
|
|
82
|
+
# rejects but a static parse can still be handed — from recursing forever.
|
|
83
|
+
def close_over_roots(candidates)
|
|
84
|
+
by_name = candidates.to_h { |entry| [entry.class_name, entry] }
|
|
85
|
+
reached = @base_classes.to_set
|
|
86
|
+
loop do
|
|
87
|
+
grown = candidates.select do |entry|
|
|
88
|
+
!reached.include?(entry.class_name) && reached.include?(entry.superclass_name)
|
|
89
|
+
end
|
|
90
|
+
break if grown.empty?
|
|
91
|
+
|
|
92
|
+
grown.each { |entry| reached << entry.class_name }
|
|
93
|
+
end
|
|
94
|
+
reached.filter_map { |name| by_name[name] }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def read_safely(path)
|
|
98
|
+
@io_boundary.read_file(path)
|
|
99
|
+
rescue Plugin::AccessDeniedError, Errno::ENOENT
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def ruby_files_under(roots)
|
|
104
|
+
roots.flat_map do |root|
|
|
105
|
+
absolute = File.expand_path(root)
|
|
106
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
107
|
+
next [] unless @io_boundary.directory?(absolute)
|
|
108
|
+
|
|
109
|
+
Dir.glob(File.join(absolute, "**", "*.rb"))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def walk(node, lexical_path, &)
|
|
114
|
+
return if node.nil?
|
|
115
|
+
|
|
116
|
+
case node
|
|
117
|
+
when Prism::ClassNode then visit_class(node, lexical_path, &)
|
|
118
|
+
when Prism::ModuleNode then visit_module(node, lexical_path, &)
|
|
119
|
+
else
|
|
120
|
+
node.rigor_each_child { |child| walk(child, lexical_path, &) }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def visit_class(node, lexical_path, &)
|
|
125
|
+
local_name = constant_path_name(node.constant_path)
|
|
126
|
+
return if local_name.nil?
|
|
127
|
+
|
|
128
|
+
superclass = node.superclass ? constant_path_name(node.superclass) : nil
|
|
129
|
+
unless superclass.nil?
|
|
130
|
+
yield({ class_name: qualify(lexical_path, local_name),
|
|
131
|
+
superclass_name: superclass.delete_prefix("::"),
|
|
132
|
+
**collect_facts(node.body) })
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
walk(node.body, lexical_path + [local_name], &) if node.body
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def visit_module(node, lexical_path, &)
|
|
139
|
+
local_name = constant_path_name(node.constant_path)
|
|
140
|
+
return if local_name.nil?
|
|
141
|
+
|
|
142
|
+
walk(node.body, lexical_path + [local_name], &) if node.body
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The three name sets, gathered in ONE pass over the class body. A nested class or module is not
|
|
146
|
+
# descended into — its `def`s and its `object` reads belong to it, not to this class.
|
|
147
|
+
def collect_facts(body)
|
|
148
|
+
facts = { declared_names: Set.new, object_reads: Set.new, own_method_names: Set.new }
|
|
149
|
+
collect_from(body, facts)
|
|
150
|
+
facts.transform_values { |set| set.to_a.sort.freeze }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def collect_from(node, facts)
|
|
154
|
+
return if node.nil? || SKIPPED_SCOPES.any? { |kind| node.is_a?(kind) }
|
|
155
|
+
|
|
156
|
+
case node
|
|
157
|
+
when Prism::DefNode
|
|
158
|
+
# A `def self.x` body is class-level: its `object` would be a NoMethodError at run time, so
|
|
159
|
+
# neither its name nor its reads are evidence about the resource.
|
|
160
|
+
return unless node.receiver.nil?
|
|
161
|
+
|
|
162
|
+
facts[:own_method_names] << node.name.to_s
|
|
163
|
+
when Prism::CallNode then record_call(node, facts)
|
|
164
|
+
end
|
|
165
|
+
node.rigor_each_child { |child| collect_from(child, facts) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def record_call(node, facts)
|
|
169
|
+
if node.receiver.nil? && DECLARATION_MACROS.include?(node.name)
|
|
170
|
+
symbol_arguments(node).each { |name| facts[:declared_names] << name }
|
|
171
|
+
elsif node.receiver.nil? && DEFINITION_MACROS.include?(node.name)
|
|
172
|
+
symbol_arguments(node).each { |name| facts[:own_method_names] << name }
|
|
173
|
+
elsif object_reader?(node.receiver) && !UNIVERSAL_METHODS.include?(node.name.to_s)
|
|
174
|
+
facts[:object_reads] << node.name.to_s
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def object_reader?(receiver)
|
|
179
|
+
receiver.is_a?(Prism::CallNode) && receiver.name == :object && receiver.receiver.nil? &&
|
|
180
|
+
receiver.arguments.nil?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def symbol_arguments(node)
|
|
184
|
+
(node.arguments&.arguments || []).filter_map do |argument|
|
|
185
|
+
argument.unescaped if argument.is_a?(Prism::SymbolNode)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# `class REST::AccountSerializer` inside no module is already fully qualified; a class written as
|
|
190
|
+
# `class AccountSerializer` inside `module REST` is not. A name the source rooted explicitly
|
|
191
|
+
# (`class ::AccountSerializer`) keeps neither the root marker nor the lexical prefix.
|
|
192
|
+
def qualify(lexical_path, local_name)
|
|
193
|
+
return local_name.delete_prefix("::") if local_name.start_with?("::")
|
|
194
|
+
|
|
195
|
+
(lexical_path + [local_name]).join("::")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def constant_path_name(node)
|
|
199
|
+
case node
|
|
200
|
+
when Prism::ConstantReadNode then node.name.to_s
|
|
201
|
+
when Prism::ConstantPathNode then constant_path_parts(node)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def constant_path_parts(node)
|
|
206
|
+
parts = []
|
|
207
|
+
current = node
|
|
208
|
+
while current.is_a?(Prism::ConstantPathNode)
|
|
209
|
+
parts.unshift(current.name.to_s)
|
|
210
|
+
current = current.parent
|
|
211
|
+
end
|
|
212
|
+
case current
|
|
213
|
+
when nil then "::#{parts.join('::')}"
|
|
214
|
+
when Prism::ConstantReadNode then "#{current.name}::#{parts.join('::')}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|