rigortype 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (433) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/core_overlay/csv.rbs +28 -0
  4. data/data/core_overlay/psych.rbs +22 -0
  5. data/docs/handbook/01-getting-started.md +9 -1
  6. data/docs/handbook/02-everyday-types.md +4 -1
  7. data/docs/handbook/08-understanding-errors.md +3 -3
  8. data/docs/manual/01-installation.md +1 -0
  9. data/docs/manual/02-cli-reference.md +16 -7
  10. data/docs/manual/04-diagnostics.md +7 -4
  11. data/docs/manual/06-baseline.md +5 -2
  12. data/docs/manual/07-plugins.md +1 -1
  13. data/docs/manual/14-rails-quickstart.md +4 -2
  14. data/docs/manual/15-type-protection-coverage.md +27 -4
  15. data/docs/manual/plugins/rigor-actionpack.md +1 -1
  16. data/docs/manual/plugins/rigor-activerecord.md +12 -5
  17. data/docs/manual/plugins/rigor-rails-routes.md +11 -0
  18. data/lib/rigor/analysis/baseline.rb +56 -96
  19. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  20. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  21. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  22. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  23. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  24. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  25. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  26. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  27. data/lib/rigor/analysis/check_rules.rb +174 -274
  28. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  29. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  30. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  31. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  32. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  33. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  34. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  35. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  36. data/lib/rigor/analysis/diagnostic.rb +48 -75
  37. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  38. data/lib/rigor/analysis/fact_store.rb +12 -22
  39. data/lib/rigor/analysis/incremental.rb +47 -63
  40. data/lib/rigor/analysis/incremental_session.rb +74 -100
  41. data/lib/rigor/analysis/project_scan.rb +12 -23
  42. data/lib/rigor/analysis/result.rb +3 -5
  43. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  44. data/lib/rigor/analysis/run_stats.rb +38 -61
  45. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  48. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  49. data/lib/rigor/analysis/runner.rb +220 -333
  50. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  51. data/lib/rigor/analysis/worker_session.rb +64 -102
  52. data/lib/rigor/ast/type_node.rb +6 -8
  53. data/lib/rigor/ast.rb +6 -10
  54. data/lib/rigor/bleeding_edge.rb +27 -35
  55. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  56. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  57. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  58. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  59. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  60. data/lib/rigor/cache/descriptor.rb +58 -94
  61. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  62. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  63. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  64. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  65. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  66. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  67. data/lib/rigor/cache/rbs_environment.rb +11 -19
  68. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  69. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  70. data/lib/rigor/cache/store.rb +248 -186
  71. data/lib/rigor/cli/annotate_command.rb +51 -88
  72. data/lib/rigor/cli/baseline_command.rb +10 -17
  73. data/lib/rigor/cli/check_command.rb +121 -199
  74. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  75. data/lib/rigor/cli/ci_detector.rb +9 -14
  76. data/lib/rigor/cli/command.rb +8 -14
  77. data/lib/rigor/cli/coverage_command.rb +62 -48
  78. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  79. data/lib/rigor/cli/coverage_report.rb +0 -2
  80. data/lib/rigor/cli/coverage_scan.rb +7 -11
  81. data/lib/rigor/cli/diagnostic_formats.rb +46 -62
  82. data/lib/rigor/cli/diff_command.rb +10 -20
  83. data/lib/rigor/cli/docs_command.rb +26 -40
  84. data/lib/rigor/cli/doctor_command.rb +8 -9
  85. data/lib/rigor/cli/explain_command.rb +4 -7
  86. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  87. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  88. data/lib/rigor/cli/lsp_command.rb +11 -19
  89. data/lib/rigor/cli/mcp_command.rb +4 -6
  90. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  91. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  92. data/lib/rigor/cli/options.rb +11 -19
  93. data/lib/rigor/cli/plugin_command.rb +18 -30
  94. data/lib/rigor/cli/plugins_command.rb +29 -51
  95. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  96. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  97. data/lib/rigor/cli/protection_fork_scan.rb +55 -0
  98. data/lib/rigor/cli/protection_renderer.rb +5 -7
  99. data/lib/rigor/cli/protection_report.rb +34 -18
  100. data/lib/rigor/cli/renderable.rb +4 -6
  101. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  102. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  103. data/lib/rigor/cli/skill_command.rb +30 -47
  104. data/lib/rigor/cli/skill_describe.rb +40 -64
  105. data/lib/rigor/cli/trace_command.rb +9 -16
  106. data/lib/rigor/cli/trace_renderer.rb +35 -51
  107. data/lib/rigor/cli/triage_command.rb +6 -10
  108. data/lib/rigor/cli/triage_renderer.rb +1 -2
  109. data/lib/rigor/cli/type_of_command.rb +14 -23
  110. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  111. data/lib/rigor/cli/type_scan_command.rb +6 -10
  112. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  113. data/lib/rigor/cli/type_scan_report.rb +2 -3
  114. data/lib/rigor/cli/upgrade_command.rb +2 -3
  115. data/lib/rigor/cli.rb +16 -28
  116. data/lib/rigor/config_audit.rb +35 -44
  117. data/lib/rigor/configuration/dependencies.rb +41 -70
  118. data/lib/rigor/configuration/severity_profile.rb +28 -42
  119. data/lib/rigor/configuration.rb +150 -241
  120. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  121. data/lib/rigor/environment/class_registry.rb +19 -26
  122. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  123. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  124. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  125. data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
  126. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  127. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  128. data/lib/rigor/environment/rbs_loader.rb +420 -518
  129. data/lib/rigor/environment/reflection.rb +28 -53
  130. data/lib/rigor/environment/reporters.rb +13 -24
  131. data/lib/rigor/environment.rb +225 -304
  132. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  133. data/lib/rigor/flow_contribution/element.rb +7 -12
  134. data/lib/rigor/flow_contribution/fact.rb +33 -57
  135. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  136. data/lib/rigor/flow_contribution/merger.rb +32 -47
  137. data/lib/rigor/flow_contribution.rb +37 -55
  138. data/lib/rigor/inference/acceptance.rb +133 -219
  139. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  140. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  141. data/lib/rigor/inference/budget_trace.rb +43 -63
  142. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  143. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  144. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  145. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  146. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  147. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  148. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  149. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  150. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  151. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  152. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  153. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  154. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  155. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  156. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  157. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  158. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  159. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  160. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  161. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  162. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  163. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  164. data/lib/rigor/inference/def_return_typer.rb +8 -14
  165. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  166. data/lib/rigor/inference/expression_typer.rb +794 -1154
  167. data/lib/rigor/inference/fallback.rb +8 -12
  168. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  169. data/lib/rigor/inference/flow_tracer.rb +25 -36
  170. data/lib/rigor/inference/fork_map.rb +87 -0
  171. data/lib/rigor/inference/hkt_body.rb +45 -68
  172. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  173. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  174. data/lib/rigor/inference/hkt_registry.rb +50 -76
  175. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  176. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  177. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  178. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  179. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  180. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  181. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  182. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  183. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  184. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  185. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  186. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  187. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  188. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  189. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  190. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  191. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  192. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  193. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  194. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  195. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  196. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  197. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  198. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  199. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  200. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  201. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  202. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  203. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  204. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  205. data/lib/rigor/inference/mutation_widening.rb +115 -158
  206. data/lib/rigor/inference/narrowing.rb +627 -727
  207. data/lib/rigor/inference/origin_lookup.rb +38 -0
  208. data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
  209. data/lib/rigor/inference/precision_scanner.rb +26 -35
  210. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  211. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  212. data/lib/rigor/inference/protection_scanner.rb +16 -18
  213. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  214. data/lib/rigor/inference/scope_indexer.rb +473 -821
  215. data/lib/rigor/inference/statement_evaluator.rb +754 -1114
  216. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  217. data/lib/rigor/inference/synthetic_method.rb +9 -16
  218. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  219. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  220. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  221. data/lib/rigor/language_server/buffer_table.rb +11 -18
  222. data/lib/rigor/language_server/completion_provider.rb +69 -116
  223. data/lib/rigor/language_server/debouncer.rb +16 -25
  224. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  225. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  226. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  227. data/lib/rigor/language_server/hover_provider.rb +8 -11
  228. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  229. data/lib/rigor/language_server/loop.rb +14 -19
  230. data/lib/rigor/language_server/project_context.rb +46 -74
  231. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  232. data/lib/rigor/language_server/server.rb +55 -83
  233. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  234. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  235. data/lib/rigor/language_server/uri.rb +8 -13
  236. data/lib/rigor/language_server.rb +4 -6
  237. data/lib/rigor/mcp/loop.rb +2 -3
  238. data/lib/rigor/mcp/server.rb +4 -7
  239. data/lib/rigor/mcp.rb +3 -6
  240. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  241. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  242. data/lib/rigor/plugin/base.rb +335 -518
  243. data/lib/rigor/plugin/blueprint.rb +14 -23
  244. data/lib/rigor/plugin/box.rb +18 -29
  245. data/lib/rigor/plugin/fact_store.rb +16 -26
  246. data/lib/rigor/plugin/inflector.rb +37 -53
  247. data/lib/rigor/plugin/io_boundary.rb +33 -56
  248. data/lib/rigor/plugin/isolation.rb +42 -55
  249. data/lib/rigor/plugin/load_error.rb +10 -15
  250. data/lib/rigor/plugin/loader.rb +30 -49
  251. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  252. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  253. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  254. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  255. data/lib/rigor/plugin/macro.rb +10 -15
  256. data/lib/rigor/plugin/manifest.rb +85 -144
  257. data/lib/rigor/plugin/node_context.rb +14 -22
  258. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  259. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  260. data/lib/rigor/plugin/registry.rb +132 -205
  261. data/lib/rigor/plugin/services.rb +21 -33
  262. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  263. data/lib/rigor/plugin/trust_policy.rb +24 -38
  264. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  265. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  266. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  267. data/lib/rigor/protection/mutator.rb +50 -70
  268. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  269. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  270. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  271. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  272. data/lib/rigor/rbs_extended.rb +107 -197
  273. data/lib/rigor/reflection.rb +68 -86
  274. data/lib/rigor/scope/discovery_index.rb +14 -19
  275. data/lib/rigor/scope.rb +255 -310
  276. data/lib/rigor/sig_gen/classification.rb +6 -10
  277. data/lib/rigor/sig_gen/generator.rb +197 -323
  278. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  279. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  280. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  281. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  282. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  283. data/lib/rigor/sig_gen/renderer.rb +7 -13
  284. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  285. data/lib/rigor/sig_gen/write_result.rb +8 -16
  286. data/lib/rigor/sig_gen/writer.rb +95 -174
  287. data/lib/rigor/sig_gen.rb +3 -6
  288. data/lib/rigor/signature_path_audit.rb +24 -30
  289. data/lib/rigor/source/constant_path.rb +10 -14
  290. data/lib/rigor/source/literals.rb +31 -45
  291. data/lib/rigor/source/node_locator.rb +9 -11
  292. data/lib/rigor/source/node_walker.rb +9 -13
  293. data/lib/rigor/source.rb +3 -4
  294. data/lib/rigor/testing.rb +16 -20
  295. data/lib/rigor/triage/catalogue.rb +38 -62
  296. data/lib/rigor/triage.rb +31 -52
  297. data/lib/rigor/trinary.rb +9 -13
  298. data/lib/rigor/type/acceptance_router.rb +4 -6
  299. data/lib/rigor/type/accepts_result.rb +10 -14
  300. data/lib/rigor/type/app.rb +19 -27
  301. data/lib/rigor/type/bot.rb +4 -6
  302. data/lib/rigor/type/bound_method.rb +10 -15
  303. data/lib/rigor/type/combinator.rb +165 -257
  304. data/lib/rigor/type/constant.rb +23 -34
  305. data/lib/rigor/type/data_class.rb +10 -15
  306. data/lib/rigor/type/data_instance.rb +14 -20
  307. data/lib/rigor/type/difference.rb +21 -32
  308. data/lib/rigor/type/dynamic.rb +3 -5
  309. data/lib/rigor/type/hash_shape.rb +32 -18
  310. data/lib/rigor/type/integer_range.rb +11 -16
  311. data/lib/rigor/type/intersection.rb +27 -42
  312. data/lib/rigor/type/nominal.rb +10 -15
  313. data/lib/rigor/type/plain_lattice.rb +9 -13
  314. data/lib/rigor/type/refined.rb +67 -114
  315. data/lib/rigor/type/singleton.rb +4 -6
  316. data/lib/rigor/type/struct_class.rb +11 -16
  317. data/lib/rigor/type/struct_instance.rb +15 -21
  318. data/lib/rigor/type/tuple.rb +14 -19
  319. data/lib/rigor/type/union.rb +30 -42
  320. data/lib/rigor/type_node/generic.rb +14 -26
  321. data/lib/rigor/type_node/identifier.rb +12 -19
  322. data/lib/rigor/type_node.rb +3 -12
  323. data/lib/rigor/value_semantics.rb +16 -21
  324. data/lib/rigor/version.rb +1 -1
  325. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
  337. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  340. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
  348. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  350. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  351. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  353. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  354. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  355. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  356. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
  357. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  358. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  359. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  361. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  363. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  365. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  366. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  367. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  368. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  369. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  372. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  373. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  376. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  377. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  378. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  379. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  380. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  381. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  385. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  386. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  389. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  390. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
  398. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
  399. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  400. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  406. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  408. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  409. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  411. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  416. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  417. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  426. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  427. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  428. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  429. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  430. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  431. data/sig/rigor/environment.rbs +1 -0
  432. data/sig/rigor/scope.rbs +6 -0
  433. metadata +9 -1
