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
@@ -3,13 +3,11 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Actionpack < Rigor::Plugin::Base
6
- # Per-run frozen index of discovered controllers AND
7
- # concerns (modules) in `controller_search_paths`. Phase 2
8
- # (filter-chain validation) consults the index at every
9
- # call site to check that `before_action :name` references
10
- # a method that actually exists on the controller, its
11
- # parent (one level of inheritance), or any module the
12
- # controller or its ancestor chain transitively `include`s.
6
+ # Per-run frozen index of discovered controllers AND concerns (modules) in
7
+ # `controller_search_paths`. Phase 2 (filter-chain validation) consults the index at every call
8
+ # site to check that `before_action :name` references a method that actually exists on the
9
+ # controller, its parent (one level of inheritance), or any module the controller or its ancestor
10
+ # chain transitively `include`s.
13
11
  #
14
12
  # Real-world example (Mastodon):
15
13
  # AccountsController < ApplicationController
@@ -19,23 +17,17 @@ module Rigor
19
17
  # SignatureVerification < module
20
18
  # def require_account_signature! # ← the filter target
21
19
  #
22
- # The include chain spans three concern modules.
23
- # `effective_methods_for("AccountsController")` walks all
24
- # of them transitively to collect `require_account_signature!`.
20
+ # The include chain spans three concern modules. `effective_methods_for("AccountsController")`
21
+ # walks all of them transitively to collect `require_account_signature!`.
25
22
  class ControllerIndex
26
- # `defined_methods` carries the discovered method names
27
- # (Symbols). `included_module_names` carries the constant
28
- # names passed to `include X` calls inside the
29
- # class / module body (Strings). `parent_class_name` is
30
- # the immediate superclass (nil for plain modules).
31
- # `enclosing_namespace` is the qualifier chain of the
32
- # declaration's *lexical* enclosing scope (e.g.
33
- # `["Admin"]` for an `Admin::Foo` declared via
34
- # `module Admin; class Foo; end; end`). Used by the
35
- # index's parent / module lookup to try lexically-scoped
36
- # constant resolution before falling through to the
37
- # top-level form — Ruby's constant lookup walks the
38
- # enclosing scope chain before `Object`.
23
+ # `defined_methods` carries the discovered method names (Symbols). `included_module_names`
24
+ # carries the constant names passed to `include X` calls inside the class / module body
25
+ # (Strings). `parent_class_name` is the immediate superclass (nil for plain modules).
26
+ # `enclosing_namespace` is the qualifier chain of the declaration's *lexical* enclosing scope
27
+ # (e.g. `["Admin"]` for an `Admin::Foo` declared via `module Admin; class Foo; end; end`). Used
28
+ # by the index's parent / module lookup to try lexically-scoped constant resolution before
29
+ # falling through to the top-level form — Ruby's constant lookup walks the enclosing scope chain
30
+ # before `Object`.
39
31
  Entry = Data.define(:class_name, :defined_methods, :parent_class_name, :included_module_names,
40
32
  :enclosing_namespace)
41
33
 
@@ -51,20 +43,15 @@ module Rigor
51
43
  @entries[class_name]
52
44
  end
53
45
 
54
- # Resolves the **effective** method set for a controller,
55
- # including methods contributed by every module the
56
- # controller / any ancestor transitively `include`s AND
57
- # methods inherited from the ancestor chain (unbounded
58
- # depth, cycle-safe via a visited set).
46
+ # Resolves the **effective** method set for a controller, including methods contributed by
47
+ # every module the controller / any ancestor transitively `include`s AND methods inherited from
48
+ # the ancestor chain (unbounded depth, cycle-safe via a visited set).
59
49
  #
60
- # Parent / module lookups are **lexically scoped** — a
61
- # bare `BaseController` reference inside `module Admin`
62
- # first tries `Admin::BaseController`, then falls
63
- # through to the top-level `BaseController`. Matches
64
- # Ruby's constant-resolution semantics. Without this an
65
- # `Admin::Foo < BaseController` declaration would
66
- # incorrectly walk the top-level `BaseController` even
67
- # when an `Admin::BaseController` exists.
50
+ # Parent / module lookups are **lexically scoped** — a bare `BaseController` reference inside
51
+ # `module Admin` first tries `Admin::BaseController`, then falls through to the top-level
52
+ # `BaseController`. Matches Ruby's constant-resolution semantics. Without this an `Admin::Foo <
53
+ # BaseController` declaration would incorrectly walk the top-level `BaseController` even when an
54
+ # `Admin::BaseController` exists.
68
55
  def effective_methods_for(class_name)
