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
@@ -8,54 +8,38 @@ require_relative "mailer_index"
8
8
  module Rigor
9
9
  module Plugin
10
10
  class Actionmailer < Rigor::Plugin::Base
11
- # Walks the configured mailer-search paths via the
12
- # plugin's `IoBoundary`, parses each `.rb` file with
13
- # Prism, and collects classes whose immediate superclass
14
- # is one of the configured base classes.
11
+ # Walks the configured mailer-search paths via the plugin's `IoBoundary`, parses each `.rb` file
12
+ # with Prism, and collects classes whose immediate superclass is one of the configured base
13
+ # classes.
15
14
  #
16
15
  # For each discovered class, the discoverer:
17
16
  #
18
- # - Reads the instance-side `def` nodes and records each
19
- # one as an action method, capturing the arity envelope.
20
- # - For each (class, action) pair, attempts to read every
21
- # candidate view template under
22
- # `app/views/<mailer_underscore>/<action>.{html,text}.erb`.
23
- # Existing templates feed the IoBoundary's cache
24
- # descriptor (so the cache invalidates when the
25
- # template changes); missing templates are recorded so
26
- # the plugin can surface a diagnostic on the mailer
27
- # class definition.
17
+ # - Reads the instance-side `def` nodes and records each one as an action method, capturing the
18
+ # arity envelope.
19
+ # - For each (class, action) pair, attempts to read every candidate view template under
20
+ # `app/views/<mailer_underscore>/<action>.{html,text}.erb`. Existing templates feed the
21
+ # IoBoundary's cache descriptor (so the cache invalidates when the template changes); missing
22
+ # templates are recorded so the plugin can surface a diagnostic on the mailer class definition.
28
23
  #
29
24
  # Limitations (intentional for v0.1.0):
30
25
  #
31
- # - Direct-superclass match only. `class CustomerMailer
32
- # < BaseMailer` where `BaseMailer < ApplicationMailer`
33
- # is NOT discovered. Add `BaseMailer` to
34
- # `mailer_base_classes` if needed.
35
- # - Action methods are read from the syntactic instance-
36
- # side `def` list. Methods built via `define_method`,
37
- # `private`, or non-action helpers (e.g. methods
38
- # starting with `_`) are out of scope. The discoverer
39
- # filters obvious non-actions (`initialize`, names
40
- # prefixed with `_`).
41
- # - Adding a brand-new view file under
42
- # `app/views/<mailer>/` will NOT invalidate the
43
- # cached index until something the mailer file
44
- # touches changes. This is the standard read-tracking
45
- # trade-off — only files we successfully read get
46
- # digested into the descriptor.
26
+ # - Direct-superclass match only. `class CustomerMailer < BaseMailer` where `BaseMailer <
27
+ # ApplicationMailer` is NOT discovered. Add `BaseMailer` to `mailer_base_classes` if needed.
28
+ # - Action methods are read from the syntactic instance-side `def` list. Methods built via
29
+ # `define_method`, `private`, or non-action helpers (e.g. methods starting with `_`) are out of
30
+ # scope. The discoverer filters obvious non-actions (`initialize`, names prefixed with `_`).
31
+ # - Adding a brand-new view file under `app/views/<mailer>/` will NOT invalidate the cached index
32
+ # until something the mailer file touches changes. This is the standard read-tracking trade-off
33
+ # only files we successfully read get digested into the descriptor.
47
34
  class MailerDiscoverer
48
35
  DEFAULT_VIEWS_ROOT = "app/views"
49
36
  VIEW_FORMATS = %w[html text].freeze
50
37
  VIEW_EXTENSIONS = %w[erb haml slim].freeze
51
38
 
52
39
  # @param io_boundary [Rigor::Plugin::IoBoundary]
53
- # @param search_paths [Array<String>] absolute or
54
- # project-relative paths to scan for mailers.
55
- # @param base_classes [Array<String>] direct
56
- # superclasses that mark a class as a mailer.
57
- # @param views_root [String] absolute or project-
58
- # relative path to the views directory (typically
40
+ # @param search_paths [Array<String>] absolute or project-relative paths to scan for mailers.
41
+ # @param base_classes [Array<String>] direct superclasses that mark a class as a mailer.
42
+ # @param views_root [String] absolute or project-relative path to the views directory (typically
59
43
  # `app/views`).
