rigortype 0.2.7 → 0.2.9
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/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/08-understanding-errors.md +3 -3
- data/docs/manual/01-installation.md +1 -0
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +27 -4
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- 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 +248 -186
- 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 +62 -48
- 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 +46 -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_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +34 -18
- 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/missing_gem_constant_index.rb +128 -0
- 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 +225 -304
- 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 +794 -1154
- 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/fork_map.rb +87 -0
- 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 +627 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
- 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 -821
- data/lib/rigor/inference/statement_evaluator.rb +754 -1114
- 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 +141 -184
- 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 +92 -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 +40 -48
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
- 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/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
- 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-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- 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/grape_api_discoverer.rb +189 -0
- 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 +61 -75
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
- 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/environment.rbs +1 -0
- data/sig/rigor/scope.rbs +6 -0
- metadata +9 -1
|
@@ -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?
|
|
@@ -5,46 +5,37 @@ require "prism"
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Analysis
|
|
7
7
|
module CheckRules
|
|
8
|
-
# ADR-47 — collects `case`/`when` clauses proven unreachable. The flow
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# narrowing proves no reaching value can match — a stale `when`,
|
|
15
|
-
# mis-ordered clauses, or a type that moved out from under a branch.
|
|
8
|
+
# ADR-47 — collects `case`/`when` clauses proven unreachable. The flow engine already narrows the case
|
|
9
|
+
# subject across `when` branches (`Narrowing.case_when_scopes`), and the per-clause `body_scope` it
|
|
10
|
+
# produces is recorded in `scope_index` keyed on the `WhenNode` (the evaluator enters the clause under
|
|
11
|
+
# that scope). So a clause whose narrowed subject local is `Type::Bot` is one the engine's own
|
|
12
|
+
# narrowing proves no reaching value can match — a stale `when`, mis-ordered clauses, or a type that
|
|
13
|
+
# moved out from under a branch.
|
|
16
14
|
#
|
|
17
|
-
# WD1 (per-clause disjointness) — the narrow, high-value core. Fires
|
|
18
|
-
# ONLY on the safe shape:
|
|
15
|
+
# WD1 (per-clause disjointness) — the narrow, high-value core. Fires ONLY on the safe shape:
|
|
19
16
|
#
|
|
20
|
-
# - the subject is a `case <local>` (a `LocalVariableReadNode`, the
|
|
21
|
-
#
|
|
22
|
-
# - every `when` condition is a class/module constant (`when String` /
|
|
23
|
-
# `when
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# `Type::Dynamic` (disjointness is never provable under gradual
|
|
27
|
-
# `Dynamic`, preserving the gradual guarantee) and never already
|
|
17
|
+
# - the subject is a `case <local>` (a `LocalVariableReadNode`, the only shape `case_when_scopes`
|
|
18
|
+
# narrows — no narrowing ⇒ no firing),
|
|
19
|
+
# - every `when` condition is a class/module constant (`when String` / `when MyClass`), the shape the
|
|
20
|
+
# narrowing recognises — `when nil` / ranges / regexps / arbitrary expressions are out of scope here,
|
|
21
|
+
# - the subject's type at case entry is a concrete carrier, never `Type::Dynamic` (disjointness is
|
|
22
|
+
# never provable under gradual `Dynamic`, preserving the gradual guarantee) and never already
|
|
28
23
|
# `Type::Bot` (dead code, not a clause error),
|
|
29
24
|
# - and the clause's narrowed `body_scope` subject is `Type::Bot`.
|
|
30
25
|
#
|
|
31
|
-
# The false-positive envelope mirrors `flow.always-truthy-condition`:
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# rather than recomputing it, so the diagnostic and the body typing
|
|
35
|
-
# can never diverge.
|
|
26
|
+
# The false-positive envelope mirrors `flow.always-truthy-condition`: clauses inside loops / blocks are
|
|
27
|
+
# skipped (mutation tracking through those is incomplete), and the rule reads the engine's own
|
|
28
|
+
# narrowing rather than recomputing it, so the diagnostic and the body typing can never diverge.
|
|
36
29
|
class UnreachableClauseCollector
|
|
37
30
|
LOOP_OR_BLOCK_NODE_CLASSES = [
|
|
38
31
|
Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::BlockNode
|
|
39
32
|
].freeze
|
|
40
33
|
|
|
41
|
-
# A trailing `else` whose body only re-raises / aborts is a
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# carry this idiom (a stale `when` is a real redundancy), so the
|
|
47
|
-
# skip is `else`-only.
|
|
34
|
+
# A trailing `else` whose body only re-raises / aborts is a deliberate defensive guard ("this should
|
|
35
|
+
# be unreachable — shout if it ever isn't"), NOT dead code the author wants removed. Flagging it
|
|
36
|
+
# would push the author to delete a safety net, which the false-positive discipline forbids. WD1's
|
|
37
|
+
# `when` shapes don't carry this idiom (a stale `when` is a real redundancy), so the skip is
|
|
38
|
+
# `else`-only.
|
|
48
39
|
DEFENSIVE_EXIT_CALLS = %i[raise fail throw abort exit].freeze
|
|
49
40
|
|
|
50
41
|
# @!attribute kind
|
|
@@ -55,11 +46,10 @@ module Rigor
|
|
|
55
46
|
# `else` never runs).
|
|
56
47
|
Result = Data.define(:clause, :body, :subject_name, :condition_source, :kind, :keyword)
|
|
57
48
|
|
|
58
|
-
# ADR-53 Track B — the node classes the shared {RuleWalk}
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# walk's `:loop_or_block` gate, applied before `#visit`.
|
|
49
|
+
# ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector, and the
|
|
50
|
+
# context gate under which the walk suppresses it (loop / block bodies — see the envelope above).
|
|
51
|
+
# The legacy walk's `!in_loop_or_block` guard is now the walk's `:loop_or_block` gate, applied
|
|
52
|
+
# before `#visit`.
|
|
63
53
|
NODE_CLASSES = [Prism::CaseNode, Prism::CaseMatchNode].freeze
|
|
64
54
|
RULE_WALK_GATES = [:loop_or_block].freeze
|
|
65
55
|
|
|
@@ -68,19 +58,17 @@ module Rigor
|
|
|
68
58
|
@results = []
|
|
69
59
|
end
|
|
70
60
|
|
|
71
|
-
# Legacy single-collector walk — kept as the oracle the
|
|
72
|
-
#
|
|
73
|
-
# against; deleted when Track B completes.
|
|
61
|
+
# Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
|
|
62
|
+
# {RuleWalk} against; deleted when Track B completes.
|
|
74
63
|
# @return [Array<Result>] one entry per provably-dead `when` clause.
|
|
75
64
|
def collect(root)
|
|
76
65
|
walk(root, in_loop_or_block: false)
|
|
77
66
|
@results.freeze
|
|
78
67
|
end
|
|
79
68
|
|
|
80
|
-
# {RuleWalk} entry point: the per-node logic of the legacy walk,
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# is the walk's `:loop_or_block` gate now.
|
|
69
|
+
# {RuleWalk} entry point: the per-node logic of the legacy walk, invoked under the same traversal
|
|
70
|
+
# contract. The `context` is unused — the loop / block suppression this collector relied on is the
|
|
71
|
+
# walk's `:loop_or_block` gate now.
|
|
84
72
|
def visit(node, _context = nil)
|
|
85
73
|
collect_case(node)
|
|
86
74
|
end
|
|
@@ -100,9 +88,8 @@ module Rigor
|
|
|
100
88
|
node.compact_child_nodes.each { |child| walk(child, in_loop_or_block: child_in_loop_or_block) }
|
|
101
89
|
end
|
|
102
90
|
|
|
103
|
-
# `case/when` is a `CaseNode`; `case/in` is a `CaseMatchNode`. Both
|
|
104
|
-
#
|
|
105
|
-
# narrows both through `eval_case_when_branches`.
|
|
91
|
+
# `case/when` is a `CaseNode`; `case/in` is a `CaseMatchNode`. Both carry `predicate` + `conditions`
|
|
92
|
+
# + `else_clause` and the engine narrows both through `eval_case_when_branches`.
|
|
106
93
|
def case_like?(node)
|
|
107
94
|
node.is_a?(Prism::CaseNode) || node.is_a?(Prism::CaseMatchNode)
|
|
108
95
|
end
|
|
@@ -116,9 +103,8 @@ module Rigor
|
|
|
116
103
|
return unless subject.is_a?(Prism::LocalVariableReadNode)
|
|
117
104
|
|
|
118
105
|
entry_type = entry_subject_type(node, subject.name)
|
|
119
|
-
# Only meaningful for a concrete subject type: a `Dynamic` subject
|
|
120
|
-
#
|
|
121
|
-
# already-`Bot` subject is dead code, not a clause-ordering error.
|
|
106
|
+
# Only meaningful for a concrete subject type: a `Dynamic` subject can never be proven disjoint
|
|
107
|
+
# (the gradual guarantee), and an already-`Bot` subject is dead code, not a clause-ordering error.
|
|
122
108
|
return if entry_type.nil? || entry_type.is_a?(Type::Bot) || entry_type.is_a?(Type::Dynamic)
|
|
123
109
|
|
|
124
110
|
keyword = node.is_a?(Prism::CaseMatchNode) ? "in" : "when"
|
|
@@ -151,12 +137,10 @@ module Rigor
|
|
|
151
137
|
)
|
|
152
138
|
end
|
|
153
139
|
|
|
154
|
-
# A dead `when` is `:prior_exhaustion` when the subject was
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
# subject was still concrete entering the clause and THIS clause's
|
|
159
|
-
# type is disjoint from it.
|
|
140
|
+
# A dead `when` is `:prior_exhaustion` when the subject was already narrowed to `bot` BEFORE this
|
|
141
|
+
# clause (an earlier clause covered it) — read from the entry scope the engine recorded on the
|
|
142
|
+
# clause's first condition node (ADR-47 WD2). Otherwise the subject was still concrete entering the
|
|
143
|
+
# clause and THIS clause's type is disjoint from it.
|
|
160
144
|
def when_clause_kind(clause, subject_name)
|
|
161
145
|
entry = @scope_index[clause.conditions.first]
|
|
162
146
|
return :prior_exhaustion if entry && entry.local(subject_name).is_a?(Type::Bot)
|
|
@@ -164,14 +148,12 @@ module Rigor
|
|
|
164
148
|
:disjoint
|
|
165
149
|
end
|
|
166
150
|
|
|
167
|
-
# ADR-47 WD3a — a dead `case/in` clause. The body scope reaches
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# narrowed, so their body is `bot` ONLY under prior-exhaustion;
|
|
174
|
-
# they never produce a spurious `:disjoint`.
|
|
151
|
+
# ADR-47 WD3a — a dead `case/in` clause. The body scope reaches `bot` either because a bare class
|
|
152
|
+
# pattern (`in C` / `in C => x`) is disjoint from the still-possible subject (the engine narrows
|
|
153
|
+
# those soundly, like `when C`), or because an earlier clause already exhausted the subject
|
|
154
|
+
# (prior-exhaustion — true for ANY pattern downstream of a covering set). Non-bare patterns are not
|
|
155
|
+
# narrowed, so their body is `bot` ONLY under prior-exhaustion; they never produce a spurious
|
|
156
|
+
# `:disjoint`.
|
|
175
157
|
def collect_in(clause, subject_name)
|
|
176
158
|
return if clause.statements.nil?
|
|
177
159
|
|
|
@@ -193,11 +175,10 @@ module Rigor
|
|
|
193
175
|
:disjoint
|
|
194
176
|
end
|
|
195
177
|
|
|
196
|
-
# The trailing `else` is dead when every subject value is covered
|
|
197
|
-
#
|
|
198
|
-
# `
|
|
199
|
-
#
|
|
200
|
-
# guards, not removable dead code, so they are skipped.
|
|
178
|
+
# The trailing `else` is dead when every subject value is covered by the `when` clauses — the final
|
|
179
|
+
# falsey scope (recorded on the `else` node) narrows the subject to `bot`. Defensive `else` bodies (a
|
|
180
|
+
# bare `raise` / `fail` / `throw`) are deliberate guards, not removable dead code, so they are
|
|
181
|
+
# skipped.
|
|
201
182
|
def collect_else(else_clause, subject_name, keyword)
|
|
202
183
|
return if else_clause.nil? || else_clause.statements.nil?
|
|
203
184
|
return if defensive_else?(else_clause)
|