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
@@ -1,26 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Convenience entry point. `require "rigor-rails"` requires
4
- # every Tier 1+2 Rails ecosystem plugin in one go, so projects
5
- # that prefer a single require statement (some `spec_helper`
6
- # patterns, ad-hoc scripts) do not have to list seven require
7
- # lines.
3
+ # Convenience entry point. `require "rigor-rails"` requires every Tier 1+2 Rails ecosystem plugin in one go,
4
+ # so projects that prefer a single require statement (some `spec_helper` patterns, ad-hoc scripts) do not
5
+ # have to list seven require lines.
8
6
  #
9
- # Note: requiring this entry point does NOT mark every plugin as
10
- # active. The Rigor plugin loader walks `.rigor.yml`'s `plugins:`
11
- # list and instantiates only the plugins enumerated there. This
12
- # is per ADR-12 WD1's "Gemfile-convenience meta-gem" pattern —
13
- # users still control which plugins participate in analysis via
14
- # `.rigor.yml`.
7
+ # Note: requiring this entry point does NOT mark every plugin as active. The Rigor plugin loader walks
8
+ # `.rigor.yml`'s `plugins:` list and instantiates only the plugins enumerated there. This is per ADR-12
9
+ # WD1's "Gemfile-convenience meta-gem" pattern users still control which plugins participate in analysis
10
+ # via `.rigor.yml`.
15
11
  #
16
- # Sub-plugins ARE registered with `Rigor::Plugin` when this file
17
- # loads (each gem's entry point side-effects a `Plugin.register`
18
- # call); the loader's lookup phase finds them by id when listed.
12
+ # Sub-plugins ARE registered with `Rigor::Plugin` when this file loads (each gem's entry point side-effects
13
+ # a `Plugin.register` call); the loader's lookup phase finds them by id when listed.
19
14
  #
20
- # Adding the gem to a project's Gemfile without listing any
21
- # plugin in `.rigor.yml` is harmless: the requires happen on
22
- # `Bundler.require`, but no plugin's `init` / `prepare` / hooks
23
- # run.
15
+ # Adding the gem to a project's Gemfile without listing any plugin in `.rigor.yml` is harmless: the requires
16
+ # happen on `Bundler.require`, but no plugin's `init` / `prepare` / hooks run.
24
17
 
25
18
  require "rigor-rails-routes"
26
19
  require "rigor-rails-i18n"
@@ -6,85 +6,64 @@ require "prism"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class RailsI18n < Rigor::Plugin::Base
9
- # Walks a parsed file's AST looking for `t(...)` /
10
- # `I18n.t(...)` / `I18n.translate(...)` calls with a
11
- # literal-string first argument. Calls with a non-literal
12
- # key (variable, expression) are silently passed through —
13
- # the plugin only validates what it can prove statically.
9
+ # Walks a parsed file's AST looking for `t(...)` / `I18n.t(...)` / `I18n.translate(...)` calls with a
10
+ # literal-string first argument. Calls with a non-literal key (variable, expression) are silently
11
+ # passed through the plugin only validates what it can prove statically.
14
12
  #
15
13
  # ## What gets emitted per recognised call
16
14
  #
17
- # - `plugin.rails-i18n.translation-call` (info) names the
18
- # key and the locales it resolves in.
19
- # - `plugin.rails-i18n.unknown-key` (error) when the key
20
- # is missing from every loaded locale; the message
21
- # includes a did-you-mean suggestion drawn from the
22
- # index.
23
- # - `plugin.rails-i18n.missing-locale` (warning) when the
24
- # key resolves in some configured locales but is absent
25
- # from at least one. Suppressed when the call passes
26
- # `default:` (the user has signalled they're aware of
27
- # the partial coverage).
28
- # - `plugin.rails-i18n.wrong-interpolation` (error) when
29
- # the call's interpolation hash uses keys that don't
30
- # match the value's `%{var}` placeholders, or omits a
31
- # required placeholder.
15
+ # - `plugin.rails-i18n.translation-call` (info) names the key and the locales it resolves in.
16
+ # - `plugin.rails-i18n.unknown-key` (error) when the key is missing from every loaded locale; the
17
+ # message includes a did-you-mean suggestion drawn from the index.
18
+ # - `plugin.rails-i18n.missing-locale` (warning) when the key resolves in some configured locales but
19
+ # is absent from at least one. Suppressed when the call passes `default:` (the user has signalled
20
+ # they're aware of the partial coverage).
21
+ # - `plugin.rails-i18n.wrong-interpolation` (error) when the call's interpolation hash uses keys that
22
+ # don't match the value's `%{var}` placeholders, or omits a required placeholder.
32
23
  module Analyzer
