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
@@ -10,7 +10,8 @@ module Rigor
10
10
  module Plugin
11
11
  # rigor-dry-schema — Tier A per
12
12
  # [ADR-12](../../../../../docs/adr/12-dry-rb-packaging.md) and the
13
- # slicing plan in [docs/design/20260517-dry-validation-slicing.md](../../../../../docs/design/20260517-dry-validation-slicing.md).
13
+ # slicing plan in
14
+ # [docs/design/20260517-dry-validation-slicing.md](../../../../../docs/design/20260517-dry-validation-slicing.md).
14
15
  #
15
16
  # Recognises the canonical dry-schema declaration shapes:
16
17
  #
@@ -28,51 +29,42 @@ module Rigor
28
29
  # required(:foo).value(:string)
29
30
  # end
30
31
  #
31
- # and publishes the resulting
32
- # `{schema_const_fqn => {required: {key => underlying_class}, optional: {…}}}`
33
- # table as the `:dry_schema_table` cross-plugin fact (ADR-9).
34
- # Downstream `rigor-dry-validation` consumes the fact for
35
- # per-Contract typed-payload synthesis.
32
+ # and publishes the resulting `{schema_const_fqn => {required: {key => underlying_class}, optional:
33
+ # {…}}}` table as the `:dry_schema_table` cross-plugin fact (ADR-9). Downstream `rigor-dry-validation`
34
+ # consumes the fact for per-Contract typed-payload synthesis.
36
35
  #
37
36
  # ## Predicate type recognition
38
37
  #
39
- # Each `required(:key).<predicate>(<arg>)` row maps the predicate
40
- # argument to an underlying Ruby class via the dry-schema
41
- # canonical-type vocabulary:
38
+ # Each `required(:key).<predicate>(<arg>)` row maps the predicate argument to an underlying Ruby
39
+ # class via the dry-schema canonical-type vocabulary:
42
40
  #
43
- # - `:string` / `:integer` / `:float` / `:decimal` / `:symbol` /
44
- # `:bool` / `:nil` / `:date` / `:date_time` / `:time` / `:hash`
45
- # / `:array` map to their underlying class.
46
- # - The four predicate verbs `filled` / `value` / `maybe` /
47
- # `each` are accepted on the same row; their semantic
48
- # difference (whether the value is nullable or coerced) does
49
- # not change the underlying class for Rigor's purposes.
50
- # - References to dry-types aliases (`value(Types::Email)`,
51
- # `filled(Types::String)`) resolve through the
52
- # `:dry_type_aliases` ADR-9 fact published by `rigor-dry-types`
53
- # when that plugin is loaded; without it the row degrades to
54
- # "no type contribution from this key".
41
+ # - `:string` / `:integer` / `:float` / `:decimal` / `:symbol` / `:bool` / `:nil` / `:date` /
42
+ # `:date_time` / `:time` / `:hash` / `:array` map to their underlying class.
43
+ # - The four predicate verbs `filled` / `value` / `maybe` / `each` are accepted on the same row; their
44
+ # semantic difference (whether the value is nullable or coerced) does not change the underlying
45
+ # class for Rigor's purposes.
46
+ # - References to dry-types aliases (`value(Types::Email)`, `filled(Types::String)`) resolve through
47
+ # the `:dry_type_aliases` ADR-9 fact published by `rigor-dry-types` when that plugin is loaded;
48
+ # without it the row degrades to "no type contribution from this key".
55
49
  #
56
50
  # ## Floor / ceiling (slice 1)
57
51
  #
58
52
  # Slice 1 ships the **floor**:
59
53
  #
