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
@@ -6,9 +6,8 @@ module Rigor
6
6
  module Analysis
7
7
  # ADR-22 Slice 1 — PHPStan-shaped per-project baseline.
8
8
  #
9
- # Loads `.rigor-baseline.yml`, filters a current run's
10
- # diagnostic stream against the recorded buckets, and emits
11
- # an `(surfaced, silenced_count)` pair for the CLI to render.
9
+ # Loads `.rigor-baseline.yml`, filters a current run's diagnostic stream against the recorded buckets, and
10
+ # emits an `(surfaced, silenced_count)` pair for the CLI to render.
12
11
  #
13
12
  # Two row shapes are accepted (WD1):
14
13
  #
@@ -27,17 +26,13 @@ module Rigor
27
26
  # ## Semantics per (file, rule [, message]) bucket (WD4)
28
27
  #
29
28
  # actual <= count → ALL diagnostics in the bucket are silenced.
30
- # actual > count → ALL diagnostics in the bucket surface
31
- # (not just the excess delta the bucket
32
- # has crossed its threshold; the team's
33
- # review focus shifts from "which N is new"
34
- # to "what's going on with this rule in
35
- # this file as a whole").
29
+ # actual > count → ALL diagnostics in the bucket surface (not just the excess delta — the bucket has
30
+ # crossed its threshold; the team's review focus shifts from "which N is new" to
31
+ # "what's going on with this rule in this file as a whole").
36
32
  #
37
33
  # ## Filter pipeline position (WD6)
38
34
  #
39
- # The baseline filter runs LAST among the diagnostic-suppression
40
- # layers:
35
+ # The baseline filter runs LAST among the diagnostic-suppression layers:
41
36
  #
42
37
  # emit → `# rigor:disable` (per-line)
43
38
  # → `# rigor:disable-file`
@@ -47,41 +42,32 @@ module Rigor
47
42
  #
48
43
  # ## Loading (WD2 (b))
49
44
  #
50
- # `Baseline.load` is called by the CLI when it has resolved
51
- # an explicit baseline path (from `--baseline=PATH` on the
52
- # CLI or `baseline: <path>` in `.rigor.yml`). The presence
53
- # of `.rigor-baseline.yml` on disk alone never triggers a
54
- # load — that's the CLI / Configuration's job to enforce.
45
+ # `Baseline.load` is called by the CLI when it has resolved an explicit baseline path (from `--baseline=PATH`
46
+ # on the CLI or `baseline: <path>` in `.rigor.yml`). The presence of `.rigor-baseline.yml` on disk alone
47
+ # never triggers a load — that's the CLI / Configuration's job to enforce.
55
48
  #
56
49
  # ## Path handling
57
50
  #
58
- # Baselines store file paths **relative to the project root**
59
- # (the working directory when `rigor` is run). This makes the
60
- # generated `.rigor-baseline.yml` portable across machines and
61
- # checkout locations. When filtering a live diagnostic stream,
62
- # the instance normalises each diagnostic's absolute path to a
63
- # relative one before the bucket lookup.
51
+ # Baselines store file paths **relative to the project root** (the working directory when `rigor` is run).
52
+ # This makes the generated `.rigor-baseline.yml` portable across machines and checkout locations. When
53
+ # filtering a live diagnostic stream, the instance normalises each diagnostic's absolute path to a relative
54
+ # one before the bucket lookup.
64
55
  class Baseline
65
- # The bucket key is intentionally tuple-shaped so rule-ID
66
- # rows and message-pattern rows can coexist in a single
67
- # multimap. `message` is `nil` for rule-ID rows; a Regexp
68
- # for message-pattern rows.
69
- # `count` shadows Struct#count; intentional — `count` is the
70
- # PHPStan-compatible field name and we don't use the
71
- # Enumerable-style `Struct#count` on Bucket instances.
56
+ # The bucket key is intentionally tuple-shaped so rule-ID rows and message-pattern rows can coexist in a
57
+ # single multimap. `message` is `nil` for rule-ID rows; a Regexp for message-pattern rows.
58
+ # `count` shadows Struct#count; intentional — `count` is the PHPStan-compatible field name and we don't
59
+ # use the Enumerable-style `Struct#count` on Bucket instances.
72
60
  Bucket = Struct.new(:file, :rule, :message_regex, :count, keyword_init: true) # rubocop:disable Lint/StructNewOverride
