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
@@ -17,56 +17,44 @@ module Rigor
17
17
  # include Dry.Types()
18
18
  # end
19
19
  #
20
- # and publishes the resulting `{aliased_name => underlying_class}`
21
- # table as the `:dry_type_aliases` cross-plugin fact (ADR-9).
22
- # Other dry-rb adapter plugins consume this fact:
20
+ # and publishes the resulting `{aliased_name => underlying_class}` table as the `:dry_type_aliases`
21
+ # cross-plugin fact (ADR-9). Other dry-rb adapter plugins consume this fact:
23
22
  #
24
- # - `rigor-dry-struct` reads it so `attribute :city, Types::String`
25
- # promotes `address.city` from `Dynamic[Top]` to `Nominal[String]`
26
- # via ADR-18's `returns_from_arg:` fact lookup.
27
- # - `rigor-dry-validation` / `rigor-dry-schema` read it for
28
- # per-key type recognition in `schema { … }` / `params { … }`
29
- # blocks (separate plugin slice).
23
+ # - `rigor-dry-struct` reads it so `attribute :city, Types::String` promotes `address.city` from
24
+ # `Dynamic[Top]` to `Nominal[String]` via ADR-18's `returns_from_arg:` fact lookup.
25
+ # - `rigor-dry-validation` / `rigor-dry-schema` read it for per-key type recognition in `schema { … }`
26
+ # / `params { … }` blocks (separate plugin slice).
30
27
  #
31
28
  # ## Floor / ceiling (slice 1)
32
29
  #
33
30
  # Slice 1 ships the **floor**:
34
31
  #
35
- # - Recognises `module X; include Dry.Types(); end` for any
36
- # constant module name `X` (commonly `Types`, sometimes
37
- # `MyTypes` / `AppTypes`).
38
- # - Maps the **basic** dry-types constants: `String`, `Integer`,
39
- # `Float`, `Decimal`, `Symbol`, `Bool`, `True`, `False`, `Nil`,
40
- # `Date`, `DateTime`, `Time`, `Hash`, `Array`, `Any`.
41
- # - Publishes the table as `{ "<Module>::<Alias>" =>
42
- # "<UnderlyingClass>" }` so consumers can match on the
43
- # qualified constant name they see in source.
32
+ # - Recognises `module X; include Dry.Types(); end` for any constant module name `X` (commonly
33
+ # `Types`, sometimes `MyTypes` / `AppTypes`).
34
+ # - Maps the **basic** dry-types constants: `String`, `Integer`, `Float`, `Decimal`, `Symbol`, `Bool`,
35
+ # `True`, `False`, `Nil`, `Date`, `DateTime`, `Time`, `Hash`, `Array`, `Any`.
36
+ # - Publishes the table as `{ "<Module>::<Alias>" => "<UnderlyingClass>" }` so consumers can match on
37
+ # the qualified constant name they see in source.
44
38
  #
45
39
  # Implemented beyond the floor:
46
40
  #
47
- # - Nested-namespace aliases (`Types::Coercible::Integer`,
48
- # `Types::Strict::Symbol`, `Types::Params::Bool`,
49
- # `Types::JSON::Date`) the four coercion categories each
50
- # map to the same underlying class as their canonical shortcut.
51
- # - User-authored compositions (`Email = Types::String.constrained(...)`,
52
- # transitive resolution through composition chains) — the alias
53
- # surface extends beyond the 15 canonical names.
41
+ # - Nested-namespace aliases (`Types::Coercible::Integer`, `Types::Strict::Symbol`,
42
+ # `Types::Params::Bool`, `Types::JSON::Date`) — the four coercion categories each map to the same
43
+ # underlying class as their canonical shortcut.
44
+ # - User-authored compositions (`Email = Types::String.constrained(...)`, transitive resolution
45
+ # through composition chains) the alias surface extends beyond the 15 canonical names.
54
46
  #
55
47
  # Deferred:
56
48
  #
57
- # - `dry-types.unknown-alias` / `dry-types.alias-shadow`
58
- # diagnostics when downstream code references a name that
59
- # wasn't published.
49
+ # - `dry-types.unknown-alias` / `dry-types.alias-shadow` diagnostics when downstream code references
50
+ # a name that wasn't published.
60
51
  #
