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
@@ -4,16 +4,12 @@ module Rigor
4
4
  class CLI
5
5
  # Base class for the `rigor <subcommand>` command objects.
6
6
  #
7
- # Every subcommand captured the same invariant wiring — the argument
8
- # vector plus the output and error streams in an identical
9
- # `initialize(argv:, out:, err:)`, and defaulted the streams
10
- # inconsistently (some to `$stdout` / `$stderr`, most not at all).
11
- # Centralising it here gives one consistent shape and lets a test
12
- # instantiate a command with just `argv:` (the streams default so a
13
- # spec can pass a `StringIO` for one and ignore the other).
7
+ # Every subcommand captured the same invariant wiring — the argument vector plus the output and error streams — in
8
+ # an identical `initialize(argv:, out:, err:)`, and defaulted the streams inconsistently (some to `$stdout` /
9
+ # `$stderr`, most not at all). Centralising it here gives one consistent shape and lets a test instantiate a command
10
+ # with just `argv:` (the streams default so a spec can pass a `StringIO` for one and ignore the other).
14
11
  #
15
- # Subclasses read the `@argv` / `@out` / `@err` ivars directly, as
16
- # they did before.
12
+ # Subclasses read the `@argv` / `@out` / `@err` ivars directly, as they did before.
17
13
  class Command
18
14
  def initialize(argv:, out: $stdout, err: $stderr)
19
15
  @argv = argv
@@ -23,11 +19,9 @@ module Rigor
23
19
 
24
20
  private
25
21
 
26
- # Expands `args` (a mix of files and directories) into a unique
27
- # list of `.rb` paths, recursing into directories. Returns nil
28
- # after writing `<command_name>: not a file or directory: <arg>` to
29
- # `@err` — on the first arg that is neither. Shared by the
30
- # path-walking commands (`type-scan`, `coverage`).
22
+ # Expands `args` (a mix of files and directories) into a unique list of `.rb` paths, recursing into directories.
23
+ # Returns nil after writing `<command_name>: not a file or directory: <arg>` to `@err` on the first arg that
24
+ # is neither. Shared by the path-walking commands (`type-scan`, `coverage`).
31
25
  def collect_paths(args, command_name:)
32
26
  paths = []
33
27
  args.each do |arg|
@@ -25,18 +25,19 @@ require_relative "mutation_protection_renderer"
25
25
  require_relative "fused_protection_report"
26
26
  require_relative "fused_protection_renderer"
27
27
  require_relative "coverage_mutation"
28
+ require_relative "protection_fork_scan"
29
+ require_relative "check_runner_factory"
28
30
  require_relative "command"
29
31
 
30
32
  module Rigor
31
33
  class CLI
32
34
  # Executes the `rigor coverage` command.
33
35
  #
34
- # Walks every Prism node in one or more files, infers its type via
35
- # `Rigor::Scope#type_of`, and classifies the result into precision tiers
36
- # (constant / nominal / shaped / refined / bot / dynamic_specific /
36
+ # Walks every Prism node in one or more files, infers its type via `Rigor::Scope#type_of`, and classifies the result
37
+ # into precision tiers (constant / nominal / shaped / refined / bot / dynamic_specific /
37
38
  # dynamic_top / top). Reports aggregate and per-file statistics so
38
- # maintainers can track type-precision trends and SKILL pipelines can
39
- # measure the impact of adding new constant-fold or shape-dispatch rules.
39
+ # maintainers can track type-precision trends and SKILL pipelines can measure the impact of adding new constant-fold
40
+ # or shape-dispatch rules.
40
41
  #
41
42
  # Exit codes:
42
43
  # 0 — scan complete, precision ratio ≥ threshold (or no threshold given)
@@ -47,8 +48,8 @@ module Rigor
47
48
 
48
49
  USAGE = "Usage: rigor coverage [options] PATH..."
49
50
 
50
- # ADR-70 — the default test runner hook for `--with-tests`. The
51
- # conventional Ruby test task; override with `--test-command`.
51
+ # ADR-70 — the default test runner hook for `--with-tests`. The conventional Ruby test task; override with
52
+ # `--test-command`.
52
53
  DEFAULT_TEST_COMMAND = %w[bundle exec rake].freeze
53
54
 
54
55
  # @return [Integer] CLI exit status.
@@ -59,23 +60,32 @@ module Rigor
59
60
  return include_dynamic_misuse_error if options[:include_dynamic] && !options[:with_tests]
60
61
  return run_mutation_protection(options) if options[:mutation]
