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
@@ -3,53 +3,35 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class RailsRoutes < Rigor::Plugin::Base
6
- # Generates the catalogue of route helpers a `devise_for
7
- # :resources` call adds to a Rails app's routing table.
8
- # The set is fixed and well-documented (the
9
- # [Devise wiki](https://github.com/heartcombo/devise/wiki/How-To:-Add-routes-helpers)
10
- # enumerates every generated helper); the generator
11
- # mirrors it.
6
+ # Generates the catalogue of route helpers a `devise_for :resources` call adds to a Rails app's
7
+ # routing table. The set is fixed and well-documented (the
8
+ # [Devise wiki](https://github.com/heartcombo/devise/wiki/How-To:-Add-routes-helpers) enumerates
9
+ # every generated helper); the generator mirrors it.
12
10
  #
13
- # Used by `RoutesParser` when it sees a top-level
14
- # `devise_for :name` (or `devise_for :name, skip:
15
- # [...]`) call. Each generated helper is materialised as
16
- # a `HelperTable::Entry` so the analyzer's
17
- # `unknown-helper` rule recognises them and the arity
18
- # check accepts the canonical signatures (Devise helpers
19
- # take zero positional args plus an optional trailing
20
- # options hash, which `HelperTable#accepts_arity?`
21
- # already handles via the `arity + 1` rule).
11
+ # Used by `RoutesParser` when it sees a top-level `devise_for :name` (or `devise_for :name, skip:
12
+ # [...]`) call. Each generated helper is materialised as a `HelperTable::Entry` so the analyzer's
13
+ # `unknown-helper` rule recognises them and the arity check accepts the canonical signatures (Devise
14
+ # helpers take zero positional args plus an optional trailing options hash, which
15
+ # `HelperTable#accepts_arity?` already handles via the `arity + 1` rule).
22
16
  #
23
17
  # Scope:
24
18
  #
25
- # - Recognises the standard six controllers
26
- # (`:sessions`, `:passwords`, `:confirmations`,
27
- # `:unlocks`, `:registrations`, `:omniauth_callbacks`).
28
- # - Honours `skip: [...]` to suppress controllers the
29
- # project disables.
30
- # - Honours `path: "..."` to remap the URL path (does
31
- # NOT remap the helper-name prefix — Devise uses the
32
- # resource name for the helper, not the path).
33
- # - Does NOT yet honour `as: "..."` overrides, `class_name:`,
34
- # `controllers: { ... }` (these are rare; expand on
35
- # demand).
36
- # - `omniauth_callbacks` helpers are dynamic — the
37
- # provider name is supplied at call time
38
- # (`user_facebook_omniauth_authorize_path`) and Devise
39
- # declares them from the configured providers, which
40
- # live in an initializer this static parser does not
41
- # read. The suffix patterns are registered in
42
- # `OMNIAUTH_SUFFIXES` and consulted by
43
- # `HelperTable#omniauth_match?`.
19
+ # - Recognises the standard six controllers (`:sessions`, `:passwords`, `:confirmations`, `:unlocks`,
20
+ # `:registrations`, `:omniauth_callbacks`).
21
+ # - Honours `skip: [...]` to suppress controllers the project disables.
22
+ # - Honours `path: "..."` to remap the URL path (does NOT remap the helper-name prefix — Devise uses
23
+ # the resource name for the helper, not the path).
24
+ # - Does NOT yet honour `as: "..."` overrides, `class_name:`, `controllers: { ... }` (these are rare;
25
+ # expand on demand).
26
+ # - `omniauth_callbacks` helpers are dynamic the provider name is supplied at call time
27
+ # (`user_facebook_omniauth_authorize_path`) and Devise declares them from the configured providers,
28
+ # which live in an initializer this static parser does not read. The suffix patterns are registered
29
+ # in `OMNIAUTH_SUFFIXES` and consulted by `HelperTable#omniauth_match?`.
44
30
  module DeviseRoutes
