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
@@ -9,13 +9,12 @@ require_relative "rails_routes/analyzer"
9
9
 
10
10
  module Rigor
11
11
  module Plugin
12
- # rigor-rails-routes — validates Rails route-helper calls
13
- # (`users_path`, `edit_user_path(@user)`, …) against the
14
- # project's `config/routes.rb`.
12
+ # rigor-rails-routes — validates Rails route-helper calls (`users_path`, `edit_user_path(@user)`, …)
13
+ # against the project's `config/routes.rb`.
15
14
  #
16
15
  # Tier 1A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
17
- # Statically interprets the routes DSL via Prism — no
18
- # `rails` runtime dependency. Recognised v0.1.0 surface:
16
+ # Statically interprets the routes DSL via Prism — no `rails` runtime dependency. Recognised v0.1.0
17
+ # surface:
19
18
  #
20
19
  # - `Rails.application.routes.draw do ... end`
21
20
  # - `resources :name [, only: [...] | except: [...]]`
@@ -25,10 +24,8 @@ module Rigor
25
24
  # - One level of `namespace :foo do ... end`
26
25
  # - One level of nested `resources`
27
26
  #
28
- # The plugin publishes its parsed `:helper_table` through
29
- # the ADR-9 cross-plugin fact store; `rigor-actionpack`
30
- # Phase 4 consumes it for route-helper validation in
31
- # controller code.
27
+ # The plugin publishes its parsed `:helper_table` through the ADR-9 cross-plugin fact store;
28
+ # `rigor-actionpack` Phase 4 consumes it for route-helper validation in controller code.
32
29
  #
33
30
  # ## Configuration
34
31
  #
@@ -39,66 +36,47 @@ module Rigor
39
36
  #
40
37
  # ## Limitations (v0.1.0)
41
38
  #
42
- # - `scope :path:` / `scope :module:` / `scope :as:` are
43
- # not interpreted — helpers nested inside these
39
+ # - `scope :path:` / `scope :module:` / `scope :as:` are not interpreted — helpers nested inside these
44
40
  # constructs are silently skipped.
45
- # - Constraints / format restrictions / mountable
46
- # engines are out of scope.
47
- # - The English inflector is intentionally tiny: it
48
- # handles `posts` `post`, `users` `user`,
49
- # `categories` ↔ `category`, `boxes` ↔ `box`. Custom
50
- # inflections (`fish` ↔ `fish`, `child` ↔ `children`)
51
- # are out of scope; users who need them ship a hand-
52
- # written RBS for the affected helper.
41
+ # - Constraints / format restrictions / mountable engines are out of scope.
42
+ # - The English inflector is intentionally tiny: it handles `posts` ↔ `post`, `users` ↔ `user`,
43
+ # `categories` `category`, `boxes` `box`. Custom inflections (`fish` ↔ `fish`, `child` ↔
44
+ # `children`) are out of scope; users who need them ship a hand-written RBS for the affected helper.
53
45
  class RailsRoutes < Rigor::Plugin::Base
