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
|
@@ -49,23 +49,32 @@ module Rigor
|
|
|
49
49
|
attr_reader :target_gems
|
|
50
50
|
|
|
51
51
|
attr_reader :id, :version, :description, :config_schema, :config_defaults, :produces, :consumes,
|
|
52
|
-
:owns_receivers, :open_receivers, :
|
|
52
|
+
:owns_receivers, :open_receivers, :rbs_complete_ancestors, :rbs_complete_extends,
|
|
53
|
+
:type_node_resolvers,
|
|
54
|
+
:block_as_methods,
|
|
53
55
|
:heredoc_templates, :nested_class_templates, :trait_registries,
|
|
54
56
|
:hkt_registrations, :hkt_definitions, :signature_paths, :protocol_contracts,
|
|
55
57
|
:source_rbs_synthesizer, :additional_initializers,
|
|
56
58
|
:effect_root, :effect_labels, :effect_attributions, :effect_edges, :effect_entry_points,
|
|
57
59
|
:effect_ancestry
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
# #392 — the project-relative globs whose files this plugin compiles into {TemplateUnit}s. Purely a
|
|
62
|
+
# CLAIM: the engine globs these, reads each match's bytes and offers them to
|
|
63
|
+
# {Base#template_units_for_file}, which is where the transform itself lives. A plugin declaring none
|
|
64
|
+
# is never asked, and a run whose plugins declare none does no extra globbing at all.
|
|
65
|
+
attr_reader :template_globs
|
|
66
|
+
|
|
67
|
+
def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
60
68
|
id:, version:,
|
|
61
69
|
description: nil, config_schema: {}, target_gems: [],
|
|
62
|
-
produces: [], consumes: [], owns_receivers: [], open_receivers: [],
|
|
70
|
+
produces: [], consumes: [], owns_receivers: [], open_receivers: [], rbs_complete_ancestors: [],
|
|
71
|
+
rbs_complete_extends: [], type_node_resolvers: [],
|
|
63
72
|
block_as_methods: [], heredoc_templates: [], nested_class_templates: [],
|
|
64
73
|
trait_registries: [],
|
|
65
74
|
hkt_registrations: [], hkt_definitions: [], signature_paths: [], protocol_contracts: [],
|
|
66
75
|
source_rbs_synthesizer: nil, additional_initializers: [],
|
|
67
76
|
effect_root: nil, effect_labels: [], effect_attributions: [], effect_edges: [],
|
|
68
|
-
effect_entry_points: [], effect_ancestry: []
|
|
77
|
+
effect_entry_points: [], effect_ancestry: [], template_globs: []
|
|
69
78
|
)
|
|
70
79
|
validate_id!(id)
|
|
71
80
|
validate_version!(version)
|
|
@@ -74,6 +83,8 @@ module Rigor
|
|
|
74
83
|
validate_target_gems!(target_gems)
|
|
75
84
|
validate_owns_receivers!(owns_receivers)
|
|
76
85
|
validate_open_receivers!(open_receivers)
|
|
86
|
+
validate_rbs_complete_ancestors!(rbs_complete_ancestors)
|
|
87
|
+
validate_rbs_complete_extends!(rbs_complete_extends)
|
|
77
88
|
validate_type_node_resolvers!(type_node_resolvers)
|
|
78
89
|
validate_block_as_methods!(block_as_methods)
|
|
79
90
|
validate_heredoc_templates!(heredoc_templates)
|
|
@@ -93,10 +104,11 @@ module Rigor
|
|
|
93
104
|
hkt_registrations, hkt_definitions, signature_paths, protocol_contracts,
|
|
94
105
|
source_rbs_synthesizer)
|
|
95
106
|
assign_target_gems(target_gems)
|
|
107
|
+
assign_rbs_complete_ancestors(rbs_complete_ancestors, rbs_complete_extends)
|
|
96
108
|
assign_nested_class_templates(nested_class_templates)
|
|
97
109
|
assign_additional_initializers(additional_initializers)
|
|
98
110
|
assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
|
|
99
|
-
effect_entry_points, effect_ancestry)
|
|
111
|
+
effect_entry_points, effect_ancestry, template_globs)
|
|
100
112
|
freeze
|
|
101
113
|
end
|
|
102
114
|
|
|
@@ -133,6 +145,13 @@ module Rigor
|
|
|
133
145
|
end
|
|
134
146
|
private :assign_target_gems
|
|
135
147
|
|
|
148
|
+
# ADR-43 WD4 — assigned outside assign_fields (which already carries the maximum positional arity).
|
|
149
|
+
def assign_rbs_complete_ancestors(rbs_complete_ancestors, rbs_complete_extends)
|
|
150
|
+
@rbs_complete_ancestors = rbs_complete_ancestors.map { |c| c.to_s.dup.freeze }.freeze
|
|
151
|
+
@rbs_complete_extends = rbs_complete_extends.map { |c| c.to_s.dup.freeze }.freeze
|
|
152
|
+
end
|
|
153
|
+
private :assign_rbs_complete_ancestors
|
|
154
|
+
|
|
136
155
|
# Assigned outside assign_fields (which already carries the maximum positional arity) — set in
|
|
137
156
|
# `initialize` before the final freeze. ADR-36 nested-class emission tier.
|
|
138
157
|
def assign_nested_class_templates(nested_class_templates)
|
|
@@ -162,15 +181,27 @@ module Rigor
|
|
|
162
181
|
private :validate_effect_fields!
|
|
163
182
|
|
|
164
183
|
def assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
|
|
165
|
-
effect_entry_points, effect_ancestry)
|
|
184
|
+
effect_entry_points, effect_ancestry, template_globs)
|
|
166
185
|
@effect_root = effect_root.nil? ? nil : effect_root.to_s.dup.freeze
|
|
167
186
|
@effect_labels = effect_labels.map { |label| label.to_s.dup.freeze }.uniq.sort.freeze
|
|
168
187
|
@effect_attributions = effect_attributions.dup.freeze
|
|
169
188
|
@effect_edges = effect_edges.dup.freeze
|
|
170
189
|
@effect_entry_points = effect_entry_points.dup.freeze
|
|
171
190
|
@effect_ancestry = effect_ancestry.dup.freeze
|
|
191
|
+
assign_template_globs(template_globs)
|
|
172
192
|
end
|
|
173
193
|
private :assign_effect_fields
|
|
194
|
+
|
|
195
|
+
# #392 — a glob is a String, and an absolute one is refused: a template unit is a project file, and
|
|
196
|
+
# a plugin reaching outside the project root is the trust boundary ADR-2 § "Plugin Trust and I/O
|
|
197
|
+
# Policy" draws. Validated and assigned in one call, because `#initialize` is at its line budget.
|
|
198
|
+
def assign_template_globs(globs)
|
|
199
|
+
validate_array_of!("template_globs", globs, "non-empty relative glob Strings") do |glob|
|
|
200
|
+
glob.is_a?(String) && !glob.empty? && !glob.start_with?("/") && !glob.include?("..")
|
|
201
|
+
end
|
|
202
|
+
@template_globs = globs.map { |glob| glob.to_s.dup.freeze }.uniq.freeze
|
|
203
|
+
end
|
|
204
|
+
private :assign_template_globs
|
|
174
205
|
# rubocop:enable Metrics/ParameterLists, Metrics/AbcSize
|
|
175
206
|
|
|
176
207
|
public
|
|
@@ -196,7 +227,7 @@ module Rigor
|
|
|
196
227
|
errors
|
|
197
228
|
end
|
|
198
229
|
|
|
199
|
-
def to_h # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
230
|
+
def to_h # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
|
200
231
|
{
|
|
201
232
|
"id" => id,
|
|
202
233
|
"version" => version,
|
|
@@ -208,6 +239,8 @@ module Rigor
|
|
|
208
239
|
"consumes" => consumes.map { |c| consumption_hash(c) },
|
|
209
240
|
"owns_receivers" => owns_receivers,
|
|
210
241
|
"open_receivers" => open_receivers,
|
|
242
|
+
"rbs_complete_ancestors" => rbs_complete_ancestors,
|
|
243
|
+
"rbs_complete_extends" => rbs_complete_extends,
|
|
211
244
|
"type_node_resolvers" => type_node_resolvers.map { |r| r.class.name },
|
|
212
245
|
"block_as_methods" => block_as_methods.map(&:to_h),
|
|
213
246
|
"heredoc_templates" => heredoc_templates.map(&:to_h),
|
|
@@ -224,7 +257,8 @@ module Rigor
|
|
|
224
257
|
"effect_attributions" => effect_attributions.map(&:to_h),
|
|
225
258
|
"effect_edges" => effect_edges.map(&:to_h),
|
|
226
259
|
"effect_entry_points" => effect_entry_points.map(&:to_h),
|
|
227
|
-
"effect_ancestry" => effect_ancestry.map(&:to_h)
|
|
260
|
+
"effect_ancestry" => effect_ancestry.map(&:to_h),
|
|
261
|
+
"template_globs" => template_globs
|
|
228
262
|
}
|
|
229
263
|
end
|
|
230
264
|
|
|
@@ -233,10 +267,11 @@ module Rigor
|
|
|
233
267
|
# otherwise. A third-party plugin's `effect_root:` is ignored here (and warned about by the registry),
|
|
234
268
|
# so it opens only the root named after itself.
|
|
235
269
|
def effect_owner
|
|
236
|
-
|
|
270
|
+
root = effect_root
|
|
271
|
+
return id if root.nil?
|
|
237
272
|
return id unless FirstParty.bundled?(id)
|
|
238
273
|
|
|
239
|
-
|
|
274
|
+
root
|
|
240
275
|
end
|
|
241
276
|
|
|
242
277
|
# Whether this plugin's `effect_attributions:` may carry `discharge: true` (ADR-103 WD6).
|
|
@@ -388,6 +423,32 @@ module Rigor
|
|
|
388
423
|
validate_array_of!("open_receivers", open_receivers, "non-empty String") { |c| c.is_a?(String) && !c.empty? }
|
|
389
424
|
end
|
|
390
425
|
|
|
426
|
+
# ADR-43 WD4 — `rbs_complete_ancestors:` declares the class names this plugin's bundled RBS covers
|
|
427
|
+
# COMPLETELY: a method call the signature does not declare is genuinely absent at runtime. A
|
|
428
|
+
# Ruby-source subclass of a listed class may then bridge inherited calls to the ancestor's RBS
|
|
429
|
+
# (the ADR-43 allow-list's manifest-declared half). The dual of `open_receivers:` — that field
|
|
430
|
+
# marks a class open to suppress diagnostics on an unbounded surface; this one marks a class
|
|
431
|
+
# closed to ENABLE them on subclasses. Listing a class whose signature is partial would turn
|
|
432
|
+
# every omitted inherited method into a `call.undefined-method` false positive on subclass
|
|
433
|
+
# receivers, so only classes the plugin's `signature_paths:` declares exhaustively belong here.
|
|
434
|
+
def validate_rbs_complete_ancestors!(rbs_complete_ancestors)
|
|
435
|
+
validate_array_of!("rbs_complete_ancestors", rbs_complete_ancestors, "non-empty String") do |c|
|
|
436
|
+
c.is_a?(String) && !c.empty?
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# `rbs_complete_extends:` is the extend-edge twin of `rbs_complete_ancestors:` (ADR-43 WD4): it names
|
|
441
|
+
# the MODULES whose bundled RBS instance surface is complete, so a project `extend M` in a class or
|
|
442
|
+
# module body lets singleton-side calls on that class resolve M's declared instance methods
|
|
443
|
+
# (`class F; extend T::Sig; sig { ... }; end` reaching `T::Sig#sig`). Same completeness rule: a
|
|
444
|
+
# module whose declared surface is partial does not belong here — the extend edge would misreport
|
|
445
|
+
# real calls as `call.undefined-method`.
|
|
446
|
+
def validate_rbs_complete_extends!(rbs_complete_extends)
|
|
447
|
+
validate_array_of!("rbs_complete_extends", rbs_complete_extends, "non-empty String") do |c|
|
|
448
|
+
c.is_a?(String) && !c.empty?
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
391
452
|
# ADR-13 slice 2 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the
|
|
392
453
|
# parser consults (in slice 3) when an RBS::Extended payload's named- or generic-type head misses the
|
|
393
454
|
# built-in registry. Slice 2 carries the declarations on the manifest and the registry exposes them in
|
|
@@ -373,6 +373,30 @@ module Rigor
|
|
|
373
373
|
@open_receivers_set.include?(class_name.to_s)
|
|
374
374
|
end
|
|
375
375
|
|
|
376
|
+
# ADR-43 WD4 — the aggregate set of "RBS-complete ancestor" class names declared across loaded
|
|
377
|
+
# plugins (manifest `rbs_complete_ancestors:`). `RbsDispatch`'s inherited-call bridge consults it
|
|
378
|
+
# alongside the engine's own allow-list: a Ruby-source subclass of a listed class resolves
|
|
379
|
+
# inherited calls against the ancestor's RBS, and the signature-reading rules apply to them.
|
|
380
|
+
attr_reader :rbs_complete_ancestors
|
|
381
|
+
|
|
382
|
+
def rbs_complete_ancestor?(class_name)
|
|
383
|
+
return false if class_name.nil?
|
|
384
|
+
|
|
385
|
+
@rbs_complete_ancestors_set.include?(class_name.to_s)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# The extend-edge twin of `rbs_complete_ancestors` — the aggregate set of module names declared via
|
|
389
|
+
# manifest `rbs_complete_extends:`. `RbsDispatch`'s singleton bridge consults it for `extend M`
|
|
390
|
+
# edges in project source: `class F; extend T::Sig; sig { ... }; end` resolves `sig` through
|
|
391
|
+
# `T::Sig`'s bundled RBS instance surface.
|
|
392
|
+
attr_reader :rbs_complete_extends
|
|
393
|
+
|
|
394
|
+
def rbs_complete_extends?(class_name)
|
|
395
|
+
return false if class_name.nil?
|
|
396
|
+
|
|
397
|
+
@rbs_complete_extends_set.include?(class_name.to_s)
|
|
398
|
+
end
|
|
399
|
+
|
|
376
400
|
# ADR-28 — flat, ordered list of every loaded plugin's path-scoped method-protocol contracts, in
|
|
377
401
|
# plugin registration order. Read from each plugin's `#protocol_contracts` (which the manifest backs
|
|
378
402
|
# by default but a plugin MAY override to fold in per-project config). Consumed by
|
|
@@ -435,14 +459,24 @@ module Rigor
|
|
|
435
459
|
# nil where {#guarded_manifest} caught a raising manifest read.
|
|
436
460
|
def compile_aggregates(manifests)
|
|
437
461
|
@additional_initializers = manifests.flat_map { |m| m&.additional_initializers || [] }.freeze
|
|
438
|
-
@open_receivers = manifests
|
|
462
|
+
@open_receivers = aggregate_class_names(manifests, :open_receivers)
|
|
439
463
|
@open_receivers_set = @open_receivers.to_set.freeze
|
|
464
|
+
@rbs_complete_ancestors = aggregate_class_names(manifests, :rbs_complete_ancestors)
|
|
465
|
+
@rbs_complete_ancestors_set = @rbs_complete_ancestors.to_set.freeze
|
|
466
|
+
@rbs_complete_extends = aggregate_class_names(manifests, :rbs_complete_extends)
|
|
467
|
+
@rbs_complete_extends_set = @rbs_complete_extends.to_set.freeze
|
|
440
468
|
@type_node_resolvers = manifests.flat_map { |m| m&.type_node_resolvers || [] }.freeze
|
|
441
469
|
@protocol_contracts = @plugins.flat_map { |p| safe_protocol_contracts(p) }.freeze
|
|
442
470
|
@contracts_by_path = {}
|
|
443
471
|
@effect_memo = {}
|
|
444
472
|
end
|
|
445
473
|
|
|
474
|
+
# The shared shape of `open_receivers:` / `rbs_complete_ancestors:` — a flat uniq'd class-name list
|
|
475
|
+
# aggregated across manifests, each entry normalised to String.
|
|
476
|
+
def aggregate_class_names(manifests, field)
|
|
477
|
+
manifests.flat_map { |m| (m&.public_send(field) || []).map(&:to_s) }.uniq.freeze
|
|
478
|
+
end
|
|
479
|
+
|
|
446
480
|
# Fail-soft per plugin: a plugin whose `effect_attributions:` override raises (a missing
|
|
447
481
|
# `config/application.rb`, an unreadable path) contributes nothing rather than taking the run down.
|
|
448
482
|
# An effect contribution is an enrichment; ADR-2 isolates plugin failures at the analyzer boundary
|
|
@@ -21,7 +21,12 @@ module Rigor
|
|
|
21
21
|
# dropping an annotation it parsed. They are not the same news — the first says the file contributed
|
|
22
22
|
# nothing, the second that it contributed all but one thing — so they carry distinct diagnostic ids.
|
|
23
23
|
# Defaults to `:failed`, the pre-WD12 meaning, so an older caller records what it always did.
|
|
24
|
-
|
|
24
|
+
#
|
|
25
|
+
# `:contradiction` (ADR-112 WD5, issue #1075) is the consistency rule's error: a member two sources
|
|
26
|
+
# declare in ways that contradict, or a refinement outside its own declared type. It is the one kind
|
|
27
|
+
# positioned at a real line (`line`, nil for every other kind), because it usually sits in a `.rbs`
|
|
28
|
+
# file whose positions are exact; the others are file-level rows at line 1.
|
|
29
|
+
Entry = Data.define(:plugin_id, :path, :message, :kind, :line)
|
|
25
30
|
|
|
26
31
|
def initialize
|
|
27
32
|
@entries = []
|
|
@@ -34,12 +39,13 @@ module Rigor
|
|
|
34
39
|
# file list and then drains each worker's stream into this one reporter, so without the dedup a
|
|
35
40
|
# `--workers N` run printed N copies of a row a `--workers=0` run printed once. The same collapse the
|
|
36
41
|
# {RbsExtended::Reporter} drain documents, for the same reason.
|
|
37
|
-
def record(plugin_id:, path:, message:, kind: :failed)
|
|
42
|
+
def record(plugin_id:, path:, message:, kind: :failed, line: nil)
|
|
38
43
|
entry = Entry.new(
|
|
39
44
|
plugin_id: plugin_id.to_s.dup.freeze,
|
|
40
45
|
path: path.to_s.dup.freeze,
|
|
41
46
|
message: message.to_s.dup.freeze,
|
|
42
|
-
kind: kind
|
|
47
|
+
kind: kind,
|
|
48
|
+
line: line
|
|
43
49
|
)
|
|
44
50
|
return nil if @seen.key?(entry)
|
|
45
51
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
# One **template unit** — a non-Ruby source file a plugin compiled into Ruby, handed to the engine to
|
|
8
|
+
# be parsed and typed like a file (ADR-16 Tier D, revived by #392 as this seam; design note
|
|
9
|
+
# `docs/design/20260816-effect-labels.md` § 11.3).
|
|
10
|
+
#
|
|
11
|
+
# ADR-16's Tier D declared `external_files:` — "files evaluated as if their body were pasted at a
|
|
12
|
+
# declared call site, with `self` typed as a declared class" — and ADR-60 WD1 removed it for want of a
|
|
13
|
+
# consumer. What Tier D lacked, and what this carries, is a **source transform with a line map** ahead
|
|
14
|
+
# of parsing: the file on disk is not Ruby, so the engine analyses `ruby_source` and reports at the
|
|
15
|
+
# template's own lines.
|
|
16
|
+
#
|
|
17
|
+
# A unit is pure data. The plugin's transform runs ONCE, on the parent, before any analysis; nothing
|
|
18
|
+
# here is a callable, so the unit survives `Marshal` into a fork-pool worker exactly as the macro
|
|
19
|
+
# substrate's value objects do.
|
|
20
|
+
#
|
|
21
|
+
# - `logical_name` — the name the unit is keyed by, independent of the handler: `users/show.html`, not
|
|
22
|
+
# `users/show.html.erb`. Becomes the `view:<logical_name>` effect-unit key, so an ERB → Haml rewrite
|
|
23
|
+
# is not a rename.
|
|
24
|
+
# - `path` — the template file as the user wrote it. Every diagnostic and every source trace names it.
|
|
25
|
+
# - `ruby_source` — the compiled Ruby the engine parses.
|
|
26
|
+
# - `line_map` — `{ ruby_source line => template line }`, 1-based. A line the map does not mention
|
|
27
|
+
# anchors at the nearest mapped line before it, and at line 1 when there is none. **An EMPTY map is
|
|
28
|
+
# the identity, not "no positions"**: every compiled line reports at its own number and the columns
|
|
29
|
+
# pass through untouched. That is correct only for a transform that emits nothing the template did
|
|
30
|
+
# not contain — a transform that prepends so much as one preamble line and omits the map reports
|
|
31
|
+
# every finding off by that many lines. Omit it only for a byte-preserving transform.
|
|
32
|
+
# - `self_type` — the fully-qualified class the body's `self` is typed as, or nil for a bare body.
|
|
33
|
+
# - `locals` — `{ name => type name }`, the parameters the render site passes.
|
|
34
|
+
# - `ivar_seeds` — `{ "@name" => type name }`, the assigns the rendering action set.
|
|
35
|
+
# - `transform_id` — the compiler's identity (`"erubi-1.13"`). Joins the unit digest, so re-compiling
|
|
36
|
+
# with a different compiler re-analyses. Defaults to the declaring plugin's `id@version`.
|
|
37
|
+
#
|
|
38
|
+
# - `suppressed_rules` — rule-id PREFIXES the engine drops for this unit's diagnostics (#393). The
|
|
39
|
+
# per-unit rule posture: a compiled template is not code the user wrote, and a plugin that knows its
|
|
40
|
+
# compiler's idioms is the only party that can say which families of finding are meaningful there.
|
|
41
|
+
# `["call."]` is what an ERB plugin declares while the synthesised receivers are still coarse — the
|
|
42
|
+
# unit still contributes its EFFECTS, which is what the view layer is analysed for first, without
|
|
43
|
+
# the typing half costing the project a diagnostic per line. Empty means "report everything", which
|
|
44
|
+
# is what a plugin whose synthesis it trusts should declare.
|
|
45
|
+
#
|
|
46
|
+
# A type name that resolves to nothing is not an error here: the engine falls back to `Dynamic`, which
|
|
47
|
+
# taints honestly rather than fabricating a type the plugin could not justify (ADR-5).
|
|
48
|
+
class TemplateUnit
|
|
49
|
+
# Bumped whenever the engine changes what it synthesises from a unit — the seeding rules, the
|
|
50
|
+
# scope binding, the effect-unit key spelling. It rides the unit digest, so a change here
|
|
51
|
+
# invalidates every cached run that analysed a unit, exactly as a changed transform does.
|
|
52
|
+
SYNTHESIS_VERSION = 2
|
|
53
|
+
|
|
54
|
+
# The prefix an effect-unit key carries for a template unit. Deliberately not a {Effects::MethodKey}
|
|
55
|
+
# shape: a view has no owner class and no selector, and spelling one would put a method in the
|
|
56
|
+
# snapshot that no call site can name.
|
|
57
|
+
KEY_PREFIX = "view:"
|
|
58
|
+
|
|
59
|
+
IVAR_NAME = /\A@[A-Za-z_][A-Za-z0-9_]*\z/
|
|
60
|
+
private_constant :IVAR_NAME
|
|
61
|
+
|
|
62
|
+
LOCAL_NAME = /\A[a-z_][A-Za-z0-9_]*\z/
|
|
63
|
+
private_constant :LOCAL_NAME
|
|
64
|
+
|
|
65
|
+
# A rule prefix is a dotted family (`call.`) or a whole rule id (`call.undefined-method`); the match
|
|
66
|
+
# is `String#start_with?`, so the former covers the latter. A bare `""` would suppress the unit
|
|
67
|
+
# entirely and is refused.
|
|
68
|
+
RULE_PREFIX = /\A[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*\.?\z/
|
|
69
|
+
private_constant :RULE_PREFIX
|
|
70
|
+
|
|
71
|
+
attr_reader :logical_name, :path, :ruby_source, :line_map, :self_type, :locals, :ivar_seeds,
|
|
72
|
+
:transform_id, :suppressed_rules
|
|
73
|
+
|
|
74
|
+
# Every field is one declared property of the unit; grouping them behind a context object would only
|
|
75
|
+
# move the same list into a second value class.
|
|
76
|
+
def initialize(logical_name:, path:, ruby_source:, line_map: {}, self_type: nil, locals: {}, # rubocop:disable Metrics/ParameterLists
|
|
77
|
+
ivar_seeds: {}, transform_id: nil, suppressed_rules: [])
|
|
78
|
+
@logical_name = validate_string!("logical_name", logical_name)
|
|
79
|
+
@path = validate_string!("path", path)
|
|
80
|
+
@ruby_source = validate_source!(ruby_source)
|
|
81
|
+
@line_map = validate_line_map!(line_map)
|
|
82
|
+
@self_type = self_type.nil? ? nil : validate_string!("self_type", self_type)
|
|
83
|
+
@locals = validate_bindings!("locals", locals, LOCAL_NAME)
|
|
84
|
+
@ivar_seeds = validate_bindings!("ivar_seeds", ivar_seeds, IVAR_NAME)
|
|
85
|
+
@transform_id = transform_id.nil? ? nil : validate_string!("transform_id", transform_id)
|
|
86
|
+
@suppressed_rules = validate_rule_prefixes!(suppressed_rules)
|
|
87
|
+
freeze
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The effect-unit key and the snapshot key: `view:users/show.html`.
|
|
91
|
+
def unit_key
|
|
92
|
+
"#{KEY_PREFIX}#{@logical_name}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The unit's cache identity — **source bytes + transform id + synthesis version**, the triple
|
|
96
|
+
# `docs/internal-spec/cache.md` § computed-value keys asks a synthesised input to carry. The
|
|
97
|
+
# template's own bytes are deliberately NOT in it: the engine analyses `ruby_source`, and two
|
|
98
|
+
# templates that compile to the same Ruby have the same answer.
|
|
99
|
+
#
|
|
100
|
+
# @param fallback_transform_id — the declaring plugin's `id@version`, used when the unit
|
|
101
|
+
# named no transform of its own.
|
|
102
|
+
def digest(fallback_transform_id = nil)
|
|
103
|
+
Digest::SHA256.hexdigest(
|
|
104
|
+
[
|
|
105
|
+
"source:#{@ruby_source}",
|
|
106
|
+
"transform:#{@transform_id || fallback_transform_id || 'unknown'}",
|
|
107
|
+
"suppressed:#{@suppressed_rules.join(',')}",
|
|
108
|
+
"synthesis:#{SYNTHESIS_VERSION}"
|
|
109
|
+
].join("\x00")
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The template line a `ruby_source` line reports at. Falls back to the nearest mapped line before it
|
|
114
|
+
# — a synthesised prologue or a buffer-append the compiler emitted between two template lines still
|
|
115
|
+
# points into the template rather than past its end — and to line 1 when the map says nothing at all.
|
|
116
|
+
def template_line(ruby_line)
|
|
117
|
+
return ruby_line if @line_map.empty?
|
|
118
|
+
return @line_map[ruby_line] if @line_map.key?(ruby_line)
|
|
119
|
+
|
|
120
|
+
before = @line_map.keys.select { |line| line < ruby_line }
|
|
121
|
+
before.empty? ? 1 : @line_map[before.max]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def to_h
|
|
125
|
+
{
|
|
126
|
+
"logical_name" => @logical_name, "path" => @path, "ruby_source" => @ruby_source,
|
|
127
|
+
"line_map" => @line_map, "self_type" => @self_type, "locals" => @locals,
|
|
128
|
+
"ivar_seeds" => @ivar_seeds, "transform_id" => @transform_id,
|
|
129
|
+
"suppressed_rules" => @suppressed_rules
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def ==(other)
|
|
134
|
+
other.is_a?(TemplateUnit) && other.to_h == to_h
|
|
135
|
+
end
|
|
136
|
+
alias eql? ==
|
|
137
|
+
|
|
138
|
+
def hash
|
|
139
|
+
[self.class, to_h].hash
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def validate_string!(field, value)
|
|
145
|
+
text = value.to_s
|
|
146
|
+
raise ArgumentError, "TemplateUnit #{field} must be a non-empty String" if text.empty?
|
|
147
|
+
|
|
148
|
+
text.dup.freeze
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def validate_source!(value)
|
|
152
|
+
raise ArgumentError, "TemplateUnit ruby_source must be a String" unless value.is_a?(String)
|
|
153
|
+
|
|
154
|
+
value.dup.freeze
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# 1-based on both sides. A zero or negative line is a compiler bug the engine would turn into a
|
|
158
|
+
# diagnostic positioned off the top of the file, so it is refused at construction.
|
|
159
|
+
def validate_line_map!(value)
|
|
160
|
+
raise ArgumentError, "TemplateUnit line_map must be a Hash" unless value.is_a?(Hash)
|
|
161
|
+
|
|
162
|
+
value.to_h do |from, to|
|
|
163
|
+
unless from.is_a?(Integer) && to.is_a?(Integer) && from.positive? && to.positive?
|
|
164
|
+
raise ArgumentError, "TemplateUnit line_map must map positive Integer to positive Integer"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
[from, to]
|
|
168
|
+
end.freeze
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def validate_rule_prefixes!(value)
|
|
172
|
+
raise ArgumentError, "TemplateUnit suppressed_rules must be an Array" unless value.is_a?(Array)
|
|
173
|
+
|
|
174
|
+
value.map do |prefix|
|
|
175
|
+
text = prefix.to_s
|
|
176
|
+
unless text.match?(RULE_PREFIX)
|
|
177
|
+
raise ArgumentError, "TemplateUnit suppressed_rules entry #{text.inspect} is malformed"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
text.dup.freeze
|
|
181
|
+
end.uniq.freeze
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def validate_bindings!(field, value, pattern)
|
|
185
|
+
raise ArgumentError, "TemplateUnit #{field} must be a Hash" unless value.is_a?(Hash)
|
|
186
|
+
|
|
187
|
+
value.to_h do |name, type_name|
|
|
188
|
+
key = name.to_s
|
|
189
|
+
raise ArgumentError, "TemplateUnit #{field} key #{key.inspect} is malformed" unless key.match?(pattern)
|
|
190
|
+
|
|
191
|
+
[key.dup.freeze, validate_string!("#{field} value", type_name)]
|
|
192
|
+
end.freeze
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
data/lib/rigor/plugin.rb
CHANGED
|
@@ -99,7 +99,9 @@ module Rigor
|
|
|
99
99
|
tables = { discovered_classes: index.fetch(:classes) }
|
|
100
100
|
%i[
|
|
101
101
|
def_nodes def_nestings singleton_def_nodes def_sources singleton_def_sources superclasses includes
|
|
102
|
-
|
|
102
|
+
prepends
|
|
103
|
+
method_visibilities methods parameter_envelopes data_member_layouts struct_member_layouts
|
|
104
|
+
deferred_ranges refinements global_write_census
|
|
103
105
|
].each do |slot|
|
|
104
106
|
tables[seed_key(slot)] = def_index.fetch(slot)
|
|
105
107
|
end
|
|
@@ -182,8 +182,7 @@ module Rigor
|
|
|
182
182
|
# `Unavailable` rescue above and disables the cache, which is the only sound reading of a key slot
|
|
183
183
|
# that could not be computed.
|
|
184
184
|
def engine_source_entries
|
|
185
|
-
|
|
186
|
-
identity.nil? ? [] : [config_entry("engine-source", identity)]
|
|
185
|
+
Cache::EngineSource.key_config_entries
|
|
187
186
|
end
|
|
188
187
|
|
|
189
188
|
# Probed BEFORE the snapshot load, for the reason string rather than for soundness. Since #289
|
data/lib/rigor/rbs_extended.rb
CHANGED
|
@@ -106,6 +106,12 @@ module Rigor
|
|
|
106
106
|
# `docs/type-specification/rbs-extended.md`.
|
|
107
107
|
INFERRED_RETURN_DIRECTIVE = "rigor:v1:inferred-return"
|
|
108
108
|
|
|
109
|
+
# `%a{rigor:v1:inferred-signature}` — every type slot on the member, parameters and return alike,
|
|
110
|
+
# is a placeholder: the author's comment block asserted nothing about this member's own contract.
|
|
111
|
+
# Strictly narrower than {INFERRED_RETURN_DIRECTIVE}, which also covers a member with an authored
|
|
112
|
+
# parameter and no authored return. Normative in `docs/type-specification/rbs-extended.md`.
|
|
113
|
+
INFERRED_SIGNATURE_DIRECTIVE = "rigor:v1:inferred-signature"
|
|
114
|
+
|
|
109
115
|
module_function
|
|
110
116
|
|
|
111
117
|
# Whether `method_def` declares its return type inferred rather than contracted (issue #823).
|
|
@@ -131,6 +137,27 @@ module Rigor
|
|
|
131
137
|
annotations.any? { |annotation| annotation.string.to_s.strip == INFERRED_RETURN_DIRECTIVE }
|
|
132
138
|
end
|
|
133
139
|
|
|
140
|
+
# Whether `method_def` declares its ENTIRE signature inferred — every parameter and the return alike
|
|
141
|
+
# — rather than only its return (issue #991).
|
|
142
|
+
#
|
|
143
|
+
# `inferred_return?` alone cannot separate a fully bare `def` from one whose parameters ARE declared
|
|
144
|
+
# (`# @rbs num: Float`) and only its return defaulted: both trip the same return-only provenance,
|
|
145
|
+
# because no equivalent per-parameter survivor exists to tell them apart. This reads the plugin's
|
|
146
|
+
# narrower, member-level fact instead: true only when nothing at all was asserted about the member.
|
|
147
|
+
# `call.wrong-arity` declines on this rather than on {inferred_return?}, since a parameter the author
|
|
148
|
+
# DID annotate is an assertion about the parameter list, and the parameter list is exactly what arity
|
|
149
|
+
# is a question about (see the rule's own comment).
|
|
150
|
+
#
|
|
151
|
+
# @param method_def — an `RBS::Definition::Method`, or anything else answering `#annotations`.
|
|
152
|
+
def inferred_signature?(method_def)
|
|
153
|
+
return false if method_def.nil?
|
|
154
|
+
|
|
155
|
+
annotations = method_def.annotations
|
|
156
|
+
return false if annotations.nil? || annotations.empty?
|
|
157
|
+
|
|
158
|
+
annotations.any? { |annotation| annotation.string.to_s.strip == INFERRED_SIGNATURE_DIRECTIVE }
|
|
159
|
+
end
|
|
160
|
+
|
|
134
161
|
# Reads RBS::Extended predicate effects off `RBS::Definition::Method#annotations`. Returns the effects in
|
|
135
162
|
# source order; duplicates and unrecognised `rigor:v1:` directives are dropped. Returns an empty array (NEVER
|
|
136
163
|
# `nil`) for a method with no recognised annotations so callers can iterate unconditionally.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# {Reflection}'s constant-ancestor walk ([#354](https://github.com/rigortype/rigor/issues/354)), split out for
|
|
5
|
+
# the reader rather than for the loader: the whole-spelling ladder's step 2 (`reflection.rb`) and the
|
|
6
|
+
# segment-wise path walk (`reflection/constant_path.rb`) both ask which project classes and modules a
|
|
7
|
+
# name's owner inherits constants from, and this file answers that once for both. Reopening the module
|
|
8
|
+
# keeps the walk behind the facade its callers already read.
|
|
9
|
+
module Reflection
|
|
10
|
+
# #354 — thread-local slot for the per-run ancestor-scope memo. See {.ancestor_constant_scopes}.
|
|
11
|
+
ANCESTOR_SCOPES_KEY = :__rigor_ancestor_constant_scopes__
|
|
12
|
+
private_constant :ANCESTOR_SCOPES_KEY
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# #354 — the project classes and modules whose own constants `class_name` inherits, in Ruby's
|
|
17
|
+
# ancestor order: included / prepended modules before the superclass (Ruby places mixins nearer),
|
|
18
|
+
# transitively, breadth-first. `class_name` itself is excluded — step 1 already covered it.
|
|
19
|
+
#
|
|
20
|
+
# Only PROJECT ancestors appear. `Scope#superclass_of` / `#includes_of` carry as-written names
|
|
21
|
+
# from the discovery pre-pass, and an as-written name that resolves to no discovered class or
|
|
22
|
+
# module is dropped — so a `class Foo < ActiveRecord::Base` contributes nothing and a constant
|
|
23
|
+
# owned by an RBS-known ancestor still resolves only if the bare name reaches it at step 3. That
|
|
24
|
+
# gap is deliberate for this slice: widening to the RBS ancestor graph is a separate question
|
|
25
|
+
# with its own FP surface.
|
|
26
|
+
#
|
|
27
|
+
# Memoised per run because step 2 runs on every constant reference whose lexical candidates all
|
|
28
|
+
# miss — which is the common case for a core-class reference (`String` inside `class Foo`). The
|
|
29
|
+
# bucket keys on the identity of the runner-seeded run-generation token (ADR-84 WD2), falling
|
|
30
|
+
# back to the per-file discovery table for runner-less scopes, so a re-run in one process (LSP,
|
|
31
|
+
# ADR-62 warm loop) cannot hit stale entries.
|
|
32
|
+
def ancestor_constant_scopes(class_name, scope)
|
|
33
|
+
# ADR-46: `superclass_of` / `includes_of` record a cross-file class dependency per consumer
|
|
34
|
+
# file, and the memo is run-scoped rather than file-scoped — a hit would skip the recording and
|
|
35
|
+
# under-record the edge for every later file. Recording runs are rare (incremental only), so
|
|
36
|
+
# they simply bypass the memo rather than complicate its key.
|
|
37
|
+
return compute_ancestor_constant_scopes(class_name, scope) if Analysis::DependencyRecorder.active?
|
|
38
|
+
|
|
39
|
+
generation = scope.run_generation || scope.discovered_superclasses
|
|
40
|
+
slot = Thread.current[ANCESTOR_SCOPES_KEY]
|
|
41
|
+
unless slot && slot[0].equal?(generation)
|
|
42
|
+
slot = [generation, {}]
|
|
43
|
+
Thread.current[ANCESTOR_SCOPES_KEY] = slot
|
|
44
|
+
end
|
|
45
|
+
bucket = slot[1]
|
|
46
|
+
bucket.fetch(class_name) { bucket[class_name] = compute_ancestor_constant_scopes(class_name, scope) }
|
|
47
|
+
end
|
|
48
|
+
private_class_method :ancestor_constant_scopes
|
|
49
|
+
|
|
50
|
+
def compute_ancestor_constant_scopes(class_name, scope)
|
|
51
|
+
queue = [class_name]
|
|
52
|
+
seen = { class_name => true }
|
|
53
|
+
out = []
|
|
54
|
+
until queue.empty?
|
|
55
|
+
current = queue.shift
|
|
56
|
+
# Mixins first, then the superclass — Ruby's ancestor order.
|
|
57
|
+
scope.includes_of(current).each do |raw|
|
|
58
|
+
resolved = resolve_ancestor_name(current, raw, scope)
|
|
59
|
+
next if resolved.nil? || seen[resolved]
|
|
60
|
+
|
|
61
|
+
seen[resolved] = true
|
|
62
|
+
out << resolved
|
|
63
|
+
queue << resolved
|
|
64
|
+
end
|
|
65
|
+
raw_super = scope.superclass_of(current)
|
|
66
|
+
next if raw_super.nil?
|
|
67
|
+
|
|
68
|
+
resolved_super = resolve_ancestor_name(current, raw_super, scope)
|
|
69
|
+
next if resolved_super.nil? || seen[resolved_super]
|
|
70
|
+
|
|
71
|
+
seen[resolved_super] = true
|
|
72
|
+
out << resolved_super
|
|
73
|
+
queue << resolved_super
|
|
74
|
+
end
|
|
75
|
+
out.freeze
|
|
76
|
+
end
|
|
77
|
+
private_class_method :compute_ancestor_constant_scopes
|
|
78
|
+
|
|
79
|
+
# Resolves an ancestor name AS WRITTEN (`"Base"`, or a qualified `"A::B"`) against the nesting in
|
|
80
|
+
# force where the subclass's header is written — `Scope#ancestor_name_candidates`, the single
|
|
81
|
+
# owner of that order, which `Scope#enqueue_ancestors` reads for method lookup and the
|
|
82
|
+
# override-visibility rule reads for its own walk. Returns nil when no candidate names a
|
|
83
|
+
# discovered project class or module.
|
|
84
|
+
def resolve_ancestor_name(subclass_qualified, raw, scope)
|
|
85
|
+
scope.ancestor_name_candidates(subclass_qualified, raw)
|
|
86
|
+
.find { |candidate| known_project_namespace?(candidate, scope) }
|
|
87
|
+
end
|
|
88
|
+
private_class_method :resolve_ancestor_name
|
|
89
|
+
|
|
90
|
+
def known_project_namespace?(name, scope)
|
|
91
|
+
scope.discovered_superclasses.key?(name) ||
|
|
92
|
+
scope.discovered_includes.key?(name) ||
|
|
93
|
+
scope.discovered_classes.key?(name)
|
|
94
|
+
end
|
|
95
|
+
private_class_method :known_project_namespace?
|
|
96
|
+
end
|
|
97
|
+
end
|