73
61
 
74
62
  CURRENT_VERSION = 1
75
63
 
76
64
  class << self
77
- # Load a baseline file from disk. Returns `nil` when the
78
- # path is nil (the caller's "no baseline configured"
79
- # state). Raises {LoadError} on malformed content;
80
- # callers translate to a user-facing diagnostic.
65
+ # Load a baseline file from disk. Returns `nil` when the path is nil (the caller's "no baseline
66
+ # configured" state). Raises {LoadError} on malformed content; callers translate to a user-facing
67
+ # diagnostic.
81
68
  #
82
- # `project_root:` is the working directory against which
83
- # stored relative paths are resolved during filtering.
84
- # Defaults to `Dir.pwd`.
69
+ # `project_root:` is the working directory against which stored relative paths are resolved during
70
+ # filtering. Defaults to `Dir.pwd`.
85
71
  def load(path, project_root: Dir.pwd)
86
72
  return nil if path.nil?
87
73
  return new([], project_root: project_root) unless File.exist?(path)
@@ -90,15 +76,12 @@ module Rigor
90
76
  parse_loaded(raw, path: path, project_root: project_root)
91
77
  end
92
78
 
93
- # Build a baseline from a current run's diagnostic stream.
94
- # `match_mode:` is `:rule` (default) or `:message`. The
95
- # message-mode generator passes literal messages through
96
- # `Regexp.escape` so generated rows never accidentally
97
- # over-match on punctuation.
79
+ # Build a baseline from a current run's diagnostic stream. `match_mode:` is `:rule` (default) or
80
+ # `:message`. The message-mode generator passes literal messages through `Regexp.escape` so generated
81
+ # rows never accidentally over-match on punctuation.
98
82
  #
99
- # `project_root:` is used to convert absolute diagnostic
100
- # paths to relative paths in the generated YAML. Defaults
101
- # to `Dir.pwd`.
83
+ # `project_root:` is used to convert absolute diagnostic paths to relative paths in the generated YAML.
84
+ # Defaults to `Dir.pwd`.
102
85
  def from_diagnostics(diagnostics, match_mode: :rule, project_root: Dir.pwd)
103
86
  raise ArgumentError, "match_mode must be :rule or :message" unless %i[rule message].include?(match_mode)
104
87
 
@@ -177,11 +160,9 @@ module Rigor
177
160
  end
178
161
  end
179
162
 
180
- # Generates a Regexp source string for the baseline row.
181
- # The string is `Regexp.escape`d so the YAML round-trip
182
- # produces a regex that matches the literal message.
183
- # Users hand-editing the row can replace the escaped
184
- # form with a pattern.
163
+ # Generates a Regexp source string for the baseline row. The string is `Regexp.escape`d so the YAML
164
+ # round-trip produces a regex that matches the literal message. Users hand-editing the row can replace
165
+ # the escaped form with a pattern.
185
166
  def message_pattern_for(message)
186
167
  Regexp.new(Regexp.escape(message.to_s))
187
168
  end
@@ -203,10 +184,8 @@ module Rigor
203
184
  # For each (file, qualified_rule) pair, two arrays:
204
185
  # - rule-ID rows (message_regex == nil)
205
186
  # - message-pattern rows (message_regex != nil)
206
- # The matcher walks message-pattern rows first (tighter
207
- # match takes precedence); diagnostics that don't match
208
- # any message row fall through to the rule-ID row if
209
- # one exists.
187
+ # The matcher walks message-pattern rows first (tighter match takes precedence); diagnostics that
188
+ # don't match any message row fall through to the rule-ID row if one exists.
210
189
  @by_pair = buckets.group_by { |b| [b.file, b.rule] }.freeze
211
190
  freeze
212
191
  end
@@ -214,10 +193,8 @@ module Rigor
214
193
  # Apply the baseline filter to a diagnostic stream.
215
194
  #
216
195
  # Returns a 2-tuple:
217
- # - `surfaced` — the diagnostics that survived the filter
218
- # (new findings + entire over-threshold buckets).
219
- # - `silenced_count` — how many diagnostics the baseline
220
- # suppressed (for the WD7 stderr summary line).
196
+ # - `surfaced` — the diagnostics that survived the filter (new findings + entire over-threshold buckets).
197
+ # - `silenced_count` — how many diagnostics the baseline suppressed (for the WD7 stderr summary line).
221
198
  def filter(diagnostics)
222
199
  return [diagnostics, 0] if buckets.empty?
223
200
 
@@ -228,10 +205,8 @@ module Rigor
228
205
  grouped.each_value do |entries|
229
206
  bucket = entries[:bucket]
230
207
  diags = entries[:diagnostics]
231
- # No matching bucket → all surface as new findings.
232
- # `actual <= count` → all silenced (within threshold,
233
- # WD4). `actual > count` → all surface (over
234
- # threshold, WD4).
208
+ # No matching bucket → all surface as new findings. `actual <= count` → all silenced (within
209
+ # threshold, WD4). `actual > count` → all surface (over threshold, WD4).
235
210
  if bucket && diags.size <= bucket.count
236
211
  silenced_count += diags.size
237
212
  else
@@ -239,39 +214,31 @@ module Rigor
239
214
  end
240
215
  end
241
216
 
242
- # Diagnostics that lacked a rule or a path bypass the
243
- # baseline entirely (the baseline can't address them).
217
+ # Diagnostics that lacked a rule or a path bypass the baseline entirely (the baseline can't address
218
+ # them).
244
219
  unkeyable = diagnostics.reject { |d| d.qualified_rule && d.path }
245
220
  [surfaced + unkeyable, silenced_count]
246
221
  end
247
222
 
248
- # A single bucket's drift state for slice 2 inspection.
249
- # `status` is one of:
223
+ # A single bucket's drift state for slice 2 inspection. `status` is one of:
250
224
  #
251
225
  # - `:within` — `actual <= count` (silenced by the filter).
252
- # - `:over` — `actual > count` (over threshold; surfaced
253
- # in the regular `rigor check` output).
226
+ # - `:over` — `actual > count` (over threshold; surfaced in the regular `rigor check` output).
254
227
  # - `:cleared` — `actual == 0` (the bucket can be pruned).
255
- # - `:reducible` — `0 < actual < count` (the bucket's count
256
- # can be tightened; future `regenerate`
257
- # slice 5 handles this).
228
+ # - `:reducible` — `0 < actual < count` (the bucket's count can be tightened; future `regenerate` slice 5
229
+ # handles this).
258
230
  DriftRow = Struct.new(:bucket, :actual_count, :status, keyword_init: true) do
259
231
  def delta
260
232
  actual_count - bucket.count
261
233
  end
262
234
  end
263
235
 
264
- # Walk the current diagnostic stream and report
265
- # bucket-level drift. Each baseline bucket becomes one
266
- # DriftRow regardless of whether the current run still
267
- # matches it.
236
+ # Walk the current diagnostic stream and report bucket-level drift. Each baseline bucket becomes one
237
+ # DriftRow regardless of whether the current run still matches it.
268
238
  #
269
- # @param diagnostics [Array<Diagnostic>] current run's
270
- # diagnostic stream (PRE-filter pass the raw
271
- # `result.diagnostics` from `Runner#run`, not the
272
- # post-baseline surface).
273
- # @return [Array<DriftRow>] one entry per baseline bucket,
274
- # in baseline-file order.
239
+ # @param diagnostics [Array<Diagnostic>] current run's diagnostic stream (PRE-filter — pass the raw
240
+ # `result.diagnostics` from `Runner#run`, not the post-baseline surface).
241
+ # @return [Array<DriftRow>] one entry per baseline bucket, in baseline-file order.
275
242
  def audit(diagnostics)
276
243
  counts = Hash.new(0)
277
244
  diagnostics.each do |diag|
@@ -287,18 +254,15 @@ module Rigor
287
254
  end
288
255
  end
289
256
 