60
44
  def initialize(io_boundary:, search_paths:, base_classes:, views_root: DEFAULT_VIEWS_ROOT)
61
45
  @io_boundary = io_boundary
@@ -66,11 +50,9 @@ module Rigor
66
50
 
67
51
  # @return [MailerIndex]
68
52
  def discover
69
- # Two-pass: first collect every module's defs (for
70
- # the include-following step), then build per-class
71
- # entries that pull in actions from include'd modules.
72
- # GitLab's `Notify` mailer derives every action from
73
- # `Emails::*` concerns under `app/mailers/emails/`.
53
+ # Two-pass: first collect every module's defs (for the include-following step), then build
54
+ # per-class entries that pull in actions from include'd modules. GitLab's `Notify` mailer
55
+ # derives every action from `Emails::*` concerns under `app/mailers/emails/`.
74
56
  module_actions = {} # module_fqn => Hash<Symbol, ActionEntry>
75
57
  class_visits = [] # collected (class_name, path, def_nodes, includes)
76
58
 
@@ -135,10 +117,8 @@ module Rigor
135
117
  walk_for_mailers(node.body, inner_path, &) if node.body
136
118
  end
137
119
 
138
- # Collects qualified-constant names passed to `include
139
- # X` calls inside the class body. Used to look up
140
- # concern-module action definitions (GitLab's
141
- # `Notify` mailer derives every action from
120
+ # Collects qualified-constant names passed to `include X` calls inside the class body. Used to
121
+ # look up concern-module action definitions (GitLab's `Notify` mailer derives every action from
142
122
  # `Emails::Issues`, `Emails::MergeRequests`, etc.).
143
123
  def collect_includes(body)
144
124
  return [] if body.nil?
@@ -155,10 +135,9 @@ module Rigor
155
135
  names
156
136
  end
157
137
 
158
- # Walks the AST collecting every module's instance-side
159
- # def nodes by fully-qualified module name. The same
160
- # `collect_action_defs` filter applies (private /
161
- # `_`-prefixed / callback-target methods skipped).
138
+ # Walks the AST collecting every module's instance-side def nodes by fully-qualified module
139
+ # name. The same `collect_action_defs` filter applies (private / `_`-prefixed / callback-target
140
+ # methods skipped).
162
141
  def collect_module_actions(node, lexical_path, accumulator)
163
142
  return if node.nil?
164
143
 
@@ -210,25 +189,18 @@ module Rigor
210
189
  end
211
190
  end
212
191
 
213
- # Returns the instance-side `def` nodes that look like
214
- # mailer actions. Filters non-actions:
192
+ # Returns the instance-side `def` nodes that look like mailer actions. Filters non-actions:
215
193
  # - `initialize`
216
- # - methods starting with `_` (Ruby convention for
217
- # private/internal)
194
+ # - methods starting with `_` (Ruby convention for private/internal)
218
195
  # - `def self.<name>` (singleton-side)
219
- # - methods after a bare `private` (or
220
- # `public` `private` transition) — these are
221
- # internal helpers, not actions
196
+ # - methods after a bare `private` (or `public` → `private` transition) — these are internal
197
+ # helpers, not actions
222
198
  # - methods named as a `private :foo` argument
223
- # - methods named as a callback target
224
- # (`before_action :name`, `after_action`,
225
- # `around_action`)
199
+ # - methods named as a callback target (`before_action :name`, `after_action`, `around_action`)
226
200
  #
227
- # Pre-fix, Mastodon's `AdminMailer#process_params` /
228
- # `set_instance` / `set_locale` / `set_important_headers!`
229
- # all surfaced as missing-view because the bare `private`
230
- # keyword wasn't honoured. ~19 false positives across
231
- # Mastodon's mailers.
201
+ # Pre-fix, Mastodon's `AdminMailer#process_params` / `set_instance` / `set_locale` /
202
+ # `set_important_headers!` all surfaced as missing-view because the bare `private` keyword
203
+ # wasn't honoured. ~19 false positives across Mastodon's mailers.
232
204
  CALLBACK_DECLARATIONS = %i[before_action after_action around_action].freeze