@@ -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
 
@@ -8,17 +8,13 @@ require_relative "factorybot/factory_index"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-factorybot — validates `FactoryBot.create(:name,
12
- # key: ...)` and the build / build_stubbed /
13
- # attributes_for / *_list family against a per-run index
14
- # built from `factory_search_paths`.
11
+ # rigor-factorybot — validates `FactoryBot.create(:name, key: ...)` and the build / build_stubbed /
12
+ # attributes_for / *_list family against a per-run index built from `factory_search_paths`.
15
13
  #
16
- # Recognises factory NAMES + literal ATTRIBUTE KEYS in
17
- # the call's keyword hash. The AR column cross-check uses
18
- # the `rigor-activerecord` `:model_index` ADR-9 fact when
19
- # that plugin is loaded (optional `consumes:`).
20
- # Traits, sequences, parent / child factories, and dynamic
21
- # factory names are deferred to follow-up slices.
14
+ # Recognises factory NAMES + literal ATTRIBUTE KEYS in the call's keyword hash. The AR column
15
+ # cross-check uses the `rigor-activerecord` `:model_index` ADR-9 fact when that plugin is loaded
16
+ # (optional `consumes:`). Traits, sequences, parent / child factories, and dynamic factory names are
17
+ # deferred to follow-up slices.
22
18
  #