69
56
  seen_classes = {}
70
57
  seen_modules = {}
@@ -81,11 +68,9 @@ module Rigor
81
68
  collect_methods(resolved_include, seen_modules, methods)
82
69
  end
83
70
  next_parent = entry.parent_class_name
84
- # Same self-reference guard as `unresolved_include?`:
85
- # `class ActivityPub::ApplicationController <
86
- # ::ApplicationController` lexically resolves
87
- # `ApplicationController` to itself; fall back to
88
- # the unprefixed top-level name in that case.
71
+ # Same self-reference guard as `unresolved_include?`: `class ActivityPub::ApplicationController
72
+ # < ::ApplicationController` lexically resolves `ApplicationController` to itself; fall back
73
+ # to the unprefixed top-level name in that case.
89
74
  current = if next_parent
90
75
  resolved = resolve_constant_lexically(next_parent, entry.enclosing_namespace)
91
76
  resolved == current ? next_parent.sub(/\A::/, "") : resolved
@@ -94,30 +79,23 @@ module Rigor
94
79
  methods.uniq.freeze
95
80
  end
96
81
 
97
- # @return [Boolean] true when the class has at least one
98
- # include OR parent class we couldn't resolve in the
99
- # index (typically a gem-shipped concern such as Devise's
100
- # `Devise::Controllers::Helpers`, or a gem-shipped
101
- # parent controller such as `Devise::ConfirmationsController`
102
- # or `Doorkeeper::AuthorizedApplicationsController`).
103
- # Phase 2 uses this to downgrade `unknown-filter-method`
104
- # to silence — the unresolved module / parent may
105
- # legitimately contribute the filter (either directly,
106
- # or via its own ancestor chain which the static
107
- # analyzer cannot follow), and there's no way to verify.
82
+ # @return [Boolean] true when the class has at least one include OR parent class we couldn't
83
+ # resolve in the index (typically a gem-shipped concern such as Devise's
84
+ # `Devise::Controllers::Helpers`, or a gem-shipped parent controller such as
85
+ # `Devise::ConfirmationsController` or `Doorkeeper::AuthorizedApplicationsController`). Phase
86
+ # 2 uses this to downgrade `unknown-filter-method` to silence — the unresolved module / parent
87
+ # may legitimately contribute the filter (either directly, or via its own ancestor chain which
88
+ # the static analyzer cannot follow), and there's no way to verify.
108
89
  def unresolved_include?(class_name)
109
90
  entry = @entries[class_name]
110
91
  return false if entry.nil?
111
92
 
112
- # Walk the full ancestor chain. `Admin::Foo <
113
- # BaseController < ApplicationController` should report
114
- # an unresolved include if ANY of the three references
115
- # a gem-shipped concern OR a gem-shipped parent class
116
- # we cannot index. The first iteration's `current_entry`
117
- # is always resolved (caller verified `known?`); a nil
118
- # `current_entry` on a subsequent iteration means the
119
- # AST-side `< Parent` reached a gem-shipped class
120
- # whose ancestor methods are invisible to us.
93
+ # Walk the full ancestor chain. `Admin::Foo < BaseController < ApplicationController` should
94
+ # report an unresolved include if ANY of the three references a gem-shipped concern OR a
95
+ # gem-shipped parent class we cannot index. The first iteration's `current_entry` is always
96
+ # resolved (caller verified `known?`); a nil `current_entry` on a subsequent iteration means
97
+ # the AST-side `< Parent` reached a gem-shipped class whose ancestor methods are invisible to
98
+ # us.
121
99
  seen_classes = {}
122
100
  current = class_name
123
101
  first = true
@@ -136,15 +114,11 @@ module Rigor
136
114
  return true if resolved.nil? || !@entries.key?(resolved)
137
115
  end
138
116
  next_parent = current_entry.parent_class_name
