rigortype 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (433) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/core_overlay/csv.rbs +28 -0
  4. data/data/core_overlay/psych.rbs +22 -0
  5. data/docs/handbook/01-getting-started.md +9 -1
  6. data/docs/handbook/02-everyday-types.md +4 -1
  7. data/docs/handbook/08-understanding-errors.md +3 -3
  8. data/docs/manual/01-installation.md +1 -0
  9. data/docs/manual/02-cli-reference.md +16 -7
  10. data/docs/manual/04-diagnostics.md +7 -4
  11. data/docs/manual/06-baseline.md +5 -2
  12. data/docs/manual/07-plugins.md +1 -1
  13. data/docs/manual/14-rails-quickstart.md +4 -2
  14. data/docs/manual/15-type-protection-coverage.md +27 -4
  15. data/docs/manual/plugins/rigor-actionpack.md +1 -1
  16. data/docs/manual/plugins/rigor-activerecord.md +12 -5
  17. data/docs/manual/plugins/rigor-rails-routes.md +11 -0
  18. data/lib/rigor/analysis/baseline.rb +56 -96
  19. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  20. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  21. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  22. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  23. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  24. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  25. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  26. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  27. data/lib/rigor/analysis/check_rules.rb +174 -274
  28. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  29. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  30. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  31. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  32. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  33. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  34. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  35. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  36. data/lib/rigor/analysis/diagnostic.rb +48 -75
  37. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  38. data/lib/rigor/analysis/fact_store.rb +12 -22
  39. data/lib/rigor/analysis/incremental.rb +47 -63
  40. data/lib/rigor/analysis/incremental_session.rb +74 -100
  41. data/lib/rigor/analysis/project_scan.rb +12 -23
  42. data/lib/rigor/analysis/result.rb +3 -5
  43. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  44. data/lib/rigor/analysis/run_stats.rb +38 -61
  45. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  48. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  49. data/lib/rigor/analysis/runner.rb +220 -333
  50. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  51. data/lib/rigor/analysis/worker_session.rb +64 -102
  52. data/lib/rigor/ast/type_node.rb +6 -8
  53. data/lib/rigor/ast.rb +6 -10
  54. data/lib/rigor/bleeding_edge.rb +27 -35
  55. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  56. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  57. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  58. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  59. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  60. data/lib/rigor/cache/descriptor.rb +58 -94
  61. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  62. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  63. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  64. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  65. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  66. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  67. data/lib/rigor/cache/rbs_environment.rb +11 -19
  68. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  69. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  70. data/lib/rigor/cache/store.rb +248 -186
  71. data/lib/rigor/cli/annotate_command.rb +51 -88
  72. data/lib/rigor/cli/baseline_command.rb +10 -17
  73. data/lib/rigor/cli/check_command.rb +121 -199
  74. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  75. data/lib/rigor/cli/ci_detector.rb +9 -14
  76. data/lib/rigor/cli/command.rb +8 -14
  77. data/lib/rigor/cli/coverage_command.rb +62 -48
  78. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  79. data/lib/rigor/cli/coverage_report.rb +0 -2
  80. data/lib/rigor/cli/coverage_scan.rb +7 -11
  81. data/lib/rigor/cli/diagnostic_formats.rb +46 -62
  82. data/lib/rigor/cli/diff_command.rb +10 -20
  83. data/lib/rigor/cli/docs_command.rb +26 -40
  84. data/lib/rigor/cli/doctor_command.rb +8 -9
  85. data/lib/rigor/cli/explain_command.rb +4 -7
  86. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  87. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  88. data/lib/rigor/cli/lsp_command.rb +11 -19
  89. data/lib/rigor/cli/mcp_command.rb +4 -6
  90. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  91. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  92. data/lib/rigor/cli/options.rb +11 -19
  93. data/lib/rigor/cli/plugin_command.rb +18 -30
  94. data/lib/rigor/cli/plugins_command.rb +29 -51
  95. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  96. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  97. data/lib/rigor/cli/protection_fork_scan.rb +55 -0
  98. data/lib/rigor/cli/protection_renderer.rb +5 -7
  99. data/lib/rigor/cli/protection_report.rb +34 -18
  100. data/lib/rigor/cli/renderable.rb +4 -6
  101. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  102. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  103. data/lib/rigor/cli/skill_command.rb +30 -47
  104. data/lib/rigor/cli/skill_describe.rb +40 -64
  105. data/lib/rigor/cli/trace_command.rb +9 -16
  106. data/lib/rigor/cli/trace_renderer.rb +35 -51
  107. data/lib/rigor/cli/triage_command.rb +6 -10
  108. data/lib/rigor/cli/triage_renderer.rb +1 -2
  109. data/lib/rigor/cli/type_of_command.rb +14 -23
  110. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  111. data/lib/rigor/cli/type_scan_command.rb +6 -10
  112. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  113. data/lib/rigor/cli/type_scan_report.rb +2 -3
  114. data/lib/rigor/cli/upgrade_command.rb +2 -3
  115. data/lib/rigor/cli.rb +16 -28
  116. data/lib/rigor/config_audit.rb +35 -44
  117. data/lib/rigor/configuration/dependencies.rb +41 -70
  118. data/lib/rigor/configuration/severity_profile.rb +28 -42
  119. data/lib/rigor/configuration.rb +150 -241
  120. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  121. data/lib/rigor/environment/class_registry.rb +19 -26
  122. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  123. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  124. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  125. data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
  126. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  127. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  128. data/lib/rigor/environment/rbs_loader.rb +420 -518
  129. data/lib/rigor/environment/reflection.rb +28 -53
  130. data/lib/rigor/environment/reporters.rb +13 -24
  131. data/lib/rigor/environment.rb +225 -304
  132. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  133. data/lib/rigor/flow_contribution/element.rb +7 -12
  134. data/lib/rigor/flow_contribution/fact.rb +33 -57
  135. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  136. data/lib/rigor/flow_contribution/merger.rb +32 -47
  137. data/lib/rigor/flow_contribution.rb +37 -55
  138. data/lib/rigor/inference/acceptance.rb +133 -219
  139. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  140. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  141. data/lib/rigor/inference/budget_trace.rb +43 -63
  142. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  143. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  144. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  145. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  146. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  147. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  148. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  149. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  150. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  151. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  152. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  153. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  154. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  155. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  156. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  157. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  158. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  159. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  160. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  161. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  162. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  163. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  164. data/lib/rigor/inference/def_return_typer.rb +8 -14
  165. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  166. data/lib/rigor/inference/expression_typer.rb +794 -1154
  167. data/lib/rigor/inference/fallback.rb +8 -12
  168. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  169. data/lib/rigor/inference/flow_tracer.rb +25 -36
  170. data/lib/rigor/inference/fork_map.rb +87 -0
  171. data/lib/rigor/inference/hkt_body.rb +45 -68
  172. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  173. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  174. data/lib/rigor/inference/hkt_registry.rb +50 -76
  175. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  176. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  177. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  178. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  179. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  180. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  181. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  182. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  183. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  184. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  185. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  186. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  187. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  188. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  189. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  190. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  191. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  192. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  193. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  194. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  195. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  196. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  197. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  198. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  199. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  200. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  201. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  202. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  203. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  204. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  205. data/lib/rigor/inference/mutation_widening.rb +115 -158
  206. data/lib/rigor/inference/narrowing.rb +627 -727
  207. data/lib/rigor/inference/origin_lookup.rb +38 -0
  208. data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
  209. data/lib/rigor/inference/precision_scanner.rb +26 -35
  210. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  211. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  212. data/lib/rigor/inference/protection_scanner.rb +16 -18
  213. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  214. data/lib/rigor/inference/scope_indexer.rb +473 -821
  215. data/lib/rigor/inference/statement_evaluator.rb +754 -1114
  216. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  217. data/lib/rigor/inference/synthetic_method.rb +9 -16
  218. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  219. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  220. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  221. data/lib/rigor/language_server/buffer_table.rb +11 -18
  222. data/lib/rigor/language_server/completion_provider.rb +69 -116
  223. data/lib/rigor/language_server/debouncer.rb +16 -25
  224. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  225. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  226. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  227. data/lib/rigor/language_server/hover_provider.rb +8 -11
  228. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  229. data/lib/rigor/language_server/loop.rb +14 -19
  230. data/lib/rigor/language_server/project_context.rb +46 -74
  231. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  232. data/lib/rigor/language_server/server.rb +55 -83
  233. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  234. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  235. data/lib/rigor/language_server/uri.rb +8 -13
  236. data/lib/rigor/language_server.rb +4 -6
  237. data/lib/rigor/mcp/loop.rb +2 -3
  238. data/lib/rigor/mcp/server.rb +4 -7
  239. data/lib/rigor/mcp.rb +3 -6
  240. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  241. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  242. data/lib/rigor/plugin/base.rb +335 -518
  243. data/lib/rigor/plugin/blueprint.rb +14 -23
  244. data/lib/rigor/plugin/box.rb +18 -29
  245. data/lib/rigor/plugin/fact_store.rb +16 -26
  246. data/lib/rigor/plugin/inflector.rb +37 -53
  247. data/lib/rigor/plugin/io_boundary.rb +33 -56
  248. data/lib/rigor/plugin/isolation.rb +42 -55
  249. data/lib/rigor/plugin/load_error.rb +10 -15
  250. data/lib/rigor/plugin/loader.rb +30 -49
  251. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  252. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  253. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  254. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  255. data/lib/rigor/plugin/macro.rb +10 -15
  256. data/lib/rigor/plugin/manifest.rb +85 -144
  257. data/lib/rigor/plugin/node_context.rb +14 -22
  258. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  259. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  260. data/lib/rigor/plugin/registry.rb +132 -205
  261. data/lib/rigor/plugin/services.rb +21 -33
  262. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  263. data/lib/rigor/plugin/trust_policy.rb +24 -38
  264. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  265. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  266. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  267. data/lib/rigor/protection/mutator.rb +50 -70
  268. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  269. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  270. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  271. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  272. data/lib/rigor/rbs_extended.rb +107 -197
  273. data/lib/rigor/reflection.rb +68 -86
  274. data/lib/rigor/scope/discovery_index.rb +14 -19
  275. data/lib/rigor/scope.rb +255 -310
  276. data/lib/rigor/sig_gen/classification.rb +6 -10
  277. data/lib/rigor/sig_gen/generator.rb +197 -323
  278. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  279. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  280. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  281. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  282. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  283. data/lib/rigor/sig_gen/renderer.rb +7 -13
  284. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  285. data/lib/rigor/sig_gen/write_result.rb +8 -16
  286. data/lib/rigor/sig_gen/writer.rb +95 -174
  287. data/lib/rigor/sig_gen.rb +3 -6
  288. data/lib/rigor/signature_path_audit.rb +24 -30
  289. data/lib/rigor/source/constant_path.rb +10 -14
  290. data/lib/rigor/source/literals.rb +31 -45
  291. data/lib/rigor/source/node_locator.rb +9 -11
  292. data/lib/rigor/source/node_walker.rb +9 -13
  293. data/lib/rigor/source.rb +3 -4
  294. data/lib/rigor/testing.rb +16 -20
  295. data/lib/rigor/triage/catalogue.rb +38 -62
  296. data/lib/rigor/triage.rb +31 -52
  297. data/lib/rigor/trinary.rb +9 -13
  298. data/lib/rigor/type/acceptance_router.rb +4 -6
  299. data/lib/rigor/type/accepts_result.rb +10 -14
  300. data/lib/rigor/type/app.rb +19 -27
  301. data/lib/rigor/type/bot.rb +4 -6
  302. data/lib/rigor/type/bound_method.rb +10 -15
  303. data/lib/rigor/type/combinator.rb +165 -257
  304. data/lib/rigor/type/constant.rb +23 -34
  305. data/lib/rigor/type/data_class.rb +10 -15
  306. data/lib/rigor/type/data_instance.rb +14 -20
  307. data/lib/rigor/type/difference.rb +21 -32
  308. data/lib/rigor/type/dynamic.rb +3 -5
  309. data/lib/rigor/type/hash_shape.rb +32 -18
  310. data/lib/rigor/type/integer_range.rb +11 -16
  311. data/lib/rigor/type/intersection.rb +27 -42
  312. data/lib/rigor/type/nominal.rb +10 -15
  313. data/lib/rigor/type/plain_lattice.rb +9 -13
  314. data/lib/rigor/type/refined.rb +67 -114
  315. data/lib/rigor/type/singleton.rb +4 -6
  316. data/lib/rigor/type/struct_class.rb +11 -16
  317. data/lib/rigor/type/struct_instance.rb +15 -21
  318. data/lib/rigor/type/tuple.rb +14 -19
  319. data/lib/rigor/type/union.rb +30 -42
  320. data/lib/rigor/type_node/generic.rb +14 -26
  321. data/lib/rigor/type_node/identifier.rb +12 -19
  322. data/lib/rigor/type_node.rb +3 -12
  323. data/lib/rigor/value_semantics.rb +16 -21
  324. data/lib/rigor/version.rb +1 -1
  325. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
  337. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  340. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
  348. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  350. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  351. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  353. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  354. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  355. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  356. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
  357. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  358. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  359. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  361. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  363. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  365. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  366. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  367. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  368. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  369. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  372. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  373. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  376. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  377. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  378. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  379. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  380. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  381. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  385. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  386. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  389. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  390. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
  398. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
  399. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  400. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  406. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  408. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  409. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  411. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  416. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  417. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  426. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  427. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  428. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  429. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  430. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  431. data/sig/rigor/environment.rbs +1 -0
  432. data/sig/rigor/scope.rbs +6 -0
  433. metadata +9 -1
