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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c16266bcbae2ff766dd6579df68ca08d31563d7f9a813ef49b88bd8f72734b7a
4
- data.tar.gz: c80a5f751c3f757c30acfa0fd1631918a5b3fdc388752c03f27ad56c11a723c2
3
+ metadata.gz: d38773edf003d6de8ea6ec3de2f19aac4c2d74e404c56f39d1ed9595f388a648
4
+ data.tar.gz: beb2b7aea30bf74f4cc8d22e66d441d63ecb906689d6a22c6ca647a7a087a719
5
5
  SHA512:
6
- metadata.gz: deda828a4f7171b30c1e0b0531fbb52cb815905e05ebb05186a87b49107d7ce9e80c4d5d9dc81fd35ed26a1872d2d6e9bbeefe98e3b146d1acd1e4d360a81a26
7
- data.tar.gz: fbe3ba7eed33351ea7ec8863f09085ed1b55f99e17ac3a0bba51234723da1a35f1e90fa9dd694aa0a680c5dbb8725855deb789728c6886c16ccc229a6d4b1be1
6
+ metadata.gz: 1c33b18e310c956f9cc9bbd4806228a095bcf95a137efba50affd49ffca17decac9107006e4a7fd350a9783f289e7d9e336ebc99755e7d69ce321d60b21c731b
7
+ data.tar.gz: c9f7286bc00f3419083ced2e35678062e8508dfe03b6064fe6c9f4cf4ad752479ca8801cce059cb3fc6bc0ee0eaaa216824a9d43cc93332feb7e267ab988253c
data/README.md CHANGED
@@ -231,7 +231,7 @@ rigor docs --list # list every bundled page
231
231
 
232
232
  ## Status
233
233
 
234
- Current release: **`v0.2.7`** (2026-07-05) — the latest cut on the
234
+ Current release: **`v0.2.8`** (2026-07-06) — the latest cut on the
235
235
  `0.2.x` evaluation line opened by `v0.2.0`, the first
236
236
  publicly-announced (general / evaluation) release. The line publishes
237
237
  an enumerated [compatibility surface](docs/compatibility.md) as a
@@ -20,16 +20,19 @@ Every rule has a two-segment `family.rule` identifier:
20
20
  | `dump` | `dump_type` notices. |
21
21
 
22
22
  `rigor explain <rule>` prints the full catalogue entry for any
23
- ID; `rigor explain` with no argument lists them all.
23
+ built-in rule ID; `rigor explain` with no argument lists them all.
24
24
 
25
25
  ### Catalogue
26
26
 
27
- Each rule has a stable per-rule anchor on this page
27
+ Each built-in rule has a stable per-rule anchor on this page
28
28
  (`#rule-<family>-<name>`, dots written as dashes) — the
29
29
  `documentation_url` field in `--format json` and `rigor explain`'s
30
30
  `Documentation:` line both point here. The `Evidence` column is
31
31
  Rigor's confidence that a firing is a true positive (see
32
- [Evidence tier](#evidence-tier) below).
32
+ [Evidence tier](#evidence-tier) below). The one exception is
33
+ `rbs_extended.unsatisfied-conformance`, an `rbs_extended`-family rule
34
+ rather than a built-in: `rigor explain` does not resolve it and it
35
+ carries no `documentation_url`.
33
36
 
34
37
  | Rule | Fires when | Evidence |
35
38
  | --- | --- | --- |
@@ -86,7 +89,7 @@ re-stamps it for the run. Three profiles, set with the
86
89
  | --- | --- |
87
90
  | `lenient` | Only proven diagnostics are errors; uncertain ones drop to `warning` / `info`. For incremental adoption on legacy code. |
88
91
  | `balanced` *(default)* | Most rules `error`; `dump.type` `info`; uncertain rules `warning`. |
89
- | `strict` | Every rule is an `error`. CI-friendly. |
92
+ | `strict` | Nearly every rule is an `error` — the exceptions are `call.self-undefined-method` (stays `off`, opt-in only) and `flow.unreachable-clause` (`warning`, pending its false-positive gate). CI-friendly. |
90
93
 
91
94
  For finer control, `severity_overrides:` maps a rule ID or a
92
95
  family to one of `error`, `warning`, `info`, or `off`:
@@ -112,5 +112,8 @@ baseline regenerate` to shrink the file. The
112
112
  [`rigor-baseline-reduce` skill](08-skills.md) walks this loop
113
113
  interactively.
114
114
 
115
- To make CI fail when the baseline *grows*, add
116
- `--baseline-strict` to `rigor check`.
115
+ To make CI fail on *any* baseline drift, add `--baseline-strict`
116
+ to `rigor check`. It fails not only when a bucket grows past its
117
+ recorded count (excess drift, which the surfaced diagnostics already
118
+ fail on) but also when the code has *shrunk* below it (deficit drift:
119
+ the baseline is now looser than the code and should be regenerated).
@@ -181,8 +181,8 @@ entry carries two fields:
181
181
 
182
182
  - **`dynamic_origin`** — the cause the value became dynamic:
183
183
  `external_gem_without_rbs`, `framework_dsl_boundary`,
184
- `analyzer_budget_cutoff`, `explicit_untyped`, or
185
- `unsupported_syntax`.
184
+ `analyzer_budget_cutoff`, `explicit_untyped`,
185
+ `inferred_return_untyped`, or `unsupported_syntax`.
186
186
  - **`tractability`** — the action axis derived from that cause:
187
187
  - **`add_rbs`** — you can close it with a type: install RBS
188
188
  (`rbs collection install`), enable `dependencies.source_inference:`,
@@ -190,8 +190,10 @@ entry carries two fields:
190
190
  - **`enable_plugin`** — a framework / DSL boundary; reach for a
191
191
  plugin or [`pre_eval:`](03-configuration.md), not a hand-written
192
192
  type.
193
- - **`engine_gap`** — not closable by a user type (a budget cutoff
194
- or a construct Rigor does not yet model); report it.
193
+ - **`engine_gap`** — not closable by a user type: a budget cutoff,
194
+ an inference gap (an untyped parameter or an unbound instance
195
+ variable — `inferred_return_untyped`), or a construct Rigor does
196
+ not yet model; report it.
195
197
 
196
198
  The text report prints a one-line `by tractability:` breakdown under
197
199
  the "Add a type here" header, and the JSON carries the same totals as
@@ -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