233
205
  private_constant :CALLBACK_DECLARATIONS
234
206
 
@@ -252,11 +224,9 @@ module Rigor
252
224
  end
253
225
  end
254
226
 
255
- # First pass over the class body: collect (a) names
256
- # passed to `private :foo` / `protected :foo` (explicit
257
- # visibility-on-existing-method form), and (b) Symbol
258
- # arguments to callback declarations
259
- # (`before_action :setup`, etc.).
227
+ # First pass over the class body: collect (a) names passed to `private :foo` / `protected :foo`
228
+ # (explicit visibility-on-existing-method form), and (b) Symbol arguments to callback
229
+ # declarations (`before_action :setup`, etc.).
260
230
  def collect_visibility_and_callbacks(body)
261
231
  private_names = []
262
232
  callback_names = []
@@ -278,10 +248,9 @@ module Rigor
278
248
  [private_names.to_set, callback_names.to_set]
279
249
  end
280
250
 
281
- # Returns the new visibility scope state after observing
282
- # `node`. Bare `private` / `protected` / `public` switch
283
- # state; the `private :foo` arg-bearing form does NOT
284
- # (already handled by `collect_visibility_and_callbacks`).
251
+ # Returns the new visibility scope state after observing `node`. Bare `private` / `protected` /
252
+ # `public` switch state; the `private :foo` arg-bearing form does NOT (already handled by
253
+ # `collect_visibility_and_callbacks`).
285
254
  def next_visibility(node, current)
286
255
  return current unless node.is_a?(Prism::CallNode)
287
256
  return current unless node.receiver.nil?
@@ -301,11 +270,9 @@ module Rigor
301
270
  [entry.method_name, entry]
302
271
  end
303
272
 
304
- # Merge actions from include'd modules (pre-collected
305
- # in `module_actions` keyed by fully-qualified name).
306
- # Unresolvable includes are tracked; `unresolved_includes?`
307
- # (consumed by the analyzer) downgrades `unknown-action`
308
- # to silence when any include remains unresolved.
273
+ # Merge actions from include'd modules (pre-collected in `module_actions` keyed by
274
+ # fully-qualified name). Unresolvable includes are tracked; `unresolved_includes?` (consumed
275
+ # by the analyzer) downgrades `unknown-action` to silence when any include remains unresolved.
309
276
  unresolved_includes = []
310
277
  includes.each do |include_name|
311
278
  inc_actions = module_actions[include_name]
@@ -357,17 +324,14 @@ module Rigor
357
324
  end
358
325
 
359
326
  # Checks whether *any* template under
360
- # `app/views/<underscore>/<action>.{html,text}.{erb,haml,slim}`
361
- # exists, by attempting to read each candidate via the
362
- # IoBoundary. Successful reads are recorded by the
363
- # boundary; failed reads (missing file or access
364
- # denied) are swallowed.
327
+ # `app/views/<underscore>/<action>.{html,text}.{erb,haml,slim}` exists, by attempting to read
328
+ # each candidate via the IoBoundary. Successful reads are recorded by the boundary; failed reads
329
+ # (missing file or access denied) are swallowed.
365
330
  def view_exists?(class_name, action_name)
366
331
  views_root_absolute = File.expand_path(@views_root)
367
- # ADR-39: the real ActiveSupport::Inflector resolves the mailer's
368
- # view directory (`Foo::BarMailer` → `foo/bar_mailer`), so a
369
- # divergence from Rails' real underscore can't point the
370
- # missing-view check at the wrong directory.
332
+ # ADR-39: the real ActiveSupport::Inflector resolves the mailer's view directory
333
+ # (`Foo::BarMailer` → `foo/bar_mailer`), so a divergence from Rails' real underscore can't
334
+ # point the missing-view check at the wrong directory.
371
335
  underscore_path = Rigor::Plugin::Inflector.underscore(class_name.delete_prefix("::"))
372
336
  mailer_dir = File.join(views_root_absolute, underscore_path)
373
337
 
