rigortype 0.3.9 → 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/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 +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- 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 +243 -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 +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- 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 +212 -25
- 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 +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -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 +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- 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 +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- 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.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- 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 +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- 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 +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- 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 +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- 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/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- 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/index_write_widening.rb +16 -3
- 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 +167 -17
- 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 +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- 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/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- 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 +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- 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 +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- 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 +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- 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 +239 -4
- 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/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -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 +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- 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.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- 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 +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -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/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 +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- 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 +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -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 +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- 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 +12 -25
- 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 +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- 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-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
|
@@ -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
|
|
|
@@ -29,13 +29,21 @@ errors_demo.rb:24:1: error: `User.find` expects at least 1 argument, got 0 [plug
|
|
|
29
29
|
| --- | --- | --- |
|
|
30
30
|
| Recognised `Model.find` / `Model.find_by` / `Model.where` call | `:info` | `plugin.activerecord.model-call` |
|
|
31
31
|
| `Model.find_by(unknown: ...)` / `Model.where(unknown: ...)` | `:error` | `plugin.activerecord.unknown-column` |
|
|
32
|
-
| `Model.find` with 0 args | `:error` | `plugin.activerecord.wrong-arity` |
|
|
32
|
+
| `Model.find` with 0 args and no block (unless the model defines `self.find`) | `:error` | `plugin.activerecord.wrong-arity` |
|
|
33
33
|
| No schema source (`db/schema.rb` or `db/structure.sql`) present — reduced mode | `:info` | `plugin.activerecord.load-error` |
|
|
34
34
|
| A schema source that exists but cannot be read or parsed | `:warning` | `plugin.activerecord.load-error` |
|
|
35
35
|
|
|
36
36
|
Did-you-mean suggestions use `DidYouMean` fuzzy matching against
|
|
37
37
|
the resolved table's column names.
|
|
38
38
|
|
|
39
|
+
A model that defines its own `self.find` owns that method's arity.
|
|
40
|
+
The plugin reports no `wrong-arity` for it, and no note for the
|
|
41
|
+
several-id or block form, where the call types as the model's
|
|
42
|
+
method rather than Rails' `find`. A single id is still noted as the
|
|
43
|
+
model, which is how it types. In an editor, a `self.find` in the
|
|
44
|
+
same file is not yet seen here, so the note and the error still
|
|
45
|
+
appear there ([#1329](https://github.com/rigortype/rigor/issues/1329)).
|
|
46
|
+
|
|
39
47
|
## Configuration
|
|
40
48
|
|
|
41
49
|
```yaml
|
|
@@ -61,8 +69,17 @@ All keys are optional. Tweak them when:
|
|
|
61
69
|
## What it infers
|
|
62
70
|
|
|
63
71
|
The plugin contributes call-site types as well as diagnostics.
|
|
64
|
-
Class-side: `User.find(1)` → `User`, `User.
|
|
65
|
-
`User
|
|
72
|
+
Class-side: `User.find(1)` → `User`, `User.find(1, 2)` →
|
|
73
|
+
`Array[User]`, `User.find_by(...)` → `User | nil`,
|
|
74
|
+
`User.find_by!(...)` → non-nullable `User`. A single argument
|
|
75
|
+
stays `User` even when it is an Array (`User.find([1, 2])`), so
|
|
76
|
+
that an untyped one, such as `params[:id]`, keeps the model type.
|
|
77
|
+
A relation or association (`user.posts.find(1, 2)`) answers the
|
|
78
|
+
same way. With a block, `find` is `Enumerable#find` over the
|
|
79
|
+
records, on the class and on a relation alike:
|
|
80
|
+
`User.find { |u| u.admin? }` → `User | nil`, and it takes no id.
|
|
81
|
+
The block's parameter is the model on a relation; on the class
|
|
82
|
+
side it stays untyped.
|
|
66
83
|
Instance-side: a column read (`user.name`) narrows to the
|
|
67
84
|
column's value type, `user.admin?` to `bool`, and a singular
|
|
68
85
|
association (`post.user`) to the target model.
|
|
@@ -76,6 +93,71 @@ Chained query methods keep the element type, and iteration
|
|
|
76
93
|
scope invoked on a typed relation (`User.where(...).published`)
|
|
77
94
|
never surfaces a false `call.undefined-method`.
|
|
78
95
|
|
|
96
|
+
A `scope` declared inside a concern's `included do ... end` block
|
|
97
|
+
counts as the including model's own. `Account.without_suspended`
|
|
98
|
+
types as `ActiveRecord::Relation[Account]` when `Account` includes
|
|
99
|
+
the concern that declares the scope — directly, or through another
|
|
100
|
+
concern that concern includes. A concern included once in your
|
|
101
|
+
base class (`ApplicationRecord`) reaches every model under it.
|
|
102
|
+
Attribution follows the `include` you wrote, not the name: a
|
|
103
|
+
model that includes nothing gets nothing, whatever some other
|
|
104
|
+
concern in the project declares.
|
|
105
|
+
|
|
106
|
+
The gate is the `included do ... end` block itself. A scope
|
|
107
|
+
declared some other way — in a `class_methods do` block, a
|
|
108
|
+
hand-written `def self.included(base)` with `base.class_eval`, or
|
|
109
|
+
directly in the body of your base class rather than in a concern —
|
|
110
|
+
is not folded, and the call stays as untyped as it was.
|
|
111
|
+
|
|
112
|
+
`belongs_to` / `has_one` / `has_many` /
|
|
113
|
+
`has_and_belongs_to_many` declared in that same `included do ...
|
|
114
|
+
end` block count as the including model's own too, with the same
|
|
115
|
+
`include`-edge attribution: `account.user` narrows to
|
|
116
|
+
`User | nil` when `Account` includes the concern that declares
|
|
117
|
+
`has_one :user`, and `where(user: ...)` stops reporting a false
|
|
118
|
+
`unknown-column`. A model that declares the association itself
|
|
119
|
+
keeps its own version.
|
|
120
|
+
|
|
121
|
+
### The names a macro defines
|
|
122
|
+
|
|
123
|
+
Three macro families define ordinary instance methods that are
|
|
124
|
+
neither columns nor associations, and the model's recorded
|
|
125
|
+
surface now carries their names:
|
|
126
|
+
|
|
127
|
+
- `delegate :followers_count, to: :account_stat` — every
|
|
128
|
+
delegated name, including the `prefix:` spellings
|
|
129
|
+
(`delegate :can?, to: :user, prefix: true` defines
|
|
130
|
+
`user_can?`). `delegate` is read from the model body, from a
|
|
131
|
+
concern's `included do ... end`, and from a concern's own
|
|
132
|
+
top level, where it defines an instance method the including
|
|
133
|
+
model inherits.
|
|
134
|
+
- Attachment macros — Paperclip's `has_attached_file :avatar`
|
|
135
|
+
(`avatar`, `avatar=`, `avatar?`) and Active Storage's
|
|
136
|
+
`has_one_attached` / `has_many_attached` (`banner`,
|
|
137
|
+
`banner_attachment`, `banner_blob`; `docs`,
|
|
138
|
+
`docs_attachments`, `docs_blobs`). Paperclip's
|
|
139
|
+
`avatar_file_name` / `avatar_content_type` / `avatar_file_size`
|
|
140
|
+
/ `avatar_updated_at` are real columns, so they come from your
|
|
141
|
+
schema rather than from the macro.
|
|
142
|
+
- `enum` value predicates — `enum :visibility, { limited: 4 },
|
|
143
|
+
suffix: :visibility` defines `limited_visibility?`, and
|
|
144
|
+
`prefix:` / `_prefix:` / `_suffix:` are read the same way. An
|
|
145
|
+
`enum` declared `instance_methods: false` defines none of
|
|
146
|
+
them, and none are recorded.
|
|
147
|
+
|
|
148
|
+
These are recorded as NAMES. Nothing here says what a delegated
|
|
149
|
+
method returns, and the plugin contributes no type for one — the
|
|
150
|
+
value is that a consumer asking "does this model answer
|
|
151
|
+
`followers_count`?" gets the right answer. The consumer this was
|
|
152
|
+
built for is
|
|
153
|
+
[`rigor-active-model-serializers`](rigor-active-model-serializers.md),
|
|
154
|
+
which types a serializer's `object` only when the candidate model
|
|
155
|
+
answers every name the serializer reads.
|
|
156
|
+
|
|
157
|
+
A declaration this plugin cannot read off the source contributes
|
|
158
|
+
nothing rather than a guess: a `prefix: true` whose `to:` is a
|
|
159
|
+
method call, a non-literal `prefix:`, a computed enum value list.
|
|
160
|
+
|
|
79
161
|
If the project also installs `activerecord` through
|
|
80
162
|
`rbs collection install`, the collection declares
|
|
81
163
|
`ActiveRecord::Relation` without a type parameter while the
|
|
@@ -182,12 +264,25 @@ close every model in the project.
|
|
|
182
264
|
the column-dependent half stands down — column readers stay
|
|
183
265
|
untyped and `where(col:)` keys are not validated, exactly as they
|
|
184
266
|
are for a table the schema does not describe. The plugin says so
|
|
185
|
-
once per run at `:info
|
|
267
|
+
once per run at `:info`, positioned on `.rigor.yml` — it is a
|
|
268
|
+
fact about your configuration, not about any one source file,
|
|
269
|
+
and you get the same single row with `--workers` as without. If
|
|
270
|
+
you baselined this row at its old position (a controller or a
|
|
271
|
+
model), that baseline entry no longer matches it — run `rigor
|
|
272
|
+
baseline regenerate`. Committing a schema dump (or pointing
|
|
186
273
|
`schema_file` / `structure_sql_file` at one) turns the column half
|
|
187
274
|
back on from the next cold run — a warm cache keeps serving the
|
|
188
275
|
reduced index until it is invalidated, so use `rigor check
|
|
189
276
|
--no-cache` (or `make cache-clean`) if you want to see the change
|
|
190
277
|
immediately.
|
|
278
|
+
- **Every kind of relation shares one signature.**
|
|
279
|
+
`user.posts`, `user.posts.where(...)` and `Post.where(...)` all
|
|
280
|
+
type as `ActiveRecord::Relation[Post]`, although only the first
|
|
281
|
+
is an association's `CollectionProxy`, so the signature they share
|
|
282
|
+
accepts the widest argument list any of them takes.
|
|
283
|
+
`user.posts.delete_all(:nullify)` is valid and not reported. The
|
|
284
|
+
same call on the other two raises `ArgumentError` at run time, and
|
|
285
|
+
is not reported either.
|
|
191
286
|
- **Column reads, not setters.** The plugin types instance-side
|
|
192
287
|
column *reads* (`user.name`, `user.admin?`) and singular
|
|
193
288
|
associations, but not the `name=` setter or the dirty-tracking
|
|
@@ -201,6 +296,17 @@ close every model in the project.
|
|
|
201
296
|
|
|
202
297
|
## Plugin internals
|
|
203
298
|
|
|
299
|
+
The plugin also answers `Plugin::Base#declared_members` (ADR-113
|
|
300
|
+
WD4): it enumerates each model's synthesized members — column
|
|
301
|
+
readers and `column?` predicates, association accessors, declared
|
|
302
|
+
scopes, enum attributes, and macro-defined names — off the prepared
|
|
303
|
+
model index, so `rigor lens` can list `User`'s members without a
|
|
304
|
+
grep-able declaration. A row carries the member-level type the
|
|
305
|
+
plugin commits to: `Dynamic[top]` for column readers on purpose
|
|
306
|
+
(the bare, receiver-less read's answer — a written `user.name`
|
|
307
|
+
still narrows to the column's type), and a type only where the
|
|
308
|
+
plugin already answers one at `check`.
|
|
309
|
+
|
|
204
310
|
Architecture (the cached schema-parser → model-index → analyzer
|
|
205
311
|
chain), the source layout, how to run the demo, and the plugin
|
|
206
312
|
contract surfaces this plugin exercises are documented in the
|
|
@@ -40,12 +40,19 @@ arguments decline — those are covered by ActiveStorage's own RBS.
|
|
|
40
40
|
| Rule | Severity | When |
|
|
41
41
|
| --- | --- | --- |
|
|
42
42
|
| `plugin.activestorage.attachment-call` | info | a recognised `model.attachment_name` call surfaces; confirms the model → attachment mapping |
|
|
43
|
-
| `plugin.activestorage.load-error` | warning | discovery failed (e.g. the model directory is inaccessible under the IoBoundary trust policy) |
|
|
43
|
+
| `plugin.activestorage.load-error` | warning | discovery failed (e.g. the model directory is inaccessible under the IoBoundary trust policy) — once per run, on `.rigor.yml` |
|
|
44
44
|
|
|
45
45
|
No `:error` diagnostics in this slice — the value is the
|
|
46
46
|
return-type contribution; an "unknown attachment name" rule is a
|
|
47
47
|
future slice.
|
|
48
48
|
|
|
49
|
+
The `load-error` warning is run-scoped: it is a fact about your
|
|
50
|
+
configuration, not about any one source file, so it is reported once
|
|
51
|
+
per run on `.rigor.yml` rather than repeated on every analysed file.
|
|
52
|
+
It is `:warning`, so if you baselined it at its old position that
|
|
53
|
+
entry no longer matches and the row fails a `--fail-on=warning` run —
|
|
54
|
+
regenerate with `rigor baseline regenerate`.
|
|
55
|
+
|
|
49
56
|
## Configuration
|
|
50
57
|
|
|
51
58
|
```yaml
|
|
@@ -36,21 +36,27 @@ path, no vendoring, no `signature_paths:` wiring.
|
|
|
36
36
|
|
|
37
37
|
Roughly the top ~40 selectors plus their close neighbours, across:
|
|
38
38
|
|
|
39
|
-
- **Object (universal)** — `#blank?`, `#present?`, `#presence`,
|
|
40
|
-
`#try!`, `#acts_like
|
|
39
|
+
- **Object (universal)** — `#blank?`, `#present?`, `#presence`,
|
|
40
|
+
`#presence_in`, `#try`, `#try!`, `#acts_like?`, `#deep_dup`, `#with`,
|
|
41
|
+
`#with_options`, `#html_safe?`, `Kernel#class_eval` (+ `NilClass` /
|
|
42
|
+
`TrueClass` / `FalseClass`).
|
|
41
43
|
- **Integer / Float** — Duration multipliers (`#days`, `#hours`,
|
|
42
44
|
`#minutes`, …) and Bytes multipliers (`#megabytes`, `#gigabytes`, …).
|
|
43
45
|
- **String** — inflections (`#underscore`, `#camelize`, `#classify`,
|
|
44
46
|
`#constantize`, `#pluralize`, …), filters (`#squish`, `#truncate`),
|
|
45
|
-
`#html_safe`, `#starts_with?` / `#ends_with
|
|
47
|
+
`#html_safe`, `#starts_with?` / `#ends_with?` (also on **Symbol**),
|
|
48
|
+
conversions and `#in_time_zone`.
|
|
46
49
|
- **Time / Date / DateTime** — `.current`, `.zone`, `#yesterday`,
|
|
47
50
|
`#tomorrow`, `#beginning_of_*` / `#end_of_*`, `#ago`, `#since`. `Time`
|
|
48
51
|
additionally carries its **whole** Rails instance surface (see below);
|
|
49
52
|
`Date` and `DateTime` carry the same subset they always did.
|
|
50
53
|
- **Array** — `.wrap`, `#to_sentence`, `#in_groups_of`, `#second` …
|
|
51
54
|
`#fifth`, `#compact_blank`, `#exclude?`.
|
|
52
|
-
- **Hash** — `#symbolize_keys` / `#stringify_keys` (+ deep / bang)
|
|
53
|
-
`#deep_merge`, `#
|
|
55
|
+
- **Hash** — `#symbolize_keys` / `#stringify_keys` (+ deep / bang) and
|
|
56
|
+
their `#to_options` alias, `#deep_merge`, `#reverse_merge` /
|
|
57
|
+
`#with_defaults` (+ bang), `#with_indifferent_access`, `#except!`,
|
|
58
|
+
`#extract!`.
|
|
59
|
+
- **Range** — `#overlaps?`, `#to_fs` / `#to_formatted_s`.
|
|
54
60
|
- **Enumerable** — `#index_by`, `#index_with`, `#pluck`, `#exclude?`.
|
|
55
61
|
|
|
56
62
|
```ruby
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# rigor-grape
|
|
2
|
+
|
|
3
|
+
Types the [Grape](https://github.com/ruby-grape/grape)
|
|
4
|
+
endpoint-declaration DSL so calls inside `class API < Grape::API`
|
|
5
|
+
bodies — and inside `Grape::Entity` subclasses from
|
|
6
|
+
[grape-entity](https://github.com/ruby-grape/grape-entity) — resolve to
|
|
7
|
+
real carriers instead of `Dynamic[top]`. It reads source only, with no
|
|
8
|
+
`grape` runtime dependency.
|
|
9
|
+
|
|
10
|
+
It ships bundled in `rigortype`. Activate it under `plugins:` (or let
|
|
11
|
+
bundler auto-detection pick it up when `grape` or `grape-entity` is in
|
|
12
|
+
`Gemfile.lock`):
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
plugins:
|
|
16
|
+
- rigor-grape
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What it types
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
class ThingsAPI < Grape::API
|
|
23
|
+
version "v1", using: :path
|
|
24
|
+
format :json
|
|
25
|
+
|
|
26
|
+
desc "List things" # Object?
|
|
27
|
+
route_setting :swagger, tags: %w[things] # Object?
|
|
28
|
+
|
|
29
|
+
params do # → Grape::Validations::ParamsScope
|
|
30
|
+
requires :id, type: Integer # Object?
|
|
31
|
+
optional :q, type: String # Object?
|
|
32
|
+
requires :filter, type: Hash do # nested scope re-enters ParamsScope
|
|
33
|
+
requires :state, type: String
|
|
34
|
+
end
|
|
35
|
+
mutually_exclusive :q, :filter
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
namespace :things do # self: the API::Instance class object
|
|
39
|
+
get "/:id" do # self: Grape::Endpoint
|
|
40
|
+
params # Hash[untyped, untyped]
|
|
41
|
+
error!("nope", 404) # bot
|
|
42
|
+
present Thing.first, with: Entities::Thing
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Entities::Thing < Grape::Entity
|
|
48
|
+
expose :id # Array[untyped]
|
|
49
|
+
expose :name do # `self` stays the class object
|
|
50
|
+
expose :first
|
|
51
|
+
end
|
|
52
|
+
format_with(:iso) { |d| d.to_s } # Object
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Recognised surfaces:
|
|
57
|
+
|
|
58
|
+
- **Class-body declarations** on `Grape::API` subclasses (including
|
|
59
|
+
through intermediate source base classes): `params`, `namespace` /
|
|
60
|
+
`group` / `resource` / `resources` / `segment` / `route_param` /
|
|
61
|
+
`version` / `given` / `mounted`, `get`/`put`/`post`/`delete`/`head`/
|
|
62
|
+
`patch`/`options`/`route`, `desc`, `route_setting`, `helpers`, `use`,
|
|
63
|
+
`mount`, `rescue_from`, the format/error-format setters, callbacks
|
|
64
|
+
(`before`/`after`/`before_validation`/`after_validation`), `prefix`,
|
|
65
|
+
`scope`, `contract`, and friends.
|
|
66
|
+
- **`params` block bodies** bind `self` to
|
|
67
|
+
`Grape::Validations::ParamsScope`, so `requires`, `optional`, `given`,
|
|
68
|
+
`with`, `use`, and the grouping macros (`mutually_exclusive`,
|
|
69
|
+
`exactly_one_of`, `at_least_one_of`, `all_or_none_of`) resolve —
|
|
70
|
+
including nested `requires :x, type: Hash do ... end` bodies.
|
|
71
|
+
- **Namespace-family bodies** (`namespace`, `route_param`, `version`,
|
|
72
|
+
`given`, `mounted`) bind `self` to the `Grape::API::Instance` class
|
|
73
|
+
object, matching Grape's `instance_eval`-on-class semantics, so nested
|
|
74
|
+
declarations resolve the same surface.
|
|
75
|
+
- **`desc 'x' do ... end` bodies** bind `self` to
|
|
76
|
+
`Grape::DSL::Desc::ConfigContext` — the closed `ROUTE_ATTRIBUTES`
|
|
77
|
+
setter surface — so `detail`, `success`, `failure`, `tags`,
|
|
78
|
+
`entity`, `hidden`, `is_array`, `consumes`, `headers`, `summary`,
|
|
79
|
+
`deprecated`, `named`, `nickname`, `produces`, `security`,
|
|
80
|
+
`http_codes`, `body_name`, `default`, `description`, and `params`
|
|
81
|
+
(the documentation setter, not `ParamsScope`) resolve.
|
|
82
|
+
- **Verb bodies** bind `self` to `Grape::Endpoint`, so `params`,
|
|
83
|
+
`headers`, `cookies`, `env`, `declared`, `present`, `error!`,
|
|
84
|
+
`status`, `redirect`, `body`, `content_type`, `route`,
|
|
85
|
+
`route_setting`, `stream`, `sendfile` resolve.
|
|
86
|
+
- **`Grape::Entity` class bodies**: `expose`, `unexpose`,
|
|
87
|
+
`with_options`, `documentation`, `format_with`, `root`,
|
|
88
|
+
`root_element`, `represent`, `present_collection`, `root_exposures`.
|
|
89
|
+
Nested `expose` bodies keep `self` on the class object, so they
|
|
90
|
+
resolve the same way.
|
|
91
|
+
|
|
92
|
+
## Deferred
|
|
93
|
+
|
|
94
|
+
- `helpers do ... end` bodies (an anonymous module is `self` —
|
|
95
|
+
unnameable).
|
|
96
|
+
- Named `params :name` scopes inside `helpers` and `contract` schema
|
|
97
|
+
blocks.
|
|
98
|
+
- Value-level typing: `present`/`declared` results against the declared
|
|
99
|
+
entity or params, entity `represent` result typing.
|
|
100
|
+
- `Grape::Middleware` and custom-middleware DSL.
|
|
101
|
+
|
|
102
|
+
Calls on classes outside `Grape::API` / `Grape::Entity` ancestry keep
|
|
103
|
+
the `Dynamic` fallback, and DSL names the bundled signature does not
|
|
104
|
+
declare stay opaque rather than diagnosed — the declared classes are
|
|
105
|
+
registered as open receivers because Grape's runtime surface is
|
|
106
|
+
generated (`override_all_methods!`) and user base classes extend it.
|
|
@@ -58,11 +58,25 @@ name. `null:` extracts to `nullable:` (defaulting to `true`, mirroring
|
|
|
58
58
|
graphql-ruby); `required:` defaults to `false`. The single-element
|
|
59
59
|
`[String]` list form is recognised.
|
|
60
60
|
|
|
61
|
+
## Typed DSL surface
|
|
62
|
+
|
|
63
|
+
Beyond the fact tables, the plugin bundles the graphql-ruby class-level
|
|
64
|
+
DSL signature, so calls inside a recognised subclass type as their real
|
|
65
|
+
carriers instead of staying opaque: `field` returns
|
|
66
|
+
`GraphQL::Schema::Field`, `argument` returns `GraphQL::Schema::Argument`,
|
|
67
|
+
`Enum.value` returns `GraphQL::Schema::EnumValue`, `description` /
|
|
68
|
+
`graphql_name` return their configured strings, and the `Schema`
|
|
69
|
+
registration macros (`query`, `mutation`, `use`, `orphan_types`, …)
|
|
70
|
+
return what they registered. A block parameter on `field`/`argument`
|
|
71
|
+
(`field(:comments) { |f| … }`) types as `GraphQL::Schema::Field` /
|
|
72
|
+
`GraphQL::Schema::Argument`.
|
|
73
|
+
|
|
61
74
|
## No diagnostics, no config
|
|
62
75
|
|
|
63
76
|
The plugin emits no diagnostics and has no configuration knobs — it
|
|
64
|
-
contributes the type tables above for
|
|
65
|
-
walks every `paths:` entry's `.rb` files
|
|
77
|
+
contributes the type tables and signature above for the engine and
|
|
78
|
+
other plugins to consume. It walks every `paths:` entry's `.rb` files
|
|
79
|
+
for the schema-class shapes.
|
|
66
80
|
|
|
67
81
|
## Limitations
|
|
68
82
|
|
|
@@ -72,6 +86,13 @@ walks every `paths:` entry's `.rb` files for the schema-class shapes.
|
|
|
72
86
|
- **No `Schema.execute(...)` result typing.** Typing
|
|
73
87
|
`Schema.execute(query).to_h` against the queried fields is a future
|
|
74
88
|
plugin.
|
|
89
|
+
- **Interface DSL stays opaque.** `include GraphQL::Schema::Interface`
|
|
90
|
+
wires the DSL onto the includer through an include-time `extend`
|
|
91
|
+
hook RBS cannot express, so `field` inside an interface module is
|
|
92
|
+
not yet typed.
|
|
93
|
+
- **Zero-arity definition blocks keep an opaque `self`.**
|
|
94
|
+
`field :x do ... end` `instance_eval`s on the new `Field` at
|
|
95
|
+
runtime; only the explicit-parameter form (`{ |f| … }`) is typed.
|
|
75
96
|
- **Constant-form types only.** The string form (`field :foo, "User"`)
|
|
76
97
|
and the `<Type>.array` / `<Type>!` sugar chains are not recognised
|
|
77
98
|
(the `[String]` bracket form is). Multi-element and empty list
|
|
@@ -33,7 +33,7 @@ authorize(Comment, :edit) # error: no policy class CommentPolicy (did you mean
|
|
|
33
33
|
| `plugin.pundit.policy-call` | info | an `authorize` / `policy` / `policy_scope` call resolved to a discovered policy |
|
|
34
34
|
| `plugin.pundit.unknown-policy-class` | error | the record maps to a `<Type>Policy` with no entry in the index (with a did-you-mean) |
|
|
35
35
|
| `plugin.pundit.unknown-policy-method` | error | the policy exists but the `:action` has no `<action>?` predicate (lists known predicates + a did-you-mean) |
|
|
36
|
-
| `plugin.pundit.load-error` | warning | policy discovery failed (parse/read error) — once per
|
|
36
|
+
| `plugin.pundit.load-error` | warning | policy discovery failed (parse/read error) — once per run, on `.rigor.yml` |
|
|
37
37
|
|
|
38
38
|
The record maps to a policy by constant name or inferred
|
|
39
39
|
`Nominal[T]` (`Post` → `PostPolicy`); `:update` normalises to
|
|
@@ -88,6 +88,13 @@ candidate rather than being guessed at.
|
|
|
88
88
|
not validated when `local` has no inferred `Nominal[T]`.
|
|
89
89
|
- **`Scope` policies** are validated for class existence, not for
|
|
90
90
|
`Scope#resolve`.
|
|
91
|
+
- **The "failed to discover policies" warning is run-scoped.** It is
|
|
92
|
+
a fact about your configuration, not about any one source file, so
|
|
93
|
+
it is reported once per run on `.rigor.yml` rather than repeated on
|
|
94
|
+
every analysed file. It is `:warning`, so if you baselined it at its
|
|
95
|
+
old position that entry no longer matches and the row fails a
|
|
96
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
97
|
+
regenerate`.
|
|
91
98
|
|
|
92
99
|
## Plugin internals
|
|
93
100
|
|
|
@@ -88,11 +88,16 @@ view templates containing lazy `t('.key')` calls.
|
|
|
88
88
|
may come from controller instance variables not visible in the
|
|
89
89
|
template source. Configure `view_search_paths:` to override the
|
|
90
90
|
default `["app/views"]`.
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
- **`load-error` rows are reported on `.rigor.yml`** — a locale
|
|
92
|
+
file that does not parse, or a locale or view scan that fails,
|
|
93
|
+
is reported once per run at `.rigor.yml:1:1`. They are
|
|
94
|
+
`:warning`, so if you baselined one at its old position on a
|
|
95
|
+
Ruby file, that entry no longer matches and the row fails a
|
|
96
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
97
|
+
regenerate`. View diagnostics keep their view file position and
|
|
98
|
+
are reported once per run whatever `--workers` is set to
|
|
99
|
+
([#1060](https://github.com/rigortype/rigor/issues/1060)); a
|
|
100
|
+
baseline entry recorded against a view still matches.
|
|
96
101
|
- **Pluralization is recognised but not validated** — `count:` is
|
|
97
102
|
treated as a reserved option; whether the locale defines
|
|
98
103
|
`:zero` / `:one` / `:other` is not checked.
|
|
@@ -129,6 +129,13 @@ routed. Helper-name recognition for all three is unaffected.
|
|
|
129
129
|
doesn't model) may not register, which can surface a false
|
|
130
130
|
`unknown-helper`. Record those in a baseline, or
|
|
131
131
|
`# rigor:disable` the line.
|
|
132
|
+
- **The "routes file did not load" warning is run-scoped.** It is a
|
|
133
|
+
fact about your configuration, not about any one source file, so
|
|
134
|
+
it is reported once per run on `.rigor.yml` rather than on the
|
|
135
|
+
first analysed file. It is `:warning`, so if you baselined it at
|
|
136
|
+
its old position that entry no longer matches and the row fails a
|
|
137
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
138
|
+
regenerate`.
|
|
132
139
|
- **Project-custom inflections** declared in
|
|
133
140
|
`config/initializers/inflections.rb` are not yet fully ingested
|
|
134
141
|
(ADR-39 slice 3); the standard ActiveSupport inflections are
|