54
46
  manifest(
55
47
  id: "rails-routes",
56
- # Bumped 2026-05-28 — GitLab FOSS sweep adds: (a)
57
- # `draw_all :name` support (action_dispatch-draw_all
58
- # gem; single-file load semantics matching `draw :name`);
59
- # (b) keyword-style `scope(path: ':project_id',
60
- # as: :project)` path read from the `:path` keyword,
61
- # not only from the positional first arg; (c) detection
62
- # of iterative `direct(name.sub(FROM, TO)) do ... end`
63
- # alias-generation idiom — generates `<TO>_*` aliases
64
- # for every registered `<FROM>_*` helper. GitLab uses
65
- # this to shorten `namespace_project_*` → `project_*`.
48
+ # Bumped 2026-05-28 — GitLab FOSS sweep adds: (a) `draw_all :name` support (action_dispatch-draw_all
49
+ # gem; single-file load semantics matching `draw :name`); (b) keyword-style `scope(path:
50
+ # ':project_id', as: :project)` path read from the `:path` keyword, not only from the positional
51
+ # first arg; (c) detection of iterative `direct(name.sub(FROM, TO)) do ... end` alias-generation
52
+ # idiomgenerates `<TO>_*` aliases for every registered `<FROM>_*` helper. GitLab uses this to
53
+ # shorten `namespace_project_*` `project_*`.
66
54
  version: "0.27.0",
67
55
  description: "Validates Rails route-helper calls against `config/routes.rb`.",
68
56
  config_schema: {
69
57
  "routes_file" => { kind: :string, default: "config/routes.rb" },
70
- # `helper_paths` — the directories `HelperDiscoverer` walks
71
- # for project-defined `*_path` / `*_url` methods. Default to
72
- # the whole `app/` tree the suffix filter inside the
73
- # discoverer keeps the registered set tight, and real-world
74
- # Rails apps routinely keep URL builders under
75
- # `app/controllers` (private `def page_url`, `def callback_url`
76
- # shapes), `app/lib` (Mastodon's
77
- # `TranslationService::DeepL#base_url`), `app/services`
78
- # (`SoftwareUpdateCheckService#api_url`), `app/serializers`,
79
- # `app/presenters`, `app/decorators`, not only `app/helpers/`.
80
- # Walking the whole tree is the honest answer to "does this
81
- # `_path` / `_url` name exist anywhere in the project?"; the
82
- # cost is a one-time Prism parse per file at startup, which is
83
- # bounded.
58
+ # `helper_paths` — the directories `HelperDiscoverer` walks for project-defined `*_path` / `*_url`
59
+ # methods. Default to the whole `app/` tree the suffix filter inside the discoverer keeps the
60
+ # registered set tight, and real-world Rails apps routinely keep URL builders under
61
+ # `app/controllers` (private `def page_url`, `def callback_url` shapes), `app/lib` (Mastodon's
62
+ # `TranslationService::DeepL#base_url`), `app/services` (`SoftwareUpdateCheckService#api_url`),
63
+ # `app/serializers`, `app/presenters`, `app/decorators`, not only `app/helpers/`. Walking the
64
+ # whole tree is the honest answer to "does this `_path` / `_url` name exist anywhere in the
65
+ # project?"; the cost is a one-time Prism parse per file at startup, which is bounded.
84
66
  "helper_paths" => { kind: :array, default: ["app"] }
85
67
  },
86
68
  produces: [:helper_table]
87
69
  )
88
70
 
89
- # Cached producer — reads `config/routes.rb` through
90
- # the trusted `IoBoundary` and parses through
91
- # {RoutesParser}. The descriptor's auto-collected
92
- # `FileEntry` digest invalidates the cache on routes-
93
- # file edits.
71
+ # Cached producer — reads `config/routes.rb` through the trusted `IoBoundary` and parses through
72
+ # {RoutesParser}. The descriptor's auto-collected `FileEntry` digest invalidates the cache on
73
+ # routes-file edits.
94
74
  #
95
- # Passes a `file_reader` lambda so the parser can follow
96
- # `draw(:admin)` `config/routes/admin.rb` partials.
97
- # `watch:` (ADR-60 WD3) covers every `.rb` under `helper_paths:`
98
- # so ADDING a helper file invalidates the table the producer's
99
- # own in-block reads (the routes file + the partials it follows
100
- # via `draw`) are captured post-compute, but a brand-new helper
101
- # file the prior run never read would otherwise read as fresh.
75
+ # Passes a `file_reader` lambda so the parser can follow `draw(:admin)` → `config/routes/admin.rb`
76
+ # partials. `watch:` (ADR-60 WD3) covers every `.rb` under `helper_paths:` so ADDING a helper file
77
+ # invalidates the table — the producer's own in-block reads (the routes file + the partials it
78
+ # follows via `draw`) are captured post-compute, but a brand-new helper file the prior run never read
79
+ # would otherwise read as fresh.
102
80
  producer :helper_table, watch: -> { @helper_paths.map { |dir| [dir, "**/*.rb"] } } do |_params|
103
81
  routes_dir = "#{File.dirname(@routes_file)}/routes"
104
82
  file_reader = lambda do |name|
@@ -119,14 +97,11 @@ module Rigor
119
97
  @load_error = nil
120
98
  end
121
99
 
122
- # Walks every configured `helper_paths:` directory
123
- # through the trusted `IoBoundary` and returns the set
124
- # of project-defined `*_path` / `*_url` method names
125
- # for {HelperDiscoverer}. Each file digest is captured
126
- # by the boundary so editing a helper file invalidates
127
- # the `:helper_table` cache automatically. Returns the
128
- # empty set when nothing under `helper_paths:` exists —
129
- # the routes table still works.
100
+ # Walks every configured `helper_paths:` directory through the trusted `IoBoundary` and returns the
101
+ # set of project-defined `*_path` / `*_url` method names for {HelperDiscoverer}. Each file digest is
102
+ # captured by the boundary so editing a helper file invalidates the `:helper_table` cache
103
+ # automatically. Returns the empty set when nothing under `helper_paths:` exists — the routes table
104
+ # still works.
130
105
  def discover_custom_helpers
