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,293 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/node_children"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Analysis
|
|
9
|
+
# #1040 — the INVERSE of a template unit's positions: a template `(line, column)` the user names, resolved
|
|
10
|
+
# to the one compiled node it denotes, or declined.
|
|
11
|
+
#
|
|
12
|
+
# Everything else the seam ships runs compiled → template ({TemplateUnits#remap}). A position probe runs
|
|
13
|
+
# the other way, and neither half of the map inverts on its own terms:
|
|
14
|
+
#
|
|
15
|
+
# - **Lines.** `line_map` is not injective — a compiler may emit several compiled lines for one template
|
|
16
|
+
# line, and lines the map does not mention anchor at the nearest mapped line before them. So a template
|
|
17
|
+
# line names a SET of compiled lines: every one whose {TemplateUnits::Entry#template_line} is that line,
|
|
18
|
+
# which is exactly the set a diagnostic would be reported from.
|
|
19
|
+
# - **Columns.** They do not correspond at all. A compiler rewrites each line's text around the Ruby it
|
|
20
|
+
# copies (`<%= @user.name %>` becomes `_buf << ( @user.name ).to_s;`), and the seam says nothing about
|
|
21
|
+
# where the copied bytes land.
|
|
22
|
+
#
|
|
23
|
+
# What IS true of every line-preserving template compiler is that the code a tag carries is copied
|
|
24
|
+
# verbatim. So a column is resolved through the **longest verbatim run**: every placement of the template
|
|
25
|
+
# line's byte at the column against the candidate compiled lines is extended left and right while the
|
|
26
|
+
# bytes agree, and the longest such run wins. The answer is accepted only when all of these hold:
|
|
27
|
+
#
|
|
28
|
+
# 1. the longest run is UNIQUE — two placements of equal length are two readings, and the probe declines
|
|
29
|
+
# (`:ambiguous`) rather than pick one;
|
|
30
|
+
# 2. the deepest compiled node at the mapped offset lies wholly INSIDE that run, on one line — which is
|
|
31
|
+
# what rejects markup: template text is copied into a string LITERAL, and the literal node's quotes
|
|
32
|
+
# are never part of the template's bytes (`:not_verbatim`).
|
|
33
|
+
#
|
|
34
|
+
# The engine knows nothing about ERB here, and needs to: an identity transform (the whole line is one run)
|
|
35
|
+
# and an ERB compiler (each tag body is a run) answer through the same rule, and a tag the compiler
|
|
36
|
+
# REWROTE rather than copied (rigor-actionpack's `yield` → `__rigor_yield`) is declined, because the node
|
|
37
|
+
# it produced is not made of the template's bytes.
|
|
38
|
+
class TemplateUnitPositions
|
|
39
|
+
# A verbatim run: `[from_start, from_end)` on the side the search started from, `shift` added to reach
|
|
40
|
+
# the other side, and `line` — the compiled line it lies on.
|
|
41
|
+
Run = Data.define(:line, :from_start, :from_end, :shift) do
|
|
42
|
+
def length = from_end - from_start
|
|
43
|
+
end
|
|
44
|
+
private_constant :Run
|
|
45
|
+
|
|
46
|
+
# Mirrors the `rigor type-of FILE:LINE` cap, so a template line table is bounded the same way.
|
|
47
|
+
ENUMERATION_CAP = 40
|
|
48
|
+
|
|
49
|
+
# @param entry — the unit's {TemplateUnits::Entry}.
|
|
50
|
+
# @param template — the template's own bytes, exactly as the plugin was handed them.
|
|
51
|
+
# @param root — the parse of `entry.source`.
|
|
52
|
+
# @param skip_node_types — node class names a line enumeration leaves out (the probe's non-expressions).
|
|
53
|
+
def initialize(entry:, template:, root:, skip_node_types: [])
|
|
54
|
+
@entry = entry
|
|
55
|
+
@template_lines = template.b.lines.map(&:chomp)
|
|
56
|
+
@compiled_lines = entry.source.b.lines
|
|
57
|
+
# Offsets are summed over the RAW lines, so a `\r\n` compiled source still lands on the right byte.
|
|
58
|
+
@compiled_widths = @compiled_lines.map(&:bytesize)
|
|
59
|
+
@compiled_lines = @compiled_lines.map(&:chomp)
|
|
60
|
+
@root = root
|
|
61
|
+
@skip_node_types = skip_node_types
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def template_line_count
|
|
65
|
+
@template_lines.length
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The deepest compiled node a template position denotes, or a Symbol saying why there is none:
|
|
69
|
+
# `:no_compiled_line`, `:not_verbatim` or `:ambiguous`. The caller has already range-checked the line.
|
|
70
|
+
def node_at(line:, column:)
|
|
71
|
+
text = @template_lines.fetch(line - 1)
|
|
72
|
+
offset = column - 1
|
|
73
|
+
return :not_verbatim if offset >= text.bytesize
|
|
74
|
+
|
|
75
|
+
candidates = compiled_lines_for(line)
|
|
76
|
+
return :no_compiled_line if candidates.empty?
|
|
77
|
+
|
|
78
|
+
# The compiled line ABOVE joins as a spill target: stdlib ERB hoists a line's leading text onto it
|
|
79
|
+
# (`_erbout.<< "\nname ".freeze`), so the literal a probe in that text has to tie with is not on
|
|
80
|
+
# this template line's compiled lines at all.
|
|
81
|
+
resolve(text, offset, targets_for(candidates), spill_targets(candidates))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The nearest NON-BLANK compiled line above this template line's own, or none.
|
|
85
|
+
#
|
|
86
|
+
# Not "the compiled lines of template line L-1": one text gap is ONE literal, and the compiler pads
|
|
87
|
+
# the lines it swallowed with blanks, so the literal carrying line L's leading text sits on the last
|
|
88
|
+
# line that emitted anything — which is L-1 only when L-1 itself carried a tag. With the L-1 reading,
|
|
89
|
+
# `<h1>T</h1>` / `<div>` / `name <%= name %>` answered about the tag's code at `3:1`, and so did any
|
|
90
|
+
# view with a blank line or a text-only line above the probed one: the common shape.
|
|
91
|
+
def spill_targets(candidates)
|
|
92
|
+
number = candidates.min - 1
|
|
93
|
+
number -= 1 while number.positive? && @compiled_lines[number - 1].strip.empty?
|
|
94
|
+
number.positive? ? targets_for([number]) : []
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def targets_for(numbers)
|
|
98
|
+
numbers.map { |number| [number, @compiled_lines[number - 1]] }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# `[[template_column, node], …]` for the expressions starting on the compiled lines a template line
|
|
102
|
+
# maps to, restricted to the ones whose bytes are the template's own — so every column in the table is
|
|
103
|
+
# a column of the TEMPLATE, and a probe at it resolves through the same verbatim run {#node_at} uses.
|
|
104
|
+
# Returns `[rows, total]`, `rows` capped at {ENUMERATION_CAP}.
|
|
105
|
+
def line_nodes(line)
|
|
106
|
+
text = @template_lines.fetch(line - 1)
|
|
107
|
+
wanted = compiled_lines_for(line).to_h { |number| [number, true] }
|
|
108
|
+
rows = []
|
|
109
|
+
walk(@root) do |node|
|
|
110
|
+
next unless wanted.key?(node.location.start_line)
|
|
111
|
+
next if @skip_node_types.include?(node.class.name)
|
|
112
|
+
|
|
113
|
+
column = template_column(node, text)
|
|
114
|
+
rows << [column, node] if column
|
|
115
|
+
end
|
|
116
|
+
rows.sort_by! { |column, node| [column, -node.location.length] }
|
|
117
|
+
[rows.first(ENUMERATION_CAP), rows.length]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The template column a compiled location starts at, or nil when it does not lie in a verbatim run.
|
|
121
|
+
# Used to position `--trace` fallback events, which carry compiled locations.
|
|
122
|
+
def template_column_for(location)
|
|
123
|
+
return nil unless location.respond_to?(:start_line)
|
|
124
|
+
|
|
125
|
+
text = @template_lines[@entry.template_line(location.start_line) - 1]
|
|
126
|
+
return nil if text.nil? || location.start_line != location.end_line
|
|
127
|
+
|
|
128
|
+
column_of_span(location, text)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def compiled_lines_for(line)
|
|
134
|
+
@compiled_by_template ||= (1..@compiled_lines.length).group_by { |number| @entry.template_line(number) }
|
|
135
|
+
@compiled_by_template.fetch(line, [])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def template_column(node, text)
|
|
139
|
+
location = node.location
|
|
140
|
+
return nil unless location.start_line == location.end_line
|
|
141
|
+
|
|
142
|
+
column_of_span(location, text)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Forward, compiled span → template column, confirmed by probing BACK from the answer: the column is
|
|
146
|
+
# reported only when {#node_at} at it resolves to a node starting where this one does. Anything the
|
|
147
|
+
# table prints is therefore a column the exact form answers, declines included.
|
|
148
|
+
def column_of_span(location, text)
|
|
149
|
+
compiled_line = location.start_line
|
|
150
|
+
compiled = @compiled_lines[compiled_line - 1]
|
|
151
|
+
return nil if compiled.nil? || location.start_column >= compiled.bytesize
|
|
152
|
+
|
|
153
|
+
forward = resolve_run(compiled, location.start_column, [[compiled_line, text]])
|
|
154
|
+
return nil if forward.is_a?(Symbol) || location.end_column > forward.from_end
|
|
155
|
+
|
|
156
|
+
template_offset = location.start_column + forward.shift
|
|
157
|
+
back = node_at(line: @entry.template_line(compiled_line), column: template_offset + 1)
|
|
158
|
+
return nil unless back.is_a?(Prism::Node) && back.location.start_offset == location.start_offset
|
|
159
|
+
|
|
160
|
+
template_offset + 1
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The one compiled node a template offset denotes, through the verbatim runs, or the Symbol reason.
|
|
164
|
+
#
|
|
165
|
+
# The LONGEST run decides, and it must be unique: two placements of equal length are two readings.
|
|
166
|
+
# But a longer run is not on its own a better reading, because a compiler's own punctuation joins the
|
|
167
|
+
# template's bytes into runs the template never had — the `=` of `<%=` matches the `=` of `<=`, `==`,
|
|
168
|
+
# `+=` or an assignment, so `"= v "` (4 bytes, ending in the WRONG `v`) outran the tag body `" v "`
|
|
169
|
+
# (3 bytes) and the probe answered about another `v` on the line. So a placement whose own deepest
|
|
170
|
+
# node lies inside its own run is a RIVAL reading, and a rival declines the position.
|
|
171
|
+
#
|
|
172
|
+
# A repeat INSIDE the winning run is not a rival: `<%= @author.nil? ? l(:a) : l(:b, f(@author)) %>`
|
|
173
|
+
# copies one tag body once, and the second `@author` is the same copy seen from the other end, not a
|
|
174
|
+
# second reading. Only a node OUTSIDE the winning run's own compiled span counts, which is what keeps
|
|
175
|
+
# the busy lines of a real view answerable while the cross-tag repeat (`<%= v %> <%= v.to_s %>`, or
|
|
176
|
+
# `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines — the family a plugin-exported tag
|
|
177
|
+
# span would answer instead
|
|
178
|
+
# (see the PR), and the follow-up this rule is deliberately conservative ahead of.
|
|
179
|
+
#
|
|
180
|
+
# `spill` is the nearest NON-BLANK compiled line above this template line's own, where a hoisted
|
|
181
|
+
# leading-text literal lives. Only a string literal the run does NOT contain counts there. A code
|
|
182
|
+
# node up there is ordinary compiled code, and counting it would decline every `<%= v %>` that
|
|
183
|
+
# repeats on consecutive template lines; a literal the run DOES contain is a literal the TAG wrote
|
|
184
|
+
# (`<%= link_to "Edit", path %>`), whose quotes are template bytes, so it passes `inside?` and the
|
|
185
|
+
# probe answered about that tag's string from an HTML attribute that happened to spell it. A
|
|
186
|
+
# hoisted literal is never inside its own run — the quotes around it are the compiler's — so spill
|
|
187
|
+
# runs can only ever form ties, which is all they exist to do.
|
|
188
|
+
def resolve(text, offset, targets, spill = [])
|
|
189
|
+
runs = collect_runs(text, offset, targets)
|
|
190
|
+
runs += collect_runs(text, offset, spill).select { |run| hoisted_text?(run, offset) }
|
|
191
|
+
return :not_verbatim if runs.empty?
|
|
192
|
+
|
|
193
|
+
longest = runs.max_by(&:length)
|
|
194
|
+
return :ambiguous if runs.count { |run| run.length == longest.length } > 1
|
|
195
|
+
|
|
196
|
+
answer = node_for(longest, offset)
|
|
197
|
+
return :not_verbatim unless inside?(answer, longest)
|
|
198
|
+
|
|
199
|
+
rival = runs.any? do |run|
|
|
200
|
+
next false if run.equal?(longest)
|
|
201
|
+
|
|
202
|
+
node = node_for(run, offset)
|
|
203
|
+
inside?(node, run) && !node.equal?(answer) && !inside?(node, longest)
|
|
204
|
+
end
|
|
205
|
+
rival ? :ambiguous : answer
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def collect_runs(text, offset, targets)
|
|
209
|
+
runs = []
|
|
210
|
+
targets.each { |number, to| runs_through(text, offset, to, number) { |run| runs << run } }
|
|
211
|
+
runs
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def hoisted_text?(run, offset)
|
|
215
|
+
node = node_for(run, offset)
|
|
216
|
+
node.is_a?(Prism::StringNode) && !inside?(node, run)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def node_for(run, offset)
|
|
220
|
+
deepest(@root, compiled_offset(run.line, offset + run.shift))
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# The longest verbatim run through `from[offset]` across every placement on the `targets` lines, or
|
|
224
|
+
# `:not_verbatim` when the byte appears nowhere and `:ambiguous` when two placements tie. The
|
|
225
|
+
# compiled → template direction, where the node is already known, so only the run's length decides.
|
|
226
|
+
def resolve_run(from, offset, targets)
|
|
227
|
+
best = nil
|
|
228
|
+
tied = false
|
|
229
|
+
targets.each do |number, to|
|
|
230
|
+
runs_through(from, offset, to, number) do |run|
|
|
231
|
+
if best.nil? || run.length > best.length
|
|
232
|
+
best = run
|
|
233
|
+
tied = false
|
|
234
|
+
elsif run.length == best.length
|
|
235
|
+
tied = true
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
return :not_verbatim if best.nil?
|
|
240
|
+
return :ambiguous if tied
|
|
241
|
+
|
|
242
|
+
best
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def runs_through(from, offset, to, number)
|
|
246
|
+
byte = from.getbyte(offset)
|
|
247
|
+
to.bytesize.times do |index|
|
|
248
|
+
next unless to.getbyte(index) == byte
|
|
249
|
+
|
|
250
|
+
shift = index - offset
|
|
251
|
+
left = offset
|
|
252
|
+
left -= 1 while left.positive? && (left + shift).positive? &&
|
|
253
|
+
from.getbyte(left - 1) == to.getbyte(left - 1 + shift)
|
|
254
|
+
right = offset + 1
|
|
255
|
+
right += 1 while right < from.bytesize && from.getbyte(right) == to.getbyte(right + shift)
|
|
256
|
+
yield Run.new(line: number, from_start: left, from_end: right, shift: shift)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def inside?(node, run)
|
|
261
|
+
return false if node.nil?
|
|
262
|
+
|
|
263
|
+
location = node.location
|
|
264
|
+
location.start_line == run.line && location.end_line == run.line &&
|
|
265
|
+
location.start_column >= run.from_start + run.shift && location.end_column <= run.from_end + run.shift
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def compiled_offset(line, column)
|
|
269
|
+
@compiled_widths.first(line - 1).sum + column
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def deepest(node, offset)
|
|
273
|
+
return nil unless node.is_a?(Prism::Node)
|
|
274
|
+
|
|
275
|
+
location = node.location
|
|
276
|
+
return nil unless location.start_offset <= offset && offset < location.end_offset
|
|
277
|
+
|
|
278
|
+
node.rigor_each_child do |child|
|
|
279
|
+
deeper = deepest(child, offset)
|
|
280
|
+
return deeper if deeper
|
|
281
|
+
end
|
|
282
|
+
node
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def walk(node, &block)
|
|
286
|
+
return unless node.is_a?(Prism::Node)
|
|
287
|
+
|
|
288
|
+
block.call(node)
|
|
289
|
+
node.rigor_each_child { |child| walk(child, &block) }
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|