290
- # Returns a new Baseline with the given buckets dropped.
291
- # Used by `rigor baseline prune` (slice 2) to remove
292
- # cleared buckets (`actual == 0`) from the on-disk file.
257
+ # Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune` (slice 2) to
258
+ # remove cleared buckets (`actual == 0`) from the on-disk file.
293
259
  def without(buckets_to_drop)
294
260
  dropset = buckets_to_drop.to_set
295
261
  self.class.new(buckets.reject { |b| dropset.include?(b) }, project_root: @project_root)
296
262
  end
297
263
 
298
- # Serialise to a YAML string. The generator path writes
299
- # this through `File.write`; the dump format is stable
300
- # across versions of this class as long as the bucket
301
- # shape is unchanged.
264
+ # Serialise to a YAML string. The generator path writes this through `File.write`; the dump format is
265
+ # stable across versions of this class as long as the bucket shape is unchanged.
302
266
  def to_yaml
303
267
  rows = buckets.map do |bucket|
304
268
  row = { "file" => bucket.file, "rule" => bucket.rule }
@@ -311,8 +275,7 @@ module Rigor
311
275
  YAML.dump(document)
312
276
  end
313
277
 
314
- # The number of buckets recorded. Useful for the CLI
315
- # summary on `generate`.
278
+ # The number of buckets recorded. Useful for the CLI summary on `generate`.
316
279
  def size
317
280
  buckets.size
318
281
  end
@@ -344,11 +307,9 @@ module Rigor
344
307
  end
345
308
 
346
309
  def group_diagnostics_for_filtering(diagnostics)
347
- # First pass: bin each diagnostic into the bucket that
348
- # claims it. Message-pattern rows take precedence over
349
- # rule-ID rows because they're more specific. A
350
- # diagnostic that matches no row goes into a synthetic
351
- # "no-bucket" bin keyed by (file, rule).
310
+ # First pass: bin each diagnostic into the bucket that claims it. Message-pattern rows take precedence
311
+ # over rule-ID rows because they're more specific. A diagnostic that matches no row goes into a
312
+ # synthetic "no-bucket" bin keyed by (file, rule).
352
313
  bins = {}
353
314
  diagnostics.each do |diag|
354
315
  next if diag.qualified_rule.nil? || diag.path.nil?
@@ -370,8 +331,7 @@ module Rigor
370
331
  candidates = @by_pair[[normalize_path(diagnostic.path), diagnostic.qualified_rule]]
371
332
  return nil if candidates.nil? || candidates.empty?
372
333
 
373
- # Tighter (message-pattern) buckets first, then the
374
- # rule-ID bucket as fallback.
334
+ # Tighter (message-pattern) buckets first, then the rule-ID bucket as fallback.
375
335
  message_buckets, rule_buckets = candidates.partition(&:message_regex)
376
336
  message_buckets.each do |b|
377
337
  return b if b.message_regex.match?(diagnostic.message.to_s)
@@ -2,32 +2,25 @@
2
2
 
3
3
  module Rigor
4
4
  module Analysis
5
- # Binds one logical project path (the path the user is editing,
6
- # e.g. `lib/foo.rb`) to a physical file containing the in-flight
7
- # buffer bytes (e.g. `/tmp/9539itfeh2.rb`). When the runner /
8
- # workers / pre-passes need to read source for the logical path,
9
- # they read from the physical path instead; when they emit a
10
- # `Diagnostic`, the path is the logical one so editors highlight
11
- # the buffer the user is actually looking at.
5
+ # Binds one logical project path (the path the user is editing, e.g. `lib/foo.rb`) to a physical file
6
+ # containing the in-flight buffer bytes (e.g. `/tmp/9539itfeh2.rb`). When the runner / workers / pre-passes
7
+ # need to read source for the logical path, they read from the physical path instead; when they emit a
8
+ # `Diagnostic`, the path is the logical one so editors highlight the buffer the user is actually looking at.
12
9
  #
