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
@@ -6,26 +6,21 @@ module Rigor
6
6
  module Source
7
7
  # Extracts literal Symbol/String values from Prism call arguments.
8
8
  #
9
- # The "is this argument a literal `:sym` or `"str"`, and if so what
10
- # Symbol does it name?" question recurs across the analyzer (sig-gen
11
- # observation, attr-accessor generation, synthetic-method scanning)
12
- # and across nearly every DSL plugin (`state :draft`,
13
- # `has_one_attached :avatar`, `validate_presence_of(:name)`, …). This
14
- # module is the one place that answers it, so the
15
- # `node.unescaped.to_sym if SymbolNode || StringNode` shape is written
16
- # once rather than copied per call site.
9
+ # The "is this argument a literal `:sym` or `"str"`, and if so what Symbol does it name?" question
10
+ # recurs across the analyzer (sig-gen observation, attr-accessor generation, synthetic-method scanning)
11
+ # and across nearly every DSL plugin (`state :draft`, `has_one_attached :avatar`,
12
+ # `validate_presence_of(:name)`, …). This module is the one place that answers it, so the
13
+ # `node.unescaped.to_sym if SymbolNode || StringNode` shape is written once rather than copied per call
14
+ # site.
17
15
  #
18
- # `#unescaped` (not `#value`) is used deliberately so an interpolation-
19
- # free `"foo"` / `:foo` round-trips to `:foo` consistently for both
20
- # node kinds.
16
+ # `#unescaped` (not `#value`) is used deliberately so an interpolation- free `"foo"` / `:foo`
17
+ # round-trips to `:foo` consistently for both node kinds.
21
18
  #
22
- # The surface is a small grid over two axes — which node kinds are
23
- # accepted (`SymbolNode` only, or `SymbolNode`/`StringNode`) and what
24
- # the caller wants back (the interned `Symbol`, or the raw `String`
25
- # name). The SymbolNode-only forms ({.symbol} / {.symbol_name}) exist
26
- # so a DSL that distinguishes `state :draft` from `state "draft"`
27
- # keeps that distinction instead of silently widening to accept the
28
- # string literal.
19
+ # The surface is a small grid over two axes — which node kinds are accepted (`SymbolNode` only, or
20
+ # `SymbolNode`/`StringNode`) and what the caller wants back (the interned `Symbol`, or the raw `String`
21
+ # name). The SymbolNode-only forms ({.symbol} / {.symbol_name}) exist so a DSL that distinguishes `state
22
+ # :draft` from `state "draft"` keeps that distinction instead of silently widening to accept the string
23
+ # literal.
29
24
  #
30
25
  # | accepts | → Symbol | → String |
31
26
  # | ------------------ | ------------------- | ------------------------ |
@@ -34,8 +29,8 @@ module Rigor
34
29
  module Literals
35
30
  module_function
36
31
 
37
- # The Symbol a literal `Prism::SymbolNode` / `Prism::StringNode`
38
- # names, or `nil` for any other node (including `nil`).
32
+ # The Symbol a literal `Prism::SymbolNode` / `Prism::StringNode` names, or `nil` for any other node
33
+ # (including `nil`).
39
34
  #
40
35
  # @param node [Prism::Node, nil]
41
36
  # @return [Symbol, nil]
@@ -45,12 +40,10 @@ module Rigor
45
40
  node.unescaped.to_sym
46
41
  end
47
42
 
48
- # The String a literal `Prism::SymbolNode` / `Prism::StringNode`
49
- # names, or `nil` for any other node (including `nil`). The
50
- # String-returning sibling of {.symbol_or_string} for callers
51
- # that key on the raw name rather than the interned Symbol (route
52
- # helpers, factory names, filter targets). `#unescaped` round-trips
53
- # an interpolation-free `:foo` / `"foo"` to `"foo"` for both kinds.
43
+ # The String a literal `Prism::SymbolNode` / `Prism::StringNode` names, or `nil` for any other node
44
+ # (including `nil`). The String-returning sibling of {.symbol_or_string} — for callers that key on the
45
+ # raw name rather than the interned Symbol (route helpers, factory names, filter targets).
46
+ # `#unescaped` round-trips an interpolation-free `:foo` / `"foo"` to `"foo"` for both kinds.
54
47
  #
55
48
  # @param node [Prism::Node, nil]
56
49
  # @return [String, nil]
@@ -60,11 +53,9 @@ module Rigor
60
53
  node.unescaped
61
54
  end
62
55
 