23
19
  # ## Configuration
24
20
  #
@@ -33,43 +29,32 @@ module Rigor
33
29
  #
34
30
  # ## What it checks
35
31
  #
36
- # - **Factory existence** — every entry call's first
37
- # positional Symbol / String literal is looked up in
38
- # the index. Missing factories emit `unknown-factory`
39
- # with a `DidYouMean` suggestion.
40
- # - **Attribute key existence** — every literal-Symbol
41
- # keyword-argument key is matched against the factory's
42
- # declared attribute names. Missing keys emit
43
- # `unknown-attribute` with a `DidYouMean` suggestion.
44
- # - **Trace** — recognised entry calls also emit a
45
- # `factory-call` info diagnostic listing the factory's
46
- # declared attribute set.
32
+ # - **Factory existence** — every entry call's first positional Symbol / String literal is looked up
33
+ # in the index. Missing factories emit `unknown-factory` with a `DidYouMean` suggestion.
34
+ # - **Attribute key existence** — every literal-Symbol keyword-argument key is matched against the
35
+ # factory's declared attribute names. Missing keys emit `unknown-attribute` with a `DidYouMean`
36
+ # suggestion.
37
+ # - **Trace** recognised entry calls also emit a `factory-call` info diagnostic listing the
38
+ # factory's declared attribute set.
47
39
  #
