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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
class ActiveModelSerializers < Rigor::Plugin::Base
|
|
6
|
+
# The serializer classes {SerializerDiscoverer} found under the configured search paths, keyed by
|
|
7
|
+
# the class's fully-qualified name as the source spells it (`"REST::AccountSerializer"`).
|
|
8
|
+
#
|
|
9
|
+
# Each entry carries the declared superclass (so the discoverer can close the ancestry chain) and
|
|
10
|
+
# the three name sets that let a candidate model be CHECKED rather than guessed:
|
|
11
|
+
#
|
|
12
|
+
# - `declared_names` — the names the serializer will read off the resource at render time: the
|
|
13
|
+
# symbols of `attributes` / `attribute` / `has_many` / `has_one` / `belongs_to`, minus the ones
|
|
14
|
+
# the serializer defines itself (AMS calls the serializer's own method when it has one, and only
|
|
15
|
+
# falls through to `object.<name>` when it does not).
|
|
16
|
+
# - `object_reads` — every `object.<name>` the body writes, minus the methods every Object has.
|
|
17
|
+
# - `own_method_names` — the serializer's own instance `def`s, which is both what subtracts from
|
|
18
|
+
# `declared_names` and how an explicit `def object` is detected.
|
|
19
|
+
class SerializerIndex
|
|
20
|
+
Entry = Data.define(:class_name, :superclass_name, :file_path, :declared_names, :object_reads,
|
|
21
|
+
:own_method_names) do
|
|
22
|
+
# The declarations AMS will render by calling the name on the resource — unless an ancestor of
|
|
23
|
+
# this serializer defines it, which only the engine's ancestor walk can say, so that half is
|
|
24
|
+
# left to the caller.
|
|
25
|
+
def unhandled_declarations = declared_names - own_method_names
|
|
26
|
+
|
|
27
|
+
# Reads of the resource itself. Unlike a declaration, one of these is a read of the resource
|
|
28
|
+
# whatever the serializer or its ancestors define.
|
|
29
|
+
def resource_reads = object_reads
|
|
30
|
+
|
|
31
|
+
def defines_object? = own_method_names.include?("object")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attr_reader :entries
|
|
35
|
+
|
|
36
|
+
def initialize(entries)
|
|
37
|
+
@entries = entries.freeze
|
|
38
|
+
@by_name = entries.to_h { |entry| [entry.class_name, entry] }.freeze
|
|
39
|
+
freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find(class_name) = @by_name[derooted(class_name)]
|
|
43
|
+
def known?(class_name) = @by_name.key?(derooted(class_name))
|
|
44
|
+
def empty? = @entries.empty?
|
|
45
|
+
def size = @entries.size
|
|
46
|
+
def names = @by_name.keys
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# A query may arrive rooted (`::REST::AccountSerializer`) while entries are keyed by the
|
|
51
|
+
# de-rooted spelling, the same normalisation `rigor-activerecord`'s ModelIndex settled on in #583.
|
|
52
|
+
def derooted(class_name) = class_name.to_s.delete_prefix("::")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
|
|
5
|
+
require_relative "active_model_serializers/serializer_index"
|
|
6
|
+
require_relative "active_model_serializers/serializer_discoverer"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Plugin
|
|
10
|
+
# rigor-active-model-serializers — types the implicit-self `object` reader inside a serializer as the
|
|
11
|
+
# model the serializer serializes.
|
|
12
|
+
#
|
|
13
|
+
# It emits no diagnostic. What it contributes is one return type and the gem's own framework
|
|
14
|
+
# constants, and both exist for the same measurement: the 2026-09-01 corpus opacity sweep (#534
|
|
15
|
+
# item 6) found `object` to be Mastodon's single largest unresolved implicit-self send — 751 sites in
|
|
16
|
+
# `app/serializers` alone — with no plugin owning ActiveModelSerializers at all. Every
|
|
17
|
+
# `object.account.username` chain below one of those sites was dispatching on `Dynamic[top]`.
|
|
18
|
+
#
|
|
19
|
+
# plugins:
|
|
20
|
+
# - gem: rigor-active-model-serializers
|
|
21
|
+
# config:
|
|
22
|
+
# serializer_search_paths: ["app/serializers", "app/lib"] # default; optional
|
|
23
|
+
# serializer_base_classes: ["ActiveModel::Serializer"] # default; optional
|
|
24
|
+
# model_overrides: {} # default; optional
|
|
25
|
+
#
|
|
26
|
+
# ## What `object` types to, and when it declines
|
|
27
|
+
#
|
|
28
|
+
# AMS's `object` is `ActiveModel::Serializer#object`, the resource the serializer was constructed
|
|
29
|
+
# with. Nothing in a serializer's source STATES that resource's class — AMS binds it at `new` time —
|
|
30
|
+
# so the type is only ever derived, and two independent things have to agree before it is:
|
|
31
|
+
#
|
|
32
|
+
# 1. The `<Model>Serializer` naming convention resolves to exactly one model `rigor-activerecord`
|
|
33
|
+
# discovered (`REST::AccountSerializer` → `Account`), and
|
|
34
|
+
# 2. that model ANSWERS the serializer: every name the serializer will read off its resource — the
|
|
35
|
+
# `attributes` / `has_many` / `has_one` declarations it does not define itself, plus every
|
|
36
|
+
# `object.<name>` in its body — is a column, association, enum, alias or scope of that model, or a
|
|
37
|
+
# method the project defines on it.
|
|
38
|
+
#
|
|
39
|
+
# A `model_overrides` entry short-circuits both: the project asserted the answer. Anything else
|
|
40
|
+
# DECLINES, and `object` keeps whatever the engine would have given it (`Dynamic`).
|
|
41
|
+
#
|
|
42
|
+
# ## Why the name alone is not enough
|
|
43
|
+
#
|
|
44
|
+
# The name is a guess and the model index only proves the guessed class EXISTS, never that this
|
|
45
|
+
# serializer serializes it. Mastodon has three serializers where the guess is wrong and the class is
|
|
46
|
+
# real: `REST::ConversationSerializer` serializes an `AccountConversation` (its `unread`,
|
|
47
|
+
# `participant_accounts` and `last_status` are all absent from `Conversation`), and both
|
|
48
|
+
# `REST::InstanceSerializer` and `REST::V1::InstanceSerializer` serialize an `InstancePresenter`
|
|
49
|
+
# (`object.contact`, `object.thumbnail`). A corpus diff cannot see the damage, because an Active
|
|
50
|
+
# Record model's surface is open and a wrong-but-real model absorbs every read in silence — so the
|
|
51
|
+
# check has to be a positive one, made before the answer is contributed.
|
|
52
|
+
#
|
|
53
|
+
# Requiring EVERY name to be answered, rather than most of them, is the same choice: a serializer
|
|
54
|
+
# whose resource is a decorator around the model shares most of the model's surface, and it is
|
|
55
|
+
# exactly the one or two extra names that say so.
|
|
56
|
+
#
|
|
57
|
+
# ## Scope
|
|
58
|
+
#
|
|
59
|
+
# - **SimpleForm inputs are NOT handled.** The sweep's 875-site `object` count mixes AMS serializers
|
|
60
|
+
# with `SimpleForm::Inputs::Base#object`, a different gem with a different resource story
|
|
61
|
+
# (`object` there is the form's record, named by the `simple_form_for` call site, not by the input
|
|
62
|
+
# class). That belongs in a `rigor-simple-form` plugin and is deliberately left out.
|
|
63
|
+
# - **`serializer:` / `each_serializer:` options are not read.** `has_many :emojis, serializer:
|
|
64
|
+
# REST::CustomEmojiSerializer` states which serializer renders an association, never which model a
|
|
65
|
+
# serializer serializes, so it cannot ground `object`.
|
|
66
|
+
# - **A serializer with no declarations and no `object` reads gets no answer.** There is nothing to
|
|
67
|
+
# check the name against, and the name alone is what this refuses to trust.
|
|
68
|
+
class ActiveModelSerializers < Rigor::Plugin::Base
|
|
69
|
+
manifest(
|
|
70
|
+
id: "active-model-serializers",
|
|
71
|
+
target_gems: ["active_model_serializers"],
|
|
72
|
+
version: "0.1.0",
|
|
73
|
+
description: "Types the implicit-self `object` reader inside ActiveModel::Serializer subclasses " \
|
|
74
|
+
"as the serializer's model, and declares the gem's framework constants.",
|
|
75
|
+
config_schema: {
|
|
76
|
+
# `app/lib` is in the default set because a project base serializer routinely lives outside
|
|
77
|
+
# `app/serializers` — Mastodon's `ActivityPub::Serializer`, the parent of 60 of its 153
|
|
78
|
+
# serializers, is at `app/lib/activitypub/serializer.rb`. Without it the ancestry closure stops
|
|
79
|
+
# at the base class and two fifths of the project's serializers are simply not serializers as
|
|
80
|
+
# far as this plugin is concerned. A directory the project does not have costs one probe.
|
|
81
|
+
"serializer_search_paths" => { kind: :array, default: ["app/serializers", "app/lib"] },
|
|
82
|
+
"serializer_base_classes" => { kind: :array, default: ["ActiveModel::Serializer"] },
|
|
83
|
+
# Serializer class name => model class name, for the resources the derivation cannot reach: a
|
|
84
|
+
# serializer whose resource is a presenter or a decorator, or one named for a JSON shape rather
|
|
85
|
+
# than a model. An override is the project's own assertion and is NOT re-checked — a project
|
|
86
|
+
# that names a class Rigor has no RBS for gets a lenient nominal.
|
|
87
|
+
"model_overrides" => { kind: :hash, default: {} }
|
|
88
|
+
},
|
|
89
|
+
# Optional on purpose: without `rigor-activerecord` there is no model set to check a name
|
|
90
|
+
# against, and every non-overridden serializer declines. That is the designed degradation — the
|
|
91
|
+
# plugin contributes less, never something wrong.
|
|
92
|
+
consumes: [{ plugin_id: "activerecord", name: :model_index, optional: true }],
|
|
93
|
+
# ADR-25 (#534 item 7, same admission rule as `rigor-activerecord`'s `sig/active_record/
|
|
94
|
+
# framework.rbs`) — the gem's own constants, declared so they resolve and asserting nothing else.
|
|
95
|
+
signature_paths: ["sig"],
|
|
96
|
+
# ADR-26 — every class and module the bundled signature names is open, so that declaring it buys
|
|
97
|
+
# constant resolution and asserts nothing about a member. `ActiveModel::Serializer` is the row
|
|
98
|
+
# that matters most (a project's serializers inherit from it, and its whole instance surface —
|
|
99
|
+
# `object`, `scope`, `read_attribute_for_serialization`, the `_attributes` class state — is
|
|
100
|
+
# unenumerated), and `ActiveModelSerializers` / `::Adapter` are the ones the canonical
|
|
101
|
+
# initializer dispatches on: `ActiveModelSerializers.config.adapter = :json_api` and
|
|
102
|
+
# `ActiveModelSerializers::Adapter.register(...)` are what an AMS `config/initializers` file says.
|
|
103
|
+
open_receivers: [
|
|
104
|
+
"ActiveModel::Serializer",
|
|
105
|
+
"ActiveModel::Serializer::CollectionSerializer",
|
|
106
|
+
"ActiveModelSerializers",
|
|
107
|
+
"ActiveModelSerializers::Adapter",
|
|
108
|
+
"ActiveModelSerializers::Model",
|
|
109
|
+
"ActiveModelSerializers::SerializableResource"
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
producer :serializer_index, watch: -> { [[@serializer_search_paths, "**/*.rb"]] } do |_params|
|
|
114
|
+
SerializerDiscoverer.new(
|
|
115
|
+
io_boundary: io_boundary,
|
|
116
|
+
search_paths: @serializer_search_paths,
|
|
117
|
+
base_classes: @serializer_base_classes
|
|
118
|
+
).discover
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def init(_services)
|
|
122
|
+
@serializer_search_paths = Array(config.fetch("serializer_search_paths")).map(&:to_s)
|
|
123
|
+
@serializer_base_classes = Array(config.fetch("serializer_base_classes")).map(&:to_s)
|
|
124
|
+
@model_overrides = config.fetch("model_overrides").to_h { |k, v| [k.to_s.delete_prefix("::"), v.to_s] }
|
|
125
|
+
@derivations = {}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The `methods:` gate keeps this off every dispatch whose name is not `object`; the receiver and
|
|
129
|
+
# argument checks keep it off `foo.object` and `object(x)`, neither of which is the AMS reader.
|
|
130
|
+
dynamic_return methods: [:object] do |call_node, scope|
|
|
131
|
+
next nil unless call_node.is_a?(Prism::CallNode)
|
|
132
|
+
next nil unless call_node.receiver.nil?
|
|
133
|
+
next nil unless call_node.arguments.nil?
|
|
134
|
+
|
|
135
|
+
entry = serializer_entry(scope)
|
|
136
|
+
next nil if entry.nil?
|
|
137
|
+
# An explicit `def object`, here or on an ancestor, is the definition that runs. Answering over
|
|
138
|
+
# it would replace a type the engine derived from real source with a guess, and silence whatever
|
|
139
|
+
# that source proves.
|
|
140
|
+
next nil if entry.defines_object? || project_defines_object?(entry.class_name, scope)
|
|
141
|
+
|
|
142
|
+
model_name = model_class_name_for(entry, scope)
|
|
143
|
+
next nil if model_name.nil?
|
|
144
|
+
|
|
145
|
+
Rigor::Type::Combinator.nominal_of(model_name)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
# The discovered serializer whose body `self` is in, or nil. Membership of the index is the only
|
|
151
|
+
# gate: a `*Serializer` name is not evidence of anything, since `object` is an ordinary method name
|
|
152
|
+
# that a `Json::ConversationSerializer` or an `Oj::AccountSerializer` may define for itself.
|
|
153
|
+
def serializer_entry(scope)
|
|
154
|
+
self_type = scope&.self_type
|
|
155
|
+
return nil unless self_type.respond_to?(:class_name)
|
|
156
|
+
|
|
157
|
+
name = self_type.class_name
|
|
158
|
+
return nil if name.nil? || name.empty?
|
|
159
|
+
|
|
160
|
+
producer_value(:serializer_index)&.find(name)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def project_defines_object?(serializer_name, scope)
|
|
164
|
+
return false unless scope.respond_to?(:user_def_through_ancestors)
|
|
165
|
+
|
|
166
|
+
!scope.user_def_through_ancestors(serializer_name, :object).first.nil?
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# The model a serializer serializes, or nil where nothing corroborates it. Memoised per serializer
|
|
170
|
+
# class INCLUDING the nil answer: the ancestor walks behind `project_defines?` are run once per
|
|
171
|
+
# name per serializer, and `object` is read hundreds of times across a real `app/serializers`.
|
|
172
|
+
def model_class_name_for(entry, scope)
|
|
173
|
+
return @derivations[entry.class_name] if @derivations.key?(entry.class_name)
|
|
174
|
+
|
|
175
|
+
@derivations[entry.class_name] = derive_model_class_name(entry, scope)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def derive_model_class_name(entry, scope)
|
|
179
|
+
override = @model_overrides[entry.class_name]
|
|
180
|
+
return override unless override.nil? || override.empty?
|
|
181
|
+
|
|
182
|
+
# The published `:model_index` fact is a Hash keyed by the model's DE-ROOTED constant path
|
|
183
|
+
# (#583). `rigor-activerecord` withholds it entirely in reduced mode (no `db/schema.rb` /
|
|
184
|
+
# `db/structure.sql`), so a schema-less project declines here rather than typing `object` off a
|
|
185
|
+
# name alone.
|
|
186
|
+
index = read_fact(plugin_id: "activerecord", name: :model_index)
|
|
187
|
+
return nil if index.nil? || index.empty?
|
|
188
|
+
|
|
189
|
+
resolved = convention_candidates(entry.class_name).select { |candidate| index.key?(candidate) }
|
|
190
|
+
# Two readings that both name a real model (`Admin::AccountSerializer` where `Admin::Account` and
|
|
191
|
+
# `Account` both exist) is an ambiguity, not a first-hit: nothing here ranks one above the other.
|
|
192
|
+
return nil unless resolved.size == 1
|
|
193
|
+
|
|
194
|
+
model = resolved.first
|
|
195
|
+
answers?(entry, index.fetch(model), model, scope) ? model : nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Whether the model ANSWERS the serializer — see the class comment for why this is required and why
|
|
199
|
+
# it is required of every name rather than most of them. A serializer that declares nothing and
|
|
200
|
+
# reads nothing states nothing to check, and is declined for that reason rather than admitted for
|
|
201
|
+
# it.
|
|
202
|
+
def answers?(entry, row, model_name, scope)
|
|
203
|
+
declarations = entry.unhandled_declarations
|
|
204
|
+
reads = entry.resource_reads
|
|
205
|
+
return false if declarations.empty? && reads.empty?
|
|
206
|
+
|
|
207
|
+
members = model_members(row)
|
|
208
|
+
# A declaration the serializer itself renders is not a read of the resource at all. The entry
|
|
209
|
+
# subtracts the serializer's OWN methods; a base serializer's `def formatted` is only visible to
|
|
210
|
+
# the engine's ancestor walk, and a declaration it handles says nothing about the model.
|
|
211
|
+
declarations.all? do |name|
|
|
212
|
+
project_defines?(entry.class_name, name, scope) ||
|
|
213
|
+
members.include?(name) || project_defines?(model_name, name, scope)
|
|
214
|
+
end && reads.all? { |name| members.include?(name) || project_defines?(model_name, name, scope) }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Every name the model answers that its `:model_index` row states. The `?` forms are Active
|
|
218
|
+
# Record's own per-column predicates, which a serializer reads as readily as the column.
|
|
219
|
+
#
|
|
220
|
+
# `macro_methods` (#1049) is the row's name-only set: `delegate`'s methods, the Paperclip / Active
|
|
221
|
+
# Storage attachment readers, and an `enum`'s per-value predicates, from the model body and from every
|
|
222
|
+
# concern the model includes. It is what lets `REST::AccountSerializer`'s `followers_count` / `user` /
|
|
223
|
+
# `avatar` reads resolve; an older producer that does not publish the key reads as the empty set here,
|
|
224
|
+
# which declines exactly as before rather than erroring.
|
|
225
|
+
def model_members(row)
|
|
226
|
+
columns = Array(row[:columns]).map(&:to_s)
|
|
227
|
+
associations = Array(row[:associations]).map { |a| a[:name].to_s }
|
|
228
|
+
enums = row[:enums].is_a?(Hash) ? row[:enums].keys.map(&:to_s) : []
|
|
229
|
+
aliases = row[:aliases].is_a?(Hash) ? row[:aliases].keys.map(&:to_s) : []
|
|
230
|
+
scopes = Array(row[:scopes]).map(&:to_s)
|
|
231
|
+
macros = Array(row[:macro_methods]).map(&:to_s)
|
|
232
|
+
(columns + columns.map { |c| "#{c}?" } + associations + enums + aliases + scopes + macros).to_set
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# A method the project writes in Ruby on `class_name` or an ancestor of it. On the model side that
|
|
236
|
+
# is `Account#local?` or a concern's reader — the model index cannot see these, and without them
|
|
237
|
+
# the check would decline nearly every real serializer. On the serializer side it is a base
|
|
238
|
+
# serializer's own rendering method, which is not a read of the resource at all.
|
|
239
|
+
def project_defines?(class_name, method_name, scope)
|
|
240
|
+
return false unless scope.respond_to?(:user_def_through_ancestors)
|
|
241
|
+
|
|
242
|
+
!scope.user_def_through_ancestors(class_name, method_name.to_sym).first.nil?
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# `REST::AccountSerializer` offers two readings — the namespaced `REST::Account` and the
|
|
246
|
+
# demodulized `Account` — and Rails apps use both (`Admin::AccountSerializer` for `Admin::Account`
|
|
247
|
+
# is as real as Mastodon's `REST::AccountSerializer` for `Account`). Both are offered to the model
|
|
248
|
+
# index, and exactly one of them has to land.
|
|
249
|
+
def convention_candidates(serializer_name)
|
|
250
|
+
return [] unless serializer_name.end_with?("Serializer")
|
|
251
|
+
# A class named exactly `Serializer` is a namespace's base serializer, never a model's.
|
|
252
|
+
return [] if serializer_name.split("::").last == "Serializer"
|
|
253
|
+
|
|
254
|
+
stripped = serializer_name.delete_suffix("Serializer").delete_suffix("::")
|
|
255
|
+
return [] if stripped.empty?
|
|
256
|
+
|
|
257
|
+
[stripped, stripped.split("::").last].uniq.reject(&:empty?)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
Rigor::Plugin.register(ActiveModelSerializers)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists
|
|
4
|
+
# `rigor-active-model-serializers` under `plugins:`. The loader expects this `require` to side-effect a
|
|
5
|
+
# call to `Rigor::Plugin.register`, which the body of
|
|
6
|
+
# `lib/rigor/plugin/active_model_serializers.rb` performs at load time.
|
|
7
|
+
require_relative "rigor/plugin/active_model_serializers"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# The ActiveModelSerializers gem's own constants — issue #534 item 6.
|
|
2
|
+
#
|
|
3
|
+
# The 2026-09-01 corpus opacity sweep counted `ActiveModel::Serializer` 101 times on Mastodon,
|
|
4
|
+
# `ActiveModelSerializers::Model` 29 and `ActiveModelSerializers::SerializableResource` 25, every
|
|
5
|
+
# one of them resolving to `untyped` because nothing in the bundle named the constant. This file
|
|
6
|
+
# names them, and nothing more.
|
|
7
|
+
#
|
|
8
|
+
# ## The admission rule
|
|
9
|
+
#
|
|
10
|
+
# The same rule `plugins/rigor-activerecord/sig/active_record/framework.rbs` states: declaring a
|
|
11
|
+
# constant makes it RBS-KNOWN, and a known class with an incomplete signature turns every member the
|
|
12
|
+
# signature omits into a false `call.undefined-method`. Every class here is therefore ALSO listed in
|
|
13
|
+
# the manifest's `open_receivers:` (ADR-26), which is what keeps the members this file does not
|
|
14
|
+
# enumerate lenient.
|
|
15
|
+
#
|
|
16
|
+
# That pairing is load-bearing rather than defensive here, because — unlike the Active Record
|
|
17
|
+
# exception hierarchy, which is rescued and not dispatched on — `ActiveModel::Serializer` and
|
|
18
|
+
# `ActiveModelSerializers::Model` are exactly the "class a project INHERITS from" shape the Active
|
|
19
|
+
# Record file declines to declare. `class REST::AccountSerializer < ActiveModel::Serializer` is what
|
|
20
|
+
# an AMS application looks like, and `object`, `scope`, `read_attribute_for_serialization`,
|
|
21
|
+
# `attributes`, `has_many` and the rest of the AMS surface are not enumerated below. The Mastodon
|
|
22
|
+
# before/after diff recorded in `docs/notes/20260917-ams-object-recognizer.md` is the evidence that
|
|
23
|
+
# the pairing holds on a real application.
|
|
24
|
+
#
|
|
25
|
+
# `ActiveModel` is declared as a module by `rigor-activerecord` as well; RBS reopens a module across
|
|
26
|
+
# files, so the two declarations compose rather than collide, and neither plugin depends on the
|
|
27
|
+
# other being enabled.
|
|
28
|
+
|
|
29
|
+
module ActiveModel
|
|
30
|
+
class Serializer
|
|
31
|
+
class CollectionSerializer
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module ActiveModelSerializers
|
|
37
|
+
class Model
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class SerializableResource
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module Adapter
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -25,10 +25,6 @@ module Rigor
|
|
|
25
25
|
|
|
26
26
|
# The job-call violations for a single call node (0..2), or `[]` when the node is not a
|
|
27
27
|
# `<Job>.perform_*` entry call on a known job. ADR-37: the engine owns the walk.
|
|
28
|
-
#
|
|
29
|
-
# @param call_node [Prism::Node]
|
|
30
|
-
# @param job_index [JobIndex]
|
|
31
|
-
# @return [Array<Violation>]
|
|
32
28
|
def violations_for(call_node:, job_index:)
|
|
33
29
|
return [] unless call_node.is_a?(Prism::CallNode) && entry_call?(call_node)
|
|
34
30
|
|
|
@@ -49,6 +49,7 @@ module Rigor
|
|
|
49
49
|
"shoryuken" => ["io.net"], "backburner" => ["io.net"], "sucker_punch" => [],
|
|
50
50
|
"async" => [], "inline" => [], "test" => []
|
|
51
51
|
}.freeze
|
|
52
|
+
Ractor.make_shareable(TRANSPORTS)
|
|
52
53
|
|
|
53
54
|
# The meaning half, which is adapter-independent and is what a policy actually names.
|
|
54
55
|
MEANING = ["rails.activejob.enqueue", "job.enqueue"].freeze
|
|
@@ -61,7 +62,7 @@ module Rigor
|
|
|
61
62
|
|
|
62
63
|
module_function
|
|
63
64
|
|
|
64
|
-
# @param adapter
|
|
65
|
+
# @param adapter — the adapter the project declares, or nil when it declares none
|
|
65
66
|
# (or more than one, across environments)
|
|
66
67
|
def attributions(adapter)
|
|
67
68
|
labels = (TRANSPORTS[adapter] || ["io"]) + MEANING
|
|
@@ -42,8 +42,6 @@ module Rigor
|
|
|
42
42
|
# `"::WelcomeJob"`; see {JobDiscoverer}), while a QUERY may legitimately arrive rooted:
|
|
43
43
|
# `::WelcomeJob.perform_later(1)` renders its receiver as `"::WelcomeJob"`. The root marker is
|
|
44
44
|
# dropped here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
|
|
45
|
-
#
|
|
46
|
-
# @return [Entry, nil]
|
|
47
45
|
def find(class_name)
|
|
48
46
|
@by_name[strip_leading_namespace(class_name.to_s)]
|
|
49
47
|
end
|
|
@@ -51,7 +51,7 @@ module Rigor
|
|
|
51
51
|
@recurring_paths = recurring_paths
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# @return
|
|
54
|
+
# @return the class names named by a `class:` key in a recurring-task entry, sorted
|
|
55
55
|
# and unique. NOT yet intersected with the discovered jobs — the caller does that.
|
|
56
56
|
def job_names
|
|
57
57
|
names = Set.new
|
|
@@ -37,6 +37,7 @@ module Rigor
|
|
|
37
37
|
class Activejob < Rigor::Plugin::Base
|
|
38
38
|
manifest(
|
|
39
39
|
id: "activejob",
|
|
40
|
+
target_gems: ["activejob"],
|
|
40
41
|
# Bumped — publishes `:reachability_roots` for `rigor unused` (ADR-102 WD3): the jobs Solid Queue's
|
|
41
42
|
# recurring schedule runs by name, which no `perform_later` call site writes down.
|
|
42
43
|
#
|
|
@@ -128,12 +129,12 @@ module Rigor
|
|
|
128
129
|
services.fact_store.publish(plugin_id: manifest.id, name: :reachability_roots, value: roots)
|
|
129
130
|
end
|
|
130
131
|
|
|
131
|
-
# File-level only: the load-error
|
|
132
|
+
# File-level only: the load-error disclosure. Per-call arity validation runs over the engine-owned
|
|
132
133
|
# walk via the node_rule below (ADR-37). The job index is lazily loaded + memoised by
|
|
133
134
|
# `producer_value`, shared by both surfaces.
|
|
134
135
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
135
136
|
index = producer_value(:job_index)
|
|
136
|
-
|
|
137
|
+
disclose_load_error if index.nil? && producer_error(:job_index)
|
|
137
138
|
|
|
138
139
|
[]
|
|
139
140
|
end
|
|
@@ -147,10 +148,14 @@ module Rigor
|
|
|
147
148
|
|
|
148
149
|
private
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
# Issue #1056 — "the job index did not load" is a fact about the run's INPUTS, not about the file being
|
|
152
|
+
# analysed, so it is registered rather than returned: see {Plugin::Base#disclose_once} for the
|
|
153
|
+
# channel, the `(plugin id, key)` de-duplication and why the engine positions it at `.rigor.yml:1:1`.
|
|
154
|
+
# Returned from the per-file hook it carried no once-guard at all and repeated on every file.
|
|
155
|
+
def disclose_load_error
|
|
151
156
|
error = producer_error(:job_index)
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
disclose_once(
|
|
158
|
+
:job_index_load_failed,
|
|
154
159
|
message: "rigor-activejob: failed to discover jobs: #{error.class}: #{error.message}",
|
|
155
160
|
severity: :warning,
|
|
156
161
|
rule: "load-error"
|
|
@@ -13,13 +13,15 @@ module Rigor
|
|
|
13
13
|
# | Method | Recognised arg shape | Validation |
|
|
14
14
|
# | --- | --- | --- |
|
|
15
15
|
# | `Model.find(id)` | any positional | arity check (1+ args) |
|
|
16
|
+
# | `Model.find { … }` | a block, no args | none (`Enumerable#find`) |
|
|
16
17
|
# | `Model.find_by(col: v, ...)` | keyword args | each key must be a column |
|
|
17
18
|
# | `Model.where(col: v, ...)` | keyword args | each key must be a column |
|
|
18
19
|
# | `Model.where(string)` | String literal | parser-side; not validated |
|
|
19
20
|
#
|
|
20
21
|
# Successful matches surface as `:info` diagnostics naming the resolved table; unknown columns
|
|
21
22
|
# surface as `:error`. Calls whose receiver is not a model in the index, and calls with
|
|
22
|
-
# non-keyword arguments to `where` / `find_by`, stay silent.
|
|
23
|
+
# non-keyword arguments to `where` / `find_by`, stay silent. A model's own `self.find` owns its arity,
|
|
24
|
+
# so the arity check and the notes for the forms the return-type tier leaves to it stay silent too.
|
|
23
25
|
class Analyzer
|
|
24
26
|
# Methods that take a column → value Hash and need each key validated against the receiver's
|
|
25
27
|
# column set. The bang variants (`find_by!` raises instead of returning nil; `find_or_create_by!`
|
|
@@ -35,9 +37,13 @@ module Rigor
|
|
|
35
37
|
|
|
36
38
|
attr_reader :diagnostics
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
# `scope` is the file's entry scope, which carries the project's discovered methods; it answers
|
|
41
|
+
# whether a model defines its own `self.find`. An editor run seeds none, so there the check misses
|
|
42
|
+
# even a same-file `self.find` the typer sees (#1329).
|
|
43
|
+
def initialize(path:, model_index:, scope:)
|
|
39
44
|
@path = path
|
|
40
45
|
@model_index = model_index
|
|
46
|
+
@scope = scope
|
|
41
47
|
@diagnostics = []
|
|
42
48
|
end
|
|
43
49
|
|
|
@@ -69,16 +75,42 @@ module Rigor
|
|
|
69
75
|
end
|
|
70
76
|
end
|
|
71
77
|
|
|
78
|
+
# A block turns `find` into `Enumerable#find` over `all` (see `Activerecord#block_find_return_type`),
|
|
79
|
+
# which takes no id, so the zero-argument check applies only to the block-less id lookup.
|
|
80
|
+
#
|
|
81
|
+
# The arity check and the notes describe Rails' `find`. A model's own `self.find` has its own arity,
|
|
82
|
+
# and the return-type tier (`Activerecord#finder_return_type`) leaves the multi-id and block forms to
|
|
83
|
+
# it, so a note there would claim a type the call does not get. One id still types as the model.
|
|
72
84
|
def validate_find(node, entry)
|
|
85
|
+
return validate_block_find(node, entry) if node.block
|
|
86
|
+
|
|
73
87
|
arity = call_argument_count(node)
|
|
74
88
|
if arity.zero?
|
|
89
|
+
return if own_find?(entry)
|
|
90
|
+
|
|
75
91
|
push_error(node, "wrong-arity",
|
|
76
92
|
"`#{entry.class_name}.find` expects at least 1 argument, got 0")
|
|
77
93
|
return
|
|
78
94
|
end
|
|
95
|
+
return if arity >= 2 && own_find?(entry)
|
|
79
96
|
|
|
97
|
+
returned = arity >= 2 ? "Array[#{entry.class_name}]" : entry.class_name
|
|
80
98
|
push_info(node, "model-call",
|
|
81
|
-
"`#{entry.class_name}.find` returns #{
|
|
99
|
+
"`#{entry.class_name}.find` returns #{returned} (table: `#{entry.table_name}`)")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def validate_block_find(node, entry)
|
|
103
|
+
return unless call_argument_count(node).zero?
|
|
104
|
+
return if own_find?(entry)
|
|
105
|
+
|
|
106
|
+
push_info(node, "model-call",
|
|
107
|
+
"`#{entry.class_name}.find` returns #{entry.class_name} | nil (table: `#{entry.table_name}`)")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The predicate `Activerecord#finder_return_type` declines on, so the note and the type agree in a
|
|
111
|
+
# CLI run. In an editor run this scope lacks the file's own methods (#1329).
|
|
112
|
+
def own_find?(entry)
|
|
113
|
+
@scope.discovered_method_through_ancestors?(entry.class_name, :find, :singleton)
|
|
82
114
|
end
|
|
83
115
|
|
|
84
116
|
def validate_column_hash_call(node, entry)
|