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
|
@@ -5,71 +5,56 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Analysis
|
|
7
7
|
module CheckRules
|
|
8
|
-
# Walks a parse tree and collects every `LocalVariableWriteNode`
|
|
9
|
-
#
|
|
10
|
-
# within the same body.
|
|
8
|
+
# Walks a parse tree and collects every `LocalVariableWriteNode` inside a `DefNode` body whose target
|
|
9
|
+
# name is **never read** within the same body.
|
|
11
10
|
#
|
|
12
|
-
# The collector is the read-side companion to the
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# leftover shape ("wrote and never used") without surfacing
|
|
16
|
-
# false positives:
|
|
11
|
+
# The collector is the read-side companion to the `def.dead-assignment` rule. v0.1.2 ships the
|
|
12
|
+
# narrowest envelope that catches the most common typo / refactoring-leftover shape ("wrote and never
|
|
13
|
+
# used") without surfacing false positives:
|
|
17
14
|
#
|
|
18
|
-
# - Only `Prism::DefNode` bodies are scanned. Top-level
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# -
|
|
28
|
-
# any `LocalVariableReadNode` anywhere in the def
|
|
29
|
-
# subtree — so a closure capture, a `return x`, a
|
|
30
|
-
# block-call argument, an interpolated string all count
|
|
31
|
-
# as a read.
|
|
32
|
-
# - A write whose name starts with `_` is skipped per the
|
|
33
|
-
# Ruby convention that `_` / `_foo` declares
|
|
15
|
+
# - Only `Prism::DefNode` bodies are scanned. Top-level scripts and class-body assignments are
|
|
16
|
+
# skipped — their variable surface bleeds across requires / introspection in ways the rule cannot
|
|
17
|
+
# reason about.
|
|
18
|
+
# - Only plain `LocalVariableWriteNode` writes are considered. Operator-writes (`x += 1`), and-/or-writes
|
|
19
|
+
# (`x ||= 1`), and `MultiWriteNode` destructures (`a, b = foo`) are skipped because their write
|
|
20
|
+
# semantics are intertwined with reads or with a wider tuple binding.
|
|
21
|
+
# - The "is this name ever read" question is answered by any `LocalVariableReadNode` anywhere in the
|
|
22
|
+
# def subtree — so a closure capture, a `return x`, a block-call argument, an interpolated string all
|
|
23
|
+
# count as a read.
|
|
24
|
+
# - A write whose name starts with `_` is skipped per the Ruby convention that `_` / `_foo` declares
|
|
34
25
|
# "intentionally unused".
|
|
35
|
-
# - The last statement of a def body is skipped — Ruby's
|
|
36
|
-
#
|
|
37
|
-
# returning `1`, so the trailing write is intentional.
|
|
26
|
+
# - The last statement of a def body is skipped — Ruby's implicit return treats `def foo; x = 1; end`
|
|
27
|
+
# as returning `1`, so the trailing write is intentional.
|
|
38
28
|
class DeadAssignmentCollector
|
|
39
|
-
# ADR-53 Track B — the node classes the shared {RuleWalk}
|
|
40
|
-
#
|
|
41
|
-
# `
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# collector needs no context gate: it fires at every `def` the
|
|
45
|
-
# shared full DFS reaches, exactly like the legacy walk.
|
|
29
|
+
# ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector. The legacy
|
|
30
|
+
# walk visits EVERY `DefNode` (it descends into all of them, nested defs included, processing each
|
|
31
|
+
# separately — `gather_*` barrier at nested defs so an outer def never sees an inner def's locals),
|
|
32
|
+
# so the collector needs no context gate: it fires at every `def` the shared full DFS reaches,
|
|
33
|
+
# exactly like the legacy walk.
|
|
46
34
|
NODE_CLASSES = [Prism::DefNode].freeze
|
|
47
35
|
|
|
48
|
-
# Returns `Array<{def_class:, def_name:, write_node:}>`
|
|
49
|
-
#
|
|
50
|
-
# the tree has no qualifying writes.
|
|
36
|
+
# Returns `Array<{def_class:, def_name:, write_node:}>` — one entry per dead-assignment write. Empty
|
|
37
|
+
# when the tree has no qualifying writes.
|
|
51
38
|
def initialize(scope_index)
|
|
52
39
|
@scope_index = scope_index
|
|
53
40
|
@results = []
|
|
54
41
|
end
|
|
55
42
|
|
|
56
|
-
# Legacy single-collector walk — kept as the oracle the ADR-53
|
|
57
|
-
#
|
|
58
|
-
# when Track B completes.
|
|
43
|
+
# Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
|
|
44
|
+
# {RuleWalk} against; deleted when Track B completes.
|
|
59
45
|
def collect(root)
|
|
60
46
|
walk_for_def_nodes(root)
|
|
61
47
|
@results.freeze
|
|
62
48
|
end
|
|
63
49
|
|
|
64
|
-
# {RuleWalk} entry point: the legacy walk's per-`def` logic,
|
|
65
|
-
#
|
|
66
|
-
# `context` is unused — this collector processes all defs.
|
|
50
|
+
# {RuleWalk} entry point: the legacy walk's per-`def` logic, invoked at every `def` under the shared
|
|
51
|
+
# traversal contract. The `context` is unused — this collector processes all defs.
|
|
67
52
|
def visit(def_node, _context = nil)
|
|
68
53
|
collect_def_assignments(def_node)
|
|
69
54
|
end
|
|
70
55
|
|
|
71
|
-
# The accumulated result, frozen the same way `#collect` returns
|
|
72
|
-
#
|
|
56
|
+
# The accumulated result, frozen the same way `#collect` returns it — used by {RuleWalk}-driven
|
|
57
|
+
# callers after the walk completes.
|
|
73
58
|
def results
|
|
74
59
|
@results.freeze
|
|
75
60
|
end
|
|
@@ -103,9 +88,8 @@ module Rigor
|
|
|
103
88
|
return accumulator unless node.is_a?(Prism::Node)
|
|
104
89
|
|
|
105
90
|
accumulator << node.name if node.is_a?(Prism::LocalVariableReadNode)
|
|
106
|
-
# Operator/and/or-writes implicitly read the prior
|
|
107
|
-
#
|
|
108
|
-
# similar shapes don't trip the rule.
|
|
91
|
+
# Operator/and/or-writes implicitly read the prior binding — count them too so `x = 1; x ||= 2; x`
|
|
92
|
+
# / similar shapes don't trip the rule.
|
|
109
93
|
accumulator << node.name if reading_assignment?(node)
|
|
110
94
|
|
|
111
95
|
node.compact_child_nodes.each { |child| gather_read_names(child, accumulator) }
|
|
@@ -122,8 +106,7 @@ module Rigor
|
|
|
122
106
|
return accumulator unless node.is_a?(Prism::Node)
|
|
123
107
|
|
|
124
108
|
accumulator << node if node.is_a?(Prism::LocalVariableWriteNode)
|
|
125
|
-
# Don't recurse into nested DefNodes — their bodies
|
|
126
|
-
# carry their own dead-assignment scope and the
|
|
109
|
+
# Don't recurse into nested DefNodes — their bodies carry their own dead-assignment scope and the
|
|
127
110
|
# outer walker visits them separately.
|
|
128
111
|
return accumulator if node.is_a?(Prism::DefNode) && !accumulator.last.equal?(node)
|
|
129
112
|
|
|
@@ -131,10 +114,8 @@ module Rigor
|
|
|
131
114
|
accumulator
|
|
132
115
|
end
|
|
133
116
|
|
|
134
|
-
# Returns the final statement of a body node, descending
|
|
135
|
-
#
|
|
136
|
-
# blocks). Used to skip the implicit-return write at the
|
|
137
|
-
# tail of a method body.
|
|
117
|
+
# Returns the final statement of a body node, descending into wrappers Ruby preserves verbatim
|
|
118
|
+
# (`begin ... end` blocks). Used to skip the implicit-return write at the tail of a method body.
|
|
138
119
|
def trailing_statement(body)
|
|
139
120
|
case body
|
|
140
121
|
when Prism::StatementsNode then body.body.last
|
|
@@ -7,70 +7,56 @@ require_relative "../../source/constant_path"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Analysis
|
|
9
9
|
module CheckRules
|
|
10
|
-
# Walks a parse tree and collects every
|
|
11
|
-
# `Prism::
|
|
12
|
-
# method body of a `Prism::ClassNode` / `Prism::ModuleNode`,
|
|
13
|
-
# grouped by (class qualified name, ivar name).
|
|
10
|
+
# Walks a parse tree and collects every `Prism::InstanceVariableWriteNode` inside an instance method
|
|
11
|
+
# body of a `Prism::ClassNode` / `Prism::ModuleNode`, grouped by (class qualified name, ivar name).
|
|
14
12
|
#
|
|
15
|
-
# The collector is the read-side companion to
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# carrier; this collector preserves the per-write list so
|
|
19
|
-
# the `def.ivar-write-mismatch` rule can compare each
|
|
20
|
-
# write's concrete class against the first.
|
|
13
|
+
# The collector is the read-side companion to `Inference::ScopeIndexer#build_class_ivar_index`. The
|
|
14
|
+
# indexer unions the rvalue types into a single per-ivar carrier; this collector preserves the per-write
|
|
15
|
+
# list so the `def.ivar-write-mismatch` rule can compare each write's concrete class against the first.
|
|
21
16
|
#
|
|
22
17
|
# Skipped on purpose:
|
|
23
18
|
#
|
|
24
|
-
# - Singleton-method bodies (`def self.foo`). Their ivars
|
|
25
|
-
#
|
|
26
|
-
# - Class-body ivar writes outside any def — the
|
|
27
|
-
# `Module#@var` surface is a separate slice the engine
|
|
19
|
+
# - Singleton-method bodies (`def self.foo`). Their ivars live on the class object, not on instances.
|
|
20
|
+
# - Class-body ivar writes outside any def — the `Module#@var` surface is a separate slice the engine
|
|
28
21
|
# doesn't yet model.
|
|
29
|
-
# - Nested classes / modules / defs inside a method body
|
|
30
|
-
# are barriers, mirroring the indexer's
|
|
22
|
+
# - Nested classes / modules / defs inside a method body are barriers, mirroring the indexer's
|
|
31
23
|
# `IVAR_BARRIER_NODES` policy.
|
|
32
24
|
class IvarWriteCollector
|
|
33
25
|
BARRIER_NODES = [Prism::DefNode, Prism::ClassNode, Prism::ModuleNode].freeze
|
|
34
26
|
private_constant :BARRIER_NODES
|
|
35
27
|
|
|
36
|
-
# ADR-53 Track B — the node classes the shared {RuleWalk}
|
|
37
|
-
#
|
|
38
|
-
# the
|
|
39
|
-
# class / module
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# the shared full DFS that prune becomes the `:inside_def` gate;
|
|
43
|
-
# the enclosing class / module name stack the legacy walk threaded
|
|
44
|
-
# as `qualified_prefix` is now `context.qualified_prefix`.
|
|
28
|
+
# ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector, and the
|
|
29
|
+
# context gate under which the walk suppresses it. The legacy walk descends only through class /
|
|
30
|
+
# module bodies and `return`s at the first `def` it meets, so it collects ivar writes only from a
|
|
31
|
+
# `def` that sits directly in a class / module body, never one nested in another `def`. On the
|
|
32
|
+
# shared full DFS that prune becomes the `:inside_def` gate; the enclosing class / module name stack
|
|
33
|
+
# the legacy walk threaded as `qualified_prefix` is now `context.qualified_prefix`.
|
|
45
34
|
NODE_CLASSES = [Prism::DefNode].freeze
|
|
46
35
|
RULE_WALK_GATES = [:inside_def].freeze
|
|
47
36
|
|
|
48
|
-
# Returns `Hash[class_name (String) => Hash[ivar_name
|
|
49
|
-
#
|
|
50
|
-
# tree has no qualifying writes.
|
|
37
|
+
# Returns `Hash[class_name (String) => Hash[ivar_name (Symbol) => Array<{node:, type:}>]]`. Empty
|
|
38
|
+
# when the tree has no qualifying writes.
|
|
51
39
|
def initialize(scope_index)
|
|
52
40
|
@scope_index = scope_index
|
|
53
41
|
@accumulator = {}
|
|
54
42
|
end
|
|
55
43
|
|
|
56
|
-
# Legacy single-collector walk — kept as the oracle the ADR-53
|
|
57
|
-
#
|
|
58
|
-
# when Track B completes.
|
|
44
|
+
# Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
|
|
45
|
+
# {RuleWalk} against; deleted when Track B completes.
|
|
59
46
|
def collect(root)
|
|
60
47
|
walk(root, [])
|
|
61
48
|
@accumulator.transform_values(&:freeze).freeze
|
|
62
49
|
end
|
|
63
50
|
|
|
64
|
-
# {RuleWalk} entry point: the legacy walk's per-`def` logic,
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# enclosing class prefix from `context.qualified_prefix`.
|
|
51
|
+
# {RuleWalk} entry point: the legacy walk's per-`def` logic, invoked at each class-/module-body `def`
|
|
52
|
+
# under the shared traversal contract. `collect_def_writes`' verbatim body reads the enclosing class
|
|
53
|
+
# prefix from `context.qualified_prefix`.
|
|
68
54
|
def visit(def_node, context)
|
|
69
55
|
collect_def_writes(def_node, context.qualified_prefix)
|
|
70
56
|
end
|
|
71
57
|
|
|
72
|
-
# The accumulated result, frozen the same way `#collect` returns
|
|
73
|
-
#
|
|
58
|
+
# The accumulated result, frozen the same way `#collect` returns it — used by {RuleWalk}-driven
|
|
59
|
+
# callers after the walk completes.
|
|
74
60
|
def results
|
|
75
61
|
@accumulator.transform_values(&:freeze).freeze
|
|
76
62
|
end
|
|
@@ -5,42 +5,35 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Analysis
|
|
7
7
|
module CheckRules
|
|
8
|
-
# ADR-53 Track B (slice B3c) — hosts the main per-node check-rule
|
|
9
|
-
#
|
|
10
|
-
# `Source::NodeWalker.each` traversal.
|
|
8
|
+
# ADR-53 Track B (slice B3c) — hosts the main per-node check-rule pass on the shared {RuleWalk} instead of
|
|
9
|
+
# its own `Source::NodeWalker.each` traversal.
|
|
11
10
|
#
|
|
12
|
-
# The main pass is the stateless half of the catalogue: each rule
|
|
13
|
-
#
|
|
14
|
-
# suppression and NO threaded context — so the collector declares no
|
|
11
|
+
# The main pass is the stateless half of the catalogue: each rule reads only `scope_index[node]` and
|
|
12
|
+
# decides, with NO loop / block suppression and NO threaded context — so the collector declares no
|
|
15
13
|
# `RULE_WALK_GATES` and ignores the walk's `context`.
|
|
16
14
|
#
|
|
17
|
-
# The per-node dispatch itself stays in `CheckRules` (the verbatim
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# its `#results` are the accumulated {Diagnostic}s, in the same
|
|
24
|
-
# emission order the inline `NodeWalker.each` produced them (the
|
|
25
|
-
# shared walk is the same visit-before-descend DFS over
|
|
26
|
-
# `compact_child_nodes`).
|
|
15
|
+
# The per-node dispatch itself stays in `CheckRules` (the verbatim `case` from `diagnose`'s former inline
|
|
16
|
+
# walk — only the traversal moved, ADR-53 WD4) and is handed in as a callable built in the `CheckRules`
|
|
17
|
+
# module context, so its calls to the private diagnostic builders remain implicit-self. The collector
|
|
18
|
+
# only owns the traversal hook and accumulation. Unlike the fact collectors its `#results` are the
|
|
19
|
+
# accumulated {Diagnostic}s, in the same emission order the inline `NodeWalker.each` produced them (the
|
|
20
|
+
# shared walk is the same visit-before-descend DFS over `compact_child_nodes`).
|
|
27
21
|
class MainPassCollector
|
|
28
|
-
# The node classes the former inline pass branched on. A plain
|
|
29
|
-
# `
|
|
30
|
-
# legacy `when Prism::IfNode, Prism::UnlessNode` arm did the same).
|
|
22
|
+
# The node classes the former inline pass branched on. A plain `Prism::IfNode` covers ternaries and
|
|
23
|
+
# postfix `if` too (the legacy `when Prism::IfNode, Prism::UnlessNode` arm did the same).
|
|
31
24
|
NODE_CLASSES = [
|
|
32
25
|
Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode
|
|
33
26
|
].freeze
|
|
34
27
|
|
|
35
|
-
# @param node_diagnostics [#call] maps a `Prism::Node` to the
|
|
36
|
-
#
|
|
28
|
+
# @param node_diagnostics [#call] maps a `Prism::Node` to the array of diagnostics the main pass emits
|
|
29
|
+
# for it.
|
|
37
30
|
def initialize(node_diagnostics)
|
|
38
31
|
@node_diagnostics = node_diagnostics
|
|
39
32
|
@diagnostics = []
|
|
40
33
|
end
|
|
41
34
|
|
|
42
|
-
# {RuleWalk} entry point: the per-node logic of the former inline
|
|
43
|
-
#
|
|
35
|
+
# {RuleWalk} entry point: the per-node logic of the former inline `NodeWalker.each` `case`, invoked
|
|
36
|
+
# under the shared traversal.
|
|
44
37
|
def visit(node, _context = nil)
|
|
45
38
|
@diagnostics.concat(@node_diagnostics.call(node))
|
|
46
39
|
end
|
|
@@ -7,42 +7,31 @@ require_relative "../../source/constant_path"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Analysis
|
|
9
9
|
module CheckRules
|
|
10
|
-
# ADR-53 Track B — the engine-owned AST walk that hosts CheckRules'
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# the built-in rules; this walk and {Plugin::NodeRuleWalk} converge
|
|
14
|
-
# in slice B4).
|
|
10
|
+
# ADR-53 Track B — the engine-owned AST walk that hosts CheckRules' per-node collectors, so a file is
|
|
11
|
+
# traversed once for all of them instead of once per collector (the ADR-52 WD4 model applied to the
|
|
12
|
+
# built-in rules; this walk and {Plugin::NodeRuleWalk} converge in slice B4).
|
|
15
13
|
#
|
|
16
|
-
# The walk is a single visit-before-descend DFS over
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# tracked some subset of it — in one immutable {Context} object,
|
|
20
|
-
# recomputed once per node during the single descent:
|
|
14
|
+
# The walk is a single visit-before-descend DFS over `compact_child_nodes`. It threads the union of the
|
|
15
|
+
# context the hosted collectors need — the per-collector hand-rolled walks each tracked some subset of
|
|
16
|
+
# it — in one immutable {Context} object, recomputed once per node during the single descent:
|
|
21
17
|
#
|
|
22
|
-
# - `in_loop_or_block` — whether the node is inside a loop / block
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# ({IvarWriteCollector} collects only at a `def` that sits
|
|
29
|
-
# directly in a class / module body, never one nested in another
|
|
30
|
-
# `def`).
|
|
18
|
+
# - `in_loop_or_block` — whether the node is inside a loop / block body (the two flow collectors
|
|
19
|
+
# suppress collection there because mutation tracking through those is incomplete).
|
|
20
|
+
# - `qualified_prefix` — the enclosing class / module name stack ({IvarWriteCollector} builds its
|
|
21
|
+
# `class_name` from it).
|
|
22
|
+
# - `inside_def` — whether the node is lexically inside a `DefNode` ({IvarWriteCollector} collects only
|
|
23
|
+
# at a `def` that sits directly in a class / module body, never one nested in another `def`).
|
|
31
24
|
#
|
|
32
|
-
# A collector joins the walk by declaring `NODE_CLASSES` (the node
|
|
33
|
-
# classes
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# *traversal* merges here; each collector's gather / filter logic is
|
|
39
|
-
# unchanged from its legacy `#collect` walk (ADR-53 WD4).
|
|
25
|
+
# A collector joins the walk by declaring `NODE_CLASSES` (the node classes it cares about — Prism node
|
|
26
|
+
# classes are leaves, so a class-equality dispatch matches the collectors' `is_a?` gates), optionally
|
|
27
|
+
# `RULE_WALK_GATES` (the {Context}-derived suppressions the walk applies before calling it), and
|
|
28
|
+
# `#visit(node, context)` with its per-node logic transplanted verbatim. Only the *traversal* merges
|
|
29
|
+
# here; each collector's gather / filter logic is unchanged from its legacy `#collect` walk (ADR-53
|
|
30
|
+
# WD4).
|
|
40
31
|
#
|
|
41
|
-
# Equivalence is load-bearing: every hosted collector keeps its
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# and, over whole corpora, by `RIGOR_SHADOW_RULE_WALK=1`
|
|
45
|
-
# (see `CheckRules.run_node_collectors`).
|
|
32
|
+
# Equivalence is load-bearing: every hosted collector keeps its legacy single-collector `#collect` walk
|
|
33
|
+
# as the oracle, compared against this walk by the permanent `rule_walk_equivalence_spec` and, over
|
|
34
|
+
# whole corpora, by `RIGOR_SHADOW_RULE_WALK=1` (see `CheckRules.run_node_collectors`).
|
|
46
35
|
module RuleWalk
|
|
47
36
|
LOOP_OR_BLOCK_NODE_CLASSES = [
|
|
48
37
|
Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::BlockNode
|
|
@@ -50,51 +39,44 @@ module Rigor
|
|
|
50
39
|
|
|
51
40
|
CLASS_OR_MODULE_NODE_CLASSES = [Prism::ClassNode, Prism::ModuleNode].freeze
|
|
52
41
|
|
|
53
|
-
# The immutable per-node descent context. Recomputed for each
|
|
54
|
-
#
|
|
55
|
-
# read only the fields they declared a need for.
|
|
42
|
+
# The immutable per-node descent context. Recomputed for each child from its parent's context as the
|
|
43
|
+
# walk descends; collectors read only the fields they declared a need for.
|
|
56
44
|
Context = Data.define(:in_loop_or_block, :qualified_prefix, :inside_def) do
|
|
57
45
|
def self.root
|
|
58
46
|
new(in_loop_or_block: false, qualified_prefix: [], inside_def: false)
|
|
59
47
|
end
|
|
60
48
|
end
|
|
61
49
|
|
|
62
|
-
# Suppression gates a collector may declare via `RULE_WALK_GATES`.
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# Each gate names the {Context} predicate method that, when true,
|
|
67
|
-
# suppresses the visit:
|
|
50
|
+
# Suppression gates a collector may declare via `RULE_WALK_GATES`. The walk skips a collector's
|
|
51
|
+
# `#visit` for a node when any of the collector's declared gates holds in that node's context —
|
|
52
|
+
# exactly reproducing the per-collector legacy walk's traversal suppression. Each gate names the
|
|
53
|
+
# {Context} predicate method that, when true, suppresses the visit:
|
|
68
54
|
#
|
|
69
|
-
# - `:loop_or_block` (`in_loop_or_block`) — inside a loop / block
|
|
70
|
-
#
|
|
71
|
-
# - `:inside_def` (`inside_def`) — lexically inside a `DefNode`;
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# `def` it meets, so a nested def is never reached).
|
|
55
|
+
# - `:loop_or_block` (`in_loop_or_block`) — inside a loop / block body, the two flow collectors'
|
|
56
|
+
# envelope.
|
|
57
|
+
# - `:inside_def` (`inside_def`) — lexically inside a `DefNode`; IvarWrite collects only at a `def`
|
|
58
|
+
# that sits directly in a class / module body (its legacy walk `return`s at the first `def` it
|
|
59
|
+
# meets, so a nested def is never reached).
|
|
75
60
|
GATE_CONTEXT_PREDICATES = {
|
|
76
61
|
loop_or_block: :in_loop_or_block,
|
|
77
62
|
inside_def: :inside_def
|
|
78
63
|
}.freeze
|
|
79
64
|
|
|
80
|
-
# A per-node driver over a fixed collector set: holds the compiled
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# {
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# {#descend}. The dispatch / gate / descend logic is identical to
|
|
88
|
-
# the standalone {RuleWalk.run} walk — only the traversal driving
|
|
89
|
-
# it differs, keeping diagnostics byte-identical (ADR-53 WD4).
|
|
65
|
+
# A per-node driver over a fixed collector set: holds the compiled `node_class => [[collector,
|
|
66
|
+
# gates], …]` hook table and applies the dispatch + context-descent that {RuleWalk.run} performs,
|
|
67
|
+
# but one node at a time. This lets a foreign traversal (the converged {Plugin::NodeRuleWalk},
|
|
68
|
+
# ADR-53 B4) drive the built-in collectors from inside its own single walk: it calls {#visit} on
|
|
69
|
+
# each node with that node's {Context}, and derives a child's context with {#descend}. The dispatch
|
|
70
|
+
# / gate / descend logic is identical to the standalone {RuleWalk.run} walk — only the traversal
|
|
71
|
+
# driving it differs, keeping diagnostics byte-identical (ADR-53 WD4).
|
|
90
72
|
class CollectorDriver
|
|
91
73
|
def initialize(collectors)
|
|
92
74
|
@hooks = RuleWalk.build_hooks(collectors)
|
|
93
75
|
freeze
|
|
94
76
|
end
|
|
95
77
|
|
|
96
|
-
# Dispatch `node` (under its own `context`) to every matching,
|
|
97
|
-
#
|
|
78
|
+
# Dispatch `node` (under its own `context`) to every matching, un-gated collector. Mirrors
|
|
79
|
+
# {RuleWalk.dispatch}.
|
|
98
80
|
def visit(node, context)
|
|
99
81
|
matched = @hooks[node.class]
|
|
100
82
|
return if matched.nil?
|
|
@@ -106,18 +88,15 @@ module Rigor
|
|
|
106
88
|
end
|
|
107
89
|
end
|
|
108
90
|
|
|
109
|
-
# The context the children of `node` descend under. Mirrors
|
|
110
|
-
# {RuleWalk.descend}.
|
|
91
|
+
# The context the children of `node` descend under. Mirrors {RuleWalk.descend}.
|
|
111
92
|
def descend(node, context)
|
|
112
93
|
RuleWalk.descend(node, context)
|
|
113
94
|
end
|
|
114
95
|
end
|
|
115
96
|
|
|
116
97
|
class << self
|
|
117
|
-
# Walks `root` once, dispatching each visited node to every
|
|
118
|
-
#
|
|
119
|
-
# whose declared `RULE_WALK_GATES` do not suppress it in that
|
|
120
|
-
# node's context.
|
|
98
|
+
# Walks `root` once, dispatching each visited node to every collector whose `NODE_CLASSES` covers
|
|
99
|
+
# the node's class and whose declared `RULE_WALK_GATES` do not suppress it in that node's context.
|
|
121
100
|
def run(root, collectors)
|
|
122
101
|
hooks = build_hooks(collectors)
|
|
123
102
|
walk(root, hooks, Context.root)
|
|
@@ -135,12 +114,10 @@ module Rigor
|
|
|
135
114
|
hooks
|
|
136
115
|
end
|
|
137
116
|
|
|
138
|
-
# Computes the context the children of `node` descend under from
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
# prefix, matching IvarWrite's fall-through); `inside_def`
|
|
143
|
-
# latches on once a `DefNode` is entered.
|
|
117
|
+
# Computes the context the children of `node` descend under from the node's own context.
|
|
118
|
+
# `in_loop_or_block` latches on once a loop / block is entered; `qualified_prefix` extends only on
|
|
119
|
+
# a nameable class / module (an unnamed one descends with the same prefix, matching IvarWrite's
|
|
120
|
+
# fall-through); `inside_def` latches on once a `DefNode` is entered.
|
|
144
121
|
def descend(node, context)
|
|
145
122
|
in_loop_or_block = context.in_loop_or_block ||
|
|
146
123
|
LOOP_OR_BLOCK_NODE_CLASSES.any? { |klass| node.is_a?(klass) }
|
|
@@ -7,24 +7,19 @@ require_relative "../../source/constant_path"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Analysis
|
|
9
9
|
module CheckRules
|
|
10
|
-
# ADR-24 slice 4 — read-side companion to `call.self-undefined-method`.
|
|
11
|
-
#
|
|
12
|
-
# declarations that are NOT confidently closed for self-call
|
|
10
|
+
# ADR-24 slice 4 — read-side companion to `call.self-undefined-method`. Walks a file's parse tree once
|
|
11
|
+
# and collects the qualified names of declarations that are NOT confidently closed for self-call
|
|
13
12
|
# undefined-method analysis, so the rule can exclude them:
|
|
14
13
|
#
|
|
15
|
-
# - **Modules** — a `module M` is a mixin contract; its methods may be
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# are not statically decidable, so the class's method surface cannot
|
|
21
|
-
# be enumerated from source.
|
|
14
|
+
# - **Modules** — a `module M` is a mixin contract; its methods may be provided by includers
|
|
15
|
+
# (template-method pattern), so an unresolved self-call inside it is not provably a typo.
|
|
16
|
+
# - **Classes with a dynamic `attr_*` accessor** — `attr_reader(*NAMES)` / `attr_accessor(:a, SOME_CONST)`
|
|
17
|
+
# synthesizes readers whose names are not statically decidable, so the class's method surface cannot be
|
|
18
|
+
# enumerated from source.
|
|
22
19
|
#
|
|
23
|
-
# The enclosing declaration of a self-call is always in the same file
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# to match the engine's qualified `self` type (`Module.nesting`-style
|
|
27
|
-
# `A::B::C`).
|
|
20
|
+
# The enclosing declaration of a self-call is always in the same file as the call (even a reopened class
|
|
21
|
+
# restates `class Foo`), so a per-file AST scan suffices — no cross-file plumbing. Names are built to
|
|
22
|
+
# match the engine's qualified `self` type (`Module.nesting`-style `A::B::C`).
|
|
28
23
|
class SelfClosednessScanner
|
|
29
24
|
ATTR_MACROS = %i[attr_reader attr_accessor attr_writer].freeze
|
|
30
25
|
private_constant :ATTR_MACROS
|
|
@@ -61,16 +56,14 @@ module Rigor
|
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
|
|
64
|
-
# A class whose source-declared surface cannot be fully enumerated:
|
|
65
|
-
#
|
|
66
|
-
# superclass.
|
|
59
|
+
# A class whose source-declared surface cannot be fully enumerated: a dynamic `attr_*` accessor, or a
|
|
60
|
+
# non-constant (dynamically produced) superclass.
|
|
67
61
|
def class_surface_open?(class_node)
|
|
68
62
|
dynamic_attr_class?(class_node) || dynamic_superclass?(class_node)
|
|
69
63
|
end
|
|
70
64
|
|
|
71
|
-
# True when the class body directly invokes an `attr_*` macro with a
|
|
72
|
-
#
|
|
73
|
-
# synthesized accessor names are then not statically knowable.
|
|
65
|
+
# True when the class body directly invokes an `attr_*` macro with a non-literal argument (a splat, a
|
|
66
|
+
# constant, a method call) — the synthesized accessor names are then not statically knowable.
|
|
74
67
|
def dynamic_attr_class?(class_node)
|
|
75
68
|
body = class_node.body
|
|
76
69
|
return false unless body.is_a?(Prism::StatementsNode)
|
|
@@ -90,13 +83,11 @@ module Rigor
|
|
|
90
83
|
args.any? { |arg| !arg.is_a?(Prism::SymbolNode) && !arg.is_a?(Prism::StringNode) }
|
|
91
84
|
end
|
|
92
85
|
|
|
93
|
-
# True when the class declares a superclass that is not a static
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
# records it and the closed-class gate would wrongly treat the class as
|
|
99
|
-
# standalone), so a missed self-call is not provably a typo.
|
|
86
|
+
# True when the class declares a superclass that is not a static constant — `class X < DelegateClass(Array)`
|
|
87
|
+
# / `< Struct.new(...)` / `< Data.define(...)`. The inherited surface is then a dynamically produced
|
|
88
|
+
# class the engine cannot enumerate from a constant name (the superclass is a method call, so
|
|
89
|
+
# `discovered_superclasses` never records it and the closed-class gate would wrongly treat the class
|
|
90
|
+
# as standalone), so a missed self-call is not provably a typo.
|
|
100
91
|
def dynamic_superclass?(class_node)
|
|
101
92
|
superclass = class_node.superclass
|
|
102
93
|
return false if superclass.nil?
|