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
@@ -2,19 +2,15 @@
2
2
 
3
3
  module Rigor
4
4
  module TypeNode
5
- # A bare named-type reference in an RBS::Extended payload. The
6
- # `name` is the head as the parser saw it — kebab-case for
7
- # built-in refinement names (`"non-empty-string"`),
8
- # PascalCase for class-like names (`"String"`, `"Pick"`),
9
- # `lower_snake` for type-function-shaped names without
5
+ # A bare named-type reference in an RBS::Extended payload. The `name` is the head as the parser
6
+ # saw it — kebab-case for built-in refinement names (`"non-empty-string"`), PascalCase for
7
+ # class-like names (`"String"`, `"Pick"`), `lower_snake` for type-function-shaped names without
10
8
  # arguments (rare).
11
9
  #
12
- # The resolver dispatch path treats an `Identifier` as the
13
- # no-arg form: if a plugin recognises `Pick` as a TS-utility
14
- # name, it MAY still return `Dynamic[top]` for the bare
15
- # `Identifier("Pick")` since TypeScript's `Pick` is only
16
- # meaningful with two type arguments. The `Generic` carrier
17
- # is what plugin resolvers normally key on.
10
+ # The resolver dispatch path treats an `Identifier` as the no-arg form: if a plugin recognises
11
+ # `Pick` as a TS-utility name, it MAY still return `Dynamic[top]` for the bare `Identifier("Pick")`
12
+ # since TypeScript's `Pick` is only meaningful with two type arguments. The `Generic` carrier is
13
+ # what plugin resolvers normally key on.
18
14
  class Identifier < Data.define(:name)
19
15
  def initialize(name:)
20
16
  unless name.is_a?(String) && !name.empty?
@@ -23,14 +19,11 @@ module Rigor
23
19
  "got #{name.inspect}"
24
20
  end
25
21
 
26
- # Freeze the String field so the resulting Data object
27
- # is `Ractor.shareable?` regardless of whether the
28
- # caller passed a `# frozen_string_literal: true`
29
- # constant or a dynamically built String. The same
30
- # discipline applies to every other TypeNode value
31
- # object — they live in the parser's hot path and are
32
- # the natural carriers to flow through future Ractor
33
- # boundaries (see CURRENT_WORK Open Items #8).
22
+ # Freeze the String field so the resulting Data object is `Ractor.shareable?` regardless of
23
+ # whether the caller passed a `# frozen_string_literal: true` constant or a dynamically built
24
+ # String. The same discipline applies to every other TypeNode value object — they live in the
25
+ # parser's hot path and are the natural carriers to flow through future Ractor boundaries
26
+ # (see CURRENT_WORK Open Items #8).
34
27
  super(name: name.frozen? ? name : name.dup.freeze)
35
28
  end
36
29
  end
@@ -2,18 +2,9 @@
2
2
 
3
3
  module Rigor
4
4
  # Mini-AST passed to plugin-supplied `TypeNode` resolvers (see
5
- # [ADR-13](../../docs/adr/13-typenode-resolver-plugin.md)). The
6
- # two leaf carriers {Identifier} and {Generic} describe a
7
- # node in an `%a{rigor:v1:...}` payload at the point the
8
- # built-in registry / `ImportedRefinements::Parser` failed to
9
- # resolve it and the analyzer hands the node off to plugins.
10
- #
11
- # Slice 1 of the ADR-13 envelope ships these two value objects
12
- # only. The `NameScope` companion + plugin manifest hook arrive
13
- # in slice 2; the parser integration arrives in slice 3. Until
14
- # those land, the carriers are reachable only through direct
15
- # instantiation in tests — they are the stable data shape every
16
- # later slice consumes.
5
+ # [ADR-13](../../docs/adr/13-typenode-resolver-plugin.md)). The two leaf carriers — {Identifier} and
6
+ # {Generic} describe a node in an `%a{rigor:v1:...}` payload at the point the built-in registry /
7
+ # `ImportedRefinements::Parser` failed to resolve it and the analyzer hands the node off to plugins.
17
8
  module TypeNode
18
9
  end
