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
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "optionparser"
|
|
5
5
|
|
|
6
6
|
require_relative "renderable"
|
|
7
|
+
require_relative "json_document"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
9
10
|
class CLI
|
|
@@ -100,7 +101,7 @@ module Rigor
|
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
def render_json(report)
|
|
103
|
-
@out.puts(JSON.pretty_generate(json_payload(report)))
|
|
104
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp(json_payload(report))))
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
def json_payload(report)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optionparser"
|
|
4
|
+
require_relative "json_document"
|
|
4
5
|
require "json"
|
|
5
6
|
|
|
6
7
|
require_relative "../configuration"
|
|
@@ -67,6 +68,23 @@ module Rigor
|
|
|
67
68
|
0
|
|
68
69
|
end
|
|
69
70
|
|
|
71
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
72
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
73
|
+
def option_parser(options = {})
|
|
74
|
+
OptionParser.new do |opts|
|
|
75
|
+
opts.banner = USAGE
|
|
76
|
+
Options.add_config(opts, options)
|
|
77
|
+
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
78
|
+
opts.on("--entry-point=GLOB", "Treat declarations in files matching GLOB as roots (repeatable)") do |v|
|
|
79
|
+
options[:entry_points] << v
|
|
80
|
+
end
|
|
81
|
+
opts.on("--limit=N", Integer, "Print at most N candidates (default: all)") { |v| options[:limit] = v }
|
|
82
|
+
# WD5 — refuse rather than silently absorb. A reachability answer is sound only over a full run, and a
|
|
83
|
+
# user who asked for the fast path must not silently get the slow one.
|
|
84
|
+
opts.on("--incremental", "(unsupported)") { options[:incremental] = true }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
70
88
|
private
|
|
71
89
|
|
|
72
90
|
# A scan's role and the spelling it records depend on the root and the working directory (#1760): a role
|
|
@@ -120,20 +138,8 @@ module Rigor
|
|
|
120
138
|
|
|
121
139
|
def parse_options
|
|
122
140
|
options = { config: nil, format: "text", entry_points: [], limit: nil }
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Options.add_config(opts, options)
|
|
126
|
-
opts.on("--format=FORMAT", "Output format: text (default) or json") { |v| options[:format] = v }
|
|
127
|
-
opts.on("--entry-point=GLOB", "Treat declarations in files matching GLOB as roots (repeatable)") do |v|
|
|
128
|
-
options[:entry_points] << v
|
|
129
|
-
end
|
|
130
|
-
opts.on("--limit=N", Integer, "Print at most N candidates (default: all)") { |v| options[:limit] = v }
|
|
131
|
-
# WD5 — refuse rather than silently absorb. A reachability answer is sound only over a full run, and a
|
|
132
|
-
# user who asked for the fast path must not silently get the slow one.
|
|
133
|
-
opts.on("--incremental", "(unsupported)") { options[:incremental] = true }
|
|
134
|
-
end
|
|
135
|
-
parser.parse!(@argv)
|
|
136
|
-
return usage_error(parser) if options[:incremental]
|
|
141
|
+
option_parser(options).parse!(@argv)
|
|
142
|
+
return usage_error if options[:incremental]
|
|
137
143
|
|
|
138
144
|
options
|
|
139
145
|
rescue OptionParser::ParseError => e
|
|
@@ -141,7 +147,7 @@ module Rigor
|
|
|
141
147
|
:usage_error
|
|
142
148
|
end
|
|
143
149
|
|
|
144
|
-
def usage_error
|
|
150
|
+
def usage_error
|
|
145
151
|
@err.puts("rigor unused does not support --incremental: reachability is only sound over a whole-project " \
|
|
146
152
|
"run, so an incremental pass would report constants as unused merely because the files that " \
|
|
147
153
|
"reference them were served from cache. Re-run without --incremental.")
|
|
@@ -379,6 +385,9 @@ module Rigor
|
|
|
379
385
|
required_features: ProjectEnvironment.required_features(configuration),
|
|
380
386
|
**ProjectEnvironment.dependency_discovery_options(configuration))
|
|
381
387
|
->(fqn) { !env.singleton_for_name(fqn).nil? }
|
|
388
|
+
rescue Environment::CoreRootError
|
|
389
|
+
# Issue #2517 — answering "unknown" for every name would report against no core at all.
|
|
390
|
+
raise
|
|
382
391
|
rescue StandardError
|
|
383
392
|
->(_fqn) { false }
|
|
384
393
|
end
|
|
@@ -413,7 +422,7 @@ module Rigor
|
|
|
413
422
|
end
|
|
414
423
|
|
|
415
424
|
def json(report, options, supply)
|
|
416
|
-
|
|
425
|
+
payload = {
|
|
417
426
|
declared: report.declared, reachable: report.reachable, roots: report.roots, edges: report.edges,
|
|
418
427
|
namespaces: report.namespaces,
|
|
419
428
|
plugin_roots: supply.supplied, plugin_roots_unmatched: supply.unmatched,
|
|
@@ -421,7 +430,8 @@ module Rigor
|
|
|
421
430
|
undecidable: limited(report.undecidable, options).map do |u|
|
|
422
431
|
{ name: u.fqn, path: u.path, line: u.line, reason: u.reason }
|
|
423
432
|
end
|
|
424
|
-
|
|
433
|
+
}
|
|
434
|
+
JSON.pretty_generate(JsonDocument.stamp(payload))
|
|
425
435
|
end
|
|
426
436
|
|
|
427
437
|
def rows_json(rows, options)
|
|
@@ -15,6 +15,11 @@ module Rigor
|
|
|
15
15
|
|
|
16
16
|
# @return CLI exit status.
|
|
17
17
|
def run
|
|
18
|
+
if @argv.intersect?(%w[-h --help])
|
|
19
|
+
@out.puts(USAGE)
|
|
20
|
+
return 0
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
@out.puts("rigor upgrade: No migration target available yet (ADR-50 WD7, queued).")
|
|
19
24
|
@out.puts("Current version: #{Rigor::VERSION}")
|
|
20
25
|
0
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -10,6 +10,9 @@ require_relative "version"
|
|
|
10
10
|
require_relative "analysis/diagnostic"
|
|
11
11
|
require_relative "analysis/result"
|
|
12
12
|
require_relative "cli/options"
|
|
13
|
+
require "did_you_mean"
|
|
14
|
+
|
|
15
|
+
require_relative "cli/catalogue"
|
|
13
16
|
require_relative "cli/diagnostic_formats"
|
|
14
17
|
require_relative "ci_detector"
|
|
15
18
|
require_relative "analysis/check_rules/rule_ids"
|
|
@@ -46,6 +49,17 @@ module Rigor
|
|
|
46
49
|
# 2026-07-17 and nothing noticed. See `docs/internal-spec/config.md`.
|
|
47
50
|
CONFIG_SCHEMA_URL = "https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json"
|
|
48
51
|
|
|
52
|
+
# The comment `rigor init` writes under `plugins: []` when Gemfile.lock names a gem a bundled plugin models. The
|
|
53
|
+
# plugins are suggested, not enabled: ADR-96 Criterion 2 keeps a lockfile's presence from opting a project into a
|
|
54
|
+
# plugin's code, so the user uncomments the ones they want (#2536).
|
|
55
|
+
DETECTED_PLUGINS_HEADER = [
|
|
56
|
+
"# Detected from Gemfile.lock (suggested, not enabled). To load them, delete the `[]` above, uncomment the",
|
|
57
|
+
"# entries you want, then run `rigor plugins --strict`."
|
|
58
|
+
].freeze
|
|
59
|
+
|
|
60
|
+
# The file whose presence marks a Rails application for `rigor init`'s `paths:` suggestion (#2560).
|
|
61
|
+
RAILS_APPLICATION_FILE = "config/application.rb"
|
|
62
|
+
|
|
49
63
|
HANDLERS = {
|
|
50
64
|
"check" => :run_check,
|
|
51
65
|
"init" => :run_init,
|
|
@@ -79,6 +93,26 @@ module Rigor
|
|
|
79
93
|
new(argv.dup, out: out, err: err).run
|
|
80
94
|
end
|
|
81
95
|
|
|
96
|
+
# The parser `rigor init` parses with, built without parsing anything (the catalogue reads it, ADR-126 WD3).
|
|
97
|
+
def self.init_option_parser(options = {})
|
|
98
|
+
OptionParser.new do |opts|
|
|
99
|
+
opts.banner = "Usage: rigor init [options]"
|
|
100
|
+
opts.on("--force", "Overwrite an existing configuration file") { options[:force] = true }
|
|
101
|
+
opts.on("--path=PATH", "Configuration file path") { |value| options[:path] = value }
|
|
102
|
+
opts.on("--no-plugins", "Write no commented plugin suggestions from Gemfile.lock") do
|
|
103
|
+
options[:plugins] = false
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The parser `rigor help` parses with, built without parsing anything (the catalogue reads it, ADR-126 WD3).
|
|
109
|
+
def self.help_option_parser(options = {})
|
|
110
|
+
OptionParser.new do |opts|
|
|
111
|
+
opts.banner = "Usage: rigor help [--format=text|json] [<command>]"
|
|
112
|
+
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
82
116
|
def initialize(argv = ARGV.dup, out: $stdout, err: $stderr)
|
|
83
117
|
@argv = argv
|
|
84
118
|
@out = out
|
|
@@ -89,12 +123,14 @@ module Rigor
|
|
|
89
123
|
command = @argv.shift
|
|
90
124
|
|
|
91
125
|
case command
|
|
92
|
-
when nil, "
|
|
126
|
+
when nil, "-h", "--help"
|
|
93
127
|
@out.puts(help)
|
|
94
128
|
0
|
|
129
|
+
when "help"
|
|
130
|
+
run_help
|
|
95
131
|
when "version", "-v", "--version"
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
run_version
|
|
133
|
+
|
|
98
134
|
else
|
|
99
135
|
dispatch(command)
|
|
100
136
|
end
|
|
@@ -139,6 +175,8 @@ module Rigor
|
|
|
139
175
|
return arm_jit_deadline { send(handler) } if handler
|
|
140
176
|
|
|
141
177
|
@err.puts("Unknown command: #{command}")
|
|
178
|
+
nearest = ::DidYouMean::SpellChecker.new(dictionary: Catalogue.names).correct(command).first
|
|
179
|
+
@err.puts("Did you mean `rigor #{nearest}`?") if nearest
|
|
142
180
|
@err.puts(help)
|
|
143
181
|
EXIT_USAGE
|
|
144
182
|
end
|
|
@@ -156,6 +194,16 @@ module Rigor
|
|
|
156
194
|
yield
|
|
157
195
|
end
|
|
158
196
|
|
|
197
|
+
# `rigor version` and `-v`/`--version` print the version; `rigor version --help` prints the usage instead.
|
|
198
|
+
def run_version
|
|
199
|
+
if @argv.intersect?(%w[-h --help])
|
|
200
|
+
@out.puts("Usage: rigor version")
|
|
201
|
+
else
|
|
202
|
+
@out.puts("rigor #{Rigor::VERSION}")
|
|
203
|
+
end
|
|
204
|
+
0
|
|
205
|
+
end
|
|
206
|
+
|
|
159
207
|
def run_check
|
|
160
208
|
require_relative "cli/check_command"
|
|
161
209
|
|
|
@@ -168,15 +216,11 @@ module Rigor
|
|
|
168
216
|
# present; no implicit merge).
|
|
169
217
|
options = {
|
|
170
218
|
force: false,
|
|
171
|
-
path: ".rigor.dist.yml"
|
|
219
|
+
path: ".rigor.dist.yml",
|
|
220
|
+
plugins: true
|
|
172
221
|
}
|
|
173
222
|
|
|
174
|
-
|
|
175
|
-
opts.banner = "Usage: rigor init [options]"
|
|
176
|
-
opts.on("--force", "Overwrite an existing configuration file") { options[:force] = true }
|
|
177
|
-
opts.on("--path=PATH", "Configuration file path") { |value| options[:path] = value }
|
|
178
|
-
end
|
|
179
|
-
parser.parse!(@argv)
|
|
223
|
+
self.class.init_option_parser(options).parse!(@argv)
|
|
180
224
|
|
|
181
225
|
path = options.fetch(:path)
|
|
182
226
|
if File.exist?(path) && !options.fetch(:force)
|
|
@@ -184,24 +228,57 @@ module Rigor
|
|
|
184
228
|
return 1
|
|
185
229
|
end
|
|
186
230
|
|
|
187
|
-
|
|
231
|
+
plugins = options.fetch(:plugins) ? detected_plugins : []
|
|
232
|
+
paths_suggestion = detected_paths
|
|
233
|
+
begin
|
|
234
|
+
File.write(path, init_template(path, plugins: plugins, paths_suggestion: paths_suggestion))
|
|
235
|
+
rescue SystemCallError => e
|
|
236
|
+
# A destination whose directory is missing or not writable is a bad path argument, the same usage error
|
|
237
|
+
# `--config=PATH` gives for a file that does not exist (exit 64, docs/manual/02-cli-reference.md). The bare
|
|
238
|
+
# Errno::ENOENT backtrace read as an internal crash.
|
|
239
|
+
reason = e.message.sub(/ @ .*\z/m, "")
|
|
240
|
+
@err.puts("rigor init: cannot write #{path}: #{reason}")
|
|
241
|
+
return EXIT_USAGE
|
|
242
|
+
end
|
|
188
243
|
@out.puts("Created #{path}")
|
|
189
|
-
print_init_next_steps(path)
|
|
244
|
+
print_init_next_steps(path, plugins, paths_suggestion)
|
|
190
245
|
0
|
|
191
246
|
end
|
|
192
247
|
|
|
193
|
-
#
|
|
194
|
-
#
|
|
248
|
+
# The bundled plugins whose target gems the project's `Gemfile.lock` declares: the read `rigor doctor` makes,
|
|
249
|
+
# through {PluginGapAdvisory}, so the two cannot disagree about what the project depends on. Empty without a
|
|
250
|
+
# lockfile, which leaves the template as it was before detection existed. The advisory is required lazily:
|
|
251
|
+
# it loads every bundled plugin's entry file, a cost a plain `rigor init` in a directory with no lockfile never
|
|
252
|
+
# pays.
|
|
253
|
+
def detected_plugins
|
|
254
|
+
require_relative "plugin_gap_advisory"
|
|
255
|
+
|
|
256
|
+
PluginGapAdvisory.gaps(project_root: Dir.pwd, plugins: []).map(&:plugin_gem)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# `rigor init`'s template lists the plugins Gemfile.lock detected, or ships empty `plugins:` so a fresh init has
|
|
260
|
+
# nothing to validate — but the moment the user adds any plugin entry, the activation-failure surfaces enumerated
|
|
261
|
+
# in `rigor plugins`'s docstring become real.
|
|
195
262
|
# Point them at the verification command + the canonical readiness flow so silent failures (the cwd / Gemfile /
|
|
196
263
|
# signature_paths mismatches that surfaced during the Mastodon trial) get caught the first time the user wires a
|
|
197
264
|
# plugin, not the first time `rigor check` reports false positives that should have been covered.
|
|
198
|
-
def print_init_next_steps(path)
|
|
265
|
+
def print_init_next_steps(path, plugins = [], paths_suggestion = nil)
|
|
199
266
|
@out.puts ""
|
|
200
267
|
@out.puts "Next steps:"
|
|
201
|
-
|
|
268
|
+
if plugins.empty?
|
|
269
|
+
@out.puts " 1. Edit #{path} — add the `plugins:` your project needs."
|
|
270
|
+
else
|
|
271
|
+
@out.puts " 1. Edit #{path} — to load a detected plugin, delete the `[]` on the `plugins:` line and"
|
|
272
|
+
@out.puts " uncomment the entries you want:"
|
|
273
|
+
plugins.each { |gem| @out.puts " #{gem}" }
|
|
274
|
+
end
|
|
202
275
|
@out.puts " 2. Run `rigor plugins` to verify every configured plugin loads."
|
|
203
276
|
@out.puts " (`--strict` exits 1 on failure; ideal CI gate.)"
|
|
204
277
|
@out.puts " 3. Run `rigor check` to analyse your code."
|
|
278
|
+
return if paths_suggestion.nil?
|
|
279
|
+
|
|
280
|
+
@out.puts " `rigor check` analyses only `lib` until you edit `paths:` in #{path}. " \
|
|
281
|
+
"The suggestion is commented beneath it."
|
|
205
282
|
end
|
|
206
283
|
|
|
207
284
|
# Word-wraps `CheckRules::ALL_RULES` into a comma-separated, `#`-commented block for `init_template`, so the
|
|
@@ -230,8 +307,9 @@ module Rigor
|
|
|
230
307
|
|
|
231
308
|
# Renders the starter `.rigor.yml` body. The template serialises `Configuration::DEFAULTS` (so the on-disk file
|
|
232
309
|
# round-trips through `Configuration.load`) and prepends a short header that points the user at the keys they are
|
|
233
|
-
# most likely to want to edit.
|
|
234
|
-
|
|
310
|
+
# most likely to want to edit. `plugins:` stays the empty list the defaults ship; detected gems appear beneath it as
|
|
311
|
+
# comments, so a project with no lockfile gets the template it always did.
|
|
312
|
+
def init_template(path = ".rigor.dist.yml", plugins: [], paths_suggestion: nil)
|
|
235
313
|
<<~YAML
|
|
236
314
|
# yaml-language-server: $schema=#{CONFIG_SCHEMA_URL}
|
|
237
315
|
# Rigor configuration. The full key reference is
|
|
@@ -287,10 +365,49 @@ module Rigor
|
|
|
287
365
|
# `target_ruby` is left commented: the default only picks the syntax Rigor parses. Set it to the lowest
|
|
288
366
|
# Ruby your project runs on, and Rigor also reports what that Ruby deprecates.
|
|
289
367
|
# target_ruby: #{Configuration::DEFAULTS.fetch('target_ruby').dump}
|
|
290
|
-
#{
|
|
368
|
+
#{settings_yaml(path, plugins, paths_suggestion)}
|
|
291
369
|
YAML
|
|
292
370
|
end
|
|
293
371
|
|
|
372
|
+
# The serialised settings. `plugins: []` stays valid YAML and schema-clean; when `Gemfile.lock` named a modelled
|
|
373
|
+
# gem, the suggestion block sits under it as comments, so nothing loads until the user uncomments a line. The
|
|
374
|
+
# `paths:` suggestion sits under the active `paths:` list the same way, and the active list stays `lib`.
|
|
375
|
+
def settings_yaml(path, plugins, paths_suggestion = nil)
|
|
376
|
+
yaml = YAML.dump(init_settings(path)).sub(/\A---\n/, "")
|
|
377
|
+
yaml = yaml.sub(/^plugins: \[\]\n/) { "plugins: []\n#{detected_plugins_block(plugins)}" } unless plugins.empty?
|
|
378
|
+
return yaml if paths_suggestion.nil?
|
|
379
|
+
|
|
380
|
+
yaml.sub(/^paths:\n(?:- .*\n)+/) { |active| active + paths_suggestion_block(paths_suggestion, path) }
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# The commented `paths:` suggestion: one line naming what was detected and why, then the value to uncomment, written
|
|
384
|
+
# relative to the config file's directory like the active list (#1399).
|
|
385
|
+
def paths_suggestion_block(suggestion, path)
|
|
386
|
+
named = suggestion.fetch(:dirs).join(", ")
|
|
387
|
+
written = roots_relative_to(suggestion.fetch(:dirs), config_dir_of(path)).join(", ")
|
|
388
|
+
pronoun = suggestion.fetch(:dirs).one? ? "it" : "them"
|
|
389
|
+
"# #{suggestion.fetch(:reason)}: #{named}. To scan #{pronoun}, delete the `paths:` list above and " \
|
|
390
|
+
"uncomment the line below:\n# paths: [#{written}]\n"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# The source directories `rigor init` suggests for `paths:`, read from the project root (the cwd, like the plugin
|
|
394
|
+
# detection). A Rails application (config/application.rb) has app/ and lib/, each listed only if it exists. Any
|
|
395
|
+
# other layout gets no suggestion: the template's `lib` already covers it, and no other directory is guessed
|
|
396
|
+
# (#2560). Nil when there is nothing to add to today's `paths: [lib]`.
|
|
397
|
+
def detected_paths
|
|
398
|
+
return nil unless File.file?(RAILS_APPLICATION_FILE)
|
|
399
|
+
|
|
400
|
+
dirs = %w[app lib].select { |dir| File.directory?(dir) }
|
|
401
|
+
return nil if dirs.empty? || dirs == ["lib"]
|
|
402
|
+
|
|
403
|
+
{ dirs: dirs, reason: "Detected a Rails app (#{RAILS_APPLICATION_FILE})" }
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def detected_plugins_block(plugins)
|
|
407
|
+
lines = DETECTED_PLUGINS_HEADER + plugins.map { |gem| "# - #{gem}" }
|
|
408
|
+
lines.map { |line| "#{line}\n" }.join
|
|
409
|
+
end
|
|
410
|
+
|
|
294
411
|
# The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
|
|
295
412
|
# the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
|
|
296
413
|
# relative to the config file's directory, which is what {Configuration.load} resolves them against. `paths:`
|
|
@@ -320,7 +437,7 @@ module Rigor
|
|
|
320
437
|
|
|
321
438
|
# Each root is resolved against the physical cwd (where `rigor` reads it from), then written relative to the config
|
|
322
439
|
# dir. With the config at the project root, both sides are the same physical directory, so the roots come out as
|
|
323
|
-
# they are spelled from it (`lib`, `spec`)
|
|
440
|
+
# they are spelled from it (`lib`, `spec`).
|
|
324
441
|
def roots_relative_to(roots, config_dir)
|
|
325
442
|
roots.map { |root| Pathname(File.absolute_path(root)).relative_path_from(config_dir).to_s }
|
|
326
443
|
end
|
|
@@ -445,11 +562,18 @@ module Rigor
|
|
|
445
562
|
# Surfaced because a bare `rigor describe` is the intuitive guess (the onboarding field trial saw it tried and met
|
|
446
563
|
# "Unknown command").
|
|
447
564
|
def run_describe
|
|
565
|
+
return print_describe_usage if @argv.intersect?(%w[-h --help])
|
|
566
|
+
|
|
448
567
|
require_relative "cli/skill_command"
|
|
449
568
|
|
|
450
569
|
CLI::SkillCommand.new(argv: ["describe", *@argv], out: @out, err: @err).run
|
|
451
570
|
end
|
|
452
571
|
|
|
572
|
+
def print_describe_usage
|
|
573
|
+
@out.puts("Usage: rigor describe [--deep]")
|
|
574
|
+
0
|
|
575
|
+
end
|
|
576
|
+
|
|
453
577
|
def run_docs
|
|
454
578
|
require_relative "cli/docs_command"
|
|
455
579
|
|
|
@@ -480,41 +604,72 @@ module Rigor
|
|
|
480
604
|
CLI::UpgradeCommand.new(argv: @argv, out: @out, err: @err).run
|
|
481
605
|
end
|
|
482
606
|
|
|
607
|
+
# The text help is rendered from {Catalogue}, so the command list cannot drift from the dispatch table.
|
|
483
608
|
def help
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
609
|
+
Catalogue.help_text
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# `rigor help [--format=text|json] [<command>]`. `help <command>` behaves as `rigor <command> --help`;
|
|
613
|
+
# `--format=json` prints the whole {Catalogue} document.
|
|
614
|
+
def run_help
|
|
615
|
+
return print_command_list if @argv.intersect?(%w[-h --help])
|
|
616
|
+
|
|
617
|
+
options = parse_help_options
|
|
618
|
+
topics = @argv
|
|
619
|
+
return run_help_json(topics) if options[:format] == "json"
|
|
620
|
+
|
|
621
|
+
topic = topics.first
|
|
622
|
+
return run_help_topic(topic, topics.drop(1)) if topic
|
|
623
|
+
|
|
624
|
+
print_command_list
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def print_command_list
|
|
628
|
+
@out.puts(help)
|
|
629
|
+
0
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def parse_help_options
|
|
633
|
+
options = Hash.new("text")
|
|
634
|
+
# `order!` stops at the first positional, so the flags of the command being asked about are never parsed here.
|
|
635
|
+
self.class.help_option_parser(options).order!(@argv)
|
|
636
|
+
options
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def run_help_json(topics)
|
|
640
|
+
if topics.any?
|
|
641
|
+
@err.puts("rigor help --format=json takes no command; it prints every command")
|
|
642
|
+
return EXIT_USAGE
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
@out.puts(JSON.pretty_generate(Catalogue.document))
|
|
646
|
+
0
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def run_help_topic(topic, rest)
|
|
650
|
+
# Flags after the command name (`help type-of --workers=2`) are not parsed; a second word is not a subcommand's
|
|
651
|
+
# help, so it is refused rather than answered with the parent's.
|
|
652
|
+
extra = rest.reject { |word| word.start_with?("-") }
|
|
653
|
+
unless extra.empty?
|
|
654
|
+
@err.puts("rigor help takes one command, got: #{[topic, *extra].join(' ')}")
|
|
655
|
+
return EXIT_USAGE
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
case topic
|
|
659
|
+
when "help"
|
|
660
|
+
@out.puts(self.class.help_option_parser.help)
|
|
661
|
+
0
|
|
662
|
+
when "version"
|
|
663
|
+
@out.puts("Usage: rigor version")
|
|
664
|
+
0
|
|
665
|
+
when *HANDLERS.keys
|
|
666
|
+
@argv = ["--help"]
|
|
667
|
+
dispatch(topic)
|
|
668
|
+
else
|
|
669
|
+
@err.puts("Unknown command: #{topic}")
|
|
670
|
+
@err.puts(help)
|
|
671
|
+
EXIT_USAGE
|
|
672
|
+
end
|
|
518
673
|
end
|
|
519
674
|
end
|
|
520
675
|
end
|
data/lib/rigor/config_audit.rb
CHANGED
|
@@ -35,7 +35,7 @@ module Rigor
|
|
|
35
35
|
# (`rbs_extended.*`, `rbs.*`, `dynamic.*`) is left alone, since the engine emits those ids
|
|
36
36
|
# outside the catalogue.
|
|
37
37
|
module ConfigAudit
|
|
38
|
-
# One config-level finding. `kind` discriminates the source key (`:signature_path`,
|
|
38
|
+
# One config-level finding. `kind` discriminates the source key (`:unknown_key`, `:signature_path`,
|
|
39
39
|
# `:bundled_plugin_signature_path`, `:library`, `:disabled_rule`, `:severity_override`,
|
|
40
40
|
# `:bundler_bundle_path`, `:bundler_lockfile`, `:rbs_collection_lockfile`); `fields` carries the kind-specific
|
|
41
41
|
# structured data merged into {#to_h} for JSON consumers.
|
|
@@ -156,9 +156,8 @@ module Rigor
|
|
|
156
156
|
disable = Array(configuration.disabled_rules).filter_map do |token|
|
|
157
157
|
disable_token_warning(token.to_s, plugin_ids)
|
|
158
158
|
end
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
end
|
|
159
|
+
keys = configuration.severity_overrides.keys.map(&:to_s)
|
|
160
|
+
overrides = keys.filter_map { |key| override_key_warning(key, plugin_ids, keys) }
|
|
162
161
|
disable + overrides
|
|
163
162
|
end
|
|
164
163
|
|
|
@@ -172,14 +171,26 @@ module Rigor
|
|
|
172
171
|
# `plugin.`-qualified key naming an unconfigured plugin, and any key outside the built-in vocabulary that is
|
|
173
172
|
# not a qualified plugin id (a plugin's bare rule included), is inert. A reserved run-level row is the home of
|
|
174
173
|
# this map, so naming one is never flagged; `all` is not a family wildcard here, so it is.
|
|
175
|
-
def self.override_key_warning(key, plugin_ids)
|
|
176
|
-
reason = override_key_problem(key, plugin_ids)
|
|
174
|
+
def self.override_key_warning(key, plugin_ids, present = [])
|
|
175
|
+
reason = legacy_alias_problem(key, present) || override_key_problem(key, plugin_ids)
|
|
177
176
|
return nil if reason.nil?
|
|
178
177
|
|
|
179
178
|
Warning.new(kind: :severity_override, message: "severity_overrides: #{key.inspect} #{reason}",
|
|
180
179
|
fields: { "token" => key })
|
|
181
180
|
end
|
|
182
181
|
|
|
182
|
+
# #2005 — `disable:` expands a legacy alias to its canonical id, but the severity resolver matches canonical ids
|
|
183
|
+
# only, so an alias key reaches no row. Name the canonical id rather than accept the key. `present` holds the
|
|
184
|
+
# keys the map already has: when the canonical id is one of them, the advice to write it is already followed.
|
|
185
|
+
def self.legacy_alias_problem(key, present)
|
|
186
|
+
canonical = Analysis::CheckRules::LEGACY_RULE_ALIASES[key]
|
|
187
|
+
return nil if canonical.nil?
|
|
188
|
+
|
|
189
|
+
reason = "is a legacy alias of `#{canonical}`; the override has no effect"
|
|
190
|
+
present.include?(canonical) ? reason : "#{reason}, so write `#{canonical}`"
|
|
191
|
+
end
|
|
192
|
+
private_class_method :legacy_alias_problem
|
|
193
|
+
|
|
183
194
|
def self.override_key_problem(key, plugin_ids)
|
|
184
195
|
return "is not a recognized rule id; the override has no effect" if key == "all"
|
|
185
196
|
return nil unless Array(Analysis::CheckRules.resolve_rule_token(key)).empty?
|
|
@@ -15,12 +15,12 @@ module Rigor
|
|
|
15
15
|
# rules `:warning`.
|
|
16
16
|
# - `strict`: Every rule is `:error`. CI-friendly.
|
|
17
17
|
#
|
|
18
|
-
# The
|
|
18
|
+
# The resolution order, first match wins:
|
|
19
19
|
#
|
|
20
|
-
# 1.
|
|
21
|
-
# 2.
|
|
22
|
-
# 3.
|
|
23
|
-
#
|
|
20
|
+
# 1. An explicit `severity_overrides:` entry for the row (`resolve`'s `overrides:`).
|
|
21
|
+
# 2. A bleeding-edge severity override.
|
|
22
|
+
# 3. The profile's entry for the canonical rule id.
|
|
23
|
+
# 4. The diagnostic's own authored severity (the rule's default); an unrecognised rule keeps it.
|
|
24
24
|
module SeverityProfile
|
|
25
25
|
VALID_PROFILES = %i[lenient balanced strict].freeze
|
|
26
26
|
VALID_SEVERITIES = %i[error warning info off].freeze
|