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
|
@@ -48,50 +48,32 @@ module Rigor
|
|
|
48
48
|
:analyzed_files, :unresolved_self_calls
|
|
49
49
|
|
|
50
50
|
# @param configuration [Rigor::Configuration]
|
|
51
|
-
# @param explain [Boolean] surface fail-soft fallback events
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# cache
|
|
55
|
-
# and successors). Pass `nil` to disable caching for this
|
|
56
|
-
# run; the CLI's `--no-cache` flag wires `nil` through.
|
|
57
|
-
# v0.0.9 group A slice 1 introduces the surface; later
|
|
51
|
+
# @param explain [Boolean] surface fail-soft fallback events as `:info` diagnostics.
|
|
52
|
+
# @param cache_store [Rigor::Cache::Store, nil] the persistent cache the runner exposes to producers
|
|
53
|
+
# (`RbsConstantTable` and successors). Pass `nil` to disable caching for this run; the CLI's
|
|
54
|
+
# `--no-cache` flag wires `nil` through. v0.0.9 group A slice 1 introduces the surface; later
|
|
58
55
|
# slices route real producers through it.
|
|
59
|
-
# @param workers [Integer] ADR-15 Phase 4b — when greater
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
# `
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# (`
|
|
78
|
-
# per-
|
|
79
|
-
#
|
|
80
|
-
# plugin `#prepare` runs once per generation rather than
|
|
81
|
-
# once per request. Watched-file invalidation is the
|
|
82
|
-
# owner's responsibility; the runner trusts the snapshot
|
|
83
|
-
# it was given.
|
|
84
|
-
# @param environment [Rigor::Environment, nil] opt-in
|
|
85
|
-
# Environment override. When supplied, sequential mode uses
|
|
86
|
-
# the provided env instance in `#analyze_files` instead of
|
|
87
|
-
# building a fresh one via `Environment.for_project`, and
|
|
88
|
-
# attaches the runner's per-run reporter pair onto the
|
|
89
|
-
# env's mutable `Reporters` slot via
|
|
90
|
-
# `Environment#attach_reporters!`. Long-lived consumers
|
|
91
|
-
# (LSP `ProjectContext`) pass a shared env so per-publish
|
|
92
|
-
# work doesn't repeat the `Environment.for_project` build
|
|
93
|
-
# (bundler / lockfile / collection discovery, RbsLoader
|
|
94
|
-
# construction). Pool mode ignores the override — each
|
|
56
|
+
# @param workers [Integer] ADR-15 Phase 4b — when greater than zero, per-file analysis dispatches
|
|
57
|
+
# across a pool of N workers. Default `0` keeps the sequential code path bit-for-bit unchanged.
|
|
58
|
+
# Controlled via the `RIGOR_RACTOR_WORKERS` env var or `.rigor.yml` `parallel.workers:` (Phase 4c,
|
|
59
|
+
# fully wired).
|
|
60
|
+
# @param collect_stats [Boolean] when true (default), `#run` builds a {RunStats} summary exposed via
|
|
61
|
+
# `result.stats` — this forces the RBS env build at end-of-run so the `class_decl_paths` snapshot
|
|
62
|
+
# has real source attribution. Set to false to skip the stats summary entirely; the CLI's
|
|
63
|
+
# `--no-stats` threads `false` through to keep trivial-fixture runs from warming `.rigor/cache`.
|
|
64
|
+
# @param prebuilt [Rigor::Analysis::ProjectScan, nil] when supplied, the runner adopts the pre-built
|
|
65
|
+
# plugin registry / dependency-source index / scanner outputs from the snapshot and skips the
|
|
66
|
+
# per-call pre-passes that produce them. Used by long-lived integrations
|
|
67
|
+
# (`Rigor::LanguageServer::ProjectContext`) to keep per-buffer requests fast — scanners walk the
|
|
68
|
+
# project once per generation rather than once per request, and plugin `#prepare` runs once per
|
|
69
|
+
# generation rather than once per request. Watched-file invalidation is the owner's responsibility;
|
|
70
|
+
# the runner trusts the snapshot it was given.
|
|
71
|
+
# @param environment [Rigor::Environment, nil] opt-in Environment override. When supplied, sequential
|
|
72
|
+
# mode uses the provided env instance in `#analyze_files` instead of building a fresh one via
|
|
73
|
+
# `Environment.for_project`, and attaches the runner's per-run reporter pair onto the env's mutable
|
|
74
|
+
# `Reporters` slot via `Environment#attach_reporters!`. Long-lived consumers (LSP `ProjectContext`)
|
|
75
|
+
# pass a shared env so per-publish work doesn't repeat the `Environment.for_project` build (bundler
|
|
76
|
+
# / lockfile / collection discovery, RbsLoader construction). Pool mode ignores the override — each
|
|
95
77
|
# worker continues to build its own Environment.
|
|
96
78
|
def initialize(configuration:, explain: false, # rubocop:disable Metrics/ParameterLists,Metrics/AbcSize,Metrics/MethodLength
|
|
97
79
|
cache_store: Cache::Store.new(root: DEFAULT_CACHE_ROOT),
|
|
@@ -107,33 +89,28 @@ module Rigor
|
|
|
107
89
|
@buffer = buffer
|
|
108
90
|
@prebuilt = prebuilt
|
|
109
91
|
@environment_override = environment
|
|
110
|
-
# ADR-46 slice 1 — opt-in cross-file dependency recording. Off by
|
|
111
|
-
#
|
|
112
|
-
# cross-file reads into `file_dependencies` (the incremental
|
|
92
|
+
# ADR-46 slice 1 — opt-in cross-file dependency recording. Off by default; when true,
|
|
93
|
+
# `analyze_file` records each file's cross-file reads into `file_dependencies` (the incremental
|
|
113
94
|
# cache, a later slice, consumes them).
|
|
114
95
|
@record_dependencies = record_dependencies
|
|
115
|
-
# ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording.
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# them). Purely observational — diagnostics are byte-identical.
|
|
96
|
+
# ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording. Off by default; when true,
|
|
97
|
+
# `analyze_file` activates the engine choke-point recorder and collects each file's misses into
|
|
98
|
+
# `unresolved_self_calls` (a later closed-class-gated rule consumes them). Purely observational —
|
|
99
|
+
# diagnostics are byte-identical.
|
|
120
100
|
@record_self_calls = record_self_calls
|
|
121
101
|
@unresolved_self_calls = {}
|
|
122
|
-
# Memoised activation decision for the `call.self-undefined-method`
|
|
123
|
-
#
|
|
102
|
+
# Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed).
|
|
103
|
+
# See `self_undefined_rule_active?`.
|
|
124
104
|
@self_undefined_rule_active = nil
|
|
125
105
|
@analyzed_files = [].freeze
|
|
126
|
-
# In-memory source map for `#run_source` — `{ logical_path => source
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# logical path. nil on a normal disk-backed run.
|
|
106
|
+
# In-memory source map for `#run_source` — `{ logical_path => source String }`. When set,
|
|
107
|
+
# `parse_source` reads bytes from here instead of disk and `expand_paths` accepts the (possibly
|
|
108
|
+
# non-existent) logical path. nil on a normal disk-backed run.
|
|
130
109
|
@in_memory_sources = nil
|
|
131
|
-
# ADR-46 slice 2 — the subset-analysis hook. When set (a collection
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# affected closure and serves the rest from the per-file cache.
|
|
136
|
-
# `nil` (the default) analyzes everything.
|
|
110
|
+
# ADR-46 slice 2 — the subset-analysis hook. When set (a collection of paths), the whole-project
|
|
111
|
+
# pre-pass still runs over every file (so the cross-file index is complete), but only files in this
|
|
112
|
+
# set are analyzed for diagnostics — the body tier re-analyses the affected closure and serves the
|
|
113
|
+
# rest from the per-file cache. `nil` (the default) analyzes everything.
|
|
137
114
|
@analyze_only = analyze_only && Set.new(analyze_only)
|
|
138
115
|
@file_dependencies = {}
|
|
139
116
|
@plugin_registry = Plugin::Registry::EMPTY
|
|
@@ -141,14 +118,11 @@ module Rigor
|
|
|
141
118
|
@rbs_extended_reporter = RbsExtended::Reporter.new
|
|
142
119
|
@boundary_cross_reporter = DependencySourceInference::BoundaryCrossReporter.new
|
|
143
120
|
@source_rbs_synthesis_reporter = Plugin::SourceRbsSynthesisReporter.new
|
|
144
|
-
# `#run` resets these for each invocation; pre-seed them to
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
# `rigor:v1:conforms-to` results) live in one shared mutable
|
|
150
|
-
# {RunSnapshots} sink so the analysis path that writes them and
|
|
151
|
-
# the run / aggregator code that reads them stay in separate
|
|
121
|
+
# `#run` resets these for each invocation; pre-seed them to empty containers so `build_run_stats` /
|
|
122
|
+
# `pre_file_diagnostics` (private, called only from `#run`) can read them without nil-guards. The
|
|
123
|
+
# four end-of-pass snapshots (RBS class / signature-path tables, synthesized-namespace names,
|
|
124
|
+
# `rigor:v1:conforms-to` results) live in one shared mutable {RunSnapshots} sink so the analysis
|
|
125
|
+
# path that writes them and the run / aggregator code that reads them stay in separate
|
|
152
126
|
# collaborators without a back-reference cycle.
|
|
153
127
|
@snapshots = RunSnapshots.new
|
|
154
128
|
@cached_plugin_prepare_diagnostics = [].freeze
|
|
@@ -166,20 +140,15 @@ module Rigor
|
|
|
166
140
|
build_collaborators
|
|
167
141
|
end
|
|
168
142
|
|
|
169
|
-
# ADR-pending editor mode — present when the runner is wired
|
|
170
|
-
#
|
|
171
|
-
# (`docs/design/20260516-editor-mode.md`). Nil for normal
|
|
172
|
-
# project runs.
|
|
143
|
+
# ADR-pending editor mode — present when the runner is wired for the `--tmp-file` / `--instead-of`
|
|
144
|
+
# buffer-binding shape (`docs/design/20260516-editor-mode.md`). Nil for normal project runs.
|
|
173
145
|
attr_reader :buffer
|
|
174
146
|
|
|
175
|
-
# Walks every Ruby file under `paths`, parses it, builds a
|
|
176
|
-
#
|
|
177
|
-
# `Rigor::
|
|
178
|
-
# `
|
|
179
|
-
#
|
|
180
|
-
# diagnostic plus any Prism parse errors. The Environment
|
|
181
|
-
# is built once at run start through `Environment.for_project`
|
|
182
|
-
# so all files share the same RBS load.
|
|
147
|
+
# Walks every Ruby file under `paths`, parses it, builds a per-node scope index through
|
|
148
|
+
# `Rigor::Inference::ScopeIndexer`, and runs the `Rigor::Analysis::CheckRules` catalogue over it.
|
|
149
|
+
# Returns a `Rigor::Analysis::Result` aggregating every produced diagnostic plus any Prism parse
|
|
150
|
+
# errors. The Environment is built once at run start through `Environment.for_project` so all files
|
|
151
|
+
# share the same RBS load.
|
|
183
152
|
def run(paths = @configuration.paths)
|
|
184
153
|
Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths =
|
|
185
154
|
@configuration.fold_platform_specific_paths
|
|
@@ -206,15 +175,12 @@ module Rigor
|
|
|
206
175
|
)
|
|
207
176
|
end
|
|
208
177
|
|
|
209
|
-
# Analyze a single source String in memory, without writing it to
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
# the result matches a one-file disk run; only the cross-file project
|
|
216
|
-
# pre-pass is empty (there is one file, and the per-file indexer
|
|
217
|
-
# self-discovers its own classes / defs).
|
|
178
|
+
# Analyze a single source String in memory, without writing it to disk — a clean entry point for
|
|
179
|
+
# embedders (LSP / editor mode) and a faster spec path than the per-call tmpdir + chdir. The source
|
|
180
|
+
# is bound to `path` (purely a logical identity carried in diagnostic locations; it need not exist on
|
|
181
|
+
# disk). The full run machinery still runs — environment build, plugin `prepare`, severity profile —
|
|
182
|
+
# so the result matches a one-file disk run; only the cross-file project pre-pass is empty (there is
|
|
183
|
+
# one file, and the per-file indexer self-discovers its own classes / defs).
|
|
218
184
|
#
|
|
219
185
|
# @param source [String] Ruby source to analyze.
|
|
220
186
|
# @param path [String] logical path for diagnostic locations.
|
|
@@ -226,37 +192,31 @@ module Rigor
|
|
|
226
192
|
@in_memory_sources = nil
|
|
227
193
|
end
|
|
228
194
|
|
|
229
|
-
# ADR-46 — the project file set that a run over `paths` would
|
|
230
|
-
#
|
|
231
|
-
# the incremental fingerprint can be derived cheaply on the warm path
|
|
195
|
+
# ADR-46 — the project file set that a run over `paths` would analyze, computed by globbing only (no
|
|
196
|
+
# RBS environment build), so the incremental fingerprint can be derived cheaply on the warm path
|
|
232
197
|
# before deciding whether to build the env at all.
|
|
233
198
|
def analysis_file_set(paths = @configuration.paths)
|
|
234
199
|
expand_paths(paths).fetch(:files)
|
|
235
200
|
end
|
|
236
201
|
|
|
237
|
-
# ADR-46 §2 — inverts {#file_dependencies} into the reverse edge the
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
# `record_dependencies: true`). The negative (`missing`) edges are
|
|
245
|
-
# NOT inverted here: they feed the structural tier (slice 3), which
|
|
246
|
-
# re-checks a consumer when a name it looked up and did not resolve
|
|
247
|
-
# later appears.
|
|
202
|
+
# ADR-46 §2 — inverts {#file_dependencies} into the reverse edge the incremental step walks:
|
|
203
|
+
# `dependents[X] = { A : A read a declaration / body from X }`. On an edit to X, the body tier
|
|
204
|
+
# (slice 2) re-analyses `{X} ∪ dependents[X]` and serves every other file from the per-file cache.
|
|
205
|
+
# Built on demand from the recorded `sources` sets (so it reflects whatever `analyze_file` captured —
|
|
206
|
+
# empty unless the runner was constructed with `record_dependencies: true`). The negative (`missing`)
|
|
207
|
+
# edges are NOT inverted here: they feed the structural tier (slice 3), which re-checks a consumer
|
|
208
|
+
# when a name it looked up and did not resolve later appears.
|
|
248
209
|
def file_dependents
|
|
249
210
|
Incremental.invert(@file_dependencies.transform_values(&:sources))
|
|
250
211
|
end
|
|
251
212
|
|
|
252
|
-
# ADR-46 slice 4 — per-symbol body fingerprints, computed from the
|
|
253
|
-
#
|
|
213
|
+
# ADR-46 slice 4 — per-symbol body fingerprints, computed from the project pre-pass def index. Returns
|
|
214
|
+
# a frozen hash of the form:
|
|
254
215
|
# { "path/to/file.rb" => { "ClassName#method" => sha256_hex, … }, … }
|
|
255
|
-
# Used by {Analysis::IncrementalSession} to detect which symbols in a
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
# analysis); returns an empty frozen hash before the first run.
|
|
216
|
+
# Used by {Analysis::IncrementalSession} to detect which symbols in a changed file actually changed
|
|
217
|
+
# bodies, so only callers of those specific symbols are re-checked. Only meaningful after a run that
|
|
218
|
+
# populated `@project_discovered_def_nodes` (i.e. any full or subset analysis); returns an empty
|
|
219
|
+
# frozen hash before the first run.
|
|
260
220
|
def symbol_fingerprints
|
|
261
221
|
result = Hash.new { |h, k| h[k] = {} }
|
|
262
222
|
@project_discovered_def_sources.each do |class_name, methods|
|
|
@@ -272,11 +232,10 @@ module Rigor
|
|
|
272
232
|
result.transform_values(&:freeze).freeze
|
|
273
233
|
end
|
|
274
234
|
|
|
275
|
-
# ADR-46 slice 3 — per-file set of the qualified class/module names
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# class-source attribution (class → declaring files).
|
|
235
|
+
# ADR-46 slice 3 — per-file set of the qualified class/module names declared in that file. Used to
|
|
236
|
+
# detect a class that *appeared* in an edit so a subclass whose ancestor was previously undefined
|
|
237
|
+
# (and so recorded a negative class edge) is re-checked. Inverts the project class-source attribution
|
|
238
|
+
# (class → declaring files).
|
|
280
239
|
def class_declarations
|
|
281
240
|
result = Hash.new { |hash, key| hash[key] = Set.new }
|
|
282
241
|
@project_discovered_class_sources.each do |class_name, files|
|
|
@@ -285,14 +244,12 @@ module Rigor
|
|
|
285
244
|
result.transform_values(&:freeze).freeze
|
|
286
245
|
end
|
|
287
246
|
|
|
288
|
-
# ADR-45 — unchanged-project fast path. Serves the whole run's
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
# run; the entry is keyed on the inputs known up front (config, gem
|
|
295
|
-
# / engine versions, analyzed-path set).
|
|
247
|
+
# ADR-45 — unchanged-project fast path. Serves the whole run's (pre-severity-profile) diagnostics
|
|
248
|
+
# from one record-and-validate cache entry when every file the previous run read is unchanged,
|
|
249
|
+
# skipping the dominant per-file inference. The dependency set is collected AFTER the run (so it
|
|
250
|
+
# captures files the plugins read mid-analysis, e.g. a Pundit policy) and re-validated on the next
|
|
251
|
+
# run; the entry is keyed on the inputs known up front (config, gem / engine versions, analyzed-path
|
|
252
|
+
# set).
|
|
296
253
|
def compute_run_diagnostics(expansion)
|
|
297
254
|
@run_served_from_cache = false
|
|
298
255
|
return assemble_run_diagnostics(expansion) unless run_result_cacheable?
|
|
@@ -313,19 +270,17 @@ module Rigor
|
|
|
313
270
|
@run_served_from_cache = !computed
|
|
314
271
|
diagnostics
|
|
315
272
|
rescue StandardError
|
|
316
|
-
# The result cache must never break a run. If anything in the
|
|
317
|
-
#
|
|
273
|
+
# The result cache must never break a run. If anything in the cache path fails, fall back to a
|
|
274
|
+
# direct, uncached analysis.
|
|
318
275
|
@run_served_from_cache = false
|
|
319
276
|
assemble_run_diagnostics(expansion)
|
|
320
277
|
end
|
|
321
278
|
|
|
322
279
|
def assemble_run_diagnostics(expansion, environment: nil)
|
|
323
280
|
diagnostics = @diagnostic_aggregator.pre_file_diagnostics(expansion)
|
|
324
|
-
# ADR-46 — record which project files this run actually analyzed
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
# per-file cache, so it needs the full analyzed set to subtract the
|
|
328
|
-
# affected closure from.
|
|
281
|
+
# ADR-46 — record which project files this run actually analyzed (the `analyze_only` subset, or
|
|
282
|
+
# all of them). The incremental orchestrator serves every analyzed-but-not-affected file from the
|
|
283
|
+
# per-file cache, so it needs the full analyzed set to subtract the affected closure from.
|
|
329
284
|
targets = target_files(expansion)
|
|
330
285
|
@analyzed_files = targets
|
|
331
286
|
diagnostics += @pool_coordinator.analyze_files(targets, environment: environment)
|
|
@@ -336,9 +291,8 @@ module Rigor
|
|
|
336
291
|
diagnostics + @diagnostic_aggregator.source_rbs_synthesis_diagnostics
|
|
337
292
|
end
|
|
338
293
|
|
|
339
|
-
# A cache hit skipped the analysis, so the per-run stats (wall
|
|
340
|
-
#
|
|
341
|
-
# rather than the stale snapshot defaults.
|
|
294
|
+
# A cache hit skipped the analysis, so the per-run stats (wall split, RBS-class counts, …) were never
|
|
295
|
+
# gathered — report none rather than the stale snapshot defaults.
|
|
342
296
|
def stats_for_run(wall_started_at:, expansion:)
|
|
343
297
|
return nil unless @collect_stats
|
|
344
298
|
return nil if @run_served_from_cache
|
|
@@ -346,20 +300,18 @@ module Rigor
|
|
|
346
300
|
build_run_stats(wall_started_at: wall_started_at, expansion: expansion)
|
|
347
301
|
end
|
|
348
302
|
|
|
349
|
-
# Cacheable only for a full sequential project run with a writable
|
|
350
|
-
#
|
|
351
|
-
#
|
|
352
|
-
# processes; editor mode is per-buffer; prebuilt is the LSP path).
|
|
303
|
+
# Cacheable only for a full sequential project run with a writable cache and no per-buffer /
|
|
304
|
+
# prebuilt override — every other mode has a different result identity (pool workers read in
|
|
305
|
+
# separate processes; editor mode is per-buffer; prebuilt is the LSP path).
|
|
353
306
|
def run_result_cacheable?
|
|
354
307
|
!@cache_store.nil? && !@cache_store.read_only? &&
|
|
355
308
|
@buffer.nil? && @prebuilt.nil? && !pool_mode?
|
|
356
309
|
end
|
|
357
310
|
|
|
358
|
-
# Stable cache key inputs — known before the run: a digest of the
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
# the cache for this run rather than risking a malformed key.
|
|
311
|
+
# Stable cache key inputs — known before the run: a digest of the resolved configuration, the engine
|
|
312
|
+
# + rbs versions + `--explain`, and the analyzed-path SET (adding/removing a file changes the key;
|
|
313
|
+
# editing one is caught by dependency validation). nil disables the cache for this run rather than
|
|
314
|
+
# risking a malformed key.
|
|
363
315
|
def run_key_descriptor(expansion, rbs_descriptor)
|
|
364
316
|
Cache::Descriptor.new(
|
|
365
317
|
gems: rbs_descriptor.gems,
|
|
@@ -373,18 +325,15 @@ module Rigor
|
|
|
373
325
|
nil
|
|
374
326
|
end
|
|
375
327
|
|
|
376
|
-
# Files the run actually depended on, collected AFTER it ran:
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
# mid-analysis are included). Re-digested on the next run by
|
|
380
|
-
# {Descriptor#fresh?}.
|
|
328
|
+
# Files the run actually depended on, collected AFTER it ran: every analyzed file, every RBS `sig`
|
|
329
|
+
# file (`rbs_descriptor.files`), and every file each plugin read (complete post-run, so reads made
|
|
330
|
+
# mid-analysis are included). Re-digested on the next run by {Descriptor#fresh?}.
|
|
381
331
|
def run_dependency_descriptor(expansion, rbs_descriptor)
|
|
382
332
|
entries = analyzed_file_entries(expansion) + rbs_descriptor.files
|
|
383
333
|
@plugin_registry.plugins.each do |plugin|
|
|
384
|
-
# Read the boundary WITHOUT triggering its lazy `@io_boundary ||=`
|
|
385
|
-
#
|
|
386
|
-
#
|
|
387
|
-
# it contributes no dependencies.
|
|
334
|
+
# Read the boundary WITHOUT triggering its lazy `@io_boundary ||=` initializer: plugin instances
|
|
335
|
+
# are frozen after the run, and a plugin that never built a boundary read no files through it,
|
|
336
|
+
# so it contributes no dependencies.
|
|
388
337
|
boundary = plugin.instance_variable_get(:@io_boundary)
|
|
389
338
|
entries.concat(boundary.cache_descriptor.files) if boundary
|
|
390
339
|
end
|
|
@@ -450,14 +399,11 @@ module Rigor
|
|
|
450
399
|
apply_pre_passes_result(@pre_passes.adopt_prebuilt(scan))
|
|
451
400
|
end
|
|
452
401
|
|
|
453
|
-
# Internal: copies a {ProjectPrePasses::Result} bundle onto the
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
# discovery tables at their frozen-empty constructor defaults
|
|
459
|
-
# (the bundle carries `nil` for them, matching the original
|
|
460
|
-
# adopt path that never touched them).
|
|
402
|
+
# Internal: copies a {ProjectPrePasses::Result} bundle onto the runner's ivars in the assignment
|
|
403
|
+
# order the original inline pre-pass body used, so every downstream reader (per-file analysis seed,
|
|
404
|
+
# pool environment build, diagnostic aggregator) sees the same ivar surface. The prebuilt path leaves
|
|
405
|
+
# the discovery tables at their frozen-empty constructor defaults (the bundle carries `nil` for them,
|
|
406
|
+
# matching the original adopt path that never touched them).
|
|
461
407
|
def apply_pre_passes_result(result) # rubocop:disable Metrics/AbcSize
|
|
462
408
|
@plugin_registry = result.plugin_registry
|
|
463
409
|
@dependency_source_index = result.dependency_source_index
|
|
@@ -483,20 +429,17 @@ module Rigor
|
|
|
483
429
|
end
|
|
484
430
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result
|
|
485
431
|
|
|
486
|
-
# Ruby versions probed (ascending) to discover the lowest one this
|
|
487
|
-
#
|
|
488
|
-
#
|
|
489
|
-
#
|
|
490
|
-
#
|
|
491
|
-
# trial, 20260620-skill-driven-onboarding-dogfood.md, burned cycles
|
|
492
|
-
# on exactly this).
|
|
432
|
+
# Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
|
|
433
|
+
# `version:`. Prism exposes no version list, so the floor is found empirically — only when a
|
|
434
|
+
# misconfigured `target_ruby` is rejected — so the diagnostic can name it instead of leaving the user
|
|
435
|
+
# to guess (the dogfood field trial, 20260620-skill-driven-onboarding-dogfood.md, burned cycles on
|
|
436
|
+
# exactly this).
|
|
493
437
|
PRISM_VERSION_LADDER = %w[
|
|
494
438
|
3.0.0 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 4.0.0 4.1.0 4.2.0
|
|
495
439
|
].freeze
|
|
496
440
|
|
|
497
|
-
# @return [String, nil] the lowest `target_ruby` this Prism build
|
|
498
|
-
#
|
|
499
|
-
# process (the value is constant for a given Prism).
|
|
441
|
+
# @return [String, nil] the lowest `target_ruby` this Prism build accepts, or nil if none of the
|
|
442
|
+
# ladder parses. Memoised per process (the value is constant for a given Prism).
|
|
500
443
|
def self.prism_supported_floor
|
|
501
444
|
@prism_supported_floor ||= PRISM_VERSION_LADDER.find do |candidate|
|
|
502
445
|
Prism.parse("nil", version: candidate)
|
|
@@ -506,14 +449,11 @@ module Rigor
|
|
|
506
449
|
end
|
|
507
450
|
end
|
|
508
451
|
|
|
509
|
-
# `target_ruby` flows through to Prism's `version:` option.
|
|
510
|
-
#
|
|
511
|
-
#
|
|
512
|
-
#
|
|
513
|
-
#
|
|
514
|
-
# the first file — and name the supported floor + where to read
|
|
515
|
-
# the right value, so the fix is obvious without a guess-and-retry
|
|
516
|
-
# loop.
|
|
452
|
+
# `target_ruby` flows through to Prism's `version:` option. Prism enforces the supported range and
|
|
453
|
+
# raises `ArgumentError` for versions it does not recognise. Run a one-time smoke parse here so a
|
|
454
|
+
# misconfigured target_ruby surfaces as a single project-level diagnostic instead of crashing the
|
|
455
|
+
# whole run on the first file — and name the supported floor + where to read the right value, so the
|
|
456
|
+
# fix is obvious without a guess-and-retry loop.
|
|
517
457
|
def validate_target_ruby
|
|
518
458
|
Prism.parse("nil", version: @configuration.target_ruby)
|
|
519
459
|
nil
|
|
@@ -533,25 +473,19 @@ module Rigor
|
|
|
533
473
|
|
|
534
474
|
private
|
|
535
475
|
|
|
536
|
-
# Editor mode § "Scope choice — option A". Under
|
|
537
|
-
#
|
|
538
|
-
#
|
|
539
|
-
#
|
|
540
|
-
#
|
|
541
|
-
# contributes no per-file diagnostics. This is the v1 cut
|
|
542
|
-
# before a per-file diagnostic cache exists; option B (full
|
|
543
|
-
# project + incremental cache) is queued.
|
|
476
|
+
# Editor mode § "Scope choice — option A". Under `buffer:` non-nil the per-file analysis emits
|
|
477
|
+
# diagnostics ONLY for the buffer's logical path; the rest of `paths:` is consumed by the
|
|
478
|
+
# project-wide pre-passes (synthetic methods, project-patched methods, plugin facts) but contributes
|
|
479
|
+
# no per-file diagnostics. This is the v1 cut before a per-file diagnostic cache exists; option B
|
|
480
|
+
# (full project + incremental cache) is queued.
|
|
544
481
|
#
|
|
545
|
-
# The buffer's logical path is added to the file list even
|
|
546
|
-
#
|
|
547
|
-
#
|
|
548
|
-
# directory → treated as a valid logical identity for the
|
|
549
|
-
# buffer".
|
|
482
|
+
# The buffer's logical path is added to the file list even if it's not under `paths:` — per design §
|
|
483
|
+
# "Failure envelope": "--instead-of=Y with Y not under any paths: directory → treated as a valid
|
|
484
|
+
# logical identity for the buffer".
|
|
550
485
|
def target_files(expansion)
|
|
551
486
|
files = expansion.fetch(:files)
|
|
552
|
-
# ADR-46 slice 2 — restrict the analyzed set to the affected
|
|
553
|
-
#
|
|
554
|
-
# full file list) keeps the cross-file index complete. Buffer mode
|
|
487
|
+
# ADR-46 slice 2 — restrict the analyzed set to the affected closure while the pre-pass (run
|
|
488
|
+
# separately over `expansion`'s full file list) keeps the cross-file index complete. Buffer mode
|
|
555
489
|
# takes precedence — its single logical path is the analyzed set.
|
|
556
490
|
files = files.select { |path| @analyze_only.include?(path) } if @analyze_only
|
|
557
491
|
return files if @buffer.nil?
|
|
@@ -559,11 +493,9 @@ module Rigor
|
|
|
559
493
|
[@buffer.logical_path]
|
|
560
494
|
end
|
|
561
495
|
|
|
562
|
-
# Editor mode (`buffer:` non-nil) auto-flips the cache store
|
|
563
|
-
#
|
|
564
|
-
#
|
|
565
|
-
# race on schema-version writes. Cache reads continue
|
|
566
|
-
# unchanged; misses still run the producer block but the
|
|
496
|
+
# Editor mode (`buffer:` non-nil) auto-flips the cache store to `read_only: true` so multiple
|
|
497
|
+
# debounced editor invocations against the same project don't churn the on-disk cache or race on
|
|
498
|
+
# schema-version writes. Cache reads continue unchanged; misses still run the producer block but the
|
|
567
499
|
# result is not persisted. Per design doc § "Cache behaviour".
|
|
568
500
|
def enforce_read_only_cache(cache_store, buffer)
|
|
569
501
|
return cache_store if buffer.nil?
|
|
@@ -573,14 +505,11 @@ module Rigor
|
|
|
573
505
|
Cache::Store.new(root: cache_store.root, read_only: true)
|
|
574
506
|
end
|
|
575
507
|
|
|
576
|
-
# Wires the three responsibility collaborators. Called at the end
|
|
577
|
-
#
|
|
578
|
-
#
|
|
579
|
-
#
|
|
580
|
-
#
|
|
581
|
-
# observes the live ivar value at call time without a
|
|
582
|
-
# back-reference cycle. The reporter accumulators and the
|
|
583
|
-
# {RunSnapshots} sink are shared mutable instances.
|
|
508
|
+
# Wires the three responsibility collaborators. Called at the end of construction (after every state
|
|
509
|
+
# ivar is seeded). The per-run varying state (the plugin registry, dependency-source / scanner
|
|
510
|
+
# indexes, prepare-diagnostic snapshot, and the four end-of-pass snapshots) is reached through reader
|
|
511
|
+
# procs so each collaborator observes the live ivar value at call time without a back-reference
|
|
512
|
+
# cycle. The reporter accumulators and the {RunSnapshots} sink are shared mutable instances.
|
|
584
513
|
def build_collaborators # rubocop:disable Metrics/MethodLength
|
|
585
514
|
@pre_passes = ProjectPrePasses.new(
|
|
586
515
|
configuration: @configuration, cache_store: @cache_store, buffer: @buffer,
|
|
@@ -616,26 +545,20 @@ module Rigor
|
|
|
616
545
|
)
|
|
617
546
|
end
|
|
618
547
|
|
|
619
|
-
# ADR-15 Phase 4b — pool mode is enabled when `@workers > 0`.
|
|
620
|
-
#
|
|
621
|
-
#
|
|
622
|
-
# {PoolCoordinator}; the predicate stays on the runner because
|
|
623
|
-
# `run_result_cacheable?` consults it (and a spec exercises it
|
|
548
|
+
# ADR-15 Phase 4b — pool mode is enabled when `@workers > 0`. Editor mode (`buffer:` non-nil)
|
|
549
|
+
# silently overrides pool mode to sequential. The real decision lives on {PoolCoordinator}; the
|
|
550
|
+
# predicate stays on the runner because `run_result_cacheable?` consults it (and a spec exercises it
|
|
624
551
|
# via `send`).
|
|
625
552
|
def pool_mode?
|
|
626
553
|
@pool_coordinator.pool_mode?
|
|
627
554
|
end
|
|
628
555
|
|
|
629
|
-
# End-of-run telemetry. Walks the cached
|
|
630
|
-
#
|
|
631
|
-
# the
|
|
632
|
-
#
|
|
633
|
-
#
|
|
634
|
-
#
|
|
635
|
-
# Gem source-walk counts come from `dependency_source_index`
|
|
636
|
-
# which is already constructed regardless of pool mode.
|
|
637
|
-
# Wall + RSS are single syscalls; total cost is bounded
|
|
638
|
-
# by the snapshot size (~1000-2000 entries).
|
|
556
|
+
# End-of-run telemetry. Walks the cached `class_decl_paths` snapshot (sequential mode: from the
|
|
557
|
+
# coordinator's environment; pool mode: from the first worker's `:prepare` payload) and partitions
|
|
558
|
+
# the RBS class universe into "project sig/" (paths under `signature_paths`) vs "bundled" (everything
|
|
559
|
+
# else). Gem source-walk counts come from `dependency_source_index` which is already constructed
|
|
560
|
+
# regardless of pool mode. Wall + RSS are single syscalls; total cost is bounded by the snapshot size
|
|
561
|
+
# (~1000-2000 entries).
|
|
639
562
|
def build_run_stats(wall_started_at:, expansion:)
|
|
640
563
|
snapshot = @snapshots.class_decl_paths
|
|
641
564
|
project_sig, bundled = RunStats.partition_classes(
|
|
@@ -655,21 +578,15 @@ module Rigor
|
|
|
655
578
|
)
|
|
656
579
|
end
|
|
657
580
|
|
|
658
|
-
# ADR-7 § "Slice 5-A/5-B" — invokes every loaded plugin's
|
|
659
|
-
#
|
|
660
|
-
#
|
|
661
|
-
#
|
|
662
|
-
#
|
|
663
|
-
#
|
|
664
|
-
#
|
|
665
|
-
#
|
|
666
|
-
#
|
|
667
|
-
# `:plugin_loader` `runtime-error` diagnostic without
|
|
668
|
-
# affecting other plugins or the rest of the run.
|
|
669
|
-
# ADR-52 WD1 — only the plugins that overrode
|
|
670
|
-
# `#diagnostics_for_file` or declared a `node_rule` are visited
|
|
671
|
-
# (`contribution_index.for_file_diagnostics`); a skipped plugin's
|
|
672
|
-
# two hooks could only have returned `[]`.
|
|
581
|
+
# ADR-7 § "Slice 5-A/5-B" — invokes every loaded plugin's per-file diagnostic emission hook
|
|
582
|
+
# (`Plugin::Base#diagnostics_for_file`) and re-stamps the returned diagnostics with
|
|
583
|
+
# `source_family: "plugin.<manifest.id>"` so plugin authors cannot accidentally publish under
|
|
584
|
+
# another plugin's identifier or under `:builtin`. Plugin exceptions are isolated per ADR-2 § "Plugin
|
|
585
|
+
# Trust and I/O Policy" — a raise from one plugin becomes a `:plugin_loader` `runtime-error`
|
|
586
|
+
# diagnostic without affecting other plugins or the rest of the run.
|
|
587
|
+
# ADR-52 WD1 — only the plugins that overrode `#diagnostics_for_file` or declared a `node_rule` are
|
|
588
|
+
# visited (`contribution_index.for_file_diagnostics`); a skipped plugin's two hooks could only have
|
|
589
|
+
# returned `[]`.
|
|
673
590
|
def plugin_emitted_diagnostics(path, root, scope, node_results)
|
|
674
591
|
return [] if @plugin_registry.empty?
|
|
675
592
|
|
|
@@ -678,18 +595,15 @@ module Rigor
|
|
|
678
595
|
end
|
|
679
596
|
end
|
|
680
597
|
|
|
681
|
-
# ADR-52 WD4 + ADR-53 B4 — one engine-owned AST walk per file
|
|
682
|
-
#
|
|
683
|
-
#
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
# (byte-identical with the old per-plugin walk); the collectors are
|
|
687
|
-
# populated in place for `diagnose` to consume.
|
|
598
|
+
# ADR-52 WD4 + ADR-53 B4 — one engine-owned AST walk per file dispatches each node to every matching
|
|
599
|
+
# (plugin, rule) AND drives the built-in node collectors (`node_collectors`), so the file is walked
|
|
600
|
+
# once for both. The per-plugin results are bucketed in registry order so plugin emission stays
|
|
601
|
+
# plugin-major (byte-identical with the old per-plugin walk); the collectors are populated in place
|
|
602
|
+
# for `diagnose` to consume.
|
|
688
603
|
#
|
|
689
|
-
# When no plugin declares a node rule, the walk still runs to drive
|
|
690
|
-
#
|
|
691
|
-
#
|
|
692
|
-
# not need built-in collection from this walk.
|
|
604
|
+
# When no plugin declares a node rule, the walk still runs to drive the collectors (the converged
|
|
605
|
+
# path replaces the standalone `RuleWalk.run`); `node_collectors` nil means a caller that does not
|
|
606
|
+
# need built-in collection from this walk.
|
|
693
607
|
def node_rule_results_by_plugin(path, root, scope, node_collectors, scope_index)
|
|
694
608
|
walk = @plugin_registry.node_rule_walk
|
|
695
609
|
driver = node_collectors && CheckRules.node_collector_driver(node_collectors)
|
|
@@ -710,9 +624,8 @@ module Rigor
|
|
|
710
624
|
|
|
711
625
|
def collect_plugin_diagnostics(plugin, path, root, scope, node_result)
|
|
712
626
|
raw = Array(plugin.diagnostics_for_file(path: path, scope: scope, root: root))
|
|
713
|
-
# A node-rule context/rule raise isolates the whole plugin's
|
|
714
|
-
#
|
|
715
|
-
# rescue (which discarded `diagnostics_for_file` output too).
|
|
627
|
+
# A node-rule context/rule raise isolates the whole plugin's node-rule contribution, matching the
|
|
628
|
+
# old combined per-plugin rescue (which discarded `diagnostics_for_file` output too).
|
|
716
629
|
raise node_result.error if node_result&.error
|
|
717
630
|
|
|
718
631
|
raw += node_result.diagnostics if node_result
|
|
@@ -755,22 +668,20 @@ module Rigor
|
|
|
755
668
|
|
|
756
669
|
# Resolves the user-supplied path list into:
|
|
757
670
|
# - `:files` — the concrete `.rb` files to analyze.
|
|
758
|
-
# - `:errors` — `Diagnostic` entries for each path that
|
|
759
|
-
#
|
|
671
|
+
# - `:errors` — `Diagnostic` entries for each path that does not exist or is not a recognisable Ruby
|
|
672
|
+
# source.
|
|
760
673
|
#
|
|
761
|
-
# Surfacing path errors is a first-preview must-have:
|
|
762
|
-
#
|
|
763
|
-
# cleanly with no output, which silently masked typos.
|
|
674
|
+
# Surfacing path errors is a first-preview must-have: `rigor check ./does_not_exist.rb` previously
|
|
675
|
+
# exited cleanly with no output, which silently masked typos.
|
|
764
676
|
def expand_paths(paths)
|
|
765
677
|
files = []
|
|
766
678
|
bad = []
|
|
767
679
|
Array(paths).each do |path|
|
|
768
680
|
if File.directory?(path)
|
|
769
681
|
files.concat(reject_excluded(Dir.glob(File.join(path, RUBY_GLOB))))
|
|
770
|
-
# Editor-mode bypass: the buffer's logical path is treated
|
|
771
|
-
#
|
|
772
|
-
#
|
|
773
|
-
# path. Common case: LSP client editing a brand-new file.
|
|
682
|
+
# Editor-mode bypass: the buffer's logical path is treated as a real `.rb` file regardless of
|
|
683
|
+
# on-disk presence — `parse_source` reads bytes from the buffer's physical path. Common case: LSP
|
|
684
|
+
# client editing a brand-new file.
|
|
774
685
|
elsif accept_as_ruby_file?(path)
|
|
775
686
|
files << path
|
|
776
687
|
elsif File.exist?(path)
|
|
@@ -782,13 +693,11 @@ module Rigor
|
|
|
782
693
|
{ files: files, errors: path_expansion_errors(bad, any_files: files.any?) }
|
|
783
694
|
end
|
|
784
695
|
|
|
785
|
-
# A bad path *among valid ones* is a warn-and-skip — the run still
|
|
786
|
-
#
|
|
787
|
-
#
|
|
788
|
-
#
|
|
789
|
-
#
|
|
790
|
-
# silently masked (the regression the path-error check was added
|
|
791
|
-
# to catch in the first place).
|
|
696
|
+
# A bad path *among valid ones* is a warn-and-skip — the run still does useful work, so `rigor check
|
|
697
|
+
# app lib` with no `lib/` (the 20260620 field trial's strap case) analyses `app` instead of aborting
|
|
698
|
+
# on exit 1. A bad path that leaves NOTHING to analyse stays an error so a lone typo (`rigor check
|
|
699
|
+
# typo.rb`) is not silently masked (the regression the path-error check was added to catch in the
|
|
700
|
+
# first place).
|
|
792
701
|
def path_expansion_errors(bad, any_files:)
|
|
793
702
|
severity = any_files ? :warning : :error
|
|
794
703
|
suffix = any_files ? " (skipped)" : ""
|
|
@@ -801,15 +710,12 @@ module Rigor
|
|
|
801
710
|
@in_memory_sources&.key?(path)
|
|
802
711
|
end
|
|
803
712
|
|
|
804
|
-
# `Configuration#exclude_patterns` is a list of glob patterns
|
|
805
|
-
#
|
|
806
|
-
#
|
|
807
|
-
# the
|
|
808
|
-
#
|
|
809
|
-
#
|
|
810
|
-
# artefacts. User-supplied entries (`.rigor.yml` `exclude:`)
|
|
811
|
-
# layer on top. Explicit file arguments to the CLI bypass this
|
|
812
|
-
# filter — only the directory-glob expansion is filtered.
|
|
713
|
+
# `Configuration#exclude_patterns` is a list of glob patterns checked against each globbed path via
|
|
714
|
+
# `File.fnmatch?` (without `FNM_PATHNAME`, so `**` and `*` both span path separators — the patterns
|
|
715
|
+
# behave like substring globs). Built-in defaults exclude `vendor/bundle`, `.bundle`, `node_modules`,
|
|
716
|
+
# and `tmp` so the analyser never walks into vendored deps or build artefacts. User-supplied entries
|
|
717
|
+
# (`.rigor.yml` `exclude:`) layer on top. Explicit file arguments to the CLI bypass this filter — only
|
|
718
|
+
# the directory-glob expansion is filtered.
|
|
813
719
|
def reject_excluded(file_list)
|
|
814
720
|
return file_list if @configuration.exclude_patterns.empty?
|
|
815
721
|
|
|
@@ -830,11 +736,9 @@ module Rigor
|
|
|
830
736
|
)
|
|
831
737
|
end
|
|
832
738
|
|
|
833
|
-
# Reads + parses the source at `path`. Under editor mode
|
|
834
|
-
#
|
|
835
|
-
#
|
|
836
|
-
# `filepath: path` so Prism's location data carries the
|
|
837
|
-
# LOGICAL path. Non-binding paths go through the cheaper
|
|
739
|
+
# Reads + parses the source at `path`. Under editor mode (`@buffer` set) reads bytes from
|
|
740
|
+
# `@buffer.physical_path` when `path` matches the logical binding, then parses with `filepath: path`
|
|
741
|
+
# so Prism's location data carries the LOGICAL path. Non-binding paths go through the cheaper
|
|
838
742
|
# `Prism.parse_file` codepath unchanged.
|
|
839
743
|
def parse_source(path)
|
|
840
744
|
if @in_memory_sources&.key?(path)
|
|
@@ -847,12 +751,9 @@ module Rigor
|
|
|
847
751
|
Prism.parse(File.read(physical), filepath: path, version: @configuration.target_ruby)
|
|
848
752
|
end
|
|
849
753
|
|
|
850
|
-
# Seeds the cross-file project pre-pass indexes onto a
|
|
851
|
-
#
|
|
852
|
-
#
|
|
853
|
-
# applied only when non-empty so a runner constructed
|
|
854
|
-
# without the project pre-pass (e.g. a single-file probe)
|
|
855
|
-
# keeps an empty seed.
|
|
754
|
+
# Seeds the cross-file project pre-pass indexes onto a fresh per-file scope: discovered classes, and
|
|
755
|
+
# the ADR-24 def-node / superclass / included-module maps. Each is applied only when non-empty so a
|
|
756
|
+
# runner constructed without the project pre-pass (e.g. a single-file probe) keeps an empty seed.
|
|
856
757
|
def seed_project_scope(scope)
|
|
857
758
|
tables = project_scope_seed_tables
|
|
858
759
|
return scope if tables.empty?
|
|
@@ -860,9 +761,8 @@ module Rigor
|
|
|
860
761
|
scope.with_discovery(scope.discovery.with(**tables))
|
|
861
762
|
end
|
|
862
763
|
|
|
863
|
-
# The cross-file pre-pass tables {#seed_project_scope} applies, as a
|
|
864
|
-
#
|
|
865
|
-
# {WorkerSession} (whose per-file scopes would otherwise miss every
|
|
764
|
+
# The cross-file pre-pass tables {#seed_project_scope} applies, as a plain Hash so the fork-pool path
|
|
765
|
+
# can hand the same seed to its {WorkerSession} (whose per-file scopes would otherwise miss every
|
|
866
766
|
# cross-file def — ADR-15 sequential-equivalence contract).
|
|
867
767
|
def project_scope_seed_tables
|
|
868
768
|
tables = {}
|
|
@@ -881,18 +781,16 @@ module Rigor
|
|
|
881
781
|
end
|
|
882
782
|
tables[:discovered_methods] = @project_discovered_methods unless @project_discovered_methods.empty?
|
|
883
783
|
seed_member_layout_tables(tables)
|
|
884
|
-
# ADR-46 slice 1 — the class-declaration source map is read only by
|
|
885
|
-
#
|
|
886
|
-
# only when recording is on; a normal run never carries it.
|
|
784
|
+
# ADR-46 slice 1 — the class-declaration source map is read only by the ancestry accessors during
|
|
785
|
+
# dependency recording, so seed it only when recording is on; a normal run never carries it.
|
|
887
786
|
if @record_dependencies && !@project_discovered_class_sources.empty?
|
|
888
787
|
tables[:discovered_class_sources] = @project_discovered_class_sources
|
|
889
788
|
end
|
|
890
789
|
tables
|
|
891
790
|
end
|
|
892
791
|
|
|
893
|
-
# ADR-48 — seed the Data + Struct member-layout tables (each only when
|
|
894
|
-
#
|
|
895
|
-
# complexity budget.
|
|
792
|
+
# ADR-48 — seed the Data + Struct member-layout tables (each only when non-empty). Extracted to keep
|
|
793
|
+
# {#project_scope_seed_tables} under the complexity budget.
|
|
896
794
|
def seed_member_layout_tables(tables)
|
|
897
795
|
tables[:data_member_layouts] = @project_data_member_layouts unless @project_data_member_layouts.empty?
|
|
898
796
|
return if @project_struct_member_layouts.empty?
|
|
@@ -900,12 +798,11 @@ module Rigor
|
|
|
900
798
|
tables[:struct_member_layouts] = @project_struct_member_layouts
|
|
901
799
|
end
|
|
902
800
|
|
|
903
|
-
# ADR-46 slice 1 — when dependency recording is enabled, wrap the
|
|
904
|
-
#
|
|
905
|
-
#
|
|
906
|
-
#
|
|
907
|
-
#
|
|
908
|
-
# is observational, so diagnostics are byte-identical either way.
|
|
801
|
+
# ADR-46 slice 1 — when dependency recording is enabled, wrap the per-file analysis so the cross-file
|
|
802
|
+
# reads its inference makes are captured into `file_dependencies[path]`. Off by default: a normal run
|
|
803
|
+
# calls the body directly and the instrumented `Scope` accessors short-circuit on
|
|
804
|
+
# `DependencyRecorder.active? == false`. Recording is observational, so diagnostics are byte-identical
|
|
805
|
+
# either way.
|
|
909
806
|
def analyze_file(path, environment)
|
|
910
807
|
return analyze_file_body(path, environment) unless @record_dependencies
|
|
911
808
|
|
|
@@ -926,10 +823,9 @@ module Rigor
|
|
|
926
823
|
end
|
|
927
824
|
|
|
928
825
|
scope = seed_project_scope(Scope.empty(environment: environment, source_path: path))
|
|
929
|
-
# ADR-24 slice 4a/4 — record unresolved implicit-self calls during the
|
|
930
|
-
#
|
|
931
|
-
#
|
|
932
|
-
# `call.self-undefined-method` collector; the recorder is inert unless
|
|
826
|
+
# ADR-24 slice 4a/4 — record unresolved implicit-self calls during the typing pass ONLY (not
|
|
827
|
+
# CheckRules, whose own `type_of` queries would otherwise re-trigger the choke-point).
|
|
828
|
+
# `self_call_misses` feeds the `call.self-undefined-method` collector; the recorder is inert unless
|
|
933
829
|
# the rule is active or `record_self_calls:` opted in.
|
|
934
830
|
index = nil
|
|
935
831
|
self_call_record = with_self_call_recording(path) do
|
|
@@ -960,12 +856,10 @@ module Rigor
|
|
|
960
856
|
]
|
|
961
857
|
end
|
|
962
858
|
|
|
963
|
-
# ADR-53 B4 — the built-in node collectors and the plugin node rules
|
|
964
|
-
#
|
|
965
|
-
#
|
|
966
|
-
#
|
|
967
|
-
# the built-in `diagnose` output and the plugin diagnostics are then
|
|
968
|
-
# built from that single walk's results.
|
|
859
|
+
# ADR-53 B4 — the built-in node collectors and the plugin node rules share ONE traversal of the file.
|
|
860
|
+
# The collectors are built here (they need the completed `index`) and populated by the converged
|
|
861
|
+
# plugin walk; `node_results` carries the per-plugin node-rule output. Both the built-in `diagnose`
|
|
862
|
+
# output and the plugin diagnostics are then built from that single walk's results.
|
|
969
863
|
def rule_and_plugin_diagnostics(path, parse_result, scope, index, self_call_misses)
|
|
970
864
|
root = parse_result.value
|
|
971
865
|
node_collectors = CheckRules.build_node_collectors(path, index)
|
|
@@ -982,11 +876,10 @@ module Rigor
|
|
|
982
876
|
diagnostics + plugin_emitted_diagnostics(path, root, scope, node_results)
|
|
983
877
|
end
|
|
984
878
|
|
|
985
|
-
# ADR-24 slice 4a — runs `block` (the typing pass) with the self-call
|
|
986
|
-
#
|
|
987
|
-
#
|
|
988
|
-
#
|
|
989
|
-
# nil when recording is inactive (the common path — one integer read).
|
|
879
|
+
# ADR-24 slice 4a — runs `block` (the typing pass) with the self-call recorder active when either
|
|
880
|
+
# the test-only `record_self_calls:` flag is set or the `call.self-undefined-method` rule resolves to
|
|
881
|
+
# a firing severity. Returns the frozen {SelfCallResolutionRecorder::Record}, or nil when recording is
|
|
882
|
+
# inactive (the common path — one integer read).
|
|
990
883
|
def with_self_call_recording(path, &)
|
|
991
884
|
unless self_call_recording_active?
|
|
992
885
|
yield
|
|
@@ -1002,11 +895,9 @@ module Rigor
|
|
|
1002
895
|
@record_self_calls || self_undefined_rule_active?
|
|
1003
896
|
end
|
|
1004
897
|
|
|
1005
|
-
# Memoised: the rule fires only when its resolved severity is not `:off`
|
|
1006
|
-
#
|
|
1007
|
-
#
|
|
1008
|
-
# corpus FP gate — see ADR-24 § "Slice 4"); a project opts in via
|
|
1009
|
-
# `severity_overrides:`.
|
|
898
|
+
# Memoised: the rule fires only when its resolved severity is not `:off` and it is not in `disable:`.
|
|
899
|
+
# Default profiles map it to `:off`, so a normal run never activates the recorder (pending the
|
|
900
|
+
# external WD4 corpus FP gate — see ADR-24 § "Slice 4"); a project opts in via `severity_overrides:`.
|
|
1010
901
|
def self_undefined_rule_active?
|
|
1011
902
|
return @self_undefined_rule_active unless @self_undefined_rule_active.nil?
|
|
1012
903
|
|
|
@@ -1023,15 +914,11 @@ module Rigor
|
|
|
1023
914
|
end
|
|
1024
915
|
end
|
|
1025
916
|
|
|
1026
|
-
# v0.0.2 #10 — fail-soft fallback explanation. When
|
|
1027
|
-
#
|
|
1028
|
-
#
|
|
1029
|
-
#
|
|
1030
|
-
#
|
|
1031
|
-
# to. The CoverageScanner is the canonical "first-event-
|
|
1032
|
-
# per-node" probe: it already filters out pass-through
|
|
1033
|
-
# wrappers (`ProgramNode`, `StatementsNode`,
|
|
1034
|
-
# `ParenthesesNode`) so the explain stream is attributable
|
|
917
|
+
# v0.0.2 #10 — fail-soft fallback explanation. When `--explain` is set the runner additionally walks
|
|
918
|
+
# the file with `Rigor::Inference::CoverageScanner` and emits one `:info` diagnostic per
|
|
919
|
+
# directly-unrecognized node, naming the node class and the type the engine fell back to. The
|
|
920
|
+
# CoverageScanner is the canonical "first-event-per-node" probe: it already filters out pass-through
|
|
921
|
+
# wrappers (`ProgramNode`, `StatementsNode`, `ParenthesesNode`) so the explain stream is attributable
|
|
1035
922
|
# to the leaf node that actually triggered the fallback.
|
|
1036
923
|
def explain_diagnostics(path, root, scope)
|
|
1037
924
|
return [] unless @explain
|