63
- # The Symbol a literal `Prism::SymbolNode` names, or `nil` for any
64
- # other node (including a `Prism::StringNode` and `nil`). Stricter
65
- # than {.symbol_or_string}: a DSL that accepts only `:draft` and
66
- # not `"draft"` keeps that distinction by reaching for this rather
67
- # than the Symbol-or-String form.
56
+ # The Symbol a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
57
+ # `Prism::StringNode` and `nil`). Stricter than {.symbol_or_string}: a DSL that accepts only `:draft`
58
+ # and not `"draft"` keeps that distinction by reaching for this rather than the Symbol-or-String form.
68
59
  #
69
60
  # @param node [Prism::Node, nil]
70
61
  # @return [Symbol, nil]
@@ -74,9 +65,8 @@ module Rigor
74
65
  node.unescaped.to_sym
75
66
  end
76
67
 
77
- # The String a literal `Prism::SymbolNode` names, or `nil` for any
78
- # other node (including a `Prism::StringNode` and `nil`). The
79
- # String-returning sibling of {.symbol} — SymbolNode-only, but the
68
+ # The String a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
69
+ # `Prism::StringNode` and `nil`). The String-returning sibling of {.symbol} — SymbolNode-only, but the
80
70
  # caller wants the raw name rather than the interned Symbol.
81
71
  #
82
72
  # @param node [Prism::Node, nil]
@@ -87,9 +77,8 @@ module Rigor
87
77
  node.unescaped
88
78
  end
89
79
 
90
- # Every literal Symbol/String positional argument of a call, in
91
- # source order. Non-literal arguments are dropped. Returns `[]` when
92
- # the call has no argument list.
80
+ # Every literal Symbol/String positional argument of a call, in source order. Non-literal arguments
81
+ # are dropped. Returns `[]` when the call has no argument list.
93
82
  #
94
83
  # @param call_node [Prism::CallNode, nil]
95
84
  # @return [Array<Symbol>]
@@ -100,11 +89,9 @@ module Rigor
100
89
  args.filter_map { |arg| symbol_or_string(arg) }
101
90
  end
102
91
 
103
- # Whether a node is a literal `Prism::SymbolNode` that names `name`.
104
- # The key-comparison counterpart to {.symbol_name} — for callers
105
- # that need a predicate rather than an extraction (hash-key matching
106
- # in keyword or assoc argument positions, e.g.
107
- # `el.is_a?(AssocNode) && symbol_named?(el.key, "required")`).
92
+ # Whether a node is a literal `Prism::SymbolNode` that names `name`. The key-comparison counterpart to
93
+ # {.symbol_name} — for callers that need a predicate rather than an extraction (hash-key matching in
94
+ # keyword or assoc argument positions, e.g. `el.is_a?(AssocNode) && symbol_named?(el.key, "required")`).
108
95
  # Uses `#unescaped` (not `#value`) for round-trip consistency.
109
96
  #
110
97
  # @param node [Prism::Node, nil]
@@ -114,9 +101,8 @@ module Rigor
114
101
  node.is_a?(Prism::SymbolNode) && node.unescaped == name
115
102
  end
116
103
 
117
- # The literal Symbol/String at positional `index`, or `nil` when the
118
- # call has no argument list, the index is out of range, or the
119
- # argument there is not a literal Symbol/String.
104
+ # The literal Symbol/String at positional `index`, or `nil` when the call has no argument list, the
105
+ # index is out of range, or the argument there is not a literal Symbol/String.
120
106
  #
121
107
  # @param call_node [Prism::CallNode, nil]
122
108
  # @param index [Integer]
@@ -6,17 +6,16 @@ module Rigor
6
6
  module Source
7
7
  # Locates the deepest Prism AST node enclosing a given source position.
8
8
  #
9
- # The locator works on byte offsets internally so that multibyte source
10
- # text behaves consistently with Prism, which itself reports offsets in
11
- # bytes. Convenience constructors translate from `(line, column)` pairs.
9
+ # The locator works on byte offsets internally so that multibyte source text behaves consistently with
10
+ # Prism, which itself reports offsets in bytes. Convenience constructors translate from `(line, column)`
11
+ # pairs.
12
12
  #
13
- # Lines are 1-indexed (matching editor / Prism / gcc conventions).
14
- # Columns are 1-indexed when supplied via the `(line, column)` API; this
15
- # matches the canonical `file.rb:line:col` form most tools emit. Internal
16
- # offsets remain 0-indexed bytes.
13
+ # Lines are 1-indexed (matching editor / Prism / gcc conventions). Columns are 1-indexed when supplied
14
+ # via the `(line, column)` API; this matches the canonical `file.rb:line:col` form most tools emit.
15
+ # Internal offsets remain 0-indexed bytes.
17
16
  #