61
52
  # ## Why no `diagnostics_for_file` at the floor?
62
53
  #
63
- # The plugin's user-visible value at slice 1 is the published
64
- # fact every downstream uplift (precision promotion in
65
- # `address.city`, schema-key recognition in `rigor-dry-validation`)
66
- # consumes the fact rather than the plugin itself emitting
67
- # diagnostics. The diagnostics surface lands when the
68
- # `dry-types.*` rule family becomes load-bearing for
69
- # demand-driven cases.
54
+ # The plugin's user-visible value at slice 1 is the published fact — every downstream uplift
55
+ # (precision promotion in `address.city`, schema-key recognition in `rigor-dry-validation`) consumes
56
+ # the fact rather than the plugin itself emitting diagnostics. The diagnostics surface lands when the
57
+ # `dry-types.*` rule family becomes load-bearing for demand-driven cases.
70
58
  class DryTypes < Rigor::Plugin::Base
71
59
  manifest(
72
60
  id: "dry-types",
@@ -75,12 +63,9 @@ module Rigor
75
63
  produces: [:dry_type_aliases]
76
64
  )
77
65
 
78
- # Walks every project file once during `prepare(services)` to
79
- # build the alias table, then publishes via the ADR-9 fact
80
- # store. The walk is bounded by the configured `paths:`
81
- # surface; each file's parse error degrades to "no
82
- # contribution" without polluting the user-visible
83
- # diagnostic stream.
66
+ # Walks every project file once during `prepare(services)` to build the alias table, then publishes
67
+ # via the ADR-9 fact store. The walk is bounded by the configured `paths:` surface; each file's
68
+ # parse error degrades to "no contribution" without polluting the user-visible diagnostic stream.
84
69
  def prepare(services)
85
70
  aliases = AliasScanner.scan(paths: scannable_paths(services))
86
71
  return if aliases.empty?
@@ -98,11 +83,9 @@ module Rigor
98
83
 
99
84
  private
100
85
 
101
- # Resolves the project's `paths:` to a flat list of `.rb`
102
- # files the scanner walks. Mirrors `Analysis::Runner`'s
103
- # `expand_paths` floor; we don't need the runner's full
104
- # exclude/sort surface because the alias table is a
105
- # union — any duplicate scan is a no-op.
86
+ # Resolves the project's `paths:` to a flat list of `.rb` files the scanner walks. Mirrors
87
+ # `Analysis::Runner`'s `expand_paths` floor; we don't need the runner's full exclude/sort surface
88
+ # because the alias table is a union — any duplicate scan is a no-op.
106
89
  def scannable_paths(services)
107
90
  @scannable_paths ||= services.configuration.paths.flat_map do |entry|
108
91
  if File.directory?(entry)
@@ -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-types` 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_types.rb` performs at load time.
3
+ # Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-dry-types` 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_types.rb` performs at load time.
8
6
  require_relative "rigor/plugin/dry_types"
@@ -5,18 +5,15 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class DryValidation < Rigor::Plugin::Base
8
- # Walks project source for `class T < Dry::Validation::Contract`
9
- # subclasses and returns the contract class FQN set.
8
+ # Walks project source for `class T < Dry::Validation::Contract` subclasses and returns the
9
+ # contract class FQN set.
10
10
  #
11
- # Recognition tightness: the superclass match accepts EITHER
12
- # the fully-qualified `Dry::Validation::Contract` (3-segment
13
- # path) OR the lexical-nested `Validation::Contract`
14
- # (2-segment path, when the class body lives inside
15
- # `module Dry`). The bare `< Contract` form (1-segment)
16
- # is NOT recognised — too ambiguous; users who deeply nest
17
- # under `Dry::Validation` should use the explicit form.
18
- # Unrelated `< MyApp::Validation::Contract` shapes with the
19
- # same tail do NOT register.
11
+ # Recognition tightness: the superclass match accepts EITHER the fully-qualified
12
+ # `Dry::Validation::Contract` (3-segment path) OR the lexical-nested `Validation::Contract`
13
+ # (2-segment path, when the class body lives inside `module Dry`). The bare `< Contract` form
14
+ # (1-segment) is NOT recognised too ambiguous; users who deeply nest under `Dry::Validation`
15
+ # should use the explicit form. Unrelated `< MyApp::Validation::Contract` shapes with the same tail
16
+ # do NOT register.
20
17
  module ContractScanner