139
- # Avoid lexically resolving to ourselves. `class
140
- # ActivityPub::ApplicationController < ::ApplicationController`
141
- # would otherwise resolve `ApplicationController` (in
142
- # the lexical scope `["ActivityPub"]`) to
143
- # `ActivityPub::ApplicationController` and short-
144
- # circuit the walk before reaching the top-level
145
- # parent. When the lexical match is the current
146
- # class itself, fall back to the unprefixed
147
- # top-level name.
117
+ # Avoid lexically resolving to ourselves. `class ActivityPub::ApplicationController <
118
+ # ::ApplicationController` would otherwise resolve `ApplicationController` (in the lexical
119
+ # scope `["ActivityPub"]`) to `ActivityPub::ApplicationController` and short-circuit the walk
120
+ # before reaching the top-level parent. When the lexical match is the current class itself,
121
+ # fall back to the unprefixed top-level name.
148
122
  current = if next_parent
149
123
  resolved = resolve_constant_lexically(next_parent, current_entry.enclosing_namespace)
150
124
  resolved == current ? next_parent.sub(/\A::/, "") : resolved
@@ -181,37 +155,28 @@ module Rigor
181
155
  end
182
156
  end
183
157
 
184
- # Ruby's constant-lookup walk: a bare constant name
185
- # inside `module Admin` first tries `Admin::Const`,
186
- # then walks outward, and finally falls through to
187
- # top-level `Const`. We approximate that by trying
188
- # the candidate `enclosing + name` chains from
189
- # deepest to shallowest, and returning the first
190
- # candidate that has an entry in the index. When
191
- # nothing resolves, return the original name
192
- # unchanged — the caller treats unresolved entries as
193
- # "gem-shipped concerns we cannot see" (the
194
- # `unresolved_include?` predicate).
158
+ # Ruby's constant-lookup walk: a bare constant name inside `module Admin` first tries
159
+ # `Admin::Const`, then walks outward, and finally falls through to top-level `Const`. We
160
+ # approximate that by trying the candidate `enclosing + name` chains from deepest to shallowest,
161
+ # and returning the first candidate that has an entry in the index. When nothing resolves,
162
+ # return the original name unchanged the caller treats unresolved entries as "gem-shipped
163
+ # concerns we cannot see" (the `unresolved_include?` predicate).
195
164
  #
196
- # `name` may already be qualified (`Foo::Bar`); we
197
- # only try lexical prefixing when the unqualified
198
- # first segment doesn't match a top-level entry.
165
+ # `name` may already be qualified (`Foo::Bar`); we only try lexical prefixing when the
166
+ # unqualified first segment doesn't match a top-level entry.
199
167
  def resolve_constant_lexically(name, enclosing)
200
168
  return nil if name.nil?
201
169
 
202
- # A leading `::` denotes the top-level constant
203
- # explicitly (`< ::ApplicationController`). Strip it
204
- # for index lookup the discoverer registers entries
205
- # under their unprefixed name. Without this strip a
206
- # `class ActivityPub::ApplicationController <
207
- # ::ApplicationController` parent never resolved.
170
+ # A leading `::` denotes the top-level constant explicitly (`< ::ApplicationController`).
171
+ # Strip it for index lookup — the discoverer registers entries under their unprefixed name.
172
+ # Without this strip a `class ActivityPub::ApplicationController < ::ApplicationController`
173
+ # parent never resolved.
208
174
  name = name.sub(/\A::/, "")
209
175
 
210
176
  # Constant already absolute or no enclosing scope.
211
177
  return name if enclosing.nil? || enclosing.empty?
212
178
 
213
- # Try the deepest enclosing scope first, walking
214
- # outward. `enclosing = ["A", "B"]` produces
179
+ # Try the deepest enclosing scope first, walking outward. `enclosing = ["A", "B"]` produces
215
180
  # candidates `["A::B::name", "A::name", "name"]`.
216
181
  enclosing.length.downto(0).each do |depth|
217
182
  prefix = enclosing[0, depth]
@@ -8,20 +8,15 @@ require_relative "actionpack/controller_index"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-actionpack — validates Action Pack DSL calls in
12
- # controller files.
11
+ # rigor-actionpack — validates Action Pack DSL calls in controller files.
13
12
  #