33
24
  TRANSLATE_METHODS = %i[t translate].freeze
34
25
 
35
- # Methods that are always I18n receivers (`I18n.t`,
36
- # `::I18n.t`).
26
+ # Methods that are always I18n receivers (`I18n.t`, `::I18n.t`).
37
27
  I18N_RECEIVER_NAMES = %w[I18n ::I18n].freeze
38
28
 
39
- # Matches controller file paths so lazy keys (`.key`)
40
- # can be expanded to `<controller_scope>.<action>.<key>`.
41
- # Captures the path segment between `controllers/` and
29
+ # Matches controller file paths so lazy keys (`.key`) can be expanded to
30
+ # `<controller_scope>.<action>.<key>`. Captures the path segment between `controllers/` and
42
31
  # `_controller.rb` (e.g. `users`, `admin/users`).
43
32
  CONTROLLER_PATH_RE = %r{(?:^|/)controllers/(.+)_controller\.rb$}
44
33
 
45
- # Matches Rails view-template file paths to derive the
46
- # I18n "virtual path" the scope that Rails uses for
47
- # lazy `t('.key')` lookups inside a template.
34
+ # Matches Rails view-template file paths to derive the I18n "virtual path" — the scope that Rails
35
+ # uses for lazy `t('.key')` lookups inside a template.
48
36
  #
49
- # Captures the path segment between `views/` and the
50
- # format+variant+handler suffix, then strips a leading
51
- # underscore from each segment — Rails templates resolve
52
- # `_form.html.erb` as "form", not "_form".
37
+ # Captures the path segment between `views/` and the format+variant+handler suffix, then strips a
38
+ # leading underscore from each segment — Rails templates resolve `_form.html.erb` as "form", not
39
+ # "_form".
53
40
  #
54
41
  # app/views/setting/index.html.erb → setting.index
55
42
  # app/views/admin/users/new.html.erb → admin.users.new
56
43
  # app/views/home/index.html+mobile.erb → home.index
57
44
  # app/views/users/_form.html.erb → users.form
58
45
  #
59
- # The view-scope lazy expansion replaces the action
60
- # part with the view's virtual path:
46
+ # The view-scope lazy expansion replaces the action part with the view's virtual path:
61
47
  # `<%= t('.title') %>` → `setting.index.title`
62
48
  VIEW_SCOPE_RE = %r{views/(.+?)\.(?:\w+)(?:\+\w+)?\.\w+\z}
63
49
 
64
- # Regex to extract lazy-key arguments from ERB / HTML
65
- # template content. Matches `t('.key')`, `t(".key")`,
66
- # `I18n.t('.key')`, and `I18n.translate('.key')` with a
67
- # leading dot on the key string. Captures only the key
68
- # part after the dot.
50
+ # Regex to extract lazy-key arguments from ERB / HTML template content. Matches `t('.key')`,
51
+ # `t(".key")`, `I18n.t('.key')`, and `I18n.translate('.key')` with a leading dot on the key string.
52
+ # Captures only the key part after the dot.
69
53
  LAZY_T_KEY_RE = /\b(?:I18n\.)?(?:t|translate)\s*\(\s*(?:"\.([^"\\]*)"|'\.([^'\\]*)')/
70
54
 
71
- # Reserved option keys — these are recognised by I18n
72
- # itself and not treated as interpolation variables.
55
+ # Reserved option keys — these are recognised by I18n itself and not treated as interpolation
56
+ # variables.
73
57
  RESERVED_OPTION_KEYS = %i[
74
58
  default scope locale count raise throw fallback
75
59
  fallback_in_progress separator deep_interpolation
76
60
  ].to_set.freeze
77
61
 