19
10
  end
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- # Generates the value-object identity trio (`==` / `eql?` / `hash`)
5
- # from a fixed field list. Every `Rigor::Type` carrier and the cache
6
- # descriptor entries are immutable value objects that each hand-wrote
7
- # the same three methods; forgetting a field in one of them (or letting
8
- # `==` and `hash` disagree) silently corrupts `Set` / `Hash`-key
9
- # behaviour — a carrier vanishes after its first insertion, or two
10
- # distinct types compare equal.
4
+ # Generates the value-object identity trio (`==` / `eql?` / `hash`) from a fixed field
5
+ # list. Every `Rigor::Type` carrier and the cache descriptor entries are immutable value
6
+ # objects that each hand-wrote the same three methods; forgetting a field in one of them
7
+ # (or letting `==` and `hash` disagree) silently corrupts `Set` / `Hash`-key behaviour — a
8
+ # carrier vanishes after its first insertion, or two distinct types compare equal.
11
9
  #
12
- # The methods are CODE-GENERATED at class-definition time, not driven
13
- # by per-call reflection, so they are as fast as the hand-written
14
- # versions they replace — `hash` and `==` run on the inference hot path
15
- # (type dedup, fact-store keys, memo caches), where a `send`-per-field
16
- # would be a real regression.
10
+ # The methods are CODE-GENERATED at class-definition time, not driven by per-call
11
+ # reflection, so they are as fast as the hand-written versions they replace — `hash` and
12
+ # `==` run on the inference hot path (type dedup, fact-store keys, memo caches), where a
13
+ # `send`-per-field would be a real regression.
17
14
  #
18
15
  # Usage:
19
16
  #
@@ -32,11 +29,10 @@ module Rigor
32
29
  # [self.class, class_name, type_args].hash
33
30
  # end
34
31
  #
35
- # Value objects whose equality is NOT a plain field-wise comparison
36
- # keep their hand-written `==` / `hash` and do not call `value_fields`
37
- # e.g. `Type::Constant` also distinguishes `value.class` (so
38
- # `Constant[1] != Constant[1.0]`), and `Type::Top` / `Type::Bot` are
39
- # field-free singletons.
32
+ # Value objects whose equality is NOT a plain field-wise comparison keep their
33
+ # hand-written `==` / `hash` and do not call `value_fields` — e.g. `Type::Constant` also
34
+ # distinguishes `value.class` (so `Constant[1] != Constant[1.0]`), and `Type::Top` /
35
+ # `Type::Bot` are field-free singletons.
40
36
  module ValueSemantics
41
37
  def self.included(base)
42
38
  base.extend(ClassMethods)
@@ -49,10 +45,9 @@ module Rigor
49
45
  guard = comparisons.empty? ? "" : " && #{comparisons.join(' && ')}"
50
46
  key = (["self.class"] + fields.map(&:to_s)).join(", ")
51
47
 
52
- # Codegen (not per-call reflection) so the generated `==` / `hash`
53
- # read fields directly and run at hand-written speed on the
54
- # inference hot path. For `value_fields :class_name, :type_args`
55
- # the emitted source is:
48
+ # Codegen (not per-call reflection) so the generated `==` / `hash` read fields
49
+ # directly and run at hand-written speed on the inference hot path. For
50
+ # `value_fields :class_name, :type_args` the emitted source is:
56
51
  #
57
52
  # def ==(other)
58
53
  # other.is_a?(self.class) && class_name == other.class_name && type_args == other.type_args
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.2.7"
4
+ VERSION = "0.2.8"
5
5
  end
@@ -6,43 +6,34 @@ require "prism"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class Actioncable < Rigor::Plugin::Base
9
- # Walks a parsed file's AST looking for ActionCable
10
- # entry-point calls and validates each against the
11
- # {ChannelIndex}.
9
+ # Walks a parsed file's AST looking for ActionCable entry-point calls and validates each against
10
+ # the {ChannelIndex}.
12
11
  #
13
12
  # Recognised shapes:
14
13
  #
15
- # - `<ChannelClass>.broadcast_to(record, data)` —
16
- # class-targeted broadcast. The class must exist in
17
- # the index.
18
- # - `ActionCable.server.broadcast(stream_name, data)`
19
- # string-targeted broadcast. When `stream_name`
20
- # is a literal string and the index has at least
21
- # one channel with no dynamic stream registrations,
22
- # we check that the name appears in
23
- # `index.all_stream_names`. Otherwise the
24
- # `unknown-stream` warning is suppressed (we can't
25
- # prove absence).
14
+ # - `<ChannelClass>.broadcast_to(record, data)` — class-targeted broadcast. The class must exist
15
+ # in the index.
16
+ # - `ActionCable.server.broadcast(stream_name, data)` — string-targeted broadcast. When
17
+ # `stream_name` is a literal string and the index has at least one channel with no dynamic
18
+ # stream registrations, we check that the name appears in `index.all_stream_names`. Otherwise
19
+ # the `unknown-stream` warning is suppressed (we can't prove absence).
26
20
  module Analyzer