61
62
 
62
- paths = collect_paths(@argv, command_name: "coverage")
63
+ configuration = Configuration.load(options.fetch(:config))
64
+ paths = resolve_paths(configuration)
63
65
  return CLI::EXIT_USAGE if paths.nil?
64
66
  return usage_error if paths.empty?
65
67
 
66
- return run_protection(paths, options) if options[:protection]
68
+ return run_protection(paths, options, configuration) if options[:protection]
67
69
 
68
- report = scan_paths(paths, options)
70
+ report = scan_paths(paths, configuration)
69
71
  CoverageRenderer.new(out: @out).render(report, format: options.fetch(:format))
70
72
  determine_exit(report, options)
71
73
  end
72
74
 
73
75
  private
74
76
 
77
+ # Like `rigor check`, fall back to the configured `paths:` when no path is given on the command line
78
+ # (`coverage` previously required an explicit path — operational friction the two commands should not
79
+ # differ on). The mutation mode keeps its own git-changed-files default and returns before this.
80
+ def resolve_paths(configuration)
81
+ args = @argv.empty? ? configuration.paths : @argv
82
+ collect_paths(args, command_name: "coverage")
83
+ end
84
+
75
85
  def parse_options
76
86
  options = { format: "text", threshold: nil, config: nil, protection: false, mutation: false,
77
87
  with_tests: false, test_command: DEFAULT_TEST_COMMAND, include_dynamic: false,
78
- limit: nil, seed: 1 }
88
+ limit: nil, seed: 1, workers: nil }
79
89
  OptionParser.new { |opts| define_options(opts, options) }.parse!(@argv)
80
90
  options
81
91
  end
@@ -88,6 +98,10 @@ module Rigor
88
98
  options[:protection] = true
89
99
  end
90
100
  define_mutation_options(opts, options)
101
+ opts.on("--workers=N", Integer, "With --protection: fork N workers over the scanned files " \
102
+ "(default: config parallel.workers / RIGOR_RACTOR_WORKERS / 0)") do |v|
103
+ options[:workers] = v
104
+ end
91
105
  opts.on("--threshold=RATIO", Float, "Exit 1 when the precision (or, with --protection, " \
92
106
  "protection/effectiveness) ratio is below RATIO (0.0–1.0)") do |v|
93
107
  options[:threshold] = v
@@ -136,26 +150,41 @@ module Rigor
136
150
  CLI::EXIT_USAGE
137
151
  end
138
152
 
139
- def run_protection(paths, options)
140
- report = scan_protection(paths, options)
153
+ def run_protection(paths, options, configuration)
154
+ report = scan_protection(paths, options, configuration)
141
155
  ProtectionRenderer.new(out: @out).render(report, format: options.fetch(:format))
142
156
  determine_protection_exit(report, options)
143
157
  end
144
158
 
145
- def scan_protection(paths, options)
146
- configuration = Configuration.load(options.fetch(:config))
159
+ # Builds the plugin-aware environment and the seeded scope ONCE on the parent, then fork-pools the
160
+ # per-file scan across the resolved worker count (P3-10). The scan is pure-read and each file is
161
+ # independent, so this is a fork-map + ordered reduce: {ProtectionForkScan} returns `{path => result}`
162
+ # and the parent absorbs in `paths` order so the report is byte-identical to a sequential run.
163
+ def scan_protection(paths, options, configuration)
147
164
  environment = plugin_aware_environment(configuration)
148
- scope = scope_with_inferred_params(paths, configuration, environment)
165
+ workers = CheckRunnerFactory.resolve_workers(options, configuration)
166
+ scope = scope_with_inferred_params(paths, configuration, environment, workers)
149
167
  scanner = Inference::ProtectionScanner.new(scope: scope)
150
168
  accumulator = ProtectionAccumulator.new
151
169
 
152
- paths.each { |path| scan_one(path, scanner, accumulator, configuration) }
170
+ results = ProtectionForkScan.run(
171
+ paths: paths, scanner: scanner, environment: environment,
172
+ configuration: configuration, workers: workers
173
+ )
174
+ paths.each { |path| absorb_protection_result(accumulator, path, results.fetch(path)) }
153
175
  accumulator.to_report
154
176
  end
155
177
 
