rigortype 0.4.5 → 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 +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- 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 +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -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/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- 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 +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -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 +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- 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 +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- 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/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- 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 +38 -33
- 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 +60 -18
- 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 +188 -93
- 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 +27 -21
- 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 +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- 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 +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- 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 +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- 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 +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- 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 +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- 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 +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- 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 +126 -0
- 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 +170 -0
- 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 +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- 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/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- 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 +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -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 +113 -12
- 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 +7 -3
- 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 +59 -24
- 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 +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -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 +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- 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_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- 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 +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- 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/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- 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 +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- 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 +9 -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 +28 -9
- 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 +98 -4
- 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 +12 -6
- 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 +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- 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 +38 -16
- data/lib/rigor/plugin/registry.rb +41 -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 +96 -2
- 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 +25 -2
- 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 +379 -36
- 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 +668 -84
- 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 +7 -6
- 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/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- 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 +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- 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 +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -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 +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -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-devise/lib/rigor/plugin/devise.rb +5 -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 +7 -6
- 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 +102 -11
- 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 +5 -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/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- 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/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- 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 +55 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "json_document"
|
|
4
5
|
require "optionparser"
|
|
5
6
|
|
|
6
7
|
require_relative "../analysis/rule_catalog"
|
|
@@ -46,22 +47,28 @@ module Rigor
|
|
|
46
47
|
0
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
51
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
52
|
+
def option_parser(options = {})
|
|
53
|
+
OptionParser.new do |opt|
|
|
54
|
+
opt.banner = USAGE
|
|
55
|
+
Options.add_format(opt, options, "Output format (text | json). Default: text.")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
49
59
|
private
|
|
50
60
|
|
|
51
61
|
def parse_options
|
|
52
62
|
options = { format: "text" }
|
|
53
|
-
|
|
54
|
-
opt.banner = USAGE
|
|
55
|
-
opt.on("--format=FORMAT", %w[text json], "Output format (text | json). Default: text.") do |fmt|
|
|
56
|
-
options[:format] = fmt
|
|
57
|
-
end
|
|
58
|
-
end.parse!(@argv)
|
|
63
|
+
option_parser(options).parse!(@argv)
|
|
59
64
|
options
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
def render_index(format)
|
|
63
68
|
case format
|
|
64
|
-
when "json"
|
|
69
|
+
when "json"
|
|
70
|
+
@out.puts(JSON.pretty_generate(Analysis::RuleCatalog.all.map(&:to_h)))
|
|
71
|
+
@err.puts(JsonDocument::SHAPE_NOTICES.fetch(:explain_rules))
|
|
65
72
|
else render_index_text
|
|
66
73
|
end
|
|
67
74
|
end
|
|
@@ -87,6 +94,7 @@ module Rigor
|
|
|
87
94
|
def render_skip_reason(entry, format)
|
|
88
95
|
if format == "json"
|
|
89
96
|
@out.puts(JSON.pretty_generate(entry.to_h))
|
|
97
|
+
@err.puts(JsonDocument::SHAPE_NOTICES.fetch(:explain_skip_reasons))
|
|
90
98
|
return
|
|
91
99
|
end
|
|
92
100
|
|
|
@@ -106,7 +114,9 @@ module Rigor
|
|
|
106
114
|
|
|
107
115
|
def render_entries(entries, format)
|
|
108
116
|
case format
|
|
109
|
-
when "json"
|
|
117
|
+
when "json"
|
|
118
|
+
@out.puts(JSON.pretty_generate(entries.map(&:to_h)))
|
|
119
|
+
@err.puts(JsonDocument::SHAPE_NOTICES.fetch(:explain_rules))
|
|
110
120
|
else
|
|
111
121
|
entries.each_with_index do |entry, index|
|
|
112
122
|
@out.puts("") if index.positive?
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "json_document"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class CLI
|
|
@@ -23,7 +24,7 @@ module Rigor
|
|
|
23
24
|
private
|
|
24
25
|
|
|
25
26
|
def render_json(report)
|
|
26
|
-
@out.puts(JSON.pretty_generate(report.to_h))
|
|
27
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp(report.to_h)))
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def render_text(report)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
class CLI
|
|
5
|
+
# The version marker every `--format=json` document Rigor defines carries (ADR-126 WD2, ADR-50 WD1).
|
|
6
|
+
#
|
|
7
|
+
# `schema_version` is the first top-level key and counts per document (per `mode` where a flag selects a
|
|
8
|
+
# different document). Adding a key keeps the version; removing or renaming one is a major-only change at
|
|
9
|
+
# v1.0.0. `lens` (`schema`/`revision`), `baseline dump` (`version`) and `check`'s external formats keep
|
|
10
|
+
# their own markers and do not pass through here.
|
|
11
|
+
module JsonDocument
|
|
12
|
+
SCHEMA_VERSION = 1
|
|
13
|
+
|
|
14
|
+
# @param payload — the document's top-level object (string or symbol keys).
|
|
15
|
+
# @param mode — names the document when a flag selects among several; nil when the command has one.
|
|
16
|
+
# @return a new Hash whose first key is `schema_version` (then `mode`, when given).
|
|
17
|
+
def self.stamp(payload, mode: nil)
|
|
18
|
+
head = { "schema_version" => SCHEMA_VERSION }
|
|
19
|
+
head["mode"] = mode if mode
|
|
20
|
+
head.merge(payload)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The one-line stderr notice for `type-of`, `explain` and `trace`, whose top-level JSON shape changes in
|
|
24
|
+
# the next minor (ADR-126 WD4). stdout stays byte-identical until then. The MCP server appends the same
|
|
25
|
+
# text as its own content block, recognised by {NOTICE_PATTERN}.
|
|
26
|
+
def self.shape_notice(command, replacement)
|
|
27
|
+
"rigor: note — `rigor #{command} --format=json` will print #{replacement} from the next minor " \
|
|
28
|
+
"release (currently a different top-level shape); update JSON consumers now. See ADR-126."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
NOTICE_PATTERN = /^rigor: note — `rigor [a-z-]+ --format=json` will print .*$/
|
|
32
|
+
|
|
33
|
+
SHAPE_NOTICES = {
|
|
34
|
+
type_of: shape_notice("type-of", '{ "schema_version": 1, "results": [...] }').freeze,
|
|
35
|
+
trace: shape_notice("trace", '{ "schema_version": 1, "events": [...] }').freeze,
|
|
36
|
+
explain_rules: shape_notice("explain", '{ "schema_version": 1, "rules": [...] }').freeze,
|
|
37
|
+
explain_skip_reasons: shape_notice("explain", '{ "schema_version": 1, "skip_reasons": [...] }').freeze
|
|
38
|
+
}.freeze
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -15,18 +15,12 @@ module Rigor
|
|
|
15
15
|
# computes them (ADR-113; output contract docs/internal-spec/lens.md). Experimental until `lens/v1`.
|
|
16
16
|
class LensCommand < Command
|
|
17
17
|
USAGE = "Usage: rigor lens [--format=text|json] [--config=PATH] [--workers=N] FILE..."
|
|
18
|
-
FORMATS = %w[text json].freeze
|
|
19
18
|
|
|
20
19
|
def run
|
|
21
20
|
options = parse_options
|
|
22
21
|
return CLI::EXIT_USAGE if options.nil?
|
|
23
22
|
|
|
24
23
|
config = options.fetch(:config)
|
|
25
|
-
if config && !File.file?(config)
|
|
26
|
-
usage_error("no configuration file at #{config}")
|
|
27
|
-
return CLI::EXIT_USAGE
|
|
28
|
-
end
|
|
29
|
-
|
|
30
24
|
configuration = Configuration.load(config)
|
|
31
25
|
config_path = options.fetch(:config) || Configuration.discover
|
|
32
26
|
# The working directory: Rigor auto-detects sig/ and the lockfiles there (lens.md § Serialization).
|
|
@@ -40,22 +34,32 @@ module Rigor
|
|
|
40
34
|
outcome.targets.any? { |target| target.status == "ok" } ? 0 : 1
|
|
41
35
|
end
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def
|
|
46
|
-
options = { format: "text", config: nil, workers: nil }
|
|
37
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
38
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
39
|
+
def option_parser(options = {})
|
|
47
40
|
OptionParser.new do |opts|
|
|
48
41
|
opts.banner = USAGE
|
|
49
|
-
|
|
42
|
+
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
50
43
|
opts.on("--workers=N", Integer, "Worker processes for a cold run") { |value| options[:workers] = value }
|
|
51
44
|
Options.add_config(opts, options)
|
|
52
|
-
end
|
|
53
|
-
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def parse_options
|
|
51
|
+
options = { format: "text", config: nil, workers: nil }
|
|
52
|
+
option_parser(options).parse!(@argv)
|
|
54
53
|
return usage_error("no FILE given") if @argv.empty?
|
|
55
54
|
|
|
56
55
|
options
|
|
57
56
|
rescue OptionParser::ParseError => e
|
|
58
57
|
usage_error(e.message)
|
|
58
|
+
rescue Rigor::ConfigurationError => e
|
|
59
|
+
# The shared --config check (CLI::Options.config_path). Mapped here the way Rigor::CLI#run maps it, so a
|
|
60
|
+
# direct LensCommand run exits with the usage code too.
|
|
61
|
+
@err.puts("rigor: #{e.message}")
|
|
62
|
+
nil
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
def usage_error(message)
|
|
@@ -4,6 +4,7 @@ require_relative "command"
|
|
|
4
4
|
require_relative "options"
|
|
5
5
|
require_relative "../runtime/jit"
|
|
6
6
|
|
|
7
|
+
require "json"
|
|
7
8
|
require "optionparser"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
@@ -15,6 +16,179 @@ module Rigor
|
|
|
15
16
|
class LspCommand < Command
|
|
16
17
|
USAGE = "Usage: rigor lsp [options]"
|
|
17
18
|
|
|
19
|
+
# Records each JSON-RPC message one per line, prefixed with the direction (`<-` read from the client, `->`
|
|
20
|
+
# written to it) and a timestamp. Only the message framing is logged; the server's own debug output joins the
|
|
21
|
+
# same file through `$stderr` (see {#with_debug_output}).
|
|
22
|
+
#
|
|
23
|
+
# It never raises into the protocol path. A message the JSON encoder rejects (raw invalid UTF-8 in a string) is
|
|
24
|
+
# logged as a scrubbed rendering, so its line is still written. Any IO failure disables the log for the rest of
|
|
25
|
+
# the session: records are dropped, and debug output falls back to `fallback` (the real stderr). Nothing is
|
|
26
|
+
# `warn`ed into the failing log.
|
|
27
|
+
class WireLog
|
|
28
|
+
def initialize(io, fallback)
|
|
29
|
+
@io = io
|
|
30
|
+
@fallback = fallback
|
|
31
|
+
@broken = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def record(direction, message)
|
|
35
|
+
deliver("#{stamp} #{direction} #{render(message)}\n")
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Debug output written to `$stderr` while the session runs (`Kernel#warn` calls `write`). The parts are joined
|
|
40
|
+
# as binary, so an odd encoding in one part cannot raise here.
|
|
41
|
+
def write(*parts)
|
|
42
|
+
text = parts.map { |part| part.to_s.b }.join
|
|
43
|
+
@fallback.write(text) unless deliver(text)
|
|
44
|
+
text.bytesize
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def print(*parts)
|
|
48
|
+
write(*parts)
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def puts(*args)
|
|
53
|
+
lines = if args.empty?
|
|
54
|
+
["\n"]
|
|
55
|
+
else
|
|
56
|
+
args.flatten.map { |arg| arg.to_s.end_with?("\n") ? arg.to_s : "#{arg}\n" }
|
|
57
|
+
end
|
|
58
|
+
write(*lines)
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def printf(format_string, *)
|
|
63
|
+
write(format(format_string, *))
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def putc(char)
|
|
68
|
+
write(char.is_a?(Integer) ? char.chr : char.to_s[0])
|
|
69
|
+
char
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def syswrite(text)
|
|
73
|
+
write(text)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def <<(part)
|
|
77
|
+
write(part)
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def flush
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The exception that escaped the server loop, with its backtrace, so a crash is in the log, not only on stderr.
|
|
86
|
+
def crash(error)
|
|
87
|
+
lines = ["#{stamp} ! #{error.class}: #{error.message}\n"]
|
|
88
|
+
lines.concat((error.backtrace || []).map { |frame| " #{frame}\n" })
|
|
89
|
+
text = lines.join
|
|
90
|
+
@fallback.write(text) unless deliver(text)
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def close
|
|
95
|
+
@io.close unless @io.closed?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Every other IO method (`tty?`, `closed?`, `fileno`, `sync=`, `to_io` for `system(err: $stderr)`, ...) goes to
|
|
99
|
+
# the current sink: the file while the log works, the real stderr once it has failed.
|
|
100
|
+
def method_missing(name, ...)
|
|
101
|
+
return super unless sink.respond_to?(name)
|
|
102
|
+
|
|
103
|
+
sink.public_send(name, ...)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def respond_to_missing?(name, include_private = false)
|
|
107
|
+
sink.respond_to?(name) || super
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def sink
|
|
113
|
+
@broken ? @fallback : @io
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Writes `text` to the file. Returns false when the log is (or just became) unusable. Only a failure of the
|
|
117
|
+
# write itself disables the log.
|
|
118
|
+
def deliver(text)
|
|
119
|
+
return false if @broken
|
|
120
|
+
|
|
121
|
+
@io.write(text)
|
|
122
|
+
true
|
|
123
|
+
rescue StandardError
|
|
124
|
+
@broken = true
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def stamp
|
|
129
|
+
Time.now.strftime("%FT%T.%6N%:z")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The JSON line for `message`. One the encoder rejects (invalid UTF-8, nesting past its limit, a `to_json` that
|
|
133
|
+
# raises) is rendered from its scrubbed copy, or from its `inspect` when even that does not encode.
|
|
134
|
+
def render(message)
|
|
135
|
+
JSON.generate(message)
|
|
136
|
+
rescue StandardError
|
|
137
|
+
begin
|
|
138
|
+
JSON.generate(scrub(message))
|
|
139
|
+
rescue StandardError
|
|
140
|
+
JSON.generate(message.inspect)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def scrub(value)
|
|
145
|
+
case value
|
|
146
|
+
when Hash then value.to_h { |key, val| [scrub(key), scrub(val)] }
|
|
147
|
+
when Array then value.map { |item| scrub(item) }
|
|
148
|
+
when Symbol then scrub(value.to_s).to_sym
|
|
149
|
+
when String then value.dup.force_encoding(Encoding::UTF_8).scrub
|
|
150
|
+
else value
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Wraps the framed reader so every request or notification it yields is logged before the Loop dispatches it.
|
|
156
|
+
class WireLogReader
|
|
157
|
+
def initialize(inner, log)
|
|
158
|
+
@inner = inner
|
|
159
|
+
@log = log
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def read(&block)
|
|
163
|
+
@inner.read do |message|
|
|
164
|
+
@log.record("<-", message)
|
|
165
|
+
block.call(message)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def close
|
|
170
|
+
@inner.close
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Wraps the framed writer so every outgoing message is logged as it goes on the wire. It sits inside
|
|
175
|
+
# `SynchronizedWriter`, so the log line and the frame it describes are written under the same lock.
|
|
176
|
+
class WireLogWriter
|
|
177
|
+
def initialize(inner, log)
|
|
178
|
+
@inner = inner
|
|
179
|
+
@log = log
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def write(message)
|
|
183
|
+
@log.record("->", message.merge(jsonrpc: "2.0"))
|
|
184
|
+
@inner.write(message)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def close
|
|
188
|
+
@inner.close
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
18
192
|
# @return CLI exit status.
|
|
19
193
|
def run
|
|
20
194
|
options = parse_options
|
|
@@ -26,6 +200,9 @@ module Rigor
|
|
|
26
200
|
return CLI::EXIT_USAGE
|
|
27
201
|
end
|
|
28
202
|
|
|
203
|
+
log = open_wire_log(options.fetch(:log))
|
|
204
|
+
return CLI::EXIT_USAGE if log == :usage_error
|
|
205
|
+
|
|
29
206
|
# A long-lived server always outlasts the JIT compile cost, so enable
|
|
30
207
|
# YJIT at boot rather than on the analysis deadline (Runtime::Jit).
|
|
31
208
|
Runtime::Jit.enable_now
|
|
@@ -34,23 +211,83 @@ module Rigor
|
|
|
34
211
|
require_relative "../configuration"
|
|
35
212
|
require "language_server-protocol"
|
|
36
213
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
214
|
+
begin
|
|
215
|
+
serve(config_path: options.fetch(:config), log: log)
|
|
216
|
+
ensure
|
|
217
|
+
log&.close
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
222
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
223
|
+
def option_parser(options = {})
|
|
224
|
+
OptionParser.new do |opts|
|
|
225
|
+
opts.banner = USAGE
|
|
226
|
+
opts.on("--transport=NAME", "Transport (default: stdio; only stdio supported in v1)") do |value|
|
|
227
|
+
options[:transport] = value
|
|
228
|
+
end
|
|
229
|
+
opts.on("--log=PATH",
|
|
230
|
+
"Append the LSP wire log and server debug output to PATH " \
|
|
231
|
+
"(default: no wire log; debug output goes to stderr)") do |value|
|
|
232
|
+
options[:log] = value
|
|
233
|
+
end
|
|
234
|
+
Options.add_config(opts, options)
|
|
235
|
+
end
|
|
47
236
|
end
|
|
48
237
|
|
|
49
238
|
private
|
|
50
239
|
|
|
240
|
+
# STDIN is read frame-by-frame via the gem's `Io::Reader`; STDOUT is wrapped in `SynchronizedWriter` so
|
|
241
|
+
# concurrent writes from the main dispatch thread + the Debouncer's async threads don't interleave frames. The
|
|
242
|
+
# Loop runs until either STDIN hits EOF or `server.exited?`; the process then exits with the server's recorded
|
|
243
|
+
# code (0 after a clean shutdown+exit, 1 otherwise). With a wire log, both directions are logged inside the lock.
|
|
244
|
+
def serve(config_path:, log:)
|
|
245
|
+
reader = ::LanguageServer::Protocol::Transport::Io::Reader.new($stdin)
|
|
246
|
+
frame_writer = ::LanguageServer::Protocol::Transport::Io::Writer.new($stdout)
|
|
247
|
+
if log
|
|
248
|
+
reader = WireLogReader.new(reader, log)
|
|
249
|
+
frame_writer = WireLogWriter.new(frame_writer, log)
|
|
250
|
+
end
|
|
251
|
+
writer = LanguageServer::SynchronizedWriter.new(frame_writer)
|
|
252
|
+
server, loop_runner = build_server(writer: writer, reader: reader, config_path: config_path)
|
|
253
|
+
with_debug_output(log) { loop_runner.run }
|
|
254
|
+
server.exit_code || 0
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Opens PATH for appending with synchronous writes, so a crash loses no logged line. Returns nil without a
|
|
258
|
+
# PATH, the open file, or `:usage_error` after reporting why it could not be opened.
|
|
259
|
+
def open_wire_log(path)
|
|
260
|
+
return nil if path.nil?
|
|
261
|
+
|
|
262
|
+
io = File.new(path, "a")
|
|
263
|
+
io.sync = true
|
|
264
|
+
WireLog.new(io, $stderr)
|
|
265
|
+
rescue SystemCallError => e
|
|
266
|
+
@err.puts("rigor lsp: cannot open --log file #{path.inspect}: #{e.message}")
|
|
267
|
+
:usage_error
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Routes the server's debug output (`warn` and the Ruby runtime's warnings, both written to `$stderr`) into the
|
|
271
|
+
# wire log for the length of the session. An exception that escapes the loop is written to the log before
|
|
272
|
+
# `$stderr` is restored and the exception is re-raised. Without a log, `$stderr` is left alone.
|
|
273
|
+
def with_debug_output(log)
|
|
274
|
+
return yield if log.nil?
|
|
275
|
+
|
|
276
|
+
saved = $stderr
|
|
277
|
+
begin
|
|
278
|
+
$stderr = log
|
|
279
|
+
yield
|
|
280
|
+
rescue StandardError, ScriptError, SystemStackError => e
|
|
281
|
+
log.crash(e)
|
|
282
|
+
raise
|
|
283
|
+
ensure
|
|
284
|
+
$stderr = saved
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
51
288
|
# Builds the full collaborator graph from a fresh `Configuration` + `ProjectContext`. Returns `[server, loop]` so
|
|
52
289
|
# the caller drives the loop and reads `server.exit_code` for the process exit status.
|
|
53
|
-
def build_server(writer:, config_path:) # rubocop:disable Metrics/MethodLength
|
|
290
|
+
def build_server(writer:, reader:, config_path:) # rubocop:disable Metrics/MethodLength
|
|
54
291
|
configuration = Configuration.load(config_path)
|
|
55
292
|
# ProjectContext caches Environment + Cache::Store across requests so hover / publish hit the warm path.
|
|
56
293
|
# Invalidated by `workspace/didChangeWatchedFiles` and `workspace/didChangeConfiguration`; every save round
|
|
@@ -89,7 +326,7 @@ module Rigor
|
|
|
89
326
|
project_context: project_context
|
|
90
327
|
)
|
|
91
328
|
loop_runner = LanguageServer::Loop.new(
|
|
92
|
-
reader:
|
|
329
|
+
reader: reader,
|
|
93
330
|
writer: writer,
|
|
94
331
|
server: server
|
|
95
332
|
)
|
|
@@ -99,17 +336,7 @@ module Rigor
|
|
|
99
336
|
def parse_options
|
|
100
337
|
options = { transport: "stdio", log: nil, config: nil }
|
|
101
338
|
|
|
102
|
-
|
|
103
|
-
opts.banner = USAGE
|
|
104
|
-
opts.on("--transport=NAME", "Transport (default: stdio; only stdio supported in v1)") do |value|
|
|
105
|
-
options[:transport] = value
|
|
106
|
-
end
|
|
107
|
-
opts.on("--log=PATH", "Write LSP wire log + server debug to PATH (default: stderr)") do |value|
|
|
108
|
-
options[:log] = value
|
|
109
|
-
end
|
|
110
|
-
Options.add_config(opts, options)
|
|
111
|
-
end
|
|
112
|
-
parser.parse!(@argv)
|
|
339
|
+
option_parser(options).parse!(@argv)
|
|
113
340
|
options
|
|
114
341
|
rescue OptionParser::ParseError => e
|
|
115
342
|
@err.puts(e.message)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "command"
|
|
4
|
+
require_relative "options"
|
|
4
5
|
require_relative "../runtime/jit"
|
|
5
6
|
|
|
6
7
|
require "optionparser"
|
|
@@ -41,12 +42,10 @@ module Rigor
|
|
|
41
42
|
0
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
parser = OptionParser.new do |opts|
|
|
45
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
46
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
47
|
+
def option_parser(options = {})
|
|
48
|
+
OptionParser.new do |opts|
|
|
50
49
|
opts.banner = USAGE
|
|
51
50
|
opts.on("--transport=NAME",
|
|
52
51
|
"Transport (default: stdio; only stdio is supported in v1)") do |value|
|
|
@@ -54,10 +53,17 @@ module Rigor
|
|
|
54
53
|
end
|
|
55
54
|
opts.on("--config=PATH",
|
|
56
55
|
"Session-level default config path (individual tool calls may override)") do |value|
|
|
57
|
-
options[:config] = value
|
|
56
|
+
options[:config] = Options.config_path(value)
|
|
58
57
|
end
|
|
59
58
|
end
|
|
60
|
-
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def parse_options
|
|
64
|
+
options = { transport: "stdio", config: nil }
|
|
65
|
+
|
|
66
|
+
option_parser(options).parse!(@argv)
|
|
61
67
|
options
|
|
62
68
|
rescue OptionParser::ParseError => e
|
|
63
69
|
@err.puts(e.message)
|
|
@@ -40,7 +40,7 @@ module Rigor
|
|
|
40
40
|
# @return one entry per path.
|
|
41
41
|
def run(paths:, scanner:, environment:, configuration:, workers:)
|
|
42
42
|
# Force the full RBS load on the parent so children copy-on-write inherit a warm environment rather
|
|
43
|
-
# than each rebuilding it after the fork.
|
|
43
|
+
# than each rebuilding it after the fork. Skipped when the run is sequential.
|
|
44
44
|
environment.rbs_loader&.prewarm if Inference::ForkMap.parallel?([workers, paths.size].min)
|
|
45
45
|
|
|
46
46
|
payloads = Inference::ForkMap.call(items: paths, workers: workers) do |slice|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "json_document"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class CLI
|
|
@@ -22,7 +23,7 @@ module Rigor
|
|
|
22
23
|
private
|
|
23
24
|
|
|
24
25
|
def render_json(report)
|
|
25
|
-
@out.puts(JSON.pretty_generate(report.to_h))
|
|
26
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp(report.to_h)))
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def render_text(report)
|
|
@@ -46,7 +47,7 @@ module Rigor
|
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
# Issue #686 — the count that makes the command exit non-zero, on stdout beside the ratio it qualifies
|
|
49
|
-
# (the stderr twin in
|
|
50
|
+
# (the stderr twin in {MeasurementIntegrityWarning} carries the explanation).
|
|
50
51
|
def render_unmeasured_files(report)
|
|
51
52
|
count = report.unmeasured_files
|
|
52
53
|
return if count.zero?
|