78
- # Key prefixes Rails / `rails-i18n` ship in every
79
- # locale by default. Projects whose own locale files
80
- # don't redeclare them still get them at runtime (via
81
- # the `rails-i18n` gem's bundled locale catalogues).
82
- # `t('date.order')` is the canonical Mastodon case
83
- # used by `Settings::Date::Order` and date-of-birth
84
- # selects, never authored project-side. Skip
85
- # `unknown-key` for these; downstream interpolation
86
- # checks have nothing to validate without a leaf entry
87
- # so they decline silently too.
62
+ # Key prefixes Rails / `rails-i18n` ship in every locale by default. Projects whose own locale
63
+ # files don't redeclare them still get them at runtime (via the `rails-i18n` gem's bundled locale
64
+ # catalogues). `t('date.order')` is the canonical Mastodon case used by `Settings::Date::Order`
65
+ # and date-of-birth selects, never authored project-side. Skip `unknown-key` for these; downstream
66
+ # interpolation checks have nothing to validate without a leaf entry so they decline silently too.
88
67
  RAILS_SHIPPED_KEY_PREFIXES = %w[
89
68
  date.
90
69
  time.
@@ -101,9 +80,8 @@ module Rigor
101
80
  activerecord.errors.models.
102
81
  ].freeze
103
82
 
104
- # One translation-call observation. Carries no path/location —
105
- # the caller (the `node_rule` block) positions it via
106
- # `Plugin::Base#diagnostic`.
83
+ # One translation-call observation. Carries no path/location — the caller (the `node_rule` block)
84
+ # positions it via `Plugin::Base#diagnostic`.
107
85
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
108
86
 
109
87
  module_function
@@ -118,12 +96,10 @@ module Rigor
118
96
  # @param locale_index [LocaleIndex]
119
97
  # @param configured_locales [Array<String>]
120
98
  # @return [Array<Diagnostic>]
121
- # The translation violations for a single call node, or `[]`
122
- # when it is not a recognised `t` / `translate` call with a
123
- # literal key. ADR-37: the engine owns the walk; `action` (the
124
- # enclosing method, for lazy `t('.key')` expansion) comes from
125
- # the node-rule `NodeContext`, and `controller_scope` from the
126
- # file path.
99
+ # The translation violations for a single call node, or `[]` when it is not a recognised `t` /
100
+ # `translate` call with a literal key. ADR-37: the engine owns the walk; `action` (the enclosing
101
+ # method, for lazy `t('.key')` expansion) comes from the node-rule `NodeContext`, and
102
+ # `controller_scope` from the file path.
127
103
  #
128
104
  # @param call_node [Prism::Node]
129
105
  # @param locale_index [LocaleIndex]
@@ -143,11 +119,11 @@ module Rigor
143
119
  options = options_hash(call_node)
144
120
  entry = locale_index.find(literal_key)
145
121
  if entry.nil?
146
- # CLDR pluralization namespace: `t('accounts.posts', count: n)`
147
- # resolves through a `.one` / `.other` child — not missing.
122
+ # CLDR pluralization namespace: `t('accounts.posts', count: n)` resolves through a `.one` /
123
+ # `.other` child — not missing.
148
124
  return [] if locale_index.pluralization_namespace?(literal_key)
149
- # Rails / rails-i18n ship `date.order`, `time.am`, etc. in
150
- # every locale at runtime even when project files omit them.
125
+ # Rails / rails-i18n ship `date.order`, `time.am`, etc. in every locale at runtime even when
126
+ # project files omit them.
151
127
  return [] if rails_shipped_key?(literal_key)
152
128
 
153
129
  return [unknown_key_violation(literal_key, locale_index)]
@@ -162,9 +138,8 @@ module Rigor
162
138
  violations
163
139
  end
164
140
 
165
- # Derives the Rails controller scope from the file path,
166
- # e.g. `app/controllers/admin/users_controller.rb` → `admin.users`.
167
- # Returns nil for non-controller paths.
141
+ # Derives the Rails controller scope from the file path, e.g.
142
+ # `app/controllers/admin/users_controller.rb` → `admin.users`. Returns nil for non-controller paths.
168
143
  def controller_scope_from_path(path)
169
144
  m = CONTROLLER_PATH_RE.match(path.to_s)
170
145
  return nil unless m
@@ -198,12 +173,10 @@ module Rigor
198
173
  violations
199
174
  end
200
175
 
201
- # Expands a lazy key (starting with `.`) to its full
202
- # dotted path using the controller scope and action name.
203
- # Returns the raw key unchanged for absolute keys.
204
- # Returns nil for lazy keys outside a controller context
205
- # or without an enclosing action — these are silently
206
- # skipped to avoid false positives.
176
+ # Expands a lazy key (starting with `.`) to its full dotted path using the controller scope and
177
+ # action name. Returns the raw key unchanged for absolute keys. Returns nil for lazy keys outside a
178
+ # controller context or without an enclosing action — these are silently skipped to avoid false
179
+ # positives.
207
180
  def expand_key(raw_key, controller_scope:, action:)