18
- # The locator is read-only: a single instance binds to one source buffer
19
- # and AST root, and queries are pure functions of the byte offset.
17
+ # The locator is read-only: a single instance binds to one source buffer and AST root, and queries are
18
+ # pure functions of the byte offset.
20
19
  class NodeLocator
21
20
  class OutOfRangeError < StandardError; end
22
21
 
@@ -58,8 +57,7 @@ module Rigor
58
57
  descend(@root, offset)
59
58
  end
60
59
 
61
- # Translate a `(line, column)` pair into a 0-indexed byte offset for the
62
- # bound source buffer.
60
+ # Translate a `(line, column)` pair into a 0-indexed byte offset for the bound source buffer.
63
61
  def position_to_offset(line, column)
64
62
  raise ArgumentError, "source buffer required for position lookup" if @source.nil?
65
63
  raise OutOfRangeError, "line must be >= 1, got #{line}" if line < 1
@@ -6,14 +6,12 @@ module Rigor
6
6
  module Source
7
7
  # Yields every `Prism::Node` reachable from a root in DFS pre-order.
8
8
  #
9
- # The walker is the source-positioning analogue to `NodeLocator`: where the
10
- # locator answers "what node is at this point?", the walker enumerates the
11
- # full set of Prism nodes for tooling that needs to operate on each one
12
- # (coverage probes, lint passes, IDE outlines).
9
+ # The walker is the source-positioning analogue to `NodeLocator`: where the locator answers "what node
10
+ # is at this point?", the walker enumerates the full set of Prism nodes for tooling that needs to
11
+ # operate on each one (coverage probes, lint passes, IDE outlines).
13
12
  #
14
- # Non-Prism children (literals embedded in node attributes, virtual nodes,
15
- # or `nil` slots) are silently skipped so callers can rely on every yielded
16
- # value responding to the `Prism::Node` API.
13
+ # Non-Prism children (literals embedded in node attributes, virtual nodes, or `nil` slots) are silently
14
+ # skipped so callers can rely on every yielded value responding to the `Prism::Node` API.
17
15
  module NodeWalker
18
16
  module_function
19
17
 
@@ -33,12 +31,10 @@ module Rigor
33
31
  node.compact_child_nodes.each { |child| walk(child, &) }
34
32
  end
35
33
 
36
- # Like {.each}, but also yields the node's lexical ancestor chain
37
- # (outermost first, EXCLUDING the node itself). The yielded
38
- # `ancestors` array is the live descent stack — callers that retain
39
- # it past the block invocation MUST copy it (`Plugin::NodeContext`
40
- # does). Used by the plugin engine to give `node_rule` blocks their
41
- # enclosing class / method / block context (ADR-37 slice 1d).
34
+ # Like {.each}, but also yields the node's lexical ancestor chain (outermost first, EXCLUDING the node
35
+ # itself). The yielded `ancestors` array is the live descent stack — callers that retain it past the
36
+ # block invocation MUST copy it (`Plugin::NodeContext` does). Used by the plugin engine to give
37
+ # `node_rule` blocks their enclosing class / method / block context (ADR-37 slice 1d).
42
38
  #
43
39
  # @yieldparam node [Prism::Node]
44
40
  # @yieldparam ancestors [Array<Prism::Node>]
data/lib/rigor/source.rb CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  # Source-text and AST positioning utilities.
4
4
  #
5
- # Anything that maps between a Ruby source buffer and Prism AST nodes belongs
6
- # here. The contents of this namespace deliberately stay independent of the
7
- # inference engine so that future tooling (LSP, refactoring helpers, doc
8
- # extractors) can reuse the same primitives without dragging in `Rigor::Type`.
5
+ # Anything that maps between a Ruby source buffer and Prism AST nodes belongs here. The contents of this
6
+ # namespace deliberately stay independent of the inference engine so that future tooling (LSP, refactoring
7
+ # helpers, doc extractors) can reuse the same primitives without dragging in `Rigor::Type`.
9
8
  module Rigor
10
9
  module Source
11
10
  end
data/lib/rigor/testing.rb CHANGED
@@ -3,21 +3,18 @@
3
3
  module Rigor
4
4
  # Slice 7 phase 19 — PHPStan-style typing helpers.
5
5
  #
