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
@@ -8,16 +8,12 @@ require_relative "rails_i18n/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-rails-i18n — validates `t('key.path')` /
12
- # `I18n.t(...)` calls against `config/locales/*.yml`.
11
+ # rigor-rails-i18n — validates `t('key.path')` / `I18n.t(...)` calls against `config/locales/*.yml`.
13
12
  #
14
13
  # Tier 1B of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
15
- # Statically reads every YAML file under
16
- # `locale_search_paths` (default `config/locales/`),
17
- # builds a flat `dotted_key => Entry` index keyed by the
18
- # leaf key path, and validates every `t(literal_key, ...)`
19
- # call site against the catalogue. No Rails runtime
20
- # dependency.
14
+ # Statically reads every YAML file under `locale_search_paths` (default `config/locales/`), builds a
15
+ # flat `dotted_key => Entry` index keyed by the leaf key path, and validates every `t(literal_key, ...)`
16
+ # call site against the catalogue. No Rails runtime dependency.
21
17
  #
22
18
  # ## Configuration
23
19
  #
@@ -30,55 +26,37 @@ module Rigor
30
26
  #
31
27
  # ## What it checks
32
28
  #
33
- # 1. **Key existence** — `t('users.welcome')` is flagged
34
- # when `users.welcome` does not appear in any locale.
35
- # 2. **Per-locale coverage** — when the key resolves in
36
- # some locales but not all configured locales, the
37
- # plugin emits a `missing-locale` warning. Suppressed
38
- # when the call site passes `default:`.
39
- # 3. **Interpolation variables** — the leaf string's
40
- # `%{var}` placeholders must match the call's keyword
41
- # arguments. Missing placeholders are errors; extra
42
- # arguments are warnings.
43
- # 4. **View template lazy keys** — `t('.title')` inside
44
- # `app/views/setting/index.html.erb` expands to
45
- # `setting.index.title` and is validated against the
46
- # locale index. ERB, Haml, and Slim templates are
29
+ # 1. **Key existence** — `t('users.welcome')` is flagged when `users.welcome` does not appear in any
30
+ # locale.
31
+ # 2. **Per-locale coverage** — when the key resolves in some locales but not all configured locales,
32
+ # the plugin emits a `missing-locale` warning. Suppressed when the call site passes `default:`.
33
+ # 3. **Interpolation variables** — the leaf string's `%{var}` placeholders must match the call's
34
+ # keyword arguments. Missing placeholders are errors; extra arguments are warnings.
35
+ # 4. **View template lazy keys** — `t('.title')` inside `app/views/setting/index.html.erb` expands to
36
+ # `setting.index.title` and is validated against the locale index. ERB, Haml, and Slim templates are
47
37
  # scanned under `view_search_paths`.
48
38
  #
49
39
  # ## Limitations
50
40
  #
51
- # - Only literal-string keys are validated. `t(key)` with
52
- # a variable receiver is silently passed through.
53
- # - Lazy lookup (`t('.key')`) is supported for controller
54
- # files (`app/controllers/**/*_controller.rb`): the key
55
- # is expanded to `<controller_scope>.<action>.<key>`
56
- # using the file path and the innermost enclosing `def`.
57
- # Lazy keys in non-controller `.rb` files (models, helpers,
58
- # mailers, …) are silently skipped the controller/action
59
- # scope cannot be statically determined there.
60
- # - View template lazy keys (`t('.key')` inside ERB / Haml /
61
- # Slim) are validated for key existence and per-locale
62
- # coverage. Interpolation variable validation is skipped
63
- # for view templates (the hash may come from controller
64
- # instance variables not visible in the template).
65
- # The view scan is a project-wide pass surfaced through the
66
- # per-file diagnostic hook, so under `--workers` each
67
- # fork-pool worker re-emits the full set (the same
68
- # once-per-run limitation the `load-error` path carries);
41
+ # - Only literal-string keys are validated. `t(key)` with a variable receiver is silently passed through.
42
+ # - Lazy lookup (`t('.key')`) is supported for controller files (`app/controllers/**/*_controller.rb`):
43
+ # the key is expanded to `<controller_scope>.<action>.<key>` using the file path and the innermost
44
+ # enclosing `def`. Lazy keys in non-controller `.rb` files (models, helpers, mailers, …) are silently
45
+ # skipped the controller/action scope cannot be statically determined there.
46
+ # - View template lazy keys (`t('.key')` inside ERB / Haml / Slim) are validated for key existence and
47
+ # per-locale coverage. Interpolation variable validation is skipped for view templates (the hash may
48
+ # come from controller instance variables not visible in the template). The view scan is a
49
+ # project-wide pass surfaced through the per-file diagnostic hook, so under `--workers` each fork-pool
50
+ # worker re-emits the full set (the same once-per-run limitation the `load-error` path carries);
69
51
  # sequential `rigor check` is unaffected.
