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
|
@@ -80,14 +80,16 @@ partition `declared` — they sum to 1497. **`reachable only from tests`
|
|
|
80
80
|
is a subset of `reachable`**, not a fifth bucket, so do not add it in.
|
|
81
81
|
|
|
82
82
|
Read `roots` first. A root is a declaration something outside your code
|
|
83
|
-
reaches — an entry point, a route, a framework convention.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
reaches — an entry point, a route, a framework convention. The
|
|
84
|
+
`(… from plugins, …)` parenthetical is omitted entirely when no plugin
|
|
85
|
+
contributed a root, so a bare `roots: N` with no such note is itself
|
|
86
|
+
the signal. Reachability is computed *from* roots, so a thin root set
|
|
87
|
+
inflates everything below it. The pathological case is a Rails
|
|
88
|
+
application with no `from plugins` note at all: nothing in Ruby source
|
|
89
|
+
names a controller, so every controller you own appears dead. There is
|
|
90
|
+
no published healthy ratio to compare against; what you are checking
|
|
91
|
+
is whether the number is plausible for your framework, and a missing
|
|
92
|
+
`from plugins` note on a framework app never is.
|
|
91
93
|
|
|
92
94
|
## Getting the number down
|
|
93
95
|
|
|
@@ -214,6 +214,16 @@ rigor effects --pure
|
|
|
214
214
|
436 methods on Redmine, and they are your `%a{pure}` candidates — the on-ramp to
|
|
215
215
|
the last section of this chapter.
|
|
216
216
|
|
|
217
|
+
You do not have to write them all out by hand. With the `effects:` block in
|
|
218
|
+
place, `rigor sig-gen` annotates the methods in this set whose signature it is
|
|
219
|
+
proposing anyway: `%a{pure}` above every method that is exhaustive, undischarged,
|
|
220
|
+
free of surviving `≤` labels, not already carrying a bound you wrote, and calling
|
|
221
|
+
nothing the analyzer had no description for; and, under `--effect-envelopes`, the labelled spelling for the methods that
|
|
222
|
+
do have a footprint. A method that is clean only because `effects.tolerated:`
|
|
223
|
+
says so is deliberately left bare — the annotation would travel to readers who
|
|
224
|
+
do not share your tolerated list. See
|
|
225
|
+
[handbook chapter 11](../handbook/11-sig-gen.md#emitting-effect-annotations).
|
|
226
|
+
|
|
217
227
|
### Asking it a question
|
|
218
228
|
|
|
219
229
|
**By label.** The question this chapter opens with, in one command:
|
|
@@ -54,6 +54,9 @@ The full catalogue, with a one-line scope for every plugin, is
|
|
|
54
54
|
`Result` / `Option` unwrap types and synthesises `Enum` variants.
|
|
55
55
|
- [rigor-pundit](rigor-pundit.md) — policy-class existence and
|
|
56
56
|
`authorize(record, :action)` predicate validation.
|
|
57
|
+
- [rigor-active-model-serializers](rigor-active-model-serializers.md) —
|
|
58
|
+
types the `object` reader inside a serializer as the serializer's
|
|
59
|
+
model (no diagnostics).
|
|
57
60
|
- [rigor-sidekiq](rigor-sidekiq.md) — Sidekiq `Worker.perform_*`
|
|
58
61
|
argument arity against the discovered `#perform`.
|
|
59
62
|
- [rigor-actioncable](rigor-actioncable.md) — `broadcast_to` channel
|
|
@@ -62,6 +65,10 @@ The full catalogue, with a one-line scope for every plugin, is
|
|
|
62
65
|
through Minitest / Test::Unit assertions and spec matchers.
|
|
63
66
|
- [rigor-graphql](rigor-graphql.md) — GraphQL-Ruby type / enum / input
|
|
64
67
|
/ mutation table publication (cross-plugin facts, no diagnostics).
|
|
68
|
+
- [rigor-grape](rigor-grape.md) — types the `Grape::API` endpoint DSL
|
|
69
|
+
(`params`, `namespace`, verb macros, `desc`, `route_setting`) and
|
|
70
|
+
`Grape::Entity` `expose` declarations, including the `instance_eval`'d
|
|
71
|
+
block `self` bindings.
|
|
65
72
|
- [rigor-rspec-rails](rigor-rspec-rails.md) — `have_http_status`
|
|
66
73
|
argument validation (out-of-range codes, unknown status symbols).
|
|
67
74
|
- [rigor-shoulda-matchers](rigor-shoulda-matchers.md) — shoulda matcher
|
|
@@ -36,7 +36,7 @@ ActionCable.server.broadcast("chat_room_42", body: "hi") # warning: no such s
|
|
|
36
36
|
| `plugin.actioncable.broadcast-stream` | info | `ActionCable.server.broadcast("...", ...)` matched a registered `stream_from` literal |
|
|
37
37
|
| `plugin.actioncable.unknown-channel` | error | the receiver ends in `Channel` but is not in the index (with a did-you-mean) |
|
|
38
38
|
| `plugin.actioncable.unknown-stream` | warning | the literal stream name matched no `stream_from` registration (with a did-you-mean) |
|
|
39
|
-
| `plugin.actioncable.load-error` | warning | channel discovery failed (parse/read error) — once per
|
|
39
|
+
| `plugin.actioncable.load-error` | warning | channel discovery failed (parse/read error) — once per run, on `.rigor.yml` |
|
|
40
40
|
|
|
41
41
|
The `unknown-stream` check is **suppressed** when any discovered
|
|
42
42
|
channel registers a dynamic stream (`stream_from interpolated_string`
|
|
@@ -100,6 +100,13 @@ default.
|
|
|
100
100
|
- **The `#receive` contract is path-scoped, not class-scoped** (ADR-28):
|
|
101
101
|
any `def receive(data)` defined anywhere under `channel_search_paths`
|
|
102
102
|
is typed, even on a class that isn't an ActionCable channel.
|
|
103
|
+
- **The "failed to discover channels" warning is run-scoped.** It is
|
|
104
|
+
a fact about your configuration, not about any one source file, so
|
|
105
|
+
it is reported once per run on `.rigor.yml` rather than repeated on
|
|
106
|
+
every analysed file. It is `:warning`, so if you baselined it at its
|
|
107
|
+
old position that entry no longer matches and the row fails a
|
|
108
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
109
|
+
regenerate`.
|
|
103
110
|
|
|
104
111
|
## Plugin internals
|
|
105
112
|
|
|
@@ -69,6 +69,13 @@ plugins:
|
|
|
69
69
|
which the report already records. Rooting every class under
|
|
70
70
|
`app/mailers` instead would mark a mailer nothing sends reachable
|
|
71
71
|
forever on no evidence.
|
|
72
|
+
- **The "failed to discover mailers" warning is run-scoped.** It is
|
|
73
|
+
a fact about your configuration, not about any one source file, so
|
|
74
|
+
it is reported once per run on `.rigor.yml` rather than repeated on
|
|
75
|
+
every analysed file. It is `:warning`, so if you baselined it at its
|
|
76
|
+
old position that entry no longer matches and the row fails a
|
|
77
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
78
|
+
regenerate`.
|
|
72
79
|
|
|
73
80
|
## Plugin internals
|
|
74
81
|
|
|
@@ -54,6 +54,7 @@ plugins:
|
|
|
54
54
|
config:
|
|
55
55
|
controller_search_paths: ["app/controllers"] # default
|
|
56
56
|
view_search_paths: ["app/views"] # default
|
|
57
|
+
view_type_checks: false # default
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
## What it types
|
|
@@ -93,8 +94,268 @@ so *can* fold a condition — and `Mime::NullType`, the value `format`
|
|
|
93
94
|
returns when there is no format, answers `nil?` with `true` while
|
|
94
95
|
being a real object. Typing it needs a nil-aware answer.
|
|
95
96
|
|
|
97
|
+
## Framework constants resolve
|
|
98
|
+
|
|
99
|
+
The plugin ships a small bundled signature naming the
|
|
100
|
+
`ActionController` namespace and the errors controllers rescue —
|
|
101
|
+
`ParameterMissing`, `UnpermittedParameters`, `RoutingError`,
|
|
102
|
+
`BadRequest`, `UnknownFormat`, `InvalidAuthenticityToken` and their
|
|
103
|
+
siblings. A `rescue ActionController::ParameterMissing => e` now types
|
|
104
|
+
`e` as that class instead of leaving it opaque.
|
|
105
|
+
|
|
106
|
+
The signature names **only** those. `ActionController::Base` and
|
|
107
|
+
`ActionController::API` are left undeclared on purpose: every
|
|
108
|
+
controller in the app inherits from one of them, and an incomplete
|
|
109
|
+
declaration of a superclass turns every member it omits — `render`,
|
|
110
|
+
`before_action`, `head` — into a report on working code. For the same
|
|
111
|
+
reason `ActionController::Parameters` and the `ActionDispatch` readers
|
|
112
|
+
above stay undeclared; their leniency is what makes the `params`
|
|
113
|
+
typing safe.
|
|
114
|
+
|
|
115
|
+
## ERB templates as effect units
|
|
116
|
+
|
|
117
|
+
Every `app/views/**/*.erb` is compiled to Ruby and analysed as one
|
|
118
|
+
**effect unit**, keyed `view:users/show.html` — Rails' own logical
|
|
119
|
+
name with the handler dropped, so an ERB → Haml rewrite is not a
|
|
120
|
+
rename. Nothing has to be enabled: activating the plugin is what
|
|
121
|
+
claims the templates.
|
|
122
|
+
|
|
123
|
+
What that buys is the answer to *"what does this request actually
|
|
124
|
+
do"* past the `render` line. A partial that calls `@user.update`
|
|
125
|
+
reports `io.db.write` at `app/views/users/_card.html.erb`, a
|
|
126
|
+
`Time.now` in a layout fragment reports `nondet.time`, a leftover
|
|
127
|
+
`puts` reports `io.output.stdout` — all of it in `rigor effects` and
|
|
128
|
+
in the snapshot, so a template that starts writing shows up in a
|
|
129
|
+
diff.
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
$ rigor effects
|
|
133
|
+
view:users/_card.html: [mutate.local, nondet.time] ≤ [io.db.write] …?
|
|
134
|
+
view:users/show.html: [mutate.local] ≤ [io.db.read] …?
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**The compiler** is Erubi when it resolves in your project's bundle
|
|
138
|
+
— Rails' own — and stdlib `ERB` otherwise. Erubi is never added to
|
|
139
|
+
your Gemfile and is not a Rigor dependency
|
|
140
|
+
([ADR-90](../../adr/90-target-library-resolution-from-project-bundle.md)).
|
|
141
|
+
Either way the line map is measured rather than assumed, so a
|
|
142
|
+
finding names the template's own line; the column is always 1,
|
|
143
|
+
because a compiler rewrites the text of each line and a column of
|
|
144
|
+
the compiled Ruby would name nothing you wrote.
|
|
145
|
+
|
|
146
|
+
**What `self` is.** `ActionView::Base`, declared so the name
|
|
147
|
+
resolves and **open** so its method surface stays lenient. That is
|
|
148
|
+
what keeps `link_to`, `form_with`, `t`, `content_for`, your own
|
|
149
|
+
`ApplicationHelper` methods and every route helper from drawing a
|
|
150
|
+
finding per line.
|
|
151
|
+
|
|
152
|
+
**What is in scope.** `@ivars` are seeded from the controller
|
|
153
|
+
actions that render the template — the implicit render
|
|
154
|
+
(`UsersController#show` → `users/show`) and explicit
|
|
155
|
+
`render :edit` / `render "admin/form"`. Two restrictions keep a seed
|
|
156
|
+
from claiming a type the template will not find. Only assignments
|
|
157
|
+
whose right-hand side is one record and cannot be `nil` contribute:
|
|
158
|
+
`User.find(id)` and `Model.new` do; `find_by` never does, nor a call
|
|
159
|
+
written to return several records — `User.find(a, b)`,
|
|
160
|
+
`User.find([1, 2])`, `User.find(*ids)`, `User.create([…])` or a
|
|
161
|
+
`find` with a block. A single argument that only holds an Array at
|
|
162
|
+
runtime (`User.find(ids)`) still seeds the model. And only
|
|
163
|
+
assignments the action reaches on **every** path contribute — not
|
|
164
|
+
one inside an `if`, a `case`, a `rescue`, a loop or a block, and
|
|
165
|
+
nothing from a `before_action` carrying `if:` / `unless:`. Anything
|
|
166
|
+
else leaves the ivar unseeded, which reads as `Dynamic` and is
|
|
167
|
+
silent. A partial inherits the assigns of its own directory, because
|
|
168
|
+
an ivar is not a local. Locals come from the Rails 7.1 strict-locals
|
|
169
|
+
comment:
|
|
170
|
+
|
|
171
|
+
```erb
|
|
172
|
+
<%# locals: (user:, admin: false) %>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**`call.*` findings are off inside templates by default**, while the
|
|
176
|
+
synthesised receivers are still coarse — measured on redmine and
|
|
177
|
+
mastodon, the feature adds **zero** new findings to either
|
|
178
|
+
([the measurement note](../../notes/20260917-erb-template-units.md)).
|
|
179
|
+
Set `view_type_checks: true` to opt in and have `@user.nmae` in
|
|
180
|
+
`show.html.erb` reported like any other call.
|
|
181
|
+
|
|
182
|
+
`flow.*` **reports in a template like anywhere else.** It was
|
|
183
|
+
suppressed alongside `call.*` for one measured reason — a partial's
|
|
184
|
+
optional-local preamble (`<% path = nil unless defined? path %>`)
|
|
185
|
+
really did assign nil, because nothing told the unit its render site
|
|
186
|
+
had bound `path`. Render-site `locals:` are traced now, and the same
|
|
187
|
+
two projects re-measured with the family reporting are byte-identical
|
|
188
|
+
to the runs with it suppressed ([the #1047 note](../../notes/20260917-render-locals-and-layouts.md)).
|
|
189
|
+
|
|
190
|
+
### Locals come from the render site
|
|
191
|
+
|
|
192
|
+
A partial's parameters are bound by whoever renders it, and every
|
|
193
|
+
spelling of that is read — on both sides of the render:
|
|
194
|
+
|
|
195
|
+
```erb
|
|
196
|
+
<%= render partial: "card", locals: { user: @user } %>
|
|
197
|
+
<%= render "card", user: @user %> <%# a view's trailing hash IS locals %>
|
|
198
|
+
<%= render partial: "card", collection: @users, as: :row %>
|
|
199
|
+
<%= render partial: "card", object: @user %>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`collection:` binds `row`, `row_counter` and `row_iteration`;
|
|
203
|
+
`object:` and `as:` bind one local named after the partial or after
|
|
204
|
+
`as:`. A controller's `render partial: …, locals: …` is read the same
|
|
205
|
+
way — but a controller's *trailing hash* is options, so
|
|
206
|
+
`render :show, status: :ok` binds nothing.
|
|
207
|
+
|
|
208
|
+
A partial rendered from several sites gets the **union** of the
|
|
209
|
+
names. A name only some of them pass is still bound, typed
|
|
210
|
+
`Dynamic` — absence is what produced the false positives above.
|
|
211
|
+
A **type** is claimed only where every site agrees on one it could
|
|
212
|
+
settle from the call itself (`User.find(1)`, or an ivar the rendering
|
|
213
|
+
action's own seeds typed); anything else is `Dynamic`. A
|
|
214
|
+
strict-locals comment still wins where a template carries one.
|
|
215
|
+
|
|
216
|
+
A partial's **own** optional-local test counts too:
|
|
217
|
+
`<% size = nil unless defined?(size) %>`, `local_assigns[:size]` and
|
|
218
|
+
`local_assigns.key?(:size)` bind `size` even when no render site the
|
|
219
|
+
plugin can read passes it — a `locals: opts` hash, a `render` from a
|
|
220
|
+
helper, or a local with a default nobody passes. A name a helper under
|
|
221
|
+
`app/helpers` defines is left alone, so
|
|
222
|
+
`<% if defined?(current_user) %>` stays a helper call. A helper that a
|
|
223
|
+
gem or a concern defines is not seen by that scan, and its name is
|
|
224
|
+
bound as a `Dynamic` local instead.
|
|
225
|
+
|
|
226
|
+
### The controller → template edge
|
|
227
|
+
|
|
228
|
+
A controller action's summary **includes what its template does**.
|
|
229
|
+
`render :show`, `render "show"`, `render "admin/form"`,
|
|
230
|
+
`render template:`, `render action:`, `render partial:` (with or
|
|
231
|
+
without `collection:`) and the implicit render of
|
|
232
|
+
`<controller>/<action>` all reach the template's own unit, and a
|
|
233
|
+
template reaches the partials *it* renders — so an `io.db.write` in
|
|
234
|
+
`app/views/users/_card.html.erb` shows up on `UsersController#show`
|
|
235
|
+
three hops away, and `rigor effects explain` prints the path.
|
|
236
|
+
|
|
237
|
+
A partial is looked up in the rendering template's own format, with
|
|
238
|
+
the one fallback Action View itself hard-codes: a `.js.erb` template
|
|
239
|
+
reaches `_list.js.erb` where it exists and `_list.html.erb`
|
|
240
|
+
otherwise, which is how "a JS response that injects rendered HTML"
|
|
241
|
+
works at all. Only one of the two is joined, never both. A `.json`,
|
|
242
|
+
`.xml` or `.turbo_stream` template gets no fallback — what those fall
|
|
243
|
+
back to depends on the request's `Accept` header, which the source
|
|
244
|
+
does not say — and neither does a render site that names its format
|
|
245
|
+
(`formats: [:js]`, `render "list.js"`) or a controller-side `render`.
|
|
246
|
+
|
|
247
|
+
The fallback stops at a template that **exists and produced no
|
|
248
|
+
unit**: a `_list.js.haml`, or a `_list.js.erb` whose compiled Ruby
|
|
249
|
+
does not parse. Rails runs that file, so the `.html` one's effects
|
|
250
|
+
are not what the render produces, and the taint stays. That is why
|
|
251
|
+
this plugin claims `app/views/**/*.{haml,slim,jbuilder,builder,rabl,ruby}`
|
|
252
|
+
and compiles none of them: the claim is how the engine learns a
|
|
253
|
+
template is there. A handler outside that list is invisible, and a
|
|
254
|
+
render of one still falls back.
|
|
255
|
+
|
|
256
|
+
Two approximations ride along, and each costs labels rather than a
|
|
257
|
+
taint. A partial reached *through* the fallback renders its own
|
|
258
|
+
partials in `html`, while Action View's context is still
|
|
259
|
+
`[:js, :html]`; where a nested partial exists in both formats, the
|
|
260
|
+
`.js` template gets the `.html` one's labels. And a template whose
|
|
261
|
+
name carries no format at all (`_row.jbuilder`) blocks nothing, since
|
|
262
|
+
its key has no format to block — which happens to agree with Rails,
|
|
263
|
+
which ranks a formatted template above it. Both are zero occurrences
|
|
264
|
+
on the measured corpus.
|
|
265
|
+
|
|
266
|
+
The `template-not-analysed` taint on a `render` is discharged
|
|
267
|
+
exactly when the edge lands on a real unit. It **stays** when it
|
|
268
|
+
does not, and both cases are common enough to name:
|
|
269
|
+
|
|
270
|
+
- the target is computed — `render params[:view]`, or
|
|
271
|
+
`render formats: some_format`. The render site is read from
|
|
272
|
+
literals only, so anything computed keeps the honest "and possibly
|
|
273
|
+
more";
|
|
274
|
+
- the target names no template this plugin compiled — a `render
|
|
275
|
+
partial: @thing`, or a partial that exists in neither the requested
|
|
276
|
+
format nor its fallback;
|
|
277
|
+
- the template is outside `app/views/**/*.erb` — a Haml, Slim or
|
|
278
|
+
Jbuilder view, which this plugin does not claim.
|
|
279
|
+
|
|
280
|
+
`render json:`, `render plain:` and the rest of the non-template
|
|
281
|
+
family are left alone: they render no template, the rule declines,
|
|
282
|
+
and the row reads exactly as it did before.
|
|
283
|
+
|
|
284
|
+
An action that answered for itself is **not** edged to the
|
|
285
|
+
conventional template. `redirect_to`, `head`, `send_data` and
|
|
286
|
+
`send_file` each mean the implicit render did not happen, and
|
|
287
|
+
attributing `users/away` to an action that redirects would be a view
|
|
288
|
+
it never runs.
|
|
289
|
+
|
|
290
|
+
### Holding views to an effect budget
|
|
291
|
+
|
|
292
|
+
A view unit is an `effects.envelopes:` subject like any class, and a
|
|
293
|
+
finding is positioned in the template:
|
|
294
|
+
|
|
295
|
+
```yaml
|
|
296
|
+
effects:
|
|
297
|
+
envelopes:
|
|
298
|
+
- match: "app/views/**/*"
|
|
299
|
+
effect: [mutate.local]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
A bound judges only what Rigor proved by reading code, so what this
|
|
303
|
+
catches in a view is what Rigor's own catalogue proves: a `puts`
|
|
304
|
+
(`io.output.stdout`) or a `Time.now` (`nondet.time`).
|
|
305
|
+
|
|
306
|
+
**Plugin-sourced labels are never judged by an envelope.** A plugin's
|
|
307
|
+
statement about a framework method — `User.find` is `io.db.read`,
|
|
308
|
+
`@user.update` is `io.db.write`, `perform_later` is `job.enqueue` —
|
|
309
|
+
rides the declared (`≤`) lane, and the envelope check reads the proven
|
|
310
|
+
one. Listing `io.db.read` in a view's envelope, or leaving it out,
|
|
311
|
+
changes nothing: the lazy `<%= user.posts.count %>` is not a finding
|
|
312
|
+
either way. That is a property of the whole Rails effect layer rather
|
|
313
|
+
than of views ([ADR-103](../../adr/103-effect-labels.md) WD17, ruled
|
|
314
|
+
in [#1059](https://github.com/rigortype/rigor/issues/1059)).
|
|
315
|
+
|
|
316
|
+
What notices a view that starts reading the database is the effect
|
|
317
|
+
snapshot. With `.rigor-effects.yml` committed, `rigor effects check`
|
|
318
|
+
fails by default on drift in either lane, and a template that gains a
|
|
319
|
+
query shows up in the diff as a declared-lane addition:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
view:users/show.html ≤+ io.db.read
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
That is a ratchet on the whole project's recorded effects, reviewed as a
|
|
326
|
+
diff — not a policy scoped to `app/views/**/*`. See
|
|
327
|
+
[Effect labels](../19-effect-labels.md#what-a-bound-can-and-cannot-see).
|
|
328
|
+
|
|
96
329
|
## Limitations
|
|
97
330
|
|
|
331
|
+
- **A controller's own layout is not edged.** A layout is a unit
|
|
332
|
+
now, and a `render layout:` *inside a view* reaches it — but the
|
|
333
|
+
layout Rails wraps an action's template in (`layouts/application`,
|
|
334
|
+
or whatever `layout "base"` named) is not attributed to that
|
|
335
|
+
action. A callee rule may read the call's literals, the unit's
|
|
336
|
+
owner and the unit's key, and a layout's name is none of those:
|
|
337
|
+
it is a class-body declaration plus a convention lookup against
|
|
338
|
+
the view tree. So the layout's own effects reach a view that
|
|
339
|
+
renders it explicitly and no further.
|
|
340
|
+
- **`yield` in a layout is a `String` and nothing more.** The
|
|
341
|
+
keyword is rewritten into a declared call on the view context so
|
|
342
|
+
the body parses; what the inner template produced is never
|
|
343
|
+
modelled.
|
|
344
|
+
- **Unsaved render sites are not read in the editor.** The
|
|
345
|
+
render-site index reads templates and controllers from disk, so a
|
|
346
|
+
`locals:` you have typed but not saved does not reach the partial
|
|
347
|
+
until the save. A keystroke recompiles only the buffer, and a save
|
|
348
|
+
rebuilds the project's analysis as it always has. A view changed on
|
|
349
|
+
disk *without* a save the editor sees (a `git checkout`, a
|
|
350
|
+
formatter run elsewhere) recompiles every view on the next
|
|
351
|
+
publish, because a partial's locals can come from any of them. A
|
|
352
|
+
full `rigor check` does
|
|
353
|
+
the same compile work it did before — the index hands its compiled
|
|
354
|
+
sources to the unit transform rather than compiling twice.
|
|
355
|
+
- **ERB only, under `app/views`.** `template_globs:` is a manifest
|
|
356
|
+
row, read without running plugin code, so it cannot consult
|
|
357
|
+
`view_search_paths:`. Haml, Slim and Jbuilder are the same seam
|
|
358
|
+
behind a different compiler and are not claimed.
|
|
98
359
|
- **Implicit-self helpers only.** `*_path` / `*_url` calls with an
|
|
99
360
|
explicit receiver (`Rails.application.routes.url_helpers.x_path`)
|
|
100
361
|
are passed through.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# rigor-active-model-serializers
|
|
2
|
+
|
|
3
|
+
Teaches Rigor what `object` is inside an ActiveModel::Serializer
|
|
4
|
+
subclass, so the reads below it — `object.username`,
|
|
5
|
+
`object.account.display_name` — resolve against your model instead of
|
|
6
|
+
dispatching on an unknown receiver. It reads source only; no
|
|
7
|
+
ActiveModelSerializers runtime dependency.
|
|
8
|
+
|
|
9
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`, alongside
|
|
10
|
+
`rigor-activerecord`, which is what tells this plugin which models exist
|
|
11
|
+
and what they answer:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
plugins:
|
|
15
|
+
- rigor-activerecord
|
|
16
|
+
- rigor-active-model-serializers
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What it does — no diagnostics
|
|
20
|
+
|
|
21
|
+
The plugin emits nothing of its own. It contributes one return type and
|
|
22
|
+
the gem's framework constants, so that
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class REST::AccountSerializer < ActiveModel::Serializer
|
|
26
|
+
attributes :id, :username, :display_name
|
|
27
|
+
|
|
28
|
+
def display_name
|
|
29
|
+
object.display_name
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
types `object` as `Account`, and `object.display_name` as the
|
|
35
|
+
`accounts.display_name` column's type. What you notice is the diagnostics
|
|
36
|
+
that now CAN fire — a typo below a serializer's `object` used to be
|
|
37
|
+
invisible:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
app/serializers/errors_serializer.rb:11:23: error: undefined method `nickname' for String [call.undefined-method]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
and `rigor coverage` reporting a larger share of `app/serializers` as
|
|
44
|
+
precise. On Mastodon that share went from 51.1 % to 53.2 % with no change
|
|
45
|
+
at all to the diagnostic set
|
|
46
|
+
([the measurement](../../notes/20260917-ams-object-recognizer.md)).
|
|
47
|
+
|
|
48
|
+
## When `object` is typed, and when it is not
|
|
49
|
+
|
|
50
|
+
Nothing in a serializer's source states which class it serializes — AMS
|
|
51
|
+
binds the resource when the serializer is constructed. So the type is
|
|
52
|
+
always DERIVED, and two independent things have to agree before the
|
|
53
|
+
plugin contributes one:
|
|
54
|
+
|
|
55
|
+
1. **The name resolves.** `<Model>Serializer` names exactly one model
|
|
56
|
+
your project has — `REST::AccountSerializer` → `Account`.
|
|
57
|
+
2. **The model answers the serializer.** Every name the serializer will
|
|
58
|
+
read off its resource — the `attributes` / `attribute` / `has_many` /
|
|
59
|
+
`has_one` / `belongs_to` declarations it does not define itself, plus
|
|
60
|
+
every `object.<name>` in its body — is a column, per-column predicate,
|
|
61
|
+
association, enum, alias, scope or macro-defined method of that model,
|
|
62
|
+
or a method your project defines on it or an ancestor.
|
|
63
|
+
|
|
64
|
+
One unanswered name declines the whole serializer, because the resource
|
|
65
|
+
is then something else — commonly a presenter or a decorator around the
|
|
66
|
+
model, which shares most of its surface and is given away by the one or
|
|
67
|
+
two names that differ. On Mastodon that check is what stops
|
|
68
|
+
`REST::ConversationSerializer` typing as `Conversation` when its resource
|
|
69
|
+
is an `AccountConversation`.
|
|
70
|
+
|
|
71
|
+
| your code | `object` types as |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `REST::AccountSerializer`, `Account` exists and answers it | `Account` |
|
|
74
|
+
| `Admin::AccountSerializer`, where `Admin::Account` and `Account` both exist | unchanged — the reading is ambiguous and nothing here ranks one above the other |
|
|
75
|
+
| `ConversationSerializer` whose declarations `Conversation` cannot answer | unchanged (`Dynamic`) |
|
|
76
|
+
| a serializer that reads nothing off its resource | unchanged — there is nothing to check the name against |
|
|
77
|
+
| `ContextSerializer`, and no `Context` **model** exists | unchanged (`Dynamic`) |
|
|
78
|
+
| a serializer you listed in `model_overrides` | the model you named |
|
|
79
|
+
| `object` written outside a serializer, or over your own `def object` | unchanged (`Dynamic`) |
|
|
80
|
+
|
|
81
|
+
A serializer is a class under `serializer_search_paths` whose superclass
|
|
82
|
+
chain reaches `ActiveModel::Serializer` — including through your own base
|
|
83
|
+
serializer (`class ApplicationSerializer < ActiveModel::Serializer`, then
|
|
84
|
+
`class AccountSerializer < ApplicationSerializer`). A class the chain does
|
|
85
|
+
not reach is not a serializer here whatever it is called: `object` is an
|
|
86
|
+
ordinary method name, and a `Json::ConversationSerializer` of your own is
|
|
87
|
+
entitled to define it.
|
|
88
|
+
|
|
89
|
+
`class << self` and `def self.` bodies are left alone. An `object` read
|
|
90
|
+
there is a `NoMethodError` at run time — AMS's reader is an instance
|
|
91
|
+
method — so there is nothing to type and nothing the plugin should say
|
|
92
|
+
about it.
|
|
93
|
+
|
|
94
|
+
**The plugin never guesses.** Where the resource cannot be established it
|
|
95
|
+
contributes nothing and `object` keeps the answer it had, because a wrong
|
|
96
|
+
class here would turn every read on it into a false
|
|
97
|
+
`call.undefined-method` on working code.
|
|
98
|
+
|
|
99
|
+
It also does nothing at all without `rigor-activerecord`, or on a project
|
|
100
|
+
with no `db/schema.rb` / `db/structure.sql`: the model set it resolves and
|
|
101
|
+
checks against comes from that plugin, and is withheld when the schema is
|
|
102
|
+
missing.
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
plugins:
|
|
108
|
+
- gem: rigor-active-model-serializers
|
|
109
|
+
config:
|
|
110
|
+
serializer_search_paths: ["app/serializers", "app/lib"]
|
|
111
|
+
serializer_base_classes: ["ActiveModel::Serializer"]
|
|
112
|
+
model_overrides:
|
|
113
|
+
REST::InstanceSerializer: InstancePresenter
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Key | Default | Meaning |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `serializer_search_paths` | `["app/serializers", "app/lib"]` | Where to look for serializer classes. `app/lib` is in the default set because a base serializer routinely lives outside `app/serializers` — Mastodon's `ActivityPub::Serializer`, the parent of 60 of its serializers, is one. A directory you do not have costs one probe. |
|
|
119
|
+
| `serializer_base_classes` | `["ActiveModel::Serializer"]` | The roots the ancestry walk starts from. |
|
|
120
|
+
| `model_overrides` | `{}` | Serializer class name → resource class name, for a serializer the derivation cannot reach — a presenter, a decorator, or a class named for a JSON shape. Your assertion; it is not re-checked. |
|
|
121
|
+
|
|
122
|
+
## Limitations
|
|
123
|
+
|
|
124
|
+
- **SimpleForm inputs.** `SimpleForm::Inputs::Base#object` shares the name
|
|
125
|
+
and nothing else — its resource comes from the `simple_form_for` call
|
|
126
|
+
site, not from the input class.
|
|
127
|
+
- **`serializer:` / `each_serializer:` options.** They name the
|
|
128
|
+
serializer for an association, never the model for a serializer.
|
|
129
|
+
- **Model surface your schema and source do not state.** A method the
|
|
130
|
+
model gets from `method_missing`, from a gem's macro `rigor-activerecord`
|
|
131
|
+
does not recognise, or from a module included at runtime is invisible to
|
|
132
|
+
the model index, so a serializer reading one is declined.
|
|
133
|
+
`rigor-activerecord` reads `delegate`, the associations a concern
|
|
134
|
+
declares in its `included do`, the Paperclip / Active Storage attachment
|
|
135
|
+
macros and `enum` value predicates
|
|
136
|
+
([#1049](https://github.com/rigortype/rigor/issues/1049)); anything else
|
|
137
|
+
a macro defines is not folded yet.
|
|
138
|
+
- **Some ways of reading the resource.** `object[:key]`, `object.title =`,
|
|
139
|
+
`object.try(:name)`, `object.present?` and an `attribute(:x) { ... }`
|
|
140
|
+
block are not read as evidence, so a serializer using one may decline
|
|
141
|
+
even where the model is right. Always the safe direction — a decline,
|
|
142
|
+
never a wrong type.
|
|
143
|
+
- **Serializers for plain objects** — `ActiveModelSerializers::Model`
|
|
144
|
+
subclasses, Structs, presenters. Use `model_overrides` where the class
|
|
145
|
+
you want is a real constant you are happy to have checked.
|
|
146
|
+
|
|
147
|
+
## Plugin internals
|
|
148
|
+
|
|
149
|
+
The serializer discoverer / index, the ancestry closure and the contract
|
|
150
|
+
surfaces this plugin exercises are in the
|
|
151
|
+
[plugin's README](../../../plugins/rigor-active-model-serializers/README.md).
|
|
152
|
+
To write a plugin, see [`examples/`](../../../examples/README.md) and the
|
|
153
|
+
[`rigor-plugin-author`](../08-skills.md) skill.
|
|
@@ -95,6 +95,13 @@ nothing loads Solid Queue.
|
|
|
95
95
|
evidence. A schedule loaded from Ruby rather than from a file under
|
|
96
96
|
`recurring_paths` supplies nothing, by the same "read only what is
|
|
97
97
|
written" rule.
|
|
98
|
+
- **The "failed to discover jobs" warning is run-scoped.** It is
|
|
99
|
+
a fact about your configuration, not about any one source file, so
|
|
100
|
+
it is reported once per run on `.rigor.yml` rather than repeated on
|
|
101
|
+
every analysed file. It is `:warning`, so if you baselined it at its
|
|
102
|
+
old position that entry no longer matches and the row fails a
|
|
103
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
104
|
+
regenerate`.
|
|
98
105
|
|
|
99
106
|
## Plugin internals
|
|
100
107
|
|