156
- # Seed the protection scan's scope with the same cross-file facts
157
- # `rigor check` resolves against, so a receiver reads the type it
158
- # actually has rather than a stripped-scope `Dynamic`:
178
+ def absorb_protection_result(accumulator, path, result)
179
+ if result.is_a?(ProtectionForkScan::ParseError)
180
+ accumulator.record_parse_error_count(path, result.count)
181
+ else
182
+ accumulator.absorb(path, result)
183
+ end
184
+ end
185
+
186
+ # Seed the protection scan's scope with the same cross-file facts `rigor check` resolves against, so a receiver
187
+ # reads the type it actually has rather than a stripped-scope `Dynamic`:
159
188
  #
160
189
  # - `discovered_classes` — a project constant referring to a class
161
190
  # defined in a *sibling* file (`Account`, `User`) types as
@@ -168,9 +197,9 @@ module Rigor
168
197
  # parameter) counts as protected when its call sites resolve to
169
198
  # concrete argument types.
170
199
  #
171
- # Both span the scanned `paths` only (no whole-project pre-pass) —
172
- # a site that gains neither is classified exactly as before.
173
- def scope_with_inferred_params(paths, configuration, environment)
200
+ # Both span the scanned `paths` only (no whole-project pre-pass) — a site that gains neither is classified exactly
201
+ # as before.
202
+ def scope_with_inferred_params(paths, configuration, environment, workers)
174
203
  base = Scope.empty(environment: environment)
175
204
  seed = {}
176
205
 
@@ -178,7 +207,7 @@ module Rigor
178
207
  seed[:discovered_classes] = discovered unless discovered.empty?
179
208
 
180
209
  table = Inference::ParameterInferenceCollector.collect(
181
- files: paths, environment: environment, target_ruby: configuration.target_ruby
210
+ files: paths, environment: environment, target_ruby: configuration.target_ruby, workers: workers
182
211
  )
183
212
  seed[:param_inferred_types] = table unless table.empty?
184
213
 
@@ -202,36 +231,21 @@ module Rigor
202
231
  CLI::EXIT_USAGE
203
232
  end
204
233
 
205
- def scan_paths(paths, options)
206
- CoverageScan.precision_report(files: paths, configuration: Configuration.load(options.fetch(:config)))
207
- end
208
-
209
- # Delegated to the shared scan module (see {CoverageScan}); the
210
- # protection path below reuses both, and `rigor check --coverage`
211
- # reuses `precision_report` over the same machinery.
212
- def project_environment(configuration)
213
- CoverageScan.project_environment(configuration)
234
+ def scan_paths(paths, configuration)
235
+ CoverageScan.precision_report(files: paths, configuration: configuration)
214
236
  end
215
237
 
216
- # The protection scan must see the same receiver types `rigor check`
217
- # does including plugin-contributed `dynamic_return` types (a
218
- # controller's `params` `ActionController::Parameters`, a
219
- # `Model.where` `ActiveRecord::Relation[Model]`). The bare
220
- # `project_environment` carries only the RBS environment (no plugin
221
- # registry), so every plugin-typed receiver reads `Dynamic` and its
222
- # dispatch site is miscounted as *unprotected* a systematic
223
- # undercount of what Rigor actually types on a plugin-using project.
224
- # `ProjectContext` builds the plugin-aware environment (registry
225
- # materialised + the per-run prepare pass that primes producers like
226
- # the controller / model index) exactly as the LSP and the runner do.
238
+ # The protection scan must see the same receiver types `rigor check` does — including plugin-contributed
239
+ # `dynamic_return` types (a controller's `params` `ActionController::Parameters`, a `Model.where` →
240
+ # `ActiveRecord::Relation[Model]`). The bare `CoverageScan.project_environment` carries only the RBS environment
241
+ # (no plugin registry), so every plugin-typed receiver reads `Dynamic` and its dispatch site is miscounted as
242
+ # *unprotected* a systematic undercount of what Rigor actually types on a plugin-using project.
243
+ # `ProjectContext` builds the plugin-aware environment (registry materialised + the per-run prepare pass that
244
+ # primes producers like the controller / model index) exactly as the LSP and the runner do.
227
245
  def plugin_aware_environment(configuration)
228
246
  LanguageServer::ProjectContext.new(configuration: configuration).environment
229
247
  end
230
248
 
231
- def scan_one(path, scanner, accumulator, configuration)
232
- CoverageScan.scan_into(path, scanner, accumulator, configuration)
233
- end
234
-
235
249
  def determine_exit(report, options)
236
250
  return 1 unless report.parse_errors.empty?
237
251
 
@@ -5,19 +5,16 @@ require "prism"
5
5
 
6
6
  module Rigor
7
7
  class CLI