70
- # - Pluralization (`t('errors.messages.too_short',
71
- # count: n)`) is recognised at the call site but the
72
- # `count` key is not used to validate the locale's
73
- # pluralization branches.
74
- # - YAML aliases / merges are accepted (Psych's standard
75
- # `aliases: true`) but custom Ruby classes inside the
76
- # YAML are NOT permitted (`safe_load`).
52
+ # - Pluralization (`t('errors.messages.too_short', count: n)`) is recognised at the call site but the
53
+ # `count` key is not used to validate the locale's pluralization branches.
54
+ # - YAML aliases / merges are accepted (Psych's standard `aliases: true`) but custom Ruby classes
55
+ # inside the YAML are NOT permitted (`safe_load`).
77
56
  class RailsI18n < Rigor::Plugin::Base
78
57
  manifest(
79
58
  id: "rails-i18n",
80
- # Bumped 2026-06-23 — view template lazy-key scanning
81
- # (`t('.key')` inside ERB / Haml / Slim under
59
+ # Bumped 2026-06-23 — view template lazy-key scanning (`t('.key')` inside ERB / Haml / Slim under
82
60
  # `view_search_paths`).
83
61
  version: "0.3.0",
84
62
  description: "Validates I18n `t(key)` calls against `config/locales/*.yml`.",
@@ -89,12 +67,10 @@ module Rigor
89
67
  }
90
68
  )
91
69
 
92
- # `watch:` covers every `.yml` / `.yaml` file under the locale
93
- # search paths so the cache invalidates when locale files are
94
- # added, removed, or edited (ADR-60 WD3). `@load_errors` is a
95
- # producer-side capture: it is populated only when the block
96
- # runs (a cache miss / a watched file changed), which is exactly
97
- # when a malformed YAML must re-surface.
70
+ # `watch:` covers every `.yml` / `.yaml` file under the locale search paths so the cache invalidates
71
+ # when locale files are added, removed, or edited (ADR-60 WD3). `@load_errors` is a producer-side
72
+ # capture: it is populated only when the block runs (a cache miss / a watched file changed), which is
73
+ # exactly when a malformed YAML must re-surface.
98
74
  producer :locale_index, watch: -> { [[@locale_search_paths, "**/*.yml", "**/*.yaml"]] } do |_params|
