rigortype 0.3.8 → 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/capability_roles/capability_roles.rbs +36 -0
- 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 +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- 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 +261 -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 +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- 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 +229 -1
- 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 +3 -3
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -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 +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- 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 +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +112 -14
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +29 -11
- 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/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- 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 +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- 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 +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- 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 +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- 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/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- 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/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- 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 +168 -21
- 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 +87 -33
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
- 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/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- 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 +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- 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 +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- 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 +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- 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 +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- 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/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -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 +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- 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 +324 -18
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -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/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- 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 +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- 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 +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -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 +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- 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 +25 -27
- 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 +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- 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-project-init/references/06-agent-contract.md +67 -0
- 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 +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
# One **template unit** — a non-Ruby source file a plugin compiled into Ruby, handed to the engine to
|
|
8
|
+
# be parsed and typed like a file (ADR-16 Tier D, revived by #392 as this seam; design note
|
|
9
|
+
# `docs/design/20260816-effect-labels.md` § 11.3).
|
|
10
|
+
#
|
|
11
|
+
# ADR-16's Tier D declared `external_files:` — "files evaluated as if their body were pasted at a
|
|
12
|
+
# declared call site, with `self` typed as a declared class" — and ADR-60 WD1 removed it for want of a
|
|
13
|
+
# consumer. What Tier D lacked, and what this carries, is a **source transform with a line map** ahead
|
|
14
|
+
# of parsing: the file on disk is not Ruby, so the engine analyses `ruby_source` and reports at the
|
|
15
|
+
# template's own lines.
|
|
16
|
+
#
|
|
17
|
+
# A unit is pure data. The plugin's transform runs ONCE, on the parent, before any analysis; nothing
|
|
18
|
+
# here is a callable, so the unit survives `Marshal` into a fork-pool worker exactly as the macro
|
|
19
|
+
# substrate's value objects do.
|
|
20
|
+
#
|
|
21
|
+
# - `logical_name` — the name the unit is keyed by, independent of the handler: `users/show.html`, not
|
|
22
|
+
# `users/show.html.erb`. Becomes the `view:<logical_name>` effect-unit key, so an ERB → Haml rewrite
|
|
23
|
+
# is not a rename.
|
|
24
|
+
# - `path` — the template file as the user wrote it. Every diagnostic and every source trace names it.
|
|
25
|
+
# - `ruby_source` — the compiled Ruby the engine parses.
|
|
26
|
+
# - `line_map` — `{ ruby_source line => template line }`, 1-based. A line the map does not mention
|
|
27
|
+
# anchors at the nearest mapped line before it, and at line 1 when there is none. **An EMPTY map is
|
|
28
|
+
# the identity, not "no positions"**: every compiled line reports at its own number and the columns
|
|
29
|
+
# pass through untouched. That is correct only for a transform that emits nothing the template did
|
|
30
|
+
# not contain — a transform that prepends so much as one preamble line and omits the map reports
|
|
31
|
+
# every finding off by that many lines. Omit it only for a byte-preserving transform.
|
|
32
|
+
# - `self_type` — the fully-qualified class the body's `self` is typed as, or nil for a bare body.
|
|
33
|
+
# - `locals` — `{ name => type name }`, the parameters the render site passes.
|
|
34
|
+
# - `ivar_seeds` — `{ "@name" => type name }`, the assigns the rendering action set.
|
|
35
|
+
# - `transform_id` — the compiler's identity (`"erubi-1.13"`). Joins the unit digest, so re-compiling
|
|
36
|
+
# with a different compiler re-analyses. Defaults to the declaring plugin's `id@version`.
|
|
37
|
+
#
|
|
38
|
+
# - `suppressed_rules` — rule-id PREFIXES the engine drops for this unit's diagnostics (#393). The
|
|
39
|
+
# per-unit rule posture: a compiled template is not code the user wrote, and a plugin that knows its
|
|
40
|
+
# compiler's idioms is the only party that can say which families of finding are meaningful there.
|
|
41
|
+
# `["call."]` is what an ERB plugin declares while the synthesised receivers are still coarse — the
|
|
42
|
+
# unit still contributes its EFFECTS, which is what the view layer is analysed for first, without
|
|
43
|
+
# the typing half costing the project a diagnostic per line. Empty means "report everything", which
|
|
44
|
+
# is what a plugin whose synthesis it trusts should declare.
|
|
45
|
+
#
|
|
46
|
+
# A type name that resolves to nothing is not an error here: the engine falls back to `Dynamic`, which
|
|
47
|
+
# taints honestly rather than fabricating a type the plugin could not justify (ADR-5).
|
|
48
|
+
class TemplateUnit
|
|
49
|
+
# Bumped whenever the engine changes what it synthesises from a unit — the seeding rules, the
|
|
50
|
+
# scope binding, the effect-unit key spelling. It rides the unit digest, so a change here
|
|
51
|
+
# invalidates every cached run that analysed a unit, exactly as a changed transform does.
|
|
52
|
+
SYNTHESIS_VERSION = 2
|
|
53
|
+
|
|
54
|
+
# The prefix an effect-unit key carries for a template unit. Deliberately not a {Effects::MethodKey}
|
|
55
|
+
# shape: a view has no owner class and no selector, and spelling one would put a method in the
|
|
56
|
+
# snapshot that no call site can name.
|
|
57
|
+
KEY_PREFIX = "view:"
|
|
58
|
+
|
|
59
|
+
IVAR_NAME = /\A@[A-Za-z_][A-Za-z0-9_]*\z/
|
|
60
|
+
private_constant :IVAR_NAME
|
|
61
|
+
|
|
62
|
+
LOCAL_NAME = /\A[a-z_][A-Za-z0-9_]*\z/
|
|
63
|
+
private_constant :LOCAL_NAME
|
|
64
|
+
|
|
65
|
+
# A rule prefix is a dotted family (`call.`) or a whole rule id (`call.undefined-method`); the match
|
|
66
|
+
# is `String#start_with?`, so the former covers the latter. A bare `""` would suppress the unit
|
|
67
|
+
# entirely and is refused.
|
|
68
|
+
RULE_PREFIX = /\A[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*\.?\z/
|
|
69
|
+
private_constant :RULE_PREFIX
|
|
70
|
+
|
|
71
|
+
attr_reader :logical_name, :path, :ruby_source, :line_map, :self_type, :locals, :ivar_seeds,
|
|
72
|
+
:transform_id, :suppressed_rules
|
|
73
|
+
|
|
74
|
+
# Every field is one declared property of the unit; grouping them behind a context object would only
|
|
75
|
+
# move the same list into a second value class.
|
|
76
|
+
def initialize(logical_name:, path:, ruby_source:, line_map: {}, self_type: nil, locals: {}, # rubocop:disable Metrics/ParameterLists
|
|
77
|
+
ivar_seeds: {}, transform_id: nil, suppressed_rules: [])
|
|
78
|
+
@logical_name = validate_string!("logical_name", logical_name)
|
|
79
|
+
@path = validate_string!("path", path)
|
|
80
|
+
@ruby_source = validate_source!(ruby_source)
|
|
81
|
+
@line_map = validate_line_map!(line_map)
|
|
82
|
+
@self_type = self_type.nil? ? nil : validate_string!("self_type", self_type)
|
|
83
|
+
@locals = validate_bindings!("locals", locals, LOCAL_NAME)
|
|
84
|
+
@ivar_seeds = validate_bindings!("ivar_seeds", ivar_seeds, IVAR_NAME)
|
|
85
|
+
@transform_id = transform_id.nil? ? nil : validate_string!("transform_id", transform_id)
|
|
86
|
+
@suppressed_rules = validate_rule_prefixes!(suppressed_rules)
|
|
87
|
+
freeze
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The effect-unit key and the snapshot key: `view:users/show.html`.
|
|
91
|
+
def unit_key
|
|
92
|
+
"#{KEY_PREFIX}#{@logical_name}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The unit's cache identity — **source bytes + transform id + synthesis version**, the triple
|
|
96
|
+
# `docs/internal-spec/cache.md` § computed-value keys asks a synthesised input to carry. The
|
|
97
|
+
# template's own bytes are deliberately NOT in it: the engine analyses `ruby_source`, and two
|
|
98
|
+
# templates that compile to the same Ruby have the same answer.
|
|
99
|
+
#
|
|
100
|
+
# @param fallback_transform_id — the declaring plugin's `id@version`, used when the unit
|
|
101
|
+
# named no transform of its own.
|
|
102
|
+
def digest(fallback_transform_id = nil)
|
|
103
|
+
Digest::SHA256.hexdigest(
|
|
104
|
+
[
|
|
105
|
+
"source:#{@ruby_source}",
|
|
106
|
+
"transform:#{@transform_id || fallback_transform_id || 'unknown'}",
|
|
107
|
+
"suppressed:#{@suppressed_rules.join(',')}",
|
|
108
|
+
"synthesis:#{SYNTHESIS_VERSION}"
|
|
109
|
+
].join("\x00")
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The template line a `ruby_source` line reports at. Falls back to the nearest mapped line before it
|
|
114
|
+
# — a synthesised prologue or a buffer-append the compiler emitted between two template lines still
|
|
115
|
+
# points into the template rather than past its end — and to line 1 when the map says nothing at all.
|
|
116
|
+
def template_line(ruby_line)
|
|
117
|
+
return ruby_line if @line_map.empty?
|
|
118
|
+
return @line_map[ruby_line] if @line_map.key?(ruby_line)
|
|
119
|
+
|
|
120
|
+
before = @line_map.keys.select { |line| line < ruby_line }
|
|
121
|
+
before.empty? ? 1 : @line_map[before.max]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def to_h
|
|
125
|
+
{
|
|
126
|
+
"logical_name" => @logical_name, "path" => @path, "ruby_source" => @ruby_source,
|
|
127
|
+
"line_map" => @line_map, "self_type" => @self_type, "locals" => @locals,
|
|
128
|
+
"ivar_seeds" => @ivar_seeds, "transform_id" => @transform_id,
|
|
129
|
+
"suppressed_rules" => @suppressed_rules
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def ==(other)
|
|
134
|
+
other.is_a?(TemplateUnit) && other.to_h == to_h
|
|
135
|
+
end
|
|
136
|
+
alias eql? ==
|
|
137
|
+
|
|
138
|
+
def hash
|
|
139
|
+
[self.class, to_h].hash
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def validate_string!(field, value)
|
|
145
|
+
text = value.to_s
|
|
146
|
+
raise ArgumentError, "TemplateUnit #{field} must be a non-empty String" if text.empty?
|
|
147
|
+
|
|
148
|
+
text.dup.freeze
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def validate_source!(value)
|
|
152
|
+
raise ArgumentError, "TemplateUnit ruby_source must be a String" unless value.is_a?(String)
|
|
153
|
+
|
|
154
|
+
value.dup.freeze
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# 1-based on both sides. A zero or negative line is a compiler bug the engine would turn into a
|
|
158
|
+
# diagnostic positioned off the top of the file, so it is refused at construction.
|
|
159
|
+
def validate_line_map!(value)
|
|
160
|
+
raise ArgumentError, "TemplateUnit line_map must be a Hash" unless value.is_a?(Hash)
|
|
161
|
+
|
|
162
|
+
value.to_h do |from, to|
|
|
163
|
+
unless from.is_a?(Integer) && to.is_a?(Integer) && from.positive? && to.positive?
|
|
164
|
+
raise ArgumentError, "TemplateUnit line_map must map positive Integer to positive Integer"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
[from, to]
|
|
168
|
+
end.freeze
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def validate_rule_prefixes!(value)
|
|
172
|
+
raise ArgumentError, "TemplateUnit suppressed_rules must be an Array" unless value.is_a?(Array)
|
|
173
|
+
|
|
174
|
+
value.map do |prefix|
|
|
175
|
+
text = prefix.to_s
|
|
176
|
+
unless text.match?(RULE_PREFIX)
|
|
177
|
+
raise ArgumentError, "TemplateUnit suppressed_rules entry #{text.inspect} is malformed"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
text.dup.freeze
|
|
181
|
+
end.uniq.freeze
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def validate_bindings!(field, value, pattern)
|
|
185
|
+
raise ArgumentError, "TemplateUnit #{field} must be a Hash" unless value.is_a?(Hash)
|
|
186
|
+
|
|
187
|
+
value.to_h do |name, type_name|
|
|
188
|
+
key = name.to_s
|
|
189
|
+
raise ArgumentError, "TemplateUnit #{field} key #{key.inspect} is malformed" unless key.match?(pattern)
|
|
190
|
+
|
|
191
|
+
[key.dup.freeze, validate_string!("#{field} value", type_name)]
|
|
192
|
+
end.freeze
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -45,8 +45,7 @@ module Rigor
|
|
|
45
45
|
freeze
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# @
|
|
49
|
-
# @return [Boolean] true when the absolute path falls inside any allowed read root. Symlinks are
|
|
48
|
+
# @return true when the absolute path falls inside any allowed read root. Symlinks are
|
|
50
49
|
# resolved through `File.expand_path` only (no `realpath`); plugins with adversarial intent are out of
|
|
51
50
|
# scope per ADR-2.
|
|
52
51
|
def allow_read?(path)
|
|
@@ -58,8 +57,7 @@ module Rigor
|
|
|
58
57
|
@network_policy != :disabled
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
# @
|
|
62
|
-
# @return [Boolean] true when the URL scheme is `https` and the parsed hostname is in
|
|
60
|
+
# @return true when the URL scheme is `https` and the parsed hostname is in
|
|
63
61
|
# `allowed_url_hosts`. Always `false` while `network_policy` is `:disabled`.
|
|
64
62
|
def allow_url?(url)
|
|
65
63
|
return false if @network_policy == :disabled
|
|
@@ -28,11 +28,11 @@ module Rigor
|
|
|
28
28
|
# Resolvers SHOULD be stateless and re-entrant; the registry builds the chain once per
|
|
29
29
|
# `Analysis::Runner.run` and may consult any resolver multiple times for the same node.
|
|
30
30
|
class TypeNodeResolver
|
|
31
|
-
# @param node
|
|
31
|
+
# @param node — the parser-emitted node the chain is
|
|
32
32
|
# asking about.
|
|
33
|
-
# @param scope
|
|
33
|
+
# @param scope — companion value object (slice 3); slice 2 invocations MAY pass
|
|
34
34
|
# `nil` because the chain doesn't exist yet.
|
|
35
|
-
# @return
|
|
35
|
+
# @return resolved type, or `nil` to fall through.
|
|
36
36
|
def resolve(node, scope) # rubocop:disable Lint/UnusedMethodArgument
|
|
37
37
|
nil
|
|
38
38
|
end
|
data/lib/rigor/plugin.rb
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "plugin/bundled_catalog"
|
|
4
|
+
require_relative "environment/lockfile_resolver"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
# ADR-96 WD2 — the plugin-gap advisory: a gem the project itself depends on that a bundled plugin declares
|
|
8
|
+
# in `target_gems:`, with that plugin absent from `plugins:`.
|
|
9
|
+
#
|
|
10
|
+
# One home for a fact that ADR-96 found in four hand-maintained copies, none of which was the plugin.
|
|
11
|
+
# `rigor doctor` and `rigor skill describe`'s project probe both route through here, so the two can no
|
|
12
|
+
# longer disagree — they were byte-identical Rails tables by luck, not by construction.
|
|
13
|
+
#
|
|
14
|
+
# The advisory is deliberately advice, not activation: naming a plugin never loads it (ADR-96 Criterion 2),
|
|
15
|
+
# and declining a plugin is a legitimate choice, which is why a gap is a warning and never a failure.
|
|
16
|
+
module PluginGapAdvisory
|
|
17
|
+
# One unenabled plugin and the depended-on gems that make it relevant.
|
|
18
|
+
Gap = Data.define(:plugin_gem, :plugin_id, :locked_gems)
|
|
19
|
+
|
|
20
|
+
# A gem whose whole point is to pull its constituents in. A Rails application's `Gemfile` says `rails`
|
|
21
|
+
# and never `activerecord`, so a direct-dependency match alone would silence the entire Rails family for
|
|
22
|
+
# the projects the advisory exists for. The table is explicit rather than derived: expanding whatever a
|
|
23
|
+
# gem happens to resolve to is the transitive read this advisory is here to avoid, and the set of gems
|
|
24
|
+
# that are genuinely umbrellas is small and known.
|
|
25
|
+
UMBRELLA_GEMS = {
|
|
26
|
+
"rails" => %w[
|
|
27
|
+
actioncable actionmailer actionpack actiontext actionview activejob activemodel
|
|
28
|
+
activerecord activestorage activesupport railties
|
|
29
|
+
].freeze
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# @param project_root — the directory whose `Gemfile.lock` and `plugins:` list are read.
|
|
34
|
+
# @param plugins — the configuration's raw `plugins:` entries (String or Hash form).
|
|
35
|
+
# @return the gaps, sorted by plugin gem name; empty when the project declares no dependency.
|
|
36
|
+
def gaps(project_root:, plugins:)
|
|
37
|
+
depended = depended_gems(project_root)
|
|
38
|
+
return [] if depended.empty?
|
|
39
|
+
|
|
40
|
+
listed = listed_plugin_names(plugins)
|
|
41
|
+
Plugin::BundledCatalog.entries.filter_map do |entry|
|
|
42
|
+
next if listed?(entry, listed)
|
|
43
|
+
|
|
44
|
+
matched = entry.target_gems.select { |gem| depended.include?(gem) }
|
|
45
|
+
next if matched.empty?
|
|
46
|
+
|
|
47
|
+
Gap.new(plugin_gem: entry.gem_name, plugin_id: entry.plugin_id, locked_gems: matched.freeze)
|
|
48
|
+
end.sort_by(&:plugin_gem)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Whether the project enables no bundled plugin at all for the gems it depends on — ADR-96's preserved
|
|
52
|
+
# `:fail` case, generalised off the Rails-only table it replaces. A project that enables *some* of the
|
|
53
|
+
# matching plugins has made choices; one that enables none of them is unconfigured for its own stack.
|
|
54
|
+
def unconfigured?(project_root:, plugins:)
|
|
55
|
+
found = gaps(project_root: project_root, plugins: plugins)
|
|
56
|
+
return false if found.empty?
|
|
57
|
+
|
|
58
|
+
listed = listed_plugin_names(plugins)
|
|
59
|
+
Plugin::BundledCatalog.entries.none? { |entry| listed?(entry, listed) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# The gems the project chose, which is the `DEPENDENCIES` section and never the resolved graph. A
|
|
65
|
+
# transitive `minitest` / `i18n` / `ffi` is somebody else's dependency, and advising on it fires on
|
|
66
|
+
# correct configuration — including, through {unconfigured?}, as a hard failure.
|
|
67
|
+
def depended_gems(project_root)
|
|
68
|
+
direct = Environment::LockfileResolver.direct_dependency_names(
|
|
69
|
+
lockfile_path: nil, project_root: project_root
|
|
70
|
+
)
|
|
71
|
+
return direct if direct.empty?
|
|
72
|
+
|
|
73
|
+
direct + direct.flat_map { |gem| UMBRELLA_GEMS.fetch(gem, []) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Both spellings a `plugins:` entry may use for the same plugin — the gem name and the manifest id.
|
|
77
|
+
# An `enabled: false` entry still counts as naming it: the user has demonstrably found the plugin, so
|
|
78
|
+
# advising them to add what is already there would be worse than staying quiet.
|
|
79
|
+
def listed_plugin_names(plugins)
|
|
80
|
+
Array(plugins).flat_map do |raw|
|
|
81
|
+
case raw
|
|
82
|
+
when String then [raw]
|
|
83
|
+
when Hash
|
|
84
|
+
keyed = raw.to_h { |key, value| [key.to_s, value] }
|
|
85
|
+
[keyed["gem"], keyed["id"]].compact.map(&:to_s)
|
|
86
|
+
else []
|
|
87
|
+
end
|
|
88
|
+
end.to_set
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def listed?(entry, listed)
|
|
92
|
+
listed.include?(entry.gem_name) || listed.include?(entry.plugin_id)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "environment"
|
|
4
|
+
require_relative "plugin"
|
|
5
|
+
require_relative "plugin/loader"
|
|
6
|
+
require_relative "plugin/services"
|
|
7
|
+
require_relative "reflection"
|
|
8
|
+
require_relative "type/combinator"
|
|
9
|
+
|
|
10
|
+
module Rigor
|
|
11
|
+
# Shared construction of the project environment for every command that types against the project WITHOUT
|
|
12
|
+
# driving a full {Analysis::Runner}: the single-shot probes (`type-of`, `type-scan`, `trace`, `annotate`) and
|
|
13
|
+
# the `sig-gen` generator / observation collector. Each of these answers "what does the engine see here?" for
|
|
14
|
+
# a hand-picked file, position or class, so the type universe they read MUST match the one `rigor check`
|
|
15
|
+
# analyses against — otherwise the command reports, or declines to emit, against a universe the real run never
|
|
16
|
+
# had.
|
|
17
|
+
#
|
|
18
|
+
# Two gaps this closes, both the same shape:
|
|
19
|
+
#
|
|
20
|
+
# - The probes historically built their environment with `Environment.for_project(libraries:,
|
|
21
|
+
# signature_paths:)` only — no plugin registry, no `source_files:`. The `source_rbs_synthesizer` plugin hook
|
|
22
|
+
# (ADR-32 / ADR-93's auto-wired `rigor-rbs-inline`) therefore never ran there, so a class whose only
|
|
23
|
+
# signature comes from an inline `#: () -> void` annotation typed as `Dynamic[top]` under a probe while
|
|
24
|
+
# `check` resolved it through the synthesized RBS. That divergence misattributed a dispatch tier during the
|
|
25
|
+
# #162 transitive-void design pass (see the 2026-07-19 addendum in
|
|
26
|
+
# docs/adr/100-static-diagnostic-family-and-void-origins.md).
|
|
27
|
+
# - Neither the probes nor `sig-gen` passed the dependency-discovery axes — the `rbs collection` lockfile, the
|
|
28
|
+
# bundle's per-gem `sig/`, the Gemfile.lock gating them — that `check` reads straight off the same
|
|
29
|
+
# configuration. On a Rails project with `rbs collection install` run, `sig-gen` therefore could not see
|
|
30
|
+
# `ActiveRecord::Base` at all, and its superclass-without-RBS skip guard declined to emit every model
|
|
31
|
+
# (issue #821). The advice the guard prints — install the collection — had no effect, because the command
|
|
32
|
+
# never looked at it.
|
|
33
|
+
#
|
|
34
|
+
# {.dependency_discovery_options} is the single spelling of those axes; `Runner`'s build path
|
|
35
|
+
# (`Analysis::Runner::PoolCoordinator#build_runner_environment`), the worker session and the LSP project
|
|
36
|
+
# context read it too, so a new axis cannot be added to `check` and silently missed here.
|
|
37
|
+
#
|
|
38
|
+
# A non-`check` command legitimately simplifies in two places: no synthesis-failure reporter (there is no
|
|
39
|
+
# diagnostic pipeline) and no synthesis cache store (a single-position probe recomputes cheaply). Plugin
|
|
40
|
+
# loading itself matches check — same `.rigor.yml` `plugins:` config, same ADR-93 auto-wire (already applied
|
|
41
|
+
# by `Configuration.load`), same `enabled:` / `require_magic_comment` semantics via `Plugin::Loader.load`.
|
|
42
|
+
#
|
|
43
|
+
# Fail-soft is the invariant: a project with no plugins, an unresolvable plugin gem, or any error during the
|
|
44
|
+
# plugin-aware build degrades — never crashes. `Plugin::Loader` already isolates per-entry load failures onto
|
|
45
|
+
# the registry; the `rescue` here is the belt-and-suspenders around anything the loader or the plugin-aware
|
|
46
|
+
# env build itself might raise.
|
|
47
|
+
module ProjectEnvironment
|
|
48
|
+
module_function
|
|
49
|
+
|
|
50
|
+
# Builds the plugin-aware, dependency-aware {Rigor::Environment} a non-`check` command works against.
|
|
51
|
+
#
|
|
52
|
+
# @param configuration — the loaded project configuration (already carries the
|
|
53
|
+
# ADR-93 auto-wired `rigor-rbs-inline` entry when the library is resolvable).
|
|
54
|
+
# @param source_files — the file(s) the command inspects. Threaded so each loaded plugin's
|
|
55
|
+
# `source_rbs_synthesizer` runs over them at env-build time; an empty list contributes no synthesized RBS.
|
|
56
|
+
def build(configuration:, source_files:)
|
|
57
|
+
Environment.for_project(
|
|
58
|
+
libraries: configuration.libraries,
|
|
59
|
+
signature_paths: configuration.signature_paths,
|
|
60
|
+
plugin_registry: load_plugin_registry(configuration),
|
|
61
|
+
source_files: source_files,
|
|
62
|
+
**dependency_discovery_options(configuration)
|
|
63
|
+
)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
bare(configuration)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The dependency-discovery axes `check` reads off the configuration, as a keyword hash. Extracted so the
|
|
69
|
+
# list lives in one place: every command that types against the project's real dependencies passes exactly
|
|
70
|
+
# these, and adding an axis to `Environment.for_project` means adding it here once.
|
|
71
|
+
def dependency_discovery_options(configuration)
|
|
72
|
+
{
|
|
73
|
+
bundler_bundle_path: configuration.bundler_bundle_path,
|
|
74
|
+
bundler_auto_detect: configuration.bundler_auto_detect,
|
|
75
|
+
bundler_lockfile: configuration.bundler_lockfile,
|
|
76
|
+
rbs_collection_lockfile: configuration.rbs_collection_lockfile,
|
|
77
|
+
rbs_collection_auto_detect: configuration.rbs_collection_auto_detect
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Loads the project's configured plugins the same way the plugin-inspection commands (`rigor plugins`,
|
|
82
|
+
# `rigor doctor`) do: a `Plugin::Services` with no cache store (these commands recompute) driving
|
|
83
|
+
# `Plugin::Loader.load`. Returns `nil` — so `Environment.for_project` skips the plugin tier entirely — when
|
|
84
|
+
# the project declares no plugins.
|
|
85
|
+
def load_plugin_registry(configuration)
|
|
86
|
+
return nil if configuration.plugins.empty?
|
|
87
|
+
|
|
88
|
+
services = Plugin::Services.new(
|
|
89
|
+
reflection: Reflection,
|
|
90
|
+
type: Type::Combinator,
|
|
91
|
+
configuration: configuration,
|
|
92
|
+
cache_store: nil
|
|
93
|
+
)
|
|
94
|
+
registry = Plugin::Loader.load(configuration: configuration, services: services)
|
|
95
|
+
run_plugin_prepare(registry)
|
|
96
|
+
registry
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `Analysis::WorkerSession#run_plugin_prepare` runs every plugin's `#prepare(services)` before the real
|
|
100
|
+
# `check` pipeline reads `services.fact_store` — an ADR-9 fact published there (#921's `:factory_index`,
|
|
101
|
+
# `rigor-activerecord`'s `:model_index`, …) is otherwise invisible. The probe commands built on
|
|
102
|
+
# {ProjectEnvironment} share the same plugin registry and the same `read_fact` call sites, so they need
|
|
103
|
+
# the same step; skipping it made every cross-plugin fact silently read nil under `type-of` / `annotate`
|
|
104
|
+
# / `trace` / `sig-gen` even once a plugin published it correctly. Per-plugin `rescue` keeps one plugin's
|
|
105
|
+
# `#prepare` failure from costing the rest — the probes have no diagnostic pipeline to report it through,
|
|
106
|
+
# so it is swallowed the same way the rest of this module fails soft.
|
|
107
|
+
def run_plugin_prepare(registry)
|
|
108
|
+
return if registry.nil? || registry.empty?
|
|
109
|
+
|
|
110
|
+
registry.plugins.each do |plugin|
|
|
111
|
+
plugin.prepare(plugin.services)
|
|
112
|
+
rescue StandardError
|
|
113
|
+
next
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The first fail-soft floor: no plugin tier and no synthesized RBS, but still the project's own dependency
|
|
118
|
+
# sources — dropping those is what issue #821 was, so a plugin-loading failure must not cost them.
|
|
119
|
+
def bare(configuration)
|
|
120
|
+
Environment.for_project(
|
|
121
|
+
libraries: configuration.libraries,
|
|
122
|
+
signature_paths: configuration.signature_paths,
|
|
123
|
+
**dependency_discovery_options(configuration)
|
|
124
|
+
)
|
|
125
|
+
rescue StandardError
|
|
126
|
+
minimal(configuration)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# The last floor: RBS core plus the project's own signature paths, which cannot fail on anything outside
|
|
130
|
+
# the project itself.
|
|
131
|
+
def minimal(configuration)
|
|
132
|
+
Environment.for_project(
|
|
133
|
+
libraries: configuration.libraries,
|
|
134
|
+
signature_paths: configuration.signature_paths
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -10,7 +10,24 @@ module Rigor
|
|
|
10
10
|
# A named class, not a bare RuntimeError: {MutationScanner#classify} already folds a per-mutant
|
|
11
11
|
# `StandardError` into the `:harness_error` bucket, and the scanner's file-level rescue must catch THIS
|
|
12
12
|
# and nothing else, so the two failure modes stay distinguishable at every site that handles either.
|
|
13
|
-
|
|
13
|
+
#
|
|
14
|
+
# Issue #790 — it says WHICH of the two refusals below fired, because they want different recovery. A
|
|
15
|
+
# crashed check rule left the Environment as it found it; an analyzer defect (the #784 HKT registry
|
|
16
|
+
# build) recorded itself ON that Environment and is memoised there, so the caller that owns the
|
|
17
|
+
# Environment has to replace it before the next mutant is scored. {#analyzer_defect?} is how a caller
|
|
18
|
+
# tells the two apart without re-deriving the classification from the message it was handed.
|
|
19
|
+
class AnalyzerCrashed < StandardError
|
|
20
|
+
def initialize(message = nil, analyzer_defect: false)
|
|
21
|
+
super(message)
|
|
22
|
+
@analyzer_defect = analyzer_defect
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# True when the refusal was an analyzer defect rather than a crashed check rule — the shape whose
|
|
26
|
+
# cause outlives the run that observed it.
|
|
27
|
+
def analyzer_defect?
|
|
28
|
+
@analyzer_defect
|
|
29
|
+
end
|
|
30
|
+
end
|
|
14
31
|
|
|
15
32
|
# The kill oracles' guard against judging a mutant by a run that never happened.
|
|
16
33
|
#
|
|
@@ -27,34 +44,96 @@ module Rigor
|
|
|
27
44
|
# let {MutationScanner} put the mutant in the `harness_errors` bucket (#264), which is already excluded
|
|
28
45
|
# from `killed + survived` and already surfaced by the CLI. A crash then READS as a crash.
|
|
29
46
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
47
|
+
# Two shapes are armed, and only two.
|
|
48
|
+
#
|
|
49
|
+
# {Analysis::Result#crashed?} — the check-rule rescue, the one shape that REPLACES a file's whole
|
|
50
|
+
# diagnostic list. Refusing more than the shapes below is the same error pointed the other way, and it
|
|
32
51
|
# was measured: on a project whose plugin `prepare` raises, refusing the `:plugin` row took a file from
|
|
33
52
|
# `killed=1 survived=6` to `killed=0 survived=0 harness_errors=7` — the builtin rules had run, their
|
|
34
53
|
# diagnostics were all present, and a real measurement was thrown away. `invoke_plugin_prepare` appends
|
|
35
54
|
# its row to every sequential run, so arming it would have refused every run for the life of the
|
|
36
|
-
# process. `:rbs_build`
|
|
37
|
-
# mutations already drops receivers whose type did not resolve, so a class whose
|
|
38
|
-
# build contributes no measured sites to begin with.
|
|
55
|
+
# process. The user-caused `:rbs_build` rungs are excluded for the same reason plus one more: the site
|
|
56
|
+
# filter that admits mutations already drops receivers whose type did not resolve, so a class whose
|
|
57
|
+
# definition failed to build contributes no measured sites to begin with.
|
|
58
|
+
#
|
|
59
|
+
# {Analysis::CrashSignature.analyzer_defect?} (issue #790) — the one `:rbs_build` rung whose cause is
|
|
60
|
+
# Rigor rather than the user's `sig/`: post-#788 a mutant that makes the implicit HKT scan raise no
|
|
61
|
+
# longer produces a per-file `internal analyzer error` but ONE readable `rbs.coverage.hkt-scan-failed`
|
|
62
|
+
# row, so `Result#crashed?` stays false and the mutant was scored as a measurement over a silently
|
|
63
|
+
# degraded universe — issue #776 was exactly such a mutant. The run is readable (every rule fired), so
|
|
64
|
+
# it stays out of `#crashed?`, which answers the user-facing "may I read these diagnostics?"; what this
|
|
65
|
+
# guard asks is the narrower "is this a valid measurement OF RIGOR?", and it is not.
|
|
66
|
+
#
|
|
67
|
+
# The refusal is asked off {Analysis::CrashSignature} rather than off a second string match here, so a
|
|
68
|
+
# reworded row or a fourth rung added to `ANALYZER_DEFECT_RULES` cannot disarm one copy and leave the
|
|
69
|
+
# others armed (#696).
|
|
70
|
+
#
|
|
71
|
+
# The defect half is asked TWICE, of two surfaces, because each covers the other's blind spot. The row
|
|
72
|
+
# is severity-stamped like every other diagnostic, so `severity_overrides:` resolving `rbs` (or the
|
|
73
|
+
# exact rule id) to `off` drops it from the run entirely — and a harness that exists to find analyzer
|
|
74
|
+
# defects must not be the one thing a project's config can hide one from. {Rigor::Environment} records
|
|
75
|
+
# the failure BEFORE any severity resolution, so `#hkt_scan_failure` still answers. Conversely that slot
|
|
76
|
+
# only ever describes THIS process's Environment: under the fork pool the defect happens in a worker's
|
|
77
|
+
# own Environment and reaches the parent as the row alone.
|
|
39
78
|
module AnalysisGuard
|
|
40
79
|
module_function
|
|
41
80
|
|
|
42
|
-
# Takes the whole {Analysis::Result} rather than its diagnostics so the question is asked
|
|
43
|
-
# {Analysis::Result#crashed?} — the same predicate the spec-side guard reads, off the same
|
|
81
|
+
# Takes the whole {Analysis::Result} rather than its diagnostics so the crash question is asked
|
|
82
|
+
# through {Analysis::Result#crashed?} — the same predicate the spec-side guard reads, off the same
|
|
44
83
|
# {Analysis::CrashSignature} table. Hands back the diagnostics, which is all a kill comparison wants.
|
|
45
84
|
#
|
|
46
|
-
# @param result
|
|
47
|
-
# @param context
|
|
48
|
-
# @
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
85
|
+
# @param result — one analysis run.
|
|
86
|
+
# @param context — which oracle call produced it, so the raise points at the right seam.
|
|
87
|
+
# @param environment — the Environment the run was made over, read for the pre-severity record of a
|
|
88
|
+
# shared-build failure. Optional: a caller that has none loses only the severity-override case.
|
|
89
|
+
# @return the run's diagnostics, when the run was healthy.
|
|
90
|
+
# @raise AnalyzerCrashed
|
|
91
|
+
def checked(result, context:, environment: nil)
|
|
92
|
+
refuse_crash(result, context) if result.crashed?
|
|
93
|
+
defect = result.diagnostics.find { |diagnostic| Analysis::CrashSignature.analyzer_defect?(diagnostic) }
|
|
94
|
+
refuse_defect(context, Analysis::CrashSignature.describe(defect)) if defect
|
|
52
95
|
|
|
96
|
+
failure = shared_build_failure(environment)
|
|
97
|
+
refuse_defect(context, failure) if failure
|
|
98
|
+
|
|
99
|
+
result.diagnostics
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The pre-severity record, described the way {Analysis::CrashSignature.describe} describes a row, so
|
|
103
|
+
# the two refusals read alike whichever surface saw the defect.
|
|
104
|
+
def shared_build_failure(environment)
|
|
105
|
+
return nil unless environment.respond_to?(:hkt_scan_failure)
|
|
106
|
+
|
|
107
|
+
failure = environment.hkt_scan_failure
|
|
108
|
+
return nil if failure.nil?
|
|
109
|
+
|
|
110
|
+
error_class, message, frame, stage = failure
|
|
111
|
+
"#{stage} HKT registry build raised #{error_class}: #{message}#{" at #{frame}" if frame}"
|
|
112
|
+
end
|
|
113
|
+
private_class_method :shared_build_failure
|
|
114
|
+
|
|
115
|
+
def refuse_crash(result, context)
|
|
53
116
|
raise AnalyzerCrashed,
|
|
54
117
|
"#{context}: the analyzer crashed, so this run's diagnostics say nothing about the code " \
|
|
55
118
|
"(#{Analysis::CrashSignature.describe(result.crash_diagnostics.first)}). A kill comparison " \
|
|
56
119
|
"against it would score the mutant a survivor it was never measured against. See issue #686."
|
|
57
120
|
end
|
|
121
|
+
private_class_method :refuse_crash
|
|
122
|
+
|
|
123
|
+
# The run is readable, so the message says what is wrong with it instead: the universe it was produced
|
|
124
|
+
# over is missing the type constructors the RBS `type` aliases and the loaded plugins would have
|
|
125
|
+
# contributed, and the BASELINE this mutant is compared against carries the identical degradation — so
|
|
126
|
+
# the set difference is empty for a reason that has nothing to do with the mutation.
|
|
127
|
+
def refuse_defect(context, description)
|
|
128
|
+
refusal = AnalyzerCrashed.new(
|
|
129
|
+
"#{context}: the analyzer degraded its own type universe, so this run is readable but is not a " \
|
|
130
|
+
"measurement of Rigor (#{description}). Scoring a mutant against it reports the analyzer's own " \
|
|
131
|
+
"defect as the mutant's survival. See issue #790.",
|
|
132
|
+
analyzer_defect: true
|
|
133
|
+
)
|
|
134
|
+
raise refusal
|
|
135
|
+
end
|
|
136
|
+
private_class_method :refuse_defect
|
|
58
137
|
end
|
|
59
138
|
end
|
|
60
139
|
end
|