@@ -3,17 +3,13 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Actionmailer < Rigor::Plugin::Base
6
- # Frozen catalogue of discovered Mailer classes, each
7
- # carrying:
6
+ # Frozen catalogue of discovered Mailer classes, each carrying:
8
7
  #
9
- # - the action methods it defines (arity envelope per
10
- # action; same shape as `rigor-activejob`'s
8
+ # - the action methods it defines (arity envelope per action; same shape as `rigor-activejob`'s
11
9
  # `JobIndex::Entry`)
12
- # - the source file path the class was declared in
13
- # (used to anchor missing-view diagnostics on the
14
- # mailer file)
15
- # - the list of `(action, location)` pairs whose view
16
- # templates are missing from `app/views/`
10
+ # - the source file path the class was declared in (used to anchor missing-view diagnostics on
11
+ # the mailer file)
12
+ # - the list of `(action, location)` pairs whose view templates are missing from `app/views/`
17
13
  class MailerIndex
18
14
  ActionEntry = Data.define(:method_name, :min_arity, :max_arity, :def_line, :def_column) do
19
15
  def arity_label
@@ -33,12 +29,10 @@ module Rigor
33
29
  actions[method_name.to_sym]
34
30
  end
35
31
 
36
- # True when the mailer `include`s a module whose
37
- # source we couldn't index (typically a gem-shipped
38
- # concern that defines additional mailer actions).
39
- # Analyzer downgrades `unknown-action` to silence in
40
- # this case — the unresolved module may legitimately
41
- # provide the action.
32
+ # True when the mailer `include`s a module whose source we couldn't index (typically a
33
+ # gem-shipped concern that defines additional mailer actions). Analyzer downgrades
34
+ # `unknown-action` to silence in this case — the unresolved module may legitimately provide
35
+ # the action.
42
36
  def unresolved_includes?
43
37
  !unresolved_includes.empty?
44
38
  end
@@ -61,8 +55,7 @@ module Rigor
61
55
  @by_name.key?(class_name.to_s)
62
56
  end
63
57
 
64
- # @param file_path [String] absolute path of a mailer
65
- # file (canonicalised — see plugin entry's
58
+ # @param file_path [String] absolute path of a mailer file (canonicalised — see plugin entry's
66
59
  # `harvest`)
67
60
  # @return [ClassEntry, nil]
68
61
  def find_by_file(file_path)
@@ -8,12 +8,10 @@ require_relative "actionmailer/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-actionmailer — validates `Mailer.action(args)`
12
- # call sites and detects missing view templates.
11
+ # rigor-actionmailer — validates `Mailer.action(args)` call sites and detects missing view templates.
13
12
  #
14
13
  # Tier 1C of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
15
- # Statically discovers mailer classes by walking
16
- # `mailer_search_paths` and parsing each file with
14
+ # Statically discovers mailer classes by walking `mailer_search_paths` and parsing each file with
17
15
  # Prism — no `action_mailer` runtime dependency.
18
16
  #
19
17
  # ## Configuration
@@ -27,35 +25,27 @@ module Rigor
27
25
  #
28
26
  # ## What it checks
29
27
  #
30
- # 1. **Method existence** — `UserMailer.welcome(user)`
31
- # is flagged when `welcome` is not defined on
28
+ # 1. **Method existence** — `UserMailer.welcome(user)` is flagged when `welcome` is not defined on
32
29
  # `UserMailer`.
33
- # 2. **Argument arity** — calls with too few / too many
34
- # positional arguments emit `wrong-arity`.
35
- # 3. **View template existence** — for every action
36
- # method, at least one of
37
- # `app/views/<mailer_underscore>/<action>.{html,text}.{erb,haml,slim}`
38
- # must exist. Missing actions get a `missing-view`
39
- # diagnostic anchored on the action's `def`.
30
+ # 2. **Argument arity** — calls with too few / too many positional arguments emit `wrong-arity`.
31
+ # 3. **View template existence** — for every action method, at least one of
32
+ # `app/views/<mailer_underscore>/<action>.{html,text}.{erb,haml,slim}` must exist. Missing
33
+ # actions get a `missing-view` diagnostic anchored on the action's `def`.
40
34
  #
41
35
  # ## Limitations (v0.1.0)
42
36
  #