131
106
  contents_per_path = {}
132
107
  each_helper_file do |path|
@@ -146,8 +121,7 @@ module Rigor
146
121
  end
147
122
  end
148
123
 
149
- # Publishes the parsed table to the cross-plugin fact
150
- # store; `rigor-actionpack` Phase 4 reads it via
124
+ # Publishes the parsed table to the cross-plugin fact store; `rigor-actionpack` Phase 4 reads it via
151
125
  # `services.fact_store.read`.
152
126
  def prepare(services)
153
127
  table = helper_table_or_nil
@@ -160,10 +134,9 @@ module Rigor
160
134
  )
161
135
  end
162
136
 
163
- # File-level only: the once-per-run load-error emission. Per-call
164
- # helper validation runs over the engine-owned walk via the
165
- # node_rule below (ADR-37), with the same-file `shadowing` set
166
- # built once as the node-rule file context.
137
+ # File-level only: the once-per-run load-error emission. Per-call helper validation runs over the
138
+ # engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built once
139
+ # as the node-rule file context.
167
140
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
168
141
  table = helper_table_or_nil
169
142
  if table.nil? && @load_error
@@ -191,31 +164,23 @@ module Rigor
191
164
 
192
165
  private
193
166
 
194
- # The load-error path used to emit the same warning on
195
- # every analyzed file in the project. On large monorepos
196
- # (Mastodon: 1,302 files; Solidus: ~1,000 files) and on
197
- # legacy projects without a top-level `config/routes.rb`,
198
- # this multiplied a single root cause into 1,000+
199
- # identical diagnostics. The error is project-global —
200
- # report it once per run.
167
+ # The load-error path used to emit the same warning on every analyzed file in the project. On large
168
+ # monorepos (Mastodon: 1,302 files; Solidus: ~1,000 files) and on legacy projects without a top-level
169
+ # `config/routes.rb`, this multiplied a single root cause into 1,000+ identical diagnostics. The
170
+ # error is project-global report it once per run.
201
171
 
202
172
  def helper_table_or_nil
203
- # Memoise the build *attempt*, not just a truthy result: when the
204
- # routes file is missing or unparseable the table is nil, and the
205
- # old `return @helper_table if @helper_table` re-ran the whole
206
- # read-and-parse on every call. The engine consults this per
207
- # route-helper dispatch, so a nil table meant re-reading (and, on
208
- # a real project, re-parsing) `config/routes.rb` tens of thousands
209
- # of times per run. The result — nil included — is stable for the
210
- # run, so one attempt is correct.
173
+ # Memoise the build *attempt*, not just a truthy result: when the routes file is missing or
174
+ # unparseable the table is nil, and the old `return @helper_table if @helper_table` re-ran the
175
+ # whole read-and-parse on every call. The engine consults this per route-helper dispatch, so a nil
176
+ # table meant re-reading (and, on a real project, re-parsing) `config/routes.rb` tens of thousands
177
+ # of times per run. The result — nil included is stable for the run, so one attempt is correct.
211
178
  return @helper_table if @helper_table_built
212
179
 
213
180
  @helper_table_built = true
214
- # ADR-60 WD3 record-and-validate: the producer's in-block reads
215
- # (the routes file + the partials it follows) are captured into
216
- # the dependency descriptor AFTER the block runs, and the
217
- # producer's `watch:` covers helper-file additions — so no
218
- # priming read is needed here.
181
+ # ADR-60 WD3 record-and-validate: the producer's in-block reads (the routes file + the partials it
182
+ # follows) are captured into the dependency descriptor AFTER the block runs, and the producer's
183
+ # `watch:` covers helper-file additions so no priming read is needed here.
219
184
  @helper_table = cache_for(:helper_table, params: {}).call
220
185
  rescue Plugin::AccessDeniedError => e
221
186
  @load_error = "rigor-rails-routes: #{e.message}"
@@ -4,24 +4,18 @@ require "rigor/plugin"
4
4
 
5
5
  # ADR-32 — bundled `rigor-rbs-inline` plugin.
6
6
  #