14
- # **Phase 4 of the Action Pack plugin family** (route-helper
15
- # consumption). Reads the `:helper_table` fact published by
16
- # `rigor-rails-routes` (ADR-9 cross-plugin API) and validates
17
- # every implicit-self `*_path` / `*_url` call inside files
18
- # under `controller_search_paths` (default `app/controllers`).
13
+ # **Phase 4 of the Action Pack plugin family** (route-helper consumption). Reads the `:helper_table`
14
+ # fact published by `rigor-rails-routes` (ADR-9 cross-plugin API) and validates every implicit-self
15
+ # `*_path` / `*_url` call inside files under `controller_search_paths` (default `app/controllers`).
19
16
  #
20
17
  # Tier 2 of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
21
- # Phase 1 (strong-parameters → AR column validation), Phase 2
22
- # (filter chains), and Phase 3 (render targets) ship as
23
- # separate slices; each phase composes additively under the
24
- # same plugin id.
18
+ # Phase 1 (strong-parameters → AR column validation), Phase 2 (filter chains), and Phase 3 (render
19
+ # targets) ship as separate slices; each phase composes additively under the same plugin id.
25
20
  #
26
21
  # ## Configuration
27
22
  #
@@ -36,46 +31,35 @@ module Rigor
36
31
  #
37
32
  # ## What it checks
38
33
  #
39
- # - **Helper existence** — every `*_path` / `*_url` call
40
- # inside a controller file is looked up in the helper
41
- # table. Missing entries emit `unknown-helper` with a
42
- # `DidYouMean` suggestion drawn from the table.
43
- # - **Helper arity** the call's positional-argument count
44
- # is matched against the helper's recorded arity (a
45
- # trailing `KeywordHashNode` like `users_path(format: :json)`
46
- # is excluded; same convention `rigor-rails-routes` uses).
47
- # Mismatches emit `wrong-helper-arity`.
48
- # - **Trace** — recognised helpers also emit a
49
- # `helper-call` info diagnostic naming the action and
34
+ # - **Helper existence** — every `*_path` / `*_url` call inside a controller file is looked up in
35
+ # the helper table. Missing entries emit `unknown-helper` with a `DidYouMean` suggestion drawn
36
+ # from the table.
37
+ # - **Helper arity** the call's positional-argument count is matched against the helper's
38
+ # recorded arity (a trailing `KeywordHashNode` like `users_path(format: :json)` is excluded; same
39
+ # convention `rigor-rails-routes` uses). Mismatches emit `wrong-helper-arity`.
40
+ # - **Trace** — recognised helpers also emit a `helper-call` info diagnostic naming the action and
50
41
  # path, mirroring the trace shape of the upstream plugin.
51
42
  #
52
43
  # ## Limitations
53
44
  #
54
- # - Implicit-self calls only. `Rails.application.routes.url_helpers.users_path`
55
- # and other explicit-receiver shapes are passed through;
56
- # they're rare in controller code and the helper table
57
- # doesn't include any extra context to validate them.
58
- # - Files outside `controller_search_paths` are skipped.
59
- # The plugin doesn't try to detect "is this a controller?"
60
- # by class hierarchy Phase 1's strong-parameters work
61
- # needs that, so it lives there. Phase 4's job is the
62
- # single-purpose helper check.
63
- # - When `rigor-rails-routes` is not installed (or its
64
- # helper table is empty), Phase 4 silently degrades to a
65
- # no-op. No load-error diagnostic is emitted; the user
66
- # gets the "no checks happened" failure mode rather than
67
- # a wall of "is this configured right?" warnings.
45
+ # - Implicit-self calls only. `Rails.application.routes.url_helpers.users_path` and other
46
+ # explicit-receiver shapes are passed through; they're rare in controller code and the helper
47
+ # table doesn't include any extra context to validate them.
48
+ # - Files outside `controller_search_paths` are skipped. The plugin doesn't try to detect "is this
49
+ # a controller?" by class hierarchy — Phase 1's strong-parameters work needs that, so it lives
50
+ # there. Phase 4's job is the single-purpose helper check.
51
+ # - When `rigor-rails-routes` is not installed (or its helper table is empty), Phase 4 silently
52
+ # degrades to a no-op. No load-error diagnostic is emitted; the user gets the "no checks
53
+ # happened" failure mode rather than a wall of "is this configured right?" warnings.
68
54
  class Actionpack < Rigor::Plugin::Base