27
- # `ActionCable.server.broadcast(...)` — the receiver
28
- # path we recognise as a server-targeted broadcast.
29
- # Single-symbol form (just `broadcast`) is too
30
- # ambiguous to validate.
21
+ # `ActionCable.server.broadcast(...)` — the receiver path we recognise as a server-targeted
22
+ # broadcast. Single-symbol form (just `broadcast`) is too ambiguous to validate.
31
23
  SERVER_BROADCAST_RECEIVER_NAMES = %w[
32
24
  ActionCable.server
33
25
  ::ActionCable.server
34
26
  ].freeze
35
27
 
36
- # One broadcast observation. Carries no path/location — the
37
- # caller (the `node_rule` block) positions it via
38
- # `Plugin::Base#diagnostic`.
28
+ # One broadcast observation. Carries no path/location — the caller (the `node_rule` block)
29
+ # positions it via `Plugin::Base#diagnostic`.
39
30
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
40
31
 
41
32
  module_function
42
33
 
43
- # The broadcast violations for a single call node, or `[]` when
44
- # the node is not a `broadcast_to` / `ActionCable.server.broadcast`
45
- # call this plugin recognises. ADR-37: the engine owns the walk.
34
+ # The broadcast violations for a single call node, or `[]` when the node is not a
35
+ # `broadcast_to` / `ActionCable.server.broadcast` call this plugin recognises. ADR-37: the
36
+ # engine owns the walk.
46
37
  #
47
38
  # @param call_node [Prism::Node]
48
39
  # @param channel_index [ChannelIndex]
@@ -61,10 +52,8 @@ module Rigor
61
52
  class_name = constant_receiver_name(call_node.receiver)
62
53
  return [] if class_name.nil?
63
54
 
64
- # broadcast_to with a class-name receiver that
65
- # doesn't end in "Channel" is almost certainly
66
- # not ActionCable — pass through silently to
67
- # avoid flagging unrelated `broadcast_to` methods.
55
+ # broadcast_to with a class-name receiver that doesn't end in "Channel" is almost certainly
56
+ # not ActionCable pass through silently to avoid flagging unrelated `broadcast_to` methods.
68
57
  return [] unless class_name.end_with?("Channel")
69
58
 
70
59
  entry = channel_index.find(class_name) || channel_index.find("::#{class_name}")
@@ -127,8 +116,7 @@ module Rigor
127
116
  )
128
117
  end
129
118
 
130
- # Renders an `A.b.c` chain as a string (used to
131
- # detect `ActionCable.server`). Returns nil for
119
+ # Renders an `A.b.c` chain as a string (used to detect `ActionCable.server`). Returns nil for
132
120
  # non-chained nodes.
133
121
  def call_chain_string(node)
134
122
  parts = []
@@ -7,36 +7,27 @@ require_relative "channel_index"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class Actioncable < Rigor::Plugin::Base
10
- # Walks the configured channel-search paths via the
11
- # plugin's `IoBoundary`, parses each `.rb` file with
12
- # Prism, and collects classes whose immediate
13
- # superclass is one of the configured base classes.
10
+ # Walks the configured channel-search paths via the plugin's `IoBoundary`, parses each `.rb` file
11
+ # with Prism, and collects classes whose immediate superclass is one of the configured base
12
+ # classes.
14
13
  #
15
14
  # For each discovered channel, the discoverer:
16
15
  #
17
- # - Records every public instance-side `def` whose
18
- # name isn't an ActionCable framework hook
19
- # (`subscribed`, `unsubscribed`, `_`-prefixed).
20
- # These are the action methods clients can invoke
16
+ # - Records every public instance-side `def` whose name isn't an ActionCable framework hook
17
+ # (`subscribed`, `unsubscribed`, `_`-prefixed). These are the action methods clients can invoke
21
18
  # via `subscription.perform("action_name", data)`.