21
18
  CONTRACT_FULL_PATH = %w[Dry Validation Contract].freeze
22
19
  CONTRACT_LEXICAL_DRY_PATH = %w[Validation Contract].freeze
@@ -24,10 +21,8 @@ module Rigor
24
21
 
25
22
  module_function
26
23
 
27
- # @param paths [Array<String>] absolute paths to `.rb`
28
- # files the project's `paths:` resolves to.
29
- # @return [Array<String>] frozen, sorted list of
30
- # recognized contract class FQNs (e.g.
24
+ # @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
25
+ # @return [Array<String>] frozen, sorted list of recognized contract class FQNs (e.g.
31
26
  # `["App::NewUserContract", "Types::EmailContract"]`).
32
27
  def scan(paths:)
33
28
  contracts = []
@@ -77,11 +72,9 @@ module Rigor
77
72
  end
78
73
  private_class_method :collect_module_node
79
74
 
80
- # Matches superclasses whose constant chain is EXACTLY
81
- # `Dry::Validation::Contract` (full path) OR EXACTLY
82
- # `Validation::Contract` (lexical-Dry path). Other shapes
83
- # — including same-tail-but-different-root chains and
84
- # the ambiguous bare `Contract` — do not match.
75
+ # Matches superclasses whose constant chain is EXACTLY `Dry::Validation::Contract` (full path) OR
76
+ # EXACTLY `Validation::Contract` (lexical-Dry path). Other shapes — including
77
+ # same-tail-but-different-root chains and the ambiguous bare `Contract` do not match.
85
78
  def contract_subclass?(class_node)
86
79
  superclass = class_node.superclass
87
80
  return false if superclass.nil?
@@ -15,31 +15,23 @@ module Rigor
15
15
  #
16
16
  # Slice 1 floor:
17
17
  #
18
- # - Walks the project for `class T < Dry::Validation::Contract`
19
- # subclasses and publishes the resulting set of contract
20
- # class FQNs as the `:dry_validation_contracts` ADR-9
21
- # cross-plugin fact.
22
- # - Ships an RBS overlay (`sig/dry_validation.rbs`) typing
23
- # `Dry::Validation::Contract#call` (returns Result) and
24
- # `Dry::Validation::Result#{success?, failure?, to_h}`.
25
- # The manifest's `signature_paths: ["sig"]` auto-contributes
26
- # the overlay (ADR-25) — no project-side wiring needed.
18
+ # - Walks the project for `class T < Dry::Validation::Contract` subclasses and publishes the
19
+ # resulting set of contract class FQNs as the `:dry_validation_contracts` ADR-9 cross-plugin fact.
20
+ # - Ships an RBS overlay (`sig/dry_validation.rbs`) typing `Dry::Validation::Contract#call` (returns
21
+ # Result) and `Dry::Validation::Result#{success?, failure?, to_h}`. The manifest's
22
+ # `signature_paths: ["sig"]` auto-contributes the overlay (ADR-25) — no project-side wiring needed.
27
23
  #
28
24
  # Slice 2 (deferred, per design note):
29
25
  #
30
- # - Integrate with `:dry_schema_table` (published by
31
- # `rigor-dry-schema`) so the `params { ... }` block inside a
32
- # Contract contributes a typed `result.to_h` shape per the
33
- # schema. Until this lands, `result.to_h` types as
34
- # `Hash[Symbol, untyped]` (the generic RBS overlay shape).
26
+ # - Integrate with `:dry_schema_table` (published by `rigor-dry-schema`) so the `params { ... }`
27
+ # block inside a Contract contributes a typed `result.to_h` shape per the schema. Until this lands,
28
+ # `result.to_h` types as `Hash[Symbol, untyped]` (the generic RBS overlay shape).
35
29
  #
