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.
Files changed (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -5,71 +5,56 @@ require "prism"
5
5
  module Rigor
6
6
  module Analysis
7
7
  module CheckRules
8
- # Walks a parse tree and collects every `LocalVariableWriteNode`
9
- # inside a `DefNode` body whose target name is **never read**
10
- # within the same body.
8
+ # Walks a parse tree and collects every `LocalVariableWriteNode` inside a `DefNode` body whose target
9
+ # name is **never read** within the same body.
11
10
  #
12
- # The collector is the read-side companion to the
13
- # `def.dead-assignment` rule. v0.1.2 ships the narrowest
14
- # envelope that catches the most common typo / refactoring-
15
- # leftover shape ("wrote and never used") without surfacing
16
- # false positives:
11
+ # The collector is the read-side companion to the `def.dead-assignment` rule. v0.1.2 ships the
12
+ # narrowest envelope that catches the most common typo / refactoring-leftover shape ("wrote and never
13
+ # used") without surfacing false positives:
17
14
  #
18
- # - Only `Prism::DefNode` bodies are scanned. Top-level
19
- # scripts and class-body assignments are skipped their
20
- # variable surface bleeds across requires / introspection
21
- # in ways the rule cannot reason about.
22
- # - Only plain `LocalVariableWriteNode` writes are
23
- # considered. Operator-writes (`x += 1`), and-/or-writes
24
- # (`x ||= 1`), and `MultiWriteNode` destructures (`a, b =
25
- # foo`) are skipped because their write semantics are
26
- # intertwined with reads or with a wider tuple binding.
27
- # - The "is this name ever read" question is answered by
28
- # any `LocalVariableReadNode` anywhere in the def
29
- # subtree — so a closure capture, a `return x`, a
30
- # block-call argument, an interpolated string all count
31
- # as a read.
32
- # - A write whose name starts with `_` is skipped per the
33
- # Ruby convention that `_` / `_foo` declares
15
+ # - Only `Prism::DefNode` bodies are scanned. Top-level scripts and class-body assignments are
16
+ # skipped their variable surface bleeds across requires / introspection in ways the rule cannot
17
+ # reason about.
18
+ # - Only plain `LocalVariableWriteNode` writes are considered. Operator-writes (`x += 1`), and-/or-writes
19
+ # (`x ||= 1`), and `MultiWriteNode` destructures (`a, b = foo`) are skipped because their write
20
+ # semantics are intertwined with reads or with a wider tuple binding.
21
+ # - The "is this name ever read" question is answered by any `LocalVariableReadNode` anywhere in the
22
+ # def subtree — so a closure capture, a `return x`, a block-call argument, an interpolated string all
23
+ # count as a read.
24
+ # - A write whose name starts with `_` is skipped per the Ruby convention that `_` / `_foo` declares
34
25
  # "intentionally unused".
35
- # - The last statement of a def body is skipped — Ruby's
36
- # implicit return treats `def foo; x = 1; end` as
37
- # returning `1`, so the trailing write is intentional.
26
+ # - The last statement of a def body is skipped — Ruby's implicit return treats `def foo; x = 1; end`
27
+ # as returning `1`, so the trailing write is intentional.
38
28
  class DeadAssignmentCollector
39
- # ADR-53 Track B — the node classes the shared {RuleWalk}
40
- # dispatches to this collector. The legacy walk visits EVERY
41
- # `DefNode` (it descends into all of them, nested defs included,
42
- # processing each separately `gather_*` barrier at nested defs
43
- # so an outer def never sees an inner def's locals), so the
44
- # collector needs no context gate: it fires at every `def` the
45
- # shared full DFS reaches, exactly like the legacy walk.
29
+ # ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector. The legacy
30
+ # walk visits EVERY `DefNode` (it descends into all of them, nested defs included, processing each
31
+ # separately — `gather_*` barrier at nested defs so an outer def never sees an inner def's locals),
32
+ # so the collector needs no context gate: it fires at every `def` the shared full DFS reaches,
33
+ # exactly like the legacy walk.
46
34
  NODE_CLASSES = [Prism::DefNode].freeze
47
35
 
48
- # Returns `Array<{def_class:, def_name:, write_node:}>`
49
- # one entry per dead-assignment write. Empty when
50
- # the tree has no qualifying writes.
36
+ # Returns `Array<{def_class:, def_name:, write_node:}>` — one entry per dead-assignment write. Empty
37
+ # when the tree has no qualifying writes.
51
38
  def initialize(scope_index)
52
39
  @scope_index = scope_index
53
40
  @results = []
54
41
  end
55
42
 
56
- # Legacy single-collector walk — kept as the oracle the ADR-53
57
- # Track B equivalence harness compares {RuleWalk} against; deleted
58
- # when Track B completes.
43
+ # Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
44
+ # {RuleWalk} against; deleted when Track B completes.
59
45
  def collect(root)
60
46
  walk_for_def_nodes(root)
61
47
  @results.freeze
62
48
  end
63
49
 
64
- # {RuleWalk} entry point: the legacy walk's per-`def` logic,
65
- # invoked at every `def` under the shared traversal contract. The
66
- # `context` is unused — this collector processes all defs.
50
+ # {RuleWalk} entry point: the legacy walk's per-`def` logic, invoked at every `def` under the shared
51
+ # traversal contract. The `context` is unused this collector processes all defs.
67
52
  def visit(def_node, _context = nil)
68
53
  collect_def_assignments(def_node)
69
54
  end
70
55
 
71
- # The accumulated result, frozen the same way `#collect` returns
72
- # it — used by {RuleWalk}-driven callers after the walk completes.
56
+ # The accumulated result, frozen the same way `#collect` returns it — used by {RuleWalk}-driven
57
+ # callers after the walk completes.
73
58
  def results
74
59
  @results.freeze
75
60
  end
@@ -103,9 +88,8 @@ module Rigor
103
88
  return accumulator unless node.is_a?(Prism::Node)
104
89
 
105
90
  accumulator << node.name if node.is_a?(Prism::LocalVariableReadNode)
106
- # Operator/and/or-writes implicitly read the prior
107
- # binding count them too so `x = 1; x ||= 2; x` /
108
- # similar shapes don't trip the rule.
91
+ # Operator/and/or-writes implicitly read the prior binding — count them too so `x = 1; x ||= 2; x`
92
+ # / similar shapes don't trip the rule.
109
93
  accumulator << node.name if reading_assignment?(node)
110
94
 
111
95
  node.compact_child_nodes.each { |child| gather_read_names(child, accumulator) }
@@ -122,8 +106,7 @@ module Rigor
122
106
  return accumulator unless node.is_a?(Prism::Node)
123
107
 
124
108
  accumulator << node if node.is_a?(Prism::LocalVariableWriteNode)
125
- # Don't recurse into nested DefNodes — their bodies
126
- # carry their own dead-assignment scope and the
109
+ # Don't recurse into nested DefNodes — their bodies carry their own dead-assignment scope and the
127
110
  # outer walker visits them separately.
128
111
  return accumulator if node.is_a?(Prism::DefNode) && !accumulator.last.equal?(node)
129
112
 
@@ -131,10 +114,8 @@ module Rigor
131
114
  accumulator
132
115
  end
133
116
 
134
- # Returns the final statement of a body node, descending
135
- # into wrappers Ruby preserves verbatim (`begin ... end`
136
- # blocks). Used to skip the implicit-return write at the
137
- # tail of a method body.
117
+ # Returns the final statement of a body node, descending into wrappers Ruby preserves verbatim
118
+ # (`begin ... end` blocks). Used to skip the implicit-return write at the tail of a method body.
138
119
  def trailing_statement(body)
139
120
  case body
140
121
  when Prism::StatementsNode then body.body.last
@@ -7,70 +7,56 @@ require_relative "../../source/constant_path"
7
7
  module Rigor
8
8
  module Analysis
9
9
  module CheckRules
10
- # Walks a parse tree and collects every
11
- # `Prism::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?