7
- # Synthesises RBS from project Ruby files that carry
8
- # rbs-inline-shaped comments (`#: () -> T`, `# @rbs name: T`,
9
- # `# @rbs return: T`, attribute `#:`, …) and contributes the
10
- # result to the analysis environment through the
11
- # `source_rbs_synthesizer:` manifest hook.
7
+ # Synthesises RBS from project Ruby files that carry rbs-inline-shaped comments (`#: () -> T`, `# @rbs
8
+ # name: T`, `# @rbs return: T`, attribute `#:`, …) and contributes the result to the analysis environment
9
+ # through the `source_rbs_synthesizer:` manifest hook.
12
10
  #
13
- # By default (WD2) only files starting with the upstream
14
- # `# rbs_inline: enabled` magic comment are processed; a host
15
- # context can flip this off by setting `require_magic_comment:
16
- # false` in the plugin config (WD10).
11
+ # By default (WD2) only files starting with the upstream `# rbs_inline: enabled` magic comment are
12
+ # processed; a host context can flip this off by setting `require_magic_comment: false` in the plugin
13
+ # config (WD10).
17
14
  module Rigor
18
15
  module Plugin
19
- # The plugin gem requires `rbs/inline` at load time; without
20
- # the upstream library the synthesizer can't do its job.
21
- # Wrapped in a begin/rescue so the analyzer still loads if
22
- # the user activated this plugin without installing the
23
- # `rbs-inline` gem (loud-on-activation, fail-soft to no
24
- # contribution).
16
+ # The plugin gem requires `rbs/inline` at load time; without the upstream library the synthesizer can't
17
+ # do its job. Wrapped in a begin/rescue so the analyzer still loads if the user activated this plugin
18
+ # without installing the `rbs-inline` gem (loud-on-activation, fail-soft to no contribution).
25
19
  begin
26
20
  require "prism"
27
21
  require "rbs/inline"
@@ -37,18 +31,13 @@ module Rigor
37
31
  end
38
32
 
39
33
  class RbsInline < Rigor::Plugin::Base
40
- # Synthesizer callable invoked once per project Ruby
41
- # source file by `Environment.for_project` at env-build
42
- # time. Returns the synthesised RBS source as a String,
43
- # or `nil` when the file contributes nothing (no magic
44
- # comment in the default mode, empty annotation set,
45
- # parse error per WD6).
34
+ # Synthesizer callable invoked once per project Ruby source file by `Environment.for_project` at
35
+ # env-build time. Returns the synthesised RBS source as a String, or `nil` when the file contributes
36
+ # nothing (no magic comment in the default mode, empty annotation set, parse error per WD6).
46
37
  class Synthesizer
47
- # @param require_magic_comment [Boolean] when `true`
48
- # (the default, WD2), only files with
49
- # `# rbs_inline: enabled` at the top are processed.
50
- # When `false` (WD10 host-context override), every
51
- # file is treated as if it carried the magic comment.
38
+ # @param require_magic_comment [Boolean] when `true` (the default, WD2), only files with
39
+ # `# rbs_inline: enabled` at the top are processed. When `false` (WD10 host-context override),
40
+ # every file is treated as if it carried the magic comment.
52
41
  def initialize(require_magic_comment:)
53
42
  @require_magic_comment = require_magic_comment
54
43
  freeze
@@ -57,8 +46,7 @@ module Rigor
57
46
  # Return value contract:
58
47
  # - `String` (non-empty) → successful synthesis
59
48
  # - `nil` → no contribution
60
- # - `[:error, message_string]` → parse failed, surface
61
- # info diagnostic per ADR-32 WD6
49
+ # - `[:error, message_string]` → parse failed, surface info diagnostic per ADR-32 WD6
62
50
  def call(source_file_path)
63
51
  return nil unless RBS_INLINE_AVAILABLE
64
52
  return nil unless File.file?(source_file_path)
@@ -67,10 +55,8 @@ module Rigor
67
55
  return nil if source.empty?
68
56
 
69
57
  result = ::Prism.parse(source)
70
- # `opt_in: true` is rbs-inline's "require the magic
71
- # comment" mode (per upstream parser.rb:62). The
72
- # plugin's `require_magic_comment:` config knob maps
73
- # directly onto it.
58
+ # `opt_in: true` is rbs-inline's "require the magic comment" mode (per upstream parser.rb:62).
59
+ # The plugin's `require_magic_comment:` config knob maps directly onto it.
74
60
  parsed = ::RBS::Inline::Parser.parse(result, opt_in: @require_magic_comment)
75
61
  return nil if parsed.nil?
76
62
 
@@ -80,11 +66,9 @@ module Rigor
80
66
 
81
67
  rendered
82
68
  rescue ::StandardError => e