36
- # Slice 3 (deferred): `json { ... }` adapter parity with
37
- # `params { ... }`. Same shape as slice 2.
30
+ # Slice 3 (deferred): `json { ... }` adapter parity with `params { ... }`. Same shape as slice 2.
38
31
  #
39
- # No ADR-3 amendment is needed for the validation surface
40
- # itself; `Dry::Validation::Result` is a generic class, not a
41
- # sum type (the `success?` / `failure?` predicates narrow via
42
- # existing bool flow facts).
32
+ # No ADR-3 amendment is needed for the validation surface itself; `Dry::Validation::Result` is a
33
+ # generic class, not a sum type (the `success?` / `failure?` predicates narrow via existing bool flow
34
+ # facts).
43
35
  class DryValidation < Rigor::Plugin::Base
44
36
  manifest(
45
37
  id: "dry-validation",
@@ -47,9 +39,8 @@ module Rigor
47
39
  description: "Recognises `class T < Dry::Validation::Contract` subclasses and " \
48
40
  "publishes the contract FQN set.",
49
41
  produces: [:dry_validation_contracts],
50
- # Auto-contribute the bundled RBS overlay (Contract#call -> Result,
51
- # Result#success?/#to_h/...) per ADR-25, so no project-side
52
- # signature_paths wiring is needed.
42
+ # Auto-contribute the bundled RBS overlay (Contract#call -> Result, Result#success?/#to_h/...)
43
+ # per ADR-25, so no project-side signature_paths wiring is needed.
53
44
  signature_paths: ["sig"]
54
45
  )
55
46
 
@@ -1,7 +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-validation` under `plugins:`.
5
- # Side-effects a `Rigor::Plugin.register` call via the
3
+ # Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists `rigor-dry-validation`
4
+ # under `plugins:`. Side-effects a `Rigor::Plugin.register` call via the
6
5
  # `lib/rigor/plugin/dry_validation.rb` class body.
7
6
  require_relative "rigor/plugin/dry_validation"
@@ -7,34 +7,27 @@ require "rigor/source/literals"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class Factorybot < Rigor::Plugin::Base
10
- # Per-file walker — visits every `FactoryBot.<entry>(...)`
11
- # call (and the `FactoryGirl` legacy alias) and validates
12
- # the factory name + the keyword-argument attribute keys
13
- # against the per-run {FactoryIndex}.
10
+ # Per-file walker — visits every `FactoryBot.<entry>(...)` call (and the `FactoryGirl` legacy alias)
11
+ # and validates the factory name + the keyword-argument attribute keys against the per-run
12
+ # {FactoryIndex}.
14
13
  #
15
- # Recognised entry methods cover the canonical create /
16
- # build / build_stubbed / attributes_for family; the same
17
- # validation applies to every entry (the runtime semantics
18
- # differ — one persists, one returns a hash — but the
19
- # call-site shape is identical from the static check's
20
- # perspective).
14
+ # Recognised entry methods cover the canonical create / build / build_stubbed / attributes_for
15
+ # family; the same validation applies to every entry (the runtime semantics differ — one persists,
16
+ # one returns a hash but the call-site shape is identical from the static check's perspective).
21
17
  module Analyzer
22
18
  ENTRY_METHODS = %i[create build build_stubbed attributes_for create_list build_list build_stubbed_list].freeze
23
19
 
24
- # One violation. Carries its own `location` because a single
25
- # entry call yields diagnostics at different positions the
26
- # matcher/method name (`message_loc`) for factory-call /
27
- # unknown-factory, and the attribute key node for
28
- # unknown-attribute. The caller positions each via
29
- # `Plugin::Base#diagnostic(node, location:)`.
20
+ # One violation. Carries its own `location` because a single entry call yields diagnostics at
21
+ # different positions the matcher/method name (`message_loc`) for factory-call /
22
+ # unknown-factory, and the attribute key node for unknown-attribute. The caller positions each
23
+ # via `Plugin::Base#diagnostic(node, location:)`.
30
24
  Violation = Data.define(:location, :message, :severity, :rule)
31
25
 
32
26
  module_function
