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
@@ -5,17 +5,13 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class Activestorage < Rigor::Plugin::Base
8
- # Per-file walker. Emits one `:info` `attachment-call`
9
- # diagnostic per recognised attachment access on a
10
- # known AR class. The diagnostic surfaces what the
11
- # plugin recognised so users can verify the model →
12
- # attachment mapping the plugin sees.
8
+ # Per-file walker. Emits one `:info` `attachment-call` diagnostic per recognised attachment access
9
+ # on a known AR class. The diagnostic surfaces what the plugin recognised so users can verify the
10
+ # model attachment mapping the plugin sees.
13
11
  #
14
- # No `:error` diagnostics here — the `dynamic_return`
15
- # return-type narrowing carries the type-checking value.
16
- # An `unknown-attachment` rule (similar to
17
- # `rigor-activerecord`'s `unknown-column`) is deferred:
18
- # it requires a coupled receiver-class narrowing pass.
12
+ # No `:error` diagnostics here — the `dynamic_return` return-type narrowing carries the
13
+ # type-checking value. An `unknown-attachment` rule (similar to `rigor-activerecord`'s
14
+ # `unknown-column`) is deferred: it requires a coupled receiver-class narrowing pass.
19
15
  class Analyzer
20
16
  attr_reader :diagnostics
21
17
 
@@ -47,10 +43,8 @@ module Rigor
47
43
  @attachment_index.attachments_for("::#{owner}")
48
44
  return if attachments.nil?
49
45
 
50
- # Only flag when the method matches a known
51
- # attachment name (the `dynamic_return` rule
52
- # provides the narrowing; the diagnostic just
53
- # confirms the recognition).
46
+ # Only flag when the method matches a known attachment name (the `dynamic_return` rule provides
47
+ # the narrowing; the diagnostic just confirms the recognition).
54
48
  attachment = attachments.find { |a| a[:name] == node.name.to_s }
55
49
  return if attachment.nil?
56
50
 
@@ -6,10 +6,8 @@ require "rigor/source/literals"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class Activestorage < Rigor::Plugin::Base
9
- # Walks the configured model search paths via the plugin's
10
- # `IoBoundary`, parses each `.rb` file with Prism, and
11
- # collects `has_one_attached` / `has_many_attached`
12
- # declarations.
9
+ # Walks the configured model search paths via the plugin's `IoBoundary`, parses each `.rb` file
10
+ # with Prism, and collects `has_one_attached` / `has_many_attached` declarations.
13
11
  #
14
12
  # Returns rows the {AttachmentIndex} consumes:
15
13
  #
@@ -19,17 +17,13 @@ module Rigor
19
17
  #
20
18
  # Limitations (intentional for v0.1.0 of the plugin):
21
19
  #
22
- # - The walker matches any class declaration that
23
- # contains a `has_*_attached` call. Whether the class
24
- # IS an ActiveRecord model is not verified here
25
- # `rigor-activerecord` provides that check via its
26
- # own model index. The analyser is intentionally
27
- # lenient so the plugin runs standalone in projects
28
- # that haven't loaded `rigor-activerecord` yet.
29
- # - Only Symbol-literal attachment names are recognised.
30
- # `has_one_attached(args)` or computed names decline.
31
- # - Modules (`class Admin::User`) are recognised; the
32
- # resulting class name is the lexical path
20
+ # - The walker matches any class declaration that contains a `has_*_attached` call. Whether the
21
+ # class IS an ActiveRecord model is not verified here — `rigor-activerecord` provides that check
22
+ # via its own model index. The analyser is intentionally lenient so the plugin runs standalone in
23
+ # projects that haven't loaded `rigor-activerecord` yet.
24
+ # - Only Symbol-literal attachment names are recognised. `has_one_attached(args)` or computed names
25
+ # decline.
26
+ # - Modules (`class Admin::User`) are recognised; the resulting class name is the lexical path
33
27
  # (`Admin::User`).
34
28
  class AttachmentDiscoverer
