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.
Files changed (433) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/core_overlay/csv.rbs +28 -0
  4. data/data/core_overlay/psych.rbs +22 -0
  5. data/docs/handbook/01-getting-started.md +9 -1
  6. data/docs/handbook/02-everyday-types.md +4 -1
  7. data/docs/handbook/08-understanding-errors.md +3 -3
  8. data/docs/manual/01-installation.md +1 -0
  9. data/docs/manual/02-cli-reference.md +16 -7
  10. data/docs/manual/04-diagnostics.md +7 -4
  11. data/docs/manual/06-baseline.md +5 -2
  12. data/docs/manual/07-plugins.md +1 -1
  13. data/docs/manual/14-rails-quickstart.md +4 -2
  14. data/docs/manual/15-type-protection-coverage.md +27 -4
  15. data/docs/manual/plugins/rigor-actionpack.md +1 -1
  16. data/docs/manual/plugins/rigor-activerecord.md +12 -5
  17. data/docs/manual/plugins/rigor-rails-routes.md +11 -0
  18. data/lib/rigor/analysis/baseline.rb +56 -96
  19. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  20. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  21. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  22. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  23. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  24. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  25. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  26. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  27. data/lib/rigor/analysis/check_rules.rb +174 -274
  28. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  29. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  30. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  31. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  32. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  33. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  34. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  35. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  36. data/lib/rigor/analysis/diagnostic.rb +48 -75
  37. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  38. data/lib/rigor/analysis/fact_store.rb +12 -22
  39. data/lib/rigor/analysis/incremental.rb +47 -63
  40. data/lib/rigor/analysis/incremental_session.rb +74 -100
  41. data/lib/rigor/analysis/project_scan.rb +12 -23
  42. data/lib/rigor/analysis/result.rb +3 -5
  43. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  44. data/lib/rigor/analysis/run_stats.rb +38 -61
  45. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  48. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  49. data/lib/rigor/analysis/runner.rb +220 -333
  50. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  51. data/lib/rigor/analysis/worker_session.rb +64 -102
  52. data/lib/rigor/ast/type_node.rb +6 -8
  53. data/lib/rigor/ast.rb +6 -10
  54. data/lib/rigor/bleeding_edge.rb +27 -35
  55. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  56. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  57. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  58. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  59. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  60. data/lib/rigor/cache/descriptor.rb +58 -94
  61. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  62. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  63. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  64. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  65. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  66. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  67. data/lib/rigor/cache/rbs_environment.rb +11 -19
  68. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  69. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  70. data/lib/rigor/cache/store.rb +248 -186
  71. data/lib/rigor/cli/annotate_command.rb +51 -88
  72. data/lib/rigor/cli/baseline_command.rb +10 -17
  73. data/lib/rigor/cli/check_command.rb +121 -199
  74. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  75. data/lib/rigor/cli/ci_detector.rb +9 -14
  76. data/lib/rigor/cli/command.rb +8 -14
  77. data/lib/rigor/cli/coverage_command.rb +62 -48
  78. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  79. data/lib/rigor/cli/coverage_report.rb +0 -2
  80. data/lib/rigor/cli/coverage_scan.rb +7 -11
  81. data/lib/rigor/cli/diagnostic_formats.rb +46 -62
  82. data/lib/rigor/cli/diff_command.rb +10 -20
  83. data/lib/rigor/cli/docs_command.rb +26 -40
  84. data/lib/rigor/cli/doctor_command.rb +8 -9
  85. data/lib/rigor/cli/explain_command.rb +4 -7
  86. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  87. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  88. data/lib/rigor/cli/lsp_command.rb +11 -19
  89. data/lib/rigor/cli/mcp_command.rb +4 -6
  90. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  91. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  92. data/lib/rigor/cli/options.rb +11 -19
  93. data/lib/rigor/cli/plugin_command.rb +18 -30
  94. data/lib/rigor/cli/plugins_command.rb +29 -51
  95. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  96. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  97. data/lib/rigor/cli/protection_fork_scan.rb +55 -0
  98. data/lib/rigor/cli/protection_renderer.rb +5 -7
  99. data/lib/rigor/cli/protection_report.rb +34 -18
  100. data/lib/rigor/cli/renderable.rb +4 -6
  101. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  102. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  103. data/lib/rigor/cli/skill_command.rb +30 -47
  104. data/lib/rigor/cli/skill_describe.rb +40 -64
  105. data/lib/rigor/cli/trace_command.rb +9 -16
  106. data/lib/rigor/cli/trace_renderer.rb +35 -51
  107. data/lib/rigor/cli/triage_command.rb +6 -10
  108. data/lib/rigor/cli/triage_renderer.rb +1 -2
  109. data/lib/rigor/cli/type_of_command.rb +14 -23
  110. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  111. data/lib/rigor/cli/type_scan_command.rb +6 -10
  112. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  113. data/lib/rigor/cli/type_scan_report.rb +2 -3
  114. data/lib/rigor/cli/upgrade_command.rb +2 -3
  115. data/lib/rigor/cli.rb +16 -28
  116. data/lib/rigor/config_audit.rb +35 -44
  117. data/lib/rigor/configuration/dependencies.rb +41 -70
  118. data/lib/rigor/configuration/severity_profile.rb +28 -42
  119. data/lib/rigor/configuration.rb +150 -241
  120. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  121. data/lib/rigor/environment/class_registry.rb +19 -26
  122. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  123. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  124. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  125. data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
  126. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  127. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  128. data/lib/rigor/environment/rbs_loader.rb +420 -518
  129. data/lib/rigor/environment/reflection.rb +28 -53
  130. data/lib/rigor/environment/reporters.rb +13 -24
  131. data/lib/rigor/environment.rb +225 -304
  132. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  133. data/lib/rigor/flow_contribution/element.rb +7 -12
  134. data/lib/rigor/flow_contribution/fact.rb +33 -57
  135. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  136. data/lib/rigor/flow_contribution/merger.rb +32 -47
  137. data/lib/rigor/flow_contribution.rb +37 -55
  138. data/lib/rigor/inference/acceptance.rb +133 -219
  139. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  140. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  141. data/lib/rigor/inference/budget_trace.rb +43 -63
  142. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  143. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  144. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  145. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  146. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  147. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  148. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  149. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  150. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  151. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  152. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  153. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  154. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  155. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  156. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  157. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  158. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  159. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  160. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  161. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  162. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  163. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  164. data/lib/rigor/inference/def_return_typer.rb +8 -14
  165. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  166. data/lib/rigor/inference/expression_typer.rb +794 -1154
  167. data/lib/rigor/inference/fallback.rb +8 -12
  168. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  169. data/lib/rigor/inference/flow_tracer.rb +25 -36
  170. data/lib/rigor/inference/fork_map.rb +87 -0
  171. data/lib/rigor/inference/hkt_body.rb +45 -68
  172. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  173. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  174. data/lib/rigor/inference/hkt_registry.rb +50 -76
  175. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  176. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  177. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  178. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  179. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  180. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  181. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  182. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  183. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  184. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  185. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  186. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  187. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  188. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  189. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  190. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  191. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  192. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  193. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  194. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  195. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  196. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  197. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  198. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  199. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  200. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  201. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  202. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  203. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  204. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  205. data/lib/rigor/inference/mutation_widening.rb +115 -158
  206. data/lib/rigor/inference/narrowing.rb +627 -727
  207. data/lib/rigor/inference/origin_lookup.rb +38 -0
  208. data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
  209. data/lib/rigor/inference/precision_scanner.rb +26 -35
  210. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  211. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  212. data/lib/rigor/inference/protection_scanner.rb +16 -18
  213. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  214. data/lib/rigor/inference/scope_indexer.rb +473 -821
  215. data/lib/rigor/inference/statement_evaluator.rb +754 -1114
  216. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  217. data/lib/rigor/inference/synthetic_method.rb +9 -16
  218. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  219. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  220. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  221. data/lib/rigor/language_server/buffer_table.rb +11 -18
  222. data/lib/rigor/language_server/completion_provider.rb +69 -116
  223. data/lib/rigor/language_server/debouncer.rb +16 -25
  224. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  225. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  226. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  227. data/lib/rigor/language_server/hover_provider.rb +8 -11
  228. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  229. data/lib/rigor/language_server/loop.rb +14 -19
  230. data/lib/rigor/language_server/project_context.rb +46 -74
  231. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  232. data/lib/rigor/language_server/server.rb +55 -83
  233. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  234. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  235. data/lib/rigor/language_server/uri.rb +8 -13
  236. data/lib/rigor/language_server.rb +4 -6
  237. data/lib/rigor/mcp/loop.rb +2 -3
  238. data/lib/rigor/mcp/server.rb +4 -7
  239. data/lib/rigor/mcp.rb +3 -6
  240. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  241. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  242. data/lib/rigor/plugin/base.rb +335 -518
  243. data/lib/rigor/plugin/blueprint.rb +14 -23
  244. data/lib/rigor/plugin/box.rb +18 -29
  245. data/lib/rigor/plugin/fact_store.rb +16 -26
  246. data/lib/rigor/plugin/inflector.rb +37 -53
  247. data/lib/rigor/plugin/io_boundary.rb +33 -56
  248. data/lib/rigor/plugin/isolation.rb +42 -55
  249. data/lib/rigor/plugin/load_error.rb +10 -15
  250. data/lib/rigor/plugin/loader.rb +30 -49
  251. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  252. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  253. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  254. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  255. data/lib/rigor/plugin/macro.rb +10 -15
  256. data/lib/rigor/plugin/manifest.rb +85 -144
  257. data/lib/rigor/plugin/node_context.rb +14 -22
  258. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  259. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  260. data/lib/rigor/plugin/registry.rb +132 -205
  261. data/lib/rigor/plugin/services.rb +21 -33
  262. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  263. data/lib/rigor/plugin/trust_policy.rb +24 -38
  264. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  265. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  266. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  267. data/lib/rigor/protection/mutator.rb +50 -70
  268. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  269. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  270. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  271. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  272. data/lib/rigor/rbs_extended.rb +107 -197
  273. data/lib/rigor/reflection.rb +68 -86
  274. data/lib/rigor/scope/discovery_index.rb +14 -19
  275. data/lib/rigor/scope.rb +255 -310
  276. data/lib/rigor/sig_gen/classification.rb +6 -10
  277. data/lib/rigor/sig_gen/generator.rb +197 -323
  278. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  279. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  280. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  281. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  282. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  283. data/lib/rigor/sig_gen/renderer.rb +7 -13
  284. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  285. data/lib/rigor/sig_gen/write_result.rb +8 -16
  286. data/lib/rigor/sig_gen/writer.rb +95 -174
  287. data/lib/rigor/sig_gen.rb +3 -6
  288. data/lib/rigor/signature_path_audit.rb +24 -30
  289. data/lib/rigor/source/constant_path.rb +10 -14
  290. data/lib/rigor/source/literals.rb +31 -45
  291. data/lib/rigor/source/node_locator.rb +9 -11
  292. data/lib/rigor/source/node_walker.rb +9 -13
  293. data/lib/rigor/source.rb +3 -4
  294. data/lib/rigor/testing.rb +16 -20
  295. data/lib/rigor/triage/catalogue.rb +38 -62
  296. data/lib/rigor/triage.rb +31 -52
  297. data/lib/rigor/trinary.rb +9 -13
  298. data/lib/rigor/type/acceptance_router.rb +4 -6
  299. data/lib/rigor/type/accepts_result.rb +10 -14
  300. data/lib/rigor/type/app.rb +19 -27
  301. data/lib/rigor/type/bot.rb +4 -6
  302. data/lib/rigor/type/bound_method.rb +10 -15
  303. data/lib/rigor/type/combinator.rb +165 -257
  304. data/lib/rigor/type/constant.rb +23 -34
  305. data/lib/rigor/type/data_class.rb +10 -15
  306. data/lib/rigor/type/data_instance.rb +14 -20
  307. data/lib/rigor/type/difference.rb +21 -32
  308. data/lib/rigor/type/dynamic.rb +3 -5
  309. data/lib/rigor/type/hash_shape.rb +32 -18
  310. data/lib/rigor/type/integer_range.rb +11 -16
  311. data/lib/rigor/type/intersection.rb +27 -42
  312. data/lib/rigor/type/nominal.rb +10 -15
  313. data/lib/rigor/type/plain_lattice.rb +9 -13
  314. data/lib/rigor/type/refined.rb +67 -114
  315. data/lib/rigor/type/singleton.rb +4 -6
  316. data/lib/rigor/type/struct_class.rb +11 -16
  317. data/lib/rigor/type/struct_instance.rb +15 -21
  318. data/lib/rigor/type/tuple.rb +14 -19
  319. data/lib/rigor/type/union.rb +30 -42
  320. data/lib/rigor/type_node/generic.rb +14 -26
  321. data/lib/rigor/type_node/identifier.rb +12 -19
  322. data/lib/rigor/type_node.rb +3 -12
  323. data/lib/rigor/value_semantics.rb +16 -21
  324. data/lib/rigor/version.rb +1 -1
  325. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
  337. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  340. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
  348. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  350. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  351. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  353. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  354. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  355. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  356. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
  357. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  358. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  359. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  361. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  363. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  365. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  366. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  367. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  368. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  369. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  372. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  373. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  376. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  377. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  378. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  379. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  380. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  381. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  385. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  386. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  389. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  390. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
  398. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
  399. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  400. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  406. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  408. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  409. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  411. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  416. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  417. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  426. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  427. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  428. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  429. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  430. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  431. data/sig/rigor/environment.rbs +1 -0
  432. data/sig/rigor/scope.rbs +6 -0
  433. 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::InstanceVariableWriteNode` inside an instance
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
- # `Inference::ScopeIndexer#build_class_ivar_index`. The
17
- # indexer unions the rvalue types into a single per-ivar
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
- # live on the class object, not on instances.
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
- # dispatches to this collector, and the context gate under which
38
- # the walk suppresses it. The legacy walk descends only through
39
- # class / module bodies and `return`s at the first `def` it meets,
40
- # so it collects ivar writes only from a `def` that sits directly
41
- # in a class / module body, never one nested in another `def`. On
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
- # (Symbol) => Array<{node:, type:}>]]`. Empty when the
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
- # Track B equivalence harness compares {RuleWalk} against; deleted
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
- # invoked at each class-/module-body `def` under the shared
66
- # traversal contract. `collect_def_writes`' verbatim body reads the
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
- # it — used by {RuleWalk}-driven callers after the walk completes.
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
- # pass on the shared {RuleWalk} instead of its own
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
- # reads only `scope_index[node]` and decides, with NO loop / block
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
- # `case` from `diagnose`'s former inline walk only the traversal
19
- # moved, ADR-53 WD4) and is handed in as a callable built in the
20
- # `CheckRules` module context, so its calls to the private
21
- # diagnostic builders remain implicit-self. The collector only owns
22
- # the traversal hook and accumulation. Unlike the fact collectors
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
- # `Prism::IfNode` covers ternaries and postfix `if` too (the
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
- # array of diagnostics the main pass emits for it.
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
- # `NodeWalker.each` `case`, invoked under the shared traversal.
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
- # per-node collectors, so a file is traversed once for all of them
12
- # instead of once per collector (the ADR-52 WD4 model applied to
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
- # `compact_child_nodes`. It threads the union of the context the
18
- # hosted collectors need the per-collector hand-rolled walks each
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
- # body (the two flow collectors suppress collection there because
24
- # mutation tracking through those is incomplete).
25
- # - `qualified_prefix` the enclosing class / module name stack
26
- # ({IvarWriteCollector} builds its `class_name` from it).
27
- # - `inside_def` whether the node is lexically inside a `DefNode`
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 it cares about Prism node classes are leaves, so a
34
- # class-equality dispatch matches the collectors' `is_a?` gates),
35
- # optionally `RULE_WALK_GATES` (the {Context}-derived suppressions
36
- # the walk applies before calling it), and `#visit(node, context)`
37
- # with its per-node logic transplanted verbatim. Only the
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
- # legacy single-collector `#collect` walk as the oracle, compared
43
- # against this walk by the permanent `rule_walk_equivalence_spec`
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
- # child from its parent's context as the walk descends; collectors
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
- # The walk skips a collector's `#visit` for a node when any of the
64
- # collector's declared gates holds in that node's context exactly
65
- # reproducing the per-collector legacy walk's traversal suppression.
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
- # body, the two flow collectors' envelope.
71
- # - `:inside_def` (`inside_def`) — lexically inside a `DefNode`;
72
- # IvarWrite collects only at a `def` that sits directly in a
73
- # class / module body (its legacy walk `return`s at the first
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
- # `node_class => [[collector, gates], …]` hook table and applies
82
- # the dispatch + context-descent that {RuleWalk.run} performs, but
83
- # one node at a time. This lets a foreign traversal (the converged
84
- # {Plugin::NodeRuleWalk}, ADR-53 B4) drive the built-in collectors
85
- # from inside its own single walk: it calls {#visit} on each node
86
- # with that node's {Context}, and derives a child's context with
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
- # un-gated collector. Mirrors {RuleWalk.dispatch}.
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
- # collector whose `NODE_CLASSES` covers the node's class and
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
- # the node's own context. `in_loop_or_block` latches on once a
140
- # loop / block is entered; `qualified_prefix` extends only on a
141
- # nameable class / module (an unnamed one descends with the same
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
- # Walks a file's parse tree once and collects the qualified names of
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
- # provided by includers (template-method pattern), so an unresolved
17
- # self-call inside it is not provably a typo.
18
- # - **Classes with a dynamic `attr_*` accessor** `attr_reader(*NAMES)`
19
- # / `attr_accessor(:a, SOME_CONST)` synthesizes readers whose names
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
- # as the call (even a reopened class restates `class Foo`), so a
25
- # per-file AST scan suffices no cross-file plumbing. Names are built
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
- # a dynamic `attr_*` accessor, or a non-constant (dynamically produced)
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
- # non-literal argument (a splat, a constant, a method call) — the
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
- # constant `class X < DelegateClass(Array)` / `< Struct.new(...)` /
95
- # `< Data.define(...)`. The inherited surface is then a dynamically
96
- # produced class the engine cannot enumerate from a constant name (the
97
- # superclass is a method call, so `discovered_superclasses` never
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
- # engine already narrows the case subject across `when` branches
10
- # (`Narrowing.case_when_scopes`), and the per-clause `body_scope` it
11
- # produces is recorded in `scope_index` keyed on the `WhenNode` (the
12
- # evaluator enters the clause under that scope). So a clause whose
13
- # narrowed subject local is `Type::Bot` is one the engine's own
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
- # only shape `case_when_scopes` narrows — no narrowing ⇒ no firing),
22
- # - every `when` condition is a class/module constant (`when String` /
23
- # `when MyClass`), the shape the narrowing recognises `when nil` /
24
- # ranges / regexps / arbitrary expressions are out of scope here,
25
- # - the subject's type at case entry is a concrete carrier, never
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
- # clauses inside loops / blocks are skipped (mutation tracking through
33
- # those is incomplete), and the rule reads the engine's own narrowing
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
- # deliberate defensive guard ("this should be unreachable shout
43
- # if it ever isn't"), NOT dead code the author wants removed.
44
- # Flagging it would push the author to delete a safety net, which
45
- # the false-positive discipline forbids. WD1's `when` shapes don't
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
- # dispatches to this collector, and the context gate under which
60
- # the walk suppresses it (loop / block bodies see the envelope
61
- # above). The legacy walk's `!in_loop_or_block` guard is now the
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
- # ADR-53 Track B equivalence harness compares {RuleWalk}
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
- # invoked under the same traversal contract. The `context` is
82
- # unused the loop / block suppression this collector relied on
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
- # carry `predicate` + `conditions` + `else_clause` and the engine
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
- # can never be proven disjoint (the gradual guarantee), and an
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
- # already narrowed to `bot` BEFORE this clause (an earlier clause
156
- # covered it) read from the entry scope the engine recorded on
157
- # the clause's first condition node (ADR-47 WD2). Otherwise the
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
- # `bot` either because a bare class pattern (`in C` / `in C => x`)
169
- # is disjoint from the still-possible subject (the engine narrows
170
- # those soundly, like `when C`), or because an earlier clause
171
- # already exhausted the subject (prior-exhaustion true for ANY
172
- # pattern downstream of a covering set). Non-bare patterns are not
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
- # by the `when` clauses the final falsey scope (recorded on the
198
- # `else` node) narrows the subject to `bot`. Defensive `else`
199
- # bodies (a bare `raise` / `fail` / `throw`) are deliberate
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)