33
27
 
34
- # The violations for a single entry call (`FactoryBot.create(...)`
35
- # etc.), or `[]` when the node is not an entry call or its first
36
- # argument is not a literal factory name. ADR-37: the engine owns
37
- # the walk.
28
+ # The violations for a single entry call (`FactoryBot.create(...)` etc.), or `[]` when the node
29
+ # is not an entry call or its first argument is not a literal factory name. ADR-37: the engine
30
+ # owns the walk.
38
31
  #
39
32
  # @param call_node [Prism::Node]
40
33
  # @param factory_index [FactoryIndex]
@@ -80,20 +73,14 @@ module Rigor
80
73
  [factory_call_violation(call_node, factory_name, entry)]
81
74
  end
82
75
 
83
- # The keyword-argument attribute keys come from the
84
- # trailing `Prism::KeywordHashNode` (Ruby's
85
- # `name: "value"` syntax). Each AssocNode whose key is
86
- # a `Prism::SymbolNode` is treated as a literal
87
- # attribute reference.
76
+ # The keyword-argument attribute keys come from the trailing `Prism::KeywordHashNode` (Ruby's
77
+ # `name: "value"` syntax). Each AssocNode whose key is a `Prism::SymbolNode` is treated as a
78
+ # literal attribute reference.
88
79
  #
89
- # When `model_index` (the cross-plugin `:model_index`
90
- # fact published by rigor-activerecord) is present,
91
- # the effective accepted key set is the UNION of the
92
- # factory's declared attributes plus the corresponding
93
- # model's columns. FactoryBot's runtime accepts any AR
94
- # attribute regardless of whether the factory declared
95
- # it, so the cross-check broadens the acceptance
96
- # accordingly.
80
+ # When `model_index` (the cross-plugin `:model_index` fact published by rigor-activerecord) is
81
+ # present, the effective accepted key set is the UNION of the factory's declared attributes plus
82
+ # the corresponding model's columns. FactoryBot's runtime accepts any AR attribute regardless of
83
+ # whether the factory declared it, so the cross-check broadens the acceptance accordingly.
97
84
  def unknown_attribute_violations(call_node, entry, model_index)
98
85
  accepted_keys, suggestion_dictionary = effective_keys(entry, model_index)
99
86
  attr_spell_checker = DidYouMean::SpellChecker.new(dictionary: suggestion_dictionary)
@@ -119,10 +106,8 @@ module Rigor
119
106
  [(factory_keys + model_columns).uniq.freeze, (factory_keys + model_columns).uniq.freeze]
120
107
  end
121
108
 
122
- # Convention: `:user` → `User`, `:order_item` →
123
- # `OrderItem`. Mirrors rigor-actionpack Phase 1's
124
- # convention; namespaced models (`:admin_user` →
125
- # `Admin::User`) are deferred.
109
+ # Convention: `:user` → `User`, `:order_item` → `OrderItem`. Mirrors rigor-actionpack Phase 1's
110
+ # convention; namespaced models (`:admin_user` `Admin::User`) are deferred.
126
111
  def model_class_for(factory_name)
127
112
  factory_name.to_s.split("_").map(&:capitalize).join
128
113
  end
@@ -8,13 +8,10 @@ require_relative "factory_index"
8
8
  module Rigor
9
9
  module Plugin
10
10
  class Factorybot < Rigor::Plugin::Base
11
- # Walks `factory_search_paths` and parses each `.rb` file
12
- # into a {FactoryIndex}. The search-path list contains
13
- # both directory paths (recursively walked) and direct
14
- # file paths (read once); the typical default
15
- # `["spec/factories", "spec/factories.rb"]` covers both
16
- # the multi-file convention RSpec uses today and the
17
- # legacy single-file form.
11
+ # Walks `factory_search_paths` and parses each `.rb` file into a {FactoryIndex}. The search-path
12
+ # list contains both directory paths (recursively walked) and direct file paths (read once); the
13
+ # typical default `["spec/factories", "spec/factories.rb"]` covers both the multi-file convention
14
+ # RSpec uses today and the legacy single-file form.
18
15
  #
