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
|
@@ -192,18 +192,12 @@ module Rigor
|
|
|
192
192
|
publish_reachability_roots(services, table)
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
# File-level only: the
|
|
196
|
-
# engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built
|
|
197
|
-
# as the node-rule file context.
|
|
195
|
+
# File-level only: the run-scoped load-error disclosure (#1051). Per-call helper validation runs over
|
|
196
|
+
# the engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built
|
|
197
|
+
# once as the node-rule file context.
|
|
198
198
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
199
199
|
table = helper_table_or_nil
|
|
200
|
-
if table.nil? && @load_error
|
|
201
|
-
return [] if @load_error_emitted
|
|
202
|
-
|
|
203
|
-
@load_error_emitted = true
|
|
204
|
-
return [load_error_diagnostic(path)]
|
|
205
|
-
end
|
|
206
|
-
|
|
200
|
+
disclose_load_error if table.nil? && @load_error
|
|
207
201
|
[]
|
|
208
202
|
end
|
|
209
203
|
|
|
@@ -265,13 +259,11 @@ module Rigor
|
|
|
265
259
|
nil
|
|
266
260
|
end
|
|
267
261
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
rule: "load-error"
|
|
274
|
-
)
|
|
262
|
+
# #1051 — "the routes file did not load" is a fact about the project, not about the file that
|
|
263
|
+
# happened to be analysed first by this instance, so it goes through the run-scoped channel and the
|
|
264
|
+
# engine positions it at `.rigor.yml:1:1`, once per run rather than once per fork-pool worker.
|
|
265
|
+
def disclose_load_error
|
|
266
|
+
disclose_once(:routes_load_failed, message: @load_error, severity: :warning, rule: "load-error")
|
|
275
267
|
end
|
|
276
268
|
end
|
|
277
269
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class RbsInline < Base
|
|
8
|
+
# Issue #998 — the two same-line `%a{}` spellings rbs's built-in `RBS::InlineParser` and Steep's inline
|
|
9
|
+
# mode accept, repaired into the shape the `rbs-inline` gem's writer honours:
|
|
10
|
+
#
|
|
11
|
+
# - `# @rbs %a{…} () -> String` — the gem's `@rbs %a{}` rule lexes the annotations and stops, so the
|
|
12
|
+
# method type after them is discarded and the method renders `() -> untyped`;
|
|
13
|
+
# - `#: %a{…} () -> String` — the gem hands the whole payload to `RBS::Parser.parse_method_type`, which
|
|
14
|
+
# has no annotation prefix, so the line becomes a `SyntaxErrorAssertion` and nothing of it survives.
|
|
15
|
+
#
|
|
16
|
+
# Both were silent (ADR-32 WD12), and the second reached users as #997's "did not parse … DROPPED"
|
|
17
|
+
# notice on input that is valid RBS for the other two readers.
|
|
18
|
+
#
|
|
19
|
+
# This is a post-parse repair of the gem's annotation list, not a rewrite of the Ruby source. A source
|
|
20
|
+
# rewrite would have to put the annotation and the method type on two comment lines — the gem has no
|
|
21
|
+
# one-line spelling for the pair, which is the defect — and inserting a line moves every later line,
|
|
22
|
+
# including the ones a `#:` notice reports and the one a `def` uses to find its comment block.
|
|
23
|
+
#
|
|
24
|
+
# The repair is exact rather than heuristic because every step is the gem's own reading:
|
|
25
|
+
#
|
|
26
|
+
# - the leading `%a{…}` spellings are delimited by the gem's own `Tokenizer`, the lexer that already
|
|
27
|
+
# accepts them in the own-line form, so what counts as an annotation cannot drift from upstream;
|
|
28
|
+
# - each half is re-read by the gem's own `AnnotationParser` as the comment the author would have written
|
|
29
|
+
# in the own-line form (`# @rbs %a{…}`, then `# @rbs () -> String` or `#: () -> String`), on the same
|
|
30
|
+
# line and column, so the replacement annotations are indistinguishable from ones upstream built;
|
|
31
|
+
# - the split lands only when the remainder re-reads as a well-formed method type. Anything else keeps
|
|
32
|
+
# the gem's original annotation untouched, so a genuinely malformed line still reaches the WD12
|
|
33
|
+
# notices, and a split can never trade one silent loss for another.
|
|
34
|
+
module SameLineAnnotations
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# The `@rbs` marker as the gem's `annotation_comment?` detects it, with the whitespace before it.
|
|
38
|
+
RBS_MARKER = /\A\s*@rbs\b/
|
|
39
|
+
|
|
40
|
+
# Replaces, in `parsing_result.annotations`, every same-line form that splits cleanly with the
|
|
41
|
+
# annotation and the method type it spells. Idempotent: a repaired list has no same-line form left.
|
|
42
|
+
def split!(parsing_result)
|
|
43
|
+
return parsing_result unless parsing_result.is_a?(::RBS::Inline::AnnotationParser::ParsingResult)
|
|
44
|
+
|
|
45
|
+
parsing_result.annotations.replace(parsing_result.annotations.flat_map { |annotation| expand(annotation) })
|
|
46
|
+
parsing_result
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Every `ParsingResult` a parsed declaration tree holds, which are the objects the writer reads.
|
|
50
|
+
def each_parsing_result(nodes, &)
|
|
51
|
+
nodes.each do |node|
|
|
52
|
+
comments = node.respond_to?(:comments) ? node.comments : nil
|
|
53
|
+
yield comments if comments.is_a?(::RBS::Inline::AnnotationParser::ParsingResult)
|
|
54
|
+
each_parsing_result(node.members, &) if node.respond_to?(:members)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The text an `@rbs %a{…}` line carries after its annotations that the gem discarded — `nil` when
|
|
59
|
+
# there is none. Call it on an annotation {.split!} has already seen: a remainder that survives the
|
|
60
|
+
# split is one that did not read as a method type.
|
|
61
|
+
def dropped_remainder(annotation)
|
|
62
|
+
return nil unless annotation.is_a?(::RBS::Inline::AST::Annotations::RBSAnnotation)
|
|
63
|
+
|
|
64
|
+
parts = same_line_parts(annotation)
|
|
65
|
+
parts && parts[2].strip
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def expand(annotation)
|
|
69
|
+
parts = same_line_parts(annotation)
|
|
70
|
+
return [annotation] if parts.nil?
|
|
71
|
+
|
|
72
|
+
prefix, spellings, rest = parts
|
|
73
|
+
method_type = reparse(annotation, "##{prefix}#{rest}")
|
|
74
|
+
return [annotation] unless well_formed_method_type?(method_type)
|
|
75
|
+
|
|
76
|
+
# Re-read even when `annotation` already is the `@rbs %a{}` carrier: the original still spans the
|
|
77
|
+
# method type in its source, and {.dropped_remainder} reads exactly that to report a failed split.
|
|
78
|
+
carrier = reparse(annotation, "# @rbs #{spellings.join(' ')}")
|
|
79
|
+
return [annotation] unless carrier.is_a?(::RBS::Inline::AST::Annotations::RBSAnnotation)
|
|
80
|
+
|
|
81
|
+
[carrier, method_type]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# `[prefix, spellings, rest]` for an annotation whose payload opens with one or more `%a{…}` and
|
|
85
|
+
# continues with something else, or `nil`. `prefix` is the marker as written (`" @rbs"` or `":"`),
|
|
86
|
+
# so the re-read keeps the gem's continuation offset for a multi-line comment block.
|
|
87
|
+
def same_line_parts(annotation)
|
|
88
|
+
# `annotations` also holds the `CommentLines` of plain prose, which has no `source` to read.
|
|
89
|
+
prefix =
|
|
90
|
+
case annotation
|
|
91
|
+
when ::RBS::Inline::AST::Annotations::RBSAnnotation
|
|
92
|
+
annotation.source.string[RBS_MARKER]
|
|
93
|
+
when ::RBS::Inline::AST::Annotations::SyntaxErrorAssertion
|
|
94
|
+
":" if annotation.source.string.start_with?(":")
|
|
95
|
+
end
|
|
96
|
+
return nil if prefix.nil?
|
|
97
|
+
|
|
98
|
+
spellings, rest = leading_annotations(annotation.source.string.delete_prefix(prefix))
|
|
99
|
+
return nil if spellings.empty? || rest.strip.empty?
|
|
100
|
+
|
|
101
|
+
[prefix, spellings, rest]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The `%a{…}` tokens the gem's tokenizer reads at the head of `body`, and the unread text after them.
|
|
105
|
+
def leading_annotations(body)
|
|
106
|
+
tokenizer = ::RBS::Inline::AnnotationParser::Tokenizer.new(StringScanner.new(body))
|
|
107
|
+
trivia = ::RBS::Inline::AST::Tree.new(:rigor_same_line_annotations)
|
|
108
|
+
tokenizer.advance(trivia)
|
|
109
|
+
tokenizer.advance(trivia)
|
|
110
|
+
|
|
111
|
+
spellings = []
|
|
112
|
+
while tokenizer.type?(::RBS::Inline::AnnotationParser::Tokens::T_ANNOTATION)
|
|
113
|
+
spellings << tokenizer.lookahead1[1]
|
|
114
|
+
tokenizer.advance(trivia)
|
|
115
|
+
end
|
|
116
|
+
[spellings, tokenizer.rest]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Reads `comment_text` through the gem's `AnnotationParser` as if it stood where `annotation`'s comment
|
|
120
|
+
# block does — same first line, same column, continuation lines re-prefixed with the `#` that
|
|
121
|
+
# `CommentLines#string` strips — and returns the one annotation it yields, or `nil`.
|
|
122
|
+
def reparse(annotation, comment_text)
|
|
123
|
+
location = annotation.source.comments.first&.location
|
|
124
|
+
return nil if location.nil?
|
|
125
|
+
|
|
126
|
+
indent = " " * location.start_column
|
|
127
|
+
padding = "\n" * (location.start_line - 1)
|
|
128
|
+
source = "#{padding}#{indent}#{comment_text.gsub("\n") { "\n#{indent}#" }}\n"
|
|
129
|
+
results = ::RBS::Inline::AnnotationParser.parse(::Prism.parse(source).comments)
|
|
130
|
+
return nil unless results.size == 1
|
|
131
|
+
|
|
132
|
+
annotations = results.first.annotations
|
|
133
|
+
annotations.size == 1 && annotations.first.is_a?(::RBS::Inline::AST::Annotations::Base) ? annotations.first : nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def well_formed_method_type?(annotation)
|
|
137
|
+
case annotation
|
|
138
|
+
when ::RBS::Inline::AST::Annotations::MethodTypeAssertion
|
|
139
|
+
true
|
|
140
|
+
when ::RBS::Inline::AST::Annotations::Method
|
|
141
|
+
annotation.method_types.is_a?(Array) && annotation.error_source.nil?
|
|
142
|
+
else
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rigor/plugin"
|
|
4
|
+
require_relative "rbs_inline/same_line_annotations"
|
|
4
5
|
|
|
5
6
|
# ADR-32 — bundled `rigor-rbs-inline` plugin.
|
|
6
7
|
#
|
|
@@ -68,6 +69,18 @@ module Rigor
|
|
|
68
69
|
# instead of adopting the placeholder. `Rigor::RbsExtended.inferred_return?` is the reader.
|
|
69
70
|
INFERRED_RETURN_ANNOTATION = "rigor:v1:inferred-return"
|
|
70
71
|
|
|
72
|
+
# The RBS annotation the synthesizer writes on every member where upstream defaulted EVERY type
|
|
73
|
+
# slot — every parameter and the return alike — because the author's comment block asserted nothing
|
|
74
|
+
# about this member's own contract (issue #991). This is a strictly narrower condition than
|
|
75
|
+
# {INFERRED_RETURN_ANNOTATION}: that one fires whenever the return alone defaulted, which is also
|
|
76
|
+
# true of a member whose parameters WERE annotated (`# @rbs num: Float` with no `# @rbs return:`).
|
|
77
|
+
# This annotation distinguishes "nobody said anything about this member" from "the return half was
|
|
78
|
+
# left to inference" — the two collapse under return-only provenance, because no equivalent
|
|
79
|
+
# per-parameter survivor exists to separate them (see {DEFAULTED_TYPE_NAME}), so this constant
|
|
80
|
+
# exists precisely to carry that missing half. Its meaning is normative in
|
|
81
|
+
# `docs/type-specification/rbs-extended.md`. `Rigor::RbsExtended.inferred_signature?` is the reader.
|
|
82
|
+
INFERRED_SIGNATURE_ANNOTATION = "rigor:v1:inferred-signature"
|
|
83
|
+
|
|
71
84
|
# The stand-in type handed to upstream's `Writer#default_type`, and the whole reason this plugin can
|
|
72
85
|
# tell a defaulted type from an authored one. `RBS::Inline::Writer` substitutes `default_type`
|
|
73
86
|
# wherever the author wrote nothing — `return_type || default_type` in `RubyDef#method_overloads`,
|
|
@@ -119,6 +132,9 @@ module Rigor
|
|
|
119
132
|
return nil if parsed.nil?
|
|
120
133
|
|
|
121
134
|
uses, decls, rbs_decls = parsed
|
|
135
|
+
# Issue #998 — before the writer reads the annotation lists, so a same-line `%a{…} () -> T` renders
|
|
136
|
+
# with its method type and its annotation both attached. See {SameLineAnnotations}.
|
|
137
|
+
SameLineAnnotations.each_parsing_result(decls) { |comments| SameLineAnnotations.split!(comments) }
|
|
122
138
|
rendered = mark_inferred_returns(render_with_defaulted_marker(uses, decls, rbs_decls))
|
|
123
139
|
rendered = reattach_declaration_annotations(rendered)
|
|
124
140
|
return nil if rendered.nil? || rendered.strip.empty?
|
|
@@ -162,6 +178,15 @@ module Rigor
|
|
|
162
178
|
# about the return. The engine reads the annotation in `RbsDispatch` and falls through to the same
|
|
163
179
|
# body-inference tier an undeclared method takes.
|
|
164
180
|
#
|
|
181
|
+
# A member additionally carries {INFERRED_SIGNATURE_ANNOTATION} when EVERY type slot on it —
|
|
182
|
+
# not only the return — defaulted: a fully bare `def`, or one whose only annotation lives on a
|
|
183
|
+
# sibling member the file also happens to carry. A member with a parameter annotated (`# @rbs num:
|
|
184
|
+
# Float`) but no `# @rbs return:` still gets {INFERRED_RETURN_ANNOTATION} (the return did default),
|
|
185
|
+
# but NOT this one — the author asserted something about the member, just not its return. Issue
|
|
186
|
+
# #991's `call.wrong-arity` reads this one rather than the return-only annotation, because the
|
|
187
|
+
# arity question is about the parameter list, and the parameter list is exactly what tells the two
|
|
188
|
+
# cases apart.
|
|
189
|
+
#
|
|
165
190
|
# Two properties make the rewrite safe rather than clever:
|
|
166
191
|
#
|
|
167
192
|
# - the stand-in is a token this plugin injected, so replacing it is exact — it can only appear
|
|
@@ -181,53 +206,85 @@ module Rigor
|
|
|
181
206
|
def mark_inferred_returns(rendered)
|
|
182
207
|
return rendered if rendered.nil? || !rendered.b.include?(DEFAULTED_TYPE_NAME.to_s)
|
|
183
208
|
|
|
184
|
-
|
|
209
|
+
return_lines, signature_lines =
|
|
210
|
+
rendered.valid_encoding? ? defaulted_member_lines(rendered) : [Set.new, Set.new]
|
|
185
211
|
scrubbed = rendered.b.gsub(DEFAULTED_TYPE_NAME.to_s, "untyped").force_encoding(rendered.encoding)
|
|
186
|
-
return scrubbed if
|
|
212
|
+
return scrubbed if return_lines.empty?
|
|
187
213
|
|
|
188
214
|
scrubbed.lines.each_with_index.flat_map do |line, index|
|
|
189
|
-
|
|
215
|
+
line_number = index + 1
|
|
216
|
+
next line unless return_lines.include?(line_number)
|
|
190
217
|
|
|
191
|
-
|
|
218
|
+
indent = line[/\A[ \t]*/]
|
|
219
|
+
annotations = ["#{indent}%a{#{INFERRED_RETURN_ANNOTATION}}\n"]
|
|
220
|
+
annotations << "#{indent}%a{#{INFERRED_SIGNATURE_ANNOTATION}}\n" if signature_lines.include?(line_number)
|
|
221
|
+
[*annotations, line]
|
|
192
222
|
end.join
|
|
193
223
|
end
|
|
194
224
|
|
|
195
|
-
# The 1-based
|
|
196
|
-
# type) is the defaulted stand-in
|
|
197
|
-
#
|
|
225
|
+
# The 1-based lines of every member in `rendered` whose return type (or, for an attribute, whose
|
|
226
|
+
# type) is the defaulted stand-in — and, of those, the subset where EVERY type slot on the member
|
|
227
|
+
# defaulted, not only the return. A member's `location.start_line` is the line its `def` /
|
|
228
|
+
# `attr_*` keyword sits on, which is where the annotation has to go. Returns `[return_lines,
|
|
229
|
+
# signature_lines]`; `signature_lines` is always a subset of `return_lines`, since a member cannot
|
|
230
|
+
# have every slot defaulted without its return being one of them.
|
|
198
231
|
def defaulted_member_lines(rendered)
|
|
199
232
|
buffer = ::RBS::Buffer.new(name: "(rigor: inline defaulted-type probe)", content: rendered)
|
|
200
233
|
_, _directives, decls = ::RBS::Parser.parse_signature(buffer)
|
|
201
|
-
collect_defaulted_member_lines(decls, Set.new)
|
|
234
|
+
collect_defaulted_member_lines(decls, Set.new, Set.new)
|
|
202
235
|
rescue ::StandardError
|
|
203
236
|
# The writer's own output failing to parse is a condition the loader already handles (ADR-32 WD6
|
|
204
237
|
# fail-soft drops the entry). Declining to mark here keeps that the only failure.
|
|
205
|
-
Set.new
|
|
238
|
+
[Set.new, Set.new]
|
|
206
239
|
end
|
|
207
240
|
|
|
208
|
-
def collect_defaulted_member_lines(nodes,
|
|
241
|
+
def collect_defaulted_member_lines(nodes, return_lines, signature_lines)
|
|
209
242
|
nodes.each do |node|
|
|
210
243
|
next unless node.respond_to?(:members)
|
|
211
244
|
|
|
212
245
|
node.members.each do |member|
|
|
213
|
-
|
|
214
|
-
lines << line if line
|
|
246
|
+
classify_defaulted_member(member, return_lines, signature_lines)
|
|
215
247
|
end
|
|
216
|
-
collect_defaulted_member_lines(
|
|
248
|
+
collect_defaulted_member_lines(
|
|
249
|
+
node.members.grep(::RBS::AST::Declarations::Base), return_lines, signature_lines
|
|
250
|
+
)
|
|
217
251
|
end
|
|
218
|
-
|
|
252
|
+
[return_lines, signature_lines]
|
|
219
253
|
end
|
|
220
254
|
|
|
221
|
-
def
|
|
255
|
+
def classify_defaulted_member(member, return_lines, signature_lines)
|
|
222
256
|
case member
|
|
223
257
|
when ::RBS::AST::Members::MethodDefinition
|
|
224
|
-
return
|
|
258
|
+
return unless member.overloads.any? { |o| defaulted_type?(o.method_type.type.return_type) }
|
|
259
|
+
|
|
260
|
+
line = member.location&.start_line
|
|
261
|
+
return if line.nil?
|
|
262
|
+
|
|
263
|
+
return_lines << line
|
|
264
|
+
signature_lines << line if member.overloads.all? { |o| fully_defaulted_overload?(o) }
|
|
225
265
|
when ::RBS::AST::Members::Attribute
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
|
|
266
|
+
return unless defaulted_type?(member.type)
|
|
267
|
+
|
|
268
|
+
line = member.location&.start_line
|
|
269
|
+
return if line.nil?
|
|
270
|
+
|
|
271
|
+
# An attribute has exactly one type slot (the value type, shared by its reader and writer
|
|
272
|
+
# overloads), so "the type defaulted" and "every type slot defaulted" are the same fact.
|
|
273
|
+
return_lines << line
|
|
274
|
+
signature_lines << line
|
|
229
275
|
end
|
|
230
|
-
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# True when every parameter type and the return type of `overload` — everything
|
|
279
|
+
# `RBS::Types::Function#each_type` yields — is the defaulted stand-in, i.e. the author's comment
|
|
280
|
+
# block asserted nothing about this overload's own contract. Block-type provenance is
|
|
281
|
+
# deliberately not part of this: a block parameter's shape does not bear on the positional arity
|
|
282
|
+
# #991 exists to check, and upstream defaults it independently of the parameter list either way.
|
|
283
|
+
def fully_defaulted_overload?(overload)
|
|
284
|
+
function = overload.method_type.type
|
|
285
|
+
return false unless function.respond_to?(:each_type)
|
|
286
|
+
|
|
287
|
+
function.each_type.all? { |type| defaulted_type?(type) }
|
|
231
288
|
end
|
|
232
289
|
|
|
233
290
|
def defaulted_type?(type)
|
|
@@ -300,29 +357,153 @@ module Rigor
|
|
|
300
357
|
# These are invisible without a report: synthesis succeeds, and the annotation comment is even echoed
|
|
301
358
|
# into the generated RBS, so the omission shows up neither in the output nor at runtime.
|
|
302
359
|
#
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
360
|
+
# Five cases today:
|
|
361
|
+
#
|
|
362
|
+
# - `module-self`, where the two inline-RBS dialects disagree on spelling. rbs's own `docs/inline.md`
|
|
363
|
+
# documents `# @rbs module-self: Foo`; the rbs-inline gem's grammar is `# @rbs module-self Foo`,
|
|
364
|
+
# without the colon. Handed the colon form the gem still builds a `ModuleSelf` annotation but
|
|
365
|
+
# extracts no types from it, so an empty `self_types` on a parsed annotation is a precise signature
|
|
366
|
+
# for "the author asked for a constraint we did not apply". Measured both ways in
|
|
367
|
+
# `docs/notes/20260730-inline-rbs-parser-grammar-diff.md`.
|
|
368
|
+
# - a `#:` line whose type does not parse (issue #997). Upstream's own tolerant parse
|
|
369
|
+
# ({#parse_type_method_type} in its `annotation_parser.rb`) never raises on this: it consumes the
|
|
370
|
+
# rest of the line into a `SyntaxErrorAssertion` and moves on, so nothing upstream ever surfaces
|
|
371
|
+
# the failure. Measured at 568138c2: `#: (finite-float) -> String` above a `def` synthesized no
|
|
372
|
+
# signature for it AT ALL, and `rigor check` on the file reported nothing beyond an unrelated
|
|
373
|
+
# `rbs.coverage.missing-gem` info — the annotation was indistinguishable from one never written,
|
|
374
|
+
# which ADR-93's "an inline annotation is a live contract" forbids.
|
|
375
|
+
# - an `# @rbs %a{…}` line whose text after the annotations is not a method type (issue #998). The
|
|
376
|
+
# gem's `@rbs %a{}` rule lexes the annotations and ignores the rest of the line, so
|
|
377
|
+
# `# @rbs %a{pure} (finite-float) -> String` keeps `%a{pure}` and discards the signature beside it
|
|
378
|
+
# without a word. A remainder that IS a method type is not reported, because
|
|
379
|
+
# {SameLineAnnotations.split!} has already made it bind — the notice is for what that repair
|
|
380
|
+
# could not read.
|
|
381
|
+
# - an `# @rbs name: T` parameter (or local-variable) annotation whose `T` does not parse (issue
|
|
382
|
+
# #1019, row H). The gem's grammar makes the colon-and-type optional — {#parse_var_decl} always
|
|
383
|
+
# builds a `VarType`, even when `parse_type` fell through — so a malformed type leaves `name`
|
|
384
|
+
# present and `type` `nil` rather than raising: `# @rbs n: Integer[1..10]` names `n` and leaves its
|
|
385
|
+
# type unset. `Writer#default_type` then fills the empty slot the same way an UNANNOTATED parameter
|
|
386
|
+
# is filled, so `VarType#type.nil?` is indistinguishable downstream from "the author wrote nothing"
|
|
387
|
+
# without this check — the same silent-drop shape #997 closed for `#:` lines and `return:`, now
|
|
388
|
+
# closed for `name:`.
|
|
389
|
+
# - an `@rbs`-prefixed comment paragraph whose body is not one of the gem's known annotation shapes
|
|
390
|
+
# (issue #1019, row B). {RBS::Inline::AnnotationParser#annotation_comment?} matches a `\b` word
|
|
391
|
+
# boundary right after `@rbs`, so `# @rbs-ext …` is inside its net — the gem treats it as an `@rbs`
|
|
392
|
+
# annotation attempt — but {RBS::Inline::AnnotationParser#parse_annotation}'s `case` has no branch
|
|
393
|
+
# for what follows `-ext`, falls through with no `else`, and returns `nil`. `AnnotationParser#parse`
|
|
394
|
+
# then folds the whole paragraph back into a `CommentLines` — the SAME class an ordinary comment
|
|
395
|
+
# parses to — so nothing distinguishes "the gem tried to read this as `@rbs` and failed" from
|
|
396
|
+
# "this was never meant to be one" except the marker the gem itself keyed on. {SameLineAnnotations::
|
|
397
|
+
# RBS_MARKER} already carries that exact marker (documented there as "the `@rbs` marker as the
|
|
398
|
+
# gem's `annotation_comment?` detects it"), reused here rather than duplicated, so a `CommentLines`
|
|
399
|
+
# paragraph is only reported when the gem's own detector would have called it an `@rbs` attempt.
|
|
400
|
+
#
|
|
401
|
+
# The list is repaired by {SameLineAnnotations.split!} first, exactly as the synthesis path repairs
|
|
402
|
+
# its own, so a valid same-line `#: %a{…} () -> T` is not reported as a `#:` line that failed to
|
|
403
|
+
# parse: it did not fail, it binds.
|
|
309
404
|
#
|
|
310
|
-
# Deliberately narrow. A construct the gem's parser REJECTS already routes through WD6's
|
|
311
|
-
# and one it never recognised at all is upstream's grammar to define (WD3) — guessing at
|
|
312
|
-
# make this a lint on comment prose, which is exactly the false-positive cost ADR-5 ranks
|
|
405
|
+
# Deliberately narrow otherwise. A construct the gem's parser REJECTS already routes through WD6's
|
|
406
|
+
# error path, and one it never recognised at all is upstream's grammar to define (WD3) — guessing at
|
|
407
|
+
# those would make this a lint on comment prose, which is exactly the false-positive cost ADR-5 ranks
|
|
408
|
+
# first. `SyntaxErrorAssertion` is neither: the gem's OWN parser recognised the `#:` shape and
|
|
409
|
+
# positively flagged its payload as unparseable, so reporting it is naming a fact rbs-inline already
|
|
410
|
+
# computed, not guessing at one. The row-B check is the same kind of fact, read off the gem's own
|
|
411
|
+
# marker rather than a fact rbs-inline names with a dedicated AST class — nothing else in the grammar
|
|
412
|
+
# collapses a recognised-but-unparseable paragraph into ordinary prose.
|
|
313
413
|
def unhonoured_annotations(prism_result)
|
|
314
414
|
::RBS::Inline::AnnotationParser.parse(prism_result.comments).flat_map do |parsed|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"
|
|
415
|
+
repaired = SameLineAnnotations.split!(parsed)
|
|
416
|
+
unrecognised_tag_notices(repaired) + repaired.each_annotation.filter_map do |annotation|
|
|
417
|
+
case annotation
|
|
418
|
+
when ::RBS::Inline::AST::Annotations::ModuleSelf
|
|
419
|
+
next if annotation.self_types.any?
|
|
420
|
+
|
|
421
|
+
"`@rbs module-self` contributed no self-type constraint. Rigor reads the " \
|
|
422
|
+
"`# @rbs module-self Foo` spelling; `# @rbs module-self: Foo` (the spelling in rbs's own " \
|
|
423
|
+
"inline documentation) is not honoured here."
|
|
424
|
+
when ::RBS::Inline::AST::Annotations::SyntaxErrorAssertion
|
|
425
|
+
syntax_error_assertion_notice(annotation)
|
|
426
|
+
when ::RBS::Inline::AST::Annotations::RBSAnnotation
|
|
427
|
+
dropped_remainder_notice(annotation)
|
|
428
|
+
when ::RBS::Inline::AST::Annotations::VarType
|
|
429
|
+
var_type_notice(annotation)
|
|
430
|
+
end
|
|
322
431
|
end
|
|
323
432
|
end.uniq
|
|
324
433
|
end
|
|
325
434
|
|
|
435
|
+
# Issue #997 — the line comes off the annotation's OWN `Prism::Comment` location, not the
|
|
436
|
+
# synthesized RBS buffer: unlike {Environment::RbsLoader#unresolved_type_name_detail}'s position,
|
|
437
|
+
# this one is read straight from the `.rb` file this synthesis call is fresh over (the per-file
|
|
438
|
+
# synthesizer cache is content-keyed, so a changed file simply misses rather than replaying a stale
|
|
439
|
+
# line), so it is the real line the author wrote, not an approximation.
|
|
440
|
+
def syntax_error_assertion_notice(annotation)
|
|
441
|
+
line = annotation.source.comments.first&.location&.start_line
|
|
442
|
+
position = line ? " on line #{line}" : ""
|
|
443
|
+
"a `#:` annotation#{position} did not parse as an RBS method type or type " \
|
|
444
|
+
"(`#{annotation.error_string}`) and was DROPPED — the method types as if the annotation had " \
|
|
445
|
+
"never been written, not merely as if its type were wrong. Fix the RBS syntax to restore it."
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Issue #998 — the line is read off the annotation's own comment, as in {#syntax_error_assertion_notice}.
|
|
449
|
+
def dropped_remainder_notice(annotation)
|
|
450
|
+
remainder = SameLineAnnotations.dropped_remainder(annotation)
|
|
451
|
+
return nil if remainder.nil?
|
|
452
|
+
|
|
453
|
+
line = annotation.source.comments.first&.location&.start_line
|
|
454
|
+
position = line ? " on line #{line}" : ""
|
|
455
|
+
"the text after the `%a{…}` annotation#{position} (`#{remainder}`) did not parse as an RBS method " \
|
|
456
|
+
"type and was DROPPED — the annotation is kept, but the method types as if no signature had been " \
|
|
457
|
+
"written beside it. Fix the RBS syntax to restore it."
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Issue #1019, row H — a `# @rbs name: T` whose `T` the gem's `parse_type` could not build, read off
|
|
461
|
+
# {RBS::Inline::AST::Annotations::VarType#type} being `nil` rather than off any pattern over the
|
|
462
|
+
# comment text. Silent otherwise: {Writer#default_type} fills the slot exactly as it fills a
|
|
463
|
+
# parameter nobody annotated, so `(untyped n)` does not distinguish "wrote nothing" from "wrote a
|
|
464
|
+
# type RBS could not parse". `raw` is `nil` (and the notice withheld) for the colon-less `# @rbs
|
|
465
|
+
# name` shorthand — the grammar accepts it too, but with no text to name as the dropped type it is a
|
|
466
|
+
# bare mention, not a failed assertion.
|
|
467
|
+
def var_type_notice(annotation)
|
|
468
|
+
return nil if annotation.type
|
|
469
|
+
|
|
470
|
+
raw = annotation.source.string[/#{Regexp.escape(annotation.name.to_s)}\s*:\s*(.+)\z/, 1]&.strip
|
|
471
|
+
return nil if raw.nil? || raw.empty?
|
|
472
|
+
|
|
473
|
+
line = annotation.source.comments.first&.location&.start_line
|
|
474
|
+
position = line ? " on line #{line}" : ""
|
|
475
|
+
"a `# @rbs #{annotation.name}:` annotation#{position} (`#{raw}`) did not parse as an RBS type " \
|
|
476
|
+
"and was DROPPED — the parameter types as `untyped`, not merely as if its type were wrong. If " \
|
|
477
|
+
"`#{raw}` is a Rigor refinement, write it as an `%a{rigor:v1:param: #{annotation.name} is " \
|
|
478
|
+
"#{raw}}` annotation beside the plain RBS type instead — see " \
|
|
479
|
+
"docs/manual/16-rbs-extended-annotations.md — to restore type coverage."
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Issue #1019, row B — an `@rbs`-prefixed comment paragraph the gem's OWN detector
|
|
483
|
+
# ({RBS::Inline::AnnotationParser#annotation_comment?}) called an `@rbs` annotation attempt, that
|
|
484
|
+
# `#parse_annotation`'s `case` had no branch for. `# @rbs-ext return: non-empty-string` is the case
|
|
485
|
+
# in point: `annotation_comment?`'s `/\A#(\s*)@rbs(\b|!)/` matches the `\b` word boundary between
|
|
486
|
+
# `s` and `-`, so the gem tries to read it as `@rbs`, fails, and folds it back into a `CommentLines`
|
|
487
|
+
# — the same class a `# rigor: …` or `# @extrbs …` paragraph parses to, which never claimed to be
|
|
488
|
+
# `@rbs` and must stay silent (row X / row C). {SameLineAnnotations::RBS_MARKER} already carries the
|
|
489
|
+
# gem's own marker, so matching it here is reading the SAME fact the gem's detector computed, not
|
|
490
|
+
# inventing a new one — see {#unhonoured_annotations}'s row-B paragraph for why that is not the
|
|
491
|
+
# "regexp lint on comment prose" WD12 otherwise declines to run.
|
|
492
|
+
def unrecognised_tag_notices(parsing_result)
|
|
493
|
+
parsing_result.annotations.grep(::RBS::Inline::AST::CommentLines).filter_map do |lines|
|
|
494
|
+
first_line = lines.string.each_line.first.to_s.strip
|
|
495
|
+
next unless SameLineAnnotations::RBS_MARKER.match?(first_line)
|
|
496
|
+
|
|
497
|
+
line = lines.comments.first&.location&.start_line
|
|
498
|
+
position = line ? " on line #{line}" : ""
|
|
499
|
+
"a comment#{position} (`##{first_line}`) opens with the `@rbs` marker but its body did not " \
|
|
500
|
+
"parse as a recognised `@rbs` annotation, so the whole line was DROPPED — this is not a " \
|
|
501
|
+
"recognised tag, and nothing on it was applied. Use one of the documented `# @rbs` forms " \
|
|
502
|
+
"(`name: T`, `return: T`, `module-self T`, …), or rename the tag so it does not start with " \
|
|
503
|
+
"`@rbs`, to avoid this notice."
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
326
507
|
# Rewrite every RDoc directive comment to its spaced spelling (`#:nodoc:` -> `# :nodoc:`) so
|
|
327
508
|
# upstream's annotation grammar never sees it, and re-parse. Two reasons this happens here rather
|
|
328
509
|
# than in {#annotated?}: the directive must not gate a file in, AND it must not reach the synthesis,
|
|
@@ -41,6 +41,7 @@ module Rigor
|
|
|
41
41
|
# `schedule.yml` IS the schedule map — and the nested paths are `sidekiq-scheduler`'s, whose entries
|
|
42
42
|
# live under `:scheduler: :schedule:` (with the bare `:schedule:` form kept for pre-3.0 layouts).
|
|
43
43
|
SCHEDULE_BLOCK_KEY_PATHS = [[], %w[scheduler schedule], %w[schedule]].freeze
|
|
44
|
+
Ractor.make_shareable(SCHEDULE_BLOCK_KEY_PATHS)
|
|
44
45
|
|
|
45
46
|
# Errno classes that mean "this path is not readable as a schedule" — swallowed so one bad path does
|
|
46
47
|
# not cost the roots the other paths supply.
|
|
@@ -137,12 +137,12 @@ module Rigor
|
|
|
137
137
|
services.fact_store.publish(plugin_id: manifest.id, name: :reachability_roots, value: roots)
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
# File-level only: the load-error
|
|
140
|
+
# File-level only: the load-error disclosure. The per-call arity validation runs over the engine-owned
|
|
141
141
|
# walk via the node_rule below (ADR-37). The worker index is lazily loaded + memoised by
|
|
142
142
|
# `producer_value`, shared by both surfaces.
|
|
143
143
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
144
144
|
index = producer_value(:worker_index)
|
|
145
|
-
|
|
145
|
+
disclose_load_error if index.nil? && producer_error(:worker_index)
|
|
146
146
|
|
|
147
147
|
[]
|
|
148
148
|
end
|
|
@@ -206,10 +206,14 @@ module Rigor
|
|
|
206
206
|
|
|
207
207
|
private
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
# Issue #1056 — "the worker index did not load" is a fact about the run's INPUTS, not about the file being
|
|
210
|
+
# analysed, so it is registered rather than returned: see {Plugin::Base#disclose_once} for the
|
|
211
|
+
# channel, the `(plugin id, key)` de-duplication and why the engine positions it at `.rigor.yml:1:1`.
|
|
212
|
+
# Returned from the per-file hook it carried no once-guard at all and repeated on every file.
|
|
213
|
+
def disclose_load_error
|
|
210
214
|
error = producer_error(:worker_index)
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
disclose_once(
|
|
216
|
+
:worker_index_load_failed,
|
|
213
217
|
message: "rigor-sidekiq: failed to discover workers: #{error.class}: #{error.message}",
|
|
214
218
|
severity: :warning,
|
|
215
219
|
rule: "load-error"
|