69
55
  manifest(
70
56
  id: "actionpack",
71
- # ADR-37: the four phases (helper / filter / render /
72
- # strong-params) run per-call over the engine-owned walk;
73
- # the enclosing controller is read from the node-rule
74
- # `NodeContext` ancestors. Nested-module qualification is
75
- # preserved `module Admin; class DomainBlocksController`
76
- # resolves as `Admin::DomainBlocksController` (matching the
77
- # `ControllerDiscoverer`), so render paths and filter-chain
78
- # validation on nested controllers are correct.
57
+ # ADR-37: the four phases (helper / filter / render / strong-params) run per-call over the
58
+ # engine-owned walk; the enclosing controller is read from the node-rule `NodeContext`
59
+ # ancestors. Nested-module qualification is preserved `module Admin; class
60
+ # DomainBlocksController` resolves as `Admin::DomainBlocksController` (matching the
61
+ # `ControllerDiscoverer`), so render paths and filter-chain validation on nested controllers are
62
+ # correct.
79
63
  version: "0.9.0",
80
64
  description: "Validates Action Pack route-helper calls and filter chains inside controllers, and types the request-context readers (`params` / `session` / `request`).",
81
65
  config_schema: {
@@ -88,12 +72,10 @@ module Rigor
88
72
  ]
89
73
  )
90
74
 
91
- # Phase 2 cached producer — the controller index built from
92
- # `controller_search_paths`. `watch:` (ADR-60 WD3) covers every
93
- # `.rb` file under those roots so the cache invalidates when a
94
- # controller is added, removed, or edited; the discoverer's
95
- # in-block `io_boundary` reads are captured into the dependency
96
- # descriptor too, so no explicit priming is needed.
75
+ # Phase 2 cached producer — the controller index built from `controller_search_paths`. `watch:`
76
+ # (ADR-60 WD3) covers every `.rb` file under those roots so the cache invalidates when a
77
+ # controller is added, removed, or edited; the discoverer's in-block `io_boundary` reads are
78
+ # captured into the dependency descriptor too, so no explicit priming is needed.
97
79
  producer :controller_index, watch: -> { [[@controller_search_paths, "**/*.rb"]] } do |_params|
98
80
  ControllerDiscoverer.new(
99
81
  io_boundary: io_boundary,
@@ -106,18 +88,15 @@ module Rigor
106
88
  @view_search_paths = Array(config.fetch("view_search_paths")).map(&:to_s)
107
89
  end
108
90
 
109
- # ADR-37 — the four Action Pack phases run per-call over the
110
- # engine-owned walk. Each rule gates on `controller_file?(path)` (the
111
- # plugin only validates files under `controller_search_paths`,
112
- # exactly as the former `diagnostics_for_file` top-level guard did),
113
- # then delegates to a per-node `Analyzer.*_violations_for` and
114
- # positions each location-free `Violation` with `Base#diagnostic`.
115
- # The filter / render phases read the enclosing controller from the
116
- # node-rule `NodeContext` ancestors (its fifth block argument).
117
-
118
- # Phase 4 — route-helper consumption. `:helper_table` is
119
- # rigor-rails-routes's published fact (ADR-9), read lazily via
120
- # `read_fact`.
91
+ # ADR-37 — the four Action Pack phases run per-call over the engine-owned walk. Each rule gates on
92
+ # `controller_file?(path)` (the plugin only validates files under `controller_search_paths`,
93
+ # exactly as the former `diagnostics_for_file` top-level guard did), then delegates to a per-node
94
+ # `Analyzer.*_violations_for` and positions each location-free `Violation` with `Base#diagnostic`.
95
+ # The filter / render phases read the enclosing controller from the node-rule `NodeContext`
96
+ # ancestors (its fifth block argument).
97
+
98
+ # Phase 4 — route-helper consumption. `:helper_table` is rigor-rails-routes's published fact
99
+ # (ADR-9), read lazily via `read_fact`.
121
100
  node_rule Prism::CallNode do |node, _scope, path|
122
101
  next [] unless controller_file?(path)
123
102
 
@@ -127,9 +106,8 @@ module Rigor
127
106
  diagnostics_for(Analyzer.helper_violations_for(call_node: node, helper_table: table), path: path, node: node)
128
107
  end
129
108
 
130
- # Phase 2 — filter-chain validation. Skips silently when the
131
- # controller index is absent or doesn't recognise the enclosing
132
- # class.
109
+ # Phase 2 — filter-chain validation. Skips silently when the controller index is absent or
110
+ # doesn't recognise the enclosing class.
133
111
  node_rule Prism::CallNode do |node, _scope, path, _fc, context|
134
112
  next [] unless controller_file?(path)
135
113
 
@@ -142,11 +120,9 @@ module Rigor
142
120
  )