19
16
  # The walker recognises:
20
17
  #
@@ -22,21 +19,16 @@ module Rigor
22
19
  # - `factory "users" do ... end` — string form
23
20
  # - `factory :users, aliases: [:author] do ... end` — alias form
24
21
  #
25
- # Inside a factory block, attribute declarations come in
26
- # several shapes. Only literal-name forms are recognised
27
- # (Symbol arg / String arg):
22
+ # Inside a factory block, attribute declarations come in several shapes. Only literal-name forms
23
+ # are recognised (Symbol arg / String arg):
28
24
  #
29
- # - `name { "Alice" }` — implicit attribute via
30
- # `method_missing` with a block (FactoryBot's modern
25
+ # - `name { "Alice" }` — implicit attribute via `method_missing` with a block (FactoryBot's modern
31
26
  # syntax)
32
- # - `name "Alice"` — implicit attribute via
33
- # `method_missing` with a positional argument (legacy)
34
- # - `add_attribute(:name) { "Alice" }` — the explicit
35
- # form
27
+ # - `name "Alice"` — implicit attribute via `method_missing` with a positional argument (legacy)
28
+ # - `add_attribute(:name) { "Alice" }` the explicit form
36
29
  #
37
- # Sequences (`sequence(:email) { ... }`), associations
38
- # (`association :author`), traits, and parent / child
39
- # relationships are deferred to later slices.
30
+ # Sequences (`sequence(:email) { ... }`), associations (`association :author`), traits, and parent
31
+ # / child relationships are deferred to later slices.
40
32
  class FactoryDiscoverer
41
33
  def initialize(io_boundary:, search_paths:)
42
34
  @io_boundary = io_boundary
@@ -83,12 +75,10 @@ module Rigor
83
75
  nil
84
76
  end
85
77
 
86
- # Yields `(factory_name, [attribute_names])` for every
87
- # `factory :name do ... end` call discovered in the
88
- # subtree. The walker recurses into top-level wrapping
89
- # blocks (`FactoryBot.define do ... end`) and into
90
- # arbitrary container nodes so factories inside `module`
91
- # / `class` blocks are still picked up.
78
+ # Yields `(factory_name, [attribute_names])` for every `factory :name do ... end` call
79
+ # discovered in the subtree. The walker recurses into top-level wrapping blocks
80
+ # (`FactoryBot.define do ... end`) and into arbitrary container nodes so factories inside
81
+ # `module` / `class` blocks are still picked up.
92
82
  def walk_for_factories(node, &)
93
83
  return unless node.is_a?(Prism::Node)
94
84
 
@@ -116,26 +106,20 @@ module Rigor
116
106
  Rigor::Source::Literals.symbol_or_string_name(call_node.arguments&.arguments&.first)
117
107
  end
118
108
 
119
- # Resolves the model class name for the factory.
120
- # Three sources, in priority order:
109
+ # Resolves the model class name for the factory. Three sources, in priority order:
121
110
  #
122
- # 1. Explicit `class: <Const>` keyword arg —
123
- # ConstantReadNode / ConstantPathNode value.
124
- # 2. Explicit `class: "<name>"` keyword arg — String
125
- # value (supports `"Admin::User"`).
126
- # 3. Inflected from the factory name — `:user` →
127
- # `"User"`, `:admin_user` → `"AdminUser"`. The
128
- # factory name is already singular by FactoryBot
129
- # convention, so we only need camelization.
111
+ # 1. Explicit `class: <Const>` keyword arg — ConstantReadNode / ConstantPathNode value.
112
+ # 2. Explicit `class: "<name>"` keyword arg — String value (supports `"Admin::User"`).
113
+ # 3. Inflected from the factory name — `:user` → `"User"`, `:admin_user` `"AdminUser"`. The
114
+ # factory name is already singular by FactoryBot convention, so we only need camelization.
130
115
  #
131
116
  # Returns a String (the canonical class name).
132
117
  def factory_model_class(call_node, factory_name)
133
118
  explicit = explicit_class_option(call_node)
134
119
  return explicit if explicit
135
120
 