208
181
  return raw_key unless raw_key.start_with?(".")
209
182
  return nil unless controller_scope && action
@@ -219,9 +192,8 @@ module Rigor
219
192
  I18N_RECEIVER_NAMES.include?(receiver_name)
220
193
  end
221
194
 
222
- # Extracts the literal-string first argument when
223
- # present. Returns nil for variable / expression keys —
224
- # only literal keys are statically validated.
195
+ # Extracts the literal-string first argument when present. Returns nil for variable / expression
196
+ # keys only literal keys are statically validated.
225
197
  def literal_key_for(call_node)
226
198
  args = call_node.arguments&.arguments || []
227
199
  return nil if args.empty?
@@ -232,11 +204,9 @@ module Rigor
232
204
  first.unescaped
233
205
  end
234
206
 
235
- # Pulls the interpolation hash from the call's
236
- # arguments. The trailing `Hash` argument (or
237
- # `Prism::KeywordHashNode`) carries both reserved I18n
238
- # options (`default:`, `scope:`, …) and interpolation
239
- # variables. Returns:
207
+ # Pulls the interpolation hash from the call's arguments. The trailing `Hash` argument (or
208
+ # `Prism::KeywordHashNode`) carries both reserved I18n options (`default:`, `scope:`, …) and
209
+ # interpolation variables. Returns:
240
210
  # {
241
211
  # :has_default => bool,
242
212
  # :all_keys => Set<Symbol> (every assoc key in the hash),
@@ -244,15 +214,11 @@ module Rigor
244
214
  # :hash_node => Prism::Node (or nil)
245
215
  # }
246
216
  #
247
- # Note: a reserved option key (e.g. `count:`) can
248
- # also serve as an interpolation value when the
249
- # locale's leaf string has `%{count}`. The analyzer
250
- # therefore checks the missing-placeholder set
251
- # against `all_keys` (so `count:` satisfies a
252
- # `%{count}` placeholder) and the
253
- # extra-placeholder set against `non_reserved` (so
254
- # `default:` / `scope:` are never reported as
255
- # extra interpolation arguments).
217
+ # Note: a reserved option key (e.g. `count:`) can also serve as an interpolation value when the
218
+ # locale's leaf string has `%{count}`. The analyzer therefore checks the missing-placeholder set
219
+ # against `all_keys` (so `count:` satisfies a `%{count}` placeholder) and the extra-placeholder set
220
+ # against `non_reserved` (so `default:` / `scope:` are never reported as extra interpolation
221
+ # arguments).
256
222
  def options_hash(call_node)
257
223
  args = call_node.arguments&.arguments || []
258
224
  last = args.last
@@ -275,8 +241,8 @@ module Rigor
275
241
  end
276
242
 
277
243
  def collect_assoc_keys(hash_node)
278
- # Both `Prism::HashNode` and `Prism::KeywordHashNode`
279
- # expose `#elements`, so a single path handles both.
244
+ # Both `Prism::HashNode` and `Prism::KeywordHashNode` expose `#elements`, so a single path
245
+ # handles both.
280
246
  hash_node.elements.filter_map do |element|
281
247
  next nil unless element.is_a?(Prism::AssocNode)
282
248
 
@@ -3,23 +3,17 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class RailsI18n < Rigor::Plugin::Base
6
- # Frozen catalogue of every dotted key discovered across
7
- # all loaded locale files. Each entry tracks the locales
8
- # the key appears in and, for each locale, the set of
9
- # `%{var}` interpolation placeholders observed in the
10
- # leaf string.
6
+ # Frozen catalogue of every dotted key discovered across all loaded locale files. Each entry tracks
7
+ # the locales the key appears in and, for each locale, the set of `%{var}` interpolation placeholders
8
+ # observed in the leaf string.
11
9
  #
12
- # The catalogue is intentionally lossy: it only records
13
- # the *presence* of each key per locale and the
14
- # placeholder names. The actual translated values are
15
- # not retained — the analyzer doesn't need them and
16
- # keeping them would bloat the cache slice.
10
+ # The catalogue is intentionally lossy: it only records the *presence* of each key per locale and the
11
+ # placeholder names. The actual translated values are not retained — the analyzer doesn't need them
12
+ # and keeping them would bloat the cache slice.
17
13
  class LocaleIndex
