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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../analysis/template_unit_paths"
|
|
6
|
+
require_relative "../analysis/template_unit_positions"
|
|
7
|
+
require_relative "../analysis/template_units"
|
|
8
|
+
require_relative "../inference/precision_scanner"
|
|
9
|
+
require_relative "../inference/scope_indexer"
|
|
10
|
+
require_relative "../source/node_locator"
|
|
11
|
+
|
|
12
|
+
module Rigor
|
|
13
|
+
class CLI
|
|
14
|
+
# #1040 — `rigor type-of` against a **template unit**: a file a loaded plugin compiles into Ruby.
|
|
15
|
+
#
|
|
16
|
+
# Before this the command read the template from disk and parsed its bytes as Ruby, so an ERB view
|
|
17
|
+
# exited 1 on a parse error and an identity-transformed one answered about the wrong node under an
|
|
18
|
+
# unseeded scope. A template path now takes the path `rigor check` takes: the run's
|
|
19
|
+
# {Analysis::TemplateUnits} index is built, the COMPILED source is parsed with the unit's `scopes:`, the
|
|
20
|
+
# file scope is seeded with the unit's `self`, locals and ivars, and the requested template position is
|
|
21
|
+
# resolved through {Analysis::TemplateUnitPositions} — which declines, with a message, whenever the
|
|
22
|
+
# position does not denote exactly one compiled node.
|
|
23
|
+
#
|
|
24
|
+
# Mixed into {TypeOfCommand}; a path no loaded plugin claims never reaches anything here, so a `.rb`
|
|
25
|
+
# probe does not build the index or change a byte of its output.
|
|
26
|
+
module TypeOfTemplateProbe
|
|
27
|
+
# Why a template position has no answer, keyed by {Analysis::TemplateUnitPositions#node_at}'s reasons.
|
|
28
|
+
DECLINE_MESSAGES = {
|
|
29
|
+
no_compiled_line: "the template compiler emitted no Ruby for this line",
|
|
30
|
+
not_verbatim: "the position is in template markup, or in code the template compiler rewrote " \
|
|
31
|
+
"rather than copied, so no compiled expression is made of the bytes there",
|
|
32
|
+
ambiguous: "the bytes there are template markup, or code the compiler copied to more than one " \
|
|
33
|
+
"place, so the position does not name a single compiled expression"
|
|
34
|
+
}.freeze
|
|
35
|
+
private_constant :DECLINE_MESSAGES
|
|
36
|
+
Ractor.make_shareable(DECLINE_MESSAGES)
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# The unit `file` compiles to, `nil` for a path that is not a template unit (the caller probes it as
|
|
41
|
+
# Ruby), or an exit status after reporting a template the claiming plugin could not compile.
|
|
42
|
+
def template_unit_probe(file, environment, buffer)
|
|
43
|
+
registry = environment.plugin_registry
|
|
44
|
+
return nil unless template_claimed?(registry, file)
|
|
45
|
+
|
|
46
|
+
units = (@template_units ||= Analysis::TemplateUnits.collect(registry: registry, buffer: buffer))
|
|
47
|
+
entry = units[file]
|
|
48
|
+
return [units, entry] if entry
|
|
49
|
+
|
|
50
|
+
# A template the plugin DECLINED is not a unit, and is probed as the command always probed it —
|
|
51
|
+
# which for a template that is not Ruby is a Prism error about bytes the user did not write, so
|
|
52
|
+
# the decline is stated first rather than left to be inferred from the error.
|
|
53
|
+
failures = template_failures(units, file)
|
|
54
|
+
if failures.empty?
|
|
55
|
+
@err.puts("type-of: #{file}: plugin declined the template; probing its bytes as Ruby")
|
|
56
|
+
return nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
failures.each do |failure|
|
|
60
|
+
@err.puts("type-of: #{file}: plugin #{failure.plugin_id} could not compile the template: " \
|
|
61
|
+
"#{failure.message}")
|
|
62
|
+
end
|
|
63
|
+
1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Only a claimed path builds the index — a `.rb` probe pays no glob expansion and no transform.
|
|
67
|
+
def template_claimed?(registry, file)
|
|
68
|
+
return false if registry.nil? || registry.empty?
|
|
69
|
+
|
|
70
|
+
relative = Analysis::TemplateUnitPaths.relative(file, Dir.pwd)
|
|
71
|
+
registry.plugins.any? do |plugin|
|
|
72
|
+
globs = begin
|
|
73
|
+
plugin.manifest.template_globs
|
|
74
|
+
rescue StandardError
|
|
75
|
+
[]
|
|
76
|
+
end
|
|
77
|
+
!globs.empty? && Analysis::TemplateUnitPaths.claims?(globs, relative)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def template_failures(units, file)
|
|
82
|
+
relative = Analysis::TemplateUnitPaths.relative(file, Dir.pwd)
|
|
83
|
+
units.failures.select { |failure| failure.path == relative }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Every result for one template: the compiled source parsed and indexed once under the unit's seeded
|
|
87
|
+
# scope, then each requested template position resolved through the inverse map.
|
|
88
|
+
def resolve_template(file, physical, unit_probe, indexed_targets, context)
|
|
89
|
+
units, entry = unit_probe
|
|
90
|
+
parse_result = Prism.parse(entry.source, filepath: file, version: context[:configuration].target_ruby,
|
|
91
|
+
scopes: units.parse_scopes(file))
|
|
92
|
+
return 1 if compiled_parse_errors?(parse_result, file, entry)
|
|
93
|
+
|
|
94
|
+
scope = units.seed(context[:base_scope].with_source_path(file), file)
|
|
95
|
+
scope_index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
|
|
96
|
+
positions = Analysis::TemplateUnitPositions.new(
|
|
97
|
+
entry: entry, template: File.binread(physical), root: parse_result.value,
|
|
98
|
+
skip_node_types: Inference::PrecisionScanner::NON_EXPRESSION_NODE_TYPES
|
|
99
|
+
)
|
|
100
|
+
probe = { file: file, entry: entry, positions: positions, scope_index: scope_index,
|
|
101
|
+
options: context[:options] }
|
|
102
|
+
indexed_targets.map do |target, index|
|
|
103
|
+
resolved = resolve_template_target(probe, target)
|
|
104
|
+
return resolved if resolved.is_a?(Integer)
|
|
105
|
+
|
|
106
|
+
[index, resolved]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def resolve_template_target(probe, target)
|
|
111
|
+
file = probe[:file]
|
|
112
|
+
positions = probe[:positions]
|
|
113
|
+
return CLI::EXIT_USAGE unless template_position_in_range?(positions, target)
|
|
114
|
+
return enumerate_template_line(probe, target.line) if target.column.nil?
|
|
115
|
+
|
|
116
|
+
node = positions.node_at(line: target.line, column: target.column)
|
|
117
|
+
if node.is_a?(Symbol)
|
|
118
|
+
@err.puts("type-of: no expression found at #{file}:#{target.line}:#{target.column}: " \
|
|
119
|
+
"#{DECLINE_MESSAGES.fetch(node)}")
|
|
120
|
+
return []
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
[type_result(file, target.line, target.column, node, probe[:scope_index], probe[:options],
|
|
124
|
+
location_mapper: template_location_mapper(probe))]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def enumerate_template_line(probe, line)
|
|
128
|
+
unless line.between?(1, probe[:positions].template_line_count)
|
|
129
|
+
@err.puts("type-of: no expression found on #{probe[:file]}:#{line}")
|
|
130
|
+
return []
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
rows, total = probe[:positions].line_nodes(line)
|
|
134
|
+
if rows.empty?
|
|
135
|
+
@err.puts("type-of: no expression found on #{probe[:file]}:#{line}: no compiled expression maps to " \
|
|
136
|
+
"a single column of this line (markup, code the template compiler rewrote, or code " \
|
|
137
|
+
"copied to more than one place)")
|
|
138
|
+
return []
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
enumeration = TypeOfCommand::LineEnumeration.new(total: total, shown: rows.length)
|
|
142
|
+
mapper = template_location_mapper(probe)
|
|
143
|
+
rows.map do |column, node|
|
|
144
|
+
type_result(probe[:file], line, column, node, probe[:scope_index], probe[:options],
|
|
145
|
+
enumeration: enumeration, location_mapper: mapper)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The same range errors, and the same exit status, a `.rb` probe gives — measured against the
|
|
150
|
+
# TEMPLATE's lines, which are the ones the user named. A bare `FILE:LINE` past the end is "no
|
|
151
|
+
# expression found", exactly as it is for Ruby.
|
|
152
|
+
def template_position_in_range?(positions, target)
|
|
153
|
+
return true if target.column.nil?
|
|
154
|
+
|
|
155
|
+
message = if target.line < 1 then "line must be >= 1, got #{target.line}"
|
|
156
|
+
elsif target.column < 1 then "column must be >= 1, got #{target.column}"
|
|
157
|
+
elsif target.line > positions.template_line_count
|
|
158
|
+
"line #{target.line} is past the end of the source buffer"
|
|
159
|
+
end
|
|
160
|
+
return true if message.nil?
|
|
161
|
+
|
|
162
|
+
@err.puts("type-of: #{message}")
|
|
163
|
+
false
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# A `--trace` fallback carries a COMPILED location; it is reported at the template line, and at a
|
|
167
|
+
# template column only when the location lies in a verbatim run.
|
|
168
|
+
def template_location_mapper(probe)
|
|
169
|
+
entry = probe[:entry]
|
|
170
|
+
positions = probe[:positions]
|
|
171
|
+
lambda do |location|
|
|
172
|
+
[entry.template_line(location.start_line), positions.template_column_for(location)]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# A compiled source that does not parse is the plugin's output, not the user's text; its errors are
|
|
177
|
+
# reported at the template lines they map to, as `rigor check` reports every other finding in a unit.
|
|
178
|
+
def compiled_parse_errors?(result, file, entry)
|
|
179
|
+
return false if result.errors.empty?
|
|
180
|
+
|
|
181
|
+
result.errors.each do |error|
|
|
182
|
+
@err.puts("#{file}:#{entry.template_line(error.location.start_line)}: " \
|
|
183
|
+
"compiled template does not parse: #{error.message}")
|
|
184
|
+
end
|
|
185
|
+
true
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -131,7 +131,7 @@ module Rigor
|
|
|
131
131
|
# ADR-102 WD2), which is exactly why the two corpora are separated rather than both widened.
|
|
132
132
|
def reference_files(_paths, configuration)
|
|
133
133
|
relative = Analysis::Reachability::ProjectFiles.own(Dir.glob(REFERENCE_GLOB, base: Dir.pwd), Dir.pwd)
|
|
134
|
-
absolute = relative.map { |rel| File.
|
|
134
|
+
absolute = relative.map { |rel| File.absolute_path(rel) }
|
|
135
135
|
Analysis::PathExpansion.reject_excluded(absolute, configuration.exclude_patterns).to_set
|
|
136
136
|
end
|
|
137
137
|
|
|
@@ -177,7 +177,7 @@ module Rigor
|
|
|
177
177
|
return [] if names.empty?
|
|
178
178
|
|
|
179
179
|
Analysis::Reachability::ProjectFiles.own(Dir.glob(TEMPLATE_GLOB, base: Dir.pwd), Dir.pwd).flat_map do |rel|
|
|
180
|
-
absolute = File.
|
|
180
|
+
absolute = File.absolute_path(rel)
|
|
181
181
|
haystack = cache.serve(:runs, absolute) { constant_bearing_text(File.read(absolute).scrub) }
|
|
182
182
|
names.filter_map do |fqn|
|
|
183
183
|
next unless haystack.include?(fqn)
|
|
@@ -208,7 +208,7 @@ module Rigor
|
|
|
208
208
|
return [] if globs.empty?
|
|
209
209
|
|
|
210
210
|
matcher = Analysis::Reachability::ProjectFiles.method(:entry_point_match?)
|
|
211
|
-
cwd = "#{File.
|
|
211
|
+
cwd = "#{File.absolute_path(Dir.pwd)}/"
|
|
212
212
|
declarations.filter_map do |d|
|
|
213
213
|
relative = d.path.delete_prefix(cwd)
|
|
214
214
|
d.fqn if globs.any? { |g| matcher.call(g, d.path) || matcher.call(g, relative) }
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -183,7 +183,7 @@ module Rigor
|
|
|
183
183
|
return 1
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
-
File.write(path, init_template)
|
|
186
|
+
File.write(path, init_template(path))
|
|
187
187
|
@out.puts("Created #{path}")
|
|
188
188
|
print_init_next_steps(path)
|
|
189
189
|
0
|
|
@@ -230,7 +230,7 @@ module Rigor
|
|
|
230
230
|
# Renders the starter `.rigor.yml` body. The template serialises `Configuration::DEFAULTS` (so the on-disk file
|
|
231
231
|
# round-trips through `Configuration.load`) and prepends a short header that points the user at the keys they are
|
|
232
232
|
# most likely to want to edit.
|
|
233
|
-
def init_template
|
|
233
|
+
def init_template(path = ".rigor.dist.yml")
|
|
234
234
|
<<~YAML
|
|
235
235
|
# yaml-language-server: $schema=#{CONFIG_SCHEMA_URL}
|
|
236
236
|
# Rigor configuration. The full key reference is
|
|
@@ -266,6 +266,12 @@ module Rigor
|
|
|
266
266
|
# Leave unset (or `null`) to auto-detect
|
|
267
267
|
# `<root>/sig`. Use `[]` to disable
|
|
268
268
|
# project-RBS loading entirely.
|
|
269
|
+
# - test_paths: the directories holding your tests.
|
|
270
|
+
# `rigor sig-gen --params=observed` reads
|
|
271
|
+
# their call sites to type parameters.
|
|
272
|
+
# `rigor init` writes the spec/ and test/
|
|
273
|
+
# it finds; unset auto-detects them, and
|
|
274
|
+
# `[]` declares the project has none.
|
|
269
275
|
# - cache.path: where Rigor will eventually persist
|
|
270
276
|
# analysis results across runs.
|
|
271
277
|
#
|
|
@@ -275,10 +281,22 @@ module Rigor
|
|
|
275
281
|
# tempfile, uri, logger, date, prism, rbs). Adding a
|
|
276
282
|
# `sig/<gem>.rbs` file under `sig/` is the simplest way
|
|
277
283
|
# to extend type coverage today.
|
|
278
|
-
#{YAML.dump(
|
|
284
|
+
#{YAML.dump(init_settings(path)).sub(/\A---\n/, '')}
|
|
279
285
|
YAML
|
|
280
286
|
end
|
|
281
287
|
|
|
288
|
+
# The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
|
|
289
|
+
# the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
|
|
290
|
+
# relative to the config file's directory, which is what {Configuration.load} resolves them against.
|
|
291
|
+
def init_settings(path)
|
|
292
|
+
test_paths = Configuration.new.resolved_test_paths
|
|
293
|
+
return Configuration::DEFAULTS if test_paths.empty?
|
|
294
|
+
|
|
295
|
+
config_dir = Pathname(File.absolute_path(File.dirname(path)))
|
|
296
|
+
relative = test_paths.map { |root| Pathname(root).relative_path_from(config_dir).to_s }
|
|
297
|
+
Configuration::DEFAULTS.merge("test_paths" => relative)
|
|
298
|
+
end
|
|
299
|
+
|
|
282
300
|
def run_annotate
|
|
283
301
|
require_relative "cli/annotate_command"
|
|
284
302
|
|
data/lib/rigor/config_audit.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "did_you_mean"
|
|
4
4
|
|
|
5
|
+
require_relative "config_path"
|
|
5
6
|
require_relative "signature_path_audit"
|
|
6
7
|
# ADR-87 WD4 — only the pure rule-id table (`ALL_RULES` / `RULE_FAMILIES`) is needed, so require the light
|
|
7
8
|
# `rule_ids.rb` rather than the engine-pulling `check_rules.rb`; keeps `config_audit` (loaded by every check)
|
|
@@ -199,14 +200,14 @@ module Rigor
|
|
|
199
200
|
end
|
|
200
201
|
|
|
201
202
|
def self.add_missing_dir(warnings, path, project_root, kind, key)
|
|
202
|
-
return if path.nil? || File.directory?(
|
|
203
|
+
return if path.nil? || File.directory?(ConfigPath.absolute(path, project_root))
|
|
203
204
|
|
|
204
205
|
warnings << Warning.new(kind: kind, message: "#{key}: #{path.inspect} is not a directory",
|
|
205
206
|
fields: { "path" => path })
|
|
206
207
|
end
|
|
207
208
|
|
|
208
209
|
def self.add_missing_file(warnings, path, project_root, kind, key)
|
|
209
|
-
return if path.nil? || File.file?(
|
|
210
|
+
return if path.nil? || File.file?(ConfigPath.absolute(path, project_root))
|
|
210
211
|
|
|
211
212
|
warnings << Warning.new(kind: kind, message: "#{key}: #{path.inspect} does not exist", fields: { "path" => path })
|
|
212
213
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# Resolves a path-valued setting that a person wrote into `.rigor.yml`, or that Bundler wrote into its
|
|
5
|
+
# own config (`BUNDLE_PATH: "~/gems"`), to an absolute path.
|
|
6
|
+
#
|
|
7
|
+
# A leading `~/` and a bare `~` expand to the invoking user's home directory — exactly what
|
|
8
|
+
# `File.expand_path` does for those two spellings, and what Bundler itself does for `BUNDLE_PATH`. Every
|
|
9
|
+
# other spelling goes through `File.absolute_path`, so `~drafts/x` stays a directory named `~drafts`
|
|
10
|
+
# instead of `File.expand_path`'s home-of-user-`drafts` reading, which raises `ArgumentError` for a user
|
|
11
|
+
# that does not exist (#1510). It never raises on a `~`.
|
|
12
|
+
#
|
|
13
|
+
# Paths the analysis is *given to analyse* (CLI path arguments, template units, the runner's file sets)
|
|
14
|
+
# do not use this: the shell has already expanded a `~` there, so a surviving one is a directory name.
|
|
15
|
+
module ConfigPath
|
|
16
|
+
HOME_PREFIX = %r{\A~(?=/|\z)}
|
|
17
|
+
|
|
18
|
+
# `base_dir` anchors a relative result and defaults to the working directory.
|
|
19
|
+
def self.absolute(path, base_dir = nil)
|
|
20
|
+
text = path.to_s
|
|
21
|
+
return File.absolute_path(text, base_dir) unless text.match?(HOME_PREFIX)
|
|
22
|
+
|
|
23
|
+
home = Dir.home
|
|
24
|
+
File.absolute_path(text.sub(HOME_PREFIX, home), base_dir)
|
|
25
|
+
rescue ArgumentError
|
|
26
|
+
File.absolute_path(text, base_dir)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -54,6 +54,11 @@ module Rigor
|
|
|
54
54
|
"def.override-return-widened" => :off,
|
|
55
55
|
"def.override-param-narrowed" => :off,
|
|
56
56
|
"def.ivar-write-mismatch" => :warning,
|
|
57
|
+
# Issue #1367 — both report a write its setter rejects, so it raises on every run. The type check rests on
|
|
58
|
+
# an inferred value type, so it follows `call.argument-type-mismatch` (warning under lenient); the
|
|
59
|
+
# read-only write needs no type at all, so it is an error in every profile.
|
|
60
|
+
"global.write-type-mismatch" => :warning,
|
|
61
|
+
"global.readonly-write" => :error,
|
|
57
62
|
# ADR-8 companion (PHPStan IgnoreParseErrorRule-modelled): a broken suppression comment is
|
|
58
63
|
# equally bad in every profile — it silently fails to do what the author believes it does — so
|
|
59
64
|
# both rules stay :warning across all three profiles (including strict).
|
|
@@ -80,7 +85,12 @@ module Rigor
|
|
|
80
85
|
# Opt-in author assertion: you only see it if you wrote a
|
|
81
86
|
# `conforms-to` directive, so it stays a :warning even in
|
|
82
87
|
# lenient — it is never unsolicited noise.
|
|
83
|
-
"rbs_extended.unsatisfied-conformance" => :warning
|
|
88
|
+
"rbs_extended.unsatisfied-conformance" => :warning,
|
|
89
|
+
# ADR-112 WD5 / #1075 — an error in every profile, lenient included. The rule guards its
|
|
90
|
+
# false-positive rate through its narrow definition of a contradiction, and ADR-112 WD5 rules
|
|
91
|
+
# out lowering the severity instead; only a proven disagreement between two declarations the
|
|
92
|
+
# project wrote itself can fire.
|
|
93
|
+
"rbs.contradicting-signature" => :error
|
|
84
94
|
}.freeze,
|
|
85
95
|
balanced: {
|
|
86
96
|
"call.undefined-method" => :error,
|
|
@@ -110,6 +120,8 @@ module Rigor
|
|
|
110
120
|
"def.override-return-widened" => :warning,
|
|
111
121
|
"def.override-param-narrowed" => :warning,
|
|
112
122
|
"def.ivar-write-mismatch" => :warning,
|
|
123
|
+
"global.write-type-mismatch" => :error,
|
|
124
|
+
"global.readonly-write" => :error,
|
|
113
125
|
"suppression.unknown-rule" => :warning,
|
|
114
126
|
"suppression.empty" => :warning,
|
|
115
127
|
"suppression.unknown-marker" => :warning,
|
|
@@ -118,7 +130,8 @@ module Rigor
|
|
|
118
130
|
"effect.liskov-widened" => :warning,
|
|
119
131
|
"effect.unknown-label" => :info,
|
|
120
132
|
"effect.annotations-unchecked" => :info,
|
|
121
|
-
"rbs_extended.unsatisfied-conformance" => :warning
|
|
133
|
+
"rbs_extended.unsatisfied-conformance" => :warning,
|
|
134
|
+
"rbs.contradicting-signature" => :error
|
|
122
135
|
}.freeze,
|
|
123
136
|
strict: {
|
|
124
137
|
"call.undefined-method" => :error,
|
|
@@ -147,6 +160,8 @@ module Rigor
|
|
|
147
160
|
"def.override-return-widened" => :error,
|
|
148
161
|
"def.override-param-narrowed" => :error,
|
|
149
162
|
"def.ivar-write-mismatch" => :error,
|
|
163
|
+
"global.write-type-mismatch" => :error,
|
|
164
|
+
"global.readonly-write" => :error,
|
|
150
165
|
"suppression.unknown-rule" => :warning,
|
|
151
166
|
"suppression.empty" => :warning,
|
|
152
167
|
"suppression.unknown-marker" => :warning,
|
|
@@ -158,7 +173,8 @@ module Rigor
|
|
|
158
173
|
# `:info` even under strict: a residual that failed a build would punish the project for
|
|
159
174
|
# carrying an annotation it has not opted into checking, which is the opposite of the point.
|
|
160
175
|
"effect.annotations-unchecked" => :info,
|
|
161
|
-
"rbs_extended.unsatisfied-conformance" => :error
|
|
176
|
+
"rbs_extended.unsatisfied-conformance" => :error,
|
|
177
|
+
"rbs.contradicting-signature" => :error
|
|
162
178
|
}.freeze
|
|
163
179
|
}.freeze
|
|
164
180
|
|
|
@@ -194,7 +210,12 @@ module Rigor
|
|
|
194
210
|
profile_table.fetch(rule, authored_severity)
|
|
195
211
|
end
|
|
196
212
|
|
|
213
|
+
# A family name has no `.` in it, so only a dot-free key can match one. `resolve` runs for every
|
|
214
|
+
# diagnostic in a run's stream, and most configurations carry no such key (usually no overrides at all),
|
|
215
|
+
# so the split below is skipped for them rather than allocating a name that nothing can match.
|
|
197
216
|
def family_override(rule, overrides)
|
|
217
|
+
return nil unless overrides.any? { |key, _| !key.to_s.include?(".") }
|
|
218
|
+
|
|
198
219
|
family = rule.split(".").first
|
|
199
220
|
return nil if family.nil?
|
|
200
221
|
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "bleeding_edge"
|
|
|
6
6
|
require_relative "ci_detector"
|
|
7
7
|
require_relative "configuration/dependencies"
|
|
8
8
|
require_relative "configuration/severity_profile"
|
|
9
|
+
require_relative "config_path"
|
|
9
10
|
require_relative "configuration_error"
|
|
10
11
|
require_relative "effects/entry_points"
|
|
11
12
|
require_relative "effects/label"
|
|
@@ -49,6 +50,9 @@ module Rigor
|
|
|
49
50
|
**/node_modules/**
|
|
50
51
|
].freeze
|
|
51
52
|
|
|
53
|
+
# The conventional test roots `test_paths: nil` auto-detects, in this order.
|
|
54
|
+
CONVENTIONAL_TEST_PATHS = %w[spec test].freeze
|
|
55
|
+
|
|
52
56
|
DEFAULTS = {
|
|
53
57
|
"target_ruby" => "4.0",
|
|
54
58
|
"paths" => ["lib"],
|
|
@@ -57,6 +61,20 @@ module Rigor
|
|
|
57
61
|
"disable" => [],
|
|
58
62
|
"libraries" => [],
|
|
59
63
|
"signature_paths" => nil,
|
|
64
|
+
# The project's test roots: the directories (or files) holding its tests. `nil` (the default) auto-detects
|
|
65
|
+
# whichever of `spec/` and `test/` exist, the same way `signature_paths: nil` auto-detects `sig/`; `[]`
|
|
66
|
+
# declares that the project has none. `rigor sig-gen --params=observed` harvests call-site observations
|
|
67
|
+
# from them when no `--observe=PATH` is given. See {#resolved_test_paths}.
|
|
68
|
+
"test_paths" => nil,
|
|
69
|
+
# ADR-112 WD4 — `rigor sig-gen` settings. `inline_declared:` decides what happens to a method the inline
|
|
70
|
+
# reader (`rigor-rbs-inline`) already declares from `# @rbs` / `#:`. `write` (the default) copies the
|
|
71
|
+
# inline declaration into `sig/`, so the generated signature is the complete contract a gem ships;
|
|
72
|
+
# `skip` leaves every member that reader declares out of `sig/`, for a project whose Steep reads the
|
|
73
|
+
# same annotations (`inline: true` beside `signature "sig"`), where the copy would be a second
|
|
74
|
+
# declaration of one method (`DuplicatedMethodDefinition`). See {#sig_gen_inline_declared}.
|
|
75
|
+
"sig_gen" => {
|
|
76
|
+
"inline_declared" => "write"
|
|
77
|
+
},
|
|
60
78
|
# ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit
|
|
61
79
|
# files that the analyzer walks before per-file inference so patched-method declarations are visible
|
|
62
80
|
# across the project (e.g. `lib/core_ext/string_extensions.rb`). Slice 1 plumbing only — listed files
|
|
@@ -202,11 +220,12 @@ module Rigor
|
|
|
202
220
|
"auto_detect" => true
|
|
203
221
|
}
|
|
204
222
|
}.freeze
|
|
223
|
+
Ractor.make_shareable(DEFAULTS)
|
|
205
224
|
|
|
206
225
|
# Top-level keys whose values are file/directory paths that MUST be resolved relative to the config
|
|
207
226
|
# file's directory. `exclude:` is intentionally NOT in this list — its entries are glob patterns
|
|
208
227
|
# (`**/vendor/**`), not paths.
|
|
209
|
-
PATH_KEYS = %w[paths signature_paths pre_eval].freeze
|
|
228
|
+
PATH_KEYS = %w[paths signature_paths test_paths pre_eval].freeze
|
|
210
229
|
private_constant :PATH_KEYS
|
|
211
230
|
|
|
212
231
|
# Top-level keys this implementation DECLARES but never reads — see `docs/internal-spec/config.md`
|
|
@@ -251,7 +270,7 @@ module Rigor
|
|
|
251
270
|
|
|
252
271
|
attr_reader :target_ruby, :paths, :exclude_patterns, :plugins, :cache_path, :cache_max_bytes,
|
|
253
272
|
:cache_validation, :disabled_rules,
|
|
254
|
-
:libraries, :signature_paths, :fold_platform_specific_paths, :parameter_inference,
|
|
273
|
+
:libraries, :signature_paths, :test_paths, :fold_platform_specific_paths, :parameter_inference,
|
|
255
274
|
:plugins_isolation,
|
|
256
275
|
:plugins_io_network, :plugins_io_allowed_paths,
|
|
257
276
|
:plugins_io_allowed_url_hosts,
|
|
@@ -260,7 +279,7 @@ module Rigor
|
|
|
260
279
|
:dependencies, :parallel_workers,
|
|
261
280
|
:bundler_bundle_path, :bundler_auto_detect, :bundler_lockfile,
|
|
262
281
|
:rbs_collection_lockfile, :rbs_collection_auto_detect,
|
|
263
|
-
:pre_eval, :baseline_path, :effects,
|
|
282
|
+
:pre_eval, :baseline_path, :effects, :sig_gen_inline_declared,
|
|
264
283
|
:effects_snapshot_path, :effects_snapshot_reach, :effects_snapshot_gate, :effects_tolerated,
|
|
265
284
|
:effects_labels, :effects_attribution, :effects_envelopes
|
|
266
285
|
|
|
@@ -282,6 +301,16 @@ module Rigor
|
|
|
282
301
|
@effects_check
|
|
283
302
|
end
|
|
284
303
|
|
|
304
|
+
# The test roots a run uses: the declared `test_paths:` as written (already resolved against the
|
|
305
|
+
# config file's directory by {.load}), or — when the key is unset — whichever of
|
|
306
|
+
# {CONVENTIONAL_TEST_PATHS} is a directory under `root`, as absolute paths. `root` is the directory
|
|
307
|
+
# the run treats as the project root (the CLI's CWD), as for `signature_paths:` auto-detection.
|
|
308
|
+
def resolved_test_paths(root: Dir.pwd)
|
|
309
|
+
return test_paths unless test_paths.nil?
|
|
310
|
+
|
|
311
|
+
CONVENTIONAL_TEST_PATHS.map { |dir| File.absolute_path(dir, root) }.select { |dir| File.directory?(dir) }
|
|
312
|
+
end
|
|
313
|
+
|
|
285
314
|
# Loads a configuration file.
|
|
286
315
|
#
|
|
287
316
|
# `path == nil` triggers auto-discovery against {DISCOVERY_ORDER}. The first present file in that list is
|
|
@@ -376,7 +405,7 @@ module Rigor
|
|
|
376
405
|
# the include-aware load before applying `--treat-all-as-inline-rbs`'s plugin injection (see
|
|
377
406
|
# {CLI::CheckCommand#load_check_configuration}).
|
|
378
407
|
def self.load_with_includes(path, visited: Set.new)
|
|
379
|
-
absolute = File.
|
|
408
|
+
absolute = File.absolute_path(path)
|
|
380
409
|
raise ConfigurationError, "circular include: #{absolute}" if visited.include?(absolute)
|
|
381
410
|
|
|
382
411
|
raw = read_yaml(absolute)
|
|
@@ -405,7 +434,7 @@ module Rigor
|
|
|
405
434
|
|
|
406
435
|
accumulated = {}
|
|
407
436
|
includes.each do |inc|
|
|
408
|
-
inc_path =
|
|
437
|
+
inc_path = ConfigPath.absolute(inc.to_s, base_dir)
|
|
409
438
|
unless File.exist?(inc_path)
|
|
410
439
|
raise ConfigurationError, "include not found: #{inc.inspect} (referenced from #{base_dir})"
|
|
411
440
|
end
|
|
@@ -431,7 +460,7 @@ module Rigor
|
|
|
431
460
|
def self.resolve_path_key!(out, key, base_dir)
|
|
432
461
|
return unless out.key?(key) && !out[key].nil?
|
|
433
462
|
|
|
434
|
-
out[key] = Array(out[key]).map { |p|
|
|
463
|
+
out[key] = Array(out[key]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
|
|
435
464
|
end
|
|
436
465
|
|
|
437
466
|
def self.resolve_plugins_io_paths!(out, base_dir)
|
|
@@ -439,7 +468,7 @@ module Rigor
|
|
|
439
468
|
return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"]
|
|
440
469
|
|
|
441
470
|
duped = plugins_io.dup
|
|
442
|
-
duped["allowed_paths"] = Array(plugins_io["allowed_paths"]).map { |p|
|
|
471
|
+
duped["allowed_paths"] = Array(plugins_io["allowed_paths"]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
|
|
443
472
|
out["plugins_io"] = duped
|
|
444
473
|
end
|
|
445
474
|
|
|
@@ -509,6 +538,9 @@ module Rigor
|
|
|
509
538
|
@libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
|
|
510
539
|
sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
|
|
511
540
|
@signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
|
|
541
|
+
test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
|
|
542
|
+
@test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
|
|
543
|
+
@sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
|
|
512
544
|
@pre_eval = expand_pre_eval_entries(
|
|
513
545
|
Array(data.fetch("pre_eval", DEFAULTS.fetch("pre_eval"))).map(&:to_s)
|
|
514
546
|
)
|
|
@@ -593,6 +625,9 @@ module Rigor
|
|
|
593
625
|
"disable" => disabled_rules,
|
|
594
626
|
"libraries" => libraries,
|
|
595
627
|
"signature_paths" => signature_paths,
|
|
628
|
+
# `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
|
|
629
|
+
# incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
|
|
630
|
+
# invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
|
|
596
631
|
"pre_eval" => pre_eval,
|
|
597
632
|
"fold_platform_specific_paths" => fold_platform_specific_paths,
|
|
598
633
|
"parameter_inference" => parameter_inference,
|
|
@@ -980,6 +1015,35 @@ module Rigor
|
|
|
980
1015
|
VALID_CACHE_VALIDATIONS = %w[auto stat digest].freeze
|
|
981
1016
|
private_constant :VALID_CACHE_VALIDATIONS
|
|
982
1017
|
|
|
1018
|
+
# The `sig_gen.inline_declared:` values, as the Symbols {#sig_gen_inline_declared} answers.
|
|
1019
|
+
VALID_SIG_GEN_INLINE_DECLARED = %w[write skip].freeze
|
|
1020
|
+
private_constant :VALID_SIG_GEN_INLINE_DECLARED
|
|
1021
|
+
|
|
1022
|
+
# Rejects rather than fails soft: an unrecognised value read as `write` would put a second declaration of
|
|
1023
|
+
# every inline-annotated method into `sig/` for exactly the project that asked for none. A `sig_gen:`
|
|
1024
|
+
# block that omits the key, or is written empty, keeps the default.
|
|
1025
|
+
def coerce_sig_gen_inline_declared(section)
|
|
1026
|
+
section = {} if section.nil?
|
|
1027
|
+
raise ConfigurationError, "sig_gen must be a mapping, got #{section.inspect}" unless section.is_a?(Hash)
|
|
1028
|
+
|
|
1029
|
+
# The schema closes this object (`additionalProperties: false`); a misspelled key here would otherwise
|
|
1030
|
+
# load as the default and write exactly what `inline_declare: skip` was meant to prevent.
|
|
1031
|
+
unknown = section.keys.map(&:to_s) - DEFAULTS.fetch("sig_gen").keys
|
|
1032
|
+
unless unknown.empty?
|
|
1033
|
+
raise ConfigurationError,
|
|
1034
|
+
"sig_gen has unknown key(s) #{unknown.inspect}; known keys: #{DEFAULTS.fetch('sig_gen').keys.inspect}"
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
value = section.fetch("inline_declared", DEFAULTS.dig("sig_gen", "inline_declared")).to_s
|
|
1038
|
+
unless VALID_SIG_GEN_INLINE_DECLARED.include?(value)
|
|
1039
|
+
raise ConfigurationError,
|
|
1040
|
+
"sig_gen.inline_declared must be one of #{VALID_SIG_GEN_INLINE_DECLARED.inspect}, " \
|
|
1041
|
+
"got #{section['inline_declared'].inspect}"
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
value.to_sym
|
|
1045
|
+
end
|
|
1046
|
+
|
|
983
1047
|
def coerce_cache_validation(value)
|
|
984
1048
|
str = value.to_s
|
|
985
1049
|
VALID_CACHE_VALIDATIONS.include?(str) ? str : "auto"
|