136
- # ADR-39: the real ActiveSupport::Inflector camelizes the factory
137
- # name to its class (`admin_user` → `AdminUser`,
138
- # `admin/user` → `Admin::User`), so the model-class fallback
121
+ # ADR-39: the real ActiveSupport::Inflector camelizes the factory name to its class
122
+ # (`admin_user` → `AdminUser`, `admin/user` → `Admin::User`), so the model-class fallback
139
123
  # matches Rails' real convention rather than an approximation.
140
124
  Rigor::Plugin::Inflector.camelize(factory_name)
141
125
  end
@@ -188,10 +172,9 @@ module Rigor
188
172
  attributes
189
173
  end
190
174
 
191
- # Walks the block body collecting attribute names. Only
192
- # top-level statements are examined attributes inside
193
- # `trait :admin do ... end` or other nested blocks are
194
- # not collected (traits deferred to a follow-up).
175
+ # Walks the block body collecting attribute names. Only top-level statements are examined —
176
+ # attributes inside `trait :admin do ... end` or other nested blocks are not collected (traits
177
+ # deferred to a follow-up).
195
178
  def collect_attributes_from(node, accumulator)
196
179
  return unless node.is_a?(Prism::Node)
197
180
 
@@ -204,15 +187,14 @@ module Rigor
204
187
 
205
188
  def record_attribute(node, accumulator)
206
189
  return unless node.is_a?(Prism::CallNode) && node.receiver.nil?
207
- # Skip association / sequence / trait / framework
208
- # methods — only plain attribute declarations are recorded.
190
+ # Skip association / sequence / trait / framework methods — only plain attribute declarations
191
+ # are recorded.
209
192
  return if SKIPPED_METHODS.include?(node.name)
210
193
 
211
194
  name = if node.name == :add_attribute
212
195
  literal_name_arg(node)
213
196
  else
214
- # method_missing form: the call's method
215
- # name IS the attribute name.
197
+ # method_missing form: the call's method name IS the attribute name.
216
198
  node.name.to_s
217
199
  end
218
200
  accumulator << name if name
@@ -3,26 +3,22 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Factorybot < Rigor::Plugin::Base
6
- # Per-run frozen index of discovered FactoryBot factories
7
- # and the attribute keys each declares. Indexes only
8
- # **literal symbol/string** factory names + **literal
9
- # symbol** attribute names; sequences, parent/child
10
- # relationships, traits, and dynamically-named factories
11
- # are deferred to follow-up slices.
6
+ # Per-run frozen index of discovered FactoryBot factories and the attribute keys each declares.
7
+ # Indexes only **literal symbol/string** factory names + **literal symbol** attribute names;
8
+ # sequences, parent/child relationships, traits, and dynamically-named factories are deferred to
9
+ # follow-up slices.
12
10
  #
13
- # Each entry carries a `model_class` — the inferred or
14
- # explicit class the factory builds. Resolved from:
11
+ # Each entry carries a `model_class` — the inferred or explicit class the factory builds. Resolved
12
+ # from:
15
13
  #
16
- # 1. An explicit `factory :user, class: User do`
17
- # keyword option (ConstantReadNode / ConstantPathNode
18
- # value).
19
- # 2. An explicit `factory :user, class: "User"` keyword
20
- # option (String value, supports `"Admin::User"`).
21
- # 3. Fallback: inflected from the factory name —
22
- # `:user` → `"User"`, `:admin_user` → `"AdminUser"`.
14
+ # 1. An explicit `factory :user, class: User do` keyword option (ConstantReadNode /
15
+ # ConstantPathNode value).
16
+ # 2. An explicit `factory :user, class: "User"` keyword option (String value, supports
17
+ # `"Admin::User"`).
18
+ # 3. Fallback: inflected from the factory name — `:user` → `"User"`, `:admin_user`
19
+ # `"AdminUser"`.
23
20
  #
24
- # The structure is intentionally flat: one entry per
25
- # factory name. Attribute lists deduplicate.
21
+ # The structure is intentionally flat: one entry per factory name. Attribute lists deduplicate.
26
22
  class FactoryIndex
27
23
  Entry = Data.define(:name, :attribute_names, :model_class)
28
24