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
@@ -3,22 +3,17 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Activejob < Rigor::Plugin::Base
6
- # Frozen catalogue of discovered ActiveJob subclasses
7
- # keyed by qualified class name. Each entry holds the
8
- # `#perform` method's arity envelope so the analyzer can
9
- # validate `Job.perform_later(...)` call sites.
6
+ # Frozen catalogue of discovered ActiveJob subclasses keyed by qualified class name. Each entry
7
+ # holds the `#perform` method's arity envelope so the analyzer can validate
8
+ # `Job.perform_later(...)` call sites.
10
9
  #
11
- # `min_arity` / `max_arity` form a closed range
12
- # (`Float::INFINITY` for the upper bound when `*args`
13
- # is present). `keyword_required` lists any required
14
- # keyword arguments Active Job supports keyword args
15
- # but they're rare in user code, so the analyzer
16
- # validates positional arity only (keyword arity
17
- # validation is deferred).
10
+ # `min_arity` / `max_arity` form a closed range (`Float::INFINITY` for the upper bound when
11
+ # `*args` is present). `keyword_required` lists any required keyword arguments — Active Job
12
+ # supports keyword args but they're rare in user code, so the analyzer validates positional arity
13
+ # only (keyword arity validation is deferred).
18
14
  class JobIndex
19
15
  Entry = Data.define(:class_name, :min_arity, :max_arity, :keyword_required) do
20
- # Flexible-friendly textual form of the arity for
21
- # error messages: `1`, `1..2`, `2+`.
16
+ # Flexible-friendly textual form of the arity for error messages: `1`, `1..2`, `2+`.
22
17
  def arity_label
23
18
  return "#{min_arity}+" if max_arity == Float::INFINITY
24
19
  return min_arity.to_s if min_arity == max_arity
@@ -8,14 +8,12 @@ require_relative "activejob/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-activejob — validates `Job.perform_later(...)` /
12
- # `.perform_now(...)` / `.perform(...)` argument arity
13
- # against the discovered `#perform` definitions.
11
+ # rigor-activejob — validates `Job.perform_later(...)` / `.perform_now(...)` / `.perform(...)`
12
+ # argument arity against the discovered `#perform` definitions.
14
13
  #
15
14
  # Tier 1D of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
16
- # Statically discovers ActiveJob subclasses by walking
17
- # the configured `job_search_paths` and parsing each
18
- # file with Prism — no `active_job` runtime dependency.
15
+ # Statically discovers ActiveJob subclasses by walking the configured `job_search_paths` and
16
+ # parsing each file with Prism — no `active_job` runtime dependency.
19
17
  #
20
18
  # ## Configuration
21
19
  #
@@ -27,16 +25,12 @@ module Rigor
27
25
  #
28
26
  # ## Limitations (v0.1.0)
29
27
  #
30
- # - Direct-superclass match only. `class WelcomeJob <
31
- # BaseJob` where `BaseJob < ApplicationJob` is NOT
32
- # discovered. Add `BaseJob` to `job_base_classes` if
33
- # needed.
34
- # - The `#perform` arity is read from the syntactic
35
- # parameter list. Methods built via `define_method`
36
- # are out of scope.
37
- # - Required keyword arguments are recognised but not
38
- # validated at the call site (positional arity only
39
- # for v0.1.0).
28
+ # - Direct-superclass match only. `class WelcomeJob < BaseJob` where `BaseJob < ApplicationJob` is
29
+ # NOT discovered. Add `BaseJob` to `job_base_classes` if needed.
30
+ # - The `#perform` arity is read from the syntactic parameter list. Methods built via
31
+ # `define_method` are out of scope.
32
+ # - Required keyword arguments are recognised but not validated at the call site (positional arity
33
+ # only for v0.1.0).
40
34
  class Activejob < Rigor::Plugin::Base
41
35
  manifest(
42
36
  id: "activejob",
@@ -48,11 +42,10 @@ module Rigor
48
42
  }
49
43
  )
