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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
require_relative "../configuration"
|
|
6
|
+
require_relative "../rbs_extended"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module SigGen
|
|
10
|
+
# The methods the inline reader declares for each analysed file (ADR-112 WD4): what `# @rbs` / `#:` in a
|
|
11
|
+
# `.rb` file says, member by member, as `rigor sig-gen` needs it to copy that declaration into `sig/`.
|
|
12
|
+
#
|
|
13
|
+
# Read from the RBS the `rigor-rbs-inline` synthesizer contributed (`RbsLoader#virtual_rbs`), not from the
|
|
14
|
+
# built environment. The environment is the wrong witness twice over: once `sig/` declares a member, the
|
|
15
|
+
# loader stands one of the two down before the build (ADR-112 WD5), so the declaration a stale `sig/` copy
|
|
16
|
+
# should be compared against may no longer be there to read; and a member's origin is only recoverable off
|
|
17
|
+
# its location, which the ADR-54 environment cache does not keep. The synthesized text is the loader's input,
|
|
18
|
+
# identical on every run.
|
|
19
|
+
#
|
|
20
|
+
# A member the synthesizer marks `rigor:v1:inferred-signature` is in the text only because rbs-inline
|
|
21
|
+
# declares every `def` of a file it reads: the author annotated nothing on it, so it is present here
|
|
22
|
+
# ({#lookup} finds it, which is what `sig_gen.inline_declared: skip` needs) but not {Member#declared?}.
|
|
23
|
+
class InlineDeclarations
|
|
24
|
+
# One inline-declared method.
|
|
25
|
+
#
|
|
26
|
+
# - `method_types` — the declared overloads (`RBS::MethodType`, names as written, unresolved).
|
|
27
|
+
# - `annotations` — the annotation strings the author wrote on the member (`deprecated` for
|
|
28
|
+
# `%a{deprecated}`), without the synthesizer's own `rigor:v1:inferred-*` markers.
|
|
29
|
+
# - `return_inferred` — the author declared the parameters and not the return: rbs-inline defaulted it,
|
|
30
|
+
# and the synthesizer marked it `rigor:v1:inferred-return`.
|
|
31
|
+
# - `signature_inferred` — every type slot defaulted; the author said nothing about this member.
|
|
32
|
+
# - `attr_line` — for an attribute, the member in RBS's own `attr_*` spelling (`attr_reader name: String`),
|
|
33
|
+
# which is what replaces an `attr_*` declaration in `sig/` under `--overwrite`; nil for a `def`.
|
|
34
|
+
Member = Data.define(:method_types, :annotations, :return_inferred, :signature_inferred, :attr_line) do
|
|
35
|
+
def declared?
|
|
36
|
+
!signature_inferred
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The member's annotations as RBS lines, each in a delimiter its content does not close.
|
|
40
|
+
def annotation_lines
|
|
41
|
+
annotations.filter_map { |string| InlineDeclarations.annotation_line(string) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The synthesizer channel this index reads; another plugin's virtual RBS is not an inline declaration.
|
|
46
|
+
SYNTHESIZER_ID = Configuration::AUTOWIRED_RBS_INLINE_ID
|
|
47
|
+
private_constant :SYNTHESIZER_ID
|
|
48
|
+
|
|
49
|
+
MARKERS = [RbsExtended::INFERRED_RETURN_DIRECTIVE, RbsExtended::INFERRED_SIGNATURE_DIRECTIVE].freeze
|
|
50
|
+
private_constant :MARKERS
|
|
51
|
+
|
|
52
|
+
# rbs lexes `%a` with each of these pairs, up to the first matching closer and with no escape
|
|
53
|
+
# (`rbs/src/lexer.re`), so an annotation is spelled in the first pair its content does not close.
|
|
54
|
+
ANNOTATION_DELIMITERS = Ractor.make_shareable([%w[{ }], %w[( )], %w[[ ]], %w[< >], %w[| |]])
|
|
55
|
+
private_constant :ANNOTATION_DELIMITERS
|
|
56
|
+
|
|
57
|
+
def self.annotation_line(string)
|
|
58
|
+
pair = ANNOTATION_DELIMITERS.find { |_open, close| !string.include?(close) }
|
|
59
|
+
pair && "%a#{pair[0]}#{string}#{pair[1]}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Fail-soft: an environment with no loader, or a loader this index cannot read, declares nothing inline,
|
|
63
|
+
# which is the answer for a project without the plugin.
|
|
64
|
+
def self.build(environment)
|
|
65
|
+
loader = environment&.rbs_loader
|
|
66
|
+
new(loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : [])
|
|
67
|
+
rescue StandardError
|
|
68
|
+
new([])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def initialize(virtual_rbs)
|
|
72
|
+
@by_path = {}
|
|
73
|
+
@generic_classes = {}
|
|
74
|
+
virtual_rbs.each do |name, content|
|
|
75
|
+
prefix, plugin_id, path = name.to_s.split(":", 3)
|
|
76
|
+
next unless prefix == "virtual" && plugin_id == SYNTHESIZER_ID && path
|
|
77
|
+
|
|
78
|
+
decls = parse(content.to_s)
|
|
79
|
+
next if decls.nil?
|
|
80
|
+
|
|
81
|
+
table = (@by_path[path] ||= {})
|
|
82
|
+
each_member(decls, []) { |class_name, member| record(table, class_name, member) }
|
|
83
|
+
end
|
|
84
|
+
@by_path.freeze
|
|
85
|
+
@generic_classes.freeze
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# `{ class name => [type parameter names] }` for the classes and modules an inline declaration gives type
|
|
89
|
+
# parameters (`# @rbs generic T`). sig-gen does not write a class's type parameters, and a `sig/` header
|
|
90
|
+
# without them makes rbs reject the class (`GenericParameterMismatchError`), so the generator writes nothing
|
|
91
|
+
# that would open one of these, nor members into a `sig/` declaration that names the parameters otherwise.
|
|
92
|
+
attr_reader :generic_classes
|
|
93
|
+
|
|
94
|
+
# @return the inline declaration of `class_name`'s `method_name` on the `kind` side, declared in the file
|
|
95
|
+
# at `path` — or nil.
|
|
96
|
+
def lookup(path, class_name, method_name, kind)
|
|
97
|
+
@by_path.dig(path.to_s, [class_name, method_name, kind])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def parse(content)
|
|
103
|
+
return nil if content.empty? || !content.valid_encoding?
|
|
104
|
+
|
|
105
|
+
_buffer, _directives, decls = ::RBS::Parser.parse_signature(content)
|
|
106
|
+
decls
|
|
107
|
+
rescue ::RBS::BaseError
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def each_member(decls, prefix, &block)
|
|
112
|
+
decls.each do |decl|
|
|
113
|
+
next unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
|
|
114
|
+
|
|
115
|
+
inner = prefix + [decl.name.to_s.delete_prefix("::")]
|
|
116
|
+
@generic_classes[inner.join("::")] ||= decl.type_params.map(&:name) unless decl.type_params.empty?
|
|
117
|
+
decl.members.each { |member| block.call(inner.join("::"), member) }
|
|
118
|
+
each_member(decl.members, inner, &block)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def record(table, class_name, member)
|
|
123
|
+
method_entries(member).each do |method_name, kind, method_types|
|
|
124
|
+
table[[class_name, method_name, kind]] ||= build_member(member, method_types)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# `[method_name, kind, method_types]` per method a member defines, mirroring what the RBS definition
|
|
129
|
+
# builder files it under: an attribute is its reader and / or writer, `def self?.x` is both sides. An
|
|
130
|
+
# `overloading?` member (`def x: ... | ...`) composes with another declaration rather than being one,
|
|
131
|
+
# and an alias declares no signature of its own, so neither is recorded.
|
|
132
|
+
def method_entries(member)
|
|
133
|
+
case member
|
|
134
|
+
when ::RBS::AST::Members::MethodDefinition then definition_entries(member)
|
|
135
|
+
when ::RBS::AST::Members::AttrReader then [reader_entry(member)]
|
|
136
|
+
when ::RBS::AST::Members::AttrWriter then [writer_entry(member)]
|
|
137
|
+
when ::RBS::AST::Members::AttrAccessor then [reader_entry(member), writer_entry(member)]
|
|
138
|
+
else []
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def definition_entries(member)
|
|
143
|
+
return [] if member.overloading?
|
|
144
|
+
|
|
145
|
+
types = member.overloads.map(&:method_type)
|
|
146
|
+
kinds = member.kind == :singleton_instance ? %i[instance singleton] : [member.kind]
|
|
147
|
+
kinds.map { |kind| [member.name, kind, types] }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def reader_entry(member)
|
|
151
|
+
[member.name, member.kind, [::RBS::Parser.parse_method_type("() -> #{member.type}")]]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def writer_entry(member)
|
|
155
|
+
[:"#{member.name}=", member.kind, [::RBS::Parser.parse_method_type("(#{member.type}) -> #{member.type}")]]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
ATTR_KEYWORDS = {
|
|
159
|
+
::RBS::AST::Members::AttrReader => "attr_reader",
|
|
160
|
+
::RBS::AST::Members::AttrWriter => "attr_writer",
|
|
161
|
+
::RBS::AST::Members::AttrAccessor => "attr_accessor"
|
|
162
|
+
}.freeze
|
|
163
|
+
private_constant :ATTR_KEYWORDS
|
|
164
|
+
|
|
165
|
+
def attr_line(member)
|
|
166
|
+
keyword = ATTR_KEYWORDS[member.class]
|
|
167
|
+
return nil if keyword.nil?
|
|
168
|
+
|
|
169
|
+
"#{keyword} #{'self.' if member.kind == :singleton}#{member.name}: #{member.type}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def build_member(member, method_types)
|
|
173
|
+
strings = member.annotations.map { |annotation| annotation.string.to_s.strip }
|
|
174
|
+
Member.new(
|
|
175
|
+
method_types: method_types.freeze,
|
|
176
|
+
annotations: (strings - MARKERS).freeze,
|
|
177
|
+
return_inferred: strings.include?(RbsExtended::INFERRED_RETURN_DIRECTIVE),
|
|
178
|
+
signature_inferred: strings.include?(RbsExtended::INFERRED_SIGNATURE_DIRECTIVE),
|
|
179
|
+
attr_line: attr_line(member)
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "classification"
|
|
4
|
+
require_relative "effect_annotation"
|
|
5
|
+
|
|
3
6
|
module Rigor
|
|
4
7
|
module SigGen
|
|
5
8
|
# Per-method record produced by the generator.
|
|
@@ -15,17 +18,28 @@ module Rigor
|
|
|
15
18
|
# - `inferred_return` — `Rigor::Type` instance (or `nil` when the inference pass disqualified the def).
|
|
16
19
|
# - `declared_return_rbs` — the existing RBS-declared return spelling, or `nil` when no RBS declares the
|
|
17
20
|
# method.
|
|
21
|
+
# - `declared_rbs` — for an `inline_overwrite`, or a `tighter_return` over the class's own declaration, the
|
|
22
|
+
# whole declared line the proposal replaces, so `--diff` and `--check` can show both; `nil` otherwise.
|
|
23
|
+
# - `declared_annotations` — for a member declared inline, the annotations the author wrote on it
|
|
24
|
+
# (`%a{deprecated}`), rendered above `rbs` like `annotations`. Empty otherwise.
|
|
18
25
|
# - `rbs` — the rendered RBS one-liner the generator would emit (`nil` for skipped / equivalent rows).
|
|
19
26
|
# - `skip_reason` — one of {Classification::SKIP_DIAGNOSTIC_IDS} keys when classification is `:skipped`,
|
|
20
27
|
# else `nil`.
|
|
28
|
+
# - `annotations` — RBS annotation lines to render ABOVE `rbs` (`%a{pure}` /
|
|
29
|
+
# `%a{rigor:v1:effect …}`, ADR-103 WD9). Empty unless the effects opt-in is on and the method's
|
|
30
|
+
# summary earned one; see {EffectAnnotation}.
|
|
31
|
+
# - `effect_reason` — one of {EffectAnnotation::DIAGNOSTIC_IDS} keys saying why `annotations` is what
|
|
32
|
+
# it is, or `nil` when the run had nothing to say about this method's effects.
|
|
21
33
|
class MethodCandidate
|
|
22
34
|
attr_reader :path, :class_name, :method_name, :kind, :classification,
|
|
23
35
|
:inferred_return, :declared_return_rbs, :rbs, :skip_reason,
|
|
24
|
-
:namespace_kinds, :class_shells, :class_superclasses
|
|
36
|
+
:namespace_kinds, :class_shells, :class_superclasses,
|
|
37
|
+
:annotations, :effect_reason, :declared_rbs, :declared_annotations
|
|
25
38
|
|
|
26
39
|
def initialize(path:, class_name:, method_name:, kind:, classification:, # rubocop:disable Metrics/ParameterLists
|
|
27
40
|
inferred_return: nil, declared_return_rbs: nil, rbs: nil, skip_reason: nil,
|
|
28
|
-
namespace_kinds: {}, class_shells: [], class_superclasses: {}
|
|
41
|
+
namespace_kinds: {}, class_shells: [], class_superclasses: {},
|
|
42
|
+
annotations: [], effect_reason: nil, declared_rbs: nil, declared_annotations: [])
|
|
29
43
|
@path = path
|
|
30
44
|
@class_name = class_name
|
|
31
45
|
@method_name = method_name
|
|
@@ -41,9 +55,32 @@ module Rigor
|
|
|
41
55
|
# superclasses appear; computed ones are absent. The Writer emits `class Bar < Base` for the leaf when
|
|
42
56
|
# present.
|
|
43
57
|
@class_superclasses = class_superclasses.freeze
|
|
58
|
+
@annotations = annotations.freeze
|
|
59
|
+
@effect_reason = effect_reason
|
|
60
|
+
@declared_rbs = declared_rbs
|
|
61
|
+
@declared_annotations = declared_annotations.freeze
|
|
44
62
|
freeze
|
|
45
63
|
end
|
|
46
64
|
|
|
65
|
+
# Every line this candidate contributes to a declaration body, annotations first. The single place
|
|
66
|
+
# the renderer and the writer agree that an annotation precedes the `def` line it binds.
|
|
67
|
+
def rbs_lines
|
|
68
|
+
@declared_annotations + @annotations + [@rbs].compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A copy carrying a different annotation decision. The generator's effect pass rebuilds rather than
|
|
72
|
+
# mutates, because a candidate is frozen the moment it is built.
|
|
73
|
+
def with_effect_annotation(annotations, reason)
|
|
74
|
+
self.class.new(
|
|
75
|
+
path: @path, class_name: @class_name, method_name: @method_name, kind: @kind,
|
|
76
|
+
classification: @classification, inferred_return: @inferred_return,
|
|
77
|
+
declared_return_rbs: @declared_return_rbs, rbs: @rbs, skip_reason: @skip_reason,
|
|
78
|
+
namespace_kinds: @namespace_kinds, class_shells: @class_shells,
|
|
79
|
+
class_superclasses: @class_superclasses, annotations: annotations, effect_reason: reason,
|
|
80
|
+
declared_rbs: @declared_rbs, declared_annotations: @declared_annotations
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
47
84
|
def to_h
|
|
48
85
|
{
|
|
49
86
|
file: path,
|
|
@@ -54,7 +91,14 @@ module Rigor
|
|
|
54
91
|
rbs: rbs,
|
|
55
92
|
inferred_return: inferred_return&.erase_to_rbs,
|
|
56
93
|
declared_return_rbs: declared_return_rbs,
|
|
57
|
-
|
|
94
|
+
declared_rbs: declared_rbs,
|
|
95
|
+
declared_annotations: declared_annotations.empty? ? nil : declared_annotations,
|
|
96
|
+
skip_reason: skip_reason ? Classification::SKIP_DIAGNOSTIC_IDS.fetch(skip_reason) : nil,
|
|
97
|
+
# Named fields rather than a merge into `rbs`: a consumer routing on the annotation has to be
|
|
98
|
+
# able to find it without re-lexing the rendered line. Both are absent (not empty / null) when
|
|
99
|
+
# the run had no effect answer, so an effects-off payload is byte-identical to a pre-#391 one.
|
|
100
|
+
effect_annotations: annotations.empty? ? nil : annotations,
|
|
101
|
+
effect_reason: effect_reason ? EffectAnnotation::DIAGNOSTIC_IDS.fetch(effect_reason) : nil
|
|
58
102
|
}.compact
|
|
59
103
|
end
|
|
60
104
|
end
|
|
@@ -15,7 +15,7 @@ module Rigor
|
|
|
15
15
|
module SigGen
|
|
16
16
|
# ADR-14 slice 3 — caller-side argument-type observation collector.
|
|
17
17
|
#
|
|
18
|
-
# Walks the
|
|
18
|
+
# Walks the `--observe=PATH...` tree (default: the project's test roots), parses every `.rb` file with `Prism`,
|
|
19
19
|
# scope- indexes it the same way the main generator does, and records the per-call-site argument-type tuples
|
|
20
20
|
# for every `Prism::CallNode` whose receiver types as a `Type::Nominal`. The {Generator} consumes the
|
|
21
21
|
# resulting map to render `--params=observed` RBS:
|
|
@@ -74,9 +74,13 @@ module Rigor
|
|
|
74
74
|
when Classification::NEW_FILE then "[new-file]"
|
|
75
75
|
when Classification::TIGHTER_RETURN
|
|
76
76
|
"[tighter, was: #{candidate.declared_return_rbs}]"
|
|
77
|
+
when Classification::INLINE_OVERWRITE
|
|
78
|
+
"[inline-overwrite, was: #{candidate.declared_rbs}]"
|
|
77
79
|
end
|
|
78
80
|
@out.puts(" # #{tag}")
|
|
79
|
-
|
|
81
|
+
# Annotations first: an RBS annotation binds the declaration BELOW it, so `%a{pure}` printed
|
|
82
|
+
# after the `def` line would bind the next member — or nothing at all at the end of a class.
|
|
83
|
+
candidate.rbs_lines.each { |line| @out.puts(" #{line}") }
|
|
80
84
|
end
|
|
81
85
|
@out.puts("end")
|
|
82
86
|
end
|
|
@@ -101,13 +105,22 @@ module Rigor
|
|
|
101
105
|
|
|
102
106
|
candidates.each do |candidate|
|
|
103
107
|
@out.puts("--- #{candidate.path}: #{candidate.class_name}##{candidate.method_name}")
|
|
104
|
-
|
|
105
|
-
@out.puts("
|
|
106
|
-
@out.puts("+ #{candidate.rbs}")
|
|
108
|
+
render_removed_line(candidate)
|
|
109
|
+
candidate.rbs_lines.each { |line| @out.puts("+ #{line}") }
|
|
107
110
|
@out.puts
|
|
108
111
|
end
|
|
109
112
|
end
|
|
110
113
|
|
|
114
|
+
# A tighter return or an inline update replaces a whole `sig/` line, which it carries; any other row
|
|
115
|
+
# knows only the declared return.
|
|
116
|
+
def render_removed_line(candidate)
|
|
117
|
+
if candidate.declared_rbs
|
|
118
|
+
@out.puts("- #{candidate.declared_rbs}")
|
|
119
|
+
elsif candidate.declared_return_rbs
|
|
120
|
+
@out.puts("- def #{candidate.method_name}: () -> #{candidate.declared_return_rbs}")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
111
124
|
def render_json(candidates)
|
|
112
125
|
payload = { candidates: candidates.map(&:to_h) }
|
|
113
126
|
@out.puts(JSON.pretty_generate(payload))
|
|
@@ -117,16 +130,94 @@ module Rigor
|
|
|
117
130
|
|
|
118
131
|
# Renders the per-source-file outcomes of a `--write` run. Distinct from {#render} because the write
|
|
119
132
|
# path's reporting surface is action-oriented (created / updated / skipped) rather than candidate-oriented.
|
|
120
|
-
|
|
133
|
+
# @param refused — methods refused as `sig.skipped.inline-differs`; the JSON payload names them under
|
|
134
|
+
# `refused` (absent when there are none, so an ordinary payload is unchanged), and text mode leaves them
|
|
135
|
+
# to the command's stderr `REFUSED` lines.
|
|
136
|
+
def render_write(results:, format:, refused: [])
|
|
121
137
|
case format
|
|
122
|
-
when "json" then render_write_json(results)
|
|
138
|
+
when "json" then render_write_json(results, refused)
|
|
123
139
|
when "text" then render_write_text(results)
|
|
124
140
|
else raise ArgumentError, "unsupported format: #{format}"
|
|
125
141
|
end
|
|
126
142
|
end
|
|
127
143
|
|
|
144
|
+
# ADR-112 WD4 — `sig-gen --check`: the results of a dry-run `--write`. Only the targets `--write` would
|
|
145
|
+
# change (or refuse) are shown, each with the lines it would add; the verdict is the exit status, which
|
|
146
|
+
# the command derives from the same results ({.out_of_date}).
|
|
147
|
+
#
|
|
148
|
+
# @param refused — the methods the generator refused to reconcile (`sig.skipped.inline-differs`):
|
|
149
|
+
# `sig/` is not up to date while one stands, and `--write` cannot fix it.
|
|
150
|
+
def render_check(results:, format:, refused: [])
|
|
151
|
+
stale = self.class.out_of_date(results)
|
|
152
|
+
case format
|
|
153
|
+
when "json"
|
|
154
|
+
@out.puts(JSON.pretty_generate({ up_to_date: stale.empty? && refused.empty?,
|
|
155
|
+
results: stale.map { |r| check_entry(r) },
|
|
156
|
+
refused: refused.map(&:to_h) }))
|
|
157
|
+
when "text" then render_check_text(stale, refused)
|
|
158
|
+
else raise ArgumentError, "unsupported format: #{format}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# One line per method sig-gen refused to reconcile with its `sig/` copy. Shared by `--write` (on stderr,
|
|
163
|
+
# next to the write report) and `--check`.
|
|
164
|
+
def self.refusal_lines(refused)
|
|
165
|
+
refused.map do |candidate|
|
|
166
|
+
separator = candidate.kind == :singleton ? "." : "#"
|
|
167
|
+
"REFUSED #{candidate.path}: #{candidate.class_name}#{separator}#{candidate.method_name} — its inline " \
|
|
168
|
+
"declaration and its sig/ declaration disagree, so neither was changed " \
|
|
169
|
+
"(#{Classification::SKIP_DIAGNOSTIC_IDS.fetch(candidate.skip_reason)}). Make them agree, or pass " \
|
|
170
|
+
"--overwrite to replace the sig/ member with the inline declaration; `rigor explain " \
|
|
171
|
+
"#{Classification::SKIP_DIAGNOSTIC_IDS.fetch(candidate.skip_reason)}` says when that is not enough."
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The results that make a `--check` fail: a target `--write` would create or change, and one it would
|
|
176
|
+
# refuse, since a write that cannot happen is not an up-to-date `sig/` either.
|
|
177
|
+
def self.out_of_date(results)
|
|
178
|
+
results.reject { |result| %i[noop skipped_outside_sig_root].include?(result.action) }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Nothing was written, so the entry must not read like one that was: `created` / `updated` become
|
|
182
|
+
# `would_create` / `would_update`. A refusal keeps its action — `--write` would refuse the same way.
|
|
183
|
+
CHECK_ACTIONS = { created: "would_create", updated: "would_update" }.freeze
|
|
184
|
+
private_constant :CHECK_ACTIONS
|
|
185
|
+
|
|
128
186
|
private
|
|
129
187
|
|
|
188
|
+
def check_entry(result)
|
|
189
|
+
entry = result.to_h
|
|
190
|
+
entry[:action] = CHECK_ACTIONS.fetch(result.action, entry[:action])
|
|
191
|
+
entry
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def render_check_text(stale, refused)
|
|
195
|
+
if stale.empty? && refused.empty?
|
|
196
|
+
@out.puts("sig/ is up to date")
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
self.class.refusal_lines(refused).each { |line| @out.puts(line) }
|
|
201
|
+
|
|
202
|
+
stale.each do |result|
|
|
203
|
+
case result.action
|
|
204
|
+
when :created, :updated then render_check_change(result)
|
|
205
|
+
when :skipped_invalid_rbs then render_write_invalid(result)
|
|
206
|
+
when :skipped_invalid_encoding then render_write_invalid_encoding(result)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def render_check_change(result)
|
|
212
|
+
counts = result.action == :created ? "#{result.applied.size} method(s)" : applied_counts(result)
|
|
213
|
+
verb = result.action == :created ? "would create" : "would update"
|
|
214
|
+
@out.puts("#{verb} #{result.target_path} (#{counts})")
|
|
215
|
+
result.applied.each do |candidate|
|
|
216
|
+
@out.puts(" - #{candidate.declared_rbs}") if candidate.declared_rbs
|
|
217
|
+
candidate.rbs_lines.each { |line| @out.puts(" + #{line}") }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
130
221
|
def render_write_text(results)
|
|
131
222
|
if results.all? { |r| r.action == :noop }
|
|
132
223
|
@out.puts("No changes")
|
|
@@ -144,13 +235,35 @@ module Rigor
|
|
|
144
235
|
end
|
|
145
236
|
end
|
|
146
237
|
|
|
238
|
+
# `+N` counts added lines; an existing line replaced (`--overwrite`, or an inline update) is counted apart,
|
|
239
|
+
# because "added 2" when one of them rewrote a line the project already had would understate the change.
|
|
240
|
+
def applied_counts(result)
|
|
241
|
+
added = "+#{result.applied.size - result.replaced.size}"
|
|
242
|
+
result.replaced.empty? ? added : "#{added}, replaced #{result.replaced.size}"
|
|
243
|
+
end
|
|
244
|
+
|
|
147
245
|
def render_write_created(result)
|
|
148
246
|
@out.puts("created #{result.target_path} (#{result.applied.size} method(s))")
|
|
149
247
|
end
|
|
150
248
|
|
|
151
249
|
def render_write_updated(result)
|
|
152
|
-
@out.puts("updated #{result.target_path} (
|
|
250
|
+
@out.puts("updated #{result.target_path} (#{applied_counts(result)}, " \
|
|
153
251
|
"skipped #{result.skipped.size} user-authored)")
|
|
252
|
+
render_left_unreadable(result)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# ADR-103 WD9 — the declarations whose annotation region the writer refused to rewrite. Named per
|
|
256
|
+
# method rather than counted: the fix is a human reading one existing annotation and deciding what
|
|
257
|
+
# it should say, and there is no count of those a reader could act on.
|
|
258
|
+
def render_left_unreadable(result)
|
|
259
|
+
return if result.left_unreadable.empty?
|
|
260
|
+
|
|
261
|
+
@out.puts(" left #{result.left_unreadable.size} existing annotation(s) byte-untouched " \
|
|
262
|
+
"(sig.effect.left-unreadable):")
|
|
263
|
+
result.left_unreadable.each do |candidate|
|
|
264
|
+
@out.puts(" #{candidate.class_name}##{candidate.method_name} — " \
|
|
265
|
+
"would have emitted #{candidate.annotations.join(' ')}")
|
|
266
|
+
end
|
|
154
267
|
end
|
|
155
268
|
|
|
156
269
|
def render_write_skipped(result)
|
|
@@ -173,8 +286,10 @@ module Rigor
|
|
|
173
286
|
@out.puts(" Re-save the file as UTF-8 and re-run; sig-gen never modifies a file it cannot read faithfully.")
|
|
174
287
|
end
|
|
175
288
|
|
|
176
|
-
def render_write_json(results)
|
|
177
|
-
|
|
289
|
+
def render_write_json(results, refused)
|
|
290
|
+
payload = { results: results.map(&:to_h) }
|
|
291
|
+
payload[:refused] = refused.map(&:to_h) unless refused.empty?
|
|
292
|
+
@out.puts(JSON.pretty_generate(payload))
|
|
178
293
|
end
|
|
179
294
|
end
|
|
180
295
|
end
|
|
@@ -52,7 +52,7 @@ module Rigor
|
|
|
52
52
|
summary: "An RBS declaration for this method already exists and was left alone.",
|
|
53
53
|
explanation: "Not a gap. The project already states a type for this member, and sig-gen does not " \
|
|
54
54
|
"overwrite a hand-authored contract without being told to. Where `sig/` and an inline " \
|
|
55
|
-
"annotation both declare the member,
|
|
55
|
+
"annotation both declare the member, ADR-112 WD5's consistency rule decides which binds.",
|
|
56
56
|
next_step: "Read the existing declaration. If it disagrees with what Rigor infers, raise the " \
|
|
57
57
|
"disagreement rather than silently retyping it; pass `--overwrite` only once you have " \
|
|
58
58
|
"decided the generated type is the better contract."
|
|
@@ -84,6 +84,49 @@ module Rigor
|
|
|
84
84
|
"defines it, or add the project file that declares it to the analysed paths — then " \
|
|
85
85
|
"re-run `rigor sig-gen`."
|
|
86
86
|
),
|
|
87
|
+
"sig.skipped.inline-declared" => Entry.new(
|
|
88
|
+
id: "sig.skipped.inline-declared",
|
|
89
|
+
summary: "The member is declared inline, and `sig_gen.inline_declared: skip` keeps it out of `sig/`.",
|
|
90
|
+
explanation: "Not a gap. The project asked sig-gen to leave every method its inline `# @rbs` / `#:` " \
|
|
91
|
+
"reader declares out of `sig/`, the setting for a project whose Steep reads the same " \
|
|
92
|
+
"annotations (`inline: true` beside `signature \"sig\"`), where a `sig/` copy would " \
|
|
93
|
+
"declare the method twice (`DuplicatedMethodDefinition`). Rigor still reads the inline " \
|
|
94
|
+
"declaration when it analyses the source; a consumer reading only the shipped `sig/` " \
|
|
95
|
+
"does not see this method.",
|
|
96
|
+
next_step: "Nothing, if the setting is intended. To ship the member in `sig/`, remove " \
|
|
97
|
+
"`sig_gen.inline_declared: skip` from `.rigor.yml` and stop Steep from reading the " \
|
|
98
|
+
"annotations of the files you generate for."
|
|
99
|
+
),
|
|
100
|
+
"sig.skipped.inline-generic-class" => Entry.new(
|
|
101
|
+
id: "sig.skipped.inline-generic-class",
|
|
102
|
+
summary: "The class is generic by an inline declaration, and sig-gen does not write type parameters.",
|
|
103
|
+
explanation: "The class (or one it is nested in) takes type parameters from `# @rbs generic`, and " \
|
|
104
|
+
"`sig/` either does not declare it yet or declares it with differently named parameters. " \
|
|
105
|
+
"sig-gen would have to open it with a header that has no type parameters, which rbs " \
|
|
106
|
+
"rejects (`GenericParameterMismatchError`) — the class, and every class whose signature " \
|
|
107
|
+
"mentions it, would fail its definition build and read `Dynamic[top]` — or write members " \
|
|
108
|
+
"whose `T` the `sig/` declaration does not bind. The inline declaration still binds when " \
|
|
109
|
+
"the source is analysed.",
|
|
110
|
+
next_step: "Declare the class in `sig/` with the same type parameters as the inline declaration " \
|
|
111
|
+
"(`class Box[T]` ... `end`) and re-run; sig-gen then writes the members into it."
|
|
112
|
+
),
|
|
113
|
+
"sig.skipped.inline-differs" => Entry.new(
|
|
114
|
+
id: "sig.skipped.inline-differs",
|
|
115
|
+
summary: "The method's inline declaration and its `sig/` declaration disagree; neither was changed.",
|
|
116
|
+
explanation: "The method is declared inline by `# @rbs` / `#:` and in `sig/` (a `def` or an `attr_*`), " \
|
|
117
|
+
"and the two do not state the same types, or `sig/` lacks an annotation written inline. " \
|
|
118
|
+
"Parameter names, union spelling and a `::` that does not change what a name resolves to " \
|
|
119
|
+
"are ignored; overload order is not, because RBS answers a call with the first overload " \
|
|
120
|
+
"that matches. For a parameter-only annotation (`# @rbs name: T`, no `return:`) only the " \
|
|
121
|
+
"parameters are compared, and the return follows the ordinary proposal rules. sig-gen " \
|
|
122
|
+
"does not presume either side right, so it refuses, and `--write` and `--check` exit 1.",
|
|
123
|
+
next_step: "Decide which declaration is right. To keep the inline one, re-run with `--overwrite`, which " \
|
|
124
|
+
"replaces the whole `sig/` member with it (an overload written only in `sig/` is dropped); " \
|
|
125
|
+
"to keep the `sig/` one, edit the annotation to match, or delete it. A parameter-only " \
|
|
126
|
+
"annotation whose parameters differ stays refused under `--overwrite`, because its return " \
|
|
127
|
+
"would be inferred under the `sig/` parameters: delete the `sig/` member and re-run, or add " \
|
|
128
|
+
"`# @rbs return:` so nothing is inferred."
|
|
129
|
+
),
|
|
87
130
|
"sig.skipped.overridden-by-unsigned-subclass" => Entry.new(
|
|
88
131
|
id: "sig.skipped.overridden-by-unsigned-subclass",
|
|
89
132
|
summary: "A subclass overrides this method and its override was not itself emitted.",
|
|
@@ -13,23 +13,32 @@ module Rigor
|
|
|
13
13
|
# - `action` — one of `:created` / `:updated` / `:noop` / `:skipped_outside_sig_root` /
|
|
14
14
|
# `:skipped_invalid_rbs` / `:skipped_invalid_encoding`.
|
|
15
15
|
# - `applied` — the {MethodCandidate}s that actually landed on disk.
|
|
16
|
+
# - `replaced` — the subset of `applied` that replaced an existing declaration rather than adding one
|
|
17
|
+
# (`--overwrite`'s tighter returns, and inline updates).
|
|
16
18
|
# - `skipped` — the {MethodCandidate}s the writer declined (e.g. tighter-return without `--overwrite`). Each
|
|
17
19
|
# entry pairs the candidate with a skip reason keyword (`:user_authored`).
|
|
20
|
+
# - `left_unreadable` — the {MethodCandidate}s whose effect annotation was NOT written because the
|
|
21
|
+
# target declaration already carries annotations, so its bytes were left alone
|
|
22
|
+
# (`sig.effect.left-unreadable`; see {Writer#splice_annotations}). The signature line itself was
|
|
23
|
+
# still updated.
|
|
18
24
|
# - `error` — the refusal cause, when `action` is a refusal: for `:skipped_invalid_rbs` the file the writer
|
|
19
25
|
# assembled does not parse, so it was NOT written (writing it would poison the project's sig tree — the
|
|
20
26
|
# consumer quarantines an unparseable `.rbs`, taking every other type in that file down with it); for
|
|
21
27
|
# `:skipped_invalid_encoding` the EXISTING target file is not valid UTF-8, so the writer refuses to merge
|
|
22
28
|
# into content it cannot read faithfully.
|
|
23
29
|
class WriteResult
|
|
24
|
-
attr_reader :source_path, :target_path, :action, :applied, :skipped, :error
|
|
30
|
+
attr_reader :source_path, :target_path, :action, :applied, :skipped, :error, :left_unreadable, :replaced
|
|
25
31
|
|
|
26
|
-
def initialize(source_path:, target_path:, action:, applied: [], skipped: [], error: nil
|
|
32
|
+
def initialize(source_path:, target_path:, action:, applied: [], skipped: [], error: nil, # rubocop:disable Metrics/ParameterLists
|
|
33
|
+
left_unreadable: [], replaced: [])
|
|
27
34
|
@source_path = source_path
|
|
28
35
|
@target_path = target_path
|
|
29
36
|
@action = action
|
|
30
37
|
@applied = applied.freeze
|
|
31
38
|
@skipped = skipped.freeze
|
|
32
39
|
@error = error
|
|
40
|
+
@left_unreadable = left_unreadable.freeze
|
|
41
|
+
@replaced = replaced.freeze
|
|
33
42
|
freeze
|
|
34
43
|
end
|
|
35
44
|
|
|
@@ -40,7 +49,14 @@ module Rigor
|
|
|
40
49
|
action: action.to_s,
|
|
41
50
|
applied: applied.map(&:to_h),
|
|
42
51
|
skipped: skipped.map { |c, reason| c.to_h.merge(write_skip_reason: reason.to_s) }
|
|
43
|
-
}.tap
|
|
52
|
+
}.tap do |h|
|
|
53
|
+
h[:error] = error if error
|
|
54
|
+
# Absent when nothing was replaced, so a run that only adds lines keeps its pre-#1076 payload.
|
|
55
|
+
h[:replaced] = replaced.map(&:to_h) unless replaced.empty?
|
|
56
|
+
# Absent rather than empty when nothing was left: an effects-off payload stays byte-identical
|
|
57
|
+
# to a pre-#391 one.
|
|
58
|
+
h[:effect_left_unreadable] = left_unreadable.map(&:to_h) unless left_unreadable.empty?
|
|
59
|
+
end
|
|
44
60
|
end
|
|
45
61
|
end
|
|
46
62
|
end
|