rigortype 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +167 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
|
@@ -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
|
|
@@ -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
|
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.expand_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
|
|
|
@@ -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
|
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -49,6 +49,9 @@ module Rigor
|
|
|
49
49
|
**/node_modules/**
|
|
50
50
|
].freeze
|
|
51
51
|
|
|
52
|
+
# The conventional test roots `test_paths: nil` auto-detects, in this order.
|
|
53
|
+
CONVENTIONAL_TEST_PATHS = %w[spec test].freeze
|
|
54
|
+
|
|
52
55
|
DEFAULTS = {
|
|
53
56
|
"target_ruby" => "4.0",
|
|
54
57
|
"paths" => ["lib"],
|
|
@@ -57,6 +60,20 @@ module Rigor
|
|
|
57
60
|
"disable" => [],
|
|
58
61
|
"libraries" => [],
|
|
59
62
|
"signature_paths" => nil,
|
|
63
|
+
# The project's test roots: the directories (or files) holding its tests. `nil` (the default) auto-detects
|
|
64
|
+
# whichever of `spec/` and `test/` exist, the same way `signature_paths: nil` auto-detects `sig/`; `[]`
|
|
65
|
+
# declares that the project has none. `rigor sig-gen --params=observed` harvests call-site observations
|
|
66
|
+
# from them when no `--observe=PATH` is given. See {#resolved_test_paths}.
|
|
67
|
+
"test_paths" => nil,
|
|
68
|
+
# ADR-112 WD4 — `rigor sig-gen` settings. `inline_declared:` decides what happens to a method the inline
|
|
69
|
+
# reader (`rigor-rbs-inline`) already declares from `# @rbs` / `#:`. `write` (the default) copies the
|
|
70
|
+
# inline declaration into `sig/`, so the generated signature is the complete contract a gem ships;
|
|
71
|
+
# `skip` leaves every member that reader declares out of `sig/`, for a project whose Steep reads the
|
|
72
|
+
# same annotations (`inline: true` beside `signature "sig"`), where the copy would be a second
|
|
73
|
+
# declaration of one method (`DuplicatedMethodDefinition`). See {#sig_gen_inline_declared}.
|
|
74
|
+
"sig_gen" => {
|
|
75
|
+
"inline_declared" => "write"
|
|
76
|
+
},
|
|
60
77
|
# ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit
|
|
61
78
|
# files that the analyzer walks before per-file inference so patched-method declarations are visible
|
|
62
79
|
# across the project (e.g. `lib/core_ext/string_extensions.rb`). Slice 1 plumbing only — listed files
|
|
@@ -202,11 +219,12 @@ module Rigor
|
|
|
202
219
|
"auto_detect" => true
|
|
203
220
|
}
|
|
204
221
|
}.freeze
|
|
222
|
+
Ractor.make_shareable(DEFAULTS)
|
|
205
223
|
|
|
206
224
|
# Top-level keys whose values are file/directory paths that MUST be resolved relative to the config
|
|
207
225
|
# file's directory. `exclude:` is intentionally NOT in this list — its entries are glob patterns
|
|
208
226
|
# (`**/vendor/**`), not paths.
|
|
209
|
-
PATH_KEYS = %w[paths signature_paths pre_eval].freeze
|
|
227
|
+
PATH_KEYS = %w[paths signature_paths test_paths pre_eval].freeze
|
|
210
228
|
private_constant :PATH_KEYS
|
|
211
229
|
|
|
212
230
|
# Top-level keys this implementation DECLARES but never reads — see `docs/internal-spec/config.md`
|
|
@@ -251,7 +269,7 @@ module Rigor
|
|
|
251
269
|
|
|
252
270
|
attr_reader :target_ruby, :paths, :exclude_patterns, :plugins, :cache_path, :cache_max_bytes,
|
|
253
271
|
:cache_validation, :disabled_rules,
|
|
254
|
-
:libraries, :signature_paths, :fold_platform_specific_paths, :parameter_inference,
|
|
272
|
+
:libraries, :signature_paths, :test_paths, :fold_platform_specific_paths, :parameter_inference,
|
|
255
273
|
:plugins_isolation,
|
|
256
274
|
:plugins_io_network, :plugins_io_allowed_paths,
|
|
257
275
|
:plugins_io_allowed_url_hosts,
|
|
@@ -260,7 +278,7 @@ module Rigor
|
|
|
260
278
|
:dependencies, :parallel_workers,
|
|
261
279
|
:bundler_bundle_path, :bundler_auto_detect, :bundler_lockfile,
|
|
262
280
|
:rbs_collection_lockfile, :rbs_collection_auto_detect,
|
|
263
|
-
:pre_eval, :baseline_path, :effects,
|
|
281
|
+
:pre_eval, :baseline_path, :effects, :sig_gen_inline_declared,
|
|
264
282
|
:effects_snapshot_path, :effects_snapshot_reach, :effects_snapshot_gate, :effects_tolerated,
|
|
265
283
|
:effects_labels, :effects_attribution, :effects_envelopes
|
|
266
284
|
|
|
@@ -282,6 +300,16 @@ module Rigor
|
|
|
282
300
|
@effects_check
|
|
283
301
|
end
|
|
284
302
|
|
|
303
|
+
# The test roots a run uses: the declared `test_paths:` as written (already resolved against the
|
|
304
|
+
# config file's directory by {.load}), or — when the key is unset — whichever of
|
|
305
|
+
# {CONVENTIONAL_TEST_PATHS} is a directory under `root`, as absolute paths. `root` is the directory
|
|
306
|
+
# the run treats as the project root (the CLI's CWD), as for `signature_paths:` auto-detection.
|
|
307
|
+
def resolved_test_paths(root: Dir.pwd)
|
|
308
|
+
return test_paths unless test_paths.nil?
|
|
309
|
+
|
|
310
|
+
CONVENTIONAL_TEST_PATHS.map { |dir| File.expand_path(dir, root) }.select { |dir| File.directory?(dir) }
|
|
311
|
+
end
|
|
312
|
+
|
|
285
313
|
# Loads a configuration file.
|
|
286
314
|
#
|
|
287
315
|
# `path == nil` triggers auto-discovery against {DISCOVERY_ORDER}. The first present file in that list is
|
|
@@ -509,6 +537,9 @@ module Rigor
|
|
|
509
537
|
@libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
|
|
510
538
|
sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
|
|
511
539
|
@signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
|
|
540
|
+
test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
|
|
541
|
+
@test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
|
|
542
|
+
@sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
|
|
512
543
|
@pre_eval = expand_pre_eval_entries(
|
|
513
544
|
Array(data.fetch("pre_eval", DEFAULTS.fetch("pre_eval"))).map(&:to_s)
|
|
514
545
|
)
|
|
@@ -593,6 +624,9 @@ module Rigor
|
|
|
593
624
|
"disable" => disabled_rules,
|
|
594
625
|
"libraries" => libraries,
|
|
595
626
|
"signature_paths" => signature_paths,
|
|
627
|
+
# `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
|
|
628
|
+
# incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
|
|
629
|
+
# invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
|
|
596
630
|
"pre_eval" => pre_eval,
|
|
597
631
|
"fold_platform_specific_paths" => fold_platform_specific_paths,
|
|
598
632
|
"parameter_inference" => parameter_inference,
|
|
@@ -980,6 +1014,35 @@ module Rigor
|
|
|
980
1014
|
VALID_CACHE_VALIDATIONS = %w[auto stat digest].freeze
|
|
981
1015
|
private_constant :VALID_CACHE_VALIDATIONS
|
|
982
1016
|
|
|
1017
|
+
# The `sig_gen.inline_declared:` values, as the Symbols {#sig_gen_inline_declared} answers.
|
|
1018
|
+
VALID_SIG_GEN_INLINE_DECLARED = %w[write skip].freeze
|
|
1019
|
+
private_constant :VALID_SIG_GEN_INLINE_DECLARED
|
|
1020
|
+
|
|
1021
|
+
# Rejects rather than fails soft: an unrecognised value read as `write` would put a second declaration of
|
|
1022
|
+
# every inline-annotated method into `sig/` for exactly the project that asked for none. A `sig_gen:`
|
|
1023
|
+
# block that omits the key, or is written empty, keeps the default.
|
|
1024
|
+
def coerce_sig_gen_inline_declared(section)
|
|
1025
|
+
section = {} if section.nil?
|
|
1026
|
+
raise ConfigurationError, "sig_gen must be a mapping, got #{section.inspect}" unless section.is_a?(Hash)
|
|
1027
|
+
|
|
1028
|
+
# The schema closes this object (`additionalProperties: false`); a misspelled key here would otherwise
|
|
1029
|
+
# load as the default and write exactly what `inline_declare: skip` was meant to prevent.
|
|
1030
|
+
unknown = section.keys.map(&:to_s) - DEFAULTS.fetch("sig_gen").keys
|
|
1031
|
+
unless unknown.empty?
|
|
1032
|
+
raise ConfigurationError,
|
|
1033
|
+
"sig_gen has unknown key(s) #{unknown.inspect}; known keys: #{DEFAULTS.fetch('sig_gen').keys.inspect}"
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
value = section.fetch("inline_declared", DEFAULTS.dig("sig_gen", "inline_declared")).to_s
|
|
1037
|
+
unless VALID_SIG_GEN_INLINE_DECLARED.include?(value)
|
|
1038
|
+
raise ConfigurationError,
|
|
1039
|
+
"sig_gen.inline_declared must be one of #{VALID_SIG_GEN_INLINE_DECLARED.inspect}, " \
|
|
1040
|
+
"got #{section['inline_declared'].inspect}"
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
value.to_sym
|
|
1044
|
+
end
|
|
1045
|
+
|
|
983
1046
|
def coerce_cache_validation(value)
|
|
984
1047
|
str = value.to_s
|
|
985
1048
|
VALID_CACHE_VALIDATIONS.include?(str) ? str : "auto"
|