13
- # See `docs/design/20260516-editor-mode.md` for the design.
14
- # The CLI surfaces this through paired `--tmp-file` /
15
- # `--instead-of` flags on `rigor check` and `rigor type-of`;
16
- # programmatic callers pass a `BufferBinding` to `Runner.new`.
10
+ # See `docs/design/20260516-editor-mode.md` for the design. The CLI surfaces this through paired
11
+ # `--tmp-file` / `--instead-of` flags on `rigor check` and `rigor type-of`; programmatic callers pass a
12
+ # `BufferBinding` to `Runner.new`.
17
13
  BufferBinding = Data.define(:logical_path, :physical_path) do
18
- # Returns the physical path to read bytes from when the caller
19
- # is about to parse `path`. For non-logical paths returns the
20
- # input unchanged. Cheap to call on every path; the binding is
21
- # singular today (one buffer per run).
14
+ # Returns the physical path to read bytes from when the caller is about to parse `path`. For non-logical
15
+ # paths returns the input unchanged. Cheap to call on every path; the binding is singular today (one
16
+ # buffer per run).
22
17
  def resolve(path)
23
18
  path == logical_path ? physical_path : path
24
19
  end
25
20
 
26
- # Returns the path the caller should report in user-facing
27
- # output (diagnostics, run stats) when it currently holds the
28
- # physical path. The inverse of `#resolve`. Non-physical paths
29
- # pass through unchanged, so it is safe to stamp every
30
- # outgoing path through this helper.
21
+ # Returns the path the caller should report in user-facing output (diagnostics, run stats) when it
22
+ # currently holds the physical path. The inverse of `#resolve`. Non-physical paths pass through
23
+ # unchanged, so it is safe to stamp every outgoing path through this helper.
31
24
  def display_path(path)
32
25
  path == physical_path ? logical_path : path
33
26
  end
@@ -5,44 +5,29 @@ require "prism"
5
5
  module Rigor
6
6
  module Analysis
7
7
  module CheckRules
8
- # Walks a parse tree and collects every `IfNode` / `UnlessNode`
9
- # whose predicate folds to a `Type::Constant` AND is NOT
10
- # disqualified by the rule's conservative envelope.
8
+ # Walks a parse tree and collects every `IfNode` / `UnlessNode` whose predicate folds to a
9
+ # `Type::Constant` AND is NOT disqualified by the rule's conservative envelope.
11
10
  #
12
- # The companion rule (`flow.always-truthy-condition`) is
13
- # the inferred-constant counterpart to
14
- # `flow.unreachable-branch` (which only fires on syntactic
15
- # literals). The literal-only rule was the v0.1.2 first-cut
16
- # because Rigor's incomplete loop / mutation / RBS-strictness
17
- # modelling produces inferred constants that look real but
18
- # are pragmatically false-positives. This collector adds two
19
- # surgical skips to bring the inferred-constant case in
20
- # without resurfacing those false positives:
11
+ # The companion rule (`flow.always-truthy-condition`) is the inferred-constant counterpart to
12
+ # `flow.unreachable-branch` (which only fires on syntactic literals). The literal-only rule was the
13
+ # v0.1.2 first-cut because Rigor's incomplete loop / mutation / RBS-strictness modelling produces
14
+ # inferred constants that look real but are pragmatically false-positives. This collector adds two
15
+ # surgical skips to bring the inferred-constant case in without resurfacing those false positives:
21
16
  #
22
- # - **Inside a loop / block**: predicates nested inside
23
- # `WhileNode` / `UntilNode` / `ForNode` / `BlockNode`
24
- # ancestors. Mutation tracking through loop bodies is
25
- # incomplete (`shift = 0; loop { shift += 7 }` keeps
26
- # `shift` at `Constant<7>` per the analyzer), so a
27
- # `Constant<bool>` predicate inside the loop body is
28
- # suspect.
29
- # - **Defensive predicate calls**: `.nil?` / `.empty?` /
30
- # `.zero?` / `.any?` / `.none?` / `.all?`. These read
31
- # like the user explicitly checking for a runtime
32
- # condition that the type system already proves can't
33
- # happen — Rigor's strict-on-returns RBS often disagrees
34
- # with the user's defensive check (e.g. `Module#name`
35
- # returns `String` per RBS but anonymous classes really
36
- # do return `nil`). Skipping these forms keeps the rule
37
- # useful for genuine logic errors without faulting
38
- # defensive code.
17
+ # - **Inside a loop / block**: predicates nested inside `WhileNode` / `UntilNode` / `ForNode` /
18
+ # `BlockNode` ancestors. Mutation tracking through loop bodies is incomplete (`shift = 0; loop {
19
+ # shift += 7 }` keeps `shift` at `Constant<7>` per the analyzer), so a `Constant<bool>` predicate
20
+ # inside the loop body is suspect.
21
+ # - **Defensive predicate calls**: `.nil?` / `.empty?` / `.zero?` / `.any?` / `.none?` / `.all?`. These
22
+ # read like the user explicitly checking for a runtime condition that the type system already proves
23
+ # can't happen — Rigor's strict-on-returns RBS often disagrees with the user's defensive check (e.g.
24
+ # `Module#name` returns `String` per RBS but anonymous classes really do return `nil`). Skipping
25
+ # these forms keeps the rule useful for genuine logic errors without faulting defensive code.
39
26
  #