8
- # ADR-63 Tier 2 + ADR-70 — the mutation-effectiveness and fused static∪dynamic
9
- # protection paths, factored out of {CoverageCommand} to keep that command
10
- # focused on dispatch. Mixed in, so each method runs in the command instance
11
- # (using `@out` / `@err` / `@argv` / `collect_paths` / `determine_protection_exit`
12
- # and the Protection + LanguageServer collaborators the command requires).
8
+ # ADR-63 Tier 2 + ADR-70 — the mutation-effectiveness and fused static∪dynamic protection paths, factored out of
9
+ # {CoverageCommand} to keep that command focused on dispatch. Mixed in, so each method runs in the command instance
10
+ # (using `@out` / `@err` / `@argv` / `collect_paths` / `determine_protection_exit` and the Protection +
11
+ # LanguageServer collaborators the command requires).
13
12
  module CoverageMutation
14
13
  private
15
14
 
16
- # ADR-63 Tier 2 — the mutation-effectiveness deep dive. Builds the RBS
17
- # environment + project pre-pass once (the warm loop), then re-analyses
18
- # each target file's mutants against its clean baseline. Defaults to the
19
- # git-changed `.rb` files; explicit paths override (and enable the
20
- # whole-project opt-in, which is minutes).
15
+ # ADR-63 Tier 2 — the mutation-effectiveness deep dive. Builds the RBS environment + project pre-pass once (the
16
+ # warm loop), then re-analyses each target file's mutants against its clean baseline. Defaults to the git-changed
17
+ # `.rb` files; explicit paths override (and enable the whole-project opt-in, which is minutes).
21
18
  def run_mutation_protection(options)
22
19
  explicit = collect_paths(@argv, command_name: "coverage")
23
20
  return CLI::EXIT_USAGE if explicit.nil?
@@ -36,8 +33,8 @@ module Rigor
36
33
  determine_protection_exit(report, options)
37
34
  end
38
35
 
39
- # A `--limit` sample makes the report an estimate (per-file ratios over a
40
- # random N of the mutations). Say so on stderr — stdout stays clean for JSON.
36
+ # A `--limit` sample makes the report an estimate (per-file ratios over a random N of the mutations). Say so on
37
+ # stderr — stdout stays clean for JSON.
41
38
  def note_sampling(options)
42
39
  return unless options[:limit]
43
40
 
@@ -47,10 +44,9 @@ module Rigor
47
44
  )
48
45
  end
49
46
 
50
- # ADR-70 — the fused static∪dynamic deep dive. The type pass is the ADR-63
51
- # Tier 2 warm loop; each type-survivor is then run against the project's
52
- # test suite (the runner hook). The suite MUST pass on clean code first, or
53
- # "a mutant survived" is meaningless — abort with a clear message if not.
47
+ # ADR-70 — the fused static∪dynamic deep dive. The type pass is the ADR-63 Tier 2 warm loop; each type-survivor is
48
+ # then run against the project's test suite (the runner hook). The suite MUST pass on clean code first, or "a
49
+ # mutant survived" is meaningless abort with a clear message if not.
54
50
  def run_fused_protection(paths, options)
55
51
  configuration = Configuration.load(options.fetch(:config))
56
52
  test_oracle = Protection::TestSuiteOracle.new(command: options.fetch(:test_command))
@@ -117,9 +113,8 @@ module Rigor
117
113
  accumulator.absorb(scanner.scan_file(path, source: source))
118
114
  end
119
115
 
120
- # The git-changed (modified / added / untracked) `.rb` files that exist on
121
- # disk — the default Tier 2 scope. Returns [] outside a git work tree or
122
- # when git is unavailable; the caller then reports "nothing to measure".
116
+ # The git-changed (modified / added / untracked) `.rb` files that exist on disk — the default Tier 2 scope.
117
+ # Returns [] outside a git work tree or when git is unavailable; the caller then reports "nothing to measure".
123
118
  def changed_ruby_files
124
119
  output = git_status_porcelain
125
120
  return [] if output.nil?
@@ -11,7 +11,6 @@ module Rigor
11
11
  :per_file,
12
12
  :total
13
13
  )
14
- # Sum of all per-file totals.
15
14
  def grand_total
16
15
  total.total
17
16
  end
@@ -44,7 +43,6 @@ module Rigor
44
43
  def initialize
45
44
  @per_file = []
46
45
  @parse_errors = []
47
- # Accumulated totals across all files.
48
46
  @total_total = 0
49
47
  @total_tier_counts = Inference::PrecisionScanner::TIERS.to_h { |t| [t, 0] }
50
48
  end