6
- # `Rigor::Testing` ships two runtime no-op helpers that serve
7
- # as anchors for static-analysis diagnostics:
8
- #
9
- # - `dump_type(value)` — returns `value` unchanged at runtime.
10
- # The Rigor analyzer surfaces an `:info`-severity diagnostic
11
- # at the call site showing the inferred type of `value` so
12
- # the user can see what the engine sees at that program point.
13
- # - `assert_type(expected, value)` returns `value` unchanged
14
- # at runtime. The analyzer compares `value`'s inferred type
15
- # (rendered through `Rigor::Type#describe(:short)`) against
16
- # the literal `expected` String; a mismatch produces an
17
- # `:error`-severity diagnostic. This lets a user-written
18
- # fixture be self-asserting: `rigor check fixture.rb` exits
19
- # non-zero exactly when the engine's inference drifts from
20
- # what the fixture documents.
6
+ # `Rigor::Testing` ships two runtime no-op helpers that serve as anchors for
7
+ # static-analysis diagnostics:
8
+ #
9
+ # - `dump_type(value)` — returns `value` unchanged at runtime. The Rigor analyzer surfaces
10
+ # an `:info`-severity diagnostic at the call site showing the inferred type of `value`
11
+ # so the user can see what the engine sees at that program point.
12
+ # - `assert_type(expected, value)` returns `value` unchanged at runtime. The analyzer
13
+ # compares `value`'s inferred type (rendered through `Rigor::Type#describe(:short)`)
14
+ # against the literal `expected` String; a mismatch produces an `:error`-severity
15
+ # diagnostic. This lets a user-written fixture be self-asserting: `rigor check
16
+ # fixture.rb` exits non-zero exactly when the engine's inference drifts from what the
17
+ # fixture documents.
21
18
  #
22
19
  # Three usage shapes are recognised by the static rules:
23
20
  #
@@ -36,9 +33,8 @@ module Rigor
36
33
  # Rigor.dump_type(x)
37
34
  # Rigor.assert_type("Constant[\"hello\"]", x)
38
35
  #
39
- # All three resolve to the same no-op runtime body, so a
40
- # fixture may freely run under MRI without depending on the
41
- # analyzer being present.
36
+ # All three resolve to the same no-op runtime body, so a fixture may freely run under MRI
37
+ # without depending on the analyzer being present.
42
38
  module Testing
43
39
  module_function
44
40
 
@@ -52,8 +48,8 @@ module Rigor
52
48
  end
53
49
 
54
50
  class << self
55
- # Convenience aliases on `Rigor` itself, so fixtures can
56
- # write `Rigor.dump_type(x)` without an `include` line.
51
+ # Convenience aliases on `Rigor` itself, so fixtures can write `Rigor.dump_type(x)`
52
+ # without an `include` line.
57
53
  def dump_type(value)
58
54
  Testing.dump_type(value)
59
55
  end
@@ -6,17 +6,13 @@ module Rigor
6
6
  module Triage
7
7
  # ADR-23 § "Heuristic catalogue" — the six v1 recognisers.
8
8
  #
9
- # {.recognise} runs them in order over the diagnostic stream.
10
- # Each recogniser sees only the diagnostics not yet claimed by
11
- # an earlier one, so a `5.minutes` diagnostic counted by H1
12
- # (ActiveSupport) is not re-counted by H2 (monkey-patch).
9
+ # {.recognise} runs them in order over the diagnostic stream. Each recogniser sees only the diagnostics not yet
10
+ # claimed by an earlier one, so a `5.minutes` diagnostic counted by H1 (ActiveSupport) is not re-counted by H2
11
+ # (monkey-patch).
13
12
  #
14
- # WD3 / slice 4: recognisers key on the structured
15
- # `qualified_rule` first; where they additionally need the
16
- # receiver type or method name they read the structured
17
- # `Diagnostic#receiver_type` / `#method_name` fields, falling
18
- # back to parsing the diagnostic message only when those are
19
- # absent. A parse failure degrades to "skip this diagnostic" —
13
+ # WD3 / slice 4: recognisers key on the structured `qualified_rule` first; where they additionally need the receiver
14
+ # type or method name they read the structured `Diagnostic#receiver_type` / `#method_name` fields, falling back to
15
+ # parsing the diagnostic message only when those are absent. A parse failure degrades to "skip this diagnostic" —
20
16
  # never a crash.
21
17
  module Catalogue # rubocop:disable Metrics/ModuleLength
22
18
  module_function
@@ -27,9 +23,8 @@ module Rigor
27
23
  # `undefined method `foo' for <receiver>`