60
- # - Top-level `Foo = Dry::Schema.{Params,JSON,define} { ... }`
61
- # assignments. Class-level constants (`class Bar; SCHEMA =
62
- # Dry::Schema.Params { ... }; end`) work too — the walker
63
- # prefixes the enclosing constant chain.
64
- # - `required(:key).<predicate>(:type_symbol_or_constant)` rows
65
- # for the canonical-type vocabulary above.
54
+ # - Top-level `Foo = Dry::Schema.{Params,JSON,define} { ... }` assignments. Class-level constants
55
+ # (`class Bar; SCHEMA = Dry::Schema.Params { ... }; end`) work too — the walker prefixes the
56
+ # enclosing constant chain.
57
+ # - `required(:key).<predicate>(:type_symbol_or_constant)` rows for the canonical-type vocabulary
58
+ # above.
66
59
  # - Publishes the table; no user-facing diagnostics yet.
67
60
  #
68
61
  # The **ceiling** (slice 2+):
69
62
  #
70
- # - Synthesise typed `result.to_h` returns from each schema
71
- # via ADR-16 Tier C heredoc-template substrate.
63
+ # - Synthesise typed `result.to_h` returns from each schema via ADR-16 Tier C heredoc-template
64
+ # substrate.
72
65
  # - Nested schemas (`schema(do ... end)` inside another row).
73
66
  # - `predicates(:size?)` / `each { ... }` recursion.
74
- # - Per-row `dry-schema.unknown-predicate` /
75
- # `dry-schema.unknown-type` `:info` diagnostics when a
67
+ # - Per-row `dry-schema.unknown-predicate` / `dry-schema.unknown-type` `:info` diagnostics when a
76
68
  # row's predicate or type symbol isn't recognised.
77
69
  class DrySchema < Rigor::Plugin::Base
78
70
  manifest(
@@ -84,10 +76,9 @@ module Rigor
84
76
  consumes: [{ plugin_id: "dry-types", name: :dry_type_aliases, optional: true }]
85
77
  )
86
78
 
87
- # Walks every project file once during `prepare(services)` to
88
- # build the schema table, then publishes via the ADR-9 fact
89
- # store. Mirrors the rigor-dry-types `#prepare` shape — the
90
- # walk is bounded by `paths:`, parse errors degrade silently.
79
+ # Walks every project file once during `prepare(services)` to build the schema table, then publishes
80
+ # via the ADR-9 fact store. Mirrors the rigor-dry-types `#prepare` shape — the walk is bounded by
81
+ # `paths:`, parse errors degrade silently.
91
82
  def prepare(services)
92
83
  type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
93
84
  table = SchemaScanner.scan(paths: scannable_paths(services), type_aliases: type_aliases)
@@ -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-dry-schema` under `plugins:`. The
5
- # loader expects this `require` to side-effect a call to
6
- # `Rigor::Plugin.register`, which the body of
7
- # `lib/rigor/plugin/dry_schema.rb` performs at load time.
3
+ # Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-dry-schema` 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/dry_schema.rb` performs at load time.
8
6
  require_relative "rigor/plugin/dry_schema"
@@ -4,16 +4,13 @@ require "rigor/plugin"
4
4
 
5
5
  module Rigor
6
6
  module Plugin
7
- # ADR-16 Tier C worked plugin: recognises dry-struct's class-
8
- # level `attribute :name, T` DSL and synthesises a reader on
9
- # the enclosing `Dry::Struct` subclass.
7
+ # ADR-16 Tier C worked plugin: recognises dry-struct's class-level `attribute :name, T` DSL and
8
+ # synthesises a reader on the enclosing `Dry::Struct` subclass.
10
9
  #
11
- # dry-struct's `Dry::Struct::ClassInterface#attribute` (per the
12
- # per-library survey, `lib/dry/struct/class_interface.rb:86-88`
13
- # in the upstream gem) is the textbook Tier C target — a
14
- # class-level DSL call enumerates a literal Symbol argument, and
15
- # `class_interface.rb:452-464` `class_eval`s a heredoc
16
- # interpolating that Symbol into a getter:
10
+ # dry-struct's `Dry::Struct::ClassInterface#attribute` (per the per-library survey,
11
+ # `lib/dry/struct/class_interface.rb:86-88` in the upstream gem) is the textbook Tier C target — a
12
+ # class-level DSL call enumerates a literal Symbol argument, and `class_interface.rb:452-464`
13
+ # `class_eval`s a heredoc interpolating that Symbol into a getter:
17
14
  #