83
- # WD6 fail-soft — surface a structured error tuple so
84
- # the engine's `Environment.for_project` can emit a
85
- # `source-rbs-synthesis-failed` info diagnostic
86
- # naming the file + the upstream error message,
87
- # without crashing analysis.
69
+ # WD6 fail-soft — surface a structured error tuple so the engine's `Environment.for_project` can
70
+ # emit a `source-rbs-synthesis-failed` info diagnostic naming the file + the upstream error
71
+ # message, without crashing analysis.
88
72
  [:error, "#{e.class}: #{e.message.to_s.lines.first.to_s.strip}"]
89
73
  end
90
74
  end
@@ -98,24 +82,19 @@ module Rigor
98
82
  source_rbs_synthesizer: nil # set per-instance below
99
83
  )
100
84
 
101
- # Per-instance synthesizer — built from the manifest's
102
- # default + the project's plugin config. The manifest
103
- # `source_rbs_synthesizer:` is nil at the class level so
104
- # the registry sees the instance's override (returned by
105
- # `#manifest`, which `Plugin::Registry#source_rbs_synthesizers`
106
- # consults via `plugin.manifest.source_rbs_synthesizer`).
85
+ # Per-instance synthesizer — built from the manifest's default + the project's plugin config. The
86
+ # manifest `source_rbs_synthesizer:` is nil at the class level so the registry sees the instance's
87
+ # override (returned by `#manifest`, which `Plugin::Registry#source_rbs_synthesizers` consults via
88
+ # `plugin.manifest.source_rbs_synthesizer`).
107
89
  #
108
- # ADR-32 WD10 — `require_magic_comment` defaults to
109
- # `true`. Setting it to `false` in `.rigor.yml` flips the
110
- # synthesizer into "process every file" mode.
90
+ # ADR-32 WD10 — `require_magic_comment` defaults to `true`. Setting it to `false` in `.rigor.yml`
91
+ # flips the synthesizer into "process every file" mode.
111
92
  def initialize(services:, config: {})
112
93
  super
113
94
  @require_magic_comment = config.fetch("require_magic_comment", true) ? true : false
114
95
  @synthesizer = Synthesizer.new(require_magic_comment: @require_magic_comment)
115
- # Build the per-instance manifest eagerly (before
116
- # `freeze`) so the registry's repeated reads return
117
- # the same object and we don't need to mutate a
118
- # frozen instance later.
96
+ # Build the per-instance manifest eagerly (before `freeze`) so the registry's repeated reads
97
+ # return the same object and we don't need to mutate a frozen instance later.
119
98
  base = self.class.manifest
120
99
  @manifest_with_synth = build_manifest_with_synthesizer(base)
121
100
  freeze
@@ -123,10 +102,9 @@ module Rigor
123
102
 
124
103
  attr_reader :synthesizer
125
104
 
126
- # Override the manifest-level `source_rbs_synthesizer:`
127
- # (which is nil at the class level) with the per-instance
128
- # synthesizer built from the merged config. The registry
129
- # reads this through `plugin.manifest.source_rbs_synthesizer`.
105
+ # Override the manifest-level `source_rbs_synthesizer:` (which is nil at the class level) with the
106
+ # per-instance synthesizer built from the merged config. The registry reads this through
107
+ # `plugin.manifest.source_rbs_synthesizer`.
130
108
  def manifest
131
109
  @manifest_with_synth
132
110
  end
@@ -1,17 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rigor-rbs-inline — ingests rbs-inline-shaped comments
4
- # (`# @rbs name: T`, `#: () -> T`, `# @rbs return: T`, attribute
5
- # `#:`, ivars, generics, override, …) as RBS contributions to
6
- # the analysis environment.
3
+ # rigor-rbs-inline — ingests rbs-inline-shaped comments (`# @rbs name: T`, `#: () -> T`, `# @rbs return: T`,
4
+ # attribute `#:`, ivars, generics, override, ) as RBS contributions to the analysis environment.
7
5
  #
8
- # Per ADR-32 the plugin gates per file on the upstream
9
- # `# rbs_inline: enabled` magic comment by default. A host
10
- # context that owns the entire analysis scope (e.g. the
11
- # ADR-29 browser playground) can set `require_magic_comment:
12
- # false` in the plugin config to skip the gate — every file
13
- # the synthesizer sees is treated as if it carried the magic
14
- # comment.
6
+ # Per ADR-32 the plugin gates per file on the upstream `# rbs_inline: enabled` magic comment by default. A
7
+ # host context that owns the entire analysis scope (e.g. the ADR-29 browser playground) can set
8
+ # `require_magic_comment: false` in the plugin config to skip the gate — every file the synthesizer sees is
9
+ # treated as if it carried the magic comment.
15
10
  #