48
40
  # ## Recognised entry methods
49
41
  #
50
- # `FactoryBot.create`, `.build`, `.build_stubbed`,
51
- # `.attributes_for`, `.create_list`, `.build_list`,
52
- # `.build_stubbed_list`. The legacy `FactoryGirl` constant
53
- # is recognised identically. Implicit-receiver calls
54
- # (`create(:name)` inside an `include FactoryBot::Syntax::Methods`
55
- # context) are NOT recognised — too many false positives on
56
- # plain `create` calls outside test files; deferred until
57
- # receiver-type inference can disambiguate.
42
+ # `FactoryBot.create`, `.build`, `.build_stubbed`, `.attributes_for`, `.create_list`, `.build_list`,
43
+ # `.build_stubbed_list`. The legacy `FactoryGirl` constant is recognised identically. Implicit-receiver
44
+ # calls (`create(:name)` inside an `include FactoryBot::Syntax::Methods` context) are NOT recognised —
45
+ # too many false positives on plain `create` calls outside test files; deferred until receiver-type
46
+ # inference can disambiguate.
58
47
  #
59
48
  # ## What's recognised inside `factory :name do ... end`
60
49
  #
61
- # - `name { "Alice" }` — implicit attribute via
62
- # `method_missing` with a block (modern syntax).
63
- # - `name "Alice"` — implicit attribute with a positional
64
- # argument (legacy syntax).
50
+ # - `name { "Alice" }` — implicit attribute via `method_missing` with a block (modern syntax).
51
+ # - `name "Alice"` — implicit attribute with a positional argument (legacy syntax).
65
52
  # - `add_attribute(:name) { "Alice" }` — explicit form.