40
- # Also skipped (so the literal-only `unreachable-branch`
41
- # rule doesn't double-fire alongside this one):
27
+ # Also skipped (so the literal-only `unreachable-branch` rule doesn't double-fire alongside this one):
42
28
  #
43
- # - Predicates whose direct AST shape is a literal
44
- # (`TrueNode` / `FalseNode` / `NilNode` / numeric / string
45
- # / symbol / regexp).
29
+ # - Predicates whose direct AST shape is a literal (`TrueNode` / `FalseNode` / `NilNode` / numeric /
30
+ # string / symbol / regexp).
46
31
  class AlwaysTruthyConditionCollector
47
32
  DEFENSIVE_PREDICATES = %i[nil? empty? zero? any? none? all? respond_to?].freeze
48
33
  LOOP_OR_BLOCK_NODE_CLASSES = [
@@ -56,34 +41,30 @@ module Rigor
56
41
 
57
42
  Result = Data.define(:node, :polarity)
58
43
 
59
- # ADR-53 Track B — the node classes the shared {RuleWalk}
60
- # dispatches to this collector, and the context gate under which
61
- # the walk suppresses it (loop / block bodies see the envelope
62
- # above). The legacy walk's `!in_loop_or_block` guard is now the
63
- # walk's `:loop_or_block` gate, applied before `#visit`.
44
+ # ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector, and the
45
+ # context gate under which the walk suppresses it (loop / block bodies — see the envelope above). The
46
+ # legacy walk's `!in_loop_or_block` guard is now the walk's `:loop_or_block` gate, applied before
47
+ # `#visit`.
64
48
  NODE_CLASSES = [Prism::IfNode, Prism::UnlessNode].freeze
65
49
  RULE_WALK_GATES = [:loop_or_block].freeze
66
50
 
67
- # @return [Array<Result>] one entry per qualifying
68
- # predicate. Empty when the tree carries no firing
51
+ # @return [Array<Result>] one entry per qualifying predicate. Empty when the tree carries no firing
69
52
  # predicates.
70
53
  def initialize(scope_index)
71
54
  @scope_index = scope_index
72
55
  @results = []
73
56
  end
74
57
 
75
- # Legacy single-collector walk — kept as the oracle the
76
- # ADR-53 Track B equivalence harness compares {RuleWalk}
77
- # against; deleted when Track B completes.
58
+ # Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
59
+ # {RuleWalk} against; deleted when Track B completes.
78
60
  def collect(root)
79
61
  walk(root, in_loop_or_block: false)
80
62
  @results.freeze
81
63
  end
82
64
 
83
- # {RuleWalk} entry point: the per-node logic of the legacy walk,
84
- # invoked under the same traversal contract. The `context` is
85
- # unused the loop / block suppression this collector relied on
86
- # is the walk's `:loop_or_block` gate now.
65
+ # {RuleWalk} entry point: the per-node logic of the legacy walk, invoked under the same traversal
66
+ # contract. The `context` is unused — the loop / block suppression this collector relied on is the
67
+ # walk's `:loop_or_block` gate now.
87
68
  def visit(node, _context = nil)
88
69
  collect_predicate(node)
89
70
  end
@@ -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