35
29
  ATTACHMENT_METHODS = {
@@ -3,16 +3,13 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Activestorage < Rigor::Plugin::Base
6
- # Maps a discovered class name to the list of attachment
7
- # rows declared on it. Marshal-clean so the cache producer
8
- # round-trips it through the standard pair.
6
+ # Maps a discovered class name to the list of attachment rows declared on it. Marshal-clean so the
7
+ # cache producer round-trips it through the standard pair.
9
8
  #
10
9
  # Each row is `{ name:, kind: }`:
11
10
  #
12
- # - `name` — String, the attachment method name as the
13
- # user invokes it (`"avatar"`).
14
- # - `kind` — `:singular` (`has_one_attached`) or
15
- # `:collection` (`has_many_attached`).
11
+ # - `name` — String, the attachment method name as the user invokes it (`"avatar"`).
12
+ # - `kind` — `:singular` (`has_one_attached`) or `:collection` (`has_many_attached`).
16
13
  class AttachmentIndex
17
14
  attr_reader :entries
18
15
 
@@ -8,26 +8,18 @@ require_relative "activestorage/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-activestorage — recognises `has_one_attached` /
12
- # `has_many_attached` macros on ActiveRecord models and
13
- # contributes attachment accessor return types so chained
14
- # calls (`user.avatar.attached?`) route through Rigor's
15
- # normal dispatch.
11
+ # rigor-activestorage — recognises `has_one_attached` / `has_many_attached` macros on ActiveRecord
12
+ # models and contributes attachment accessor return types so chained calls (`user.avatar.attached?`)
13
+ # route through Rigor's normal dispatch.
16
14
  #
17
15
  # ## Architecture
18
16
  #
19
- # One discovery pass per run reads the configured AR model
20
- # paths (default `app/models/`) via the plugin's
21
- # `IoBoundary`, walks each `.rb` file with Prism, and
22
- # collects `has_one_attached :avatar` /
23
- # `has_many_attached :photos` macros into an
24
- # {AttachmentIndex} keyed by class name. The walker is
25
- # stand-alone (mirrors `rigor-activerecord`'s
26
- # `ModelDiscoverer`) so the plugin works even when
27
- # `rigor-activerecord` is not loaded; when it IS loaded,
28
- # the published `:model_index` fact (ADR-9 cross-plugin
29
- # API) drives the same class set so the two plugins agree
30
- # on what counts as a model.
17
+ # One discovery pass per run reads the configured AR model paths (default `app/models/`) via the
18
+ # plugin's `IoBoundary`, walks each `.rb` file with Prism, and collects `has_one_attached :avatar` /
19
+ # `has_many_attached :photos` macros into an {AttachmentIndex} keyed by class name. The walker is
20
+ # stand-alone (mirrors `rigor-activerecord`'s `ModelDiscoverer`) so the plugin works even when
21
+ # `rigor-activerecord` is not loaded; when it IS loaded, the published `:model_index` fact (ADR-9
22
+ # cross-plugin API) drives the same class set so the two plugins agree on what counts as a model.
31
23
  #
32
24
  # ## Configuration
33
25
  #
@@ -39,9 +31,8 @@ module Rigor
39
31
  #
40
32
  # `model_search_paths` defaults to `["app/models"]`.
41
33
  #
42
- # The class name `Rigor::Plugin::Activestorage` (single
43
- # capital A) matches the constant-distinguishing convention
44
- # used by `rigor-activerecord`.
34
+ # The class name `Rigor::Plugin::Activestorage` (single capital A) matches the constant-distinguishing
35
+ # convention used by `rigor-activerecord`.
45
36
  class Activestorage < Rigor::Plugin::Base
46
37
  manifest(
47
38
  id: "activestorage",
@@ -53,11 +44,9 @@ module Rigor
53
44
  consumes: [{ plugin_id: "activerecord", name: :model_index, optional: true }]
54
45
  )
55
46
 
56
- # Cached: attachment index. Walks every `.rb` file under
57
- # `model_search_paths` for `has_*_attached` macros. `watch:`
58
- # (ADR-60 WD3) covers model-file additions; the discoverer's
59
- # in-block reads are captured into the record-and-validate
60
- # dependency descriptor after the block runs.
47
+ # Cached: attachment index. Walks every `.rb` file under `model_search_paths` for `has_*_attached`
48
+ # macros. `watch:` (ADR-60 WD3) covers model-file additions; the discoverer's in-block reads are
49
+ # captured into the record-and-validate dependency descriptor after the block runs.
61
50
  producer :attachment_index, watch: -> { [[@model_search_paths, "**/*.rb"]] } do |_params|
62
51
  rows = AttachmentDiscoverer.new(
63
52
  io_boundary: io_boundary,
@@ -80,18 +69,12 @@ module Rigor
80
69
  Analyzer.new(path: path, attachment_index: index).analyze(root).diagnostics
81
70
  end
82
71
 
83
- # Return-type contribution: when the receiver is
84
- # `Nominal[Model]` and the method matches a discovered
85
- # attachment, narrows to
86
- # `Nominal[ActiveStorage::Attached::One]` (singular) or
87
- # `Nominal[ActiveStorage::Attached::Many]` (collection)
88
- # via a `dynamic_return` rule keyed on the live set of
89
- # model class names from the attachment index.
90
- # The chained call (`.attached?`, `.purge`, `.url`)
91
- # then resolves through ActiveStorage's RBS surface.
92
- # Attachment setters (`user.avatar=`) decline — they
93
- # take side-effecting argument types that the RBS
94
- # surface already covers.
72
+ # Return-type contribution: when the receiver is `Nominal[Model]` and the method matches a
73
+ # discovered attachment, narrows to `Nominal[ActiveStorage::Attached::One]` (singular) or
74
+ # `Nominal[ActiveStorage::Attached::Many]` (collection) via a `dynamic_return` rule keyed on the
75
+ # live set of model class names from the attachment index. The chained call (`.attached?`, `.purge`,
76
+ # `.url`) then resolves through ActiveStorage's RBS surface. Attachment setters (`user.avatar=`)
77
+ # decline they take side-effecting argument types that the RBS surface already covers.
95
78
  dynamic_return receivers: -> { attachment_index&.class_names || [] } do |call_node, scope|
96
79
  next nil unless call_node.is_a?(Prism::CallNode)
97
80
  next nil if call_node.receiver.nil?
@@ -129,10 +112,9 @@ module Rigor
129
112
  def attachment_index
130
113
  return @attachment_index if @attachment_index
131
114
 
132
- # ADR-60 WD3 record-and-validate: the producer's in-block
133
- # `AttachmentDiscoverer` reads are captured into the
134
- # dependency descriptor after the block runs, and the
135
- # producer's `watch:` covers model-file additions.
115
+ # ADR-60 WD3 record-and-validate: the producer's in-block `AttachmentDiscoverer` reads are
116
+ # captured into the dependency descriptor after the block runs, and the producer's `watch:`
117
+ # covers model-file additions.
136
118
  @attachment_index = cache_for(:attachment_index, params: {}).call
137
119
  rescue Plugin::AccessDeniedError => e
138
120
  @load_errors << "rigor-activestorage: #{e.message}"
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Entry point matching the `rigor-<id>` gem-name convention.
4
- # The Plugin::Loader's `require_gem!` step calls
5
- # `require "rigor-activestorage"`, and this file loads the
6
- # plugin's actual implementation under the `rigor/plugin/`
7
- # namespace so the require / register pair fires.
3
+ # Entry point matching the `rigor-<id>` gem-name convention. The Plugin::Loader's `require_gem!` step
4
+ # calls `require "rigor-activestorage"`, and this file loads the plugin's actual implementation under
5
+ # the `rigor/plugin/` namespace so the require / register pair fires.
8
6
  require "rigor/plugin/activestorage"
@@ -4,14 +4,11 @@ require "rigor/plugin"
4
4
 
5
5
  module Rigor
6
6
  module Plugin
7
- # ADR-25 — a pure RBS-bundle plugin. It ships NO analyzer code:
8
- # no `diagnostics_for_file`, no `dynamic_return`. Its
9
- # whole contribution is the manifest's `signature_paths: ["sig"]`,
10
- # which declares the bundled ActiveSupport `core_ext` RBS
11
- # directory. `Plugin::Loader` resolves that directory against
12
- # this gem's root and `Environment.for_project` merges it into
13
- # the RBS environment, so the ActiveSupport core-extension
14
- # selectors (`3.days`, `"x".squish`, `Time.current`, …) resolve.
7
+ # ADR-25 — a pure RBS-bundle plugin. It ships NO analyzer code: no `diagnostics_for_file`, no
8
+ # `dynamic_return`. Its whole contribution is the manifest's `signature_paths: ["sig"]`, which
9
+ # declares the bundled ActiveSupport `core_ext` RBS directory. `Plugin::Loader` resolves that
10
+ # directory against this gem's root and `Environment.for_project` merges it into the RBS environment,
11
+ # so the ActiveSupport core-extension selectors (`3.days`, `"x".squish`, `Time.current`, …) resolve.
15
12
  #
16
13
  # Activate it like any plugin — no path, no vendoring:
17
14
  #
@@ -23,8 +20,7 @@ module Rigor
23
20
  class ActivesupportCoreExt < Rigor::Plugin::Base
24
21
  manifest(
25
22
  id: "activesupport-core-ext",
26
- # Bumped 2026-05-28 — added Date#midnight /
27
- # at_midnight / beginning_of_day / end_of_day (Rails
23
+ # Bumped 2026-05-28 — added Date#midnight / at_midnight / beginning_of_day / end_of_day (Rails
28
24
  # aliases that return Time, not Date).
29
25
  version: "0.2.0",
30
26
  description: "RBS bundle for the most-frequently-flagged ActiveSupport core_ext extensions.",
@@ -1,20 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rigor-activesupport-core-ext — a community-maintained RBS bundle
4
- # for the most-frequently-flagged ActiveSupport core extensions.
3
+ # rigor-activesupport-core-ext — a community-maintained RBS bundle for the most-frequently-flagged
4
+ # ActiveSupport core extensions.
5
5
  #
6
- # Per ADR-25 this gem is a pure RBS-bundle *plugin*: it ships a
7
- # `sig/` directory and a trivial `Rigor::Plugin::Base` subclass
8
- # whose manifest declares `signature_paths: ["sig"]`. Activate it
9
- # by listing the gem under `.rigor.yml`'s `plugins:` — Rigor
10
- # resolves and loads the bundled `sig/` automatically, with no
11
- # hand-written path:
6
+ # Per ADR-25 this gem is a pure RBS-bundle *plugin*: it ships a `sig/` directory and a trivial
7
+ # `Rigor::Plugin::Base` subclass whose manifest declares `signature_paths: ["sig"]`. Activate it by
8
+ # listing the gem under `.rigor.yml`'s `plugins:` — Rigor resolves and loads the bundled `sig/`
9
+ # automatically, with no hand-written path:
12
10
  #
13
11
  # # .rigor.yml
14
12
  # plugins:
15
13
  # - rigor-activesupport-core-ext
16
14
  #
17
- # Coverage scope and rationale: see `README.md` in this directory
18
- # and `docs/notes/20260515-real-world-rails-survey.md` for the
19
- # survey that established the selector ranking.
15
+ # Coverage scope and rationale: see `README.md` in this directory and
16
+ # `docs/notes/20260515-real-world-rails-survey.md` for the survey that established the selector ranking.
20
17
  require_relative "rigor/plugin/activesupport_core_ext"
@@ -4,26 +4,21 @@ require "rigor/plugin"
4
4
 
5
5
  module Rigor
6
6
  module Plugin
7
- # ADR-16 Tier B worked plugin: recognises Devise's model-side
8
- # `devise :strategy_a, :strategy_b` DSL on `ActiveRecord::Base`
9
- # subclasses and explodes each strategy's module RBS into the
10
- # calling class.
7
+ # ADR-16 Tier B worked plugin: recognises Devise's model-side `devise :strategy_a, :strategy_b` DSL on
8
+ # `ActiveRecord::Base` subclasses and explodes each strategy's module RBS into the calling class.
11
9
  #
12
- # Per the per-library survey (§ Devise), Devise's `devise(*modules)`
13
- # at `lib/devise/models.rb:79-112` is a **table-driven include
14
- # sequence**: each Symbol argument resolves through
15
- # `Devise::Models.const_get(m.to_s.classify)` to a concrete
16
- # `Devise::Models::*` module, which is then mixed into the
17
- # calling class. The substrate replays the same shape
18
- # statically — the manifest's `modules_by_symbol:` mirrors
19
- # Devise's `lib/devise/modules.rb` and the pre-pass scanner
20
- # synthesises one SyntheticMethod per (calling class, included
21
- # module instance method) pair into the SyntheticMethodIndex.
10
+ # Per the per-library survey (§ Devise), Devise's `devise(*modules)` at `lib/devise/models.rb:79-112`
11
+ # is a **table-driven include sequence**: each Symbol argument resolves through
12
+ # `Devise::Models.const_get(m.to_s.classify)` to a concrete `Devise::Models::*` module, which is then
13
+ # mixed into the calling class. The substrate replays the same shape statically — the manifest's
14
+ # `modules_by_symbol:` mirrors Devise's `lib/devise/modules.rb` and the pre-pass scanner synthesises
15
+ # one SyntheticMethod per (calling class, included module instance method) pair into the
16
+ # SyntheticMethodIndex.
22
17
  #
23
18
  # ## Reach
24
19
  #
25
- # The full set of strategies Devise registers via
26
- # `Devise.add_module` at gem load (per `lib/devise/modules.rb`):
20
+ # The full set of strategies Devise registers via `Devise.add_module` at gem load (per
21
+ # `lib/devise/modules.rb`):
27
22
  #
28
23
  # - `:database_authenticatable` — password + email auth core
29
24
  # - `:recoverable` — password-reset flow
@@ -37,41 +32,31 @@ module Rigor
37
32
  # - `:omniauthable` — OmniAuth providers
38
33
  # - `:authenticatable` — always-included base module
39
34
  #
40
- # The substrate's pre-pass scanner consults each module's RBS
41
- # via `Environment::RbsLoader#instance_definition` to enumerate
42
- # method names. A user project providing real Devise via
43
- # Bundler will see methods like `valid_password?`,
44
- # `send_reset_password_instructions`, etc. resolve through the
45
- # synthetic-method tier without `call.undefined-method`.
35
+ # The substrate's pre-pass scanner consults each module's RBS via `Environment::RbsLoader#instance_definition`
36
+ # to enumerate method names. A user project providing real Devise via Bundler will see methods like
37
+ # `valid_password?`, `send_reset_password_instructions`, etc. resolve through the synthetic-method
38
+ # tier without `call.undefined-method`.
46
39
  #
47
40
  # ## Precision tier
48
41
  #
49
- # The scanner records `origin_module:` in each synthetic
50
- # method's provenance. The dispatcher's slice-6a TierB path
51
- # (`promote_via_origin_module`) redispatches on
52
- # `Nominal[origin_module]` via `RbsDispatch`, so Devise's
53
- # authored RBS return types win: `valid_password?` returns
54
- # `bool`, not `Dynamic[T]`. Unknown return types degrade
55
- # gracefully to `Dynamic[T]`.
42
+ # The scanner records `origin_module:` in each synthetic method's provenance. The dispatcher's
43
+ # slice-6a TierB path (`promote_via_origin_module`) redispatches on `Nominal[origin_module]` via
44
+ # `RbsDispatch`, so Devise's authored RBS return types win: `valid_password?` returns `bool`, not
45
+ # `Dynamic[T]`. Unknown return types degrade gracefully to `Dynamic[T]`.
56
46
  #
57
47
  # ## Scope
58
48
  #
59
- # - Recognises model-side `devise :a, :b` on any AR::Base
60
- # subclass; trait symbol set mirrors `lib/devise/modules.rb`.
61
- # - `Devise::Models::Authenticatable` is always_included
62
- # (matches Devise's `with_options model: true`).
63
- # - Unknown trait symbols silently skipped (per slice-3
64
- # design judgment (2)). User initializers that call
65
- # `Devise.add_module :my_strategy, ...` are NOT seen — that
66
- # path requires a separate scanner for `config/initializers/`
67
- # and is deferred.
68
- # - Controller-side helpers (`current_user`, `authenticate_user!`,
69
- # etc.) are Tier C work, NOT Tier B; deferred to a future
70
- # slice that consumes ADR-9 fact-store entries from a
71
- # `rigor-rails-routes`-style walker.
72
- # - Per-strategy `ClassMethods` extend (Devise's `extend
73
- # Mod::ClassMethods` pattern) is NOT yet wired — slice 3
74
- # covers instance methods only per WD13 floor.
49
+ # - Recognises model-side `devise :a, :b` on any AR::Base subclass; trait symbol set mirrors
50
+ # `lib/devise/modules.rb`.
51
+ # - `Devise::Models::Authenticatable` is always_included (matches Devise's `with_options model: true`).
52
+ # - Unknown trait symbols silently skipped (per slice-3 design judgment (2)). User initializers that
53
+ # call `Devise.add_module :my_strategy, ...` are NOT seen that path requires a separate scanner
54
+ # for `config/initializers/` and is deferred.
55
+ # - Controller-side helpers (`current_user`, `authenticate_user!`, etc.) are Tier C work, NOT Tier B;
56
+ # deferred to a future slice that consumes ADR-9 fact-store entries from a `rigor-rails-routes`-style
57
+ # walker.
58
+ # - Per-strategy `ClassMethods` extend (Devise's `extend Mod::ClassMethods` pattern) is NOT yet wired
59
+ # slice 3 covers instance methods only per WD13 floor.
75
60
  class Devise < Rigor::Plugin::Base
76
61
  manifest(
77
62
  id: "devise",
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Gem entry point. Required by Rigor's plugin loader when
4
- # `.rigor.yml` lists `rigor-devise` under `plugins:`. The loader
5
- # expects this `require` to side-effect a call to
6
- # `Rigor::Plugin.register`, which the body of
7
- # `lib/rigor/plugin/devise.rb` performs at load time.
3
+ # Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-devise` under
4
+ # `plugins:`. The loader expects this `require` to side-effect a call to `Rigor::Plugin.register`,
5
+ # which the body of `lib/rigor/plugin/devise.rb` performs at load time.
8
6
  require_relative "rigor/plugin/devise"
@@ -6,16 +6,13 @@ require "rigor/source/literals"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class DrySchema < Rigor::Plugin::Base
9
- # Walks project source for `Foo = Dry::Schema.{Params,JSON,define}
10
- # { ... }` shapes and emits a
11
- # `{schema_const_fqn => {required: {key => underlying_class}, optional: {…}}}`
12
- # table covering each schema's typed-key surface.
9
+ # Walks project source for `Foo = Dry::Schema.{Params,JSON,define} { ... }` shapes and emits a
10
+ # `{schema_const_fqn => {required: {key => underlying_class}, optional: {…}}}` table covering each
11
+ # schema's typed-key surface.
13
12
  module SchemaScanner
14
- # The dry-schema canonical-type symbols accepted as
15
- # predicate arguments. Maps each to the underlying Ruby
16
- # class name (the same vocabulary `rigor-dry-types`
17
- # uses for `CANONICAL_ALIASES`, intersected with what
18
- # dry-schema's predicate engine accepts).
13
+ # The dry-schema canonical-type symbols accepted as predicate arguments. Maps each to the
14
+ # underlying Ruby class name (the same vocabulary `rigor-dry-types` uses for `CANONICAL_ALIASES`,
15
+ # intersected with what dry-schema's predicate engine accepts).
19
16
  CANONICAL_TYPES = {
20
17
  string: "String",
21
18
  integer: "Integer",
@@ -30,12 +27,10 @@ module Rigor
30
27
  array: "Array"
31
28
  }.tap { |h| h[:nil] = "NilClass" }.freeze
32
29
 
33
- # The dry-schema predicate verbs that accept a type
34
- # argument. Each verb has a slightly different runtime
35
- # semantic (`filled` = present + non-empty; `value` =
36
- # present; `maybe` = present-or-nil; `each` = collection
37
- # element type) but for Rigor's purposes they all
38
- # contribute the same underlying class for the key.
30
+ # The dry-schema predicate verbs that accept a type argument. Each verb has a slightly different
31
+ # runtime semantic (`filled` = present + non-empty; `value` = present; `maybe` = present-or-nil;
32
+ # `each` = collection element type) but for Rigor's purposes they all contribute the same
33
+ # underlying class for the key.
39
34
  TYPE_BEARING_PREDICATES = %i[filled value maybe each].to_set.freeze
40
35
  private_constant :TYPE_BEARING_PREDICATES
41
36
 
@@ -45,17 +40,12 @@ module Rigor
45
40
 
46
41
  module_function
47
42
 
48
- # @param paths [Array<String>] absolute paths to `.rb`
49
- # files the project's `paths:` resolves to.
50
- # @param type_aliases [Hash{String => String}] the
51
- # ADR-9 `:dry_type_aliases` fact published by
52
- # `rigor-dry-types` when loaded. Used to resolve
53
- # `value(Types::Email)` references to their
54
- # underlying class. Empty when the plugin isn't
55
- # loaded.
56
- # @return [Hash{String => Hash{Symbol => Hash{Symbol => String}}}]
57
- # frozen per-schema typed-key table. Empty when no
58
- # recognisable schema declaration is found.
43
+ # @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
44
+ # @param type_aliases [Hash{String => String}] the ADR-9 `:dry_type_aliases` fact published by
45
+ # `rigor-dry-types` when loaded. Used to resolve `value(Types::Email)` references to their
46
+ # underlying class. Empty when the plugin isn't loaded.
47
+ # @return [Hash{String => Hash{Symbol => Hash{Symbol => String}}}] frozen per-schema typed-key
48
+ # table. Empty when no recognisable schema declaration is found.
59
49
  def scan(paths:, type_aliases: {})
60
50
  table = {}
61
51
  paths.each do |path|
@@ -77,11 +67,9 @@ module Rigor
77
67
  end
78
68
  private_class_method :scan_file
79
69
 
80
- # Walks the AST collecting `<Const> = Dry::Schema.X { ... }`
81
- # assignments at any nesting level. Tracks the enclosing
82
- # constant chain so a class-level `class Foo; SCHEMA =
83
- # Dry::Schema.Params { ... }; end` registers as
84
- # `"Foo::SCHEMA"`.
70
+ # Walks the AST collecting `<Const> = Dry::Schema.X { ... }` assignments at any nesting level.
71
+ # Tracks the enclosing constant chain so a class-level `class Foo; SCHEMA = Dry::Schema.Params
72
+ # { ... }; end` registers as `"Foo::SCHEMA"`.
85
73
  def collect_schemas(node, qualified_prefix, type_aliases)
86
74
  return {} if node.nil?
87
75
 
@@ -119,8 +107,7 @@ module Rigor
119
107
  end
120
108
  private_class_method :collect_schema_assignment
121
109
 
122
- # Matches `Dry::Schema.Params { ... }` /
123
- # `Dry::Schema.JSON { ... }` / `Dry::Schema.define { ... }`.
110
+ # Matches `Dry::Schema.Params { ... }` / `Dry::Schema.JSON { ... }` / `Dry::Schema.define { ... }`.
124
111
  def schema_entry_call?(node)
125
112
  return false unless node.is_a?(Prism::CallNode)
126
113
  return false unless SCHEMA_ENTRY_NAMES.include?(node.name)
@@ -147,10 +134,9 @@ module Rigor
147
134
  end
148
135
  private_class_method :collect_schema_shape
149
136
 
150
- # Walks every top-level `required(:key).<predicate>(...)` /
151
- # `optional(:key).<predicate>(...)` chain in the block
152
- # body. The block's body is either a `Prism::StatementsNode`
153
- # (multi-statement) or a single expression node.
137
+ # Walks every top-level `required(:key).<predicate>(...)` / `optional(:key).<predicate>(...)`
138
+ # chain in the block body. The block's body is either a `Prism::StatementsNode` (multi-statement)
139
+ # or a single expression node.
154
140
  def walk_block_body(block_node, &)
155
141
  body = block_node.body
156
142
  return if body.nil?
@@ -160,13 +146,10 @@ module Rigor
160
146
  end
161
147
  private_class_method :walk_block_body
162
148
 
163
- # `required(:key).filled(:string)` parses as a CallNode
164
- # whose receiver is the `required(:key)` call. Walk the
165
- # chain inward looking for the type-bearing predicate at
166
- # the head; the key sits on the chain's tail. The
167
- # `each(<Type>)` predicate yields a list-of-element
168
- # type info (`{type: <T>, list: true}`); other type-bearing
169
- # predicates (`filled`/`value`/`maybe`) yield scalar info
149
+ # `required(:key).filled(:string)` parses as a CallNode whose receiver is the `required(:key)`
150
+ # call. Walk the chain inward looking for the type-bearing predicate at the head; the key sits on
151
+ # the chain's tail. The `each(<Type>)` predicate yields a list-of-element type info (`{type: <T>,
152
+ # list: true}`); other type-bearing predicates (`filled`/`value`/`maybe`) yield scalar info
170
153
  # (`{type: <T>, list: false}`).
171
154
  def visit_chain(node, &block)
172
155
  return unless node.is_a?(Prism::CallNode)
@@ -179,11 +162,9 @@ module Rigor
179
162
  end
180
163
  private_class_method :visit_chain
181
164
 
182
- # `required(:key).filled(:string).value(...)...` — the
183
- # OUTERMOST call's receiver chain ends in the
184
- # `required(:key)` / `optional(:key)` call. Recurse on
185
- # `node.receiver` until we hit the `required` /
186
- # `optional` call, recording the key + kind.
165
+ # `required(:key).filled(:string).value(...)...` — the OUTERMOST call's receiver chain ends in
166
+ # the `required(:key)` / `optional(:key)` call. Recurse on `node.receiver` until we hit the
167
+ # `required` / `optional` call, recording the key + kind.
187
168
  def extract_key_and_kind(node)
188
169
  current = node
189
170
  while current.is_a?(Prism::CallNode)
@@ -199,11 +180,9 @@ module Rigor
199
180
  end
200
181
  private_class_method :extract_key_and_kind
201
182
 
202
- # Walks the call chain finding the first type-bearing
203
- # predicate (`filled` / `value` / `maybe` / `each`) and
204
- # extracts its argument type. Returns a `{type:, list:}`
205
- # tuple (`each` is the only verb that produces a list)
206
- # or nil when no recognisable type sits on the chain.
183
+ # Walks the call chain finding the first type-bearing predicate (`filled` / `value` / `maybe` /
184
+ # `each`) and extracts its argument type. Returns a `{type:, list:}` tuple (`each` is the only
185
+ # verb that produces a list) or nil when no recognisable type sits on the chain.
207
186
  def walk_predicate_chain(node)
208
187
  current = node
209
188
  while current.is_a?(Prism::CallNode)
@@ -217,12 +196,10 @@ module Rigor
217
196
  end
218
197
  private_class_method :walk_predicate_chain
219
198
 
220
- # Reads the first positional argument of a `filled(:string)`
221
- # / `value(:integer)` / `maybe(Types::Email)` call. Returns
222
- # either the canonical-type-symbol's underlying class
223
- # ("String" / "Integer" / …), or the constant's qualified
224
- # name for downstream type-alias resolution. Returns nil
225
- # for anything else.
199
+ # Reads the first positional argument of a `filled(:string)` / `value(:integer)` /
200
+ # `maybe(Types::Email)` call. Returns either the canonical-type-symbol's underlying class
201
+ # ("String" / "Integer" / …), or the constant's qualified name for downstream type-alias
202
+ # resolution. Returns nil for anything else.
226
203
  def extract_type_from_predicate(call_node)
227
204
  arg = call_node.arguments&.arguments&.first
228
205
  case arg
@@ -236,12 +213,10 @@ module Rigor
236
213
  end
237
214
  private_class_method :extract_type_from_predicate
238
215
 
239
- # In-place: any value's `type:` slot in `bucket` that
240
- # doesn't already match a canonical class (e.g.
241
- # `"Types::Email"`) gets resolved through the
242
- # type_aliases fact. Unresolvable values drop from the
243
- # bucket (no fact contribution rather than misleading
244
- # data). The `list:` slot rides along unchanged.
216
+ # In-place: any value's `type:` slot in `bucket` that doesn't already match a canonical class
217
+ # (e.g. `"Types::Email"`) gets resolved through the type_aliases fact. Unresolvable values drop
218
+ # from the bucket (no fact contribution rather than misleading data). The `list:` slot rides along
219
+ # unchanged.
245
220
  def remap_aliases!(bucket, type_aliases)
246
221
  canonical_set = CANONICAL_TYPES.values.to_set
247
222
  bucket.each_pair.to_a.each do |key, info|
@@ -258,9 +233,8 @@ module Rigor
258
233
  end
259
234
  private_class_method :remap_aliases!
260
235
 
261
- # Constant-path serialiser: `Dry::Schema` -> "Dry::Schema",
262
- # bare `Foo` -> "Foo". Returns nil for shapes Prism
263
- # doesn't expose as ConstantRead/PathNode.
236
+ # Constant-path serialiser: `Dry::Schema` -> "Dry::Schema", bare `Foo` -> "Foo". Returns nil for
237
+ # shapes Prism doesn't expose as ConstantRead/PathNode.
264
238
  def constant_name_for(node)
265
239
  return nil if node.nil?
266
240