50
44
 
51
- # Cached: discovered job index. `watch:` (ADR-60 WD3) covers
52
- # every `.rb` under `job_search_paths` so the cache invalidates
53
- # when a job is added, removed, or edited; the discoverer's
54
- # in-block `IoBoundary` reads are captured into the record-and-
55
- # validate dependency descriptor after the block runs.
45
+ # Cached: discovered job index. `watch:` (ADR-60 WD3) covers every `.rb` under `job_search_paths`
46
+ # so the cache invalidates when a job is added, removed, or edited; the discoverer's in-block
47
+ # `IoBoundary` reads are captured into the record-and-validate dependency descriptor after the
48
+ # block runs.
56
49
  producer :job_index, watch: -> { [[@job_search_paths, "**/*.rb"]] } do |_params|
57
50
  JobDiscoverer.new(
58
51
  io_boundary: io_boundary,
@@ -66,9 +59,8 @@ module Rigor
66
59
  @job_base_classes = Array(config.fetch("job_base_classes")).map(&:to_s)
67
60
  end
68
61
 
69
- # File-level only: the load-error emission. Per-call arity
70
- # validation runs over the engine-owned walk via the node_rule
71
- # below (ADR-37). The job index is lazily loaded + memoised by
62
+ # File-level only: the load-error emission. Per-call arity validation runs over the engine-owned
63
+ # walk via the node_rule below (ADR-37). The job index is lazily loaded + memoised by
72
64
  # `producer_value`, shared by both surfaces.
73
65
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
74
66
  index = producer_value(:job_index)
@@ -5,9 +5,8 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class Activerecord < Rigor::Plugin::Base
8
- # Per-file AST walker. For each `Model.find(...)` /
9
- # `Model.find_by(...)` / `Model.where(...)` call where
10
- # `Model` is a name in the {ModelIndex}, emits diagnostics:
8
+ # Per-file AST walker. For each `Model.find(...)` / `Model.find_by(...)` / `Model.where(...)` call
9
+ # where `Model` is a name in the {ModelIndex}, emits diagnostics:
11
10
  #
12
11
  # | Method | Recognised arg shape | Validation |
13
12
  # | --- | --- | --- |
@@ -16,17 +15,13 @@ module Rigor
16
15
  # | `Model.where(col: v, ...)` | keyword args | each key must be a column |
17
16
  # | `Model.where(string)` | String literal | parser-side; not validated |
18
17
  #
19
- # Successful matches surface as `:info` diagnostics naming
20
- # the resolved table; unknown columns surface as `:error`.
21
- # Calls whose receiver is not a model in the index, and
22
- # calls with non-keyword arguments to `where` / `find_by`,
23
- # stay silent.
18
+ # Successful matches surface as `:info` diagnostics naming the resolved table; unknown columns
19
+ # surface as `:error`. Calls whose receiver is not a model in the index, and calls with
20
+ # non-keyword arguments to `where` / `find_by`, stay silent.
24
21
  class Analyzer
25
- # Methods that take a column → value Hash and need each key
26
- # validated against the receiver's column set. The bang
27
- # variants (`find_by!` raises instead of returning nil;
28
- # `find_or_create_by!` raises on a validation failure) and
29
- # `create_or_find_by` / `create_or_find_by!` take the
22
+ # Methods that take a column → value Hash and need each key validated against the receiver's
23
+ # column set. The bang variants (`find_by!` raises instead of returning nil; `find_or_create_by!`
24
+ # raises on a validation failure) and `create_or_find_by` / `create_or_find_by!` take the
30
25
  # identical column-hash argument shape.
31
26
  COLUMN_HASH_METHODS = %i[
32
27
  where
@@ -88,14 +83,11 @@ module Rigor
88
83
  keyword_pairs = keyword_argument_pairs(node)
89
84
  return push_recognised(node, entry) if keyword_pairs.empty?
90
85
 
91
- # Models with no schema-side columns are virtual — backed
92
- # by a database VIEW (Mastodon's `Instance` model wraps a
93
- # SQL view that isn't in `db/schema.rb`), seeded from an
94
- # external source, or otherwise opaque to our schema
95
- # parser. Without a column set we cannot meaningfully
96
- # check query keys; surface the call as recognised and
97
- # skip column validation entirely rather than firing a
98
- # false `unknown-column` against every key.
86
+ # Models with no schema-side columns are virtual — backed by a database VIEW (Mastodon's
87
+ # `Instance` model wraps a SQL view that isn't in `db/schema.rb`), seeded from an external
88
+ # source, or otherwise opaque to our schema parser. Without a column set we cannot meaningfully
89
+ # check query keys; surface the call as recognised and skip column validation entirely rather
90
+ # than firing a false `unknown-column` against every key.
99
91
  return push_recognised(node, entry, keyword_pairs.map { |p| p[:key] }) if entry.column_names.empty?
100
92
 
101
93
  unknown = keyword_pairs.reject { |pair| valid_query_key?(entry, pair[:key]) }
@@ -114,23 +106,20 @@ module Rigor
114
106
  end
115
107
  end
116
108
 
117
- # ActiveRecord's `where` / `find_by` / `find_or_initialize_by`
118
- # accept either a column name *or* a singular association
119
- # name (belongs_to / has_one). The latter resolves to the
120
- # association's FK column behind the scenes:
109
+ # ActiveRecord's `where` / `find_by` / `find_or_initialize_by` accept either a column name *or* a
110
+ # singular association name (belongs_to / has_one). The latter resolves to the association's FK
111
+ # column behind the scenes:
121
112
  #
122
113
  # AccountPin.find_by(account: x)
123
114
  # # → SELECT … WHERE account_id = x.id
124
115
  #
125
- # Without this allowance, every `find_by(<assoc>:)` call
126
- # surfaces as an `unknown-column` false positive Mastodon
127
- # saw ~100 such hits across the API controllers, all of
128
- # which were the canonical Rails idiom rather than typos.
116
+ # Without this allowance, every `find_by(<assoc>:)` call surfaces as an `unknown-column` false
117
+ # positive Mastodon saw ~100 such hits across the API controllers, all of which were the
118
+ # canonical Rails idiom rather than typos.
129
119
  #
130
- # `alias_attribute` names resolve to their target before
131
- # the column / association check querying by an aliased
132
- # attribute is a valid Rails idiom and must not surface as
133
- # an `unknown-column`.
120
+ # `alias_attribute` names resolve to their target before the column / association check —
121
+ # querying by an aliased attribute is a valid Rails idiom and must not surface as an
122
+ # `unknown-column`.
134
123
  def valid_query_key?(entry, key)
135
124
  key = entry.resolve_alias(key) if entry.alias?(key)
136
125
 
@@ -140,13 +129,10 @@ module Rigor
140
129
  assoc && assoc[:kind] == :singular
141
130
  end
142
131
 
143
- # When the column is an enum-bearing column AND the
144
- # passed value is a Symbol literal, the value MUST be
145
- # one of the declared enum values. Non-Symbol values
146
- # (variables, expressions) decline so dynamic call
147
- # sites stay silent. Sequences (`status: [:active,
148
- # :archived]`) are intentionally NOT walked here —
149
- # the relation-shape check belongs in a future track.
132
+ # When the column is an enum-bearing column AND the passed value is a Symbol literal, the value
133
+ # MUST be one of the declared enum values. Non-Symbol values (variables, expressions) decline so
134
+ # dynamic call sites stay silent. Sequences (`status: [:active, :archived]`) are intentionally NOT
135
+ # walked here the relation-shape check belongs in a future track.
150
136
  def validate_enum_value(node, entry, pair)
151
137
  return unless entry.enum?(pair[:key])
152
138
  return unless pair[:value].is_a?(Prism::SymbolNode)
@@ -199,13 +185,10 @@ module Rigor
199
185
  node.arguments.arguments.size
200
186
  end
201
187
 
202
- # Returns the symbol-keyed pairs of any KeywordHashNode
203
- # argument as `{ key:, value: }` rows. Plain hash-literal
204
- # arguments (`Model.where({a: 1})`) are NOT walked
205
- # Rails accepts both, but the keyword form is the
206
- # idiomatic one. The `value` Prism node is preserved so
207
- # the enum check downstream can recognise Symbol
208
- # literals without re-walking the call.
188
+ # Returns the symbol-keyed pairs of any KeywordHashNode argument as `{ key:, value: }` rows. Plain
189
+ # hash-literal arguments (`Model.where({a: 1})`) are NOT walked — Rails accepts both, but the
190
+ # keyword form is the idiomatic one. The `value` Prism node is preserved so the enum check
191
+ # downstream can recognise Symbol literals without re-walking the call.
209
192
  def keyword_argument_pairs(node)
210
193
  return [] if node.arguments.nil?
211
194
 
@@ -6,20 +6,15 @@ require "rigor/source/literals"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class Activerecord < Rigor::Plugin::Base
9
- # Walks the configured model search paths via the plugin's
10
- # `IoBoundary`, parses each `.rb` file with Prism, and
11
- # collects class declarations that resolve to ActiveRecord
12
- # models.
9
+ # Walks the configured model search paths via the plugin's `IoBoundary`, parses each `.rb` file with
10
+ # Prism, and collects class declarations that resolve to ActiveRecord models.
13
11
  #
14
- # Discovery is a two-step pass. First every class declaration
15
- # is captured as a *candidate* (its name, its superclass
16
- # name, and its DSL metadata). Then a fixpoint marks a
17
- # candidate as a model when its superclass is a configured
18
- # base class OR (transitively) the class name of another
19
- # model this is what makes single-table-inheritance
20
- # subclasses (`class Admin < User`) discoverable. Each STI
21
- # child carries an `sti_parent:` pointer the {ModelIndex}
22
- # uses to inherit the root model's table and DSL surface.
12
+ # Discovery is a two-step pass. First every class declaration is captured as a *candidate* (its
13
+ # name, its superclass name, and its DSL metadata). Then a fixpoint marks a candidate as a model
14
+ # when its superclass is a configured base class OR (transitively) the class name of another model
15
+ # this is what makes single-table-inheritance subclasses (`class Admin < User`) discoverable.
16
+ # Each STI child carries an `sti_parent:` pointer the {ModelIndex} uses to inherit the root model's
17
+ # table and DSL surface.
23
18
  #
24
19
  # Returns rows the {ModelIndex} consumes:
25
20
  #
@@ -28,21 +23,16 @@ module Rigor
28
23
  #
29
24
  # Limitations (intentional for v0.1.0 of the plugin):
30
25
  #
31
- # - `self.table_name = "..."` recognised only when the RHS
32
- # is a String literal. Computed names
26
+ # - `self.table_name = "..."` recognised only when the RHS is a String literal. Computed names
33
27
  # (`self.table_name = "#{tenant}_users"`) are skipped.
34
- # - Modules (`class Admin::User < ApplicationRecord`) are
35
- # recognised; the resulting class name is the lexical
36
- # path (`Admin::User`).
37
- # - The STI fixpoint matches a superclass name against model
38
- # class names tolerating a leading `::`; richer constant
39
- # resolution (relative namespacing) is not modelled.
28
+ # - Modules (`class Admin::User < ApplicationRecord`) are recognised; the resulting class name is
29
+ # the lexical path (`Admin::User`).
30
+ # - The STI fixpoint matches a superclass name against model class names tolerating a leading `::`;
31
+ # richer constant resolution (relative namespacing) is not modelled.
40
32
  class ModelDiscoverer
41
33
  # @param io_boundary [Rigor::Plugin::IoBoundary]
42
- # @param search_paths [Array<String>] absolute or
43
- # project-relative paths.
44
- # @param base_classes [Array<String>] superclass names that
45
- # identify a class as an AR model.
34
+ # @param search_paths [Array<String>] absolute or project-relative paths.
35
+ # @param base_classes [Array<String>] superclass names that identify a class as an AR model.
46
36
  def initialize(io_boundary:, search_paths:, base_classes:)
47
37
  @io_boundary = io_boundary
48
38
  @search_paths = search_paths
@@ -64,17 +54,13 @@ module Rigor
64
54
 
65
55
  private
66
56
 
67
- # Fixpoint over the captured class candidates: a candidate
68
- # is a model when its superclass is a configured base
69
- # class, or transitively the class name of an
70
- # already-known model. The second arm is what discovers
71
- # STI subclasses; the matched parent name is stamped onto
72
- # the row as `sti_parent:` so the {ModelIndex} can inherit
73
- # the root model's table and association surface.
57
+ # Fixpoint over the captured class candidates: a candidate is a model when its superclass is a
58
+ # configured base class, or transitively the class name of an already-known model. The
59
+ # second arm is what discovers STI subclasses; the matched parent name is stamped onto the row as
60
+ # `sti_parent:` so the {ModelIndex} can inherit the root model's table and association surface.
74
61
  #
75
- # Non-model classes (POROs, service objects that happen to
76
- # live under `app/models/`) never enter `model_names` and
77
- # are dropped.
62
+ # Non-model classes (POROs, service objects that happen to live under `app/models/`) never enter
63
+ # `model_names` and are dropped.
78
64
  def resolve_models(candidates)
79
65
  model_names = {}
80
66
  sti_parent = {}
@@ -108,9 +94,8 @@ module Rigor
108
94
  end
109
95
  end
110
96
 
111
- # Resolves a superclass NAME against the set of known
112
- # model class names, tolerating a leading `::`. Returns
113
- # the matched model class name, or nil.
97
+ # Resolves a superclass NAME against the set of known model class names, tolerating a leading
98
+ # `::`. Returns the matched model class name, or nil.
114
99
  def model_match(superclass_name, model_names)
115
100
  return superclass_name if model_names.key?(superclass_name)
116
101
 
@@ -146,11 +131,9 @@ module Rigor
146
131
  end
147
132
  end
148
133
 
149
- # Captures EVERY class declaration as a candidate — the
150
- # `resolve_models` fixpoint decides afterwards which ones
151
- # are models. The DSL metadata is extracted eagerly; for a
152
- # non-model class it is simply discarded when the candidate
153
- # is dropped.
134
+ # Captures EVERY class declaration as a candidate — the `resolve_models` fixpoint decides
135
+ # afterwards which ones are models. The DSL metadata is extracted eagerly; for a non-model class
136
+ # it is simply discarded when the candidate is dropped.
154
137
  def visit_class(node, lexical_path, &)
155
138
  class_local_name = constant_path_name(node.constant_path)
156
139
  return if class_local_name.nil?
@@ -183,8 +166,7 @@ module Rigor
183
166
  walk_for_classes(node.body, inner_path, &) if node.body
184
167
  end
185
168
 
186
- # Renders a constant-path node (`Admin::User`,
187
- # `::ApplicationRecord`) as a String. Returns nil for
169
+ # Renders a constant-path node (`Admin::User`, `::ApplicationRecord`) as a String. Returns nil for
188
170
  # shapes the discoverer chooses not to handle.
189
171
  def constant_path_name(node)
190
172
  return nil if node.nil?
@@ -208,9 +190,8 @@ module Rigor
208
190
  end
209
191
  end
210
192
 
211
- # Looks for `self.table_name = "..."` at the top level of
212
- # the class body. Returns the literal String when found,
213
- # nil otherwise.
193
+ # Looks for `self.table_name = "..."` at the top level of the class body. Returns the literal
194
+ # String when found, nil otherwise.
214
195
  def lookup_table_name_override(body)
215
196
  return nil if body.nil?
216
197
 
@@ -224,19 +205,14 @@ module Rigor
224
205
  nil
225
206
  end
226
207
 
227
- # Recognised single-instance and collection association
228
- # DSL methods. The kind drives the eventual return-type
229
- # contribution: singular associations narrow to
230
- # `Nominal[Target] | nil`, plural ones narrow to
231
- # `ActiveRecord::Relation[Target]`.
208
+ # Recognised single-instance and collection association DSL methods. The kind drives the eventual
209
+ # return-type contribution: singular associations narrow to `Nominal[Target] | nil`, plural ones
210
+ # narrow to `ActiveRecord::Relation[Target]`.
232
211
  #
233
- # `composed_of` value-object aggregations and
234
- # `delegated_type` roles are folded in here too both
235
- # accept the association name as a `where` / `find_by`
236
- # query key, so omitting them turns every such query into
237
- # a false `unknown-column`. `composed_of` resolves to its
238
- # value class (a real target); `delegated_type` is
239
- # polymorphic (no single target).
212
+ # `composed_of` value-object aggregations and `delegated_type` roles are folded in here too — both
213
+ # accept the association name as a `where` / `find_by` query key, so omitting them turns every
214
+ # such query into a false `unknown-column`. `composed_of` resolves to its value class (a real
215
+ # target); `delegated_type` is polymorphic (no single target).
240
216
  ASSOCIATION_METHODS = {
241
217
  belongs_to: :singular,
242
218
  has_one: :singular,
@@ -247,28 +223,21 @@ module Rigor
247
223
  }.freeze
248
224
  private_constant :ASSOCIATION_METHODS
249
225
 
250
- # Association DSL methods that are ALWAYS polymorphic —
251
- # the accessor has no single static target class.
252
- # `belongs_to` / `has_one` become polymorphic only with
253
- # an explicit `polymorphic: true` option.
226
+ # Association DSL methods that are ALWAYS polymorphic — the accessor has no single static target
227
+ # class. `belongs_to` / `has_one` become polymorphic only with an explicit `polymorphic: true`
228
+ # option.
254
229
  POLYMORPHIC_BY_DEFAULT = %i[delegated_type].freeze
255
230
  private_constant :POLYMORPHIC_BY_DEFAULT
256
231
 
257
- # Class-body declaration calls — the top-level `CallNode`s
258
- # PLUS those nested inside a `with_options(...) do … end`
259
- # block. `with_options` is Rails' idiom for sharing
260
- # options across a group of `belongs_to` / `validates` /
261
- # etc. declarations; without descending into it every
262
- # association / enum / validation declared inside is
263
- # invisible to the discoverer, turning `where(<assoc>:
264
- # ...)` into a false `unknown-column`. Nested
265
- # `with_options` blocks recurse.
232
+ # Class-body declaration calls — the top-level `CallNode`s PLUS those nested inside a
233
+ # `with_options(...) do … end` block. `with_options` is Rails' idiom for sharing options across a
234
+ # group of `belongs_to` / `validates` / etc. declarations; without descending into it every
235
+ # association / enum / validation declared inside is invisible to the discoverer, turning
236
+ # `where(<assoc>: ...)` into a false `unknown-column`. Nested `with_options` blocks recurse.
266
237
  #
267
- # The options the `with_options` call itself carries (e.g.
268
- # `with_options class_name: 'Account'`) are NOT merged into
269
- # the nested calls discovering the declaration name is
270
- # what clears the false positive; the merged-option target
271
- # precision is a separate refinement.
238
+ # The options the `with_options` call itself carries (e.g. `with_options class_name: 'Account'`)
239
+ # are NOT merged into the nested calls — discovering the declaration name is what clears the
240
+ # false positive; the merged-option target precision is a separate refinement.
272
241
  def declaration_calls(body)
273
242
  return [] if body.nil?
274
243
 
@@ -283,17 +252,13 @@ module Rigor
283
252
  end
284
253
  end
285
254
 
286
- # Walks the class body for association DSL calls and
287
- # returns a list of rows shaped:
255
+ # Walks the class body for association DSL calls and returns a list of rows shaped:
288
256
  #
289
257
  # { name: "user", kind: :singular, target: "User" }
290
258
  #
291
- # The `target` is resolved from an explicit
292
- # `class_name: "Foo"` option when supplied, otherwise
293
- # inferred from the association name via
294
- # {Inflector.classify}. Calls whose first arg is not a
295
- # Symbol literal (or whose `class_name:` is a non-literal
296
- # expression) decline rather than guess.
259
+ # The `target` is resolved from an explicit `class_name: "Foo"` option when supplied, otherwise
260
+ # inferred from the association name via {Inflector.classify}. Calls whose first arg is not a
261
+ # Symbol literal (or whose `class_name:` is a non-literal expression) decline rather than guess.
297
262
  def lookup_associations(body)
298
263
  return [] if body.nil?
299
264
 
@@ -319,10 +284,8 @@ module Rigor
319
284
  polymorphic = POLYMORPHIC_BY_DEFAULT.include?(node.name) ||
320
285
  association_option(args, "polymorphic") == true
321
286
 
322
- # A polymorphic association has no single static target
323
- # class `target` is nil and the flow contribution
324
- # declines to narrow rather than inventing a wrong
325
- # `Nominal[<classified-name>]`.
287
+ # A polymorphic association has no single static target class — `target` is nil and the flow
288
+ # contribution declines to narrow rather than inventing a wrong `Nominal[<classified-name>]`.
326
289
  if polymorphic
327
290
  target = nil
328
291
  else
@@ -334,15 +297,12 @@ module Rigor
334
297
  nullable: association_nullable?(node.name, args) }
335
298
  end
336
299
 
337
- # Whether a `:singular` association's accessor can return
338
- # `nil`. `has_one` genuinely can (no associated record
339
- # `nil`). `belongs_to` is **required (non-`nil`) by default
340
- # since Rails 5** (`belongs_to_required_by_default`); it
341
- # becomes nullable only when the call passes `optional: true`
342
- # or `required: false`. `composed_of` is non-nullable
343
- # unless `allow_nil: true`. `delegated_type` roles are
344
- # required. A non-literal option value declines to the
345
- # default rather than guessing.
300
+ # Whether a `:singular` association's accessor can return `nil`. `has_one` genuinely can (no
301
+ # associated record → `nil`). `belongs_to` is **required (non-`nil`) by default since Rails 5**
302
+ # (`belongs_to_required_by_default`); it becomes nullable only when the call passes
303
+ # `optional: true` or `required: false`. `composed_of` is non-nullable unless `allow_nil: true`.
304
+ # `delegated_type` roles are required. A non-literal option value declines to the default rather
305
+ # than guessing.
346
306
  def association_nullable?(method_name, args)
347
307
  case method_name
348
308
  when :has_one
@@ -357,9 +317,8 @@ module Rigor
357
317
  end
358
318
  end
359
319
 
360
- # Reads a literal boolean association option (`optional:` /
361
- # `required:`). Returns `true` / `false` for a literal, or
362
- # `nil` when the key is absent or its value is non-literal.
320
+ # Reads a literal boolean association option (`optional:` / `required:`). Returns `true` / `false`
321
+ # for a literal, or `nil` when the key is absent or its value is non-literal.
363
322
  def association_option(args, key)
364
323
  args.each do |arg|
365
324
  next unless arg.is_a?(Prism::KeywordHashNode)
@@ -388,10 +347,9 @@ module Rigor
388
347
  nil
389
348
  end
390
349
 
391
- # `enum status: { active: 0, archived: 1 }` (Rails ≤6)
392
- # and `enum :status, [:active, :archived]` (Rails 7+).
393
- # Returns `Hash<column_name => Array<Symbol>>`.
394
- # Non-literal forms decline rather than guess.
350
+ # `enum status: { active: 0, archived: 1 }` (Rails ≤6) and `enum :status, [:active, :archived]`
351
+ # (Rails 7+). Returns `Hash<column_name => Array<Symbol>>`. Non-literal forms decline rather than
352
+ # guess.
395
353
  def lookup_enums(body)
396
354
  return {} if body.nil?
397
355
 
@@ -445,10 +403,9 @@ module Rigor
445
403
  end
446
404
  end
447
405
 
448
- # `scope :active, -> { ... }`. Records the scope name
449
- # only (the body is intentionally NOT introspected
450
- # the caller contributes `ActiveRecord::Relation[Model]`
451
- # based on the name alone via `class_scope_return_type`).
406
+ # `scope :active, -> { ... }`. Records the scope name only (the body is intentionally NOT
407
+ # introspected the caller contributes `ActiveRecord::Relation[Model]` based on the name alone
408
+ # via `class_scope_return_type`).
452
409
  def lookup_scopes(body)
453
410
  return [] if body.nil?
454
411
 
@@ -468,11 +425,9 @@ module Rigor
468
425
  scopes.freeze
469
426
  end
470
427
 
471
- # `validates :name, presence: true, length: { maximum: 100 }`.
472
- # Records the attribute name (the validator option set
473
- # is ignored the value here is the diagnostic
474
- # `validates :unknown_attr` surfacing when the attribute
475
- # isn't a column on the table).
428
+ # `validates :name, presence: true, length: { maximum: 100 }`. Records the attribute name (the
429
+ # validator option set is ignored — the value here is the diagnostic `validates :unknown_attr`
430
+ # surfacing when the attribute isn't a column on the table).
476
431
  def lookup_validations(body)
477
432
  return [] if body.nil?
478
433
 
@@ -487,10 +442,9 @@ module Rigor
487
442
  attrs.uniq.freeze
488
443
  end
489
444
 
490
- # `before_save :foo`, `after_create :bar`, etc. Records
491
- # the referenced method name (a Symbol literal). The
492
- # diagnostic value is "did you forget to `def` this?".
493
- # Block callbacks (`before_save { ... }`) decline.
445
+ # `before_save :foo`, `after_create :bar`, etc. Records the referenced method name (a Symbol
446
+ # literal). The diagnostic value is "did you forget to `def` this?". Block callbacks
447
+ # (`before_save { ... }`) decline.
494
448
  CALLBACK_METHODS = %i[
495
449
  before_validation after_validation
496
450
  before_save after_save around_save
@@ -517,12 +471,10 @@ module Rigor
517
471
  targets.freeze
518
472
  end
519
473
 
520
- # `alias_attribute :new_name, :old_name`. Records the
521
- # mapping so the analyzer accepts the alias as a query
522
- # key without it every `where(<alias>: ...)` /
523
- # `find_by(<alias>: ...)` call surfaces as a false
524
- # `unknown-column`. Returns `Hash<alias => target>`;
525
- # non-Symbol-literal forms decline rather than guess.
474
+ # `alias_attribute :new_name, :old_name`. Records the mapping so the analyzer accepts the alias
475
+ # as a query key without it every `where(<alias>: ...)` / `find_by(<alias>: ...)` call surfaces
476
+ # as a false `unknown-column`. Returns `Hash<alias => target>`; non-Symbol-literal forms decline
477
+ # rather than guess.
526
478
  def lookup_aliases(body)
527
479
  return {} if body.nil?
528
480
 
@@ -543,10 +495,8 @@ module Rigor
543
495
  aliases.freeze
544
496
  end
545
497
 
546
- # Collects every Symbol-literal positional argument
547
- # from a CallNode. Used by both `lookup_validations`
548
- # and `lookup_callbacks` to extract the attribute /
549
- # method name list.
498
+ # Collects every Symbol-literal positional argument from a CallNode. Used by both
499
+ # `lookup_validations` and `lookup_callbacks` to extract the attribute / method name list.
550
500
  def symbol_args(node)
551
501
  args = node.arguments&.arguments
552
502
  return [] if args.nil?