rigortype 0.3.9 → 0.4.1
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 +5 -4
- 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/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- 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 +427 -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 +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- 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 +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- 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 +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- 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 +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- 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/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- 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 +49 -37
- 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/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- 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 +88 -26
- 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 +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +3 -2
- 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/definer_resolution.rb +266 -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 +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -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 +488 -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 +179 -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 +901 -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/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -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 +674 -124
- 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 +13 -11
- 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/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- 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 +196 -13
- 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/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- 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/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- 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 +580 -47
- 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 +112 -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-ffi/lib/rigor/plugin/ffi.rb +10 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.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 +14 -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 +2 -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 +177 -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 +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- 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 +132 -1
|
@@ -43,10 +43,39 @@ module Rigor
|
|
|
43
43
|
# - `#cache_descriptor` — flushes the accumulated entries into a fresh {Cache::Descriptor} for the
|
|
44
44
|
# contribution that built it. URL fetches contribute `ConfigEntry` rows keyed `"url:#{url}"` with the
|
|
45
45
|
# response body's SHA-256 so contributions invalidate when the remote document changes.
|
|
46
|
+
# - `#replay(descriptor)` — records a {Cache::Descriptor}'s rows as though this boundary had observed
|
|
47
|
+
# them (#1558). A producer served from its own cache ran no block and read nothing, so
|
|
48
|
+
# {Plugin::Base#cache_for} replays the served entry's stored dependency rows here; the run-result
|
|
49
|
+
# descriptor and any enclosing producer's descriptor then carry the served value's inputs.
|
|
46
50
|
class IoBoundary
|
|
47
51
|
URL_TIMEOUT_SECONDS = 10
|
|
48
52
|
URL_MAX_BYTES = 10 * 1024 * 1024
|
|
49
53
|
|
|
54
|
+
ATTRIBUTION_KEY = :rigor_io_boundary_attribution
|
|
55
|
+
private_constant :ATTRIBUTION_KEY
|
|
56
|
+
|
|
57
|
+
# ADR-45 WD2 (#1507) — runs the block with `sink` (an Array) receiving every file and listing row ANY
|
|
58
|
+
# boundary records on this thread meanwhile, in addition to the boundary's own table, and returns the
|
|
59
|
+
# block's value. `Analysis::Runner` wraps each file's analysis in one when it records dependencies, so the
|
|
60
|
+
# `--incremental` session can keep the reads one file's analysis made beside that file's cached answer: a
|
|
61
|
+
# later run that serves the answer without re-analysing the file still has to validate what it read.
|
|
62
|
+
#
|
|
63
|
+
# A thread variable rather than a fiber-local, so a read a plugin makes from inside an `Enumerator` still
|
|
64
|
+
# lands in the sink. The boundary's own table is unchanged by it, including its first-existence-row-stands
|
|
65
|
+
# rule: the sink records what each read observed. Only reads are credited: a value a plugin memoised reaches
|
|
66
|
+
# later files without one, so the first file to trigger the read is the only one credited with it.
|
|
67
|
+
def self.attributing(sink)
|
|
68
|
+
previous = Thread.current.thread_variable_get(ATTRIBUTION_KEY)
|
|
69
|
+
Thread.current.thread_variable_set(ATTRIBUTION_KEY, sink)
|
|
70
|
+
yield
|
|
71
|
+
ensure
|
|
72
|
+
Thread.current.thread_variable_set(ATTRIBUTION_KEY, previous)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.attribute(entry)
|
|
76
|
+
Thread.current.thread_variable_get(ATTRIBUTION_KEY)&.push(entry)
|
|
77
|
+
end
|
|
78
|
+
|
|
50
79
|
attr_reader :policy, :plugin_id
|
|
51
80
|
|
|
52
81
|
def initialize(policy:, plugin_id:, http_client: DefaultHttpClient.new)
|
|
@@ -61,7 +90,7 @@ module Rigor
|
|
|
61
90
|
@first_refused_path = nil
|
|
62
91
|
end
|
|
63
92
|
|
|
64
|
-
# Issue #959 — `TrustPolicy#allow_read?` refuses a path purely by `File.
|
|
93
|
+
# Issue #959 — `TrustPolicy#allow_read?` refuses a path purely by `File.absolute_path` comparison
|
|
65
94
|
# (ADR-2's documented bound; no `File.realpath`), so a project rooted under a symlink (macOS'
|
|
66
95
|
# `/tmp` → `/private/tmp` alias is the common case) can have every plugin read refused with no
|
|
67
96
|
# visible cause: the boundary answered "not readable" and the plugin swallowed or surfaced only its
|
|
@@ -96,7 +125,7 @@ module Rigor
|
|
|
96
125
|
# outcome is recorded: a path that exists but cannot be read (`EISDIR`, `EACCES`) is a failure the
|
|
97
126
|
# plugin reports, not a probe of absence, and records nothing — as before.
|
|
98
127
|
def read_file(path)
|
|
99
|
-
absolute = File.
|
|
128
|
+
absolute = File.absolute_path(path.to_s)
|
|
100
129
|
unless @policy.allow_read?(absolute)
|
|
101
130
|
record_refusal(absolute)
|
|
102
131
|
raise AccessDeniedError.new(
|
|
@@ -159,7 +188,7 @@ module Rigor
|
|
|
159
188
|
# @param path — project directory; relative paths expand against the working directory
|
|
160
189
|
# @return the absolute paths directly under `path` in `Dir.glob` order; empty when not a directory
|
|
161
190
|
def list_directory(path)
|
|
162
|
-
absolute = File.
|
|
191
|
+
absolute = File.absolute_path(path.to_s)
|
|
163
192
|
entries = Dir.glob(File.join(absolute, "*"))
|
|
164
193
|
unless @policy.allow_read?(absolute)
|
|
165
194
|
record_refusal(absolute)
|
|
@@ -202,8 +231,53 @@ module Rigor
|
|
|
202
231
|
Cache::Descriptor.new(files: files, configs: configs, globs: globs)
|
|
203
232
|
end
|
|
204
233
|
|
|
234
|
+
# #1558 — records every row of `descriptor` as though this boundary had observed it. The caller is
|
|
235
|
+
# {Plugin::Base#cache_for}: a producer served from its own cache ran no block, so without the replay
|
|
236
|
+
# the value's inputs reached neither the ADR-45 run-result descriptor nor the dependency descriptor of
|
|
237
|
+
# a producer computing around it, and both went on validating fresh after those inputs changed.
|
|
238
|
+
#
|
|
239
|
+
# The rows are the ones a fresh hit just validated, so they describe the current filesystem, and they
|
|
240
|
+
# are merged by the precedence the live recorders use. A content row (`:stat` / `:digest` / `:mtime`)
|
|
241
|
+
# replaces an existence row for its path, as {#read_file} does after a probe. Any other collision
|
|
242
|
+
# keeps the row already here: an existence row never displaces one, as in {#probe}, and between two
|
|
243
|
+
# content rows or two glob rows for one slot the live row carries the newer stat tuple, which keeps
|
|
244
|
+
# the next warm run on its stat fast path. The tables are keyed by path, glob slot and URL key, so
|
|
245
|
+
# replaying the same descriptor twice adds nothing. Row order reaches no cache key, because
|
|
246
|
+
# {Cache::Descriptor#to_canonical_hash} sorts every slot.
|
|
247
|
+
#
|
|
248
|
+
# The policy is not consulted: the rows were recorded under it when the entry was written, and replay
|
|
249
|
+
# reads no file. A {Cache::Descriptor::GlobEntry} keeps its `mode`, which is part of its slot key.
|
|
250
|
+
#
|
|
251
|
+
# ADR-45 WD2 (#1507) — every replayed row is also handed to the {.attributing} sink, the ones a held row
|
|
252
|
+
# wins over included, exactly as the live recorders hand over what each read observed: a producer first
|
|
253
|
+
# asked while a file is analysed (rigor-actionpack's `:controller_index`, rigor-rails-i18n's
|
|
254
|
+
# `:locale_index`) serves that file its inputs, and the `--incremental` slot must credit them to it. On a
|
|
255
|
+
# miss the replayed descriptor is the whole boundary's ({Plugin::Base#producer_dependency_descriptor}), so
|
|
256
|
+
# the file is also credited with rows other reads recorded; that over-credits, which only ever makes the
|
|
257
|
+
# slot decline sooner.
|
|
258
|
+
#
|
|
259
|
+
# @param descriptor — a dependency descriptor; only its `files`, `configs` and `globs` slots are read,
|
|
260
|
+
# the only slots a boundary records
|
|
261
|
+
def replay(descriptor)
|
|
262
|
+
@mutex.synchronize do
|
|
263
|
+
descriptor.files.each { |entry| replay_file_entry(entry) }
|
|
264
|
+
descriptor.configs.each { |entry| @config_entries[entry.key] ||= entry }
|
|
265
|
+
descriptor.globs.each { |entry| @glob_entries[entry.slot_key] ||= entry }
|
|
266
|
+
end
|
|
267
|
+
(descriptor.files + descriptor.globs).each { |entry| self.class.attribute(entry) }
|
|
268
|
+
nil
|
|
269
|
+
end
|
|
270
|
+
|
|
205
271
|
private
|
|
206
272
|
|
|
273
|
+
# The {#replay} precedence for one file row; the caller holds `@mutex`.
|
|
274
|
+
def replay_file_entry(entry)
|
|
275
|
+
current = @file_entries[entry.path]
|
|
276
|
+
return if current && !(current.comparator == :exists && entry.comparator != :exists)
|
|
277
|
+
|
|
278
|
+
@file_entries[entry.path] = entry
|
|
279
|
+
end
|
|
280
|
+
|
|
207
281
|
# ADR-45 WD1b (#613) — the shared body of {#file?} / {#directory?}.
|
|
208
282
|
#
|
|
209
283
|
# The probe answers TRUTHFULLY for every path, in or out of the trusted-read scope, and never raises:
|
|
@@ -222,7 +296,7 @@ module Rigor
|
|
|
222
296
|
# reverse) → NOTHING, the same bound `#read_file` pins for `EISDIR`: the probe observed neither a
|
|
223
297
|
# usable file nor an absence, and either row would misdescribe what the plugin saw.
|
|
224
298
|
def probe(path)
|
|
225
|
-
absolute = File.
|
|
299
|
+
absolute = File.absolute_path(path.to_s)
|
|
226
300
|
answer = yield(absolute)
|
|
227
301
|
unless @policy.allow_read?(absolute)
|
|
228
302
|
record_refusal(absolute)
|
|
@@ -247,6 +321,7 @@ module Rigor
|
|
|
247
321
|
# A content row replaces an earlier existence row for the same path outright: the file appeared and
|
|
248
322
|
# its bytes were consumed, and a content row validates existence as well as content.
|
|
249
323
|
@mutex.synchronize { @file_entries[path] = entry }
|
|
324
|
+
self.class.attribute(entry)
|
|
250
325
|
end
|
|
251
326
|
|
|
252
327
|
# ADR-45 WD1 (#577) — the absence row for a probed-but-missing path. A content row already recorded
|
|
@@ -263,6 +338,7 @@ module Rigor
|
|
|
263
338
|
def record_absence_entry(path)
|
|
264
339
|
entry = Cache::Descriptor::FileEntry.absent(path: path)
|
|
265
340
|
@mutex.synchronize { @file_entries[path] ||= entry }
|
|
341
|
+
self.class.attribute(entry)
|
|
266
342
|
end
|
|
267
343
|
|
|
268
344
|
# ADR-45 WD1b (#613) — the presence row: fresh while the probed path exists, stale once it is gone.
|
|
@@ -273,6 +349,7 @@ module Rigor
|
|
|
273
349
|
def record_presence_entry(path)
|
|
274
350
|
entry = Cache::Descriptor::FileEntry.present(path: path)
|
|
275
351
|
@mutex.synchronize { @file_entries[path] ||= entry }
|
|
352
|
+
self.class.attribute(entry)
|
|
276
353
|
end
|
|
277
354
|
|
|
278
355
|
# ADR-45 WD1c (#629) — the listing row for {#list_directory}, deduplicated per (root, pattern) slot.
|
|
@@ -281,6 +358,7 @@ module Rigor
|
|
|
281
358
|
def record_glob_entry(root, pattern)
|
|
282
359
|
entry = Cache::Descriptor::GlobEntry.compute(root: root, pattern: pattern)
|
|
283
360
|
@mutex.synchronize { @glob_entries[entry.slot_key] = entry }
|
|
361
|
+
self.class.attribute(entry)
|
|
284
362
|
end
|
|
285
363
|
|
|
286
364
|
# Issue #959 — records that `path` fell outside every {TrustPolicy} read root, for {#refusal_summary}.
|
|
@@ -47,7 +47,7 @@ module Rigor
|
|
|
47
47
|
# build resolves the same root and skips the write — #414), and reading an unshareable value out of
|
|
48
48
|
# a module ivar from a non-main Ractor is the same `Ractor::IsolationError` as writing one.
|
|
49
49
|
def target_bundle_root=(root)
|
|
50
|
-
@target_bundle_root = root.nil? ? nil : File.
|
|
50
|
+
@target_bundle_root = root.nil? ? nil : File.absolute_path(root.to_s).freeze
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# Requires `feature`, falling back to the analyzed project's bundler install tree. The fallback
|
data/lib/rigor/plugin/loader.rb
CHANGED
|
@@ -35,13 +35,14 @@ module Rigor
|
|
|
35
35
|
suffix = "/#{gem_name}.rb"
|
|
36
36
|
$LOADED_FEATURES.rfind { |feature| feature.end_with?(suffix) }
|
|
37
37
|
end
|
|
38
|
+
Ractor.make_shareable(FEATURE_RESOLVER)
|
|
38
39
|
|
|
39
40
|
# #194 slice 2 (ADR-93 WD5) — the engine's own root, anchored from THIS file's location: the loader
|
|
40
41
|
# lives at `<root>/lib/rigor/plugin/loader.rb`, so three levels up is the engine root. It resolves
|
|
41
42
|
# identically in a git checkout and inside an installed `rigortype` gem, because the gem packages the
|
|
42
43
|
# `plugins/` tree at the same relative path — which is exactly what makes it a trustworthy anchor for
|
|
43
44
|
# the engine's own bundled plugin copies.
|
|
44
|
-
ENGINE_ROOT = File.expand_path("../../..", __dir__)
|
|
45
|
+
ENGINE_ROOT = File.expand_path("../../..", __dir__).freeze
|
|
45
46
|
|
|
46
47
|
# @param requirer — takes a gem name OR an absolute file path (#194 slice 2 — a bundled plugin is
|
|
47
48
|
# required by its {.bundled_plugin_path}) and returns truthy on successful require. Defaulted to
|
|
@@ -36,9 +36,14 @@ module Rigor
|
|
|
36
36
|
# - `method_names` — Array of Symbol method names. A call shape `<receiver_subclass>.get('/path') { ... }`
|
|
37
37
|
# matches when `:get` is in this list. (Named `verbs:` before ADR-60 WD2 normalised the macro
|
|
38
38
|
# value-object vocabulary.)
|
|
39
|
-
# - `self_type` —
|
|
40
|
-
#
|
|
41
|
-
#
|
|
39
|
+
# - `self_type` — the `self`-binding the substrate applies inside the block. `:receiver_instance`
|
|
40
|
+
# (the default) types `self` as an instance of the receiver class — the Sinatra contract. A String
|
|
41
|
+
# binds `self` to a *named* class instead of the receiver: `"Grape::Endpoint"` binds
|
|
42
|
+
# `Nominal[Grape::Endpoint]` (the block is `instance_eval`'d on an instance of that class, e.g. a
|
|
43
|
+
# route body on `Grape::Endpoint`), and `"singleton(Grape::API::Instance)"` binds
|
|
44
|
+
# `Singleton[Grape::API::Instance]` (the block is `instance_eval`'d on that *class object*, e.g.
|
|
45
|
+
# a `namespace` body on `Grape::API::Instance`). Reserved Symbol names (`:receiver_singleton`,
|
|
46
|
+
# `:dsl_recorder`) remain unaccepted.
|
|
42
47
|
#
|
|
43
48
|
# ## Ractor-shareability
|
|
44
49
|
#
|
|
@@ -48,6 +53,13 @@ module Rigor
|
|
|
48
53
|
SELF_TYPE_RECEIVER_INSTANCE = :receiver_instance
|
|
49
54
|
VALID_SELF_TYPES = [SELF_TYPE_RECEIVER_INSTANCE].freeze
|
|
50
55
|
|
|
56
|
+
CLASS_NAME_PATTERN = /[A-Z]\w*(?:::[A-Z]\w*)*/
|
|
57
|
+
# `self_type: "Foo::Bar"` — the block is `instance_eval`'d on an instance of `Foo::Bar`.
|
|
58
|
+
NAMED_SELF_TYPE_PATTERN = /\A#{CLASS_NAME_PATTERN}\z/
|
|
59
|
+
# `self_type: "singleton(Foo::Bar)"` — the block is `instance_eval`'d on the `Foo::Bar` class
|
|
60
|
+
# object itself (Grape's `namespace`/`route_param` bodies run on `Grape::API::Instance`).
|
|
61
|
+
SINGLETON_SELF_TYPE_PATTERN = /\Asingleton\((#{CLASS_NAME_PATTERN})\)\z/
|
|
62
|
+
|
|
51
63
|
attr_reader :receiver_constraint, :method_names, :self_type
|
|
52
64
|
|
|
53
65
|
def initialize(receiver_constraint:, method_names:, self_type: SELF_TYPE_RECEIVER_INSTANCE)
|
|
@@ -57,7 +69,7 @@ module Rigor
|
|
|
57
69
|
|
|
58
70
|
@receiver_constraint = receiver_constraint.dup.freeze
|
|
59
71
|
@method_names = method_names.map(&:to_sym).freeze
|
|
60
|
-
@self_type = self_type
|
|
72
|
+
@self_type = self_type.is_a?(String) ? self_type.dup.freeze : self_type
|
|
61
73
|
freeze
|
|
62
74
|
end
|
|
63
75
|
|
|
@@ -81,6 +93,31 @@ module Rigor
|
|
|
81
93
|
[receiver_constraint, method_names, self_type].hash
|
|
82
94
|
end
|
|
83
95
|
|
|
96
|
+
# The class name a String `self_type` binds `self` to — `"Foo::Bar"` for both the plain and the
|
|
97
|
+
# `singleton(Foo::Bar)` form — or `nil` for `:receiver_instance`.
|
|
98
|
+
def self_type_name
|
|
99
|
+
return nil unless self_type.is_a?(String)
|
|
100
|
+
|
|
101
|
+
self_type[SINGLETON_SELF_TYPE_PATTERN, 1] || self_type
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Whether a String `self_type` binds the *class object* (`"singleton(Foo::Bar)"`) rather than an
|
|
105
|
+
# instance (`"Foo::Bar"`). Nominal-receiver call sites only match instance-binding entries —
|
|
106
|
+
# a `singleton(...)` binding exists solely for `instance_eval`-on-class contracts, which always
|
|
107
|
+
# surface as `Singleton[X]` receivers.
|
|
108
|
+
def singleton_binding?
|
|
109
|
+
self_type.is_a?(String) && self_type.match?(SINGLETON_SELF_TYPE_PATTERN)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Whether `self_type` names a class whose *instance* the block is `instance_eval`'d on
|
|
113
|
+
# (`"Foo::Bar"`). These entries are the only ones that also match `Nominal[Y]` receivers: inside
|
|
114
|
+
# an already-narrowed `instance_eval` body (`self : Nominal[ParamsScope]`), a nested call on
|
|
115
|
+
# `self` re-enters the same context. `:receiver_instance` keeps its original Singleton-only
|
|
116
|
+
# contract — a nominal receiver calling a class-level verb is a different (unmodelled) shape.
|
|
117
|
+
def named_instance_binding?
|
|
118
|
+
self_type.is_a?(String) && !singleton_binding?
|
|
119
|
+
end
|
|
120
|
+
|
|
84
121
|
private
|
|
85
122
|
|
|
86
123
|
def validate_receiver_constraint!(value)
|
|
@@ -108,10 +145,12 @@ module Rigor
|
|
|
108
145
|
|
|
109
146
|
def validate_self_type!(self_type)
|
|
110
147
|
return if VALID_SELF_TYPES.include?(self_type)
|
|
148
|
+
return if self_type.is_a?(String) &&
|
|
149
|
+
(self_type.match?(NAMED_SELF_TYPE_PATTERN) || self_type.match?(SINGLETON_SELF_TYPE_PATTERN))
|
|
111
150
|
|
|
112
151
|
raise ArgumentError,
|
|
113
|
-
"Plugin::Macro::BlockAsMethod#self_type must be one of #{VALID_SELF_TYPES.inspect}
|
|
114
|
-
"got #{self_type.inspect}"
|
|
152
|
+
"Plugin::Macro::BlockAsMethod#self_type must be one of #{VALID_SELF_TYPES.inspect} " \
|
|
153
|
+
"or a class-name String ('Foo::Bar' / 'singleton(Foo::Bar)'), got #{self_type.inspect}"
|
|
115
154
|
end
|
|
116
155
|
end
|
|
117
156
|
end
|
|
@@ -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
|
|