16
11
  # # .rigor.yml
17
12
  # plugins:
@@ -19,6 +14,6 @@
19
14
  # config:
20
15
  # require_magic_comment: false # default true
21
16
  #
22
- # The plugin depends on the upstream `rbs-inline` gem; Rigor's
23
- # core `rigortype` gemspec stays zero-dep per ADR-0 / ADR-32 WD1.
17
+ # The plugin depends on the upstream `rbs-inline` gem; Rigor's core `rigortype` gemspec stays zero-dep per
18
+ # ADR-0 / ADR-32 WD1.
24
19
  require_relative "rigor/plugin/rbs_inline"
@@ -9,16 +9,12 @@ module Rigor
9
9
  class Rspec < Rigor::Plugin::Base
10
10
  # Per-file walker that:
11
11
  #
12
- # 1. Collects every RSpec scope (each `RSpec.describe`
13
- # plus its nested `describe` / `context` blocks)
12
+ # 1. Collects every RSpec scope (each `RSpec.describe` plus its nested `describe` / `context` blocks)
14
13
  # via {ScopeWalker}.
15
- # 2. Reports duplicate `let(:name)` / `subject(:name)`
16
- # declarations within the same scope (the second
17
- # declaration wins at runtimean easy
18
- # copy-paste bug).
19
- # 3. Reports recursive self-references —
20
- # `let(:user) { user.something }` will infinite-loop
21
- # at runtime — an easy oversight.
14
+ # 2. Reports duplicate `let(:name)` / `subject(:name)` declarations within the same scope (the second
15
+ # declaration wins at runtime an easy copy-paste bug).
16
+ # 3. Reports recursive self-references `let(:user) { user.something }` will infinite-loop at
17
+ # runtime — an easy oversight.
22
18
  module Analyzer
23
19
  Diagnostic = Struct.new(:path, :line, :column, :severity, :rule, :message, keyword_init: true)
24
20
 
@@ -49,11 +45,9 @@ module Rigor
49
45
  end
50
46
 
51
47
  def duplicate_diagnostics_for(path, name, decls)
52
- # Report each subsequent occurrence; the first
53
- # one is the "winner" only by literal source
54
- # order, but RSpec lets the LAST declaration win
55
- # at runtime, so flag everything after the first
56
- # so the user can see the full list.
48
+ # Report each subsequent occurrence; the first one is the "winner" only by literal source order,
49
+ # but RSpec lets the LAST declaration win at runtime, so flag everything after the first so the
50
+ # user can see the full list.
57
51
  decls.drop(1).map do |decl|