18
15
  # class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
19
16
  # def #{key} # def city
@@ -21,44 +18,36 @@ module Rigor
21
18
  # end
22
19
  # RUBY
23
20
  #
24
- # The substrate replays the same contract statically. For
25
- # source like:
21
+ # The substrate replays the same contract statically. For source like:
26
22
  #
27
23
  # class Address < Dry::Struct
28
24
  # attribute :city, Types::String
29
25
  # attribute :country, Types::String
30
26
  # end
31
27
  #
32
- # the pre-pass scans the file, sees `attribute :city, ...`, and
33
- # synthesises `Address#city` as a SyntheticMethod the dispatcher
34
- # surfaces below RBS dispatch. Bare `address.city` calls in
35
- # other files then dispatch through the synthetic record rather
36
- # than falling through to `call.undefined-method`.
28
+ # the pre-pass scans the file, sees `attribute :city, ...`, and synthesises `Address#city` as a
29
+ # SyntheticMethod the dispatcher surfaces below RBS dispatch. Bare `address.city` calls in other
30
+ # files then dispatch through the synthetic record rather than falling through to
31
+ # `call.undefined-method`.
37
32
  #
38
33
  # ## Precision model (ADR-16 WD13 + ADR-18)
39
34
  #
40
- # The synthetic reader's return type is resolved via ADR-18's
41
- # `returns_from_arg:` fact lookup: the call-site's second argument
42
- # (`Types::String` etc.) is looked up through the `:dry_type_aliases`
43
- # fact published by `rigor-dry-types`, yielding `Nominal[String]`
44
- # for common cases. When the lookup misses (e.g. inline method-chain
45
- # argument whose chain-head isn't currently extracted), the row
35
+ # The synthetic reader's return type is resolved via ADR-18's `returns_from_arg:` fact lookup: the
36
+ # call-site's second argument (`Types::String` etc.) is looked up through the `:dry_type_aliases`
37
+ # fact published by `rigor-dry-types`, yielding `Nominal[String]` for common cases. When the lookup
38
+ # misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
46
39
  # falls back to `Dynamic[Top]` silently.
47
40
  #
48
41
  # ## Scope (slice 2c minimum)
49
42
  #
50
43
  # - Recognises `attribute :name, T` at class body top level.
51
- # - Recognises `attribute? :name, T` via a separate template
52
- # entry (omittable attribute; same reader name, `?` stripped).
53
- # - Synthesises **only the reader** — the other survey-listed
54
- # emit rows (`schema` key, `to_h` row, `[:key]` access, `.new`
55
- # kwarg) are not yet wired by the substrate (they require
56
- # either RBS-level shape synthesis or additional substrate
57
- # primitives). Slice 2c stops at the reader.
58
- # - Nested-block form (`attribute :details do ... end` minting
59
- # `Address::Details`) is out of scope for slice 2c; that
60
- # pattern needs Tier A + Tier C composition + const_set
61
- # emission. Deferred.
44
+ # - Recognises `attribute? :name, T` via a separate template entry (omittable attribute; same reader
45
+ # name, `?` stripped).
46
+ # - Synthesises **only the reader** — the other survey-listed emit rows (`schema` key, `to_h` row,
47
+ # `[:key]` access, `.new` kwarg) are not yet wired by the substrate (they require either RBS-level
48
+ # shape synthesis or additional substrate primitives). Slice 2c stops at the reader.
49
+ # - Nested-block form (`attribute :details do ... end` minting `Address::Details`) is out of scope
50
+ # for slice 2c; that pattern needs Tier A + Tier C composition + const_set emission. Deferred.
62
51
  class DryStruct < Rigor::Plugin::Base