@@ -10,12 +10,10 @@ require_relative "coverage_report"
10
10
 
11
11
  module Rigor
12
12
  class CLI
13
- # Shared type-precision scan behind both `rigor coverage` (the
14
- # dedicated command) and `rigor check --coverage` (the in-run
15
- # coverage block). Walks each file's AST, types every expression via
16
- # `Scope#type_of`, and accumulates the precision-tier breakdown into a
17
- # `CoverageReport`. Extracted so the two surfaces stay byte-identical
18
- # on the same file set.
13
+ # Shared type-precision scan behind both `rigor coverage` (the dedicated command) and `rigor check --coverage` (the
14
+ # in-run coverage block). Walks each file's AST, types every expression via `Scope#type_of`, and accumulates the
15
+ # precision-tier breakdown into a `CoverageReport`. Extracted so the two surfaces stay byte-identical on the same
16
+ # file set.
19
17
  module CoverageScan
20
18
  module_function
21
19
 
@@ -37,11 +35,9 @@ module Rigor
37
35
  )
38
36
  end
39
37
 
40
- # Parses one file and feeds the scan result (or a parse-error
41
- # record) into `accumulator`. `scanner` / `accumulator` are a
42
- # matched pair a `PrecisionScanner` + `CoverageAccumulator`, or a
43
- # `ProtectionScanner` + `ProtectionAccumulator` — both of which
44
- # respond to `scan(node)` and `absorb(path, result)`.
38
+ # Parses one file and feeds the scan result (or a parse-error record) into `accumulator`. `scanner` /
39
+ # `accumulator` are a matched pair — a `PrecisionScanner` + `CoverageAccumulator`, or a `ProtectionScanner` +
40
+ # `ProtectionAccumulator`both of which respond to `scan(node)` and `absorb(path, result)`.
45
41
  def scan_into(path, scanner, accumulator, configuration)
46
42
  source = File.read(path)
47
43
  parse_result = Prism.parse(source, filepath: path, version: configuration.target_ruby)
@@ -6,11 +6,9 @@ require_relative "../version"
6
6
 
7
7
  module Rigor
8
8
  class CLI
9
- # CI-native diagnostic output formats (ADR-51). Each renders an
10
- # `Analysis::Result` to a string a CI platform consumes to surface
11
- # diagnostics inline in a pull / merge request, rather than only in the
12
- # job log. They read the same `Diagnostic` fields as `--format json`
13
- # (path / line / column / severity / qualified rule / message) and add
9
+ # CI-native diagnostic output formats (ADR-51). Each renders an `Analysis::Result` to a string a CI platform
10
+ # consumes to surface diagnostics inline in a pull / merge request, rather than only in the job log. They read the
11
+ # same `Diagnostic` fields as `--format json` (path / line / column / severity / qualified rule / message) and add
14
12
  # no new information — only a platform-native rendering of it.
15
13
  #
16
14
  # sarif — SARIF 2.1.0 (cross-platform; GitHub code-scanning, any
@@ -23,11 +21,9 @@ module Rigor
23
21
  # reviewdog (`-f=checkstyle`) and Jenkins/etc. consume
24
22
  # junit — JUnit XML, the test-report format many CI systems render
25
23
  #
26
- # Severity maps once per format from Rigor's `:error` / `:warning` /
27
- # `:info`; the qualified rule (`<source_family>.<rule>` or the bare rule
28
- # for the builtin family) is the stable identifier, nil only for the
29
- # ruleless producers (parse / internal errors), which each format
30
- # degrades gracefully.
24
+ # Severity maps once per format from Rigor's `:error` / `:warning` / `:info`; the qualified rule
25
+ # (`<source_family>.<rule>` or the bare rule for the builtin family) is the stable identifier, nil only for the
26
+ # ruleless producers (parse / internal errors), which each format degrades gracefully.
31
27
  module DiagnosticFormats
32
28
  FORMATS = %w[sarif github gitlab checkstyle junit teamcity].freeze
33
29
 
@@ -37,8 +33,9 @@ module Rigor
37
33
  FORMATS.include?(format)
38
34
  end
39
35
 
40
- # Renders `result` in the named CI format. Callers gate on
41
- # {.supports?} first; an unrecognised format returns nil.
36
+ # Renders `result` in the named CI format. Callers gate on {.supports?} first, so an unrecognised format means
37
+ # {FORMATS} and the dispatch below have drifted apart — raise rather than return nil, which every caller would
38
+ # then have to guard.
42
39
  def render(result, format)