18
- # `placeholders` is a Hash: `locale_name => Set<String>`.
19
- # `array_value` is true when at least one locale's leaf
20
- # is an Array (used by `l(time, format:)` and similar).
21
- # `value_kinds` is a Hash: `locale_name => Symbol`
22
- # (`:string` / `:array` / `:hash`).
14
+ # `placeholders` is a Hash: `locale_name => Set<String>`. `array_value` is true when at least one
15
+ # locale's leaf is an Array (used by `l(time, format:)` and similar). `value_kinds` is a Hash:
16
+ # `locale_name => Symbol` (`:string` / `:array` / `:hash`).
23
17
  Entry = Data.define(:dotted_key, :placeholders, :value_kinds) do
24
18
  def locales
25
19
  placeholders.keys
@@ -33,8 +27,7 @@ module Rigor
33
27
  placeholders.fetch(locale.to_s) { Set.new }
34
28
  end
35
29
 
36
- # Union of placeholder names across all known
37
- # locales — used by the analyzer when no specific
30
+ # Union of placeholder names across all known locales — used by the analyzer when no specific
38
31
  # locale is in scope.
39
32
  def all_placeholders
40
33
  placeholders.values.reduce(Set.new) { |acc, set| acc | set }
@@ -44,8 +37,7 @@ module Rigor
44
37
  attr_reader :entries, :locales
45
38
 
46
39
  # @param entries [Array<Entry>]
47
- # @param locales [Array<String>] all locale names
48
- # that contributed at least one key.
40
+ # @param locales [Array<String>] all locale names that contributed at least one key.
49
41
  def initialize(entries, locales:)
50
42
  @entries = entries.freeze
51
43
  @locales = locales.dup.freeze
@@ -62,19 +54,15 @@ module Rigor
62
54
  @by_key.key?(dotted_key.to_s)
63
55
  end
64
56
 
65
- # CLDR plural form keys recognised by Ruby I18n. When a
66
- # locale defines `accounts.posts.one`, `accounts.posts.other`,
67
- # the call `t('accounts.posts', count: n)` resolves into
68
- # the matching plural sub-key the parent `accounts.posts`
69
- # is a **pluralization namespace**, not a missing key.
70
- # Mastodon hits this on `accounts.posts` /
71
- # `accounts.following` / `accounts.followers` for the post,
72
- # follow, and follower counts shown on the profile page.
57
+ # CLDR plural form keys recognised by Ruby I18n. When a locale defines `accounts.posts.one`,
58
+ # `accounts.posts.other`, the call `t('accounts.posts', count: n)` resolves into the matching
59
+ # plural sub-key — the parent `accounts.posts` is a **pluralization namespace**, not a missing key.
60
+ # Mastodon hits this on `accounts.posts` / `accounts.following` / `accounts.followers` for the
61
+ # post, follow, and follower counts shown on the profile page.
73
62
  PLURAL_SUBKEYS = %w[zero one two few many other].freeze
74
63
 
75
- # Returns true when the dotted key itself isn't a leaf in
76
- # any locale, but at least one of its CLDR plural form
77
- # children exists.
64
+ # Returns true when the dotted key itself isn't a leaf in any locale, but at least one of its CLDR
65
+ # plural form children exists.
78
66
  def pluralization_namespace?(dotted_key)
79
67
  base = dotted_key.to_s
80
68
  PLURAL_SUBKEYS.any? { |sub| @by_key.key?("#{base}.#{sub}") }
@@ -88,14 +76,12 @@ module Rigor
88
76
  @entries.size
89
77
  end
90
78
 
91
- # All known dotted keys, sorted for stable did-you-mean
92
- # output.
79
+ # All known dotted keys, sorted for stable did-you-mean output.
93
80
  def keys
94
81
  @by_key.keys.sort
95
82
  end
96
83
 
97
- # Returns the locales (set of strings) in which a key
98
- # is *missing*, given the configured locale list.
84
+ # Returns the locales (set of strings) in which a key is *missing*, given the configured locale list.
99
85
  def missing_locales_for(dotted_key, configured_locales:)
100
86
  entry = find(dotted_key)
101
87
  return configured_locales.to_set if entry.nil?