143
121
  end
144
122
 
145
- # Phase 3 — render-target validation against the configured
146
- # `view_search_paths`. Recognised purely from the call site + the
147
- # enclosing controller name, so no per-controller pre-discovery is
148
- # needed; the controller index is consulted only to suppress
149
- # gem-shipped-view false positives.
123
+ # Phase 3 — render-target validation against the configured `view_search_paths`. Recognised purely
124
+ # from the call site + the enclosing controller name, so no per-controller pre-discovery is
125
+ # needed; the controller index is consulted only to suppress gem-shipped-view false positives.
150
126
  node_rule Prism::CallNode do |node, _scope, path, _fc, context|
151
127
  next [] unless controller_file?(path)
152
128
 
@@ -159,11 +135,9 @@ module Rigor
159
135
  )
160
136
  end
161
137
 
162
- # Phase 1 — strong-parameter validation. Reads the `:model_index`
163
- # fact from the cross-plugin fact store (published by
164
- # rigor-activerecord) and validates every
165
- # `params.require(:user).permit(:name, :email)` chain against the
166
- # User model's column list.
138
+ # Phase 1 — strong-parameter validation. Reads the `:model_index` fact from the cross-plugin fact
139
+ # store (published by rigor-activerecord) and validates every `params.require(:user).permit(:name,
140
+ # :email)` chain against the User model's column list.
167
141
  node_rule Prism::CallNode do |node, _scope, path|
168
142
  next [] unless controller_file?(path)
169
143
 
@@ -173,28 +147,21 @@ module Rigor
173
147
  diagnostics_for(Analyzer.permit_violations_for(call_node: node, model_index: index), path: path, node: node)
174
148
  end
175
149
 
176
- # Phase 5 (2026-07-04) — type the implicit-self request-context
177
- # readers (`params`, `session`, `request`, `flash`, `cookies`)
178
- # inside controllers. The
179
- # typing-obstacle probe
180
- # (docs/notes/20260704-rails-coverage-onboarding-carrier-trap.md,
181
- # obstacle O3) found `params` typing to `Dynamic[top]` the single
182
- # largest protection-coverage hole on real Rails apps: `params[:x]`
183
- # is the #1 dispatch cluster (redmine app+lib: `[]` 2378 sites) and
184
- # `session[:x] =` a large share of the `[]=` cluster, all
185
- # unprotected because the receiver is Dynamic.
150
+ # Phase 5 (2026-07-04) — type the implicit-self request-context readers (`params`, `session`,
151
+ # `request`, `flash`, `cookies`) inside controllers. The typing-obstacle probe
152
+ # (docs/notes/20260704-rails-coverage-onboarding-carrier-trap.md, obstacle O3) found `params`
153
+ # typing to `Dynamic[top]` the single largest protection-coverage hole on real Rails apps:
154
+ # `params[:x]` is the #1 dispatch cluster (redmine app+lib: `[]` 2378 sites) and `session[:x] =` a
155
+ # large share of the `[]=` cluster, all unprotected because the receiver is Dynamic.
186
156
  #