43
40
  case format
44
41
  when "sarif" then Sarif.new(result).render
@@ -47,12 +44,12 @@ module Rigor
47
44
  when "checkstyle" then Checkstyle.new(result).render
48
45
  when "junit" then Junit.new(result).render
49
46
  when "teamcity" then Teamcity.new(result).render
47
+ else raise ArgumentError, "unsupported format: #{format}"
50
48
  end
51
49
  end
52
50
 
53
- # XML attribute / text escaping shared by the Checkstyle and JUnit
54
- # formatters. Covers the five predefined XML entities so a diagnostic
55
- # message carrying `<`, `&`, or a quote can't break the document.
51
+ # XML attribute / text escaping shared by the Checkstyle and JUnit formatters. Covers the five predefined XML
52
+ # entities so a diagnostic message carrying `<`, `&`, or a quote can't break the document.
56
53
  module XmlEscaping
57
54
  ENTITIES = { "&" => "&amp;", "<" => "&lt;", ">" => "&gt;",
58
55
  '"' => "&quot;", "'" => "&apos;" }.freeze
@@ -62,15 +59,14 @@ module Rigor
62
59
  end
63
60
  end
64
61
 
65
- # SARIF 2.1.0 — the OASIS static-analysis interchange format. GitHub's
66
- # `codeql-action/upload-sarif` ingests it to render findings on the PR
67
- # diff and in the Security tab; it is the cross-platform anchor format.
62
+ # SARIF 2.1.0 — the OASIS static-analysis interchange format. GitHub's `codeql-action/upload-sarif` ingests it to
63
+ # render findings on the PR diff and in the Security tab; it is the cross-platform anchor format.
68
64
  class Sarif
69
65
  SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json"
70
66
  INFORMATION_URI = "https://github.com/rigortype/rigor"
71
67
 
72
- # SARIF defines exactly three result levels; Rigor's `:info` is a
73
- # `note` (the SARIF spelling for advisory findings).
68
+ # SARIF defines exactly three result levels; Rigor's `:info` is a `note` (the SARIF spelling for advisory
69
+ # findings).
74
70
  LEVELS = { error: "error", warning: "warning", info: "note" }.freeze
75
71
 
76
72
  def initialize(result)
@@ -103,9 +99,8 @@ module Rigor
103
99
  }
104
100
  end
105
101
 
106
- # The distinct rule ids seen in this run, declared so consumers can
107
- # cross-reference each result's `ruleId`. Id-only is a valid minimal
108
- # SARIF rule object; richer per-rule metadata is a later enrichment.
102
+ # The distinct rule ids seen in this run, declared so consumers can cross-reference each result's `ruleId`.
103
+ # Id-only is a valid minimal SARIF rule object; richer per-rule metadata is a later enrichment.
109
104
  def rules
110
105
  @result.diagnostics.filter_map(&:qualified_rule).uniq.map { |id| { "id" => id } }
111
106
  end
@@ -121,9 +116,8 @@ module Rigor
121
116
  entry
122
117
  end
123
118
 
124
- # Rigor lines / columns are already 1-based, matching SARIF's
125
- # 1-based `startLine` / `startColumn`. Paths are project-relative;
126
- # SARIF URIs use forward slashes on every platform.
119
+ # Rigor lines / columns are already 1-based, matching SARIF's 1-based `startLine` / `startColumn`. Paths are
120
+ # project-relative; SARIF URIs use forward slashes on every platform.
127
121
  def location_for(diagnostic)