@@ -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",
@@ -215,14 +182,33 @@ module Rigor
215
182
  Rigor::Type::Combinator.nominal_of(class_name)
216
183
  end
217
184
 
185
+ # Phase 5b (2026-07-10) — keep the strong-parameters fluent chain typed. `params` types to
186
+ # `ActionController::Parameters` (above), but the chained `params.require(:user).permit(:name)` /
187
+ # `params.permit(...)` calls returned `Dynamic` at the first hop (Parameters ships no bundled RBS,
188
+ # so a call on it resolves lenient-to-Dynamic), leaking every downstream site (`.permit`, `.to_h`,
189
+ # `.each`) to unprotected. These three methods return another `Parameters`, so gate on a
190
+ # `Parameters` receiver and re-type the result as the same lenient nominal — the chain stays a
191
+ # concrete receiver end-to-end and `coverage --protection` counts the sites as protected.
192
+ #
193
+ # `require` may return a scalar for a flat key at runtime (`params.require(:id) → String`), but
194
+ # typing it as the RBS-less `Parameters` is FP-safe by the same argument as the readers: every
195
+ # method on a Parameters value stays engine-lenient (no `undefined-method`), and this types the
196
+ # container only, never a caller's argument (ADR-5). The GitLab strong-params survey (108 leaked
197
+ # `.permit` sites) is the demand.
198
+ STRONG_PARAMS_CHAIN_METHODS = %i[require permit permit!].freeze
199
+
200
+ dynamic_return receivers: [REQUEST_CONTEXT_READER_TYPES[:params]], methods: STRONG_PARAMS_CHAIN_METHODS do |call_node, _scope|
201
+ next nil unless call_node.is_a?(Prism::CallNode)
202
+
203
+ Rigor::Type::Combinator.nominal_of(REQUEST_CONTEXT_READER_TYPES[:params])
204
+ end
205
+
218
206
  private