99
75
  loader = LocaleLoader.new(
100
76
  io_boundary: io_boundary,
@@ -105,14 +81,12 @@ module Rigor
105
81
  index
106
82
  end
107
83
 
108
- # Scans view templates under `view_search_paths` for lazy
109
- # `t('.key')` / `I18n.translate('.key')` calls and validates
110
- # each expanded key against the locale index. Interpolation
111
- # validation is skipped — the hash may come from controller
112
- # instance variables not visible in the template source.
84
+ # Scans view templates under `view_search_paths` for lazy `t('.key')` / `I18n.translate('.key')`
85
+ # calls and validates each expanded key against the locale index. Interpolation validation is
86
+ # skipped the hash may come from controller instance variables not visible in the template source.
113
87
  #
114
- # Watches `**/*.{erb,haml,slim}` under each search root so
115
- # the cache invalidates when templates are edited.
88
+ # Watches `**/*.{erb,haml,slim}` under each search root so the cache invalidates when templates are
89
+ # edited.
116
90
  producer :view_diagnostics, watch: -> { [[@view_search_paths, "**/*.erb", "**/*.haml", "**/*.slim"]] } do |_params|
117
91
  index = producer_value(:locale_index)
118
92
  next [] if index.nil? || index.empty?
@@ -129,11 +103,10 @@ module Rigor
129
103
  @view_diagnostics_emitted = false
130
104
  end
131
105
 
132
- # File-level only: the once-per-run YAML load errors + the
133
- # runtime (cache-load) error + the view-template scan. Per-call
134
- # `t('key')` validation runs over the engine-owned walk via the
135
- # node_rule below (ADR-37). The locale index and view diagnostics
136
- # are lazily loaded + memoised by `producer_value`.
106
+ # File-level only: the once-per-run YAML load errors + the runtime (cache-load) error + the
107
+ # view-template scan. Per-call `t('key')` validation runs over the engine-owned walk via the
108
+ # node_rule below (ADR-37). The locale index and view diagnostics are lazily loaded + memoised by
109
+ # `producer_value`.
137
110
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
138
111
  index = producer_value(:locale_index)
139
112
  diagnostics = []
@@ -151,9 +124,8 @@ module Rigor
151
124
  diagnostics
152
125
  end
153
126
 
154
- # The lazy-key (`t('.key')`) expansion needs the enclosing method
155
- # (the controller action), supplied by the node-rule NodeContext;
156
- # the controller scope comes from the file path.
127
+ # The lazy-key (`t('.key')`) expansion needs the enclosing method (the controller action), supplied
128
+ # by the node-rule NodeContext; the controller scope comes from the file path.
157
129
  node_rule Prism::CallNode do |node, _scope, path, _fc, context|
158
130
  index = producer_value(:locale_index)
159
131
  next [] if index.nil? || index.empty?
@@ -205,12 +177,10 @@ module Rigor
205
177
  end.sort
206
178
  end
207
179
 
208
- # Diagnostics anchor on a path relative to the working
209
- # directorythe same base `File.expand_path` globbed the
210
- # view files against so view diagnostics render and match
211
- # baselines like every other diagnostic (which carry
212
- # project-relative paths). Falls back to the absolute path
213
- # for a view outside the working tree.
180
+ # Diagnostics anchor on a path relative to the working directory — the same base `File.expand_path`
181
+ # globbed the view files against so view diagnostics render and match baselines like every other
182
+ # diagnostic (which carry project-relative paths). Falls back to the absolute path for a view outside
183
+ # the working tree.
214
184
  def relative_view_path(absolute_path)
215
185
  Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
216
186
  rescue ArgumentError
@@ -232,12 +202,9 @@ module Rigor
232
202
  )
233
203
  end
234
204
 
235
- # The runner only invokes `diagnostics_for_file` for
236
- # Ruby files (`paths:` is filtered to `.rb`). YAML
237
- # parse errors therefore can't be anchored on the
238
- # offending locale file directly; instead, we emit
239
- # them once per run on the first analyzed Ruby file,
240
- # naming the offending YAML path in the message.
205
+ # The runner only invokes `diagnostics_for_file` for Ruby files (`paths:` is filtered to `.rb`). YAML
206
+ # parse errors therefore can't be anchored on the offending locale file directly; instead, we emit
207
+ # them once per run on the first analyzed Ruby file, naming the offending YAML path in the message.
241
208
  def consume_load_error_diagnostics(path)
242
209
  return [] if @load_errors_emitted
243
210
 
@@ -6,18 +6,13 @@ require "rigor/source/literals"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class RailsRoutes < Rigor::Plugin::Base
9
- # Walks a parsed file's AST looking for `*_path` /
10
- # `*_url` calls and validates each against the
11
- # plugin's {HelperTable}. Emits info diagnostics for
12
- # recognised helpers and error diagnostics for typos /
13
- # arity mismatches.
9
+ # Walks a parsed file's AST looking for `*_path` / `*_url` calls and validates each against the
10
+ # plugin's {HelperTable}. Emits info diagnostics for recognised helpers and error diagnostics for
11
+ # typos / arity mismatches.
14
12
  module Analyzer
15
- # Built-in Rails helpers we don't want to flag as
16
- # unknown. The plugin's HelperTable describes
17
- # user-declared routes; Rails (and a small set of
18
- # widely-used asset gems) ship built-in helpers
19
- # (`url_for`, `polymorphic_path`, vite_ruby's
20
- # `vite_asset_path`, …) the plugin deliberately ignores.
13
+ # Built-in Rails helpers we don't want to flag as unknown. The plugin's HelperTable describes
14
+ # user-declared routes; Rails (and a small set of widely-used asset gems) ship built-in helpers
15
+ # (`url_for`, `polymorphic_path`, vite_ruby's `vite_asset_path`, …) the plugin deliberately ignores.
21
16
  BUILTIN_PASSTHROUGH = %w[
22
17
  url_for_path url_for_url
23
18
  polymorphic_path polymorphic_url
@@ -32,35 +27,27 @@ module Rigor
32
27
  audio_path audio_url
33
28
  ].freeze
