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
|
@@ -32,9 +32,19 @@ module Rigor
|
|
|
32
32
|
# `docs/internal-spec/inference-engine.md` § "Declaration-sourced provenance mark (ADR-58)"; keep this
|
|
33
33
|
# comment subordinate to it.
|
|
34
34
|
#
|
|
35
|
-
# So `marked?` matches exactly the
|
|
36
|
-
#
|
|
35
|
+
# So `marked?` matches exactly the node shapes the scope actually models and invents no propagation of its
|
|
36
|
+
# own; anything flow-live keeps firing.
|
|
37
|
+
#
|
|
38
|
+
# Issue #1362 adds a third carrier, a global still on its program-global seed, which joins the non-nil part of
|
|
39
|
+
# the declared type of a global Ruby's own signatures declare with the file's writes (ADR-117 Decision point
|
|
40
|
+
# 2: never warn that a value might deviate from the idiom). Its declared members are the declaration-sourced
|
|
41
|
+
# part. The consumers judge such a value by the file's writes ({.written_type}, {.withholds_nil?}), and a
|
|
42
|
+
# value that mixes it with something else without the declared members the file never writes
|
|
43
|
+
# ({.declared_only_rejection?}), so a report the writes alone earn still fires.
|
|
37
44
|
module DeclarationSourcedGuard
|
|
45
|
+
NO_GLOBALS = [].freeze
|
|
46
|
+
private_constant :NO_GLOBALS
|
|
47
|
+
|
|
38
48
|
module_function
|
|
39
49
|
|
|
40
50
|
# True when `node` is a direct read of a binding whose optionality is purely declaration-sourced. Any
|
|
@@ -44,9 +54,164 @@ module Rigor
|
|
|
44
54
|
case node
|
|
45
55
|
when Prism::InstanceVariableReadNode then scope.declaration_sourced?(:ivar, node.name)
|
|
46
56
|
when Prism::LocalVariableReadNode then scope.declaration_sourced?(:local, node.name)
|
|
57
|
+
when Prism::GlobalVariableReadNode then scope.declaration_sourced?(:global, node.name)
|
|
47
58
|
else false
|
|
48
59
|
end
|
|
49
60
|
end
|
|
61
|
+
|
|
62
|
+
# The globals whose declared seeds `node` still reads, empty for none: a read of a marked global, or of a
|
|
63
|
+
# local copied from one or more (`sep = $/`, `Scope#declaration_sourced_global_copies`), bare or in
|
|
64
|
+
# parentheses (`($stdout)`).
|
|
65
|
+
def global_sources(node, scope)
|
|
66
|
+
node = unparenthesised(node)
|
|
67
|
+
case node
|
|
68
|
+
when Prism::GlobalVariableReadNode
|
|
69
|
+
scope.declaration_sourced?(:global, node.name) ? [node.name] : NO_GLOBALS
|
|
70
|
+
when Prism::LocalVariableReadNode
|
|
71
|
+
scope.declaration_sourced_global_copies(node.name)
|
|
72
|
+
else NO_GLOBALS
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The globals a local written from `value` copies the declared seeds of, empty for none: a read of a marked
|
|
77
|
+
# global, bare or in parentheses (`sep = ($/)`), or a bare read of a local that copies some (`s = sep`).
|
|
78
|
+
# Unlike an ivar copy's, this mark follows bare local-to-local copies; a method result (`$/.dup`) or a
|
|
79
|
+
# container element (`[$/].each { |s| … }`) carries none, as ADR-58's one-hop boundary has it.
|
|
80
|
+
def copied_globals(value, scope)
|
|
81
|
+
return scope.declaration_sourced_global_copies(value.name) if value.is_a?(Prism::LocalVariableReadNode)
|
|
82
|
+
|
|
83
|
+
read = unparenthesised(value)
|
|
84
|
+
return NO_GLOBALS unless read.is_a?(Prism::GlobalVariableReadNode) &&
|
|
85
|
+
scope.declaration_sourced?(:global, read.name)
|
|
86
|
+
|
|
87
|
+
[read.name]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# `node` with its enclosing parentheses taken off while they hold a single expression.
|
|
91
|
+
def unparenthesised(node)
|
|
92
|
+
while node.is_a?(Prism::ParenthesesNode)
|
|
93
|
+
body = node.body
|
|
94
|
+
break unless body.is_a?(Prism::StatementsNode) && body.body.size == 1
|
|
95
|
+
|
|
96
|
+
node = body.body.first
|
|
97
|
+
end
|
|
98
|
+
node
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The union of the file's own writes to the globals `node` reads the seeds of ({.global_sources}), or nil.
|
|
102
|
+
def written_type(node, scope)
|
|
103
|
+
sources = global_sources(node, scope)
|
|
104
|
+
return nil if sources.empty?
|
|
105
|
+
|
|
106
|
+
written = sources.map { |source| scope.program_globals[source] }
|
|
107
|
+
written.include?(nil) ? nil : Type::Combinator.union(*written)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# True when a `nil` in `node`'s type is not diagnostic fuel: for a global's seed or its copy, when the file
|
|
111
|
+
# never writes `nil` to the global, so the declaration alone contributes it; otherwise when `node` carries
|
|
112
|
+
# the ADR-58 mark.
|
|
113
|
+
def withholds_nil?(node, scope)
|
|
114
|
+
written = written_type(node, scope)
|
|
115
|
+
return marked?(node, scope) if written.nil?
|
|
116
|
+
|
|
117
|
+
!nil_bearing?(written)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Issue #1362 — true when every member of `type` that `expected` rejects is a declared member of a builtin
|
|
121
|
+
# global this file writes and joins ({#declared_only_members}), so the rejection rests on the join alone. The
|
|
122
|
+
# argument and return gates read it for a value that mixes such a global with something else
|
|
123
|
+
# (`c ? $stdout : StringIO.new`), which the ADR-58 mark does not follow. It is a type-level reading: it also
|
|
124
|
+
# withholds a rejection of such a member that comes from elsewhere in the file (`STDOUT` passed where a
|
|
125
|
+
# `StringIO` is required, in a file that writes `$stdout`).
|
|
126
|
+
#
|
|
127
|
+
# Otherwise the value with those members taken out of every union inside it ({#without_declared_only}) is
|
|
128
|
+
# handed to the block, which answers whether the caller's rule accepts it: the value as it was before the
|
|
129
|
+
# join, one level into a container it builds as well (`[$stdout]` or `{ io: $stdout }` passed where an
|
|
130
|
+
# `Array[StringIO]` is required), and with a `nil` the file writes judged as the rule judges a `nil`.
|
|
131
|
+
def declared_only_rejection?(type, expected, scope)
|
|
132
|
+
declared_only = declared_only_members(scope)
|
|
133
|
+
return false if declared_only.empty?
|
|
134
|
+
|
|
135
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
136
|
+
rejected = members.select { |member| Inference::Acceptance.accepts(expected, member, mode: :gradual).no? }
|
|
137
|
+
return false if rejected.empty?
|
|
138
|
+
return true if rejected.all? { |member| declared_only.include?(member) }
|
|
139
|
+
|
|
140
|
+
stripped = without_declared_only(type, declared_only)
|
|
141
|
+
!stripped.equal?(type) && yield(stripped)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# `type` with each member of `declared_only` taken out of every union in it, through tuples, hash shapes and
|
|
145
|
+
# generic arguments; a union with nothing else left keeps its members. Answers `type` itself when nothing
|
|
146
|
+
# was taken out.
|
|
147
|
+
def without_declared_only(type, declared_only)
|
|
148
|
+
case type
|
|
149
|
+
when Type::Union then union_without_declared_only(type, declared_only)
|
|
150
|
+
when Type::Tuple
|
|
151
|
+
elements = type.elements.map { |element| without_declared_only(element, declared_only) }
|
|
152
|
+
elements == type.elements ? type : Type::Combinator.tuple_of(*elements)
|
|
153
|
+
when Type::HashShape
|
|
154
|
+
pairs = type.pairs.transform_values { |value| without_declared_only(value, declared_only) }
|
|
155
|
+
pairs == type.pairs ? type : rebuild_hash_shape(type, pairs)
|
|
156
|
+
when Type::Nominal
|
|
157
|
+
args = type.type_args.map { |arg| without_declared_only(arg, declared_only) }
|
|
158
|
+
args == type.type_args ? type : Type::Combinator.nominal_of(type.class_name, type_args: args)
|
|
159
|
+
else type
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def union_without_declared_only(union, declared_only)
|
|
164
|
+
kept = union.members.reject { |member| declared_only.include?(member) }
|
|
165
|
+
kept = union.members if kept.empty?
|
|
166
|
+
rebuilt = kept.map { |member| without_declared_only(member, declared_only) }
|
|
167
|
+
rebuilt == union.members ? union : Type::Combinator.union(*rebuilt)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def rebuild_hash_shape(shape, pairs)
|
|
171
|
+
Type::Combinator.hash_shape_of(
|
|
172
|
+
pairs, required_keys: shape.required_keys, optional_keys: shape.optional_keys,
|
|
173
|
+
read_only_keys: shape.read_only_keys, extra_keys: shape.extra_keys
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# For each builtin global this file writes and joins, the non-nil members of its declared type that the file's
|
|
178
|
+
# writes to that global do not hold (`IO` after `$stdout = StringIO.new`, nothing after `$stderr = STDERR`),
|
|
179
|
+
# gathered over the globals. A declared literal also counts in its class form, which a method called on the
|
|
180
|
+
# joined union returns: `$VERBOSE.itself` reads `FalseClass | TrueClass` after `$VERBOSE = true`.
|
|
181
|
+
def declared_only_members(scope)
|
|
182
|
+
seeds = scope.discovery.program_global_seeds
|
|
183
|
+
return NO_GLOBALS if seeds.empty?
|
|
184
|
+
|
|
185
|
+
seeds.keys.flat_map do |name|
|
|
186
|
+
written = type_members(scope.program_globals[name])
|
|
187
|
+
type_members(scope.environment.global_for_name(name, builtin: true))
|
|
188
|
+
.reject { |member| nil_bearing?(member) }
|
|
189
|
+
.flat_map { |member| [member, *class_form(member)] }
|
|
190
|
+
.reject { |member| written.include?(member) }
|
|
191
|
+
end.uniq
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def class_form(member)
|
|
195
|
+
return NO_GLOBALS unless member.is_a?(Type::Constant)
|
|
196
|
+
|
|
197
|
+
[Type::Combinator.nominal_of(member.value.class.name)]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def type_members(type)
|
|
201
|
+
return NO_GLOBALS if type.nil?
|
|
202
|
+
|
|
203
|
+
type.is_a?(Type::Union) ? type.members : [type]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def nil_bearing?(type)
|
|
207
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
208
|
+
members.any? do |member|
|
|
209
|
+
(member.is_a?(Type::Constant) && member.value.nil?) ||
|
|
210
|
+
(member.is_a?(Type::Nominal) && member.class_name == "NilClass")
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
private_class_method :nil_bearing?, :unparenthesised, :declared_only_members, :type_members, :class_form,
|
|
214
|
+
:without_declared_only, :union_without_declared_only, :rebuild_hash_shape
|
|
50
215
|
end
|
|
51
216
|
end
|
|
52
217
|
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../inference/scope_indexer"
|
|
6
|
+
require_relative "../../source/constant_path"
|
|
7
|
+
require_relative "../../source/node_children"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Analysis
|
|
11
|
+
module CheckRules
|
|
12
|
+
# Issue #1120 — the two ways one file makes a method callable at some sites and not at others, which
|
|
13
|
+
# `call.undefined-method` has to ask the file's syntax about because no project-wide table can answer:
|
|
14
|
+
#
|
|
15
|
+
# - **Refinements.** `using M` activates M's refinements from the end of the call to the end of the body
|
|
16
|
+
# that holds it — the file's top level or a `class` / `module` body, including everything nested in it —
|
|
17
|
+
# and nowhere else, not even in another file. A `refine X do … end` block is active inside itself. What
|
|
18
|
+
# each module refines is the project-wide `Scope#discovered_refinements`; this answers whether any of
|
|
19
|
+
# those modules is in effect at a call site. A `using` whose argument is not a constant
|
|
20
|
+
# (`using Module.new { refine … }`) names no module, so every refinement counts as in effect throughout
|
|
21
|
+
# its file. A `using` inside a `def` raises in Ruby and activates nothing.
|
|
22
|
+
# - **Singleton defs on locals.** `def o.m` defines `m` on the one object `o` holds. The local's type
|
|
23
|
+
# is not changed; `o.m` is simply not reported within the scope the `def` is written in (the
|
|
24
|
+
# enclosing `def`, `class` / `module` body, or file — a block shares its enclosing scope's locals).
|
|
25
|
+
#
|
|
26
|
+
# Built lazily: the walk runs the first time a would-fire call asks, so a file with no such call pays
|
|
27
|
+
# one small object and nothing else.
|
|
28
|
+
class LexicalMethodSites
|
|
29
|
+
def initialize(root)
|
|
30
|
+
@root = root
|
|
31
|
+
@built = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Is a refinement from one of `modules` (refining-module names) in effect at `call_node`?
|
|
35
|
+
def refinement_active?(call_node, modules)
|
|
36
|
+
build
|
|
37
|
+
return true if @unresolved_using
|
|
38
|
+
|
|
39
|
+
offset = call_node.location.start_offset
|
|
40
|
+
return true if @refine_blocks.any? { |start, stop| offset >= start && offset < stop }
|
|
41
|
+
|
|
42
|
+
@usings.any? do |start, stop, candidates|
|
|
43
|
+
offset >= start && offset < stop && candidates.any? { |name| modules.include?(name) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Issue #1367 — is any refinement in effect at `node`: a `using` of any module, a `using` of a non-constant, or
|
|
48
|
+
# the inside of a `refine` block? The `global.*` stream check asks it where it cannot name the refining
|
|
49
|
+
# module (a `define_method` or `alias_method` inside `refine` records none).
|
|
50
|
+
def using_in_effect?(node)
|
|
51
|
+
build
|
|
52
|
+
return true if @unresolved_using
|
|
53
|
+
|
|
54
|
+
offset = node.location.start_offset
|
|
55
|
+
@refine_blocks.any? { |start, stop| offset >= start && offset < stop } ||
|
|
56
|
+
@usings.any? { |start, stop, _candidates| offset >= start && offset < stop }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Is `def_node` one of the defs a `refine X do … end` body defines on X? Such a def redefines X's method by
|
|
60
|
+
# design, so X's declared signature for the name is not its contract (issue #1120, maintainer ruling a′).
|
|
61
|
+
def refinement_def?(def_node)
|
|
62
|
+
build
|
|
63
|
+
@refinement_defs.include?(def_node.location.start_offset)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Does a `def <local>.<name>` for this call's local receiver and method name sit in the scope the call
|
|
67
|
+
# is in?
|
|
68
|
+
def singleton_local_def?(call_node)
|
|
69
|
+
receiver = call_node.receiver
|
|
70
|
+
return false unless receiver.is_a?(Prism::LocalVariableReadNode)
|
|
71
|
+
|
|
72
|
+
build
|
|
73
|
+
offset = call_node.location.start_offset
|
|
74
|
+
@singleton_defs.any? do |local, method_name, start, stop|
|
|
75
|
+
local == receiver.name && method_name == call_node.name && offset >= start && offset < stop &&
|
|
76
|
+
!nested_scope_holds?(offset, start, stop)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Is `offset` inside a local scope nested in the one `[start, stop)` spans? A `def` in the file's top level
|
|
83
|
+
# does not see the top level's `o`.
|
|
84
|
+
def nested_scope_holds?(offset, start, stop)
|
|
85
|
+
@scopes.any? do |inner_start, inner_stop|
|
|
86
|
+
(inner_start > start || inner_stop < stop) && inner_start >= start && inner_stop <= stop &&
|
|
87
|
+
offset >= inner_start && offset < inner_stop
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build
|
|
92
|
+
return if @built
|
|
93
|
+
|
|
94
|
+
@built = true
|
|
95
|
+
@usings = []
|
|
96
|
+
@refine_blocks = []
|
|
97
|
+
@singleton_defs = []
|
|
98
|
+
@scopes = []
|
|
99
|
+
@refinement_defs = Set.new
|
|
100
|
+
@unresolved_using = false
|
|
101
|
+
return if @root.nil?
|
|
102
|
+
|
|
103
|
+
location = @root.location
|
|
104
|
+
span = [location.start_offset, location.end_offset]
|
|
105
|
+
walk(@root, [], span, span, false)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# `body` is the `[start, end]` of the body a `using` here stays in effect to the end of; `locals` is the
|
|
109
|
+
# span of the scope a local written here belongs to.
|
|
110
|
+
def walk(node, prefix, body, locals, in_def)
|
|
111
|
+
case node
|
|
112
|
+
when Prism::ClassNode, Prism::ModuleNode
|
|
113
|
+
span = scope_span(node)
|
|
114
|
+
inner = Source::ConstantPath.declaration_prefix(prefix, node.constant_path) || prefix
|
|
115
|
+
return walk_children(node.body, inner, span, span, false)
|
|
116
|
+
when Prism::SingletonClassNode
|
|
117
|
+
walk(node.expression, prefix, body, locals, in_def)
|
|
118
|
+
span = scope_span(node)
|
|
119
|
+
return walk_children(node.body, prefix, span, span, false)
|
|
120
|
+
when Prism::DefNode
|
|
121
|
+
record_singleton_def(node, locals)
|
|
122
|
+
return walk_children(node.body, prefix, body, scope_span(node), true)
|
|
123
|
+
when Prism::CallNode
|
|
124
|
+
record_call(node, prefix, body, in_def)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
walk_children(node, prefix, body, locals, in_def)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def walk_children(node, prefix, body, locals, in_def)
|
|
131
|
+
return if node.nil?
|
|
132
|
+
|
|
133
|
+
node.rigor_each_child { |child| walk(child, prefix, body, locals, in_def) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def record_singleton_def(node, locals)
|
|
137
|
+
receiver = node.receiver
|
|
138
|
+
return unless receiver.is_a?(Prism::LocalVariableReadNode)
|
|
139
|
+
|
|
140
|
+
@singleton_defs << [receiver.name, node.name, *locals]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def record_call(node, prefix, body, in_def)
|
|
144
|
+
if Inference::ScopeIndexer.refine_target(node)
|
|
145
|
+
@refine_blocks << span_of(node.block)
|
|
146
|
+
record_refinement_defs(node.block.body)
|
|
147
|
+
elsif using_call?(node) && !in_def
|
|
148
|
+
record_using(node, prefix, body)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def record_refinement_defs(body)
|
|
153
|
+
return if body.nil?
|
|
154
|
+
|
|
155
|
+
Inference::ScopeIndexer.each_refinement_def(body) do |def_node|
|
|
156
|
+
@refinement_defs << def_node.location.start_offset
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def using_call?(node)
|
|
161
|
+
node.name == :using && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)) &&
|
|
162
|
+
node.arguments&.arguments&.size == 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def record_using(node, prefix, body)
|
|
166
|
+
argument = node.arguments.arguments.first
|
|
167
|
+
if argument.is_a?(Prism::ConstantReadNode) || argument.is_a?(Prism::ConstantPathNode)
|
|
168
|
+
candidates = Inference::ScopeIndexer.constant_receiver_candidates(argument, prefix)
|
|
169
|
+
@usings << [node.location.end_offset, body.last, candidates]
|
|
170
|
+
else
|
|
171
|
+
@unresolved_using = true
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The span of a node that opens a local scope, recorded for {#nested_scope_holds?}.
|
|
176
|
+
def scope_span(node)
|
|
177
|
+
span = span_of(node)
|
|
178
|
+
@scopes << span
|
|
179
|
+
span
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def span_of(node)
|
|
183
|
+
location = node.location
|
|
184
|
+
[location.start_offset, location.end_offset]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -19,10 +19,12 @@ module Rigor
|
|
|
19
19
|
# accumulated {Diagnostic}s, in the same emission order the inline `NodeWalker.each` produced them (the
|
|
20
20
|
# shared walk is the same visit-before-descend DFS over `compact_child_nodes`).
|
|
21
21
|
class MainPassCollector
|
|
22
|
-
# The node classes the
|
|
23
|
-
#
|
|
22
|
+
# The node classes the main pass branches on. A plain `Prism::IfNode` covers ternaries and postfix `if`
|
|
23
|
+
# too (the legacy `when Prism::IfNode, Prism::UnlessNode` arm did the same). The three global-write
|
|
24
|
+
# classes carry issue #1367's `global.*` rules.
|
|
24
25
|
NODE_CLASSES = [
|
|
25
|
-
Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode
|
|
26
|
+
Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode,
|
|
27
|
+
Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::MultiWriteNode
|
|
26
28
|
].freeze
|
|
27
29
|
|
|
28
30
|
# @param node_diagnostics — maps a `Prism::Node` to the array of diagnostics the main pass emits
|
|
@@ -6,9 +6,9 @@ module Rigor
|
|
|
6
6
|
# Canonical identifiers for each rule. Per ADR-8 § "Diagnostic ID family hierarchy", rule names are
|
|
7
7
|
# `family.rule-name` two-segment strings; the families group diagnostics by where they originate
|
|
8
8
|
# (`call.*` for call-site rules, `flow.*` for flow-analysis proofs, `assert.*` for runtime-assertion
|
|
9
|
-
# rules, `dump.*` for debug helpers, `def.*` for method-definition rules
|
|
10
|
-
# `disable:` list and the in-source `# rigor:disable <rule>`
|
|
11
|
-
# register here so user configuration can refer to them.
|
|
9
|
+
# rules, `dump.*` for debug helpers, `def.*` for method-definition rules, `global.*` for writes to special
|
|
10
|
+
# globals). Used by the configuration `disable:` list and the in-source `# rigor:disable <rule>`
|
|
11
|
+
# suppression comment system; new rules MUST register here so user configuration can refer to them.
|
|
12
12
|
#
|
|
13
13
|
# ADR-87 WD4 — this pure-data constant table is split out of the (engine-heavy) `check_rules.rb` so
|
|
14
14
|
# {Analysis::RuleCatalog} — which the CLI's JSON `evidence_tier` / `documentation_url` enrichment reads
|
|
@@ -66,6 +66,18 @@ module Rigor
|
|
|
66
66
|
# annotation must never turn collection on by itself (that would be a project-wide cost cliff
|
|
67
67
|
# nobody asked for), and it must equally never be silently inert.
|
|
68
68
|
RULE_EFFECT_ANNOTATIONS_UNCHECKED = "effect.annotations-unchecked"
|
|
69
|
+
# ADR-112 WD5 / #1075 — two statements of one member's contract that contradict: a `sig/` declaration
|
|
70
|
+
# against an inline `@rbs` / `#:` one (and, once #1073 reads it, `@rbs` / `#:` against `@extrbs`), or a
|
|
71
|
+
# `rigor:v1:` refinement outside the member's own declared type. `rbs.` because the finding is about
|
|
72
|
+
# the RBS the environment is built from, whichever file spelled it; not `rbs_extended.`, whose rows
|
|
73
|
+
# are payload validity, since most contradictions involve no `RBS::Extended` annotation at all.
|
|
74
|
+
RULE_CONTRADICTING_SIGNATURE = "rbs.contradicting-signature"
|
|
75
|
+
# Issue #1367 (ADR-117 WD2) — the `global.*` family: a write to a special global that its setter rejects, so
|
|
76
|
+
# the write raises on every run. Two ids so a project can tune the type check without hiding read-only
|
|
77
|
+
# writes; the `write` wording follows `def.ivar-write-mismatch`. The envelope is the interpreter's setter
|
|
78
|
+
# (`SpecialGlobalSetters`), not the global's RBS declaration.
|
|
79
|
+
RULE_GLOBAL_WRITE_TYPE_MISMATCH = "global.write-type-mismatch"
|
|
80
|
+
RULE_GLOBAL_READONLY_WRITE = "global.readonly-write"
|
|
69
81
|
|
|
70
82
|
ALL_RULES = [
|
|
71
83
|
RULE_UNDEFINED_METHOD,
|
|
@@ -91,6 +103,8 @@ module Rigor
|
|
|
91
103
|
RULE_OVERRIDE_RETURN_WIDENED,
|
|
92
104
|
RULE_OVERRIDE_PARAM_NARROWED,
|
|
93
105
|
RULE_IVAR_WRITE_MISMATCH,
|
|
106
|
+
RULE_GLOBAL_WRITE_TYPE_MISMATCH,
|
|
107
|
+
RULE_GLOBAL_READONLY_WRITE,
|
|
94
108
|
RULE_SUPPRESSION_UNKNOWN_RULE,
|
|
95
109
|
RULE_SUPPRESSION_EMPTY,
|
|
96
110
|
RULE_SUPPRESSION_UNKNOWN_MARKER,
|
|
@@ -98,7 +112,8 @@ module Rigor
|
|
|
98
112
|
RULE_EFFECT_ENVELOPE_EXCEEDED,
|
|
99
113
|
RULE_EFFECT_LISKOV_WIDENED,
|
|
100
114
|
RULE_EFFECT_UNKNOWN_LABEL,
|
|
101
|
-
RULE_EFFECT_ANNOTATIONS_UNCHECKED
|
|
115
|
+
RULE_EFFECT_ANNOTATIONS_UNCHECKED,
|
|
116
|
+
RULE_CONTRADICTING_SIGNATURE
|
|
102
117
|
].freeze
|
|
103
118
|
|
|
104
119
|
# Backward-compat alias table (ADR-8 § "Backward compatibility"). Existing user code with
|
|
@@ -128,7 +143,7 @@ module Rigor
|
|
|
128
143
|
|
|
129
144
|
# Family wildcard — a `<family>` token in a suppression comment or `disable:` list disables every rule
|
|
130
145
|
# whose canonical id starts with `<family>.`. Per ADR-8 § "1".
|
|
131
|
-
RULE_FAMILIES = %w[call flow assert dump def suppression static effect].freeze
|
|
146
|
+
RULE_FAMILIES = %w[call flow assert dump def global suppression static effect].freeze
|
|
132
147
|
|
|
133
148
|
# Families of diagnostics the engine emits OUTSIDE the CheckRules catalogue (aggregator-level and
|
|
134
149
|
# reporter-level diagnostics such as `rbs_extended.unsatisfied-conformance`,
|
|
@@ -50,6 +50,7 @@ module Rigor
|
|
|
50
50
|
Struct: %i[new],
|
|
51
51
|
Data: %i[define]
|
|
52
52
|
}.freeze
|
|
53
|
+
Ractor.make_shareable(DETACHED_CLASS_FACTORIES)
|
|
53
54
|
private_constant :DETACHED_CLASS_FACTORIES
|
|
54
55
|
|
|
55
56
|
EMPTY_METHODS = [].freeze
|
|
@@ -108,7 +109,7 @@ module Rigor
|
|
|
108
109
|
end
|
|
109
110
|
end
|
|
110
111
|
|
|
111
|
-
#
|
|
112
|
+
# Mirrors {RuleWalk.descend}.
|
|
112
113
|
def descend(node, context)
|
|
113
114
|
RuleWalk.descend(node, context)
|
|
114
115
|
end
|