45
- # The standard Devise controllers and the helper
46
- # actions each generates. Keys are the controller
47
- # name; values are arrays of `[helper_prefix,
48
- # http_method, action]` triples `helper_prefix` is
49
- # the part BEFORE the resource-name segment (Rails
50
- # produces `<prefix>_<resource>_<suffix>_path`; for
51
- # the bare `<resource>_session_path` shape the prefix
52
- # is empty).
31
+ # The standard Devise controllers and the helper actions each generates. Keys are the controller
32
+ # name; values are arrays of `[helper_prefix, http_method, action]` triples — `helper_prefix` is
33
+ # the part BEFORE the resource-name segment (Rails produces `<prefix>_<resource>_<suffix>_path`;
34
+ # for the bare `<resource>_session_path` shape the prefix is empty).
53
35
  CONTROLLER_HELPERS = {
54
36
  sessions: [
55
37
  ["new", "session", :get, :new],
@@ -77,13 +59,10 @@ module Rigor
77
59
  ]
78
60
  }.freeze
79
61
 
80
- # Dynamic-provider helper patterns. `_omniauth_authorize_path`
81
- # and `_omniauth_callback_path` are generated per
82
- # configured provider (Facebook, GitHub, ) by an
83
- # initializer this parser does not read. We treat any
84
- # `<resource>_<provider>_omniauth_(authorize|callback)_(path|url)`
85
- # call as recognised when the resource is one the
86
- # project declared via `devise_for`.
62
+ # Dynamic-provider helper patterns. `_omniauth_authorize_path` and `_omniauth_callback_path` are
63
+ # generated per configured provider (Facebook, GitHub, …) by an initializer this parser does not
64
+ # read. We treat any `<resource>_<provider>_omniauth_(authorize|callback)_(path|url)` call as
65
+ # recognised when the resource is one the project declared via `devise_for`.
87
66
  OMNIAUTH_SUFFIXES = %w[
88
67
  _omniauth_authorize_path _omniauth_authorize_url
89
68
  _omniauth_callback_path _omniauth_callback_url
@@ -91,16 +70,12 @@ module Rigor
91
70
 
92
71
  module_function
93
72
 
94
- # @param resource [String, Symbol] the `devise_for`
95
- # argument typically `:users`. Used as both the
96
- # helper-name segment (`new_user_session_path`) and
97
- # to derive the resource path.
98
- # @param skip [Array<Symbol>] controllers the project
99
- # disables via `devise_for :users, skip: [:registrations]`.
100
- # @return [Array<HelperTable::Entry>] one entry per
101
- # generated `_path` helper. The caller is expected to
102
- # pair `_url` variants the same way `RoutesParser` does
103
- # for other entries.
73
+ # @param resource [String, Symbol] the `devise_for` argument — typically `:users`. Used as both the
74
+ # helper-name segment (`new_user_session_path`) and to derive the resource path.
75
+ # @param skip [Array<Symbol>] controllers the project disables via `devise_for :users, skip:
76
+ # [:registrations]`.
77
+ # @return [Array<HelperTable::Entry>] one entry per generated `_path` helper. The caller is
78
+ # expected to pair `_url` variants the same way `RoutesParser` does for other entries.
104
79
  def generate(resource:, skip: [])
105
80
  resource_segment = singularize(resource.to_s)
106
81
  skip_set = skip.to_set(&:to_sym)
@@ -126,21 +101,15 @@ module Rigor
126
101
  end
127
102
  end
128
103
 
129
- # Devise also ships *scoped* helpers
130
- # (`Devise::Controllers::UrlHelpers`) that DROP the
131
- # resource segment and take the scope as a positional
132
- # argument: `new_password_path(resource_name)` is the
104
+ # Devise also ships *scoped* helpers (`Devise::Controllers::UrlHelpers`) that DROP the resource
105
+ # segment and take the scope as a positional argument: `new_password_path(resource_name)` is the
133
106
  # bare form of `new_user_password_path`. Used inside
134
- # `Auth::PasswordsController < Devise::PasswordsController`
135
- # (Mastodon's idiom). Arity 1 (the scope), and a
136
- # trailing options-hash bumps it via the existing
137
- # `arity + 1` rule.
107
+ # `Auth::PasswordsController < Devise::PasswordsController` (Mastodon's idiom). Arity 1 (the
108
+ # scope), and a trailing options-hash bumps it via the existing `arity + 1` rule.
138
109
  entries.concat(scoped_helpers(skip_set))
139
110
 
140
- # `omniauth_authorize_path(scope, provider)` and
141
- # `omniauth_callback_path(scope, provider)` are
142
- # `Devise::Controllers::UrlHelpers` scoped helpers
143
- # that delegate to the OmniAuth gem. Arity 2.
111
+ # `omniauth_authorize_path(scope, provider)` and `omniauth_callback_path(scope, provider)` are
112
+ # `Devise::Controllers::UrlHelpers` scoped helpers that delegate to the OmniAuth gem. Arity 2.
144
113
  unless skip_set.include?(:omniauth_callbacks)
145
114
  %w[omniauth_authorize_path omniauth_callback_path].each do |name|
146
115
  entries << HelperTable::Entry.new(
@@ -156,11 +125,9 @@ module Rigor
156
125
  entries
157
126
  end
158
127
 
159
- # Scoped helpers Devise exposes inside its controllers.
160
- # The arity-1 family caller supplies the resource
161
- # scope (`:user` etc.) as the first positional arg.
162
- # Names mirror the qualified `<scope>_<name>_path`
163
- # entries above, minus the scope segment.
128
+ # Scoped helpers Devise exposes inside its controllers. The arity-1 family — caller supplies the
129
+ # resource scope (`:user` etc.) as the first positional arg. Names mirror the qualified
130
+ # `<scope>_<name>_path` entries above, minus the scope segment.
164
131
  SCOPED_HELPERS = {
165
132
  sessions: %w[new_session_path session_path destroy_session_path],
166
133
  passwords: %w[new_password_path edit_password_path password_path],
@@ -186,28 +153,22 @@ module Rigor
186
153
  end
187
154
  end
188
155
 
189
- # Returns the set of OmniAuth pattern suffixes the
190
- # analyzer accepts for a given resource. The analyzer
191
- # consults this set (via `HelperTable#omniauth_match?`)
192
- # when a `*_path` / `*_url` call's name does not match
193
- # any registered entry and its prefix matches a Devise
194
- # resource.
156
+ # Returns the set of OmniAuth pattern suffixes the analyzer accepts for a given resource. The
157
+ # analyzer consults this set (via `HelperTable#omniauth_match?`) when a `*_path` / `*_url` call's
158
+ # name does not match any registered entry and its prefix matches a Devise resource.
195
159
  def omniauth_suffixes
196
160
  OMNIAUTH_SUFFIXES
197
161
  end
198
162
 
199
- # ADR-39 — delegates to the shared inflector (real
200
- # `ActiveSupport::Inflector` when available), replacing the tiny
201
- # AS-replica this module used to duplicate from `RoutesParser`.
163
+ # ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available),
164
+ # replacing the tiny AS-replica this module used to duplicate from `RoutesParser`.
202
165
  def singularize(word)
203
166
  Rigor::Plugin::Inflector.singularize(word)
204
167
  end
205
168
 
206
- # Approximate path generation. We don't honour the
207
- # `path:` Devise option here because the helper NAME
208
- # is what the analyzer cares about; the `path` field is
209
- # informational and surfaces only in the `:helper` info
210
- # diagnostic.
169
+ # Approximate path generation. We don't honour the `path:` Devise option here because the helper
170
+ # NAME is what the analyzer cares about; the `path` field is informational and surfaces only in
171
+ # the `:helper` info diagnostic.
211
172
  def devise_path_for(resource_segment, controller, action)
212
173
  plural = "#{resource_segment}s"
213
174
  base = "/#{plural}"
@@ -3,39 +3,27 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class RailsRoutes < Rigor::Plugin::Base
6
- # Generates the catalogue of OAuth route helpers a
7
- # `use_doorkeeper do ... end` block adds to a Rails app's
8
- # routing table. The set is the Doorkeeper gem's
9
- # documented default see Doorkeeper's
10
- # [Routing](https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes)
11
- # documentation; this module mirrors the canonical
12
- # helper names.
6
+ # Generates the catalogue of OAuth route helpers a `use_doorkeeper do ... end` block adds to a Rails
7
+ # app's routing table. The set is the Doorkeeper gem's documented default — see Doorkeeper's
8
+ # [Routing](https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes) documentation; this
9
+ # module mirrors the canonical helper names.
13
10
  #
14
- # Used by `RoutesParser` when it sees a `use_doorkeeper`
15
- # call. Each generated helper is materialised as a
16
- # `HelperTable::Entry` so the analyzer's
17
- # `unknown-helper` rule recognises them. Arity for the
18
- # `oauth_application_path(:id)` / `oauth_authorized_application_path(:id)`
19
- # forms is 1 (the `:id`); all other Doorkeeper helpers
20
- # are arity 0.
11
+ # Used by `RoutesParser` when it sees a `use_doorkeeper` call. Each generated helper is materialised
12
+ # as a `HelperTable::Entry` so the analyzer's `unknown-helper` rule recognises them. Arity for the
13
+ # `oauth_application_path(:id)` / `oauth_authorized_application_path(:id)` forms is 1 (the `:id`);
14
+ # all other Doorkeeper helpers are arity 0.
21
15
  #
22
16
  # Scope:
23
17
  #
24
- # - Recognises the standard helper set: authorization,
25
- # token, revoke, introspect, token_info, userinfo,
26
- # applications (resources), authorized_applications.
27
- # - Honours `skip_controllers <names>` inside the block
28
- # to omit the named controller's helpers.
29
- # - Does NOT yet honour `controllers ...:` remappings
30
- # those change WHICH class serves the route but not
31
- # the helper NAMES, so omitting them is sound.
32
- # - Does NOT yet honour custom `as:` / `at:` on
33
- # `use_doorkeeper` itself.
18
+ # - Recognises the standard helper set: authorization, token, revoke, introspect, token_info,
19
+ # userinfo, applications (resources), authorized_applications.
20
+ # - Honours `skip_controllers <names>` inside the block to omit the named controller's helpers.
21
+ # - Does NOT yet honour `controllers ...:` remappings — those change WHICH class serves the route but
22
+ # not the helper NAMES, so omitting them is sound.
23
+ # - Does NOT yet honour custom `as:` / `at:` on `use_doorkeeper` itself.
34
24
  module DoorkeeperRoutes
35
- # Per-controller helper catalogue. Keys are the
36
- # `skip_controllers :<key>` names; values are the
37
- # entries (name + arity + path + http_method).
38
- # Both `_path` and `_url` variants are paired by
25
+ # Per-controller helper catalogue. Keys are the `skip_controllers :<key>` names; values are the
26
+ # entries (name + arity + path + http_method). Both `_path` and `_url` variants are paired by
39
27
  # `RoutesParser` after registration.
40
28
  CONTROLLER_HELPERS = {
41
29
  authorizations: [
@@ -78,8 +66,7 @@ module Rigor
78
66
 
79
67
  module_function
80
68
 
81
- # @param skip [Array<Symbol>] controller names the
82
- # project omits via `skip_controllers :name, ...`.
69
+ # @param skip [Array<Symbol>] controller names the project omits via `skip_controllers :name, ...`.
83
70
  # @return [Array<HelperTable::Entry>] flattened entries.
84
71
  def generate(skip: [])
85
72
  skip_set = skip.to_set(&:to_sym)
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Rigor
6
+ module Plugin
7
+ class RailsRoutes < Rigor::Plugin::Base
8
+ # Recognises the helper *namespaces* the `grape-path-helpers` gem generates, so a call to one of them
9
+ # does not false-fire `unknown-helper`.
10
+ #
11
+ # The gem names each helper after its route's path segments, joined with `_`
12
+ # (`DecoratedRoute#path_helper_name`): `/api/:version/groups/:id/badges` under `version 'v4'` becomes
13
+ # `api_v4_groups_badges_path`. It walks `Grape::API::Instance.routes` — the *runtime* route table — and
14
+ # real grape sources build that table with metaprogramming (GitLab's `%w[group project].each { |t|
15
+ # resource t.pluralize … }`). A static parser cannot enumerate those names, and deriving only the
16
+ # easy ones would be worse than deriving none: every route it missed would keep firing on working
17
+ # code.
18
+ #
19
+ # What IS static is the leading segments. `prefix :api` contributes the first; `version 'v4'`
20
+ # contributes the second when the strategy is `:path` (Grape's default — `using: :header` / `:param`
21
+ # keeps the version out of the URL, and so out of the helper name). Both are literal arguments in the
22
+ # body of a class whose superclass chain reaches `Grape::API`. Everything after them is opaque.
23
+ #
24
+ # So the namespace `api_v4_…_path` is treated as open — a name the project's routes may define but
25
+ # Rigor cannot enumerate, so proving it undefined is unsound (the ADR-26 `open_receivers` reasoning,
26
+ # already applied in this plugin to Devise's runtime-provider OmniAuth family). Teeth survive where the
27
+ # gem's own contract keeps them sound: it defines no `_url` helper
28
+ # (`NamedRouteMatcher#method_missing` returns `super` unless the name ends `_path`), so
29
+ # `api_v4_anything_url` still fires, as does any name outside a declared prefix.
30
+ #
31
+ # Design note: `docs/notes/20260710-grape-path-helper-namespace.md`.
32
+ module GrapeApiDiscoverer
33
+ # A class is a grape API when its superclass chain reaches one of these. `Grape::API::Instance` is
34
+ # what a mounted `Grape::API` subclass actually becomes, and real code inherits from it directly
35
+ # (GitLab's `API::Base < Grape::API::Instance`).
36
+ GRAPE_BASE_NAMES = ["Grape::API", "Grape::API::Instance"].freeze
37
+
38
+ # `version 'v4', using: :path` puts the version in the URL. `:header` / `:param` do not, so those
39
+ # versions contribute no helper-name segment. Grape's default strategy is `:path`.
40
+ PATH_VERSION_STRATEGY = :path
41
+
42
+ module_function
43
+
44
+ # @param contents_per_path [Hash{String => String}] file path → source text, read by the caller
45
+ # (through the trusted `IoBoundary`, so cache invalidation works).
46
+ # @return [Array<String>] the recognised helper-name prefixes (`["api_v3", "api_v4"]`). Empty when
47
+ # the project declares no grape API — nothing changes for such a project.
48
+ def discover(contents_per_path)
49
+ declarations = {}
50
+ superclasses = {}
51
+ contents_per_path.each_value do |contents|
52
+ collect_from_contents(contents, declarations, superclasses)
53
+ rescue StandardError
54
+ # Best-effort: a parse failure in one grape file must not abort discovery for the rest. The core
55
+ # pipeline surfaces the parse error separately.
56
+ next
57
+ end
58
+ prefixes_for(declarations, superclasses)
59
+ end
60
+
61
+ # Composes `prefix` × `version` into helper-name prefixes, for grape classes only. A class with a
62
+ # prefix and no path-strategy version contributes the bare prefix; one with versions and no prefix
63
+ # contributes each version alone.
64
+ def prefixes_for(declarations, superclasses)
65
+ declarations.filter_map do |class_name, declaration|
66
+ next unless grape_api?(class_name, superclasses)
67
+
68
+ segments = declaration[:prefix]
69
+ versions = declaration[:versions]
70
+ next segments.join("_") if versions.empty? && !segments.empty?
71
+
72
+ versions.map { |version| (segments + [version]).join("_") }
73
+ end.flatten.reject(&:empty?).uniq
74
+ end
75
+
76
+ # Follows the recorded superclass chain (cycle-guarded) looking for a grape base. The chain lives
77
+ # entirely inside the scanned files, so an unresolvable parent simply ends the walk.
78
+ def grape_api?(class_name, superclasses)
79
+ seen = {}
80
+ current = class_name
81
+ while current && !seen[current]
82
+ seen[current] = true
83
+ parent = superclasses[current]
84
+ return true if parent && GRAPE_BASE_NAMES.include?(parent)
85
+
86
+ current = parent
87
+ end
88
+ false
89
+ end
90
+
91
+ def collect_from_contents(contents, declarations, superclasses)
92
+ root = Prism.parse(contents).value
93
+ walk(root, [], declarations, superclasses)
94
+ end
95
+
96
+ def walk(node, prefix_path, declarations, superclasses)
97
+ return unless node.is_a?(Prism::Node)
98
+
99
+ if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
100
+ name = constant_name(node.constant_path)
101
+ if name
102
+ qualified = (prefix_path + [name]).join("::")
103
+ record_class(node, qualified, declarations, superclasses) if node.is_a?(Prism::ClassNode)
104
+ walk(node.body, prefix_path + [name], declarations, superclasses) if node.body
105
+ return
106
+ end
107
+ end
108
+
109
+ node.compact_child_nodes.each { |child| walk(child, prefix_path, declarations, superclasses) }
110
+ end
111
+
112
+ def record_class(node, qualified, declarations, superclasses)
113
+ parent = constant_name(node.superclass)
114
+ superclasses[qualified] = parent if parent
115
+ declaration = { prefix: [], versions: [] }
116
+ statements_of(node.body).each { |statement| absorb_declaration(statement, declaration) }
117
+ declarations[qualified] = declaration unless declaration[:prefix].empty? && declaration[:versions].empty?
118
+ end
119
+
120
+ # `prefix :api` / `prefix 'api/v2'` (a multi-segment prefix splits on `/`), and `version 'v4'` /
121
+ # `version 'v4', using: :path` — both in block and non-block form.
122
+ def absorb_declaration(node, declaration)
123
+ return unless node.is_a?(Prism::CallNode) && node.receiver.nil?
124
+
125
+ case node.name
126
+ when :prefix
127
+ value = literal_value(first_argument(node))
128
+ declaration[:prefix] = value.split("/").reject(&:empty?) if value
129
+ when :version
130
+ value = literal_value(first_argument(node))
131
+ declaration[:versions] << value if value && path_strategy?(node)
132
+ end
133
+ end
134
+
135
+ # True unless an explicit `using:` names a non-path strategy. Grape defaults to `:path`.
136
+ def path_strategy?(node)
137
+ arguments = node.arguments&.arguments || []
138
+ keywords = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) }
139
+ return true if keywords.nil?
140
+
141
+ using = keywords.elements.find do |element|
142
+ element.is_a?(Prism::AssocNode) && literal_value(element.key) == "using"
143
+ end
144
+ return true if using.nil?
145
+
146
+ literal_value(using.value) == PATH_VERSION_STRATEGY.to_s
147
+ end
148
+
149
+ def first_argument(node)
150
+ node.arguments&.arguments&.first
151
+ end
152
+
153
+ # Symbol and String literals only. A computed prefix / version is not a declaration we can ground a
154
+ # namespace in, so it contributes nothing.
155
+ def literal_value(node)
156
+ case node
157
+ when Prism::SymbolNode, Prism::StringNode then node.unescaped
158
+ end
159
+ end
160
+
161
+ def constant_name(node)
162
+ case node
163
+ when Prism::ConstantReadNode then node.name.to_s
164
+ when Prism::ConstantPathNode then constant_path_name(node)
165
+ end
166
+ end
167
+
168
+ def constant_path_name(node)
169
+ parent = node.parent
170
+ own = node.name&.to_s
171
+ return nil unless own
172
+ return own if parent.nil? # `::Foo` — a cbase root
173
+
174
+ parent_name = constant_name(parent)
175
+ parent_name ? "#{parent_name}::#{own}" : own
176
+ end
177
+
178
+ def statements_of(body)
179
+ case body
180
+ when Prism::StatementsNode then body.body
181
+ when Prism::BeginNode then statements_of(body.statements)
182
+ when nil then []
183
+ else [body]
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -5,76 +5,54 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class RailsRoutes < Rigor::Plugin::Base
8
- # Walks `app/helpers/**/*.rb` (or any configured set of
9
- # helper directories) and extracts every public
10
- # module-level method name so the analyzer's
11
- # `unknown-helper` rule does not false-fire on calls to
12
- # project-defined `*_path` / `*_url` helpers (the
13
- # canonical Rails pattern: a `UrlHelper` module exposing
14
- # `full_asset_url`, `host_to_url`, `frontend_asset_url`,
15
- # and so on).
8
+ # Walks `app/helpers/**/*.rb` (or any configured set of helper directories) and extracts every public
9
+ # module-level method name so the analyzer's `unknown-helper` rule does not false-fire on calls to
10
+ # project-defined `*_path` / `*_url` helpers (the canonical Rails pattern: a `UrlHelper` module
11
+ # exposing `full_asset_url`, `host_to_url`, `frontend_asset_url`, and so on).
16
12
  #
17
13
  # The discoverer is deliberately conservative:
18
14
  #
19
- # - Walks `def name(...)` declarations at the module /
20
- # class top level. Nested `def` inside a method body or
21
- # a `define_method` macro is NOT extracted (the same
22
- # reason `rigor-actionpack` is not the canonical
23
- # custom-helper sourcemetaprogrammed helpers are out
24
- # of scope for a static scan).
25
- # - **Visibility-agnostic.** A `private` / `protected`
26
- # `def some_url` IS registered. The `unknown-helper`
27
- # rule is a project-wide name-presence check, not a
28
- # visibility checkif the user wrote a `_path` /
29
- # `_url` method anywhere in `app/`, they very likely
30
- # intend to call it (often Mastodon's pattern: a
31
- # controller's `private def page_url(page)` invoked
32
- # by a `link_to` in its own view). The core engine's
33
- # `call.undefined-method` rule still catches the case
34
- # where the receiver genuinely cannot see the method.
35
- # - Ignores `def self.x` (singleton-method definitions) —
36
- # Rails helpers are instance methods on the helper
37
- # module; class-side `self.x` shapes are usually
38
- # internal utilities, not view helpers.
39
- # - Filters the resulting set to names ending in `_path`
40
- # or `_url` (the dispatch surface the analyzer's rule
41
- # actually cares about). A helper whose name does not
42
- # end with either suffix is irrelevant to the rule's
43
- # false-positive surface, so excluding it costs nothing
44
- # and keeps the registered set focused.
15
+ # - Walks `def name(...)` declarations at the module / class top level. Nested `def` inside a method
16
+ # body or a `define_method` macro is NOT extracted (the same reason `rigor-actionpack` is not the
17
+ # canonical custom-helper source metaprogrammed helpers are out of scope for a static scan).
18
+ # - **Visibility-agnostic.** A `private` / `protected` `def some_url` IS registered. The
19
+ # `unknown-helper` rule is a project-wide name-presence check, not a visibility check if the user
20
+ # wrote a `_path` / `_url` method anywhere in `app/`, they very likely intend to call it (often
21
+ # Mastodon's pattern: a controller's `private def page_url(page)` invoked by a `link_to` in its own
22
+ # view). The core engine's `call.undefined-method` rule still catches the case where the receiver
23
+ # genuinely cannot see the method.
24
+ # - Ignores `def self.x` (singleton-method definitions) Rails helpers are instance methods on the
25
+ # helper module; class-side `self.x` shapes are usually internal utilities, not view helpers.
26
+ # - Filters the resulting set to names ending in `_path` or `_url` (the dispatch surface the
27
+ # analyzer's rule actually cares about). A helper whose name does not end with either suffix is
28
+ # irrelevant to the rule's false-positive surface, so excluding it costs nothing and keeps the
29
+ # registered set focused.
45
30
  #
46
31
  # Out of scope (intentional):
47
32
  #
48
- # - Helpers defined via `define_method` or via macros like
49
- # `inheritance_traversed_helpers`. These would need
50
- # ADR-16 macro substrate or per-plugin custom recognizers.
51
- # - Helpers inherited from gems (`Devise::Controllers::Helpers`,
52
- # `ViteRails::TagHelpers`, etc.). Those need their own
53
- # handling — Devise auto-routes are covered by the
54
- # companion `DeviseRoutes` generator; other gem-injected
55
- # helpers fall to ADR-25 plugin-contributed RBS or the
56
- # ADR-10 `dependencies.source_inference:` path.
33
+ # - Helpers defined via `define_method` or via macros like `inheritance_traversed_helpers`. These
34
+ # would need ADR-16 macro substrate or per-plugin custom recognizers.
35
+ # - Helpers inherited from gems (`Devise::Controllers::Helpers`, `ViteRails::TagHelpers`, etc.).
36
+ # Those need their own handling — Devise auto-routes are covered by the companion `DeviseRoutes`
37
+ # generator; other gem-injected helpers fall to ADR-25 plugin-contributed RBS or the ADR-10
38
+ # `dependencies.source_inference:` path.
57
39
  module HelperDiscoverer
58
40
  HELPER_SUFFIXES = [/_path\z/, /_url\z/].freeze
59
41
 
60
42
  module_function
61
43
 
62
- # @param contents_per_path [Hash{String => String}]
63
- # file path source text. The caller is responsible
64
- # for reading files (typically through the trusted
65
- # `IoBoundary` so cache invalidation works).
66
- # @return [Set<String>] method names suitable for
67
- # inclusion in the `HelperTable`'s custom-helper set.
44
+ # @param contents_per_path [Hash{String => String}] file path → source text. The caller is
45
+ # responsible for reading files (typically through the trusted `IoBoundary` so cache invalidation
46
+ # works).
47
+ # @return [Set<String>] method names suitable for inclusion in the `HelperTable`'s custom-helper set.
68
48
  def discover(contents_per_path)
69
49
  names = []
70
50
  contents_per_path.each_value do |contents|
71
51
  names.concat(extract_from_contents(contents))
72
52
  rescue StandardError
73
- # Skip any file whose AST walk explodes — discovery
74
- # is best-effort and a parse failure in one helper
75
- # file MUST NOT abort discovery for the rest. Other
76
- # rules will still surface the parse failure on the
77
- # affected file through the core pipeline.
53
+ # Skip any file whose AST walk explodes — discovery is best-effort and a parse failure in one
54
+ # helper file MUST NOT abort discovery for the rest. Other rules will still surface the parse
55
+ # failure on the affected file through the core pipeline.
78
56
  next
79
57
  end
80
58
  names.to_set
@@ -89,17 +67,9 @@ module Rigor
89
67
  walker.helper_names
90
68
  end
91
69
 
92
- # Per-file AST walker. Tracks the current visibility
93
- # mode (`:public` by default; `private` / `protected`
94
- # calls flip the bit) so a `def` declared after a bare
95
- # `private` is excluded.
96
- #
97
- # The walker is recursive into module / class bodies so
98
- # `module UrlHelpers; module Routing; def foo_url; end;
99
- # end; end` registers `foo_url`. It does NOT recurse
100
- # into method bodies (a `def inside def` would be
101
- # `define_method`-shaped, which we skip per the
102
- # docstring).
70
+ # Per-file AST walker. The walker is recursive into module / class bodies so `module UrlHelpers;
71
+ # module Routing; def foo_url; end; end; end` registers `foo_url`. It does NOT recurse into method
72
+ # bodies (a `def inside def` would be `define_method`-shaped, which we skip per the docstring).
103
73
  class ModuleBodyWalker
104
74
  attr_reader :helper_names
105
75
 
@@ -134,11 +104,9 @@ module Rigor
134
104
  def visit_statements(body)
135
105
  return if body.nil?
136
106
 
137
- # Visibility tracking is intentionally absent — see
138
- # the "visibility-agnostic" point in the module
139
- # docstring. A `private def page_url(page)` inside
140
- # a controller IS registered so a caller in the
141
- # paired view does not false-fire `unknown-helper`.
107
+ # Visibility tracking is intentionally absent — see the "visibility-agnostic" point in the
108
+ # module docstring. A `private def page_url(page)` inside a controller IS registered so a
109
+ # caller in the paired view does not false-fire `unknown-helper`.
142
110
  statements_of(body).each do |stmt|
143
111
  case stmt
144
112
  when Prism::DefNode