22
- # - Records every literal-string `stream_from "name"`
23
- # call as a registered stream name.
24
- # - Sets `dynamic_streams: true` when the channel has
25
- # ANY non-literal `stream_from` argument (or a
26
- # `stream_for` call) so the analyzer knows it can't
27
- # be sure of every stream name.
19
+ # - Records every literal-string `stream_from "name"` call as a registered stream name.
20
+ # - Sets `dynamic_streams: true` when the channel has ANY non-literal `stream_from` argument (or a
21
+ # `stream_for` call) so the analyzer knows it can't be sure of every stream name.
28
22
  #
29
23
  # Intentional limitations:
30
24
  #
31
25
  # - Direct-superclass match only.
32
- # - Public-vs-private is not tracked; the framework
33
- # hooks (`subscribed`/`unsubscribed`) are excluded
34
- # by name. Methods marked `private` after a
35
- # `private` keyword would still appear in the
26
+ # - Public-vs-private is not tracked; the framework hooks (`subscribed`/`unsubscribed`) are
27
+ # excluded by name. Methods marked `private` after a `private` keyword would still appear in the
36
28
  # `action_methods` set.
37
- # - `stream_for(record)` (model-scoped streams) is
38
- # recognised as setting `dynamic_streams: true` but
39
- # not introspected further.
29
+ # - `stream_for(record)` (model-scoped streams) is recognised as setting `dynamic_streams: true`
30
+ # but not introspected further.
40
31
  class ChannelDiscoverer
41
32
  FRAMEWORK_HOOKS = %i[subscribed unsubscribed].to_set.freeze
42
33
 
@@ -126,12 +117,9 @@ module Rigor
126
117
  )
127
118
  end
128
119
 
129
- # Walks the channel body recursively (so
130
- # `stream_from` / `stream_for` calls inside
131
- # `subscribed` / helper methods are picked up).
132
- # Returns `[Array<String>, bool]` — the literal
133
- # stream names + whether any dynamic registration
134
- # was seen.
120
+ # Walks the channel body recursively (so `stream_from` / `stream_for` calls inside `subscribed`
121
+ # / helper methods are picked up). Returns `[Array<String>, bool]` the literal stream names +
122
+ # whether any dynamic registration was seen.
135
123
  def collect_stream_registrations(node, names: [], dynamic: false)
136
124
  return [names, dynamic] if node.nil?
137
125
 
@@ -145,8 +133,7 @@ module Rigor
145
133
  dynamic = true
146
134
  end
147
135
  when :stream_for
148
- # Model-scoped stream — name is computed from
149
- # the record at runtime; treat as dynamic.
136
+ # Model-scoped stream — name is computed from the record at runtime; treat as dynamic.
150
137
  dynamic = true
151
138
  end
152
139
  end
@@ -3,23 +3,16 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Actioncable < Rigor::Plugin::Base
6
- # Frozen catalogue of discovered ActionCable channel
7
- # classes keyed by qualified class name. Each entry
8
- # holds:
6
+ # Frozen catalogue of discovered ActionCable channel classes keyed by qualified class name. Each
7
+ # entry holds:
9
8
  #
10
- # - `action_methods` — the set of public instance
11
- # methods that aren't ActionCable framework hooks
12
- # (`subscribed` / `unsubscribed`). These are the
13
- # methods clients invoke via
9
+ # - `action_methods` — the set of public instance methods that aren't ActionCable framework hooks
10
+ # (`subscribed` / `unsubscribed`). These are the methods clients invoke via
14
11
  # `subscription.perform("action_name", data)`.
15
- # - `stream_names` — the set of literal-string stream
16
- # names registered via `stream_from "name"` calls
17
- # inside the channel body. Dynamic registrations
18
- # (`stream_from interpolated_string`) are recorded
19
- # separately as `dynamic_streams: true` so the
20
- # analyzer can suppress unknown-stream warnings on
21
- # any channel that has at least one dynamic
22
- # registration.
12
+ # - `stream_names` — the set of literal-string stream names registered via `stream_from "name"`
13
+ # calls inside the channel body. Dynamic registrations (`stream_from interpolated_string`) are
14
+ # recorded separately as `dynamic_streams: true` so the analyzer can suppress unknown-stream
15
+ # warnings on any channel that has at least one dynamic registration.
23
16
  class ChannelIndex
24
17
  Entry = Data.define(:class_name, :file_path, :action_methods, :stream_names, :dynamic_streams) do
25
18
  def includes_action?(name)
