rigortype 0.2.7 → 0.2.8
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 +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d38773edf003d6de8ea6ec3de2f19aac4c2d74e404c56f39d1ed9595f388a648
|
|
4
|
+
data.tar.gz: beb2b7aea30bf74f4cc8d22e66d441d63ecb906689d6a22c6ca647a7a087a719
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c33b18e310c956f9cc9bbd4806228a095bcf95a137efba50affd49ffca17decac9107006e4a7fd350a9783f289e7d9e336ebc99755e7d69ce321d60b21c731b
|
|
7
|
+
data.tar.gz: c9f7286bc00f3419083ced2e35678062e8508dfe03b6064fe6c9f4cf4ad752479ca8801cce059cb3fc6bc0ee0eaaa216824a9d43cc93332feb7e267ab988253c
|
data/README.md
CHANGED
|
@@ -231,7 +231,7 @@ rigor docs --list # list every bundled page
|
|
|
231
231
|
|
|
232
232
|
## Status
|
|
233
233
|
|
|
234
|
-
Current release: **`v0.2.
|
|
234
|
+
Current release: **`v0.2.8`** (2026-07-06) — the latest cut on the
|
|
235
235
|
`0.2.x` evaluation line opened by `v0.2.0`, the first
|
|
236
236
|
publicly-announced (general / evaluation) release. The line publishes
|
|
237
237
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
|
@@ -20,16 +20,19 @@ Every rule has a two-segment `family.rule` identifier:
|
|
|
20
20
|
| `dump` | `dump_type` notices. |
|
|
21
21
|
|
|
22
22
|
`rigor explain <rule>` prints the full catalogue entry for any
|
|
23
|
-
ID; `rigor explain` with no argument lists them all.
|
|
23
|
+
built-in rule ID; `rigor explain` with no argument lists them all.
|
|
24
24
|
|
|
25
25
|
### Catalogue
|
|
26
26
|
|
|
27
|
-
Each rule has a stable per-rule anchor on this page
|
|
27
|
+
Each built-in rule has a stable per-rule anchor on this page
|
|
28
28
|
(`#rule-<family>-<name>`, dots written as dashes) — the
|
|
29
29
|
`documentation_url` field in `--format json` and `rigor explain`'s
|
|
30
30
|
`Documentation:` line both point here. The `Evidence` column is
|
|
31
31
|
Rigor's confidence that a firing is a true positive (see
|
|
32
|
-
[Evidence tier](#evidence-tier) below).
|
|
32
|
+
[Evidence tier](#evidence-tier) below). The one exception is
|
|
33
|
+
`rbs_extended.unsatisfied-conformance`, an `rbs_extended`-family rule
|
|
34
|
+
rather than a built-in: `rigor explain` does not resolve it and it
|
|
35
|
+
carries no `documentation_url`.
|
|
33
36
|
|
|
34
37
|
| Rule | Fires when | Evidence |
|
|
35
38
|
| --- | --- | --- |
|
|
@@ -86,7 +89,7 @@ re-stamps it for the run. Three profiles, set with the
|
|
|
86
89
|
| --- | --- |
|
|
87
90
|
| `lenient` | Only proven diagnostics are errors; uncertain ones drop to `warning` / `info`. For incremental adoption on legacy code. |
|
|
88
91
|
| `balanced` *(default)* | Most rules `error`; `dump.type` `info`; uncertain rules `warning`. |
|
|
89
|
-
| `strict` |
|
|
92
|
+
| `strict` | Nearly every rule is an `error` — the exceptions are `call.self-undefined-method` (stays `off`, opt-in only) and `flow.unreachable-clause` (`warning`, pending its false-positive gate). CI-friendly. |
|
|
90
93
|
|
|
91
94
|
For finer control, `severity_overrides:` maps a rule ID or a
|
|
92
95
|
family to one of `error`, `warning`, `info`, or `off`:
|
data/docs/manual/06-baseline.md
CHANGED
|
@@ -112,5 +112,8 @@ baseline regenerate` to shrink the file. The
|
|
|
112
112
|
[`rigor-baseline-reduce` skill](08-skills.md) walks this loop
|
|
113
113
|
interactively.
|
|
114
114
|
|
|
115
|
-
To make CI fail
|
|
116
|
-
|
|
115
|
+
To make CI fail on *any* baseline drift, add `--baseline-strict`
|
|
116
|
+
to `rigor check`. It fails not only when a bucket grows past its
|
|
117
|
+
recorded count (excess drift, which the surfaced diagnostics already
|
|
118
|
+
fail on) but also when the code has *shrunk* below it (deficit drift:
|
|
119
|
+
the baseline is now looser than the code and should be regenerated).
|
|
@@ -181,8 +181,8 @@ entry carries two fields:
|
|
|
181
181
|
|
|
182
182
|
- **`dynamic_origin`** — the cause the value became dynamic:
|
|
183
183
|
`external_gem_without_rbs`, `framework_dsl_boundary`,
|
|
184
|
-
`analyzer_budget_cutoff`, `explicit_untyped`,
|
|
185
|
-
`unsupported_syntax`.
|
|
184
|
+
`analyzer_budget_cutoff`, `explicit_untyped`,
|
|
185
|
+
`inferred_return_untyped`, or `unsupported_syntax`.
|
|
186
186
|
- **`tractability`** — the action axis derived from that cause:
|
|
187
187
|
- **`add_rbs`** — you can close it with a type: install RBS
|
|
188
188
|
(`rbs collection install`), enable `dependencies.source_inference:`,
|
|
@@ -190,8 +190,10 @@ entry carries two fields:
|
|
|
190
190
|
- **`enable_plugin`** — a framework / DSL boundary; reach for a
|
|
191
191
|
plugin or [`pre_eval:`](03-configuration.md), not a hand-written
|
|
192
192
|
type.
|
|
193
|
-
- **`engine_gap`** — not closable by a user type
|
|
194
|
-
|
|
193
|
+
- **`engine_gap`** — not closable by a user type: a budget cutoff,
|
|
194
|
+
an inference gap (an untyped parameter or an unbound instance
|
|
195
|
+
variable — `inferred_return_untyped`), or a construct Rigor does
|
|
196
|
+
not yet model; report it.
|
|
195
197
|
|
|
196
198
|
The text report prints a one-line `by tractability:` breakdown under
|
|
197
199
|
the "Add a type here" header, and the JSON carries the same totals as
|
|
@@ -6,9 +6,8 @@ module Rigor
|
|
|
6
6
|
module Analysis
|
|
7
7
|
# ADR-22 Slice 1 — PHPStan-shaped per-project baseline.
|
|
8
8
|
#
|
|
9
|
-
# Loads `.rigor-baseline.yml`, filters a current run's
|
|
10
|
-
#
|
|
11
|
-
# an `(surfaced, silenced_count)` pair for the CLI to render.
|
|
9
|
+
# Loads `.rigor-baseline.yml`, filters a current run's diagnostic stream against the recorded buckets, and
|
|
10
|
+
# emits an `(surfaced, silenced_count)` pair for the CLI to render.
|
|
12
11
|
#
|
|
13
12
|
# Two row shapes are accepted (WD1):
|
|
14
13
|
#
|
|
@@ -27,17 +26,13 @@ module Rigor
|
|
|
27
26
|
# ## Semantics per (file, rule [, message]) bucket (WD4)
|
|
28
27
|
#
|
|
29
28
|
# actual <= count → ALL diagnostics in the bucket are silenced.
|
|
30
|
-
# actual > count → ALL diagnostics in the bucket surface
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# review focus shifts from "which N is new"
|
|
34
|
-
# to "what's going on with this rule in
|
|
35
|
-
# this file as a whole").
|
|
29
|
+
# actual > count → ALL diagnostics in the bucket surface (not just the excess delta — the bucket has
|
|
30
|
+
# crossed its threshold; the team's review focus shifts from "which N is new" to
|
|
31
|
+
# "what's going on with this rule in this file as a whole").
|
|
36
32
|
#
|
|
37
33
|
# ## Filter pipeline position (WD6)
|
|
38
34
|
#
|
|
39
|
-
# The baseline filter runs LAST among the diagnostic-suppression
|
|
40
|
-
# layers:
|
|
35
|
+
# The baseline filter runs LAST among the diagnostic-suppression layers:
|
|
41
36
|
#
|
|
42
37
|
# emit → `# rigor:disable` (per-line)
|
|
43
38
|
# → `# rigor:disable-file`
|
|
@@ -47,41 +42,32 @@ module Rigor
|
|
|
47
42
|
#
|
|
48
43
|
# ## Loading (WD2 (b))
|
|
49
44
|
#
|
|
50
|
-
# `Baseline.load` is called by the CLI when it has resolved
|
|
51
|
-
#
|
|
52
|
-
# CLI
|
|
53
|
-
# of `.rigor-baseline.yml` on disk alone never triggers a
|
|
54
|
-
# load — that's the CLI / Configuration's job to enforce.
|
|
45
|
+
# `Baseline.load` is called by the CLI when it has resolved an explicit baseline path (from `--baseline=PATH`
|
|
46
|
+
# on the CLI or `baseline: <path>` in `.rigor.yml`). The presence of `.rigor-baseline.yml` on disk alone
|
|
47
|
+
# never triggers a load — that's the CLI / Configuration's job to enforce.
|
|
55
48
|
#
|
|
56
49
|
# ## Path handling
|
|
57
50
|
#
|
|
58
|
-
# Baselines store file paths **relative to the project root**
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# the instance normalises each diagnostic's absolute path to a
|
|
63
|
-
# relative one before the bucket lookup.
|
|
51
|
+
# Baselines store file paths **relative to the project root** (the working directory when `rigor` is run).
|
|
52
|
+
# This makes the generated `.rigor-baseline.yml` portable across machines and checkout locations. When
|
|
53
|
+
# filtering a live diagnostic stream, the instance normalises each diagnostic's absolute path to a relative
|
|
54
|
+
# one before the bucket lookup.
|
|
64
55
|
class Baseline
|
|
65
|
-
# The bucket key is intentionally tuple-shaped so rule-ID
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# `count` shadows Struct#count; intentional — `count` is the
|
|
70
|
-
# PHPStan-compatible field name and we don't use the
|
|
71
|
-
# Enumerable-style `Struct#count` on Bucket instances.
|
|
56
|
+
# The bucket key is intentionally tuple-shaped so rule-ID rows and message-pattern rows can coexist in a
|
|
57
|
+
# single multimap. `message` is `nil` for rule-ID rows; a Regexp for message-pattern rows.
|
|
58
|
+
# `count` shadows Struct#count; intentional — `count` is the PHPStan-compatible field name and we don't
|
|
59
|
+
# use the Enumerable-style `Struct#count` on Bucket instances.
|
|
72
60
|
Bucket = Struct.new(:file, :rule, :message_regex, :count, keyword_init: true) # rubocop:disable Lint/StructNewOverride
|
|
73
61
|
|
|
74
62
|
CURRENT_VERSION = 1
|
|
75
63
|
|
|
76
64
|
class << self
|
|
77
|
-
# Load a baseline file from disk. Returns `nil` when the
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# callers translate to a user-facing diagnostic.
|
|
65
|
+
# Load a baseline file from disk. Returns `nil` when the path is nil (the caller's "no baseline
|
|
66
|
+
# configured" state). Raises {LoadError} on malformed content; callers translate to a user-facing
|
|
67
|
+
# diagnostic.
|
|
81
68
|
#
|
|
82
|
-
# `project_root:` is the working directory against which
|
|
83
|
-
#
|
|
84
|
-
# Defaults to `Dir.pwd`.
|
|
69
|
+
# `project_root:` is the working directory against which stored relative paths are resolved during
|
|
70
|
+
# filtering. Defaults to `Dir.pwd`.
|
|
85
71
|
def load(path, project_root: Dir.pwd)
|
|
86
72
|
return nil if path.nil?
|
|
87
73
|
return new([], project_root: project_root) unless File.exist?(path)
|
|
@@ -90,15 +76,12 @@ module Rigor
|
|
|
90
76
|
parse_loaded(raw, path: path, project_root: project_root)
|
|
91
77
|
end
|
|
92
78
|
|
|
93
|
-
# Build a baseline from a current run's diagnostic stream.
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# `Regexp.escape` so generated rows never accidentally
|
|
97
|
-
# over-match on punctuation.
|
|
79
|
+
# Build a baseline from a current run's diagnostic stream. `match_mode:` is `:rule` (default) or
|
|
80
|
+
# `:message`. The message-mode generator passes literal messages through `Regexp.escape` so generated
|
|
81
|
+
# rows never accidentally over-match on punctuation.
|
|
98
82
|
#
|
|
99
|
-
# `project_root:` is used to convert absolute diagnostic
|
|
100
|
-
#
|
|
101
|
-
# to `Dir.pwd`.
|
|
83
|
+
# `project_root:` is used to convert absolute diagnostic paths to relative paths in the generated YAML.
|
|
84
|
+
# Defaults to `Dir.pwd`.
|
|
102
85
|
def from_diagnostics(diagnostics, match_mode: :rule, project_root: Dir.pwd)
|
|
103
86
|
raise ArgumentError, "match_mode must be :rule or :message" unless %i[rule message].include?(match_mode)
|
|
104
87
|
|
|
@@ -177,11 +160,9 @@ module Rigor
|
|
|
177
160
|
end
|
|
178
161
|
end
|
|
179
162
|
|
|
180
|
-
# Generates a Regexp source string for the baseline row.
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
# Users hand-editing the row can replace the escaped
|
|
184
|
-
# form with a pattern.
|
|
163
|
+
# Generates a Regexp source string for the baseline row. The string is `Regexp.escape`d so the YAML
|
|
164
|
+
# round-trip produces a regex that matches the literal message. Users hand-editing the row can replace
|
|
165
|
+
# the escaped form with a pattern.
|
|
185
166
|
def message_pattern_for(message)
|
|
186
167
|
Regexp.new(Regexp.escape(message.to_s))
|
|
187
168
|
end
|
|
@@ -203,10 +184,8 @@ module Rigor
|
|
|
203
184
|
# For each (file, qualified_rule) pair, two arrays:
|
|
204
185
|
# - rule-ID rows (message_regex == nil)
|
|
205
186
|
# - message-pattern rows (message_regex != nil)
|
|
206
|
-
# The matcher walks message-pattern rows first (tighter
|
|
207
|
-
# match
|
|
208
|
-
# any message row fall through to the rule-ID row if
|
|
209
|
-
# one exists.
|
|
187
|
+
# The matcher walks message-pattern rows first (tighter match takes precedence); diagnostics that
|
|
188
|
+
# don't match any message row fall through to the rule-ID row if one exists.
|
|
210
189
|
@by_pair = buckets.group_by { |b| [b.file, b.rule] }.freeze
|
|
211
190
|
freeze
|
|
212
191
|
end
|
|
@@ -214,10 +193,8 @@ module Rigor
|
|
|
214
193
|
# Apply the baseline filter to a diagnostic stream.
|
|
215
194
|
#
|
|
216
195
|
# Returns a 2-tuple:
|
|
217
|
-
# - `surfaced` — the diagnostics that survived the filter
|
|
218
|
-
#
|
|
219
|
-
# - `silenced_count` — how many diagnostics the baseline
|
|
220
|
-
# suppressed (for the WD7 stderr summary line).
|
|
196
|
+
# - `surfaced` — the diagnostics that survived the filter (new findings + entire over-threshold buckets).
|
|
197
|
+
# - `silenced_count` — how many diagnostics the baseline suppressed (for the WD7 stderr summary line).
|
|
221
198
|
def filter(diagnostics)
|
|
222
199
|
return [diagnostics, 0] if buckets.empty?
|
|
223
200
|
|
|
@@ -228,10 +205,8 @@ module Rigor
|
|
|
228
205
|
grouped.each_value do |entries|
|
|
229
206
|
bucket = entries[:bucket]
|
|
230
207
|
diags = entries[:diagnostics]
|
|
231
|
-
# No matching bucket → all surface as new findings.
|
|
232
|
-
# `actual
|
|
233
|
-
# WD4). `actual > count` → all surface (over
|
|
234
|
-
# threshold, WD4).
|
|
208
|
+
# No matching bucket → all surface as new findings. `actual <= count` → all silenced (within
|
|
209
|
+
# threshold, WD4). `actual > count` → all surface (over threshold, WD4).
|
|
235
210
|
if bucket && diags.size <= bucket.count
|
|
236
211
|
silenced_count += diags.size
|
|
237
212
|
else
|
|
@@ -239,39 +214,31 @@ module Rigor
|
|
|
239
214
|
end
|
|
240
215
|
end
|
|
241
216
|
|
|
242
|
-
# Diagnostics that lacked a rule or a path bypass the
|
|
243
|
-
#
|
|
217
|
+
# Diagnostics that lacked a rule or a path bypass the baseline entirely (the baseline can't address
|
|
218
|
+
# them).
|
|
244
219
|
unkeyable = diagnostics.reject { |d| d.qualified_rule && d.path }
|
|
245
220
|
[surfaced + unkeyable, silenced_count]
|
|
246
221
|
end
|
|
247
222
|
|
|
248
|
-
# A single bucket's drift state for slice 2 inspection.
|
|
249
|
-
# `status` is one of:
|
|
223
|
+
# A single bucket's drift state for slice 2 inspection. `status` is one of:
|
|
250
224
|
#
|
|
251
225
|
# - `:within` — `actual <= count` (silenced by the filter).
|
|
252
|
-
# - `:over` — `actual > count` (over threshold; surfaced
|
|
253
|
-
# in the regular `rigor check` output).
|
|
226
|
+
# - `:over` — `actual > count` (over threshold; surfaced in the regular `rigor check` output).
|
|
254
227
|
# - `:cleared` — `actual == 0` (the bucket can be pruned).
|
|
255
|
-
# - `:reducible` — `0 < actual < count` (the bucket's count
|
|
256
|
-
#
|
|
257
|
-
# slice 5 handles this).
|
|
228
|
+
# - `:reducible` — `0 < actual < count` (the bucket's count can be tightened; future `regenerate` slice 5
|
|
229
|
+
# handles this).
|
|
258
230
|
DriftRow = Struct.new(:bucket, :actual_count, :status, keyword_init: true) do
|
|
259
231
|
def delta
|
|
260
232
|
actual_count - bucket.count
|
|
261
233
|
end
|
|
262
234
|
end
|
|
263
235
|
|
|
264
|
-
# Walk the current diagnostic stream and report
|
|
265
|
-
#
|
|
266
|
-
# DriftRow regardless of whether the current run still
|
|
267
|
-
# matches it.
|
|
236
|
+
# Walk the current diagnostic stream and report bucket-level drift. Each baseline bucket becomes one
|
|
237
|
+
# DriftRow regardless of whether the current run still matches it.
|
|
268
238
|
#
|
|
269
|
-
# @param diagnostics [Array<Diagnostic>] current run's
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
# post-baseline surface).
|
|
273
|
-
# @return [Array<DriftRow>] one entry per baseline bucket,
|
|
274
|
-
# in baseline-file order.
|
|
239
|
+
# @param diagnostics [Array<Diagnostic>] current run's diagnostic stream (PRE-filter — pass the raw
|
|
240
|
+
# `result.diagnostics` from `Runner#run`, not the post-baseline surface).
|
|
241
|
+
# @return [Array<DriftRow>] one entry per baseline bucket, in baseline-file order.
|
|
275
242
|
def audit(diagnostics)
|
|
276
243
|
counts = Hash.new(0)
|
|
277
244
|
diagnostics.each do |diag|
|
|
@@ -287,18 +254,15 @@ module Rigor
|
|
|
287
254
|
end
|
|
288
255
|
end
|
|
289
256
|
|
|
290
|
-
# Returns a new Baseline with the given buckets dropped.
|
|
291
|
-
#
|
|
292
|
-
# cleared buckets (`actual == 0`) from the on-disk file.
|
|
257
|
+
# Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune` (slice 2) to
|
|
258
|
+
# remove cleared buckets (`actual == 0`) from the on-disk file.
|
|
293
259
|
def without(buckets_to_drop)
|
|
294
260
|
dropset = buckets_to_drop.to_set
|
|
295
261
|
self.class.new(buckets.reject { |b| dropset.include?(b) }, project_root: @project_root)
|
|
296
262
|
end
|
|
297
263
|
|
|
298
|
-
# Serialise to a YAML string. The generator path writes
|
|
299
|
-
# this
|
|
300
|
-
# across versions of this class as long as the bucket
|
|
301
|
-
# shape is unchanged.
|
|
264
|
+
# Serialise to a YAML string. The generator path writes this through `File.write`; the dump format is
|
|
265
|
+
# stable across versions of this class as long as the bucket shape is unchanged.
|
|
302
266
|
def to_yaml
|
|
303
267
|
rows = buckets.map do |bucket|
|
|
304
268
|
row = { "file" => bucket.file, "rule" => bucket.rule }
|
|
@@ -311,8 +275,7 @@ module Rigor
|
|
|
311
275
|
YAML.dump(document)
|
|
312
276
|
end
|
|
313
277
|
|
|
314
|
-
# The number of buckets recorded. Useful for the CLI
|
|
315
|
-
# summary on `generate`.
|
|
278
|
+
# The number of buckets recorded. Useful for the CLI summary on `generate`.
|
|
316
279
|
def size
|
|
317
280
|
buckets.size
|
|
318
281
|
end
|
|
@@ -344,11 +307,9 @@ module Rigor
|
|
|
344
307
|
end
|
|
345
308
|
|
|
346
309
|
def group_diagnostics_for_filtering(diagnostics)
|
|
347
|
-
# First pass: bin each diagnostic into the bucket that
|
|
348
|
-
#
|
|
349
|
-
#
|
|
350
|
-
# diagnostic that matches no row goes into a synthetic
|
|
351
|
-
# "no-bucket" bin keyed by (file, rule).
|
|
310
|
+
# First pass: bin each diagnostic into the bucket that claims it. Message-pattern rows take precedence
|
|
311
|
+
# over rule-ID rows because they're more specific. A diagnostic that matches no row goes into a
|
|
312
|
+
# synthetic "no-bucket" bin keyed by (file, rule).
|
|
352
313
|
bins = {}
|
|
353
314
|
diagnostics.each do |diag|
|
|
354
315
|
next if diag.qualified_rule.nil? || diag.path.nil?
|
|
@@ -370,8 +331,7 @@ module Rigor
|
|
|
370
331
|
candidates = @by_pair[[normalize_path(diagnostic.path), diagnostic.qualified_rule]]
|
|
371
332
|
return nil if candidates.nil? || candidates.empty?
|
|
372
333
|
|
|
373
|
-
# Tighter (message-pattern) buckets first, then the
|
|
374
|
-
# rule-ID bucket as fallback.
|
|
334
|
+
# Tighter (message-pattern) buckets first, then the rule-ID bucket as fallback.
|
|
375
335
|
message_buckets, rule_buckets = candidates.partition(&:message_regex)
|
|
376
336
|
message_buckets.each do |b|
|
|
377
337
|
return b if b.message_regex.match?(diagnostic.message.to_s)
|
|
@@ -2,32 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Analysis
|
|
5
|
-
# Binds one logical project path (the path the user is editing,
|
|
6
|
-
# e.g.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# they read from the physical path instead; when they emit a
|
|
10
|
-
# `Diagnostic`, the path is the logical one so editors highlight
|
|
11
|
-
# the buffer the user is actually looking at.
|
|
5
|
+
# Binds one logical project path (the path the user is editing, e.g. `lib/foo.rb`) to a physical file
|
|
6
|
+
# containing the in-flight buffer bytes (e.g. `/tmp/9539itfeh2.rb`). When the runner / workers / pre-passes
|
|
7
|
+
# need to read source for the logical path, they read from the physical path instead; when they emit a
|
|
8
|
+
# `Diagnostic`, the path is the logical one so editors highlight the buffer the user is actually looking at.
|
|
12
9
|
#
|
|
13
|
-
# See `docs/design/20260516-editor-mode.md` for the design.
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# programmatic callers pass a `BufferBinding` to `Runner.new`.
|
|
10
|
+
# See `docs/design/20260516-editor-mode.md` for the design. The CLI surfaces this through paired
|
|
11
|
+
# `--tmp-file` / `--instead-of` flags on `rigor check` and `rigor type-of`; programmatic callers pass a
|
|
12
|
+
# `BufferBinding` to `Runner.new`.
|
|
17
13
|
BufferBinding = Data.define(:logical_path, :physical_path) do
|
|
18
|
-
# Returns the physical path to read bytes from when the caller
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# singular today (one buffer per run).
|
|
14
|
+
# Returns the physical path to read bytes from when the caller is about to parse `path`. For non-logical
|
|
15
|
+
# paths returns the input unchanged. Cheap to call on every path; the binding is singular today (one
|
|
16
|
+
# buffer per run).
|
|
22
17
|
def resolve(path)
|
|
23
18
|
path == logical_path ? physical_path : path
|
|
24
19
|
end
|
|
25
20
|
|
|
26
|
-
# Returns the path the caller should report in user-facing
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# pass through unchanged, so it is safe to stamp every
|
|
30
|
-
# outgoing path through this helper.
|
|
21
|
+
# Returns the path the caller should report in user-facing output (diagnostics, run stats) when it
|
|
22
|
+
# currently holds the physical path. The inverse of `#resolve`. Non-physical paths pass through
|
|
23
|
+
# unchanged, so it is safe to stamp every outgoing path through this helper.
|
|
31
24
|
def display_path(path)
|
|
32
25
|
path == physical_path ? logical_path : path
|
|
33
26
|
end
|
|
@@ -5,44 +5,29 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Analysis
|
|
7
7
|
module CheckRules
|
|
8
|
-
# Walks a parse tree and collects every `IfNode` / `UnlessNode`
|
|
9
|
-
#
|
|
10
|
-
# disqualified by the rule's conservative envelope.
|
|
8
|
+
# Walks a parse tree and collects every `IfNode` / `UnlessNode` whose predicate folds to a
|
|
9
|
+
# `Type::Constant` AND is NOT disqualified by the rule's conservative envelope.
|
|
11
10
|
#
|
|
12
|
-
# The companion rule (`flow.always-truthy-condition`) is
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# modelling produces inferred constants that look real but
|
|
18
|
-
# are pragmatically false-positives. This collector adds two
|
|
19
|
-
# surgical skips to bring the inferred-constant case in
|
|
20
|
-
# without resurfacing those false positives:
|
|
11
|
+
# The companion rule (`flow.always-truthy-condition`) is the inferred-constant counterpart to
|
|
12
|
+
# `flow.unreachable-branch` (which only fires on syntactic literals). The literal-only rule was the
|
|
13
|
+
# v0.1.2 first-cut because Rigor's incomplete loop / mutation / RBS-strictness modelling produces
|
|
14
|
+
# inferred constants that look real but are pragmatically false-positives. This collector adds two
|
|
15
|
+
# surgical skips to bring the inferred-constant case in without resurfacing those false positives:
|
|
21
16
|
#
|
|
22
|
-
# - **Inside a loop / block**: predicates nested inside
|
|
23
|
-
# `
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# like the user explicitly checking for a runtime
|
|
32
|
-
# condition that the type system already proves can't
|
|
33
|
-
# happen — Rigor's strict-on-returns RBS often disagrees
|
|
34
|
-
# with the user's defensive check (e.g. `Module#name`
|
|
35
|
-
# returns `String` per RBS but anonymous classes really
|
|
36
|
-
# do return `nil`). Skipping these forms keeps the rule
|
|
37
|
-
# useful for genuine logic errors without faulting
|
|
38
|
-
# defensive code.
|
|
17
|
+
# - **Inside a loop / block**: predicates nested inside `WhileNode` / `UntilNode` / `ForNode` /
|
|
18
|
+
# `BlockNode` ancestors. Mutation tracking through loop bodies is incomplete (`shift = 0; loop {
|
|
19
|
+
# shift += 7 }` keeps `shift` at `Constant<7>` per the analyzer), so a `Constant<bool>` predicate
|
|
20
|
+
# inside the loop body is suspect.
|
|
21
|
+
# - **Defensive predicate calls**: `.nil?` / `.empty?` / `.zero?` / `.any?` / `.none?` / `.all?`. These
|
|
22
|
+
# read like the user explicitly checking for a runtime condition that the type system already proves
|
|
23
|
+
# can't happen — Rigor's strict-on-returns RBS often disagrees with the user's defensive check (e.g.
|
|
24
|
+
# `Module#name` returns `String` per RBS but anonymous classes really do return `nil`). Skipping
|
|
25
|
+
# these forms keeps the rule useful for genuine logic errors without faulting defensive code.
|
|
39
26
|
#
|
|
40
|
-
# Also skipped (so the literal-only `unreachable-branch`
|
|
41
|
-
# rule doesn't double-fire alongside this one):
|
|
27
|
+
# Also skipped (so the literal-only `unreachable-branch` rule doesn't double-fire alongside this one):
|
|
42
28
|
#
|
|
43
|
-
# - Predicates whose direct AST shape is a literal
|
|
44
|
-
#
|
|
45
|
-
# / symbol / regexp).
|
|
29
|
+
# - Predicates whose direct AST shape is a literal (`TrueNode` / `FalseNode` / `NilNode` / numeric /
|
|
30
|
+
# string / symbol / regexp).
|
|
46
31
|
class AlwaysTruthyConditionCollector
|
|
47
32
|
DEFENSIVE_PREDICATES = %i[nil? empty? zero? any? none? all? respond_to?].freeze
|
|
48
33
|
LOOP_OR_BLOCK_NODE_CLASSES = [
|
|
@@ -56,34 +41,30 @@ module Rigor
|
|
|
56
41
|
|
|
57
42
|
Result = Data.define(:node, :polarity)
|
|
58
43
|
|
|
59
|
-
# ADR-53 Track B — the node classes the shared {RuleWalk}
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# walk's `:loop_or_block` gate, applied before `#visit`.
|
|
44
|
+
# ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector, and the
|
|
45
|
+
# context gate under which the walk suppresses it (loop / block bodies — see the envelope above). The
|
|
46
|
+
# legacy walk's `!in_loop_or_block` guard is now the walk's `:loop_or_block` gate, applied before
|
|
47
|
+
# `#visit`.
|
|
64
48
|
NODE_CLASSES = [Prism::IfNode, Prism::UnlessNode].freeze
|
|
65
49
|
RULE_WALK_GATES = [:loop_or_block].freeze
|
|
66
50
|
|
|
67
|
-
# @return [Array<Result>] one entry per qualifying
|
|
68
|
-
# predicate. Empty when the tree carries no firing
|
|
51
|
+
# @return [Array<Result>] one entry per qualifying predicate. Empty when the tree carries no firing
|
|
69
52
|
# predicates.
|
|
70
53
|
def initialize(scope_index)
|
|
71
54
|
@scope_index = scope_index
|
|
72
55
|
@results = []
|
|
73
56
|
end
|
|
74
57
|
|
|
75
|
-
# Legacy single-collector walk — kept as the oracle the
|
|
76
|
-
#
|
|
77
|
-
# against; deleted when Track B completes.
|
|
58
|
+
# Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
|
|
59
|
+
# {RuleWalk} against; deleted when Track B completes.
|
|
78
60
|
def collect(root)
|
|
79
61
|
walk(root, in_loop_or_block: false)
|
|
80
62
|
@results.freeze
|
|
81
63
|
end
|
|
82
64
|
|
|
83
|
-
# {RuleWalk} entry point: the per-node logic of the legacy walk,
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# is the walk's `:loop_or_block` gate now.
|
|
65
|
+
# {RuleWalk} entry point: the per-node logic of the legacy walk, invoked under the same traversal
|
|
66
|
+
# contract. The `context` is unused — the loop / block suppression this collector relied on is the
|
|
67
|
+
# walk's `:loop_or_block` gate now.
|
|
87
68
|
def visit(node, _context = nil)
|
|
88
69
|
collect_predicate(node)
|
|
89
70
|
end
|