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
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require "optionparser"
|
|
4
4
|
|
|
5
5
|
require_relative "../configuration"
|
|
6
|
+
require_relative "../effects/config_envelopes"
|
|
7
|
+
require_relative "../effects/registry"
|
|
6
8
|
require_relative "options"
|
|
7
9
|
require_relative "../sig_gen"
|
|
8
10
|
require_relative "command"
|
|
@@ -22,21 +24,27 @@ module Rigor
|
|
|
22
24
|
# AND the candidate is a `tighter-return`.
|
|
23
25
|
#
|
|
24
26
|
# Parameter policy defaults to `untyped`. `--params=observed` (slice 3) opts in to caller-side observation
|
|
25
|
-
# harvesting: the `ObservationCollector` walks `--observe=PATH...` (default
|
|
26
|
-
#
|
|
27
|
+
# harvesting: the `ObservationCollector` walks `--observe=PATH...` (default: the project's test roots,
|
|
28
|
+
# {Configuration#resolved_test_paths}), unions per-position arg types, and the generator emits the union per ADR-5
|
|
29
|
+
# clause 2.
|
|
27
30
|
# `--params=observed-strict` stays reserved-but-inert until the capability-role catalog ships (rejected with a usage
|
|
28
31
|
# error so the surface stays stable).
|
|
29
|
-
|
|
32
|
+
#
|
|
33
|
+
# `--check` (ADR-112 WD4) is the CI freshness gate: it runs the `--write` merge with every other flag as given,
|
|
34
|
+
# writes nothing, prints what would change, and exits 1 when anything would.
|
|
35
|
+
class SigGenCommand < Command # rubocop:disable Metrics/ClassLength
|
|
30
36
|
USAGE = "Usage: rigor sig-gen [options] [paths]"
|
|
31
37
|
|
|
32
|
-
VALID_MODES = %w[print diff write].freeze
|
|
38
|
+
VALID_MODES = %w[print diff write check].freeze
|
|
33
39
|
VALID_PARAM_POLICIES = %w[untyped observed observed-strict].freeze
|
|
34
40
|
VALID_FORMATS = %w[text json].freeze
|
|
35
41
|
|
|
36
|
-
# The skip reasons {#report_skipped} counts. The
|
|
37
|
-
# ({#report_unrenderable}, {#report_unresolvable_superclasses}
|
|
42
|
+
# The skip reasons {#report_skipped} counts. The four left out each have a report of their own
|
|
43
|
+
# ({#report_unrenderable}, {#report_unresolvable_superclasses}, {#report_inline_declared}, and the refusal
|
|
44
|
+
# lines `--write` / `--check` print for `inline_differs`), so a method never shows up in two tallies.
|
|
38
45
|
SUMMARISED_SKIP_REASONS = (SigGen::Classification::SKIP_DIAGNOSTIC_IDS.keys -
|
|
39
|
-
%i[unrenderable_rbs unresolvable_superclass
|
|
46
|
+
%i[unrenderable_rbs unresolvable_superclass inline_declared
|
|
47
|
+
inline_differs]).freeze
|
|
40
48
|
private_constant :SUMMARISED_SKIP_REASONS
|
|
41
49
|
|
|
42
50
|
# @return CLI exit status.
|
|
@@ -50,17 +58,22 @@ module Rigor
|
|
|
50
58
|
observations = collect_observations(configuration, options)
|
|
51
59
|
generator = SigGen::Generator.new(configuration: configuration, paths: paths,
|
|
52
60
|
observations: observations,
|
|
53
|
-
include_private: options.fetch(:include_private)
|
|
61
|
+
include_private: options.fetch(:include_private),
|
|
62
|
+
effect_annotator: effect_annotator(configuration, paths, options),
|
|
63
|
+
overwrite: options.fetch(:overwrite))
|
|
54
64
|
candidates = generator.run
|
|
55
65
|
mode = options.fetch(:mode).to_sym
|
|
56
66
|
|
|
57
|
-
status =
|
|
58
|
-
|
|
67
|
+
status = case mode
|
|
68
|
+
when :write then dispatch_write(candidates, configuration, options)
|
|
69
|
+
when :check then dispatch_check(candidates, configuration, options)
|
|
59
70
|
else
|
|
60
71
|
dispatch_print_or_diff(candidates, mode, options)
|
|
61
72
|
0
|
|
62
73
|
end
|
|
63
74
|
report_skipped(candidates, options)
|
|
75
|
+
report_inline_declared(candidates, options)
|
|
76
|
+
report_withheld_annotations(candidates, options)
|
|
64
77
|
report_unrenderable(generator.unrenderable)
|
|
65
78
|
report_unresolvable_superclasses(generator.unresolvable_superclasses)
|
|
66
79
|
status
|
|
@@ -68,6 +81,74 @@ module Rigor
|
|
|
68
81
|
|
|
69
82
|
private
|
|
70
83
|
|
|
84
|
+
# ADR-103 WD9 — the effect table sig-gen writes annotations from, or `nil`.
|
|
85
|
+
#
|
|
86
|
+
# The gate is the project's own `effects:` opt-in and nothing else. An annotation is read back as an
|
|
87
|
+
# enforced envelope, so turning emission on from a sig-gen flag alone would let one command commit a
|
|
88
|
+
# project to a contract `rigor check` was never asked to keep; and a project that has not opted in
|
|
89
|
+
# pays neither the analysis this needs nor a single changed byte of output.
|
|
90
|
+
#
|
|
91
|
+
# `--effect-envelopes` is the second, narrower switch: `%a{pure}` is the ecosystem's existing purity
|
|
92
|
+
# spelling and round-trips through Steep as well as Rigor, while `%a{rigor:v1:effect …}` is Rigor's
|
|
93
|
+
# own and belongs in a project's `sig/` only when the author asked for it by name.
|
|
94
|
+
def effect_annotator(configuration, paths, options)
|
|
95
|
+
unless configuration.effects_enabled?
|
|
96
|
+
if options.fetch(:effect_envelopes)
|
|
97
|
+
@err.puts("rigor sig-gen: --effect-envelopes needs the `effects:` opt-in in .rigor.yml; " \
|
|
98
|
+
"no effect annotation was emitted.")
|
|
99
|
+
end
|
|
100
|
+
return nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
require_relative "check_runner_factory"
|
|
104
|
+
# Through the same factory `rigor check` and `rigor doctor` use, so the LRU cap, the worker
|
|
105
|
+
# resolution and the tolerated-effects switch cannot drift from the command whose cache this run
|
|
106
|
+
# shares. `workers: 0` because a collecting run is pinned to the sequential path anyway, and
|
|
107
|
+
# `--no-cache` mirrors `rigor check`'s flag of the same name.
|
|
108
|
+
runner = CheckRunnerFactory.build(
|
|
109
|
+
configuration: configuration,
|
|
110
|
+
options: { no_cache: options.fetch(:no_cache), explain: false, stats: false, workers: 0 },
|
|
111
|
+
buffer: nil, cache_root: configuration.cache_path
|
|
112
|
+
)
|
|
113
|
+
runner.run((configuration.paths + paths).uniq)
|
|
114
|
+
SigGen::EffectAnnotation::Annotator.new(
|
|
115
|
+
table: runner.effect_table, envelopes: options.fetch(:effect_envelopes),
|
|
116
|
+
envelope_index: runner.effect_envelopes, config_envelopes: config_envelopes(configuration)
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The project's `effects.envelopes:` entries, for gate 0's `match:` half. Built with a plain
|
|
121
|
+
# registry rather than the run's: gate 0 asks only WHETHER an entry selects this class, never what
|
|
122
|
+
# it bounds, so the vocabulary a plugin would add cannot change the answer.
|
|
123
|
+
def config_envelopes(configuration)
|
|
124
|
+
Effects::ConfigEnvelopes.build(
|
|
125
|
+
entries: configuration.effects_envelopes,
|
|
126
|
+
registry: Effects::Registry.for_configuration(configuration)
|
|
127
|
+
)
|
|
128
|
+
rescue StandardError
|
|
129
|
+
[]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The withheld half of the emission, counted the way {#report_skipped} counts a skip: a method that
|
|
133
|
+
# did NOT get `%a{pure}` is the interesting case for a reader who expected one, and silence would
|
|
134
|
+
# read as "sig-gen does not do this" rather than "this method did not earn it".
|
|
135
|
+
def report_withheld_annotations(candidates, options)
|
|
136
|
+
return unless options.fetch(:format) == "text"
|
|
137
|
+
|
|
138
|
+
counts = candidates.each_with_object(Hash.new(0)) do |candidate, acc|
|
|
139
|
+
reason = candidate.effect_reason
|
|
140
|
+
acc[reason] += 1 if reason && reason != :emitted
|
|
141
|
+
end
|
|
142
|
+
return if counts.empty?
|
|
143
|
+
|
|
144
|
+
breakdown = counts.map { |reason, n| "#{SigGen::EffectAnnotation::DIAGNOSTIC_IDS.fetch(reason)}: #{n}" }
|
|
145
|
+
@err.puts(
|
|
146
|
+
"rigor sig-gen: withheld an effect annotation from #{counts.values.sum} method(s) " \
|
|
147
|
+
"(#{breakdown.join(', ')}). An annotation is enforced once written, so it is emitted only " \
|
|
148
|
+
"from an exhaustive, undischarged summary."
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
|
|
71
152
|
# Issue #778 — one stderr line per run saying how many methods the generator declined and why, so a
|
|
72
153
|
# method missing from the output is never a silent absence. Text mode only: under `--format=json` every
|
|
73
154
|
# skipped row is already in the payload with its `skip_reason`, and stderr stays clean for the consumer.
|
|
@@ -90,6 +171,18 @@ module Rigor
|
|
|
90
171
|
)
|
|
91
172
|
end
|
|
92
173
|
|
|
174
|
+
# ADR-112 WD4 — `sig_gen.inline_declared: skip` leaving methods out is the project's own choice, not a
|
|
175
|
+
# method sig-gen "could not type or would not overwrite", so it is counted on a line of its own.
|
|
176
|
+
def report_inline_declared(candidates, options)
|
|
177
|
+
return unless options.fetch(:format) == "text"
|
|
178
|
+
|
|
179
|
+
count = candidates.count { |c| c.skip_reason == :inline_declared }
|
|
180
|
+
return if count.zero?
|
|
181
|
+
|
|
182
|
+
@err.puts("rigor sig-gen: left #{count} method(s) declared inline out of sig/, as " \
|
|
183
|
+
"`sig_gen.inline_declared: skip` asks (sig.skipped.inline-declared).")
|
|
184
|
+
end
|
|
185
|
+
|
|
93
186
|
# A method whose rendered RBS does not parse is a Rigor rendering defect, not a fact about the user's
|
|
94
187
|
# code — the generator skipped it (so the rest of the signatures are still usable and still valid), but
|
|
95
188
|
# staying silent would leave the user with a quietly incomplete `sig/` and us with an unreported bug.
|
|
@@ -137,30 +230,99 @@ module Rigor
|
|
|
137
230
|
|
|
138
231
|
# @return exit status — non-zero when a file the user asked to write could not be written.
|
|
139
232
|
def dispatch_write(candidates, configuration, options)
|
|
140
|
-
|
|
141
|
-
path_mapper = SigGen::PathMapper.new(configuration: configuration, layout_index: layout_index)
|
|
142
|
-
writer = SigGen::Writer.new(path_mapper: path_mapper, overwrite: options.fetch(:overwrite))
|
|
143
|
-
|
|
144
|
-
results = writer.write_all(candidates)
|
|
233
|
+
results = build_writer(configuration, options).write_all(candidates)
|
|
145
234
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
#
|
|
235
|
+
refused = refused_candidates(candidates)
|
|
236
|
+
SigGen::Renderer.new(out: @out).render_write(results: results, format: options.fetch(:format), refused: refused)
|
|
237
|
+
SigGen::Renderer.refusal_lines(refused).each { |line| @err.puts(line) }
|
|
238
|
+
# A refused write (an assembled file that does not parse, an existing target that is not valid UTF-8,
|
|
239
|
+
# or a method whose inline and `sig/` declarations disagree without `--overwrite`) means the user asked for a
|
|
240
|
+
# write and did not get one, so the command must not report success — a green `sig-gen --write` in CI
|
|
241
|
+
# would otherwise mean nothing.
|
|
150
242
|
refusals = %i[skipped_invalid_rbs skipped_invalid_encoding]
|
|
151
|
-
results.any? { |result| refusals.include?(result.action) } ? 1 : 0
|
|
243
|
+
results.any? { |result| refusals.include?(result.action) } || !refused.empty? ? 1 : 0
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# ADR-112 WD4 — the methods whose inline and `sig/` declarations disagree, refused because the run did not
|
|
247
|
+
# pass `--overwrite`. Unlike every other skip, `sig/` contradicts the source while one stands.
|
|
248
|
+
def refused_candidates(candidates)
|
|
249
|
+
candidates.select { |candidate| candidate.skip_reason == :inline_differs }
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# ADR-112 WD4 — the same writer as {#dispatch_write}, flags included, in dry-run mode. Defined by what
|
|
253
|
+
# `--write` would do rather than by whether `--diff` prints anything: a tighter return against an existing
|
|
254
|
+
# declaration is a proposal `--write` declines without `--overwrite`, and a gate that counted it could
|
|
255
|
+
# never pass on a project that reviewed it and said no. With `--overwrite` it counts, because `--write
|
|
256
|
+
# --overwrite` would apply it.
|
|
257
|
+
#
|
|
258
|
+
# @return 1 when `sig/` is out of date (or a write would be refused), 0 when it is current.
|
|
259
|
+
def dispatch_check(candidates, configuration, options)
|
|
260
|
+
results = build_writer(configuration, options, dry_run: true).write_all(candidates)
|
|
261
|
+
refused = refused_candidates(candidates)
|
|
262
|
+
SigGen::Renderer.new(out: @out).render_check(results: results, format: options.fetch(:format),
|
|
263
|
+
refused: refused)
|
|
264
|
+
stale = SigGen::Renderer.out_of_date(results)
|
|
265
|
+
return 0 if stale.empty? && refused.empty?
|
|
266
|
+
|
|
267
|
+
report_check_failure(stale, refused) if options.fetch(:format) == "text"
|
|
268
|
+
1
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def report_check_failure(stale, refused)
|
|
272
|
+
unless stale.empty?
|
|
273
|
+
@err.puts("rigor sig-gen --check: #{stale.size} signature file(s) out of date; " \
|
|
274
|
+
"run `rigor sig-gen --write` with the same options to update them.")
|
|
275
|
+
end
|
|
276
|
+
return if refused.empty?
|
|
277
|
+
|
|
278
|
+
@err.puts("rigor sig-gen --check: #{refused.size} method(s) whose inline declaration and sig/ copy " \
|
|
279
|
+
"disagree; make them agree, or pass --overwrite to replace the sig/ member.")
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def build_writer(configuration, options, dry_run: false)
|
|
283
|
+
layout_index = SigGen::LayoutIndex.new(signature_paths: configuration.signature_paths)
|
|
284
|
+
path_mapper = SigGen::PathMapper.new(configuration: configuration, layout_index: layout_index)
|
|
285
|
+
SigGen::Writer.new(path_mapper: path_mapper, overwrite: options.fetch(:overwrite), dry_run: dry_run)
|
|
152
286
|
end
|
|
153
287
|
|
|
154
288
|
# Slice 3 — collect call-site argument observations when `--params=observed` is set. When `--observe=PATH` is not
|
|
155
|
-
# specified,
|
|
289
|
+
# specified, observe the project's test roots: `test_paths:`, or whichever of `spec/` and `test/` exist. With no
|
|
290
|
+
# root at all the run still succeeds, but says so — otherwise every parameter stays `untyped` with nothing
|
|
291
|
+
# naming the reason.
|
|
156
292
|
def collect_observations(configuration, options)
|
|
157
293
|
return {} if options.fetch(:params) != "observed"
|
|
158
294
|
|
|
159
295
|
observe_paths = options.fetch(:observe)
|
|
160
|
-
observe_paths =
|
|
296
|
+
observe_paths = configuration.resolved_test_paths if observe_paths.empty?
|
|
297
|
+
if observe_paths.empty?
|
|
298
|
+
warn_no_test_roots(configuration)
|
|
299
|
+
else
|
|
300
|
+
warn_missing_test_roots(observe_paths)
|
|
301
|
+
end
|
|
161
302
|
SigGen::ObservationCollector.new(configuration: configuration, paths: observe_paths).collect
|
|
162
303
|
end
|
|
163
304
|
|
|
305
|
+
# A declared root that does not exist is read as empty, so name it: `sig-gen` is the only command that reads
|
|
306
|
+
# the test roots, and without this the run looks exactly like one whose tests pass no typed arguments.
|
|
307
|
+
def warn_missing_test_roots(observe_paths)
|
|
308
|
+
missing = observe_paths.reject { |path| File.exist?(path) }
|
|
309
|
+
return if missing.empty?
|
|
310
|
+
|
|
311
|
+
consequence = missing.size == observe_paths.size ? "; every parameter stays untyped" : ""
|
|
312
|
+
@err.puts("rigor sig-gen: no call sites are observed from #{missing.map(&:inspect).join(', ')}, " \
|
|
313
|
+
"which does not exist#{consequence}. Check `test_paths:` or --observe=PATH.")
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def warn_no_test_roots(configuration)
|
|
317
|
+
reason = if configuration.test_paths.nil?
|
|
318
|
+
"no spec/ or test/ directory was found and `test_paths:` is unset"
|
|
319
|
+
else
|
|
320
|
+
"`test_paths:` is empty"
|
|
321
|
+
end
|
|
322
|
+
@err.puts("rigor sig-gen: --params=observed has no test roots to observe (#{reason}); every parameter " \
|
|
323
|
+
"stays untyped. Declare `test_paths:` in the configuration or pass --observe=PATH.")
|
|
324
|
+
end
|
|
325
|
+
|
|
164
326
|
def parse_options
|
|
165
327
|
options = {
|
|
166
328
|
mode: "print",
|
|
@@ -170,6 +332,8 @@ module Rigor
|
|
|
170
332
|
overwrite: false,
|
|
171
333
|
observe: [],
|
|
172
334
|
include_private: false,
|
|
335
|
+
effect_envelopes: false,
|
|
336
|
+
no_cache: false,
|
|
173
337
|
config: nil
|
|
174
338
|
}
|
|
175
339
|
build_option_parser(options).parse!(@argv)
|
|
@@ -181,23 +345,33 @@ module Rigor
|
|
|
181
345
|
nil
|
|
182
346
|
end
|
|
183
347
|
|
|
184
|
-
def build_option_parser(options) # rubocop:disable Metrics/AbcSize
|
|
348
|
+
def build_option_parser(options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
185
349
|
OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
|
186
350
|
opts.banner = USAGE
|
|
187
|
-
opts.on("--print", "Write RBS skeletons to stdout (default)") { options
|
|
188
|
-
opts.on("--diff", "Write a unified diff against existing RBS") { options
|
|
189
|
-
opts.on("--write", "Write generated RBS to sig/<path>.rbs files") { options
|
|
190
|
-
opts.on("--
|
|
351
|
+
opts.on("--print", "Write RBS skeletons to stdout (default)") { select_mode(options, "print") }
|
|
352
|
+
opts.on("--diff", "Write a unified diff against existing RBS") { select_mode(options, "diff") }
|
|
353
|
+
opts.on("--write", "Write generated RBS to sig/<path>.rbs files") { select_mode(options, "write") }
|
|
354
|
+
opts.on("--check", "Exit 1 when --write would change sig/; write nothing") { select_mode(options, "check") }
|
|
355
|
+
opts.on("--overwrite", "Allow tighter-return updates, and inline declarations that disagree with sig/, " \
|
|
356
|
+
"to replace user-authored RBS") do
|
|
191
357
|
options[:overwrite] = true
|
|
192
358
|
end
|
|
193
359
|
opts.on("--include-private", "Emit private / protected instance methods (default: public only)") do
|
|
194
360
|
options[:include_private] = true
|
|
195
361
|
end
|
|
362
|
+
opts.on("--effect-envelopes", "Also emit %a{rigor:v1:effect ...} for effectful methods " \
|
|
363
|
+
"(requires the effects: opt-in)") do
|
|
364
|
+
options[:effect_envelopes] = true
|
|
365
|
+
end
|
|
366
|
+
opts.on("--no-cache", "Do not read or write the analysis cache (effect collection only)") do
|
|
367
|
+
options[:no_cache] = true
|
|
368
|
+
end
|
|
196
369
|
opts.on("--format=FORMAT", "Output format: text or json") { |value| options[:format] = value }
|
|
197
370
|
opts.on("--params=POLICY", "Parameter policy: untyped (default), observed, observed-strict") do |value|
|
|
198
371
|
options[:params] = value
|
|
199
372
|
end
|
|
200
|
-
opts.on("--observe=PATH", "Directory / file to scan for call-site observations (repeatable
|
|
373
|
+
opts.on("--observe=PATH", "Directory / file to scan for call-site observations (repeatable; " \
|
|
374
|
+
"default: the configured test_paths)") do |value|
|
|
201
375
|
options[:observe] << value
|
|
202
376
|
end
|
|
203
377
|
opts.on("--new-files", "Emit only new-file classifications") do
|
|
@@ -213,12 +387,19 @@ module Rigor
|
|
|
213
387
|
end
|
|
214
388
|
end
|
|
215
389
|
|
|
390
|
+
# A second, different mode flag is a usage error rather than last-one-wins: `--check --write` must not
|
|
391
|
+
# quietly write in a CI job that meant to gate, nor `--write --check` quietly not.
|
|
392
|
+
def select_mode(options, mode)
|
|
393
|
+
options[:mode] = options[:mode_given] && options[:mode] != mode ? "conflict" : mode
|
|
394
|
+
options[:mode_given] = true
|
|
395
|
+
end
|
|
396
|
+
|
|
216
397
|
def validation_error(options)
|
|
217
398
|
mode = options.fetch(:mode)
|
|
218
399
|
format = options.fetch(:format)
|
|
219
400
|
params = options.fetch(:params)
|
|
220
401
|
|
|
221
|
-
return "--print, --diff, and --
|
|
402
|
+
return "--print, --diff, --write, and --check are mutually exclusive flags; pick one" if mode == "conflict"
|
|
222
403
|
return "unsupported --format=#{format}" unless VALID_FORMATS.include?(format)
|
|
223
404
|
return "unsupported --params=#{params}" unless VALID_PARAM_POLICIES.include?(params)
|
|
224
405
|
if params == "observed-strict"
|
|
@@ -74,7 +74,7 @@ module Rigor
|
|
|
74
74
|
|
|
75
75
|
# The bundled skills live at `<gem_root>/skills/`. From `lib/rigor/cli/skill_command.rb` that is three directories
|
|
76
76
|
# up.
|
|
77
|
-
SKILLS_ROOT = File.expand_path("../../../skills", __dir__)
|
|
77
|
+
SKILLS_ROOT = File.expand_path("../../../skills", __dir__).freeze
|
|
78
78
|
|
|
79
79
|
# @return CLI exit status.
|
|
80
80
|
def run
|
|
@@ -13,6 +13,7 @@ require_relative "../inference/scope_indexer"
|
|
|
13
13
|
require_relative "../inference/precision_scanner"
|
|
14
14
|
require_relative "../source/node_children"
|
|
15
15
|
require_relative "type_of_renderer"
|
|
16
|
+
require_relative "type_of_template_probe"
|
|
16
17
|
require_relative "command"
|
|
17
18
|
require_relative "options"
|
|
18
19
|
require_relative "../project_environment"
|
|
@@ -28,10 +29,15 @@ module Rigor
|
|
|
28
29
|
# type-of UX (extra flags, watch mode, streaming output) without bloating the CLI shell. Output formatting is
|
|
29
30
|
# delegated to {TypeOfRenderer}.
|
|
30
31
|
class TypeOfCommand < Command
|
|
32
|
+
include TypeOfTemplateProbe
|
|
33
|
+
|
|
31
34
|
USAGE = "Usage: rigor type-of [options] FILE:LINE[:COL] [FILE:LINE[:COL] ...]"
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
# `location_mapper` is set for a template unit's result (#1040): the `--trace` fallback locations are
|
|
37
|
+
# positions in the COMPILED Ruby, and the mapper turns one into the `[template_line, column_or_nil]`
|
|
38
|
+
# the user can find in the file they named.
|
|
39
|
+
Result = Data.define(:file, :line, :column, :node, :type, :tracer, :enumeration, :location_mapper) do
|
|
40
|
+
def initialize(enumeration: nil, location_mapper: nil, **rest)
|
|
35
41
|
super
|
|
36
42
|
end
|
|
37
43
|
end
|
|
@@ -106,6 +112,14 @@ module Rigor
|
|
|
106
112
|
physical = buffer ? buffer.resolve(file) : file
|
|
107
113
|
return 1 unless file_exists?(buffer ? physical : file)
|
|
108
114
|
|
|
115
|
+
unit_probe = template_unit_probe(file, base_scope.environment, buffer)
|
|
116
|
+
return unit_probe if unit_probe.is_a?(Integer)
|
|
117
|
+
|
|
118
|
+
if unit_probe
|
|
119
|
+
context = { configuration: configuration, base_scope: base_scope, options: options }
|
|
120
|
+
return resolve_template(file, physical, unit_probe, indexed_targets, context)
|
|
121
|
+
end
|
|
122
|
+
|
|
109
123
|
source = File.read(physical)
|
|
110
124
|
parse_result = Prism.parse(source, filepath: file, version: configuration.target_ruby)
|
|
111
125
|
return 1 if parse_errors?(parse_result, file)
|
|
@@ -191,11 +205,11 @@ module Rigor
|
|
|
191
205
|
end
|
|
192
206
|
end
|
|
193
207
|
|
|
194
|
-
|
|
208
|
+
# `presentation` is the Result's optional `enumeration:` / `location_mapper:`.
|
|
209
|
+
def type_result(file, line, column, node, scope_index, options, **presentation)
|
|
195
210
|
tracer = options[:trace] ? Inference::FallbackTracer.new : nil
|
|
196
211
|
type = scope_index[node].type_of(node, tracer: tracer)
|
|
197
|
-
Result.new(file: file, line: line, column: column, node: node, type: type, tracer: tracer,
|
|
198
|
-
enumeration: enumeration)
|
|
212
|
+
Result.new(file: file, line: line, column: column, node: node, type: type, tracer: tracer, **presentation)
|
|
199
213
|
end
|
|
200
214
|
|
|
201
215
|
# Builds the plugin-aware environment relative to the probed file, so the reported type matches what `rigor
|
|
@@ -84,7 +84,7 @@ module Rigor
|
|
|
84
84
|
@out.puts("fallbacks: none")
|
|
85
85
|
else
|
|
86
86
|
@out.puts("fallbacks (#{tracer.size}):")
|
|
87
|
-
tracer.each { |event| @out.puts(" - #{format_fallback_text(event, result
|
|
87
|
+
tracer.each { |event| @out.puts(" - #{format_fallback_text(event, result)}") }
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
@@ -118,21 +118,31 @@ module Rigor
|
|
|
118
118
|
type: result.type.describe,
|
|
119
119
|
erased: result.type.erase_to_rbs
|
|
120
120
|
}
|
|
121
|
-
payload[:fallbacks] = result.tracer.map { |event| fallback_to_h(event) } if result.tracer
|
|
121
|
+
payload[:fallbacks] = result.tracer.map { |event| fallback_to_h(event, result) } if result.tracer
|
|
122
122
|
payload
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
def format_fallback_text(event,
|
|
126
|
-
"#{event.node_class} (#{event.family}) @ #{location_text(event.location,
|
|
125
|
+
def format_fallback_text(event, result)
|
|
126
|
+
"#{event.node_class} (#{event.family}) @ #{location_text(event.location, result)}"
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
def location_text(location,
|
|
129
|
+
def location_text(location, result)
|
|
130
130
|
return "<no location>" unless location.respond_to?(:start_line)
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
line, column = fallback_position(location, result)
|
|
133
|
+
column ? "#{result.file}:#{line}:#{column}" : "#{result.file}:#{line}"
|
|
133
134
|
end
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
# A template unit's result (#1040) maps the compiled location back; a column that names nothing in the
|
|
137
|
+
# template is left out rather than printed as a column of bytes the user never wrote.
|
|
138
|
+
def fallback_position(location, result)
|
|
139
|
+
mapper = result.location_mapper
|
|
140
|
+
return [location.start_line, location.start_column + 1] if mapper.nil?
|
|
141
|
+
|
|
142
|
+
mapper.call(location)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def fallback_to_h(event, result)
|
|
136
146
|
hash = {
|
|
137
147
|
node_class: event.node_class.name,
|
|
138
148
|
family: event.family,
|
|
@@ -140,8 +150,9 @@ module Rigor
|
|
|
140
150
|
}
|
|
141
151
|
location = event.location
|
|
142
152
|
if location.respond_to?(:start_line)
|
|
143
|
-
|
|
144
|
-
hash[:
|
|
153
|
+
line, column = fallback_position(location, result)
|
|
154
|
+
hash[:line] = line
|
|
155
|
+
hash[:column] = column if column
|
|
145
156
|
end
|
|
146
157
|
hash
|
|
147
158
|
end
|