@@ -60,18 +53,14 @@ module Rigor
60
53
  @by_name.keys
61
54
  end
62
55
 
63
- # All literal stream names registered across every
64
- # discovered channel.
56
+ # All literal stream names registered across every discovered channel.
65
57
  def all_stream_names
66
58
  @entries.flat_map { |e| e.stream_names.to_a }.to_set
67
59
  end
68
60
 
69
- # True when at least one discovered channel uses a
70
- # dynamic stream registration. The analyzer treats
71
- # this as "we can't be sure any literal name is
72
- # missing" and skips the `unknown-stream` warning
73
- # entirely — absence of a literal match doesn't prove
74
- # the name is wrong.
61
+ # True when at least one discovered channel uses a dynamic stream registration. The analyzer
62
+ # treats this as "we can't be sure any literal name is missing" and skips the `unknown-stream`
63
+ # warning entirely absence of a literal match doesn't prove the name is wrong.
75
64
  def any_dynamic_streams?
76
65
  @entries.any?(&:dynamic_streams)
77
66
  end
@@ -8,14 +8,11 @@ require_relative "actioncable/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-actioncable — validates ActionCable
12
- # `<Channel>.broadcast_to(...)` and
13
- # `ActionCable.server.broadcast(stream_name, ...)`
14
- # call sites against the discovered channel index.
11
+ # rigor-actioncable — validates ActionCable `<Channel>.broadcast_to(...)` and
12
+ # `ActionCable.server.broadcast(stream_name, ...)` call sites against the discovered channel index.
15
13
  #
16
14
  # Tier 3F of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
17
- # Statically discovers channel classes by walking
18
- # `channel_search_paths` and parsing each file with
15
+ # Statically discovers channel classes by walking `channel_search_paths` and parsing each file with
19
16
  # Prism — no `actioncable` runtime dependency.
20
17
  #
21
18
  # ## Configuration
@@ -28,33 +25,22 @@ module Rigor
28
25
  #
29
26
  # ## What it checks
30
27
  #
31
- # 1. **Channel class existence** — `<X>.broadcast_to(...)`
32
- # where `X` ends in `Channel` must resolve to a
33
- # discovered channel.
34
- # 2. **Stream-name registration**
35
- # `ActionCable.server.broadcast("stream_name", ...)`
36
- # with a literal stream name is checked against
37
- # every discovered channel's `stream_from "..."`
38
- # registrations. The check is suppressed when ANY
39
- # discovered channel uses a dynamic registration
40
- # (`stream_from interpolated_string` or
41
- # `stream_for record`) — the absence of a literal
42
- # match doesn't prove absence.
28
+ # 1. **Channel class existence** — `<X>.broadcast_to(...)` where `X` ends in `Channel` must resolve
29
+ # to a discovered channel.
30
+ # 2. **Stream-name registration** — `ActionCable.server.broadcast("stream_name", ...)` with a
31
+ # literal stream name is checked against every discovered channel's `stream_from "..."`
32
+ # registrations. The check is suppressed when ANY discovered channel uses a dynamic registration
33
+ # (`stream_from interpolated_string` or `stream_for record`) the absence of a literal match
34
+ # doesn't prove absence.
43
35
  #
44
36
  # ## Limitations
45
37
  #
46
- # - **Direct-superclass match only.** Indirect
47
- # inheritance (`AdminChannel < BaseChannel <
48
- # ApplicationCable::Channel`) needs `BaseChannel`
49
- # listed in `channel_base_classes`.
50
- # - **Action method invocations are not validated.**
51
- # ActionCable actions are invoked from JS via
52
- # `subscription.perform("action_name", data)`; we
53
- # don't analyse JS so the action-method index is
54
- # informational only (deferred: cross-plugin handoff
55
- # to a JS-side analyzer).
56
- # - **`broadcast_to` arity isn't checked.** The method
57
- # takes any record + any data hash; there's no
38
+ # - **Direct-superclass match only.** Indirect inheritance (`AdminChannel < BaseChannel <
39
+ # ApplicationCable::Channel`) needs `BaseChannel` listed in `channel_base_classes`.
40
+ # - **Action method invocations are not validated.** ActionCable actions are invoked from JS via
41
+ # `subscription.perform("action_name", data)`; we don't analyse JS so the action-method index is
42
+ # informational only (deferred: cross-plugin handoff to a JS-side analyzer).
43
+ # - **`broadcast_to` arity isn't checked.** The method takes any record + any data hash; there's no
58
44
  # useful arity envelope.