219
207
 
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.
208
+ # True when the current `self` is a controller — the enclosing class is one the discoverer
209
+ # indexed, or its name follows the Rails `*Controller` convention (covering controllers outside
210
+ # `controller_search_paths`, e.g. one shipped by an engine). Typing `params` is precision-additive,
211
+ # so the name-convention fallback is FP-safe.
226
212
  def controller_scope?(scope)
227
213
  self_type = scope&.self_type
228
214
  return false unless self_type.respond_to?(:class_name)
@@ -238,12 +224,10 @@ module Rigor
238
224
 
239
225
  def controller_file?(path)
240
226
  @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.
227
+ # The runner may pass `path` as either an absolute path (when `paths:` was configured
228
+ # absolutely) or a relative one (when configured relatively). The `controller_search_paths`
229
+ # knob is always project-root-relative. Match the configured root as a /-bracketed substring
230
+ # so both shapes resolve.
247
231
  path.include?("/#{root}/") || path.start_with?("#{root}/") || path == root
248
232
  end
249
233
  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(
@@ -3,22 +3,17 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Activejob < Rigor::Plugin::Base
6
- # Frozen catalogue of discovered ActiveJob subclasses
7
- # keyed by qualified class name. Each entry holds the
8
- # `#perform` method's arity envelope so the analyzer can
9
- # validate `Job.perform_later(...)` call sites.
6
+ # Frozen catalogue of discovered ActiveJob subclasses keyed by qualified class name. Each entry
7
+ # holds the `#perform` method's arity envelope so the analyzer can validate
8
+ # `Job.perform_later(...)` call sites.
10
9
  #
11
- # `min_arity` / `max_arity` form a closed range
12
- # (`Float::INFINITY` for the upper bound when `*args`
13
- # is present). `keyword_required` lists any required
14
- # keyword arguments Active Job supports keyword args
15
- # but they're rare in user code, so the analyzer
16
- # validates positional arity only (keyword arity
17
- # validation is deferred).
10
+ # `min_arity` / `max_arity` form a closed range (`Float::INFINITY` for the upper bound when
11
+ # `*args` is present). `keyword_required` lists any required keyword arguments — Active Job
12
+ # supports keyword args but they're rare in user code, so the analyzer validates positional arity
13
+ # only (keyword arity validation is deferred).
18
14
  class JobIndex
19
15
  Entry = Data.define(:class_name, :min_arity, :max_arity, :keyword_required) do
20
- # Flexible-friendly textual form of the arity for
21
- # error messages: `1`, `1..2`, `2+`.
16
+ # Flexible-friendly textual form of the arity for error messages: `1`, `1..2`, `2+`.
22
17
  def arity_label
23
18
  return "#{min_arity}+" if max_arity == Float::INFINITY
24
19
  return min_arity.to_s if min_arity == max_arity
@@ -8,14 +8,12 @@ require_relative "activejob/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-activejob — validates `Job.perform_later(...)` /
12
- # `.perform_now(...)` / `.perform(...)` argument arity
13
- # against the discovered `#perform` definitions.
11
+ # rigor-activejob — validates `Job.perform_later(...)` / `.perform_now(...)` / `.perform(...)`
12
+ # argument arity against the discovered `#perform` definitions.
14
13
  #
15
14
  # Tier 1D of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
16
- # Statically discovers ActiveJob subclasses by walking
17
- # the configured `job_search_paths` and parsing each
18
- # file with Prism — no `active_job` runtime dependency.
15
+ # Statically discovers ActiveJob subclasses by walking the configured `job_search_paths` and
16
+ # parsing each file with Prism — no `active_job` runtime dependency.
19
17
  #
20
18
  # ## Configuration
21
19
  #
@@ -27,16 +25,12 @@ module Rigor
27
25
  #
28
26
  # ## Limitations (v0.1.0)
29
27
  #
30
- # - Direct-superclass match only. `class WelcomeJob <
31
- # BaseJob` where `BaseJob < ApplicationJob` is NOT
32
- # discovered. Add `BaseJob` to `job_base_classes` if
33
- # needed.
34
- # - The `#perform` arity is read from the syntactic
35
- # parameter list. Methods built via `define_method`
36
- # are out of scope.
37
- # - Required keyword arguments are recognised but not
38
- # validated at the call site (positional arity only
39
- # for v0.1.0).
28
+ # - Direct-superclass match only. `class WelcomeJob < BaseJob` where `BaseJob < ApplicationJob` is
29
+ # NOT discovered. Add `BaseJob` to `job_base_classes` if needed.
30
+ # - The `#perform` arity is read from the syntactic parameter list. Methods built via
31
+ # `define_method` are out of scope.
32
+ # - Required keyword arguments are recognised but not validated at the call site (positional arity
33
+ # only for v0.1.0).
40
34
  class Activejob < Rigor::Plugin::Base
41
35
  manifest(
42
36
  id: "activejob",
@@ -48,11 +42,10 @@ module Rigor
48
42
  }
49
43
  )