187
- # Each returns a bare nominal with NO bundled RBS on purpose. That
188
- # makes the reader a *concrete* receiver (so `coverage --protection`
189
- # counts the site as protected and the dispatch resolves against a
190
- # named class) while its method surface stays engine-lenient — Rigor
191
- # does not fire `undefined-method` on a class it has no RBS for, so
192
- # `params.require(...).permit(...)`, `session.delete(:x)`,
193
- # `request.xhr?`, and every other method on these stay FP-safe.
194
- # Shipping a partial RBS would re-introduce the carrier-additivity
195
- # trap (a declared class drops every member the RBS omits → false
196
- # `undefined-method`). ADR-5: this types the container, never the
197
- # caller's argument, so the values stay lenient.
157
+ # Each returns a bare nominal with NO bundled RBS on purpose. That makes the reader a *concrete*
158
+ # receiver (so `coverage --protection` counts the site as protected and the dispatch resolves
159
+ # against a named class) while its method surface stays engine-lenient — Rigor does not fire
160
+ # `undefined-method` on a class it has no RBS for, so `params.require(...).permit(...)`,
161
+ # `session.delete(:x)`, `request.xhr?`, and every other method on these stay FP-safe. Shipping a
162
+ # partial RBS would re-introduce the carrier-additivity trap (a declared class drops every member
163
+ # the RBS omits → false `undefined-method`). ADR-5: this types the container, never the caller's
164
+ # argument, so the values stay lenient.
198
165
  REQUEST_CONTEXT_READER_TYPES = {
199
166
  params: "ActionController::Parameters",
200
167
  session: "ActionDispatch::Request::Session",
@@ -217,12 +184,10 @@ module Rigor
217
184
 
218
185
  private
219
186
 
220
- # True when the current `self` is a controller — the enclosing
221
- # class is one the discoverer indexed, or its name follows the
222
- # Rails `*Controller` convention (covering controllers outside
223
- # `controller_search_paths`, e.g. one shipped by an engine).
224
- # Typing `params` is precision-additive, so the name-convention
225
- # fallback is FP-safe.
187
+ # True when the current `self` is a controller — the enclosing class is one the discoverer
188
+ # indexed, or its name follows the Rails `*Controller` convention (covering controllers outside
189
+ # `controller_search_paths`, e.g. one shipped by an engine). Typing `params` is precision-additive,
190
+ # so the name-convention fallback is FP-safe.
226
191
  def controller_scope?(scope)
227
192
  self_type = scope&.self_type
228
193
  return false unless self_type.respond_to?(:class_name)
@@ -238,12 +203,10 @@ module Rigor
238
203
 
239
204
  def controller_file?(path)
240
205
  @controller_search_paths.any? do |root|
241
- # The runner may pass `path` as either an absolute
242
- # path (when `paths:` was configured absolutely) or a
243
- # relative one (when configured relatively). The
244
- # `controller_search_paths` knob is always project-
245
- # root-relative. Match the configured root as a
246
- # /-bracketed substring so both shapes resolve.
206
+ # The runner may pass `path` as either an absolute path (when `paths:` was configured
207
+ # absolutely) or a relative one (when configured relatively). The `controller_search_paths`
208
+ # knob is always project-root-relative. Match the configured root as a /-bracketed substring
209
+ # so both shapes resolve.
247
210
  path.include?("/#{root}/") || path.start_with?("#{root}/") || path == root
248
211
  end
249
212
  end
@@ -5,34 +5,26 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class Activejob < Rigor::Plugin::Base
8
- # Walks a parsed file's AST looking for
9
- # `<JobClass>.perform_later(...)` /
10
- # `.perform_now(...)` / `.perform(...)` calls and
11
- # validates each against the {JobIndex}.
8
+ # Walks a parsed file's AST looking for `<JobClass>.perform_later(...)` / `.perform_now(...)` /
9
+ # `.perform(...)` calls and validates each against the {JobIndex}.
12
10
  #
13
- # The plugin recognises a call as job-shaped when the
14
- # receiver is a `ConstantReadNode` / `ConstantPathNode`
15
- # whose resolved name appears in the index, and the
16
- # method name is one of the three ActiveJob entry
17
- # points.
11
+ # The plugin recognises a call as job-shaped when the receiver is a `ConstantReadNode` /
12
+ # `ConstantPathNode` whose resolved name appears in the index, and the method name is one of the
13
+ # three ActiveJob entry points.
18
14
  module Analyzer
19
- # Methods that delegate to the job's `#perform`. All
20
- # three accept the same argument shape `perform_later`
21
- # is the most common (queues for later execution),
22
- # `perform_now` runs synchronously, and `perform` is
23
- # the bare execution path.
15
+ # Methods that delegate to the job's `#perform`. All three accept the same argument shape —
16
+ # `perform_later` is the most common (queues for later execution), `perform_now` runs
17
+ # synchronously, and `perform` is the bare execution path.
24
18
  ENTRY_METHODS = %i[perform_later perform_now perform].freeze
25
19
 
26
- # One job-call observation. Carries no path/location — the
27
- # caller (the `node_rule` block) positions it via
28
- # `Plugin::Base#diagnostic`.
20
+ # One job-call observation. Carries no path/location — the caller (the `node_rule` block)
21
+ # positions it via `Plugin::Base#diagnostic`.
29
22
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
30
23
 
31
24
  module_function
32
25
 
33
- # The job-call violations for a single call node (0..2), or `[]`
34
- # when the node is not a `<Job>.perform_*` entry call on a known
35
- # job. ADR-37: the engine owns the walk.
26
+ # The job-call violations for a single call node (0..2), or `[]` when the node is not a
27
+ # `<Job>.perform_*` entry call on a known job. ADR-37: the engine owns the walk.
36
28
  #
37
29
  # @param call_node [Prism::Node]
38
30
  # @param job_index [JobIndex]
@@ -76,8 +68,7 @@ module Rigor
76
68
  )