43
37
  # - Direct-superclass match only.
44
- # - Action methods are read from the syntactic instance-
45
- # side `def` list. `define_method` actions are out of
46
- # scope.
47
- # - Adding a brand-new view file does not invalidate the
48
- # cache until something the mailer file touches
49
- # changes.
38
+ # - Action methods are read from the syntactic instance-side `def` list. `define_method` actions are
39
+ # out of scope.
40
+ # - Adding a brand-new view file does not invalidate the cache until something the mailer file
41
+ # touches changes.
50
42
  class Actionmailer < Rigor::Plugin::Base
51
43
  manifest(
52
44
  id: "actionmailer",
53
- # Bumped 2026-05-28 — extended RESERVED_CLASS_METHODS to
54
- # include `respond_to?` / `public_send` / `send` /
55
- # `__send__` / `method` and friends so dynamic-dispatch
56
- # idioms (`Mailer.respond_to?(action)` /
57
- # `Mailer.public_send(action)`) stop firing
58
- # `unknown-action` against the Ruby reflection method.
45
+ # Bumped 2026-05-28 — extended RESERVED_CLASS_METHODS to include `respond_to?` / `public_send` /
46
+ # `send` / `__send__` / `method` and friends so dynamic-dispatch idioms
47
+ # (`Mailer.respond_to?(action)` / `Mailer.public_send(action)`) stop firing `unknown-action`
48
+ # against the Ruby reflection method.
59
49
  version: "0.4.0",
60
50
  description: "Validates ActionMailer call shape and view template existence.",
61
51
  config_schema: {
@@ -65,11 +55,10 @@ module Rigor
65
55
  }
66
56
  )
67
57
 
68
- # `watch:` covers every mailer class under `mailer_search_paths`
69
- # AND every view template under `views_root` (ADR-60 WD3) — a
70
- # newly-added view a mailer references must invalidate the index,
71
- # and `view_exists?` failures the producer never records would
72
- # otherwise be invisible to the dependency descriptor.
58
+ # `watch:` covers every mailer class under `mailer_search_paths` AND every view template under
59
+ # `views_root` (ADR-60 WD3) — a newly-added view a mailer references must invalidate the index,
60
+ # and `view_exists?` failures the producer never records would otherwise be invisible to the
61
+ # dependency descriptor.
73
62
  producer :mailer_index,
74
63
  watch: -> { [[@mailer_search_paths, "**/*.rb"], [@views_root, "**/*"]] } do |_params|
75
64
  MailerDiscoverer.new(
@@ -86,11 +75,10 @@ module Rigor
86
75
  @views_root = config.fetch("views_root").to_s
87
76
  end
88
77
 
89
- # File-level: load-error + the missing-view check (anchored on the
90
- # mailer's own source file, so it is genuinely per-file, not
91
- # per-call). The per-call mailer validation (action existence /
92
- # arity) runs over the engine-owned walk via the node_rule below
93
- # (ADR-37). The mailer index is lazily loaded + memoised, shared.
78
+ # File-level: load-error + the missing-view check (anchored on the mailer's own source file, so it
79
+ # is genuinely per-file, not per-call). The per-call mailer validation (action existence / arity)
80
+ # runs over the engine-owned walk via the node_rule below (ADR-37). The mailer index is lazily
81
+ # loaded + memoised, shared.
94
82
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
95
83
  index = producer_value(:mailer_index)
96
84
  return [load_error_diagnostic(path)] if index.nil? && producer_error(:mailer_index)
@@ -108,10 +96,9 @@ module Rigor
108
96
 
109
97
  private
110
98
 
111
- # Anchors `missing-view` diagnostics on the mailer file
112
- # itself: when the file currently being analysed is the
113
- # mailer's source file, emit one diagnostic per missing
114
- # action template at the action's `def` location.
99
+ # Anchors `missing-view` diagnostics on the mailer file itself: when the file currently being
100
+ # analysed is the mailer's source file, emit one diagnostic per missing action template at the
101
+ # action's `def` location.
115
102
  def missing_view_diagnostics(path, index)
116
103
  canonical = canonical_path(path)
117
104
  class_entry = index.find_by_file(canonical)