28
24
  UNDEF_METHOD = /\Aundefined method [`'"]([^`'"]+)['"`] for (.+)\z/
29
25
 
30
- # ActiveSupport `core_ext` selectors, grouped by the core
31
- # class they extend. Survey-grounded (the dominant clusters
32
- # from the five-project survey + the Mastodon measurement).
26
+ # ActiveSupport `core_ext` selectors, grouped by the core class they extend. Survey-grounded (the dominant
27
+ # clusters from the five-project survey + the Mastodon measurement).
33
28
  AS_NUMERIC = %w[
34
29
  day days hour hours minute minutes second seconds week weeks
35
30
  fortnight fortnights month months year years
@@ -72,8 +67,8 @@ module Rigor
72
67
  }.freeze
73
68
  private_constant :AS_NUMERIC, :AS_STRING, :AS_HASH, :AS_ARRAY, :AS_TIMEDATE
74
69
 
75
- # ActiveRecord query-builder methods. When flagged on an
76
- # `Array[...]` receiver they signal a relation misinference.
70
+ # ActiveRecord query-builder methods. When flagged on an `Array[...]` receiver they signal a relation
71
+ # misinference.
77
72
  AR_QUERY_METHODS = %w[
78
73
  where joins includes preload eager_load references select
79
74
  order reorder distinct group having limit offset pluck
@@ -87,16 +82,12 @@ module Rigor
87
82
  GENUINE_BUG_MAX_COUNT = 5 # rule total ≤ N → "likely genuine bug"
88
83
  private_constant :SYSTEMIC_THRESHOLD, :MONKEY_PATCH_MIN_FILES, :GENUINE_BUG_MAX_COUNT
89
84
 
90
- # WD6 (ADR-23): H5 (systemic cluster) and H6 (genuine bugs) are
91
- # the only count-based, severity-agnostic recognisers, so they
92
- # alone risk reading plugin recognition trace (`:info`
93
- # `*.model-call` / `*.helper`, …) as a "systemic cluster" or a
94
- # "genuine bug" frightening working code. They are guarded
95
- # against `:info` unless `include_info` restores the pre-v0.2.3
96
- # behaviour. Every other recogniser keys on an error/warning rule
97
- # (H1/H2/H2K/H4/H7) or intentionally reads an info notice
98
- # (H3 — the `gem-without-rbs` `rbs.coverage.missing-gem`), so the
99
- # full pool is correct for them.
85
+ # WD6 (ADR-23): H5 (systemic cluster) and H6 (genuine bugs) are the only count-based, severity-agnostic
86
+ # recognisers, so they alone risk reading plugin recognition trace (`:info` `*.model-call` / `*.helper`, …) as a
87
+ # "systemic cluster" or a "genuine bug" — frightening working code. They are guarded against `:info` unless
88
+ # `include_info` restores the pre-v0.2.3 behaviour. Every other recogniser keys on an error/warning rule
89
+ # (H1/H2/H2K/H4/H7) or intentionally reads an info notice (H3 — the `gem-without-rbs` `rbs.coverage.missing-gem`),
90
+ # so the full pool is correct for them.
100
91
  INFO_GUARDED = %i[h5_systemic_cluster h6_genuine_bugs].freeze
101
92
  private_constant :INFO_GUARDED
102
93
 
@@ -116,19 +107,14 @@ module Rigor
116
107
  end
117
108
  end
118
109
 
119
- # H4 (ActiveRecord query methods) runs before H2 (generic
120
- # monkey-patch): a known AR method on `Array[...]` deserves
121
- # the precise relation-misinference hint, not the generic
122
- # "project core-ext" guess H2 would otherwise claim it for.
110
+ # H4 (ActiveRecord query methods) runs before H2 (generic monkey-patch): a known AR method on `Array[...]`
111
+ # deserves the precise relation-misinference hint, not the generic "project core-ext" guess H2 would otherwise
112
+ # claim it for.
123
113
  #
124
- # H2K (known project patch) runs before the generic H2: the
125
- # engine has already proved the defining site via the
126
- # `project_definition_site` field (ADR-17), so those
127
- # diagnostics get the high-confidence file-naming hint rather
128
- # than the spread-based guess. H7 (unresolved toplevel) runs
129
- # before the systemic / genuine-bug catch-alls so toplevel
130
- # resolution misses route to `pre_eval:` (ADR-34) instead of
131
- # reading as scattered bugs.
114
+ # H2K (known project patch) runs before the generic H2: the engine has already proved the defining site via the
115
+ # `project_definition_site` field (ADR-17), so those diagnostics get the high-confidence file-naming hint rather
116
+ # than the spread-based guess. H7 (unresolved toplevel) runs before the systemic / genuine-bug catch-alls so
117
+ # toplevel resolution misses route to `pre_eval:` (ADR-34) instead of reading as scattered bugs.
132
118
  def recognisers
133
119
  %i[h1_activesupport h4_ar_relation h3_gem_without_rbs
134
120
  h2k_known_project_patch h2_monkey_patch h7_unresolved_toplevel
@@ -154,13 +140,10 @@ module Rigor
154
140
  end
155
141
 
156
142
  # --- H2K — known project monkey-patch (engine-proven) ------
157
- # ADR-17 / WD3 slice 4: the `call.undefined-method` rule sets
158
- # `project_definition_site` when the project itself defines the
159
- # called method on the receiver class somewhere in the file set
160
- # (a reopened core/stdlib/gem class the dispatcher does not
161
- # apply cross-file). That is direct evidence — not a spread
162
- # heuristic — so this recogniser is `:likely` and names the
163
- # defining files outright. It runs before the generic H2.
143
+ # ADR-17 / WD3 slice 4: the `call.undefined-method` rule sets `project_definition_site` when the project itself
144
+ # defines the called method on the receiver class somewhere in the file set (a reopened core/stdlib/gem class the
145
+ # dispatcher does not apply cross-file). That is direct evidence not a spread heuristic — so this recogniser is
146
+ # `:likely` and names the defining files outright. It runs before the generic H2.
164
147
  def h2k_known_project_patch(pool)
165
148
  matched = pool.select(&:project_definition_site)
166
149
  return nil if matched.empty?
@@ -231,13 +214,10 @@ module Rigor
231
214
  end
232
215
 
233
216
  # --- H7 — unresolved toplevel implicit-self calls ----------
234
- # ADR-34: `call.unresolved-toplevel` fires on a toplevel
235
- # implicit-self call (no receiver, outside any def / class /
236
- # module) that resolves against no visible contributor. The
237
- # canonical opt-out is `pre_eval:` the file is usually a
238
- # script relying on methods defined by a monkey-patch or a
239
- # required helper Rigor did not walk. Grouped, not per-site,
240
- # so the report names the cluster once.
217
+ # ADR-34: `call.unresolved-toplevel` fires on a toplevel implicit-self call (no receiver, outside any def / class
218
+ # / module) that resolves against no visible contributor. The canonical opt-out is `pre_eval:` — the file is
219
+ # usually a script relying on methods defined by a monkey-patch or a required helper Rigor did not walk. Grouped,
220
+ # not per-site, so the report names the cluster once.
241
221
  def h7_unresolved_toplevel(pool)
242
222
  matched = pool.select { |d| rule_of(d) == UNRESOLVED_TOPLEVEL_RULE }
243
223
  return nil if matched.empty?
@@ -290,11 +270,9 @@ module Rigor
290
270
 
291
271
  # --- shared helpers ----------------------------------------
292
272
 
293
- # WD3 / slice 4: prefer the structured `receiver_type` /
294
- # `method_name` fields the `call.undefined-method` rule now
295
- # populates; fall back to parsing the message only when they
296
- # are absent (older diagnostics, plugin-emitted rules). Either
297
- # way the receiver token is normalised through `receiver_class`.
273
+ # WD3 / slice 4: prefer the structured `receiver_type` / `method_name` fields the `call.undefined-method` rule now
274
+ # populates; fall back to parsing the message only when they are absent (older diagnostics, plugin-emitted rules).
275
+ # Either way the receiver token is normalised through `receiver_class`.
298
276
  def parse_undefined_method(diag)
299
277
  return nil unless rule_of(diag) == UNDEFINED_METHOD_RULE
300
278
 
@@ -319,8 +297,7 @@ module Rigor
319
297
  m && [m[1], m[2]]
320
298
  end
321
299
 
322
- # Normalises a message receiver token to a class name. The fold
323
- # logic is shared with the selector axis — see
300
+ # Normalises a message receiver token to a class name. The fold logic is shared with the selector axis — see
324
301
  # {Triage.normalize_receiver}.
325
302
  def receiver_class(token)
326
303
  Triage.normalize_receiver(token)
@@ -342,10 +319,9 @@ module Rigor
342
319
  groups.keys.first(3).map { |method, recv| "`#{method}` on #{recv}" }.join(", ")
343
320
  end
344
321
 
345
- # `parser: :undefined_method` (default) reads the method from
346
- # the parsed `undefined-method` shape; `parser: :toplevel`
347
- # reads the structured `method_name` field directly (the
348
- # `unresolved-toplevel` rule carries no receiver to parse).
322
+ # `parser: :undefined_method` (default) reads the method from the parsed `undefined-method` shape; `parser:
323
+ # :toplevel` reads the structured `method_name` field directly (the `unresolved-toplevel` rule carries no receiver
324
+ # to parse).
349
325
  def top_methods(diagnostics, limit: 5, parser: :undefined_method)
350
326
  names = diagnostics.filter_map do |d|
351
327
  parser == :toplevel ? d.method_name : parse_undefined_method(d)&.fetch(:method)
data/lib/rigor/triage.rb CHANGED
@@ -4,46 +4,34 @@ require_relative "triage/hint"
4
4
  require_relative "triage/catalogue"
5
5
 
6
6
  module Rigor
7
- # ADR-23 — diagnostic triage. Aggregates a `rigor check`
8
- # diagnostic stream into the data behind the `rigor triage`
9
- # report: a rule-ID distribution, per-file hotspots, and the
10
- # heuristic hint catalogue ({Triage::Catalogue}).
7
+ # ADR-23 — diagnostic triage. Aggregates a `rigor check` diagnostic stream into the data behind the `rigor triage`
8
+ # report: a rule-ID distribution, per-file hotspots, and the heuristic hint catalogue ({Triage::Catalogue}).
11
9
  #
12
- # Pure over the diagnostic stream — no second analysis pass, no
13
- # analyzer internals. `Triage.analyze` is the single entry point;
14
- # rendering is {CLI::TriageRenderer}'s job.
10
+ # Pure over the diagnostic stream — no second analysis pass, no analyzer internals. `Triage.analyze` is the single
11
+ # entry point; rendering is {CLI::TriageRenderer}'s job.
15
12
  module Triage
16
13
  UNCATEGORISED = "(uncategorised)"
17
14
 
18
15
  Summary = Data.define(:total, :error, :warning, :info)
19
16
  RuleCount = Data.define(:rule, :count)
20
17
  Hotspot = Data.define(:file, :count, :by_rule)
21
- # A (receiver-class, method) dispatch target the diagnostics
22
- # cluster on, built from the structured `Diagnostic#receiver_type`
23
- # / `#method_name` fields never from message-string parsing.
24
- # `receiver` is nil for method-only diagnostics (a toplevel call,
25
- # a `def`-side return / override finding that has no call
26
- # receiver); `files` is the distinct-file count (a systemic vs.
27
- # localised signal); `rules` is the per-rule breakdown.
18
+ # A (receiver-class, method) dispatch target the diagnostics cluster on, built from the structured
19
+ # `Diagnostic#receiver_type` / `#method_name` fields — never from message-string parsing. `receiver` is nil for
20
+ # method-only diagnostics (a toplevel call, a `def`-side return / override finding that has no call receiver);
21
+ # `files` is the distinct-file count (a systemic vs. localised signal); `rules` is the per-rule breakdown.
28
22
  Selector = Data.define(:receiver, :method_name, :count, :files, :rules)
29
23
  Report = Data.define(:summary, :distribution, :selectors, :hotspots, :hints, :include_info)
30
24
 
31
25
  module_function
32
26
 
33
- # WD6 (ADR-23): the volume views — distribution / selectors /
34
- # hotspots route only the *actionable* diagnostics (error +
35
- # warning) by default. Plugin-emitted `:info` diagnostics are
36
- # overwhelmingly recognition trace (`plugin.activerecord.model-call`,
37
- # `plugin.rails-routes.helper`, ) positive "Rigor resolved this
38
- # call" records, not problems and on a real Rails app they swamp
39
- # the genuine error/warning signal (the field trip: 257 of 267
40
- # diagnostics were such trace) and invert the hotspot ranking
41
- # towards the files with the *most working* code. The summary still
42
- # reports the full info count, and `include_info: true` (the
43
- # `--include-info` flag) restores the pre-v0.2.3 behaviour. Hints
44
- # always see the full stream so the `gem-without-rbs` notice (an
45
- # info-severity `rbs.coverage.missing-gem`) survives; the
46
- # count-based H5/H6 recognisers guard against info themselves so
27
+ # WD6 (ADR-23): the volume views — distribution / selectors / hotspots — route only the *actionable* diagnostics
28
+ # (error + warning) by default. Plugin-emitted `:info` diagnostics are overwhelmingly recognition trace
29
+ # (`plugin.activerecord.model-call`, `plugin.rails-routes.helper`, …) positive "Rigor resolved this call" records,
30
+ # not problems and on a real Rails app they swamp the genuine error/warning signal (the field trip: 257 of 267
31
+ # diagnostics were such trace) and invert the hotspot ranking towards the files with the *most working* code. The
32
+ # summary still reports the full info count, and `include_info: true` (the `--include-info` flag) restores the
33
+ # pre-v0.2.3 behaviour. Hints always see the full stream so the `gem-without-rbs` notice (an info-severity
34
+ # `rbs.coverage.missing-gem`) survives; the count-based H5/H6 recognisers guard against info themselves so
47
35
  # recognition trace never reads as a bug.
48
36
  #
49
37
  # @param diagnostics [Array<Analysis::Diagnostic>]
@@ -63,8 +51,8 @@ module Rigor
63
51
  )
64
52
  end
65
53
 
66
- # Diagnostics without a `rule` (parse errors, internal-analyzer
67
- # errors) bucket under a single sentinel rather than vanishing.
54
+ # Diagnostics without a `rule` (parse errors, internal-analyzer errors) bucket under a single sentinel rather than
55
+ # vanishing.
68
56
  def rule_key(diagnostic)
69
57
  diagnostic.qualified_rule || UNCATEGORISED
70
58
  end
@@ -85,15 +73,11 @@ module Rigor
85
73
  .sort_by { |row| [-row.count, row.rule] }
86
74
  end
87
75
 
88
- # The class/method aggregation axis (ADR-23 follow-up). Groups
89
- # every diagnostic that carries a `method_name` by its
90
- # `(receiver_type, method_name)` pair so a consumer can answer
91
- # "which method / class concentrates the diagnostics?" with a
92
- # `jq` query over the JSON instead of parsing message text.
93
- # Method-only diagnostics (nil `receiver_type`) keep a null
94
- # `receiver` and still group by method. The full list is
95
- # returned uncapped — the JSON is the agent-facing surface; the
96
- # text renderer caps its own rows.
76
+ # The class/method aggregation axis (ADR-23 follow-up). Groups every diagnostic that carries a `method_name` by its
77
+ # `(receiver_type, method_name)` pair so a consumer can answer "which method / class concentrates the diagnostics?"
78
+ # with a `jq` query over the JSON instead of parsing message text. Method-only diagnostics (nil `receiver_type`)
79
+ # keep a null `receiver` and still group by method. The full list is returned uncapped — the JSON is the
80
+ # agent-facing surface; the text renderer caps its own rows.
97
81
  def build_selectors(diagnostics)
98
82
  diagnostics.select(&:method_name)
99
83
  .group_by { |d| [normalize_receiver(d.receiver_type) || d.receiver_type, d.method_name.to_s] }
@@ -101,16 +85,12 @@ module Rigor
101
85
  .sort_by { |s| [-s.count, s.receiver.to_s, s.method_name] }
102
86
  end
103
87
 
104
- # Folds a receiver token — a `Diagnostic#receiver_type` display
105
- # string or a message-parsed token to the class the diagnostics
106
- # should bucket under, so the selector axis does not fragment one
107
- # method across every distinct literal receiver. String / integer
108
- # / float / symbol literals collapse to their class; `singleton(C)`
109
- # and a bare `C` fold to `C`; a generic `C[...]` keeps the
110
- # `Array[String]` element form (the AR-relation heuristic keys on
111
- # it). Returns nil for a token it cannot reduce to a class (a
112
- # union display, an inferred shape) — the caller keeps the raw
113
- # string then, never losing the row. Shared with {Catalogue}.
88
+ # Folds a receiver token — a `Diagnostic#receiver_type` display string or a message-parsed token — to the class the
89
+ # diagnostics should bucket under, so the selector axis does not fragment one method across every distinct literal
90
+ # receiver. String / integer / float / symbol literals collapse to their class; `singleton(C)` and a bare `C` fold
91
+ # to `C`; a generic `C[...]` keeps the `Array[String]` element form (the AR-relation heuristic keys on it). Returns
92
+ # nil for a token it cannot reduce to a class (a union display, an inferred shape) — the caller keeps the raw string
93
+ # then, never losing the row. Shared with {Catalogue}.
114
94
  def normalize_receiver(token)
115
95
  return nil if token.nil?
116
96
 
@@ -170,9 +150,8 @@ module Rigor
170
150
  { "file" => h.file, "count" => h.count, "by_rule" => h.by_rule }
171
151
  end,
172
152
  "hints" => report.hints.map(&:to_h),
173
- # WD6: false means distribution / selectors / hotspots above
174
- # exclude `:info` (their counts will not sum to summary.total);
175
- # the summary's `info` field still reports the full count.
153
+ # WD6: false means distribution / selectors / hotspots above exclude `:info` (their counts will not sum to
154
+ # summary.total); the summary's `info` field still reports the full count.
176
155
  "include_info" => report.include_info
177
156
  }
178
157
  end