34
29
 
35
- # One route-helper observation. Carries no path/location — the
36
- # caller (the `node_rule` block) positions it via
37
- # `Plugin::Base#diagnostic`.
30
+ # One route-helper observation. Carries no path/location — the caller (the `node_rule` block)
31
+ # positions it via `Plugin::Base#diagnostic`.
38
32
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
39
33
 
40
34
  module_function
41
35
 
42
- # RSpec / Minitest DSL methods that DECLARE a memoized
43
- # local — `let(:foo) { ... }`, `subject(:foo) { ... }`,
44
- # plus the bang and let_it_be variants. The first
45
- # positional Symbol argument is the local name. A bare
46
- # `subject { ... }` (no arg) defines `:subject` itself.
36
+ # RSpec / Minitest DSL methods that DECLARE a memoized local — `let(:foo) { ... }`, `subject(:foo)
37
+ # { ... }`, plus the bang and let_it_be variants. The first positional Symbol argument is the local
38
+ # name. A bare `subject { ... }` (no arg) defines `:subject` itself.
47
39
  SHADOWING_DSL = %i[
48
40
  let let! let_it_be let_it_be!
49
41
  subject subject!
50
42
  ].freeze
51
43
 
52
- # Paths under which `unknown-helper` is suppressed. Route
53
- # helpers are not customarily resolved through these
54
- # directories, so a bare `shared_inbox_url` call inside
55
- # `app/models/account.rb` (the call-site is the
56
- # `accounts.shared_inbox_url` AR column accessor same
57
- # name as a hypothetical route helper) or a `default_url`
58
- # inside `lib/paperclip/url_generator_extensions.rb` (the
59
- # call-site is `Paperclip::UrlGenerator#default_url`, a
60
- # gem-side instance method) would be a false positive.
61
- # The core engine's `call.undefined-method` still catches
62
- # a genuinely unreachable receiver. Cheap path-prefix
63
- # check; no AR-column / gem-include analysis required.
44
+ # Paths under which `unknown-helper` is suppressed. Route helpers are not customarily resolved
45
+ # through these directories, so a bare `shared_inbox_url` call inside `app/models/account.rb` (the
46
+ # call-site is the `accounts.shared_inbox_url` AR column accessor — same name as a hypothetical
47
+ # route helper) or a `default_url` inside `lib/paperclip/url_generator_extensions.rb` (the
48
+ # call-site is `Paperclip::UrlGenerator#default_url`, a gem-side instance method) would be a false
49
+ # positive. The core engine's `call.undefined-method` still catches a genuinely unreachable
50
+ # receiver. Cheap path-prefix check; no AR-column / gem-include analysis required.
64
51
  SKIP_UNKNOWN_HELPER_PATHS = [
65
52
  %r{(?:\A|/)app/models/},
66
53
  %r{(?:\A|/)app/services/},
@@ -78,12 +65,10 @@ module Rigor
78
65
  # @param root [Prism::Node]
79
66
  # @param helper_table [HelperTable]
80
67
  # @return [Array<Diagnostic>]
81
- # The route-helper violations for a single call node (0..2),
82
- # or `[]` when the node is not an implicit `*_path` / `*_url`
83
- # helper call, is shadowed by a same-file binding, or is in a
84
- # suppressed directory. ADR-37: the engine owns the walk; the
85
- # same-file `shadowing` set is built once as the node-rule file
86
- # context.
68
+ # The route-helper violations for a single call node (0..2), or `[]` when the node is not an
69
+ # implicit `*_path` / `*_url` helper call, is shadowed by a same-file binding, or is in a
70
+ # suppressed directory. ADR-37: the engine owns the walk; the same-file `shadowing` set is built
71
+ # once as the node-rule file context.
87
72
  #
88
73
  # @param call_node [Prism::Node]
89
74
  # @param helper_table [HelperTable]
@@ -97,9 +82,8 @@ module Rigor
97
82
  return [] if BUILTIN_PASSTHROUGH.include?(name)
98
83
  return [] if shadowing.include?(name)
99
84
 
100
- # The SKIP set silences both `unknown-helper` and `wrong-arity`:
101
- # a `group_path` inside `app/services/...` is more likely the
102
- # file's own method (an `attr_accessor`) than a route helper.
85
+ # The SKIP set silences both `unknown-helper` and `wrong-arity`: a `group_path` inside
86
+ # `app/services/...` is more likely the file's own method (an `attr_accessor`) than a route helper.
103
87
  suppress_unknown = SKIP_UNKNOWN_HELPER_PATHS.any? { |re| path.match?(re) }
104
88
 
105
89
  entry = helper_table.find(name)
@@ -111,26 +95,21 @@ module Rigor
111
95
  violations << arity if arity
112
96
  violations
113
97
  elsif helper_table.recognised?(name) || suppress_unknown
114
- # Recognised custom / dynamic helper (no arity to check), or
115
- # a directory where helpers aren't customarily resolved.
98
+ # Recognised custom / dynamic helper (no arity to check), or a directory where helpers aren't
99
+ # customarily resolved.
116
100
  []
117
101
  else
118
102
  [unknown_helper_violation(name, helper_table)]
119
103
  end
120
104
  end
121
105
 
122
- # Walks the AST once and returns the Set of names that
123
- # shadow a route helper for this file. Includes:
106
+ # Walks the AST once and returns the Set of names that shadow a route helper for this file. Includes:
124
107
  #
125
- # - `def name` declarations (any level — the
126
- # per-method-scope visibility model Ruby uses means a
127
- # local `def` shadows the helper at every call site
128
- # reachable from where it is defined; we approximate
129
- # "reachable" with "anywhere in the same file").
130
- # - RSpec `let` / `let!` / `let_it_be` / `let_it_be!` /
131
- # `subject` / `subject!` declarations.
132
- # - Local assignments at any scope
133
- # (`foo_url = "..."`).
108
+ # - `def name` declarations (any level — the per-method-scope visibility model Ruby uses means a
109
+ # local `def` shadows the helper at every call site reachable from where it is defined; we
110
+ # approximate "reachable" with "anywhere in the same file").
111
+ # - RSpec `let` / `let!` / `let_it_be` / `let_it_be!` / `subject` / `subject!` declarations.
112
+ # - Local assignments at any scope (`foo_url = "..."`).
134
113
  def collect_shadowing_names(root)
135
114
  names = Set.new
136
115
  Source::NodeWalker.each(root) do |node|
@@ -160,22 +139,17 @@ module Rigor
160
139
  end
161
140
  end
162
141
 
163
- # `*_path` / `*_url` calls without an explicit
164
- # receiver. Calls like `obj.users_path` or
165
- # `Foo::users_path` are NOT route-helper invocations
166
- # in Rails — controllers / views call helpers
142
+ # `*_path` / `*_url` calls without an explicit receiver. Calls like `obj.users_path` or
143
+ # `Foo::users_path` are NOT route-helper invocations in Rails — controllers / views call helpers
167
144
  # implicitly.
168
145
  def implicit_helper_call?(node)
169
146
  node.receiver.nil? && (node.name.to_s.end_with?("_path") || node.name.to_s.end_with?("_url"))
170
147
  end
171
148
 
172
- # Paths where the implicit-params-fill pattern is
173
- # idiomatic controllers / mailers / views routinely
174
- # call `*_path` / `*_url` helpers with fewer args than
175
- # the route's static placeholder count because Rails
176
- # fills `:foo_id` segments from `request.params` at
177
- # runtime (controllers / views) or from the call's
178
- # polymorphic-friendly receiver (mailers).
149
+ # Paths where the implicit-params-fill pattern is idiomatic — controllers / mailers / views
150
+ # routinely call `*_path` / `*_url` helpers with fewer args than the route's static placeholder
151
+ # count because Rails fills `:foo_id` segments from `request.params` at runtime (controllers /
152
+ # views) or from the call's polymorphic-friendly receiver (mailers).
179
153
  IMPLICIT_FILL_PATHS = [
180
154
  %r{(?:\A|/)app/controllers/},
181
155
  %r{(?:\A|/)app/mailers/},
@@ -200,40 +174,29 @@ module Rigor
200
174
  def arity_violation(call_node, entry, helper_table, path)
201
175
  args = call_node.arguments&.arguments || []
202
176
  actual = args.size
203
- # Uncountable nouns (`news` / `series` / `media`) cause
204
- # Rails to register two entries under the same helper
205
- # name `news_path` accepts both arity 0 (index) and
206
- # arity 1 (show). The HelperTable multimap stores both;
207
- # accepts_arity? checks the full set.
177
+ # Uncountable nouns (`news` / `series` / `media`) cause Rails to register two entries under the
178
+ # same helper name `news_path` accepts both arity 0 (index) and arity 1 (show). The HelperTable
179
+ # multimap stores both; accepts_arity? checks the full set.
208
180
  return nil if helper_table.accepts_arity?(entry.name, actual)
209
181
 
210
- # Rails accepts a kwargs-only call shape that supplies
211
- # route segments by name:
182
+ # Rails accepts a kwargs-only call shape that supplies route segments by name:
212
183
  # short_account_status_url(account_username: u, id: i)
213
- # for the route `/@:account_username/:id` (arity 2).
214
- # Our positional-arg count is 1 (the KeywordHashNode),
215
- # so the strict check rejects but Rails would
216
- # resolve every segment from the hash. When the call
217
- # has a trailing KeywordHashNode AND the positional
218
- # count (excluding it) is `<= expected_arity`, accept
219
- # — the kwargs may carry the missing segments.
184
+ # for the route `/@:account_username/:id` (arity 2). Our positional-arg count is 1 (the
185
+ # KeywordHashNode), so the strict check rejects — but Rails would resolve every segment from the
186
+ # hash. When the call has a trailing KeywordHashNode AND the positional count (excluding it) is
187
+ # `<= expected_arity`, accept the kwargs may carry the missing segments.
220
188
  if args.last.is_a?(Prism::KeywordHashNode)
221
189
  positional = actual - 1
222
190
  return nil if helper_table.acceptable_arities(entry.name).any? { |exp| positional <= exp }
223
191
  end
224
192
 
225
- # Underflow tolerance for controllers / mailers /
226
- # views. A `redirect_to namespace_project_milestones_path`
227
- # inside `Projects::MilestonesController` legitimately
228
- # passes 0 args Rails fills the missing `:namespace_id`
229
- # / `:project_id` segments from `request.params` at
230
- # runtime. Mailers reach helpers polymorphically too
231
- # (`project_commit_url(commit)` resolves project from
232
- # commit.project). Silence when `actual < min_arity`
233
- # AND the call site is in a controller / mailer / view
234
- # directory (where the implicit-params-fill pattern is
235
- # idiomatic). Overflow (`actual > max_arity`) still
236
- # fires — that's almost always a typo.
193
+ # Underflow tolerance for controllers / mailers / views. A `redirect_to
194
+ # namespace_project_milestones_path` inside `Projects::MilestonesController` legitimately passes
195
+ # 0 args — Rails fills the missing `:namespace_id` / `:project_id` segments from `request.params`
196
+ # at runtime. Mailers reach helpers polymorphically too (`project_commit_url(commit)` resolves
197
+ # project from commit.project). Silence when `actual < min_arity` AND the call site is in a
198
+ # controller / mailer / view directory (where the implicit-params-fill pattern is idiomatic).
199
+ # Overflow (`actual > max_arity`) still fires — that's almost always a typo.
237
200
  arities_set = helper_table.acceptable_arities(entry.name)
238
201
  min_arity = arities_set.min
239
202
  return nil if actual < min_arity && implicit_fill_path?(path)
@@ -248,8 +211,8 @@ module Rigor
248
211
  end
249
212
 
250
213
  def unknown_helper_violation(name, helper_table)
251
- # ADR-39 / boilerplate 0c — the shared DidYouMean-backed suggester
252
- # replaces the hand-rolled Levenshtein this module used to carry.
214
+ # ADR-39 / boilerplate 0c — the shared DidYouMean-backed suggester replaces the hand-rolled
215
+ # Levenshtein this module used to carry.
253
216
  suggestion = Rigor::Plugin::Base.suggest(name, helper_table.names)
254
217
  message = "no route helper `#{name}`"
255
218
  message += " (did you mean `#{suggestion}`?)" if suggestion