50
44
 
51
- # Cached: discovered job index. `watch:` (ADR-60 WD3) covers
52
- # every `.rb` under `job_search_paths` so the cache invalidates
53
- # when a job is added, removed, or edited; the discoverer's
54
- # in-block `IoBoundary` reads are captured into the record-and-
55
- # validate dependency descriptor after the block runs.
45
+ # Cached: discovered job index. `watch:` (ADR-60 WD3) covers every `.rb` under `job_search_paths`
46
+ # so the cache invalidates when a job is added, removed, or edited; the discoverer's in-block
47
+ # `IoBoundary` reads are captured into the record-and-validate dependency descriptor after the
48
+ # block runs.
56
49
  producer :job_index, watch: -> { [[@job_search_paths, "**/*.rb"]] } do |_params|
57
50
  JobDiscoverer.new(
58
51
  io_boundary: io_boundary,
@@ -66,9 +59,8 @@ module Rigor
66
59
  @job_base_classes = Array(config.fetch("job_base_classes")).map(&:to_s)
67
60
  end
68
61
 
69
- # File-level only: the load-error emission. Per-call arity
70
- # validation runs over the engine-owned walk via the node_rule
71
- # below (ADR-37). The job index is lazily loaded + memoised by
62
+ # File-level only: the load-error emission. Per-call arity validation runs over the engine-owned
63
+ # walk via the node_rule below (ADR-37). The job index is lazily loaded + memoised by
72
64
  # `producer_value`, shared by both surfaces.
73
65
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
74
66
  index = producer_value(:job_index)