59
45
  class Actioncable < Rigor::Plugin::Base
60
46
  manifest(
@@ -70,11 +56,9 @@ module Rigor
70
56
  }
71
57
  )
72
58
 
73
- # `watch:` covers every `.rb` file under the channel search paths
74
- # so the cache invalidates when channels are added, removed, or
75
- # edited (ADR-60 WD3). The dependency descriptor is recorded after
76
- # the discoverer runs, so the `io_boundary` reads inside it are
77
- # captured too.
59
+ # `watch:` covers every `.rb` file under the channel search paths so the cache invalidates when
60
+ # channels are added, removed, or edited (ADR-60 WD3). The dependency descriptor is recorded after
61
+ # the discoverer runs, so the `io_boundary` reads inside it are captured too.
78
62
  producer :channel_index, watch: -> { [[@channel_search_paths, "**/*.rb"]] } do |_params|
79
63
  ChannelDiscoverer.new(
80
64
  io_boundary: io_boundary,
@@ -88,10 +72,9 @@ module Rigor
88
72
  @channel_base_classes = Array(config.fetch("channel_base_classes")).map(&:to_s)
89
73
  end
90
74
 
91
- # File-level only: the load-error emission. Per-call broadcast
92
- # validation runs over the engine-owned walk via the node_rule
93
- # below (ADR-37). The channel index is lazily loaded + memoised by
94
- # `producer_value`, shared by both surfaces.
75
+ # File-level only: the load-error emission. Per-call broadcast validation runs over the
76
+ # engine-owned walk via the node_rule below (ADR-37). The channel index is lazily loaded +
77
+ # memoised by `producer_value`, shared by both surfaces.
95
78
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
96
79
  index = producer_value(:channel_index)
97
80
  return [load_error_diagnostic(path)] if index.nil? && producer_error(:channel_index)
@@ -5,36 +5,25 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class Actionmailer < Rigor::Plugin::Base
8
- # Walks a parsed file's AST looking for
9
- # `<MailerClass>.<action>(...)` calls and validates
10
- # each against the {MailerIndex}. Recognises both:
8
+ # Walks a parsed file's AST looking for `<MailerClass>.<action>(...)` calls and validates each
9
+ # against the {MailerIndex}. Recognises both:
11
10
  #
12
- # - `UserMailer.welcome(user)` — direct action call
13
- # (the call returns a `Mail::Message` ready for
11
+ # - `UserMailer.welcome(user)` — direct action call (the call returns a `Mail::Message` ready for
14
12
  # `.deliver_now` / `.deliver_later`).
15
- # - `UserMailer.with(user: u).welcome` — parametrized
16
- # action call. The `.with(...)` call is treated as a
17
- # pass-through; the action's argument shape is
18
- # validated on the trailing `.welcome` invocation
19
- # even though the receiver is a method-call chain
20
- # rather than a constant.
13
+ # - `UserMailer.with(user: u).welcome` — parametrized action call. The `.with(...)` call is
14
+ # treated as a pass-through; the action's argument shape is validated on the trailing `.welcome`
15
+ # invocation even though the receiver is a method-call chain rather than a constant.
21
16
  #
22
- # The analyzer is purely syntactic: it does not look
23
- # at runtime mailer state. Constants that don't appear
24
- # in the index are silently ignored — the rule has no
25
- # opinion on non-mailer call shapes.
17
+ # The analyzer is purely syntactic: it does not look at runtime mailer state. Constants that don't
18
+ # appear in the index are silently ignored — the rule has no opinion on non-mailer call shapes.
26
19
  module Analyzer
27
- # `.with(...)` is recognised as a forwarding step:
28
- # the receiver of `.with(...)` is the mailer class,
29
- # so the trailing action-method call's class context
30
- # is the same.
20
+ # `.with(...)` is recognised as a forwarding step: the receiver of `.with(...)` is the mailer
21
+ # class, so the trailing action-method call's class context is the same.
31
22
  WITH_METHODS = %i[with].freeze
32
23
 
33
- # Ruby method names that ActionMailer reserves on the
34
- # class itself. We don't validate against these as
35
- # actions even if a mailer happens to override them
36
- # — the user almost certainly meant the framework
37
- # method, not their own action.
24
+ # Ruby method names that ActionMailer reserves on the class itself. We don't validate against
25
+ # these as actions even if a mailer happens to override them — the user almost certainly meant
26
+ # the framework method, not their own action.
38
27
  RESERVED_CLASS_METHODS = %i[
39
28
  new allocate name superclass class
40
29
  deliver_later deliver_now deliver_later! deliver_now!
@@ -45,16 +34,14 @@ module Rigor
45
34
  method instance_method methods
46
35
  ].freeze
47
36
 
48
- # One mailer-call observation. Carries no path/location — the
49
- # caller (the `node_rule` block) positions it via
50
- # `Plugin::Base#diagnostic`.
37
+ # One mailer-call observation. Carries no path/location — the caller (the `node_rule` block)
38
+ # positions it via `Plugin::Base#diagnostic`.
51
39
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
52
40
 
53
41
  module_function
54
42
 
55
- # The mailer-call violations for a single call node (0..2), or
56
- # `[]` when it is not a `<Mailer>.action(...)` call on a known
57
- # mailer. ADR-37: the engine owns the walk.
43
+ # The mailer-call violations for a single call node (0..2), or `[]` when it is not a
44
+ # `<Mailer>.action(...)` call on a known mailer. ADR-37: the engine owns the walk.
58
45
  #
59
46
  # @param call_node [Prism::Node]
60
47
  # @param mailer_index [MailerIndex]
@@ -71,9 +58,8 @@ module Rigor
71
58
 
72
59
  action_entry = class_entry.find_action(call_node.name)
73
60
  if action_entry.nil?
74
- # Skip `unknown-action` when the mailer's include set has any
75
- # unresolved module it may legitimately define the action
76
- # (gem-shipped concern, dynamically loaded extension).
61
+ # Skip `unknown-action` when the mailer's include set has any unresolved module — it may
62
+ # legitimately define the action (gem-shipped concern, dynamically loaded extension).
77
63
  return [] if class_entry.unresolved_includes?
78
64
 
79
65
  return [unknown_action_violation(call_node, class_entry)]
@@ -86,19 +72,16 @@ module Rigor
86
72
  end
87
73
 
88
74
  def action_call_candidate?(node)
89
- # Skip anything that doesn't look like a mailer
90
- # action call: no receiver, or a non-constant /
75
+ # Skip anything that doesn't look like a mailer action call: no receiver, or a non-constant /
91
76
  # non-`.with(...)` receiver.
92
77
  return false if node.receiver.nil?
93
78
 
94
79
  mailer_class_for_call(node) ? true : false
95
80
  end
96
81
 
97
- # Extracts the mailer class name when the call's
98
- # receiver is either:
82
+ # Extracts the mailer class name when the call's receiver is either:
99
83
  # - A constant (`UserMailer.welcome(...)`), or
100
- # - A `.with(...)` call whose receiver is a constant
101
- # (`UserMailer.with(user: u).welcome`).
84
+ # - A `.with(...)` call whose receiver is a constant (`UserMailer.with(user: u).welcome`).
102
85
  def mailer_class_for_call(node)
103
86
  receiver = node.receiver
104
87
  case receiver
@@ -125,13 +108,10 @@ module Rigor
125
108
  actual = args.size
126
109
  return nil if action_entry.accepts?(actual)
127
110
 
128
- # Trailing keyword-hash relaxation. `Notify.foo(uid,
129
- # gid, success_count: 5)` is 3 positional args from
130
- # Prism's perspective (2 + a KeywordHashNode); the
131
- # action's `def foo(uid, gid, success_count:)` has
132
- # arity 2. When the call's trailing arg is a kwargs
133
- # hash, allow `(actual - 1) ≤ max_arity` so kwargs-
134
- # carrying calls don't surface as wrong-arity.
111
+ # Trailing keyword-hash relaxation. `Notify.foo(uid, gid, success_count: 5)` is 3 positional
112
+ # args from Prism's perspective (2 + a KeywordHashNode); the action's `def foo(uid, gid,
113
+ # success_count:)` has arity 2. When the call's trailing arg is a kwargs hash, allow `(actual
114
+ # - 1) ≤ max_arity` so kwargs-carrying calls don't surface as wrong-arity.
135
115
  return nil if args.last.is_a?(Prism::KeywordHashNode) && action_entry.accepts?(actual - 1)
136
116
 
137
117
  Violation.new(