58
52
  Diagnostic.new(
59
53
  path: path,
@@ -76,8 +70,7 @@ module Rigor
76
70
  end
77
71
  end
78
72
 
79
- # Walks the declaration's block body looking for a
80
- # call to its own name with no explicit receiver.
73
+ # Walks the declaration's block body looking for a call to its own name with no explicit receiver.
81
74
  # Returns true if at least one such call exists.
82
75
  def self_references?(decl)
83
76
  body = decl.block_node&.body
@@ -8,22 +8,17 @@ require_relative "scope_walker"
8
8
  module Rigor
9
9
  module Plugin
10
10
  class Rspec < Rigor::Plugin::Base
11
- # Per-file index of RSpec scope hierarchy. Each
12
- # describe scope carries:
11
+ # Per-file index of RSpec scope hierarchy. Each describe scope carries:
13
12
  #
14
- # - `outer_range`: `Range[Integer]` of line numbers
15
- # covered by the describe's block body (start_line..end_line).
16
- # - `describe_const`: the model constant string named in
17
- # `RSpec.describe SomeModel do` / `describe SomeModel do`,
18
- # or `nil` when the describe's first arg is a String
19
- # (`describe ".foo"`).
20
- # - `lets`: `Hash{Symbol => Prism::BlockNode}` of `let(:name)
21
- # { ... }` and `subject(:name) { ... }` declarations.
22
- # `:subject` is the key for the implicit `subject { ... }`.
13
+ # - `outer_range`: `Range[Integer]` of line numbers covered by the describe's block body
14
+ # (start_line..end_line).
15
+ # - `describe_const`: the model constant string named in `RSpec.describe SomeModel do` / `describe
16
+ # SomeModel do`, or `nil` when the describe's first arg is a String (`describe ".foo"`).
17
+ # - `lets`: `Hash{Symbol => Prism::BlockNode}` of `let(:name) { ... }` and `subject(:name) { ... }`
18
+ # declarations. `:subject` is the key for the implicit `subject { ... }`.
23
19
  #
24
- # Used by the plugin's let-binding `dynamic_return`
25
- # rule to bind `let`-named method-shape calls inside
26
- # `it` bodies to the let block's inferred type.
20
+ # Used by the plugin's let-binding `dynamic_return` rule to bind `let`-named method-shape calls
21
+ # inside `it` bodies to the let block's inferred type.
27
22
  class LetScopeIndex
28
23
  Record = Struct.new(:outer_range, :describe_const, :lets, keyword_init: true) do
29
24
  def contains?(line) = outer_range.cover?(line)
@@ -38,27 +33,22 @@ module Rigor
38
33
  freeze
39
34
  end
40
35
 
41
- # Returns every record whose outer_range contains the
42
- # given line, ordered from outermost to innermost. The
43
- # innermost wins on `let` name collisions per Ruby's
44
- # method-resolution order (RSpec nested `let` shadows
45
- # the outer `let`).
36
+ # Returns every record whose outer_range contains the given line, ordered from outermost to
37
+ # innermost. The innermost wins on `let` name collisions per Ruby's method-resolution order (RSpec
38
+ # nested `let` shadows the outer `let`).
46
39
  def records_at(line)
47
40
  @records.select { |rec| rec.contains?(line) }
48
41
  end
49
42
 
50
- # ADR-52 slice 5a — every `let` / `subject` name declared
51
- # anywhere in the file, across all describe scopes. Feeds the
52
- # plugin's `dynamic_return file_methods:` gate: the engine only
53
- # consults the rule for a call whose name appears here; the
54
- # precise line-scoped resolution stays in `let_block_at`.
43
+ # ADR-52 slice 5a — every `let` / `subject` name declared anywhere in the file, across all describe
44
+ # scopes. Feeds the plugin's `dynamic_return file_methods:` gate: the engine only consults the rule
45
+ # for a call whose name appears here; the precise line-scoped resolution stays in `let_block_at`.
55
46
  # @return [Array<Symbol>]
56
47
  def let_names
57
48
  @records.flat_map { |rec| rec.lets.keys }.uniq
58
49
  end
59
50
 
60
- # Resolves a `let` name at the given line by walking
61
- # records innermost to outermost.
51
+ # Resolves a `let` name at the given line by walking records innermost to outermost.
62
52
  # @return [Prism::BlockNode, nil]
63
53
  def let_block_at(line, name)
64
54
  name_sym = name.to_sym
@@ -68,9 +58,8 @@ module Rigor
68
58
  nil
69
59
  end
70
60
 
71
- # Resolves the `describe`-anchor constant name at the
72
- # given line. The innermost describe with a constant
73
- # anchor wins; describe-with-String anchors are skipped.
61
+ # Resolves the `describe`-anchor constant name at the given line. The innermost describe with a
62
+ # constant anchor wins; describe-with-String anchors are skipped.
74
63
  # @return [String, nil]
75
64
  def describe_const_at(line)
76
65
  records_at(line).reverse.each do |rec|
@@ -92,9 +81,8 @@ module Rigor
92
81
  if describe_call?(node)
93
82
  record = build_record(node, anchor)
94
83
  accumulator << record
95
- # `describe_call?` already guarantees a `Prism::CallNode`; the
96
- # explicit `is_a?` re-states it so the analyzer narrows `node`
97
- # from `Prism::Node` and resolves `#block` (a CallNode method).
84
+ # `describe_call?` already guarantees a `Prism::CallNode`; the explicit `is_a?` re-states it so
85
+ # the analyzer narrows `node` from `Prism::Node` and resolves `#block` (a CallNode method).
98
86
  if node.is_a?(Prism::CallNode) && node.block&.body
99
87
  collect(node.block.body, anchor: record.describe_const || anchor, accumulator: accumulator)
100
88
  end
@@ -123,8 +111,7 @@ module Rigor
123
111
  return false unless ScopeWalker::SCOPE_METHODS.include?(node.name) || node.name == :describe
124
112
  return false unless node.block.is_a?(Prism::BlockNode)
125
113
 
126
- # `RSpec.describe ...` has receiver = RSpec; bare
127
- # `describe ... do` has nil receiver. Accept both.
114
+ # `RSpec.describe ...` has receiver = RSpec; bare `describe ... do` has nil receiver. Accept both.
128
115
  node.receiver.nil? || describe_receiver?(node.receiver)
129
116
  end
130
117
 
@@ -158,11 +145,9 @@ module Rigor
158
145
  end
159
146
  end
160
147
 
161
- # Walks the describe's body shallowly: collects every
162
- # top-level `let(:name) { ... }` / `subject(:name) { ... }`
163
- # / `subject { ... }` declaration. Does NOT recurse into
164
- # nested describe / context blocks — those have their
165
- # own record built by `collect` above.
148
+ # Walks the describe's body shallowly: collects every top-level `let(:name) { ... }` /
149
+ # `subject(:name) { ... }` / `subject { ... }` declaration. Does NOT recurse into nested describe /
150
+ # context blocks those have their own record built by `collect` above.
166
151
  def self.collect_lets(body)
167
152
  return {} unless body.is_a?(Prism::Node)
168
153
 
@@ -6,28 +6,22 @@ require "rigor/type"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class Rspec < Rigor::Plugin::Base
9
- # Resolves the runtime type of a
10
- # `let(:name) { body }` or `subject(:name) { body }`
11
- # block by pattern-matching its body's tail expression.
9
+ # Resolves the runtime type of a `let(:name) { body }` or `subject(:name) { body }` block by
10
+ # pattern-matching its body's tail expression.
12
11
  #
13
12
  # Recognised body shapes (floor):
14
13
  #
15
14
  # - `ConstantClass.new(...)` → `Nominal[ConstantClass]`
16
15
  # - `Foo::Bar.new(...)` → `Nominal[Foo::Bar]`
17
- # - `described_class.new(...)` → `Nominal[<describe const>]`
18
- # when the surrounding describe block has a constant
19
- # anchor.
20
- # - `create(:factory)` / `build(:factory)` /
21
- # `build_stubbed(:factory)` / `attributes_for(:factory)`
22
- # → `Nominal[<factory.model_class>]` when the
23
- # `:factory_index` ADR-9 fact is available and the
16
+ # - `described_class.new(...)` → `Nominal[<describe const>]` when the surrounding describe block has
17
+ # a constant anchor.
18
+ # - `create(:factory)` / `build(:factory)` / `build_stubbed(:factory)` / `attributes_for(:factory)` →
19
+ # `Nominal[<factory.model_class>]` when the `:factory_index` ADR-9 fact is available and the
24
20
  # factory name is known.
25
- # - `FactoryBot.create(:factory)` etc. — same as the
26
- # implicit-receiver factorybot form.
21
+ # - `FactoryBot.create(:factory)` etc. — same as the implicit-receiver factorybot form.
27
22
  #
28
- # Returns `nil` for any other body shape. Multi-statement
29
- # bodies use the LAST statement (the block's return
30
- # value).
23
+ # Returns `nil` for any other body shape. Multi-statement bodies use the LAST statement (the block's
24
+ # return value).
31
25
  module LetTypeResolver
32
26
  FACTORYBOT_ENTRY_METHODS = %i[
33
27
  create build build_stubbed attributes_for
@@ -38,9 +32,8 @@ module Rigor
38
32
 
39
33
  # @param block_node [Prism::BlockNode]
40
34
  # @param describe_const [String, nil]
41
- # @param factory_index [Object, nil] something
42
- # responding to `find(factory_name)` and returning
43
- # an entry that responds to `model_class`.
35
+ # @param factory_index [Object, nil] something responding to `find(factory_name)` and returning an
36
+ # entry that responds to `model_class`.
44
37
  # @param environment [Rigor::Environment, nil]
45
38
  # @return [Rigor::Type, nil]
46
39
  def resolve(block_node, describe_const:, factory_index:, environment:)
@@ -103,10 +96,8 @@ module Rigor
103
96
  nominal_for(model_class, environment: environment)
104
97
  end
105
98
 
106
- # Recognises both `create(:name)` (implicit receiver,
107
- # via `include FactoryBot::Syntax::Methods`) and the
108
- # explicit `FactoryBot.create(:name)` /
109
- # `FactoryGirl.create(:name)` (legacy) forms.
99
+ # Recognises both `create(:name)` (implicit receiver, via `include FactoryBot::Syntax::Methods`)
100
+ # and the explicit `FactoryBot.create(:name)` / `FactoryGirl.create(:name)` (legacy) forms.
110
101
  def factorybot_call?(call_node)
111
102
  return false unless FACTORYBOT_ENTRY_METHODS.include?(call_node.name)
112
103