@@ -7,27 +7,21 @@ require_relative "locale_index"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class RailsI18n < Rigor::Plugin::Base
10
- # Walks `locale_search_paths` for `.yml` / `.yaml`
11
- # files, reads each through the trusted IoBoundary,
12
- # parses with `YAML.safe_load`, and folds the resulting
13
- # nested hash into a flat `dotted_key => Entry` table.
10
+ # Walks `locale_search_paths` for `.yml` / `.yaml` files, reads each through the trusted IoBoundary,
11
+ # parses with `YAML.safe_load`, and folds the resulting nested hash into a flat `dotted_key => Entry`
12
+ # table.
14
13
  #
15
- # The top-level YAML key is the locale (`en:`, `ja:`,
16
- # …). Anything underneath is recursively flattened into
17
- # dotted keys (`users.welcome`, `errors.messages.blank`,
18
- # …). For each leaf string, `%{var}` placeholders are
19
- # extracted via a simple regex.
14
+ # The top-level YAML key is the locale (`en:`, `ja:`, …). Anything underneath is recursively
15
+ # flattened into dotted keys (`users.welcome`, `errors.messages.blank`, …). For each leaf string,
16
+ # `%{var}` placeholders are extracted via a simple regex.
20
17
  #
21
- # Files that fail to parse are skipped with a load-error
22
- # diagnostic surfaced through the plugin's error
23
- # channel. Non-Hash YAML roots (e.g. a top-level
24
- # sequence) are also skipped — the format is locale-keyed
25
- # by convention.
18
+ # Files that fail to parse are skipped with a load-error diagnostic surfaced through the plugin's
19
+ # error channel. Non-Hash YAML roots (e.g. a top-level sequence) are also skipped — the format is
20
+ # locale-keyed by convention.
26
21
  class LocaleLoader
27
22
  PLACEHOLDER_RE = /%\{(?<name>[^}]+)\}/
28
23
 
29
- # Errno classes that indicate "this file is not
30
- # readable as a YAML locale" — swallowed so a single
24
+ # Errno classes that indicate "this file is not readable as a YAML locale" — swallowed so a single
31
25
  # bad path doesn't take down the rest of the index.
32
26
  IO_ERRORS = [Errno::ENOENT, Errno::EACCES, Errno::EISDIR].freeze
33
27
 
@@ -100,19 +94,15 @@ module Rigor
100
94
  end.sort
101
95
  end
102
96
 
103
- # Recursively walks the per-locale subtree, yielding
104
- # `[dotted_key, leaf_value]` for each leaf. Hash leaves are
105
- # *not* recorded as entries themselves — only their
106
- # descendants — but every leaf scalar / array IS recorded.
97
+ # Recursively walks the per-locale subtree, yielding `[dotted_key, leaf_value]` for each leaf. Hash
98
+ # leaves are *not* recorded as entries themselves — only their descendants — but every leaf scalar
99
+ # / array IS recorded.
107
100
  #
108
- # `breadcrumbs` is a single mutable stack reused across the
109
- # whole walk (push before recursing, pop after): for the
110
- # 530-file / 14 MB Mastodon locale corpus the old
111
- # `flat_map { flatten_tree(v, breadcrumbs + [k]) }` shape
112
- # allocated a fresh breadcrumb Array at every node plus an
113
- # intermediate result Array at every level — millions of
114
- # short-lived objects and the run's top allocation site. The
115
- # dotted key is still materialised once per leaf (it has to
101
+ # `breadcrumbs` is a single mutable stack reused across the whole walk (push before recursing, pop
102
+ # after): for the 530-file / 14 MB Mastodon locale corpus the old
103
+ # `flat_map { flatten_tree(v, breadcrumbs + [k]) }` shape allocated a fresh breadcrumb Array at
104
+ # every node plus an intermediate result Array at every level — millions of short-lived objects and
105
+ # the run's top allocation site. The dotted key is still materialised once per leaf (it has to
116
106
  # be); everything else is now allocation-free traversal.
117
107
  def each_flattened(node, breadcrumbs, &)
118
108
  if node.is_a?(Hash)
@@ -129,9 +119,8 @@ module Rigor
129
119
  def extract_placeholders(value)
130
120
  case value
131
121
  when String
132
- # Most locale leaves carry no `%{var}`; skip the scan +
133
- # flatten + to_set allocation trio for them. A string with
134
- # no `%{` yields an empty placeholder set either way.
122
+ # Most locale leaves carry no `%{var}`; skip the scan + flatten + to_set allocation trio for
123
+ # them. A string with no `%{` yields an empty placeholder set either way.
135
124
  return Set.new unless value.include?("%{")
136
125
 
137
126
  value.scan(PLACEHOLDER_RE).flatten.to_set