63
52
  manifest(
64
53
  id: "dry-struct",
@@ -66,26 +55,20 @@ module Rigor
66
55
  description: "Recognises dry-struct `attribute :name, T` DSL via ADR-16 Tier C; " \
67
56
  "promotes the reader's return type through ADR-18's `returns_from_arg:` " \
68
57
  "by consuming `rigor-dry-types`'s `:dry_type_aliases` fact.",
69
- # ADR-9 consumption — the precision-promotion path
70
- # below uses `:dry_type_aliases` published by
71
- # `rigor-dry-types`. The fact is optional: when the
72
- # `rigor-dry-types` plugin isn't loaded, the
73
- # `returns_from_arg:` lookup misses and the synthetic
74
- # readers fall back to `Dynamic[Top]` (the pre-ADR-18
75
- # floor).
58
+ # ADR-9 consumption — the precision-promotion path below uses `:dry_type_aliases` published by
59
+ # `rigor-dry-types`. The fact is optional: when the `rigor-dry-types` plugin isn't loaded, the
60
+ # `returns_from_arg:` lookup misses and the synthetic readers fall back to `Dynamic[Top]` (the
61
+ # pre-ADR-18 floor).
76
62
  consumes: [{ plugin_id: "dry-types", name: :dry_type_aliases, optional: true }],
77
63
  heredoc_templates: [
78
64
  Rigor::Plugin::Macro::HeredocTemplate.new(
79
65
  receiver_constraint: "Dry::Struct",
80
66
  method_name: :attribute,
81
67
  symbol_arg_position: 0,
82
- # ADR-18 — the synthetic reader's return type comes
83
- # from the call site's second argument
84
- # (`Types::String` etc.), resolved through the
85
- # `:dry_type_aliases` fact. When the lookup misses
86
- # (e.g. inline `attribute :tag, Types::String.constrained(...)`,
87
- # whose receiver chain head isn't currently
88
- # extracted), the row falls back to Dynamic[Top].
68
+ # ADR-18 — the synthetic reader's return type comes from the call site's second argument
69
+ # (`Types::String` etc.), resolved through the `:dry_type_aliases` fact. When the lookup
70
+ # misses (e.g. inline `attribute :tag, Types::String.constrained(...)`, whose receiver chain
71
+ # head isn't currently extracted), the row falls back to Dynamic[Top].
89
72
  emit: [{
90
73
  name: "\#{name}",
91
74
  returns_from_arg: {
@@ -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-dry-struct` under `plugins:`. The
5
- # loader expects this `require` to side-effect a call to
6
- # `Rigor::Plugin.register`, which the body of
7
- # `lib/rigor/plugin/dry_struct.rb` performs at load time.
3
+ # Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-dry-struct` 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/dry_struct.rb` performs at load time.
8
6
  require_relative "rigor/plugin/dry_struct"
@@ -5,15 +5,12 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class DryTypes < Rigor::Plugin::Base
8
- # Walks project source for `module X; include Dry.Types(); end`
9
- # shapes and emits a `{ "<X>::<Alias>" => "<UnderlyingClass>" }`
10
- # alias table covering the dry-types canonical-shortcut names.
11
- # See {DryTypes} module-docstring for the floor / ceiling
12
- # scoping.
8
+ # Walks project source for `module X; include Dry.Types(); end` shapes and emits a
9
+ # `{ "<X>::<Alias>" => "<UnderlyingClass>" }` alias table covering the dry-types canonical-shortcut
10
+ # names. See {DryTypes} module-docstring for the floor / ceiling scoping.
13
11
  module AliasScanner
14
- # The canonical-shortcut names dry-types exposes through
15
- # `include Dry.Types()`. Mirrors `Dry::Types.type_keys`
16
- # from the upstream gem.
12
+ # The canonical-shortcut names dry-types exposes through `include Dry.Types()`. Mirrors
13
+ # `Dry::Types.type_keys` from the upstream gem.
17
14
  CANONICAL_ALIASES = {
18
15
  "String" => "String",
19
16
  "Integer" => "Integer",
@@ -32,29 +29,21 @@ module Rigor
32
29
  "Any" => "Object"
33
30
  }.freeze
34
31
 
35
- # Slice 2 — nested-category aliases. dry-types installs
36
- # four parallel coercion categories: `Coercible::*`
37
- # (everything-to-target coercion), `Strict::*` (no
38
- # coercion; raise if mismatch), `Params::*` (HTTP /
39
- # query-string-style coercion, used by Hanami / Roda /
40
- # dry-web in request handling), `JSON::*` (JSON-shape
41
- # coercion). Each category exposes the same set of names
42
- # as the canonical shortcuts above, plus a few additions
43
- # that are category-specific (`Params::Nil`,
44
- # `JSON::Symbol`). For Rigor's purposes the underlying
45
- # class is the same regardless of category — coercion
46
- # semantics are a runtime concern. We register every
47
- # `<module>::<Category>::<Name>` mapping the upstream gem
48
- # publishes so call-site references work uniformly.
32
+ # Slice 2 — nested-category aliases. dry-types installs four parallel coercion categories:
33
+ # `Coercible::*` (everything-to-target coercion), `Strict::*` (no coercion; raise if mismatch),
34
+ # `Params::*` (HTTP / query-string-style coercion, used by Hanami / Roda / dry-web in request
35
+ # handling), `JSON::*` (JSON-shape coercion). Each category exposes the same set of names as the
36
+ # canonical shortcuts above, plus a few additions that are category-specific (`Params::Nil`,
37
+ # `JSON::Symbol`). For Rigor's purposes the underlying class is the same regardless of category —
38
+ # coercion semantics are a runtime concern. We register every `<module>::<Category>::<Name>`
39
+ # mapping the upstream gem publishes so call-site references work uniformly.
49
40
  NESTED_CATEGORIES = %w[Coercible Strict Params JSON].freeze
50
41
  private_constant :NESTED_CATEGORIES
51
42
 
52
43
  module_function
53
44
 
54
- # @param paths [Array<String>] absolute paths to `.rb`
55
- # files the project's `paths:` resolves to.
56
- # @return [Hash{String => String}] frozen
57
- # `{aliased_name => underlying_class_name}` map. Empty
45
+ # @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
46
+ # @return [Hash{String => String}] frozen `{aliased_name => underlying_class_name}` map. Empty
58
47
  # when no `include Dry.Types()` declaration is found.
59
48
  def scan(paths:)
60
49
  results = paths.flat_map { |path| scan_file(path) }
@@ -64,16 +53,16 @@ module Rigor
64
53
  base = canonical_table(modules)
65
54
  results.each do |result|
66
55
  result[:compositions].each do |const_name, underlying|
67
- # Each result's compositions are scoped under that
68
- # result's enclosing module (`Types::Email`, etc.).
56
+ # Each result's compositions are scoped under that result's enclosing module
57
+ # (`Types::Email`, etc.).
69
58
  base["#{result[:module_name]}::#{const_name}"] ||= underlying
70
59
  end
71
60
  end
72
61
  base.freeze
73
62
  end
74
63
 
75
- # Populates the canonical-shortcut + nested-category
76
- # table (15 + 15 × 4 = 75 entries per alias module).
64
+ # Populates the canonical-shortcut + nested-category table (15 + 15 × 4 = 75 entries per alias
65
+ # module).
77
66
  def canonical_table(modules)
78
67
  modules.each_with_object({}) do |module_name, acc|
79
68
  CANONICAL_ALIASES.each do |alias_name, underlying|
@@ -96,22 +85,18 @@ module Rigor
96
85
  { module_name: module_info[:module_name], compositions: compositions }
97
86
  end
98
87
  rescue StandardError
99
- # Missing-file / parse failures degrade to "no
100
- # contribution from this file"; the plugin's
101
- # user-visible surface is the published fact, and
102
- # dropping unparseable files keeps the fact stable.
88
+ # Missing-file / parse failures degrade to "no contribution from this file"; the plugin's
89
+ # user-visible surface is the published fact, and dropping unparseable files keeps the fact
90
+ # stable.
103
91
  []
104
92
  end
105
93
  private_class_method :scan_file
106
94
 
107
- # Walks a Prism AST collecting alias-module info:
108
- # `{module_name:, body:}` for every `module X; include
109
- # Dry.Types(); end` shape. Tracks the enclosing module
110
- # chain so a nested `module App; module Types; include
111
- # Dry.Types(); end; end` publishes `"App::Types"` as the
112
- # alias scope. The `body:` field is the
113
- # `Prism::StatementsNode` (or nil) we re-walk later for
114
- # user-authored compositions (slice 3).
95
+ # Walks a Prism AST collecting alias-module info: `{module_name:, body:}` for every `module X;
96
+ # include Dry.Types(); …end` shape. Tracks the enclosing module chain so a nested `module App;
97
+ # module Types; include Dry.Types(); end; end` publishes `"App::Types"` as the alias scope. The
98
+ # `body:` field is the `Prism::StatementsNode` (or nil) we re-walk later for user-authored
99
+ # compositions (slice 3).
115
100
  def collect_alias_modules(node, qualified_prefix)
116
101
  return [] unless node.is_a?(Prism::Node)
117
102
 
@@ -127,9 +112,8 @@ module Rigor
127
112
  end
128
113
  current + children
129
114
  when Prism::ClassNode
130
- # Module-level declarations win; we don't recurse into
131
- # class bodies for `include Dry.Types()` because the
132
- # canonical pattern is module-level.
115
+ # Module-level declarations win; we don't recurse into class bodies for `include
116
+ # Dry.Types()` because the canonical pattern is module-level.
133
117
  []
134
118
  else
135
119
  node.compact_child_nodes.flat_map { |c| collect_alias_modules(c, qualified_prefix) }
@@ -137,31 +121,20 @@ module Rigor
137
121
  end
138
122
  private_class_method :collect_alias_modules
139
123
 
140
- # Slice 3 — user-authored composition recognition.
141
- # Walks the alias-module body for `Email =
142
- # String.constrained(...)` shapes. Each
143
- # `ConstantWriteNode` whose RHS is a method chain
144
- # rooted on a canonical-shortcut name (`String`,
145
- # `Integer`, …) — or on a nested-category form
146
- # (`Strict::String` etc.) — registers the LHS under
147
- # the canonical head's underlying class. Unions
148
- # (`String | Integer`) and intersections are skipped
149
- # (no single underlying class).
124
+ # Slice 3 — user-authored composition recognition. Walks the alias-module body for `Email =
125
+ # String.constrained(...)` shapes. Each `ConstantWriteNode` whose RHS is a method chain rooted on
126
+ # a canonical-shortcut name (`String`, `Integer`, …) — or on a nested-category form
127
+ # (`Strict::String` etc.) registers the LHS under the canonical head's underlying class. Unions
128
+ # (`String | Integer`) and intersections are skipped (no single underlying class).
150
129
  #
151
- # Slice 4 — transitive composition resolution. After
152
- # the direct (slice-3) pass collects compositions
153
- # whose RHS root is canonical, a second pass walks the
154
- # remaining `ConstantWriteNode`s for RHS shapes that
155
- # resolve THROUGH an already-published composition
156
- # e.g. `ManagerEmail = Email` (bare reference) or
157
- # `ManagerEmail = Email.constrained(min_size: 3)`
158
- # (method chain rooted on a composition LHS). Cycle
159
- # detection: `A = B; B = A` resolves neither (each
160
- # LHS's resolution walk sees itself in the visited
161
- # set and bails). Unknown references (`ManagerEmail =
162
- # NotAComposition`) silently drop — the user is free
163
- # to assign any constant to any other; the plugin only
164
- # publishes facts when the underlying class is known.
130
+ # Slice 4 — transitive composition resolution. After the direct (slice-3) pass collects
131
+ # compositions whose RHS root is canonical, a second pass walks the remaining
132
+ # `ConstantWriteNode`s for RHS shapes that resolve THROUGH an already-published composition
133
+ # e.g. `ManagerEmail = Email` (bare reference) or `ManagerEmail = Email.constrained(min_size: 3)`
134
+ # (method chain rooted on a composition LHS). Cycle detection: `A = B; B = A` resolves neither
135
+ # (each LHS's resolution walk sees itself in the visited set and bails). Unknown references
136
+ # (`ManagerEmail = NotAComposition`) silently drop — the user is free to assign any constant to
137
+ # any other; the plugin only publishes facts when the underlying class is known.
165
138
  def collect_compositions(body)
166
139
  return {} if body.nil?
167
140
 
@@ -188,14 +161,10 @@ module Rigor
188
161
  end
189
162
  private_class_method :collect_compositions
190
163
 
191
- # Slice-4 helper. Returns the un-resolved RHS root
192
- # constant name (not necessarily a CANONICAL_ALIASES
193
- # entry) for transitive resolution. Mirrors
194
- # {composition_head_canonical} but accepts any
195
- # `ConstantReadNode` / `ConstantPathNode` tail rather
196
- # than canonical-only. Declines on union / intersection
197
- # operators for the same single-underlying-class
198
- # reason.
164
+ # Slice-4 helper. Returns the un-resolved RHS root constant name (not necessarily a
165
+ # CANONICAL_ALIASES entry) for transitive resolution. Mirrors {composition_head_canonical} but
166
+ # accepts any `ConstantReadNode` / `ConstantPathNode` tail rather than canonical-only. Declines on
167
+ # union / intersection operators for the same single-underlying-class reason.
199
168
  def transitive_reference_name(node)
200
169
  case node
201
170
  when Prism::ConstantReadNode
@@ -211,14 +180,10 @@ module Rigor
211
180
  end
212
181
  private_class_method :transitive_reference_name
213
182
 
214
- # Slice-4 helper. Resolves `lhs`'s RHS through the
215
- # `direct` (slice-3) compositions table, chaining
216
- # through `ref_edges` for transitive references.
217
- # Returns the canonical underlying-class name (e.g.
218
- # `"String"`) or nil when no chain ends at a direct
219
- # composition. Cycles silently return nil — every step
220
- # adds the current lhs to `visited` and bails on
221
- # re-entry.
183
+ # Slice-4 helper. Resolves `lhs`'s RHS through the `direct` (slice-3) compositions table, chaining
184
+ # through `ref_edges` for transitive references. Returns the canonical underlying-class name
185
+ # (e.g. `"String"`) or nil when no chain ends at a direct composition. Cycles silently return nil
186
+ # every step adds the current lhs to `visited` and bails on re-entry.
222
187
  def resolve_transitive_ref(lhs, direct, ref_edges, visited:)
223
188
  return nil if visited.include?(lhs)
224
189
 
@@ -231,25 +196,19 @@ module Rigor
231
196
  end
232
197
  private_class_method :resolve_transitive_ref
233
198
 
234
- # Walks an RHS expression looking for the canonical
235
- # shortcut name at the root of a method chain. Returns
236
- # the canonical name (`"String"` etc.) or nil.
199
+ # Walks an RHS expression looking for the canonical shortcut name at the root of a method chain.
200
+ # Returns the canonical name (`"String"` etc.) or nil.
237
201
  #
238
202
  # Recognised shapes (recursively on `node.receiver`):
239
203
  #
240
- # - Bare `String` / `Integer` — `Prism::ConstantReadNode`
241
- # whose name is in `CANONICAL_ALIASES`.
242
- # - `Strict::String` / `Coercible::Integer` / etc. —
243
- # `Prism::ConstantPathNode` whose tail is in
204
+ # - Bare `String` / `Integer` — `Prism::ConstantReadNode` whose name is in `CANONICAL_ALIASES`.
205
+ # - `Strict::String` / `Coercible::Integer` / etc. — `Prism::ConstantPathNode` whose tail is in
244
206
  # `CANONICAL_ALIASES`.
245
- # - `String.constrained(...)` / `.optional` /
246
- # `.default(...)` / arbitrary single-arg method —
207
+ # - `String.constrained(...)` / `.optional` / `.default(...)` / arbitrary single-arg method —
247
208
  # recurse on the receiver.
248
209
  #
249
- # Declines on `String | Integer` (union, `:|`) and
250
- # `String & Foo` (intersection, `:&`) so the alias
251
- # table doesn't claim a single underlying class for
252
- # a multi-class composition.
210
+ # Declines on `String | Integer` (union, `:|`) and `String & Foo` (intersection, `:&`) so the
211
+ # alias table doesn't claim a single underlying class for a multi-class composition.
253
212
  def composition_head_canonical(node)
254
213
  case node
255
214
  when Prism::ConstantReadNode
@@ -266,12 +225,10 @@ module Rigor
266
225
  end
267
226
  private_class_method :composition_head_canonical
268
227
 
269
- # `include Dry.Types()` at the top of the module body is the
270
- # canonical alias declaration. We accept the call anywhere
271
- # in the body (some projects guard it with a `if defined?`
272
- # check). The argument list must be empty (or a kwargs-only
273
- # `default: :nominal` style accepted by upstream; we treat
274
- # both as "alias-installing").
228
+ # `include Dry.Types()` at the top of the module body is the canonical alias declaration. We
229
+ # accept the call anywhere in the body (some projects guard it with a `if defined?` check). The
230
+ # argument list must be empty (or a kwargs-only `default: :nominal` style accepted by upstream;
231
+ # we treat both as "alias-installing").
275
232
  def contains_dry_types_include?(body)
276
233
  return false if body.nil?
277
234
 
@@ -295,10 +252,9 @@ module Rigor
295
252
  end
296
253
  private_class_method :tree_walk
297
254
 
298
- # Matches `include Dry.Types()` (with or without kwargs).
299
- # The receiver of the include call MUST be implicit
300
- # (i.e., called on `self`), and the argument MUST be a
301
- # method call on the `Dry` constant naming `Types`.
255
+ # Matches `include Dry.Types()` (with or without kwargs). The receiver of the include call MUST
256
+ # be implicit (i.e., called on `self`), and the argument MUST be a method call on the `Dry`
257
+ # constant naming `Types`.
302
258
  def include_call_targeting_dry_types?(node)
303
259
  return false unless node.is_a?(Prism::CallNode)
304
260
  return false unless node.name == :include && node.receiver.nil?
@@ -319,11 +275,9 @@ module Rigor
319
275
  end
320
276
  private_class_method :dry_types_call?
321
277
 
322
- # Resolves a `Prism::ConstantPathNode` /
323
- # `Prism::ConstantReadNode` chain to its dot-separated
324
- # name (e.g. `"App::Types"`). Returns nil for the
325
- # dynamic-prefix shape so the scanner treats those as
326
- # opaque rather than guessing.
278
+ # Resolves a `Prism::ConstantPathNode` / `Prism::ConstantReadNode` chain to its dot-separated
279
+ # name (e.g. `"App::Types"`). Returns nil for the dynamic-prefix shape so the scanner treats
280
+ # those as opaque rather than guessing.
327
281
  def qualified_name_for(node)
328
282
  case node
329
283
  when Prism::ConstantReadNode then node.name.to_s