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
|
@@ -6,26 +6,21 @@ module Rigor
|
|
|
6
6
|
module Source
|
|
7
7
|
# Extracts literal Symbol/String values from Prism call arguments.
|
|
8
8
|
#
|
|
9
|
-
# The "is this argument a literal `:sym` or `"str"`, and if so what
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# `
|
|
14
|
-
#
|
|
15
|
-
# `node.unescaped.to_sym if SymbolNode || StringNode` shape is written
|
|
16
|
-
# once rather than copied per call site.
|
|
9
|
+
# The "is this argument a literal `:sym` or `"str"`, and if so what Symbol does it name?" question
|
|
10
|
+
# recurs across the analyzer (sig-gen observation, attr-accessor generation, synthetic-method scanning)
|
|
11
|
+
# and across nearly every DSL plugin (`state :draft`, `has_one_attached :avatar`,
|
|
12
|
+
# `validate_presence_of(:name)`, …). This module is the one place that answers it, so the
|
|
13
|
+
# `node.unescaped.to_sym if SymbolNode || StringNode` shape is written once rather than copied per call
|
|
14
|
+
# site.
|
|
17
15
|
#
|
|
18
|
-
# `#unescaped` (not `#value`) is used deliberately so an interpolation-
|
|
19
|
-
#
|
|
20
|
-
# node kinds.
|
|
16
|
+
# `#unescaped` (not `#value`) is used deliberately so an interpolation- free `"foo"` / `:foo`
|
|
17
|
+
# round-trips to `:foo` consistently for both node kinds.
|
|
21
18
|
#
|
|
22
|
-
# The surface is a small grid over two axes — which node kinds are
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# keeps that distinction instead of silently widening to accept the
|
|
28
|
-
# string literal.
|
|
19
|
+
# The surface is a small grid over two axes — which node kinds are accepted (`SymbolNode` only, or
|
|
20
|
+
# `SymbolNode`/`StringNode`) and what the caller wants back (the interned `Symbol`, or the raw `String`
|
|
21
|
+
# name). The SymbolNode-only forms ({.symbol} / {.symbol_name}) exist so a DSL that distinguishes `state
|
|
22
|
+
# :draft` from `state "draft"` keeps that distinction instead of silently widening to accept the string
|
|
23
|
+
# literal.
|
|
29
24
|
#
|
|
30
25
|
# | accepts | → Symbol | → String |
|
|
31
26
|
# | ------------------ | ------------------- | ------------------------ |
|
|
@@ -34,8 +29,8 @@ module Rigor
|
|
|
34
29
|
module Literals
|
|
35
30
|
module_function
|
|
36
31
|
|
|
37
|
-
# The Symbol a literal `Prism::SymbolNode` / `Prism::StringNode`
|
|
38
|
-
#
|
|
32
|
+
# The Symbol a literal `Prism::SymbolNode` / `Prism::StringNode` names, or `nil` for any other node
|
|
33
|
+
# (including `nil`).
|
|
39
34
|
#
|
|
40
35
|
# @param node [Prism::Node, nil]
|
|
41
36
|
# @return [Symbol, nil]
|
|
@@ -45,12 +40,10 @@ module Rigor
|
|
|
45
40
|
node.unescaped.to_sym
|
|
46
41
|
end
|
|
47
42
|
|
|
48
|
-
# The String a literal `Prism::SymbolNode` / `Prism::StringNode`
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# helpers, factory names, filter targets). `#unescaped` round-trips
|
|
53
|
-
# an interpolation-free `:foo` / `"foo"` to `"foo"` for both kinds.
|
|
43
|
+
# The String a literal `Prism::SymbolNode` / `Prism::StringNode` names, or `nil` for any other node
|
|
44
|
+
# (including `nil`). The String-returning sibling of {.symbol_or_string} — for callers that key on the
|
|
45
|
+
# raw name rather than the interned Symbol (route helpers, factory names, filter targets).
|
|
46
|
+
# `#unescaped` round-trips an interpolation-free `:foo` / `"foo"` to `"foo"` for both kinds.
|
|
54
47
|
#
|
|
55
48
|
# @param node [Prism::Node, nil]
|
|
56
49
|
# @return [String, nil]
|
|
@@ -60,11 +53,9 @@ module Rigor
|
|
|
60
53
|
node.unescaped
|
|
61
54
|
end
|
|
62
55
|
|
|
63
|
-
# The Symbol a literal `Prism::SymbolNode` names, or `nil` for any
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# not `"draft"` keeps that distinction by reaching for this rather
|
|
67
|
-
# than the Symbol-or-String form.
|
|
56
|
+
# The Symbol a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
|
|
57
|
+
# `Prism::StringNode` and `nil`). Stricter than {.symbol_or_string}: a DSL that accepts only `:draft`
|
|
58
|
+
# and not `"draft"` keeps that distinction by reaching for this rather than the Symbol-or-String form.
|
|
68
59
|
#
|
|
69
60
|
# @param node [Prism::Node, nil]
|
|
70
61
|
# @return [Symbol, nil]
|
|
@@ -74,9 +65,8 @@ module Rigor
|
|
|
74
65
|
node.unescaped.to_sym
|
|
75
66
|
end
|
|
76
67
|
|
|
77
|
-
# The String a literal `Prism::SymbolNode` names, or `nil` for any
|
|
78
|
-
#
|
|
79
|
-
# String-returning sibling of {.symbol} — SymbolNode-only, but the
|
|
68
|
+
# The String a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
|
|
69
|
+
# `Prism::StringNode` and `nil`). The String-returning sibling of {.symbol} — SymbolNode-only, but the
|
|
80
70
|
# caller wants the raw name rather than the interned Symbol.
|
|
81
71
|
#
|
|
82
72
|
# @param node [Prism::Node, nil]
|
|
@@ -87,9 +77,8 @@ module Rigor
|
|
|
87
77
|
node.unescaped
|
|
88
78
|
end
|
|
89
79
|
|
|
90
|
-
# Every literal Symbol/String positional argument of a call, in
|
|
91
|
-
#
|
|
92
|
-
# the call has no argument list.
|
|
80
|
+
# Every literal Symbol/String positional argument of a call, in source order. Non-literal arguments
|
|
81
|
+
# are dropped. Returns `[]` when the call has no argument list.
|
|
93
82
|
#
|
|
94
83
|
# @param call_node [Prism::CallNode, nil]
|
|
95
84
|
# @return [Array<Symbol>]
|
|
@@ -100,11 +89,9 @@ module Rigor
|
|
|
100
89
|
args.filter_map { |arg| symbol_or_string(arg) }
|
|
101
90
|
end
|
|
102
91
|
|
|
103
|
-
# Whether a node is a literal `Prism::SymbolNode` that names `name`.
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# in keyword or assoc argument positions, e.g.
|
|
107
|
-
# `el.is_a?(AssocNode) && symbol_named?(el.key, "required")`).
|
|
92
|
+
# Whether a node is a literal `Prism::SymbolNode` that names `name`. The key-comparison counterpart to
|
|
93
|
+
# {.symbol_name} — for callers that need a predicate rather than an extraction (hash-key matching in
|
|
94
|
+
# keyword or assoc argument positions, e.g. `el.is_a?(AssocNode) && symbol_named?(el.key, "required")`).
|
|
108
95
|
# Uses `#unescaped` (not `#value`) for round-trip consistency.
|
|
109
96
|
#
|
|
110
97
|
# @param node [Prism::Node, nil]
|
|
@@ -114,9 +101,8 @@ module Rigor
|
|
|
114
101
|
node.is_a?(Prism::SymbolNode) && node.unescaped == name
|
|
115
102
|
end
|
|
116
103
|
|
|
117
|
-
# The literal Symbol/String at positional `index`, or `nil` when the
|
|
118
|
-
#
|
|
119
|
-
# argument there is not a literal Symbol/String.
|
|
104
|
+
# The literal Symbol/String at positional `index`, or `nil` when the call has no argument list, the
|
|
105
|
+
# index is out of range, or the argument there is not a literal Symbol/String.
|
|
120
106
|
#
|
|
121
107
|
# @param call_node [Prism::CallNode, nil]
|
|
122
108
|
# @param index [Integer]
|
|
@@ -6,17 +6,16 @@ module Rigor
|
|
|
6
6
|
module Source
|
|
7
7
|
# Locates the deepest Prism AST node enclosing a given source position.
|
|
8
8
|
#
|
|
9
|
-
# The locator works on byte offsets internally so that multibyte source
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# The locator works on byte offsets internally so that multibyte source text behaves consistently with
|
|
10
|
+
# Prism, which itself reports offsets in bytes. Convenience constructors translate from `(line, column)`
|
|
11
|
+
# pairs.
|
|
12
12
|
#
|
|
13
|
-
# Lines are 1-indexed (matching editor / Prism / gcc conventions).
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# offsets remain 0-indexed bytes.
|
|
13
|
+
# Lines are 1-indexed (matching editor / Prism / gcc conventions). Columns are 1-indexed when supplied
|
|
14
|
+
# via the `(line, column)` API; this matches the canonical `file.rb:line:col` form most tools emit.
|
|
15
|
+
# Internal offsets remain 0-indexed bytes.
|
|
17
16
|
#
|
|
18
|
-
# The locator is read-only: a single instance binds to one source buffer
|
|
19
|
-
#
|
|
17
|
+
# The locator is read-only: a single instance binds to one source buffer and AST root, and queries are
|
|
18
|
+
# pure functions of the byte offset.
|
|
20
19
|
class NodeLocator
|
|
21
20
|
class OutOfRangeError < StandardError; end
|
|
22
21
|
|
|
@@ -58,8 +57,7 @@ module Rigor
|
|
|
58
57
|
descend(@root, offset)
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
# Translate a `(line, column)` pair into a 0-indexed byte offset for the
|
|
62
|
-
# bound source buffer.
|
|
60
|
+
# Translate a `(line, column)` pair into a 0-indexed byte offset for the bound source buffer.
|
|
63
61
|
def position_to_offset(line, column)
|
|
64
62
|
raise ArgumentError, "source buffer required for position lookup" if @source.nil?
|
|
65
63
|
raise OutOfRangeError, "line must be >= 1, got #{line}" if line < 1
|
|
@@ -6,14 +6,12 @@ module Rigor
|
|
|
6
6
|
module Source
|
|
7
7
|
# Yields every `Prism::Node` reachable from a root in DFS pre-order.
|
|
8
8
|
#
|
|
9
|
-
# The walker is the source-positioning analogue to `NodeLocator`: where the
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# (coverage probes, lint passes, IDE outlines).
|
|
9
|
+
# The walker is the source-positioning analogue to `NodeLocator`: where the locator answers "what node
|
|
10
|
+
# is at this point?", the walker enumerates the full set of Prism nodes for tooling that needs to
|
|
11
|
+
# operate on each one (coverage probes, lint passes, IDE outlines).
|
|
13
12
|
#
|
|
14
|
-
# Non-Prism children (literals embedded in node attributes, virtual nodes,
|
|
15
|
-
#
|
|
16
|
-
# value responding to the `Prism::Node` API.
|
|
13
|
+
# Non-Prism children (literals embedded in node attributes, virtual nodes, or `nil` slots) are silently
|
|
14
|
+
# skipped so callers can rely on every yielded value responding to the `Prism::Node` API.
|
|
17
15
|
module NodeWalker
|
|
18
16
|
module_function
|
|
19
17
|
|
|
@@ -33,12 +31,10 @@ module Rigor
|
|
|
33
31
|
node.compact_child_nodes.each { |child| walk(child, &) }
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
# Like {.each}, but also yields the node's lexical ancestor chain
|
|
37
|
-
#
|
|
38
|
-
# `
|
|
39
|
-
#
|
|
40
|
-
# does). Used by the plugin engine to give `node_rule` blocks their
|
|
41
|
-
# enclosing class / method / block context (ADR-37 slice 1d).
|
|
34
|
+
# Like {.each}, but also yields the node's lexical ancestor chain (outermost first, EXCLUDING the node
|
|
35
|
+
# itself). The yielded `ancestors` array is the live descent stack — callers that retain it past the
|
|
36
|
+
# block invocation MUST copy it (`Plugin::NodeContext` does). Used by the plugin engine to give
|
|
37
|
+
# `node_rule` blocks their enclosing class / method / block context (ADR-37 slice 1d).
|
|
42
38
|
#
|
|
43
39
|
# @yieldparam node [Prism::Node]
|
|
44
40
|
# @yieldparam ancestors [Array<Prism::Node>]
|
data/lib/rigor/source.rb
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# Source-text and AST positioning utilities.
|
|
4
4
|
#
|
|
5
|
-
# Anything that maps between a Ruby source buffer and Prism AST nodes belongs
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# extractors) can reuse the same primitives without dragging in `Rigor::Type`.
|
|
5
|
+
# Anything that maps between a Ruby source buffer and Prism AST nodes belongs here. The contents of this
|
|
6
|
+
# namespace deliberately stay independent of the inference engine so that future tooling (LSP, refactoring
|
|
7
|
+
# helpers, doc extractors) can reuse the same primitives without dragging in `Rigor::Type`.
|
|
9
8
|
module Rigor
|
|
10
9
|
module Source
|
|
11
10
|
end
|
data/lib/rigor/testing.rb
CHANGED
|
@@ -3,21 +3,18 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
# Slice 7 phase 19 — PHPStan-style typing helpers.
|
|
5
5
|
#
|
|
6
|
-
# `Rigor::Testing` ships two runtime no-op helpers that serve
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# - `dump_type(value)` — returns `value` unchanged at runtime.
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# the
|
|
17
|
-
#
|
|
18
|
-
# fixture be self-asserting: `rigor check fixture.rb` exits
|
|
19
|
-
# non-zero exactly when the engine's inference drifts from
|
|
20
|
-
# what the fixture documents.
|
|
6
|
+
# `Rigor::Testing` ships two runtime no-op helpers that serve as anchors for
|
|
7
|
+
# static-analysis diagnostics:
|
|
8
|
+
#
|
|
9
|
+
# - `dump_type(value)` — returns `value` unchanged at runtime. The Rigor analyzer surfaces
|
|
10
|
+
# an `:info`-severity diagnostic at the call site showing the inferred type of `value`
|
|
11
|
+
# so the user can see what the engine sees at that program point.
|
|
12
|
+
# - `assert_type(expected, value)` — returns `value` unchanged at runtime. The analyzer
|
|
13
|
+
# compares `value`'s inferred type (rendered through `Rigor::Type#describe(:short)`)
|
|
14
|
+
# against the literal `expected` String; a mismatch produces an `:error`-severity
|
|
15
|
+
# diagnostic. This lets a user-written fixture be self-asserting: `rigor check
|
|
16
|
+
# fixture.rb` exits non-zero exactly when the engine's inference drifts from what the
|
|
17
|
+
# fixture documents.
|
|
21
18
|
#
|
|
22
19
|
# Three usage shapes are recognised by the static rules:
|
|
23
20
|
#
|
|
@@ -36,9 +33,8 @@ module Rigor
|
|
|
36
33
|
# Rigor.dump_type(x)
|
|
37
34
|
# Rigor.assert_type("Constant[\"hello\"]", x)
|
|
38
35
|
#
|
|
39
|
-
# All three resolve to the same no-op runtime body, so a
|
|
40
|
-
#
|
|
41
|
-
# analyzer being present.
|
|
36
|
+
# All three resolve to the same no-op runtime body, so a fixture may freely run under MRI
|
|
37
|
+
# without depending on the analyzer being present.
|
|
42
38
|
module Testing
|
|
43
39
|
module_function
|
|
44
40
|
|
|
@@ -52,8 +48,8 @@ module Rigor
|
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
class << self
|
|
55
|
-
# Convenience aliases on `Rigor` itself, so fixtures can
|
|
56
|
-
#
|
|
51
|
+
# Convenience aliases on `Rigor` itself, so fixtures can write `Rigor.dump_type(x)`
|
|
52
|
+
# without an `include` line.
|
|
57
53
|
def dump_type(value)
|
|
58
54
|
Testing.dump_type(value)
|
|
59
55
|
end
|
|
@@ -6,17 +6,13 @@ module Rigor
|
|
|
6
6
|
module Triage
|
|
7
7
|
# ADR-23 § "Heuristic catalogue" — the six v1 recognisers.
|
|
8
8
|
#
|
|
9
|
-
# {.recognise} runs them in order over the diagnostic stream.
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# (ActiveSupport) is not re-counted by H2 (monkey-patch).
|
|
9
|
+
# {.recognise} runs them in order over the diagnostic stream. Each recogniser sees only the diagnostics not yet
|
|
10
|
+
# claimed by an earlier one, so a `5.minutes` diagnostic counted by H1 (ActiveSupport) is not re-counted by H2
|
|
11
|
+
# (monkey-patch).
|
|
13
12
|
#
|
|
14
|
-
# WD3 / slice 4: recognisers key on the structured
|
|
15
|
-
# `
|
|
16
|
-
#
|
|
17
|
-
# `Diagnostic#receiver_type` / `#method_name` fields, falling
|
|
18
|
-
# back to parsing the diagnostic message only when those are
|
|
19
|
-
# absent. A parse failure degrades to "skip this diagnostic" —
|
|
13
|
+
# WD3 / slice 4: recognisers key on the structured `qualified_rule` first; where they additionally need the receiver
|
|
14
|
+
# type or method name they read the structured `Diagnostic#receiver_type` / `#method_name` fields, falling back to
|
|
15
|
+
# parsing the diagnostic message only when those are absent. A parse failure degrades to "skip this diagnostic" —
|
|
20
16
|
# never a crash.
|
|
21
17
|
module Catalogue # rubocop:disable Metrics/ModuleLength
|
|
22
18
|
module_function
|
|
@@ -27,9 +23,8 @@ module Rigor
|
|
|
27
23
|
# `undefined method `foo' for <receiver>`
|
|
28
24
|
UNDEF_METHOD = /\Aundefined method [`'"]([^`'"]+)['"`] for (.+)\z/
|
|
29
25
|
|
|
30
|
-
# ActiveSupport `core_ext` selectors, grouped by the core
|
|
31
|
-
#
|
|
32
|
-
# from the five-project survey + the Mastodon measurement).
|
|
26
|
+
# ActiveSupport `core_ext` selectors, grouped by the core class they extend. Survey-grounded (the dominant
|
|
27
|
+
# clusters from the five-project survey + the Mastodon measurement).
|
|
33
28
|
AS_NUMERIC = %w[
|
|
34
29
|
day days hour hours minute minutes second seconds week weeks
|
|
35
30
|
fortnight fortnights month months year years
|
|
@@ -72,8 +67,8 @@ module Rigor
|
|
|
72
67
|
}.freeze
|
|
73
68
|
private_constant :AS_NUMERIC, :AS_STRING, :AS_HASH, :AS_ARRAY, :AS_TIMEDATE
|
|
74
69
|
|
|
75
|
-
# ActiveRecord query-builder methods. When flagged on an
|
|
76
|
-
#
|
|
70
|
+
# ActiveRecord query-builder methods. When flagged on an `Array[...]` receiver they signal a relation
|
|
71
|
+
# misinference.
|
|
77
72
|
AR_QUERY_METHODS = %w[
|
|
78
73
|
where joins includes preload eager_load references select
|
|
79
74
|
order reorder distinct group having limit offset pluck
|
|
@@ -87,16 +82,12 @@ module Rigor
|
|
|
87
82
|
GENUINE_BUG_MAX_COUNT = 5 # rule total ≤ N → "likely genuine bug"
|
|
88
83
|
private_constant :SYSTEMIC_THRESHOLD, :MONKEY_PATCH_MIN_FILES, :GENUINE_BUG_MAX_COUNT
|
|
89
84
|
|
|
90
|
-
# WD6 (ADR-23): H5 (systemic cluster) and H6 (genuine bugs) are
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# behaviour. Every other recogniser keys on an error/warning rule
|
|
97
|
-
# (H1/H2/H2K/H4/H7) or intentionally reads an info notice
|
|
98
|
-
# (H3 — the `gem-without-rbs` `rbs.coverage.missing-gem`), so the
|
|
99
|
-
# full pool is correct for them.
|
|
85
|
+
# WD6 (ADR-23): H5 (systemic cluster) and H6 (genuine bugs) are the only count-based, severity-agnostic
|
|
86
|
+
# recognisers, so they alone risk reading plugin recognition trace (`:info` `*.model-call` / `*.helper`, …) as a
|
|
87
|
+
# "systemic cluster" or a "genuine bug" — frightening working code. They are guarded against `:info` unless
|
|
88
|
+
# `include_info` restores the pre-v0.2.3 behaviour. Every other recogniser keys on an error/warning rule
|
|
89
|
+
# (H1/H2/H2K/H4/H7) or intentionally reads an info notice (H3 — the `gem-without-rbs` `rbs.coverage.missing-gem`),
|
|
90
|
+
# so the full pool is correct for them.
|
|
100
91
|
INFO_GUARDED = %i[h5_systemic_cluster h6_genuine_bugs].freeze
|
|
101
92
|
private_constant :INFO_GUARDED
|
|
102
93
|
|
|
@@ -116,19 +107,14 @@ module Rigor
|
|
|
116
107
|
end
|
|
117
108
|
end
|
|
118
109
|
|
|
119
|
-
# H4 (ActiveRecord query methods) runs before H2 (generic
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# "project core-ext" guess H2 would otherwise claim it for.
|
|
110
|
+
# H4 (ActiveRecord query methods) runs before H2 (generic monkey-patch): a known AR method on `Array[...]`
|
|
111
|
+
# deserves the precise relation-misinference hint, not the generic "project core-ext" guess H2 would otherwise
|
|
112
|
+
# claim it for.
|
|
123
113
|
#
|
|
124
|
-
# H2K (known project patch) runs before the generic H2: the
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
# than the spread-based guess. H7 (unresolved toplevel) runs
|
|
129
|
-
# before the systemic / genuine-bug catch-alls so toplevel
|
|
130
|
-
# resolution misses route to `pre_eval:` (ADR-34) instead of
|
|
131
|
-
# reading as scattered bugs.
|
|
114
|
+
# H2K (known project patch) runs before the generic H2: the engine has already proved the defining site via the
|
|
115
|
+
# `project_definition_site` field (ADR-17), so those diagnostics get the high-confidence file-naming hint rather
|
|
116
|
+
# than the spread-based guess. H7 (unresolved toplevel) runs before the systemic / genuine-bug catch-alls so
|
|
117
|
+
# toplevel resolution misses route to `pre_eval:` (ADR-34) instead of reading as scattered bugs.
|
|
132
118
|
def recognisers
|
|
133
119
|
%i[h1_activesupport h4_ar_relation h3_gem_without_rbs
|
|
134
120
|
h2k_known_project_patch h2_monkey_patch h7_unresolved_toplevel
|
|
@@ -154,13 +140,10 @@ module Rigor
|
|
|
154
140
|
end
|
|
155
141
|
|
|
156
142
|
# --- H2K — known project monkey-patch (engine-proven) ------
|
|
157
|
-
# ADR-17 / WD3 slice 4: the `call.undefined-method` rule sets
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
# apply cross-file). That is direct evidence — not a spread
|
|
162
|
-
# heuristic — so this recogniser is `:likely` and names the
|
|
163
|
-
# defining files outright. It runs before the generic H2.
|
|
143
|
+
# ADR-17 / WD3 slice 4: the `call.undefined-method` rule sets `project_definition_site` when the project itself
|
|
144
|
+
# defines the called method on the receiver class somewhere in the file set (a reopened core/stdlib/gem class the
|
|
145
|
+
# dispatcher does not apply cross-file). That is direct evidence — not a spread heuristic — so this recogniser is
|
|
146
|
+
# `:likely` and names the defining files outright. It runs before the generic H2.
|
|
164
147
|
def h2k_known_project_patch(pool)
|
|
165
148
|
matched = pool.select(&:project_definition_site)
|
|
166
149
|
return nil if matched.empty?
|
|
@@ -231,13 +214,10 @@ module Rigor
|
|
|
231
214
|
end
|
|
232
215
|
|
|
233
216
|
# --- H7 — unresolved toplevel implicit-self calls ----------
|
|
234
|
-
# ADR-34: `call.unresolved-toplevel` fires on a toplevel
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# script relying on methods defined by a monkey-patch or a
|
|
239
|
-
# required helper Rigor did not walk. Grouped, not per-site,
|
|
240
|
-
# so the report names the cluster once.
|
|
217
|
+
# ADR-34: `call.unresolved-toplevel` fires on a toplevel implicit-self call (no receiver, outside any def / class
|
|
218
|
+
# / module) that resolves against no visible contributor. The canonical opt-out is `pre_eval:` — the file is
|
|
219
|
+
# usually a script relying on methods defined by a monkey-patch or a required helper Rigor did not walk. Grouped,
|
|
220
|
+
# not per-site, so the report names the cluster once.
|
|
241
221
|
def h7_unresolved_toplevel(pool)
|
|
242
222
|
matched = pool.select { |d| rule_of(d) == UNRESOLVED_TOPLEVEL_RULE }
|
|
243
223
|
return nil if matched.empty?
|
|
@@ -290,11 +270,9 @@ module Rigor
|
|
|
290
270
|
|
|
291
271
|
# --- shared helpers ----------------------------------------
|
|
292
272
|
|
|
293
|
-
# WD3 / slice 4: prefer the structured `receiver_type` /
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
# are absent (older diagnostics, plugin-emitted rules). Either
|
|
297
|
-
# way the receiver token is normalised through `receiver_class`.
|
|
273
|
+
# WD3 / slice 4: prefer the structured `receiver_type` / `method_name` fields the `call.undefined-method` rule now
|
|
274
|
+
# populates; fall back to parsing the message only when they are absent (older diagnostics, plugin-emitted rules).
|
|
275
|
+
# Either way the receiver token is normalised through `receiver_class`.
|
|
298
276
|
def parse_undefined_method(diag)
|
|
299
277
|
return nil unless rule_of(diag) == UNDEFINED_METHOD_RULE
|
|
300
278
|
|
|
@@ -319,8 +297,7 @@ module Rigor
|
|
|
319
297
|
m && [m[1], m[2]]
|
|
320
298
|
end
|
|
321
299
|
|
|
322
|
-
# Normalises a message receiver token to a class name. The fold
|
|
323
|
-
# logic is shared with the selector axis — see
|
|
300
|
+
# Normalises a message receiver token to a class name. The fold logic is shared with the selector axis — see
|
|
324
301
|
# {Triage.normalize_receiver}.
|
|
325
302
|
def receiver_class(token)
|
|
326
303
|
Triage.normalize_receiver(token)
|
|
@@ -342,10 +319,9 @@ module Rigor
|
|
|
342
319
|
groups.keys.first(3).map { |method, recv| "`#{method}` on #{recv}" }.join(", ")
|
|
343
320
|
end
|
|
344
321
|
|
|
345
|
-
# `parser: :undefined_method` (default) reads the method from
|
|
346
|
-
# the
|
|
347
|
-
#
|
|
348
|
-
# `unresolved-toplevel` rule carries no receiver to parse).
|
|
322
|
+
# `parser: :undefined_method` (default) reads the method from the parsed `undefined-method` shape; `parser:
|
|
323
|
+
# :toplevel` reads the structured `method_name` field directly (the `unresolved-toplevel` rule carries no receiver
|
|
324
|
+
# to parse).
|
|
349
325
|
def top_methods(diagnostics, limit: 5, parser: :undefined_method)
|
|
350
326
|
names = diagnostics.filter_map do |d|
|
|
351
327
|
parser == :toplevel ? d.method_name : parse_undefined_method(d)&.fetch(:method)
|
data/lib/rigor/triage.rb
CHANGED
|
@@ -4,46 +4,34 @@ require_relative "triage/hint"
|
|
|
4
4
|
require_relative "triage/catalogue"
|
|
5
5
|
|
|
6
6
|
module Rigor
|
|
7
|
-
# ADR-23 — diagnostic triage. Aggregates a `rigor check`
|
|
8
|
-
#
|
|
9
|
-
# report: a rule-ID distribution, per-file hotspots, and the
|
|
10
|
-
# heuristic hint catalogue ({Triage::Catalogue}).
|
|
7
|
+
# ADR-23 — diagnostic triage. Aggregates a `rigor check` diagnostic stream into the data behind the `rigor triage`
|
|
8
|
+
# report: a rule-ID distribution, per-file hotspots, and the heuristic hint catalogue ({Triage::Catalogue}).
|
|
11
9
|
#
|
|
12
|
-
# Pure over the diagnostic stream — no second analysis pass, no
|
|
13
|
-
#
|
|
14
|
-
# rendering is {CLI::TriageRenderer}'s job.
|
|
10
|
+
# Pure over the diagnostic stream — no second analysis pass, no analyzer internals. `Triage.analyze` is the single
|
|
11
|
+
# entry point; rendering is {CLI::TriageRenderer}'s job.
|
|
15
12
|
module Triage
|
|
16
13
|
UNCATEGORISED = "(uncategorised)"
|
|
17
14
|
|
|
18
15
|
Summary = Data.define(:total, :error, :warning, :info)
|
|
19
16
|
RuleCount = Data.define(:rule, :count)
|
|
20
17
|
Hotspot = Data.define(:file, :count, :by_rule)
|
|
21
|
-
# A (receiver-class, method) dispatch target the diagnostics
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# `
|
|
25
|
-
# a `def`-side return / override finding that has no call
|
|
26
|
-
# receiver); `files` is the distinct-file count (a systemic vs.
|
|
27
|
-
# localised signal); `rules` is the per-rule breakdown.
|
|
18
|
+
# A (receiver-class, method) dispatch target the diagnostics cluster on, built from the structured
|
|
19
|
+
# `Diagnostic#receiver_type` / `#method_name` fields — never from message-string parsing. `receiver` is nil for
|
|
20
|
+
# method-only diagnostics (a toplevel call, a `def`-side return / override finding that has no call receiver);
|
|
21
|
+
# `files` is the distinct-file count (a systemic vs. localised signal); `rules` is the per-rule breakdown.
|
|
28
22
|
Selector = Data.define(:receiver, :method_name, :count, :files, :rules)
|
|
29
23
|
Report = Data.define(:summary, :distribution, :selectors, :hotspots, :hints, :include_info)
|
|
30
24
|
|
|
31
25
|
module_function
|
|
32
26
|
|
|
33
|
-
# WD6 (ADR-23): the volume views — distribution / selectors /
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# the
|
|
40
|
-
#
|
|
41
|
-
# towards the files with the *most working* code. The summary still
|
|
42
|
-
# reports the full info count, and `include_info: true` (the
|
|
43
|
-
# `--include-info` flag) restores the pre-v0.2.3 behaviour. Hints
|
|
44
|
-
# always see the full stream so the `gem-without-rbs` notice (an
|
|
45
|
-
# info-severity `rbs.coverage.missing-gem`) survives; the
|
|
46
|
-
# count-based H5/H6 recognisers guard against info themselves so
|
|
27
|
+
# WD6 (ADR-23): the volume views — distribution / selectors / hotspots — route only the *actionable* diagnostics
|
|
28
|
+
# (error + warning) by default. Plugin-emitted `:info` diagnostics are overwhelmingly recognition trace
|
|
29
|
+
# (`plugin.activerecord.model-call`, `plugin.rails-routes.helper`, …) — positive "Rigor resolved this call" records,
|
|
30
|
+
# not problems — and on a real Rails app they swamp the genuine error/warning signal (the field trip: 257 of 267
|
|
31
|
+
# diagnostics were such trace) and invert the hotspot ranking towards the files with the *most working* code. The
|
|
32
|
+
# summary still reports the full info count, and `include_info: true` (the `--include-info` flag) restores the
|
|
33
|
+
# pre-v0.2.3 behaviour. Hints always see the full stream so the `gem-without-rbs` notice (an info-severity
|
|
34
|
+
# `rbs.coverage.missing-gem`) survives; the count-based H5/H6 recognisers guard against info themselves so
|
|
47
35
|
# recognition trace never reads as a bug.
|
|
48
36
|
#
|
|
49
37
|
# @param diagnostics [Array<Analysis::Diagnostic>]
|
|
@@ -63,8 +51,8 @@ module Rigor
|
|
|
63
51
|
)
|
|
64
52
|
end
|
|
65
53
|
|
|
66
|
-
# Diagnostics without a `rule` (parse errors, internal-analyzer
|
|
67
|
-
#
|
|
54
|
+
# Diagnostics without a `rule` (parse errors, internal-analyzer errors) bucket under a single sentinel rather than
|
|
55
|
+
# vanishing.
|
|
68
56
|
def rule_key(diagnostic)
|
|
69
57
|
diagnostic.qualified_rule || UNCATEGORISED
|
|
70
58
|
end
|
|
@@ -85,15 +73,11 @@ module Rigor
|
|
|
85
73
|
.sort_by { |row| [-row.count, row.rule] }
|
|
86
74
|
end
|
|
87
75
|
|
|
88
|
-
# The class/method aggregation axis (ADR-23 follow-up). Groups
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
# Method-only diagnostics (nil `receiver_type`) keep a null
|
|
94
|
-
# `receiver` and still group by method. The full list is
|
|
95
|
-
# returned uncapped — the JSON is the agent-facing surface; the
|
|
96
|
-
# text renderer caps its own rows.
|
|
76
|
+
# The class/method aggregation axis (ADR-23 follow-up). Groups every diagnostic that carries a `method_name` by its
|
|
77
|
+
# `(receiver_type, method_name)` pair so a consumer can answer "which method / class concentrates the diagnostics?"
|
|
78
|
+
# with a `jq` query over the JSON instead of parsing message text. Method-only diagnostics (nil `receiver_type`)
|
|
79
|
+
# keep a null `receiver` and still group by method. The full list is returned uncapped — the JSON is the
|
|
80
|
+
# agent-facing surface; the text renderer caps its own rows.
|
|
97
81
|
def build_selectors(diagnostics)
|
|
98
82
|
diagnostics.select(&:method_name)
|
|
99
83
|
.group_by { |d| [normalize_receiver(d.receiver_type) || d.receiver_type, d.method_name.to_s] }
|
|
@@ -101,16 +85,12 @@ module Rigor
|
|
|
101
85
|
.sort_by { |s| [-s.count, s.receiver.to_s, s.method_name] }
|
|
102
86
|
end
|
|
103
87
|
|
|
104
|
-
# Folds a receiver token — a `Diagnostic#receiver_type` display
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
# `Array[String]` element form (the AR-relation heuristic keys on
|
|
111
|
-
# it). Returns nil for a token it cannot reduce to a class (a
|
|
112
|
-
# union display, an inferred shape) — the caller keeps the raw
|
|
113
|
-
# string then, never losing the row. Shared with {Catalogue}.
|
|
88
|
+
# Folds a receiver token — a `Diagnostic#receiver_type` display string or a message-parsed token — to the class the
|
|
89
|
+
# diagnostics should bucket under, so the selector axis does not fragment one method across every distinct literal
|
|
90
|
+
# receiver. String / integer / float / symbol literals collapse to their class; `singleton(C)` and a bare `C` fold
|
|
91
|
+
# to `C`; a generic `C[...]` keeps the `Array[String]` element form (the AR-relation heuristic keys on it). Returns
|
|
92
|
+
# nil for a token it cannot reduce to a class (a union display, an inferred shape) — the caller keeps the raw string
|
|
93
|
+
# then, never losing the row. Shared with {Catalogue}.
|
|
114
94
|
def normalize_receiver(token)
|
|
115
95
|
return nil if token.nil?
|
|
116
96
|
|
|
@@ -170,9 +150,8 @@ module Rigor
|
|
|
170
150
|
{ "file" => h.file, "count" => h.count, "by_rule" => h.by_rule }
|
|
171
151
|
end,
|
|
172
152
|
"hints" => report.hints.map(&:to_h),
|
|
173
|
-
# WD6: false means distribution / selectors / hotspots above
|
|
174
|
-
#
|
|
175
|
-
# the summary's `info` field still reports the full count.
|
|
153
|
+
# WD6: false means distribution / selectors / hotspots above exclude `:info` (their counts will not sum to
|
|
154
|
+
# summary.total); the summary's `info` field still reports the full count.
|
|
176
155
|
"include_info" => report.include_info
|
|
177
156
|
}
|
|
178
157
|
end
|