66
53
  #
67
- # Sequences (`sequence(:email) { ... }`), associations
68
- # (`association :author`), traits (`trait :admin do ... end`),
69
- # and parent / child relationships (`factory :admin,
70
- # parent: :user do ... end`) are deferred to follow-up
71
- # slices. Factories whose name is a non-literal expression
72
- # (`factory FACTORY_NAME do ... end`) are silently skipped.
54
+ # Sequences (`sequence(:email) { ... }`), associations (`association :author`), traits (`trait :admin
55
+ # do ... end`), and parent / child relationships (`factory :admin, parent: :user do ... end`) are
56
+ # deferred to follow-up slices. Factories whose name is a non-literal expression (`factory
57
+ # FACTORY_NAME do ... end`) are silently skipped.
73
58
  class Factorybot < Rigor::Plugin::Base
74
59
  manifest(
75
60
  id: "factorybot",
@@ -96,18 +81,16 @@ module Rigor
96
81
  @factory_search_paths = Array(config.fetch("factory_search_paths")).map(&:to_s)
97
82
  end
98
83
 
99
- # ADR-37 — per-call factory/attribute validation over the
100
- # engine-owned walk. Each violation carries its own location
101
- # (the call's message_loc, or the offending attribute key), so it
102
- # is positioned via `diagnostic(node, location:)`. No file-level
103
- # diagnostic remains, so there is no `diagnostics_for_file`.
84
+ # ADR-37 — per-call factory/attribute validation over the engine-owned walk. Each violation carries
85
+ # its own location (the call's message_loc, or the offending attribute key), so it is positioned
86
+ # via `diagnostic(node, location:)`. No file-level diagnostic remains, so there is no
87
+ # `diagnostics_for_file`.
104
88
  node_rule Prism::CallNode do |node, _scope, path|
105
89
  index = producer_value(:factory_index)
106
90
  next [] if index.nil? || index.empty?
107
91
 
108
- # `:model_index` is rigor-activerecord's published fact (ADR-9);
109
- # nil when that plugin isn't loaded, in which case the analyzer
110
- # falls back to factory-attributes-only checking.
92
+ # `:model_index` is rigor-activerecord's published fact (ADR-9); nil when that plugin isn't
93
+ # loaded, in which case the analyzer falls back to factory-attributes-only checking.
111
94
  violations = Analyzer.violations_for(
112
95
  call_node: node, factory_index: index,
113
96
  model_index: read_fact(plugin_id: "activerecord", name: :model_index)
@@ -6,17 +6,13 @@ require "rigor/source/literals"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class Graphql < Rigor::Plugin::Base
9
- # Walks project source for `class T < GraphQL::Schema::Object`
10
- # subclasses and emits a `{type_class_fqn => {field_name => {type:, nullable:}}}`
11
- # table covering every `field :name, Type, null: ...` declaration
12
- # inside the class body.
9
+ # Walks project source for `class T < GraphQL::Schema::Object` subclasses and emits a
10
+ # `{type_class_fqn => {field_name => {type:, nullable:}}}` table covering every `field :name, Type,
11
+ # null: ...` declaration inside the class body.
13
12
  module TypeScanner
14
- # The canonical GraphQL scalar type names accepted as
15
- # `field`'s second positional argument. The plugin maps
16
- # each to the underlying Ruby class name so downstream
17
- # consumers can cross-reference against Ruby types
18
- # without re-implementing the GraphQL→Ruby coercion
19
- # table.
13
+ # The canonical GraphQL scalar type names accepted as `field`'s second positional argument. The
14
+ # plugin maps each to the underlying Ruby class name so downstream consumers can cross-reference
15
+ # against Ruby types without re-implementing the GraphQL→Ruby coercion table.
20
16
  CANONICAL_TYPES = {
21
17
  "String" => "String",
22
18
  "Integer" => "Integer",
@@ -26,21 +22,17 @@ module Rigor
26
22
  "ID" => "String"
27
23
  }.freeze
28
24
 
29
- # The base class name a Schema::Object subclass MUST
30
- # inherit from to be recognised. Match is on the
31
- # rightmost segment of the superclass constant chain so
32
- # both `GraphQL::Schema::Object` and the locally-aliased
33
- # `BaseObject = GraphQL::Schema::Object` shape work when
34
- # the alias's RHS is the canonical path.
25
+ # The base class name a Schema::Object subclass MUST inherit from to be recognised. Match is on
26
+ # the rightmost segment of the superclass constant chain so both `GraphQL::Schema::Object` and
27
+ # the locally-aliased `BaseObject = GraphQL::Schema::Object` shape work when the alias's RHS is
28
+ # the canonical path.
35
29
  SCHEMA_OBJECT_TAIL = "Object"
36
30
  SCHEMA_ENUM_TAIL = "Enum"
37
31
  SCHEMA_INPUT_OBJECT_TAIL = "InputObject"
38
32
  SCHEMA_MUTATION_TAIL = "Mutation"
39
- # Common path-segment for `Schema::Object` / `Schema::Enum`
40
- # / `Schema::InputObject` / `Schema::Mutation`; the
41
- # second-to-last segment must be `Schema` (either
42
- # fully-qualified `GraphQL::Schema::X` or lexically nested
43
- # `Schema::X` inside `module GraphQL`).
33
+ # Common path-segment for `Schema::Object` / `Schema::Enum` / `Schema::InputObject` /
34
+ # `Schema::Mutation`; the second-to-last segment must be `Schema` (either fully-qualified
35
+ # `GraphQL::Schema::X` or lexically nested `Schema::X` inside `module GraphQL`).
44
36
  SCHEMA_PARENT_SEGMENTS = %w[Schema GraphQL].freeze
45
37
  private_constant :SCHEMA_OBJECT_TAIL, :SCHEMA_ENUM_TAIL,
46
38
  :SCHEMA_INPUT_OBJECT_TAIL, :SCHEMA_MUTATION_TAIL,
@@ -48,15 +40,12 @@ module Rigor
48
40
 
49
41
  module_function
50
42
 
51
- # @param paths [Array<String>] absolute paths to `.rb` files
52
- # the project's `paths:` resolves to.
53
- # @return [Hash{Symbol => Hash}] frozen 4-key result:
54
- # `:types` (per-`Schema::Object` field table),
55
- # `:enums` (per-`Schema::Enum` value list),
56
- # `:input_objects` (per-`Schema::InputObject` argument table),
57
- # `:mutations` (per-`Schema::Mutation` arguments+fields table).
58
- # Any subset may be empty when no recognisable declaration
59
- # of that kind is found.
43
+ # @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
44
+ # @return [Hash{Symbol => Hash}] frozen 4-key result: `:types` (per-`Schema::Object` field
45
+ # table), `:enums` (per-`Schema::Enum` value list), `:input_objects`
46
+ # (per-`Schema::InputObject` argument table), `:mutations` (per-`Schema::Mutation`
47
+ # arguments+fields table). Any subset may be empty when no recognisable declaration of that
48
+ # kind is found.
60
49
  def scan(paths:)
61
50
  acc = empty_accumulator
62
51
  paths.each do |path|
@@ -97,10 +86,9 @@ module Rigor
97
86
  end
98
87
  private_class_method :scan_file
99
88
 
100
- # Walks the AST collecting `class X < GraphQL::Schema::Object`,
101
- # `Schema::Enum`, `Schema::InputObject`, and `Schema::Mutation`
102
- # decls at any nesting level. Returns a 4-key hash so the
103
- # caller can publish multiple cross-plugin facts from one walk.
89
+ # Walks the AST collecting `class X < GraphQL::Schema::Object`, `Schema::Enum`,
90
+ # `Schema::InputObject`, and `Schema::Mutation` decls at any nesting level. Returns a 4-key hash
91
+ # so the caller can publish multiple cross-plugin facts from one walk.
104
92
  def collect_definitions(node, qualified_prefix)
105
93
  return empty_accumulator if node.nil?
106
94
 
@@ -154,10 +142,9 @@ module Rigor
154
142
  end
155
143
  private_class_method :collect_module_node
156
144
 
157
- # `class X < GraphQL::Schema::<Tail>` matches when the
158
- # superclass's last two path segments are `Schema::<Tail>`.
159
- # Matches both `< GraphQL::Schema::<Tail>` (fully qualified)
160
- # and `< Schema::<Tail>` (lexically inside `module GraphQL`).
145
+ # `class X < GraphQL::Schema::<Tail>` matches when the superclass's last two path segments are
146
+ # `Schema::<Tail>`. Matches both `< GraphQL::Schema::<Tail>` (fully qualified) and `<
147
+ # Schema::<Tail>` (lexically inside `module GraphQL`).
161
148
  def schema_subclass?(class_node, tail)
162
149
  superclass = class_node.superclass
163
150
  return false if superclass.nil?
@@ -193,19 +180,16 @@ module Rigor
193
180
  end
194
181
  private_class_method :statement_nodes
195
182
 
196
- # Walks every top-level `value "..."` call inside an
197
- # enum subclass body and returns the value names as an
198
- # Array<String>. Both shapes graphql-ruby accepts work:
183
+ # Walks every top-level `value "..."` call inside an enum subclass body and returns the value
184
+ # names as an Array<String>. Both shapes graphql-ruby accepts work:
199
185
  #
200
186
  # value "ACTIVE"
201
187
  # value "DISABLED", value: :off, description: "..."
202
188
  #
203
- # The first positional must be a String literal — the
204
- # graphql-ruby `value` API also accepts a Symbol form
205
- # (`value :ACTIVE`) but the documented idiom is String.
206
- # Only the GraphQL-side value name is stored; the optional
207
- # `value:` kwarg (Ruby-side override) and `description:`
208
- # are omitted from the published table.
189
+ # The first positional must be a String literal — the graphql-ruby `value` API also accepts a
190
+ # Symbol form (`value :ACTIVE`) but the documented idiom is String. Only the GraphQL-side value
191
+ # name is stored; the optional `value:` kwarg (Ruby-side override) and `description:` are
192
+ # omitted from the published table.
209
193
  def collect_values(body)
210
194
  return [] if body.nil?
211
195
 
@@ -220,13 +204,10 @@ module Rigor
220
204
  end
221
205
  private_class_method :collect_values
222
206
 
223
- # Walks every top-level `argument :name, Type, required: ...`
224
- # call inside an InputObject (or Mutation) subclass body and
225
- # returns the per-argument shape table. Argument syntax
226
- # mirrors `field` except the nullability axis is named
227
- # `required:` (default `false` — per graphql-ruby's
228
- # `argument` default; the OPPOSITE polarity of `field`'s
229
- # `null:`).
207
+ # Walks every top-level `argument :name, Type, required: ...` call inside an InputObject (or
208
+ # Mutation) subclass body and returns the per-argument shape table. Argument syntax mirrors
209
+ # `field` except the nullability axis is named `required:` (default `false` — per graphql-ruby's
210
+ # `argument` default; the OPPOSITE polarity of `field`'s `null:`).
230
211
  #
231
212
  # argument :name, String, required: true
232
213
  # argument :tags, [String], required: false
@@ -270,10 +251,9 @@ module Rigor
270
251
  end
271
252
  private_class_method :parse_argument_call
272
253
 
273
- # Mirror of `extract_nullability` but reads the `required:`
274
- # kwarg, defaulting to `false` (graphql-ruby's argument
275
- # default — the OPPOSITE polarity of `field`'s `null:` /
276
- # nullability default).
254
+ # Mirror of `extract_nullability` but reads the `required:` kwarg, defaulting to `false`
255
+ # (graphql-ruby's argument default — the OPPOSITE polarity of `field`'s `null:` / nullability
256
+ # default).
277
257
  # rubocop:disable Naming/PredicateMethod -- extractor returns the literal required value
278
258
  def extract_required_flag(args)
279
259
  kwargs = args.last
@@ -293,12 +273,10 @@ module Rigor
293
273
  # rubocop:enable Naming/PredicateMethod
294
274
  private_class_method :extract_required_flag
295
275
 
296
- # `field :name, Type, null: false` shape. The first
297
- # positional is a Symbol (field name); the second is a
298
- # constant reference (GraphQL type) OR a single-element
299
- # ArrayNode (`[Type]`) for GraphQL list types; `null:` is
300
- # the nullability keyword (defaults to TRUE per
301
- # graphql-ruby's field defaults so we mirror that).
276
+ # `field :name, Type, null: false` shape. The first positional is a Symbol (field name); the
277
+ # second is a constant reference (GraphQL type) OR a single-element ArrayNode (`[Type]`) for
278
+ # GraphQL list types; `null:` is the nullability keyword (defaults to TRUE per graphql-ruby's
279
+ # field defaults so we mirror that).
302
280
  def parse_field_call(node)
303
281
  args = node.arguments&.arguments
304
282
  return nil if args.nil? || args.size < 2
@@ -320,13 +298,10 @@ module Rigor
320
298
  end
321
299
  private_class_method :parse_field_call
322
300
 
323
- # Resolves the `Type` positional argument to a
324
- # `{type: "ClassName", list: bool}` tuple. ArrayNode
325
- # forms (`[String]` / `[Types::User]`) unwrap the single
326
- # element and mark `list: true`. Bare constant refs are
327
- # not lists. Returns nil for unrecognised shapes (string
328
- # types `"User"`, Proc lazy types, etc.) so callers drop
329
- # the field.
301
+ # Resolves the `Type` positional argument to a `{type: "ClassName", list: bool}` tuple.
302
+ # ArrayNode forms (`[String]` / `[Types::User]`) unwrap the single element and mark `list: true`.
303
+ # Bare constant refs are not lists. Returns nil for unrecognised shapes (string types `"User"`,
304
+ # Proc lazy types, etc.) so callers drop the field.
330
305
  def resolve_field_type(node)
331
306
  if node.is_a?(Prism::ArrayNode)
332
307
  element = node.elements.first
@@ -354,9 +329,8 @@ module Rigor
354
329
  end
355
330
  private_class_method :resolve_constant_type
356
331
 
357
- # Defaults to `true` (matches graphql-ruby's `field`
358
- # default nullability). Looks for an explicit `null:`
359
- # keyword and reads its boolean literal.
332
+ # Defaults to `true` (matches graphql-ruby's `field` default nullability). Looks for an explicit
333
+ # `null:` keyword and reads its boolean literal.
360
334
  # rubocop:disable Naming/PredicateMethod -- extractor returns the literal nullability value
361
335
  def extract_nullability(args)
362
336
  kwargs = args.last
@@ -376,9 +350,8 @@ module Rigor
376
350
  # rubocop:enable Naming/PredicateMethod
377
351
  private_class_method :extract_nullability
378
352
 
379
- # Returns the constant chain as an Array of String
380
- # segments (`["GraphQL", "Schema", "Object"]`). Empty
381
- # array for unrecognised node kinds.
353
+ # Returns the constant chain as an Array of String segments (`["GraphQL", "Schema", "Object"]`).
354
+ # Empty array for unrecognised node kinds.
382
355
  def constant_path_segments(node)
383
356
  case node
384
357
  when Prism::ConstantReadNode then [node.name.to_s]
@@ -397,9 +370,8 @@ module Rigor
397
370
  end
398
371
  private_class_method :constant_path_segments
399
372
 
400
- # Joined `::`-form of {.constant_path_segments}. Returns
401
- # nil for unrecognised node kinds (so callers can short-
402
- # circuit).
373
+ # Joined `::`-form of {.constant_path_segments}. Returns nil for unrecognised node kinds (so
374
+ # callers can short-circuit).
403
375
  def constant_name_for(node)
404
376
  segments = constant_path_segments(node)
405
377
  segments.empty? ? nil : segments.join("::")