rigortype 0.4.6 → 0.4.7
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/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -32,13 +32,10 @@ module Rigor
|
|
|
32
32
|
# `foo=`, and deliberately not `==` / `<=` / `!=` / `===`.
|
|
33
33
|
ATTRIBUTE_WRITER = /\A[a-z_][A-Za-z0-9_]*=\z/
|
|
34
34
|
|
|
35
|
-
# Every Hash method that mutates its receiver
|
|
36
|
-
# questions of a `HashShape`
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# `h.clear` does (unless the hash already compares by identity, when it returns before the check), and a
|
|
40
|
-
# method whose one effect was `@h.compare_by_identity` did not read as mutating while this cited the first
|
|
41
|
-
# table alone.
|
|
35
|
+
# Every Hash method that mutates its receiver: the union of two widening tables that answer different
|
|
36
|
+
# questions of a `HashShape` (the pair set; what a read of the pairs answers). Either is a write to the
|
|
37
|
+
# receiver, so either counts. `compare_by_identity` lives only in the second, and it raises `FrozenError`
|
|
38
|
+
# on a frozen hash as `h.clear` does, so a method whose one effect is that call must still read as mutating.
|
|
42
39
|
#
|
|
43
40
|
# `rehash` is in neither table because it changes no read a shape can state: a shape's keys are literals, which
|
|
44
41
|
# a rebuild never merges. It still rebuilds the receiver's table in place, and it is the one name spelt here.
|
|
@@ -363,7 +363,7 @@ module Rigor
|
|
|
363
363
|
# dispatch target is spelled under a different key, and this is the rewrite: the same ancestor walk
|
|
364
364
|
# {#resolve_owner} performs, on the instance side, for `initialize`.
|
|
365
365
|
#
|
|
366
|
-
#
|
|
366
|
+
# Four boundaries, and each of them can only narrow:
|
|
367
367
|
#
|
|
368
368
|
# - a project `def self.new` **wins**. It is the definition `Const.new` actually reaches, and a
|
|
369
369
|
# constructor that overrides `new` is the case where `#initialize` is not the answer. Resolved
|
|
@@ -35,8 +35,8 @@ module Rigor
|
|
|
35
35
|
# without proposing an unrelated label for a genuinely new spelling.
|
|
36
36
|
SUGGESTION_DISTANCE_CAP = 2
|
|
37
37
|
|
|
38
|
-
# The shared registry as shipped. Memoised: the YAML parse is a once-per-process cost,
|
|
39
|
-
#
|
|
38
|
+
# The shared registry as shipped. Memoised: the YAML parse is a once-per-process cost, so it stays
|
|
39
|
+
# lazy rather than paying at `require "rigor"`.
|
|
40
40
|
def self.default
|
|
41
41
|
@default ||= load_file(DATA_PATH)
|
|
42
42
|
end
|
|
@@ -55,7 +55,7 @@ module Rigor
|
|
|
55
55
|
#
|
|
56
56
|
# A count rather than the causes themselves (#434). On redmine the cause renderings were 144,771 of
|
|
57
57
|
# the file's 293,276 bytes, on lines up to 821 characters — simultaneously the half a reviewer
|
|
58
|
-
# cannot read and the half that churns when an unrelated call moves.
|
|
58
|
+
# cannot read and the half that churns when an unrelated call moves. {SnapshotDiff} already makes that
|
|
59
59
|
# argument about origins ("a record keyed by them would churn on every refactor"); the causes are
|
|
60
60
|
# the same class of thing and were the one place it was not applied. What a reviewer needs from the
|
|
61
61
|
# record is the stable fact that the row is not exhaustive and by how much, which is what a count
|
|
@@ -8,8 +8,6 @@ module Rigor
|
|
|
8
8
|
# A tainted summary reads "these effects, and possibly more": the cause says which knowledge the
|
|
9
9
|
# analyzer could not have. Taint never produces a finding — diagnostics read the proven lane
|
|
10
10
|
# only — so this enum is a reporting vocabulary, not a diagnostic one.
|
|
11
|
-
#
|
|
12
|
-
# Nothing produces causes yet; the collector of #379 is the first writer.
|
|
13
11
|
module TaintCause
|
|
14
12
|
# Closed and ordered by how it arises: dispatch the analyzer could not resolve first, then
|
|
15
13
|
# attribution it had to take on trust, then the collector's own limits.
|
|
@@ -586,9 +586,9 @@ module Rigor
|
|
|
586
586
|
# #1071 widens the answer per `respond_to` arm: a unit that spelled a dispatcher renders one
|
|
587
587
|
# conventional `<action>.<fmt>` template per arm rather than one `<action>.html`, so the row is
|
|
588
588
|
# applied once per arm the walk read, each answered outright (a `responds:` row at the arm's own
|
|
589
|
-
# top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html`
|
|
590
|
-
#
|
|
591
|
-
#
|
|
589
|
+
# top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html` answer
|
|
590
|
+
# is kept where no arm was read, and beside the arms when the dispatcher is nested under a branch,
|
|
591
|
+
# because the plain fall-through still exists on the path that skips it.
|
|
592
592
|
def apply_unit_callees
|
|
593
593
|
return if @responded || @singleton || @non_public
|
|
594
594
|
return if @owner_class.nil? || @method_name.nil?
|
|
@@ -811,11 +811,9 @@ module Rigor
|
|
|
811
811
|
#
|
|
812
812
|
# **Unless the syntax named the class** (#463). The `Dynamic` exclusion exists because a projected
|
|
813
813
|
# receiver class is a guess; a written constant path is not one, and {#catalog_target} already
|
|
814
|
-
# trusts it for the row lookup. Refusing the posture there made the catalogue's reach
|
|
815
|
-
# whether the bundled rbs
|
|
816
|
-
#
|
|
817
|
-
# `net-imap`. That is why the `Net::FTP` row has never once fired, and why Redmine's IMAP and POP3
|
|
818
|
-
# pollers proved no network at all.
|
|
814
|
+
# trusts it for the row lookup. Refusing the posture there made the catalogue's reach depend on
|
|
815
|
+
# whether the bundled rbs ships a signature for the constant: `Net::HTTP.get` proved `io.net.http`,
|
|
816
|
+
# while `Net::IMAP.new` proved nothing, because rbs ships `net-http` and not `net-imap`.
|
|
819
817
|
#
|
|
820
818
|
# The site is then **claimed**, and so discharges, like every other catalogue answer. That is not a
|
|
821
819
|
# softening of the `dynamic-receiver` rule but its premise failing: the taint says the class the
|
|
@@ -974,12 +972,10 @@ module Rigor
|
|
|
974
972
|
positional_arity(node).positive?
|
|
975
973
|
end
|
|
976
974
|
|
|
977
|
-
# The class the catalogue would look this call up under, as `[owner, singleton,
|
|
975
|
+
# The class the catalogue would look this call up under, as `[owner, singleton, implicit, named]`.
|
|
978
976
|
# Spelled from the syntax where the syntax settles it and from the typer's receiver otherwise;
|
|
979
|
-
# `[nil, …]` when neither does.
|
|
980
|
-
#
|
|
981
|
-
# author wrote**, which is what licenses the posture on a receiver the typer could not resolve
|
|
982
|
-
# ({#posture_allowed?}).
|
|
977
|
+
# `[nil, …]` when neither does. `named` says the owner came from a **constant path the author wrote**,
|
|
978
|
+
# which is what licenses the posture on a receiver the typer could not resolve ({#posture_allowed?}).
|
|
983
979
|
def catalog_target(node, record)
|
|
984
980
|
receiver = node.receiver
|
|
985
981
|
return ["Kernel", false, true, false] if receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
@@ -31,7 +31,7 @@ module Rigor
|
|
|
31
31
|
# The list is hard-coded for the MVP because it tracks rigor's bundled coverage 1:1. When a new gem is
|
|
32
32
|
# vendored under `data/vendored_gem_sigs/` or added to `DEFAULT_LIBRARIES`, add its name here.
|
|
33
33
|
SKIPPED_GEMS_BY_DEFAULT = Set[
|
|
34
|
-
# DEFAULT_LIBRARIES (lib/rigor/environment.rb)
|
|
34
|
+
# DEFAULT_LIBRARIES (lib/rigor/environment/default_libraries.rb)
|
|
35
35
|
"pathname", "optparse", "json", "yaml", "fileutils",
|
|
36
36
|
"tempfile", "tmpdir", "stringio", "forwardable",
|
|
37
37
|
"digest", "securerandom", "uri", "logger", "date",
|
|
@@ -100,14 +100,10 @@ module Rigor
|
|
|
100
100
|
expected_dirs = expected_gem_dirs(locked_gems)
|
|
101
101
|
filtered.select do |sig_dir|
|
|
102
102
|
if git_sourced_layout?(sig_dir)
|
|
103
|
-
# A git install's directory
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
# its NAME is still present in the lockfile (now via the rubygems entry). Matching on name only
|
|
108
|
-
# would silently readmit that stale git install — this repo's own `vendor/bundle` had exactly
|
|
109
|
-
# this after `binpacker` moved from `git:` to a released gem. `locked_gems` must say this name is
|
|
110
|
-
# CURRENTLY git-sourced, not merely present.
|
|
103
|
+
# A git install's directory carries no version, so the lockfile filter can only check the gem NAME.
|
|
104
|
+
# Name alone is not enough: a gem that moved from `git:` to a released version keeps its stale
|
|
105
|
+
# `bundler/gems/` checkout until `bundle clean`, under a name the lockfile still lists. `locked_gems`
|
|
106
|
+
# must say this name is CURRENTLY git-sourced, not merely present.
|
|
111
107
|
locked = locked_gems[gem_name_from_sig_path(sig_dir)]
|
|
112
108
|
!!locked&.git_source
|
|
113
109
|
else
|
|
@@ -9,8 +9,8 @@ module Rigor
|
|
|
9
9
|
# This is the static fast path for built-ins; `Environment` falls through to `RbsLoader` for all other
|
|
10
10
|
# names — the two tiers are complementary.
|
|
11
11
|
#
|
|
12
|
-
# See docs/internal-spec/inference-engine.md for the binding contract (
|
|
13
|
-
# recognised).
|
|
12
|
+
# See docs/internal-spec/inference-engine.md for the binding contract (the classes in
|
|
13
|
+
# `SLICE_1_BUILT_INS` MUST always be recognised).
|
|
14
14
|
class ClassRegistry
|
|
15
15
|
SLICE_1_BUILT_INS = [
|
|
16
16
|
Integer,
|
|
@@ -27,7 +27,7 @@ module Rigor
|
|
|
27
27
|
# Common Ruby core classes that user code routinely names by constant reference. Adding them to the
|
|
28
28
|
# registry lets `nominal_for_name` resolve `Array`, `Hash`, etc. without each call site re-listing
|
|
29
29
|
# them. The static registry is the cheap fast path; `RbsLoader` extends coverage to all other
|
|
30
|
-
# RBS-declared names
|
|
30
|
+
# RBS-declared names.
|
|
31
31
|
SLICE_2_BUILT_INS = [
|
|
32
32
|
Array,
|
|
33
33
|
Hash,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../configuration_error"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
class Environment
|
|
8
|
+
# Issue #2517 — a core signature directory chosen IN PLACE OF the `rbs` gem's own `core/` (`core_root:` in
|
|
9
|
+
# `.rigor.yml`). The caller is a provider such as rbs-shifter, which rewrites upstream core for an older
|
|
10
|
+
# CRuby (`Array#rfind` gone, `String#strip` without arguments); Rigor accepts the directory and passes it to
|
|
11
|
+
# `RBS::EnvironmentLoader.new(core_root:)`, so the default core is never loaded beside it.
|
|
12
|
+
#
|
|
13
|
+
# An explicit choice is never second-guessed by a fallback: a directory that is missing, unreadable or holds
|
|
14
|
+
# no signature, and an environment that fails to build from it, raise {CoreRootError} instead of analysing
|
|
15
|
+
# against the default core or against a degraded `Dynamic[top]` environment. Both would report a project as
|
|
16
|
+
# clean for the very APIs the selection exists to reject.
|
|
17
|
+
module CoreRoot
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param path — the configured directory, already resolved against the config file's directory.
|
|
21
|
+
# @return the absolute directory as a frozen String (`Ractor.shareable?`, so a frozen loader stays shareable).
|
|
22
|
+
# @raise CoreRootError — the directory cannot serve as a core.
|
|
23
|
+
def validate!(path)
|
|
24
|
+
absolute = File.expand_path(path.to_s)
|
|
25
|
+
reason = invalid_reason(absolute)
|
|
26
|
+
raise CoreRootError, "core_root: #{absolute.inspect} #{reason}" if reason
|
|
27
|
+
|
|
28
|
+
absolute.dup.freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# A selected core inside a signature root, or one containing it, loads its files twice — once as core and once
|
|
32
|
+
# as project signatures — and every class it declares then fails to build on its duplicated methods.
|
|
33
|
+
#
|
|
34
|
+
# @param core_root — a {.validate!}d directory.
|
|
35
|
+
# @param signature_paths — the project's own signature roots.
|
|
36
|
+
# @raise CoreRootError — the two overlap.
|
|
37
|
+
def reject_overlap!(core_root, signature_paths)
|
|
38
|
+
core = File.join(real_path(core_root), "")
|
|
39
|
+
Array(signature_paths).each do |path|
|
|
40
|
+
root = File.join(real_path(path), "")
|
|
41
|
+
next unless core.start_with?(root) || root.start_with?(core)
|
|
42
|
+
|
|
43
|
+
raise CoreRootError, "core_root: #{core_root.inspect} overlaps the signature path #{path.to_s.inspect}, so " \
|
|
44
|
+
"its signatures would load twice; move the core outside the project's signature paths"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Compared resolved, because the project-signature walk does not follow a symlinked directory: a `sig/core`
|
|
49
|
+
# link to a core kept elsewhere loads once, as core, and must not be rejected. A path that does not exist yet
|
|
50
|
+
# resolves through its nearest existing ancestor, so the two sides of a comparison are spelled alike.
|
|
51
|
+
def real_path(path)
|
|
52
|
+
absolute = File.expand_path(path.to_s)
|
|
53
|
+
missing = []
|
|
54
|
+
until File.exist?(absolute) || File.dirname(absolute) == absolute
|
|
55
|
+
missing.unshift(File.basename(absolute))
|
|
56
|
+
absolute = File.dirname(absolute)
|
|
57
|
+
end
|
|
58
|
+
File.join(File.realpath(absolute), *missing)
|
|
59
|
+
rescue SystemCallError
|
|
60
|
+
File.expand_path(path.to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def invalid_reason(absolute)
|
|
64
|
+
return "does not exist" unless File.exist?(absolute)
|
|
65
|
+
return "is not a directory" unless File.directory?(absolute)
|
|
66
|
+
return "is not readable" unless File.readable?(absolute)
|
|
67
|
+
|
|
68
|
+
"contains no .rbs signature file" if RootedGlob.glob(absolute, "**/*.rbs").empty?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The selected core cannot be used. A {ConfigurationError}, so the CLI reports it as one `rigor:` line with
|
|
73
|
+
# the usage exit code rather than as a crash.
|
|
74
|
+
class CoreRootError < ConfigurationError
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
class Environment
|
|
5
|
-
#
|
|
5
|
+
# Stdlib libraries that `Environment.for_project` loads on top of RBS core
|
|
6
6
|
# unless the caller passes an explicit `libraries:` array. Each entry MUST be a stdlib library name
|
|
7
7
|
# accepted by `RBS::EnvironmentLoader#has_library?`; unknown libraries MUST fail-soft
|
|
8
8
|
# (`RbsLoader#build_env` already filters through `has_library?`). The default set covers the common
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
class Environment
|
|
7
|
+
# Issue #2422 — what a bundled plugin's deferred signature file loads as, against the environment every other
|
|
8
|
+
# signature source has already built. The #610 arity check stands a file down whole first
|
|
9
|
+
# (`RbsLoader.generic_arity_conflict`); {.plan} decides the rest declaration by declaration and member by
|
|
10
|
+
# member, with {GatedSignaturePlan}'s #1713 helpers:
|
|
11
|
+
#
|
|
12
|
+
# - a method another source declares (a `def`, an attribute or an alias), where that source is NOT the project's own
|
|
13
|
+
# (an `rbs collection install`, a gem's `sig/`, a vendored or another plugin's file), is *composed*: the plugin's
|
|
14
|
+
# `def` becomes an overload continuation, `def m: <plugin overloads> | ...`, so RBS puts the plugin's overloads
|
|
15
|
+
# first and keeps the other source's as the fallback. The plugin's signatures are often written wider on purpose
|
|
16
|
+
# (`Object#html_safe?: () -> bool` where the collection says `-> false`), and standing them down would let the
|
|
17
|
+
# narrower one fold correct code into dead branches;
|
|
18
|
+
# - any other method, attribute or alias another source declares stands down: the project's own `sig/` or
|
|
19
|
+
# inline RBS binds by #610's rule, and an attribute, an alias or one side of a `def self?.m` cannot be
|
|
20
|
+
# continued ({GatedSignaturePlan.untaken} narrows an `attr_accessor` or `self?` to its untaken half);
|
|
21
|
+
# - a constant, global, type alias, interface or class alias another source already declares, and a type
|
|
22
|
+
# whose kind (class or module) contradicts another source's, stands down on its own, where adding it would
|
|
23
|
+
# have raised `RBS::DuplicatedDeclarationError` and dropped the plugin's whole file;
|
|
24
|
+
# - a class header's superclass clause another source already gives the class one stands down (#2439), where two
|
|
25
|
+
# different clauses would have raised `RBS::SuperclassMismatchError`; the plugin's declaration reopens the class.
|
|
26
|
+
#
|
|
27
|
+
# A declaration with nothing taken comes back as the same object, so a file with no collision loads as it
|
|
28
|
+
# stands. {.standdown_rows} reads what stood down back off the built environment, so a warm run off the env
|
|
29
|
+
# cache reports what the cold run decided.
|
|
30
|
+
module DeferredSignaturePlan
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# @param own — the buffer names that are the project's own sources: its `signature_paths:` files (not the
|
|
34
|
+
# collection or gem directories also on that list) and its inline RBS.
|
|
35
|
+
def plan(decls, env, own, namespace = nil)
|
|
36
|
+
planned = decls.filter_map { |decl| plan_declaration(decl, env, own, namespace) }
|
|
37
|
+
same?(planned, decls) ? decls : planned
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def plan_declaration(decl, env, own, namespace)
|
|
41
|
+
name = GatedSignaturePlan.qualify(namespace, declared_name(decl))
|
|
42
|
+
if GatedSignaturePlan.type_declaration?(decl)
|
|
43
|
+
return nil if kind_clash?(env, decl, name)
|
|
44
|
+
|
|
45
|
+
return plan_type(decl, env, own, name)
|
|
46
|
+
end
|
|
47
|
+
declared_elsewhere(env, decl, name) ? nil : decl
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def plan_type(decl, env, own, name)
|
|
51
|
+
others = GatedSignaturePlan.other_declarations(env, name)
|
|
52
|
+
taken = GatedSignaturePlan.declared_keys(others)
|
|
53
|
+
external = others.reject { |other| own.include?(buffer_of(other)) }
|
|
54
|
+
defined = GatedSignaturePlan.declared_keys(external) | definition_keys(external)
|
|
55
|
+
members = decl.members.filter_map do |member|
|
|
56
|
+
next plan_declaration(member, env, own, name) if declaration?(member)
|
|
57
|
+
next member if taken.empty?
|
|
58
|
+
|
|
59
|
+
composed(member, defined) || GatedSignaturePlan.untaken(member, taken)
|
|
60
|
+
end
|
|
61
|
+
drop = superclass_given?(decl, others)
|
|
62
|
+
return decl if same?(members, decl.members) && !drop
|
|
63
|
+
|
|
64
|
+
GatedSignaturePlan.rebuild(decl, members: members, drop_super_class: drop)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Issue #2439 — true when `decl` is a class with a superclass clause and another source (`others`) already
|
|
68
|
+
# gives the class one. Two different clauses raise `RBS::SuperclassMismatchError` and the class and every
|
|
69
|
+
# descendant read `Dynamic[top]`, so the plugin's header stands down and reopens the class without one. The
|
|
70
|
+
# two names are not compared: they are not resolved yet, and the other source's superclass binds either way.
|
|
71
|
+
def superclass_given?(decl, others)
|
|
72
|
+
super_class?(decl) && others.any? { |other| super_class?(other) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# `member` as an overload continuation, or nil when it cannot be one: only a plain one-sided `def` can, and
|
|
76
|
+
# only onto a method — a `def`, an attribute or an alias, all of which RBS continues — that a source other
|
|
77
|
+
# than the project's own declares.
|
|
78
|
+
def composed(member, defined)
|
|
79
|
+
return nil unless member.is_a?(::RBS::AST::Members::MethodDefinition) && !member.overloading?
|
|
80
|
+
return nil if member.kind == :singleton_instance
|
|
81
|
+
|
|
82
|
+
key = [member.name, member.kind]
|
|
83
|
+
return nil unless defined.include?(key)
|
|
84
|
+
|
|
85
|
+
member.update(overloading: true)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The `[name, kind]` keys a plain or continued `def` in `decls` declares — what a continuation can extend.
|
|
89
|
+
def definition_keys(decls)
|
|
90
|
+
decls.each_with_object(Set.new) do |decl, keys|
|
|
91
|
+
decl.members.each do |member|
|
|
92
|
+
next unless member.is_a?(::RBS::AST::Members::MethodDefinition)
|
|
93
|
+
|
|
94
|
+
sides = member.kind == :singleton_instance ? %i[instance singleton] : [member.kind]
|
|
95
|
+
sides.each { |side| keys << [member.name, side] }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def kind_clash?(env, decl, name)
|
|
101
|
+
type_name = ::RBS::TypeName.parse("::#{name}")
|
|
102
|
+
return true if env.class_alias_decls.key?(type_name)
|
|
103
|
+
|
|
104
|
+
first = GatedSignaturePlan.other_declarations(env, name).first
|
|
105
|
+
!first.nil? && !first.instance_of?(decl.class)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The buffer of the declaration another source already makes under `decl`'s name, or nil.
|
|
109
|
+
def declared_elsewhere(env, decl, name)
|
|
110
|
+
entry = declaration_entry(env, decl, name)
|
|
111
|
+
entry && (RbsLoader.declaration_buffer_name(entry.decl) || "")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def declaration_entry(env, decl, name)
|
|
115
|
+
type_name = -> { ::RBS::TypeName.parse("::#{name}") }
|
|
116
|
+
case decl
|
|
117
|
+
when ::RBS::AST::Declarations::Constant then env.constant_decls[type_name.call]
|
|
118
|
+
when ::RBS::AST::Declarations::Global then env.global_decls[decl.name]
|
|
119
|
+
when ::RBS::AST::Declarations::TypeAlias then env.type_alias_decls[type_name.call]
|
|
120
|
+
when ::RBS::AST::Declarations::Interface then env.interface_decls[type_name.call]
|
|
121
|
+
when ::RBS::AST::Declarations::ClassAlias, ::RBS::AST::Declarations::ModuleAlias
|
|
122
|
+
env.class_alias_decls[type_name.call] || env.class_decls[type_name.call]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# A class or module alias names itself as `new_name`; every other declaration as `name`.
|
|
127
|
+
def declared_name(decl)
|
|
128
|
+
decl.respond_to?(:new_name) ? decl.new_name : decl.name
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def declaration?(member)
|
|
132
|
+
member.is_a?(::RBS::AST::Declarations::Base)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def same?(planned, original)
|
|
136
|
+
planned.size == original.size && planned.zip(original).all? { |a, b| a.equal?(b) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def buffer_of(decl)
|
|
140
|
+
RbsLoader.declaration_buffer_name(decl)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# One {.standdown_rows} walk over a deferred file: the built env, the file's buffer name, the rows so far, and
|
|
144
|
+
# the env's type-name resolver, built on first use (only a dropped superclass clause needs it).
|
|
145
|
+
class Walk
|
|
146
|
+
attr_reader :env, :file, :rows
|
|
147
|
+
|
|
148
|
+
def initialize(env, file)
|
|
149
|
+
@env = env
|
|
150
|
+
@file = file
|
|
151
|
+
@rows = []
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# rbs 4.x builds the resolver with `TypeNameResolver.build(env)`; rbs 3.x's constructor takes the env, which
|
|
155
|
+
# the 4.x signature Rigor reads (`new(all_names, aliases)`) does not describe.
|
|
156
|
+
def resolver
|
|
157
|
+
@resolver ||= begin
|
|
158
|
+
klass = ::RBS::Resolver::TypeNameResolver
|
|
159
|
+
if klass.respond_to?(:build)
|
|
160
|
+
klass.build(env)
|
|
161
|
+
else
|
|
162
|
+
klass.new(env) # rigor:disable call.wrong-arity
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# What {.plan} left out of the deferred `file` (its parsed `decls`) in the built `env`, as `[declaration,
|
|
169
|
+
# cause_file]` rows — `Object#acts_like?`, `Time.at`, `FFI::Pointer::NULL`, `Foo < Bar`, or a type name — with
|
|
170
|
+
# the buffer of the other source's declaration, or nil when it cannot be named. A composed method is not a row:
|
|
171
|
+
# the plugin's overloads still bind.
|
|
172
|
+
def standdown_rows(env, file, decls)
|
|
173
|
+
walk = Walk.new(env, file)
|
|
174
|
+
decls.each { |decl| declaration_rows(walk, decl, nil) }
|
|
175
|
+
walk.rows.uniq
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# `context` is RBS's resolver context for where `decl` is written: nil at the top level, else `[outer,
|
|
179
|
+
# absolute name of the enclosing declaration]`, as `RBS::Environment#append_context` builds it.
|
|
180
|
+
def declaration_rows(walk, decl, context)
|
|
181
|
+
name = GatedSignaturePlan.qualify(namespace_of(context), declared_name(decl))
|
|
182
|
+
return type_rows(walk, decl, name, context) if GatedSignaturePlan.type_declaration?(decl)
|
|
183
|
+
|
|
184
|
+
cause = displaced_by(walk.env, decl, name, walk.file)
|
|
185
|
+
walk.rows << [decl.is_a?(::RBS::AST::Declarations::Global) ? decl.name.to_s : name, cause] if cause
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def namespace_of(context)
|
|
189
|
+
context && context.last.to_s.delete_prefix("::")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# A non-nil buffer name (possibly empty) when another source's declaration of `decl` is the one loaded.
|
|
193
|
+
def displaced_by(env, decl, name, file)
|
|
194
|
+
entry = declaration_entry(env, decl, name)
|
|
195
|
+
return nil if entry.nil?
|
|
196
|
+
|
|
197
|
+
buffer = RbsLoader.declaration_buffer_name(entry.decl)
|
|
198
|
+
buffer == file ? nil : buffer
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def type_rows(walk, decl, name, context)
|
|
202
|
+
loaded, others = loaded_and_others(walk, name)
|
|
203
|
+
if loaded.empty?
|
|
204
|
+
cause = others.empty? ? alias_cause(walk.env, name) : buffer_of(others.first)
|
|
205
|
+
walk.rows << [name, cause] if cause
|
|
206
|
+
return
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
walk.rows.concat(superclass_rows(walk, decl, name, context, others))
|
|
210
|
+
walk.rows.concat(member_rows(decl, name, loaded, others))
|
|
211
|
+
inner = [context, ::RBS::TypeName.parse("::#{name}")]
|
|
212
|
+
decl.members.each { |member| declaration_rows(walk, member, inner) if declaration?(member) }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The declarations of `name` the walked file loaded, and the ones other sources made (synthesized stubs left out).
|
|
216
|
+
def loaded_and_others(walk, name)
|
|
217
|
+
entry = walk.env.class_decls[::RBS::TypeName.parse("::#{name}")]
|
|
218
|
+
declarations = entry.nil? ? [] : RbsLoader.entry_declarations(entry)
|
|
219
|
+
loaded = declarations.select { |other| buffer_of(other) == walk.file }
|
|
220
|
+
[loaded, declarations.reject { |other| loaded.include?(other) || GatedSignaturePlan.synthetic?(other) }]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# The buffer of a class or module alias declaring `name` (empty when it cannot be named), or nil.
|
|
224
|
+
def alias_cause(env, name)
|
|
225
|
+
entry = env.class_alias_decls[::RBS::TypeName.parse("::#{name}")]
|
|
226
|
+
entry && (RbsLoader.declaration_buffer_name(entry.decl) || "")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# The plugin's superclass clause as a row (`ActiveRecord::StatementInvalid < ActiveRecordError`) when the plan
|
|
230
|
+
# dropped it for another source's ({.superclass_given?}) and that source's names a different class or type
|
|
231
|
+
# arguments. The plan drops an identical clause too, but nothing is lost, so no row.
|
|
232
|
+
def superclass_rows(walk, decl, name, context, others)
|
|
233
|
+
return [] unless superclass_given?(decl, others)
|
|
234
|
+
|
|
235
|
+
cause = others.find { |other| super_class?(other) }
|
|
236
|
+
return [] if same_super_class?(walk, decl.super_class, context, cause.super_class)
|
|
237
|
+
|
|
238
|
+
written = decl.super_class
|
|
239
|
+
args = written.args.empty? ? "" : "[#{written.args.join(', ')}]"
|
|
240
|
+
[["#{name} < #{written.name}#{args}", buffer_of(cause)]]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Whether the plugin's clause `written`, resolved as RBS resolves a superclass (in the context the class is
|
|
244
|
+
# written in, not its own), names the class and type arguments of `other`, the built env's already-absolute
|
|
245
|
+
# clause, class aliases normalised on both sides. An unresolvable name differs.
|
|
246
|
+
def same_super_class?(walk, written, context, other)
|
|
247
|
+
name = absolute_name(walk, written.name, context)
|
|
248
|
+
return false if name.nil? || normalized(walk.env, name) != normalized(walk.env, other.name)
|
|
249
|
+
|
|
250
|
+
written.args.map { |arg| normalized_type(walk, arg, context) } ==
|
|
251
|
+
other.args.map { |arg| normalized_type(walk, arg, nil) }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def absolute_name(walk, name, context)
|
|
255
|
+
name.absolute? ? name : walk.resolver.resolve(name, context: context)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def normalized(env, name)
|
|
259
|
+
name.class? ? env.normalize_module_name(name) : name
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def normalized_type(walk, type, context)
|
|
263
|
+
return type.to_s unless type.respond_to?(:map_type_name)
|
|
264
|
+
|
|
265
|
+
type.map_type_name { |name, _, _| normalized(walk.env, absolute_name(walk, name, context) || name) }.to_s
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def super_class?(decl)
|
|
269
|
+
decl.is_a?(::RBS::AST::Declarations::Class) && !decl.super_class.nil?
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def member_rows(decl, name, loaded, others)
|
|
273
|
+
present = GatedSignaturePlan.declared_keys(loaded) | definition_keys(loaded)
|
|
274
|
+
decl.members.flat_map do |member|
|
|
275
|
+
RbsLoader.member_method_keys(member).reject { |key| present.include?(key) }.filter_map do |key|
|
|
276
|
+
cause = others.find { |other| other.members.any? { |m| RbsLoader.member_method_keys(m).include?(key) } }
|
|
277
|
+
cause && [GatedSignaturePlan.member_label(name, key), buffer_of(cause)]
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -13,7 +13,7 @@ module Rigor
|
|
|
13
13
|
module GatedSignatureGuard
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
|
-
# @param dir — the vendored directory
|
|
16
|
+
# @param dir — the vendored directory.
|
|
17
17
|
# @return the absolute-less names (`"Prime::PseudoPrimeGenerator"`) of every class and module it declares.
|
|
18
18
|
def type_names(dir)
|
|
19
19
|
RootedGlob.glob(dir, "*.rbs").flat_map do |file|
|
|
@@ -213,10 +213,13 @@ module Rigor
|
|
|
213
213
|
end
|
|
214
214
|
end
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
# `decl` with `members` in place of its own. A class keeps its superclass unless `drop_super_class` (#2439: a
|
|
217
|
+
# plugin's class header another source already gives a superclass reopens the class without one).
|
|
218
|
+
def rebuild(decl, members:, drop_super_class: false)
|
|
217
219
|
if decl.is_a?(::RBS::AST::Declarations::Class)
|
|
218
220
|
::RBS::AST::Declarations::Class.new(
|
|
219
|
-
name: decl.name, type_params: decl.type_params, super_class: decl.super_class,
|
|
221
|
+
name: decl.name, type_params: decl.type_params, super_class: drop_super_class ? nil : decl.super_class,
|
|
222
|
+
members: members,
|
|
220
223
|
annotations: decl.annotations, location: decl.location, comment: decl.comment
|
|
221
224
|
)
|
|
222
225
|
else
|
|
@@ -7,7 +7,7 @@ module Rigor
|
|
|
7
7
|
# Gemfile.lock parser for target-project bundler awareness (O4 Layer 3, implemented).
|
|
8
8
|
#
|
|
9
9
|
# Parses a target project's `Gemfile.lock` via Bundler's `LockfileParser` and exposes the locked gem set
|
|
10
|
-
# as a frozen
|
|
10
|
+
# as a frozen map from gem name to `LockedGem`. Used by
|
|
11
11
|
# {Rigor::Environment::BundleSigDiscovery} as a filter so the discovered `sig/` directories under the
|
|
12
12
|
# bundler install root are limited to gems the project actually declares (and at the version it declared
|
|
13
13
|
# them).
|
|
@@ -38,8 +38,12 @@ module Rigor
|
|
|
38
38
|
# variant for the readers that want a single version (coverage report, doctor): the `ruby` one when the
|
|
39
39
|
# lockfile has it, otherwise the last parsed. Defaults to the single `[version, platform]` pair, and the
|
|
40
40
|
# pair must be one of `variants`.
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
#
|
|
42
|
+
# `path_source` is the absolute directory a lockfile `PATH` stanza resolved this gem from (its `remote:`
|
|
43
|
+
# expanded against the lockfile's directory), and `nil` for every other source, `GIT` included. Issue
|
|
44
|
+
# #2441 reads it to tell the project's own path gems apart from the rest of the bundle. Defaults `nil`.
|
|
45
|
+
LockedGem = Data.define(:name, :version, :platform, :git_source, :variants, :path_source) do
|
|
46
|
+
def initialize(name:, version:, platform:, git_source: false, variants: nil, path_source: nil)
|
|
43
47
|
version = -version.to_s
|
|
44
48
|
platform = -platform.to_s
|
|
45
49
|
variants = (variants || [[version, platform]]).map { |v, p| [-v.to_s, -p.to_s].freeze }.uniq.freeze
|
|
@@ -52,7 +56,8 @@ module Rigor
|
|
|
52
56
|
version: version,
|
|
53
57
|
platform: platform,
|
|
54
58
|
git_source: git_source ? true : false,
|
|
55
|
-
variants: variants
|
|
59
|
+
variants: variants,
|
|
60
|
+
path_source: path_source && -path_source.to_s
|
|
56
61
|
)
|
|
57
62
|
end
|
|
58
63
|
end
|
|
@@ -115,9 +120,9 @@ module Rigor
|
|
|
115
120
|
def self.do_parse_dependency_names(path)
|
|
116
121
|
parser = Bundler::LockfileParser.new(File.read(path.to_s))
|
|
117
122
|
# `#dependencies` is the parsed `DEPENDENCIES` section keyed by gem name, so the version constraint
|
|
118
|
-
# and the pinned-source `!` suffix are already stripped for us.
|
|
119
|
-
#
|
|
120
|
-
parser.dependencies.keys.to_set { |name| -name.to_s }.freeze
|
|
123
|
+
# and the pinned-source `!` suffix are already stripped for us. Rigor's vendored bundler
|
|
124
|
+
# signature declares it as a Hash keyed by gem name.
|
|
125
|
+
parser.dependencies.keys.to_set { |name| -name.to_s }.freeze
|
|
121
126
|
rescue ::Ractor::IsolationError => e
|
|
122
127
|
warn_isolated(path, e)
|
|
123
128
|
EMPTY_NAMES
|
|
@@ -171,10 +176,9 @@ module Rigor
|
|
|
171
176
|
parser = Bundler::LockfileParser.new(body)
|
|
172
177
|
locked = parser.specs.each_with_object({}) do |spec, h|
|
|
173
178
|
# `Bundler::LazySpecification` carries name, version, platform. Platform is `Gem::Platform` or the
|
|
174
|
-
# symbol `:ruby`; both stringify cleanly.
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
platform = spec.platform.to_s # rigor:disable undefined-method
|
|
179
|
+
# symbol `:ruby`; both stringify cleanly. Rigor's vendored bundler signature declares
|
|
180
|
+
# `LazySpecification#platform`, so the call needs no suppression.
|
|
181
|
+
platform = spec.platform.to_s
|
|
178
182
|
# `#source` is the `Bundler::Source::*` this spec resolved from — `Git` for a `GIT` lockfile
|
|
179
183
|
# stanza, `Rubygems` for a `GEM` one, `Path`/`Gemspec` for `path:`. Only `Git` sources land under
|
|
180
184
|
# {BundleSigDiscovery}'s `bundler/gems/` walk.
|
|
@@ -182,7 +186,8 @@ module Rigor
|
|
|
182
186
|
# instead of letting the last one overwrite the rest (#1844).
|
|
183
187
|
entry = LockedGem.new(
|
|
184
188
|
name: spec.name, version: spec.version.to_s, platform: platform,
|
|
185
|
-
git_source: spec.source.is_a?(Bundler::Source::Git)
|
|
189
|
+
git_source: spec.source.is_a?(Bundler::Source::Git),
|
|
190
|
+
path_source: path_source_dir(spec.source, path)
|
|
186
191
|
)
|
|
187
192
|
h[entry.name] = (prior = h[entry.name]) ? merge_variant(prior, entry) : entry
|
|
188
193
|
end
|
|
@@ -196,6 +201,17 @@ module Rigor
|
|
|
196
201
|
end
|
|
197
202
|
private_class_method :do_parse
|
|
198
203
|
|
|
204
|
+
# The absolute directory a `PATH` stanza names, resolved against the lockfile's own directory — never
|
|
205
|
+
# against `Bundler.root`, which is Rigor's process and not the analysed project. `Bundler::Source::Git`
|
|
206
|
+
# subclasses `Path`, so a `GIT` stanza is excluded by name.
|
|
207
|
+
def self.path_source_dir(source, lockfile_path)
|
|
208
|
+
return nil unless source.is_a?(Bundler::Source::Path)
|
|
209
|
+
return nil if source.is_a?(Bundler::Source::Git)
|
|
210
|
+
|
|
211
|
+
File.expand_path(source.path.to_s, File.dirname(File.expand_path(lockfile_path.to_s)))
|
|
212
|
+
end
|
|
213
|
+
private_class_method :path_source_dir
|
|
214
|
+
|
|
199
215
|
# Folds a further platform variant of an already-parsed name into it. The representative keeps a `ruby`
|
|
200
216
|
# variant once seen; otherwise the last variant wins, as it did before variants were kept.
|
|
201
217
|
def self.merge_variant(prior, entry)
|