128
122
  {
129
123
  "physicalLocation" => {
@@ -134,9 +128,8 @@ module Rigor
134
128
  end
135
129
  end
136
130
 
137
- # GitHub Actions workflow commands — `::<level> file=…,line=…,col=…,
138
- # title=…::<message>` lines the runner parses out of stdout and turns
139
- # into inline PR annotations, with no separate upload step.
131
+ # GitHub Actions workflow commands — `::<level> file=…,line=…,col=…, title=…::<message>` lines the runner parses
132
+ # out of stdout and turns into inline PR annotations, with no separate upload step.
140
133
  class GithubActions
141
134
  # GitHub's annotation levels; Rigor's `:info` is a `notice`.
142
135
  LEVELS = { error: "error", warning: "warning", info: "notice" }.freeze
@@ -160,26 +153,24 @@ module Rigor
160
153
  "::#{level} #{props.join(',')}::#{escape_data(diagnostic.message)}"
161
154
  end
162
155
 
163
- # GitHub's documented workflow-command escaping: `%` and the CR / LF
164
- # that would otherwise terminate the command line, for message data.
156
+ # GitHub's documented workflow-command escaping: `%` and the CR / LF that would otherwise terminate the command
157
+ # line, for message data.
165
158
  def escape_data(value)
166
159
  value.to_s.gsub("%", "%25").gsub("\r", "%0D").gsub("\n", "%0A")
167
160
  end
168
161
 
169
- # Property values additionally escape the `,` (property separator)
170
- # and `:` (command terminator) so a path or rule id can carry them.
162
+ # Property values additionally escape the `,` (property separator) and `:` (command terminator) so a path or
163
+ # rule id can carry them.
171
164
  def escape_property(value)
172
165
  escape_data(value).gsub(",", "%2C").gsub(":", "%3A")
173
166
  end
174
167
  end
175
168
 
176
- # GitLab Code Quality report — the CodeClimate-subset JSON array GitLab
177
- # reads from a `codequality` CI artifact to populate the merge-request
178
- # Code Quality widget.
169
+ # GitLab Code Quality report — the CodeClimate-subset JSON array GitLab reads from a `codequality` CI artifact to
170
+ # populate the merge-request Code Quality widget.
179
171
  class GitlabCodeQuality
180
- # GitLab's severity vocabulary (a CodeClimate subset). Rigor maps
181
- # error major, warning minor, info info; `critical` / `blocker`
182
- # are left for a louder future tier.
172
+ # GitLab's severity vocabulary (a CodeClimate subset). Rigor maps error → major, warning → minor, info → info;
173
+ # `critical` / `blocker` are left for a louder future tier.
183
174
  SEVERITIES = { error: "major", warning: "minor", info: "info" }.freeze
184
175
 
185
176
  def initialize(result)
@@ -205,17 +196,16 @@ module Rigor
205
196
  }
206
197
  end
207
198
 
208
- # The rule id is folded into the description (the widget shows it)
209
- # because Code Quality has no dedicated rule field.
199
+ # The rule id is folded into the description (the widget shows it) because Code Quality has no dedicated rule
200
+ # field.
210
201
  def description(diagnostic)
211
202
  rule_id = diagnostic.qualified_rule
212
203
  rule_id ? "#{diagnostic.message} [#{rule_id}]" : diagnostic.message
213
204
  end
214
205
 
215
- # GitLab dedups findings by fingerprint and tracks them across runs
216
- # by it, so it must be stable for an unchanged finding and unique per
217
- # finding. Hashing the locating tuple (path, rule, line, column,
218
- # message) satisfies both — order-independent, no run-volatile input.
206
+ # GitLab dedups findings by fingerprint and tracks them across runs by it, so it must be stable for an unchanged
207
+ # finding and unique per finding. Hashing the locating tuple (path, rule, line, column, message) satisfies both
208
+ # order-independent, no run-volatile input.
219
209
  def fingerprint(diagnostic)
220
210
  payload = [diagnostic.path, diagnostic.qualified_rule, diagnostic.line,
221
211
  diagnostic.column, diagnostic.message].join("")
@@ -223,12 +213,10 @@ module Rigor
223
213
  end
224
214
  end
225
215
 
226
- # Checkstyle XML — the lint-interchange format a wide range of tools
227
- # read, most usefully reviewdog (`-f=checkstyle`), which then posts to
228
- # any of its reporters (GitHub PR review, GitLab MR, Gerrit, ). Errors
229
- # are grouped by file; the qualified rule rides in `source` (the rule
230
- # code reviewdog surfaces). Checkstyle's native severities are
231
- # `error` / `warning` / `info`, so Rigor's map through unchanged.
216
+ # Checkstyle XML — the lint-interchange format a wide range of tools read, most usefully reviewdog
217
+ # (`-f=checkstyle`), which then posts to any of its reporters (GitHub PR review, GitLab MR, Gerrit, …). Errors are
218
+ # grouped by file; the qualified rule rides in `source` (the rule code reviewdog surfaces). Checkstyle's native
219
+ # severities are `error` / `warning` / `info`, so Rigor's map through unchanged.
232
220
  class Checkstyle
233
221
  include XmlEscaping
234
222
 
@@ -258,12 +246,10 @@ module Rigor
258
246
  end
259
247
  end
260
248
 
261
- # JUnit XML — the test-report format GitHub's test reporting, GitLab,
262
- # Jenkins, and CircleCI render. Following the established linter
263
- # convention (rubocop / eslint / PHPStan): every diagnostic is a
264
- # `testcase` carrying a `failure` typed by its severity, so all of them
265
- # are visible in the report. The exit code (errors only) remains the
266
- # gate; this view is for surfacing, not gating.
249
+ # JUnit XML — the test-report format GitHub's test reporting, GitLab, Jenkins, and CircleCI render. Following the
250
+ # established linter convention (rubocop / eslint / PHPStan): every diagnostic is a `testcase` carrying a
251
+ # `failure` typed by its severity, so all of them are visible in the report. The exit code (errors only) remains
252
+ # the gate; this view is for surfacing, not gating.
267
253
  class Junit
268
254
  include XmlEscaping
269
255
 
@@ -299,11 +285,9 @@ module Rigor
299
285
  end
300
286
  end
301
287
 
302
- # TeamCity inspection service messages — the `##teamcity[…]` lines a
303
- # TeamCity build agent parses out of the build log into its Inspections
304
- # view. The one stdout-native format (besides `github`) that
305
- # CI-detection auto-emits. One `inspectionType` declares the category;
306
- # each diagnostic is an `inspection` typed by severity.
288
+ # TeamCity inspection service messages — the `##teamcity[…]` lines a TeamCity build agent parses out of the build
289
+ # log into its Inspections view. The one stdout-native format (besides `github`) that CI-detection auto-emits. One
290
+ # `inspectionType` declares the category; each diagnostic is an `inspection` typed by severity.
307
291
  class Teamcity
308
292
  SEVERITIES = { error: "ERROR", warning: "WARNING", info: "INFO" }.freeze
309
293
 
@@ -7,10 +7,8 @@ require "optionparser"
7
7
 
8
8
  module Rigor
9
9
  class CLI
10
- # Executes `rigor diff <baseline.json> [paths...]`. Compares
11
- # the current `rigor check` diagnostics against a saved
12
- # baseline JSON (the output of a previous `rigor check
13
- # --format=json` run) and prints the delta:
10
+ # Executes `rigor diff <baseline.json> [paths...]`. Compares the current `rigor check` diagnostics against a saved
11
+ # baseline JSON (the output of a previous `rigor check --format=json` run) and prints the delta:
14
12
  #
15
13
  # - **new** — diagnostics in the current run that were not
16
14
  # in the baseline (typically a regression introduced in
@@ -18,19 +16,13 @@ module Rigor
18
16
  # - **fixed** — diagnostics in the baseline that no longer
19
17
  # appear in the current run (typically progress).
20
18
  #
21
- # Identity for matching is the tuple
22
- # `(path, line, column, rule, source_family, message)`.
23
- # An edit that moves a diagnostic to a new line surfaces as
24
- # one fixed + one new pair, which lines up with the user's
25
- # mental model of "you changed the line, the analyzer's
26
- # position changed too."
19
+ # Identity for matching is the tuple `(path, line, column, rule, source_family, message)`. An edit that moves a
20
+ # diagnostic to a new line surfaces as one fixed + one new pair, which lines up with the user's mental model of "you
21
+ # changed the line, the analyzer's position changed too."
27
22
  #
28
- # CI usage: commit a `rigor.baseline.json` produced once
29
- # with `rigor check --format=json > rigor.baseline.json`,
30
- # then run `rigor diff rigor.baseline.json` in CI. Exit code
31
- # is `1` when any new diagnostic appears, `0` otherwise —
32
- # so adding new errors fails CI but legacy errors recorded
33
- # in the baseline don't.
23
+ # CI usage: commit a `rigor.baseline.json` produced once with `rigor check --format=json > rigor.baseline.json`,
24
+ # then run `rigor diff rigor.baseline.json` in CI. Exit code is `1` when any new diagnostic appears, `0` otherwise —
25
+ # so adding new errors fails CI but legacy errors recorded in the baseline don't.
34
26
  class DiffCommand < Command
35
27
  USAGE = "Usage: rigor diff [options] <baseline.json> [paths...]"
36
28
 
@@ -77,10 +69,8 @@ module Rigor
77
69
  options
78
70
  end
79
71
 
80
- # Runs `rigor check` against the remaining argv (or the
81
- # configured paths) and returns the diagnostics array.
82
- # Reuses the analyzer + configuration plumbing the
83
- # check-command path uses.
72
+ # Runs `rigor check` against the remaining argv (or the configured paths) and returns the diagnostics array.
73
+ # Reuses the analyzer + configuration plumbing the check-command path uses.
84
74
  def run_current(options)
85
75
  require_relative "../analysis/runner"
86
76
  require_relative "../configuration"