77
69
  end
78
70
 
79
- # Renders a constant-path receiver as a String.
80
- # Mirrors the helpers in rigor-activerecord /
71
+ # Renders a constant-path receiver as a String. Mirrors the helpers in rigor-activerecord /
81
72
  # rigor-rails-routes for parity.
82
73
  def constant_receiver_name(node)
83
74
  case node
@@ -7,25 +7,19 @@ require_relative "job_index"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class Activejob < Rigor::Plugin::Base
10
- # Walks the configured job-search paths via the plugin's
11
- # `IoBoundary`, parses each `.rb` file with Prism, and
12
- # collects classes whose immediate superclass is one of
13
- # the configured base classes. For each discovered class,
14
- # the discoverer also reads the `#perform` method's
15
- # parameter list and computes the arity envelope.
10
+ # Walks the configured job-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
11
+ # Prism, and collects classes whose immediate superclass is one of the configured base classes.
12
+ # For each discovered class, the discoverer also reads the `#perform` method's parameter list and
13
+ # computes the arity envelope.
16
14
  #
17
15
  # Limitations (intentional for v0.1.0):
18
16
  #
19
- # - Only direct-superclass matches. `class WelcomeJob <
20
- # BaseJob` where `BaseJob < ApplicationJob` is NOT
21
- # discovered. List `BaseJob` in `job_base_classes`
22
- # if needed.
23
- # - The qualified class name is the lexical path
24
- # (`Admin::WelcomeJob` for a class declared inside
17
+ # - Only direct-superclass matches. `class WelcomeJob < BaseJob` where `BaseJob < ApplicationJob`
18
+ # is NOT discovered. List `BaseJob` in `job_base_classes` if needed.
19
+ # - The qualified class name is the lexical path (`Admin::WelcomeJob` for a class declared inside
25
20
  # `module Admin`).
26
- # - The `#perform` arity is read from the syntactic
27
- # parameter list. Methods built via `define_method`
28
- # are out of scope.
21
+ # - The `#perform` arity is read from the syntactic parameter list. Methods built via
22
+ # `define_method` are out of scope.
29
23
  class JobDiscoverer
30
24
  def initialize(io_boundary:, search_paths:, base_classes:)
31
25
  @io_boundary = io_boundary
@@ -119,9 +113,8 @@ module Rigor
119
113
  end
120
114
  end
121
115
 
122
- # Returns the `def perform(...)` node from a class
123
- # body, or `nil` when the class doesn't override
124
- # `#perform`. Only matches instance-side `def perform`.
116
+ # Returns the `def perform(...)` node from a class body, or `nil` when the class doesn't
117
+ # override `#perform`. Only matches instance-side `def perform`.
125
118
  def lookup_perform_def(body)
126
119
  return nil if body.nil?
127
120
 
@@ -134,13 +127,10 @@ module Rigor
134
127
  nil
135
128
  end
136
129
 
137
- # Builds a JobIndex::Entry from the discovered class's
138
- # `#perform` def. When the class doesn't override
139
- # `#perform`, we record an "any-arity" entry Active
140
- # Job's default `#perform` is abstract; calling
141
- # `perform_later` on a job that didn't override it is
142
- # itself a bug, but it's the user's bug, not the
143
- # plugin's call to flag without runtime context.
130
+ # Builds a JobIndex::Entry from the discovered class's `#perform` def. When the class doesn't
131
+ # override `#perform`, we record an "any-arity" entry — Active Job's default `#perform` is
132
+ # abstract; calling `perform_later` on a job that didn't override it is itself a bug, but it's
133
+ # the user's bug, not the plugin's call to flag without runtime context.
144
134
  def build_entry(class_name, perform_def)
145
135
  if perform_def.nil?
146
136
  return JobIndex::Entry.new(