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
@@ -6,40 +6,27 @@ module Rigor
6
6
  class Environment
7
7
  # Target-project Bundler awareness (O4, implemented).
8
8
  #
9
- # Walks a Bundler-installed gem tree (e.g., the project's
10
- # `vendor/bundle` or a Docker-mounted bundle root) and
11
- # returns the per-gem `sig/` directories to feed into
12
- # `RbsLoader`'s `signature_paths:`. Of the ~3% of gems that
13
- # ship `sig/` in their gem package today (per the four-project
14
- # Mastodon Docker bundle-install measurement on 2026-05-15:
15
- # 10 of 343 gems shipped sig — `prism`, `aws-sdk-s3`,
16
- # `aws-sdk-kms`, `aws-sdk-core`, `playwright-ruby-client`,
17
- # `mutex_m`, `webrick`, `base64`, `stoplight`, `ffi`), this
18
- # discovery surfaces the typed contract the gem author
19
- # explicitly published.
9
+ # Walks a Bundler-installed gem tree (e.g., the project's `vendor/bundle` or a Docker-mounted bundle
10
+ # root) and returns the per-gem `sig/` directories to feed into `RbsLoader`'s `signature_paths:`. Of the
11
+ # ~3% of gems that ship `sig/` in their gem package today (per the four-project Mastodon Docker
12
+ # bundle-install measurement on 2026-05-15: 10 of 343 gems shipped sig — `prism`, `aws-sdk-s3`,
13
+ # `aws-sdk-kms`, `aws-sdk-core`, `playwright-ruby-client`, `mutex_m`, `webrick`, `base64`, `stoplight`,
14
+ # `ffi`), this discovery surfaces the typed contract the gem author explicitly published.
20
15
  #
21
- # Conflicts with rigor's bundled stdlib RBS (the prism case
22
- # was the motivating example) degrade gracefully via O7's
23
- # failure-memo in `RbsLoader#env`: a single warning naming
24
- # the offending file is emitted and analysis continues with
25
- # `Dynamic[top]` everywhere rather than hanging.
16
+ # Conflicts with rigor's bundled stdlib RBS (the prism case was the motivating example) degrade
17
+ # gracefully via O7's failure-memo in `RbsLoader#env`: a single warning naming the offending file is
18
+ # emitted and analysis continues with `Dynamic[top]` everywhere rather than hanging.
26
19
  #
27
- # The discovery is intentionally a pure file-system walk —
28
- # no `Bundler` API call, no `Gemfile.lock` parse so rigor
29
- # doesn't need the target project's Bundler context.
20
+ # The discovery is intentionally a pure file-system walk — no `Bundler` API call, no `Gemfile.lock`
21
+ # parse so rigor doesn't need the target project's Bundler context.
30
22
  module BundleSigDiscovery
31
- # Gems already covered by rigor's `DEFAULT_LIBRARIES`
32
- # (stdlib RBS) plus the `data/vendored_gem_sigs/` bundle.
33
- # Skipping these from bundle discovery prevents
34
- # `RBS::DuplicatedDeclarationError` (the prism case was the
35
- # motivating example — Ruby 4.0 ships prism's RBS in
36
- # stdlib, and the gem also ships its own `sig/`, so loading
37
- # both raises on `Prism::BACKEND` etc.).
23
+ # Gems already covered by rigor's `DEFAULT_LIBRARIES` (stdlib RBS) plus the `data/vendored_gem_sigs/`
24
+ # bundle. Skipping these from bundle discovery prevents `RBS::DuplicatedDeclarationError` (the prism
25
+ # case was the motivating example — Ruby 4.0 ships prism's RBS in stdlib, and the gem also ships its
26
+ # own `sig/`, so loading both raises on `Prism::BACKEND` etc.).
38
27
  #
39
- # The list is hard-coded for the MVP because it tracks
40
- # rigor's bundled coverage 1:1. When a new gem is vendored
41
- # under `data/vendored_gem_sigs/` or added to
42
- # `DEFAULT_LIBRARIES`, add its name here.
28
+ # The list is hard-coded for the MVP because it tracks rigor's bundled coverage 1:1. When a new gem is
29
+ # vendored under `data/vendored_gem_sigs/` or added to `DEFAULT_LIBRARIES`, add its name here.
43
30
  SKIPPED_GEMS_BY_DEFAULT = Set[
44
31
  # DEFAULT_LIBRARIES (lib/rigor/environment.rb)
45
32
  "pathname", "optparse", "json", "yaml", "fileutils",
@@ -58,31 +45,22 @@ module Rigor
58
45
  "pg", "mysql2", "nokogiri", "bcrypt", "redis", "idn-ruby"
59
46
  ].freeze
60
47
 
61
- # @param bundle_path [String, Pathname, nil] explicit path
62
- # to the bundler install root. When `nil`, falls back to
63
- # `auto_detect` if `auto_detect:` is true.
64
- # @param project_root [String] resolution base for relative
65
- # `bundle_path:` and the auto-detect search.
66
- # @param auto_detect [Boolean] when true and `bundle_path:`
67
- # is nil, try `.bundle/config`'s `BUNDLE_PATH:` and
68
- # `vendor/bundle/` under `project_root`.
69
- # @param skip_gems [Set<String>] gem names to exclude from
70
- # discovery. Defaults to {SKIPPED_GEMS_BY_DEFAULT}.
71
- # @param locked_gems [Hash{String => LockfileResolver::LockedGem}, nil]
72
- # Optional O4-Layer-3 filter. When non-nil and non-empty,
73
- # only `sig/` directories whose gem `(name, version,
74
- # platform)` tuple matches a lockfile entry are returned.
75
- # Bundle entries absent from the lockfile (or at a drifted
76
- # version) are silently dropped the lockfile is treated
77
- # as the source of truth for "what gems this project
78
- # actually declares". Pass `nil` (the default) to keep
79
- # the pre-Layer-3 behaviour of returning every non-skipped
80
- # `sig/` under the bundle.
81
- # @return [Array<Pathname>] every `<gem-dir>/sig` directory
82
- # under the resolved bundle path, minus any whose gem
83
- # name is in `skip_gems` and (when `locked_gems` is
84
- # supplied) minus any whose `(name, version, platform)`
85
- # does not match a lockfile entry.
48
+ # @param bundle_path [String, Pathname, nil] explicit path to the bundler install root. When `nil`,
49
+ # falls back to `auto_detect` if `auto_detect:` is true.
50
+ # @param project_root [String] resolution base for relative `bundle_path:` and the auto-detect search.
51
+ # @param auto_detect [Boolean] when true and `bundle_path:` is nil, try `.bundle/config`'s
52
+ # `BUNDLE_PATH:` and `vendor/bundle/` under `project_root`.
53
+ # @param skip_gems [Set<String>] gem names to exclude from discovery. Defaults to
54
+ # {SKIPPED_GEMS_BY_DEFAULT}.
55
+ # @param locked_gems [Hash{String => LockfileResolver::LockedGem}, nil] Optional O4-Layer-3 filter.
56
+ # When non-nil and non-empty, only `sig/` directories whose gem `(name, version, platform)` tuple
57
+ # matches a lockfile entry are returned. Bundle entries absent from the lockfile (or at a drifted
58
+ # version) are silently dropped the lockfile is treated as the source of truth for "what gems this
59
+ # project actually declares". Pass `nil` (the default) to keep the pre-Layer-3 behaviour of
60
+ # returning every non-skipped `sig/` under the bundle.
61
+ # @return [Array<Pathname>] every `<gem-dir>/sig` directory under the resolved bundle path, minus any
62
+ # whose gem name is in `skip_gems` and (when `locked_gems` is supplied) minus any whose `(name,
63
+ # version, platform)` does not match a lockfile entry.
86
64
  def self.discover(bundle_path:, project_root: Dir.pwd, auto_detect: true,
87
65
  skip_gems: SKIPPED_GEMS_BY_DEFAULT, locked_gems: nil, home: nil)
88
66
  resolved = resolve_bundle_path(
@@ -93,9 +71,8 @@ module Rigor
93
71
  )
94
72
  return [] if resolved.nil?
95
73
 
96
- # `<bundle>/ruby/X.Y.Z/gems/<name>-<ver>/sig/` is the
97
- # canonical bundler layout. `*` on the ruby version dir
98
- # picks up whichever Ruby the bundle was installed for.
74
+ # `<bundle>/ruby/X.Y.Z/gems/<name>-<ver>/sig/` is the canonical bundler layout. `*` on the ruby
75
+ # version dir picks up whichever Ruby the bundle was installed for.
99
76
  all = Dir.glob(resolved.join("ruby", "*", "gems", "*", "sig")).map { |d| Pathname.new(d) }
100
77
  filtered = all.reject { |sig_dir| skip_gems.include?(gem_name_from_sig_path(sig_dir)) }
101
78
  return filtered if locked_gems.nil? || locked_gems.empty?
@@ -104,12 +81,10 @@ module Rigor
104
81
  filtered.select { |sig_dir| expected_dirs.include?(sig_dir.parent.basename.to_s) }
105
82
  end
106
83
 
107
- # `{name => LockedGem}` → set of canonical bundler gem
108
- # directory basenames. Pure-Ruby gems install as
109
- # `<name>-<version>`; platform-specific gems install as
110
- # `<name>-<version>-<platform>` (e.g. `ffi-1.17.4-aarch64-linux-gnu`).
111
- # Lockfile platform `"ruby"` is the pure-Ruby case; any
112
- # other value is treated as a platform tag.
84
+ # `{name => LockedGem}` → set of canonical bundler gem directory basenames. Pure-Ruby gems install as
85
+ # `<name>-<version>`; platform-specific gems install as `<name>-<version>-<platform>` (e.g.
86
+ # `ffi-1.17.4-aarch64-linux-gnu`). Lockfile platform `"ruby"` is the pure-Ruby case; any other value is
87
+ # treated as a platform tag.
113
88
  def self.expected_gem_dirs(locked_gems)
114
89
  locked_gems.each_value.with_object(Set.new) do |locked, set|
115
90
  base = "#{locked.name}-#{locked.version}"
@@ -122,28 +97,22 @@ module Rigor
122
97
  end
123
98
  private_class_method :expected_gem_dirs
124
99
 
125
- # `<bundle>/ruby/X.Y.Z/gems/<name>-<ver>/sig` → `<name>`.
126
- # The gem directory follows the canonical
127
- # `<name>-<version>` pattern; we strip everything from the
128
- # last hyphen onwards to recover the name. (Platform-tagged
129
- # variants like `ffi-1.17.4-aarch64-linux-gnu/` keep their
130
- # platform suffix in the version part, so the first hyphen
131
- # from the right is still the name boundary.)
100
+ # `<bundle>/ruby/X.Y.Z/gems/<name>-<ver>/sig` → `<name>`. The gem directory follows the canonical
101
+ # `<name>-<version>` pattern; we strip everything from the last hyphen onwards to recover the name.
102
+ # (Platform-tagged variants like `ffi-1.17.4-aarch64-linux-gnu/` keep their platform suffix in the
103
+ # version part, so the first hyphen from the right is still the name boundary.)
132
104
  #
133
- # Public so the O4 Layer 3 slice-3 coverage report
134
- # (`RbsCoverageReport`) can classify discovered bundle sigs
135
- # against locked gem names without re-running discovery.
105
+ # Public so the O4 Layer 3 slice-3 coverage report (`RbsCoverageReport`) can classify discovered bundle
106
+ # sigs against locked gem names without re-running discovery.
136
107
  def self.gem_name_from_sig_path(sig_dir)
137
108
  gem_dir = sig_dir.parent.basename.to_s
138
- # Strip `-<version>` and any platform suffix. The version
139
- # always starts with a digit, so split at the first
140
- # `-` followed by a digit.
109
+ # Strip `-<version>` and any platform suffix. The version always starts with a digit, so split at
110
+ # the first `-` followed by a digit.
141
111
  gem_dir.sub(/-\d.*\z/, "")
142
112
  end
143
113
 
144
- # Returns `Pathname` resolved bundle path, or `nil` when
145
- # neither explicit nor auto-detected. Public for the stats
146
- # banner so end users can see what rigor picked up.
114
+ # Returns `Pathname` resolved bundle path, or `nil` when neither explicit nor auto-detected. Public
115
+ # for the stats banner so end users can see what rigor picked up.
147
116
  def self.resolve_bundle_path(bundle_path:, project_root: Dir.pwd, auto_detect: true, home: nil)
148
117
  if bundle_path
149
118
  path = Pathname.new(File.expand_path(bundle_path.to_s, project_root))
@@ -158,38 +127,28 @@ module Rigor
158
127
  Pathname.new(detected) if detected
159
128
  end
160
129
 
161
- # Auto-detection order — project-local strategies win over the
162
- # user-global one, mirroring Bundler's own config precedence:
163
- # 1. `<project_root>/.bundle/config` carries `BUNDLE_PATH:`
164
- # set by `bundle config set --local path <dir>`.
165
- # 2. `<project_root>/vendor/bundle/` — the conventional
166
- # in-tree install location when a developer ran
130
+ # Auto-detection order — project-local strategies win over the user-global one, mirroring Bundler's own
131
+ # config precedence:
132
+ # 1. `<project_root>/.bundle/config` carries `BUNDLE_PATH:` set by `bundle config set --local path
133
+ # <dir>`.
134
+ # 2. `<project_root>/vendor/bundle/` — the conventional in-tree install location when a developer ran
167
135
  # `bundle install --path vendor/bundle`.
168
- # 3. The user-global bundler config `<home>/.bundle/config`
169
- # `BUNDLE_PATH:` (`bundle config set --global path <dir>`),
170
- # resolved relative to the project root and used only when
171
- # it points at an existing directory the last resort for
172
- # a project with no in-tree bundle. Purely additive: it is
173
- # consulted only when steps 1–2 found nothing, so it never
174
- # changes an already-working detection.
175
- # 4. `nil` — let the caller proceed without bundle sig
176
- # discovery (rigor's vendored RBS still loads).
136
+ # 3. The user-global bundler config `<home>/.bundle/config` `BUNDLE_PATH:` (`bundle config set
137
+ # --global path <dir>`), resolved relative to the project root and used only when it points at an
138
+ # existing directory the last resort for a project with no in-tree bundle. Purely additive: it is
139
+ # consulted only when steps 1–2 found nothing, so it never changes an already-working detection.
140
+ # 4. `nil` let the caller proceed without bundle sig discovery (rigor's vendored RBS still loads).
177
141
  #
178
- # Note (ADR-27): rigor reads the *project* as data, so
179
- # detection is limited to paths recorded in project-local or
180
- # user-global Bundler config files. The pure-default install
181
- # location gems in the active Ruby's GEM_HOME with no `path`
182
- # configured is the *project's* Ruby's gem home, which the
183
- # isolated analyzer cannot know without running the project's
184
- # toolchain. Point rigor at it with `bundler.bundle_path:`, or
185
- # supply signatures via `rbs collection install` /
186
- # `dependencies.source_inference:`. `BUNDLE_PATH` from rigor's
187
- # own environment is deliberately NOT consulted — it describes
188
- # rigor's bundle, not the analyzed project's.
142
+ # Note (ADR-27): rigor reads the *project* as data, so detection is limited to paths recorded in
143
+ # project-local or user-global Bundler config files. The pure-default install location — gems in the
144
+ # active Ruby's GEM_HOME with no `path` configured — is the *project's* Ruby's gem home, which the
145
+ # isolated analyzer cannot know without running the project's toolchain. Point rigor at it with
146
+ # `bundler.bundle_path:`, or supply signatures via `rbs collection install` /
147
+ # `dependencies.source_inference:`. `BUNDLE_PATH` from rigor's own environment is deliberately NOT
148
+ # consulted it describes rigor's bundle, not the analyzed project's.
189
149
  #
190
- # `home:` defaults to the invoking user's home directory; it is
191
- # a parameter so tests stay hermetic (no read of the real
192
- # `~/.bundle/config`).
150
+ # `home:` defaults to the invoking user's home directory; it is a parameter so tests stay hermetic (no
151
+ # read of the real `~/.bundle/config`).
193
152
  def self.auto_detect(project_root:, home: nil)
194
153
  from_config = read_bundle_config_path(File.join(project_root, ".bundle", "config"))
195
154
  return File.expand_path(from_config, project_root) if from_config
@@ -200,10 +159,9 @@ module Rigor
200
159
  global_bundle_path(project_root: project_root, home: home)
201
160
  end
202
161
 
203
- # Resolves the user-global `BUNDLE_PATH` against the project
204
- # root, returning it only when it is an existing directory.
205
- # Returns nil when there is no global config, no home, or the
206
- # configured path does not exist.
162
+ # Resolves the user-global `BUNDLE_PATH` against the project root, returning it only when it is an
163
+ # existing directory. Returns nil when there is no global config, no home, or the configured path does
164
+ # not exist.
207
165
  def self.global_bundle_path(project_root:, home:)
208
166
  home ||= default_home
209
167
  return nil if home.nil?
@@ -226,17 +184,15 @@ module Rigor
226
184
  def self.read_bundle_config_path(config_path)
227
185
  return nil unless config_path && File.exist?(config_path)
228
186
 
229
- # `.bundle/config` is YAML with all-caps env-style keys.
230
- # `BUNDLE_PATH:` is the canonical key (Bundler 2.x); the
231
- # `--path` flag sets it.
187
+ # `.bundle/config` is YAML with all-caps env-style keys. `BUNDLE_PATH:` is the canonical key
188
+ # (Bundler 2.x); the `--path` flag sets it.
232
189
  data = YAML.safe_load_file(config_path)
233
190
  return nil unless data.is_a?(Hash)
234
191
 
235
192
  path = data["BUNDLE_PATH"]
236
193
  path && !path.to_s.empty? ? path.to_s : nil
237
194
  rescue StandardError
238
- # Malformed `.bundle/config` should not break analysis;
239
- # silently skip auto-detection.
195
+ # Malformed `.bundle/config` should not break analysis; silently skip auto-detection.
240
196
  nil
241
197
  end
242
198
 
@@ -4,14 +4,13 @@ require_relative "../type"
4
4
 
5
5
  module Rigor
6
6
  class Environment
7
- # Resolves Ruby Class/Module objects to Rigor::Type::Nominal instances.
8
- # The hardcoded list spans the core classes the literal typer (Slice 1)
9
- # and the constant-resolution path (Slice 2 strengthening) need.
10
- # This is the static fast path for built-ins; `Environment` falls through
11
- # to `RbsLoader` for all other names — the two tiers are complementary.
7
+ # Resolves Ruby Class/Module objects to Rigor::Type::Nominal instances. The hardcoded list spans the core
8
+ # classes the literal typer (Slice 1) and the constant-resolution path (Slice 2 strengthening) need.
9
+ # This is the static fast path for built-ins; `Environment` falls through to `RbsLoader` for all other
10
+ # names the two tiers are complementary.
12
11
  #
13
- # See docs/internal-spec/inference-engine.md for the binding contract
14
- # (every entry below MUST always be recognised).
12
+ # See docs/internal-spec/inference-engine.md for the binding contract (every entry below MUST always be
13
+ # recognised).
15
14
  class ClassRegistry
16
15
  SLICE_1_BUILT_INS = [
17
16
  Integer,
@@ -25,11 +24,10 @@ module Rigor
25
24
  BasicObject
26
25
  ].freeze
27
26
 
28
- # Common Ruby core classes that user code routinely names by constant
29
- # reference. Adding them to the registry lets `nominal_for_name`
30
- # resolve `Array`, `Hash`, etc. without each call site re-listing
31
- # them. The static registry is the cheap fast path; `RbsLoader`
32
- # extends coverage to all other RBS-declared names (Slice 4, landed).
27
+ # Common Ruby core classes that user code routinely names by constant reference. Adding them to the
28
+ # registry lets `nominal_for_name` resolve `Array`, `Hash`, etc. without each call site re-listing
29
+ # them. The static registry is the cheap fast path; `RbsLoader` extends coverage to all other
30
+ # RBS-declared names (Slice 4, landed).
33
31
  SLICE_2_BUILT_INS = [
34
32
  Array,
35
33
  Hash,
@@ -68,15 +66,11 @@ module Rigor
68
66
 
69
67
  private
70
68
 
71
- # ADR-15 Phase 4b — the default registry MUST be
72
- # `Ractor.shareable?` so worker Ractors that consult
73
- # `Environment.for_project`'s default `class_registry:`
74
- # don't trip `Ractor::IsolationError`. The internal
75
- # `@nominals` / `@class_objects` Hashes are populated
76
- # via `register`, then `Ractor.make_shareable`
77
- # recursively freezes the registry, the two Hashes,
78
- # and confirms every entry (Type::Nominal carriers +
79
- # core Ruby classes) is itself shareable.
69
+ # ADR-15 Phase 4b — the default registry MUST be `Ractor.shareable?` so worker Ractors that consult
70
+ # `Environment.for_project`'s default `class_registry:` don't trip `Ractor::IsolationError`. The
71
+ # internal `@nominals` / `@class_objects` Hashes are populated via `register`, then
72
+ # `Ractor.make_shareable` recursively freezes the registry, the two Hashes, and confirms every entry
73
+ # (Type::Nominal carriers + core Ruby classes) is itself shareable.
80
74
  def build_default
81
75
  registry = new
82
76
  CORE_BUILT_INS.each { |klass| registry.register(klass) }
@@ -112,11 +106,10 @@ module Rigor
112
106
  @nominals.fetch(class_object.name)
113
107
  end
114
108
 
115
- # Nil-safe lookup by class name. Accepts Symbol or String. Returns the
116
- # registered Rigor::Type::Nominal, or nil when the name is unknown.
117
- # Used by ExpressionTyper to resolve Prism::ConstantReadNode and
118
- # Prism::ConstantPathNode under the fail-soft policy: unknown names
119
- # MUST NOT raise and MUST flow through the engine's tracer.
109
+ # Nil-safe lookup by class name. Accepts Symbol or String. Returns the registered Rigor::Type::Nominal,
110
+ # or nil when the name is unknown. Used by ExpressionTyper to resolve Prism::ConstantReadNode and
111
+ # Prism::ConstantPathNode under the fail-soft policy: unknown names MUST NOT raise and MUST flow
112
+ # through the engine's tracer.
120
113
  def nominal_for_name(name)
121
114
  return nil if name.nil?
122
115
 
@@ -2,32 +2,25 @@
2
2
 
3
3
  module Rigor
4
4
  class Environment
5
- # Per-key memoization container for {Environment#constant_for_name}.
6
- # Held by {Environment} so the otherwise-frozen instance can cache
7
- # constant-resolution results on first access — the sibling of
5
+ # Per-key memoization container for {Environment#constant_for_name}. Held by {Environment} so the
6
+ # otherwise-frozen instance can cache constant-resolution results on first access — the sibling of
8
7
  # {HktRegistryHolder}, generalised from one slot to a name-keyed map.
9
8
  #
10
- # Why this exists: `constant_for_name` is a pure function of its
11
- # name for a given Environment (both the predefined-constant
12
- # refinement table and the RBS loader's constant table are fixed for
13
- # the Environment's lifetime), yet it is the hottest non-dispatch
14
- # path on a large Rails app. {ExpressionTyper#resolve_constant_name}
15
- # peels a lexical-candidate ladder per constant reference, so the
16
- # same qualified names recur thousands of times across files — and
17
- # each miss runs a `const_get` walk that raises + rescues `NameError`
18
- # for every project-defined constant (the common case). Caching the
9
+ # Why this exists: `constant_for_name` is a pure function of its name for a given Environment (both the
10
+ # predefined-constant refinement table and the RBS loader's constant table are fixed for the
11
+ # Environment's lifetime), yet it is the hottest non-dispatch path on a large Rails app.
12
+ # {ExpressionTyper#resolve_constant_name} peels a lexical-candidate ladder per constant reference, so the
13
+ # same qualified names recur thousands of times across files — and each miss runs a `const_get` walk
14
+ # that raises + rescues `NameError` for every project-defined constant (the common case). Caching the
19
15
  # result collapses the repeats to a hash lookup.
20
16
  #
21
- # Caches `nil` results too (a name that resolves to no refined /
22
- # RBS constant type stays unresolved for the run), so the
23
- # const_get-and-rescue is paid at most once per distinct name.
17
+ # Caches `nil` results too (a name that resolves to no refined / RBS constant type stays unresolved for
18
+ # the run), so the const_get-and-rescue is paid at most once per distinct name.
24
19
  #
25
- # Concurrency: single-threaded use only, the same discipline as
26
- # {HktRegistryHolder} the Ractor pool builds a per-worker
27
- # Environment and the LSP single-publish invariant serialises the
28
- # shared-Environment reader path. If a future caller introduces a
29
- # multi-threaded reader against one Environment, the synchronisation
30
- # belongs at that caller's seam, not here.
20
+ # Concurrency: single-threaded use only, the same discipline as {HktRegistryHolder} — the Ractor pool
21
+ # builds a per-worker Environment and the LSP single-publish invariant serialises the shared-Environment
22
+ # reader path. If a future caller introduces a multi-threaded reader against one Environment, the
23
+ # synchronisation belongs at that caller's seam, not here.
31
24
  class ConstantTypeCacheHolder
32
25
  def initialize
33
26
  @cache = {}
@@ -2,19 +2,13 @@
2
2
 
3
3
  module Rigor
4
4
  class Environment
5
- # ADR-20 slice 2e — mutable single-slot memoization
6
- # container for the per-Environment HKT registry. Held by
7
- # {Environment} so the otherwise-frozen instance can
8
- # still cache a computed value on first access.
5
+ # ADR-20 slice 2e — mutable single-slot memoization container for the per-Environment HKT registry. Held
6
+ # by {Environment} so the otherwise-frozen instance can still cache a computed value on first access.
9
7
  #
10
- # Concurrent {#fetch} calls from multiple threads against
11
- # one Environment are NOT serialised here the LSP
12
- # single-publish-at-a-time discipline and the Ractor
13
- # pool's per-worker Environment shape already prevent
14
- # cross-thread races. If a future caller introduces a
15
- # multi-threaded reader path against a shared
16
- # Environment, the synchronisation belongs at that
17
- # caller's seam, not here.
8
+ # Concurrent {#fetch} calls from multiple threads against one Environment are NOT serialised here — the
9
+ # LSP single-publish-at-a-time discipline and the Ractor pool's per-worker Environment shape already
10
+ # prevent cross-thread races. If a future caller introduces a multi-threaded reader path against a
11
+ # shared Environment, the synchronisation belongs at that caller's seam, not here.
18
12
  class HktRegistryHolder
19
13
  def initialize
20
14
  @loaded = false
@@ -4,32 +4,25 @@ module Rigor
4
4
  class Environment
5
5
  # Gemfile.lock parser for target-project bundler awareness (O4 Layer 3, implemented).
6
6
  #
7
- # Parses a target project's `Gemfile.lock` via Bundler's
8
- # `LockfileParser` and exposes the locked gem set as a frozen
9
- # `Hash[String, LockfileResolver::LockedGem]` keyed by gem
10
- # name. Used by {Rigor::Environment::BundleSigDiscovery} as a
11
- # filter so the discovered `sig/` directories under the
12
- # bundler install root are limited to gems the project
13
- # actually declares (and at the version it declared them).
7
+ # Parses a target project's `Gemfile.lock` via Bundler's `LockfileParser` and exposes the locked gem set
8
+ # as a frozen `Hash[String, LockfileResolver::LockedGem]` keyed by gem name. Used by
9
+ # {Rigor::Environment::BundleSigDiscovery} as a filter so the discovered `sig/` directories under the
10
+ # bundler install root are limited to gems the project actually declares (and at the version it declared
11
+ # them).
14
12
  #
15
- # The resolver is intentionally read-only. It does NOT load
16
- # the project's `Gemfile`, does NOT resolve dependencies,
17
- # does NOT touch the network, and does NOT require the
18
- # target project's Bundler context. It only reads bytes from
19
- # the lockfile.
13
+ # The resolver is intentionally read-only. It does NOT load the project's `Gemfile`, does NOT resolve
14
+ # dependencies, does NOT touch the network, and does NOT require the target project's Bundler context. It
15
+ # only reads bytes from the lockfile.
20
16
  #
21
- # Failure modes are deliberately quiet: a missing or
22
- # malformed lockfile returns an empty map. The auto-detect
23
- # path is the configuration default; users who want hard
24
- # failures should pass an explicit `bundler.lockfile:` and
25
- # check the result via the stats banner.
17
+ # Failure modes are deliberately quiet: a missing or malformed lockfile returns an empty map. The
18
+ # auto-detect path is the configuration default; users who want hard failures should pass an explicit
19
+ # `bundler.lockfile:` and check the result via the stats banner.
26
20
  module LockfileResolver
27
21
  # Frozen value object for one locked gem entry.
28
22
  #
29
- # `version` is the resolved version string (e.g. "8.0.1");
30
- # `platform` is the lockfile's platform tag, normalised to
31
- # `"ruby"` when the lockfile records `ruby` and to the
32
- # raw String otherwise (e.g. "aarch64-linux-gnu").
23
+ # `version` is the resolved version string (e.g. "8.0.1"); `platform` is the lockfile's platform tag,
24
+ # normalised to `"ruby"` when the lockfile records `ruby` and to the raw String otherwise (e.g.
25
+ # "aarch64-linux-gnu").
33
26
  LockedGem = Data.define(:name, :version, :platform) do
34
27
  def initialize(name:, version:, platform:)
35
28
  super(
@@ -40,18 +33,15 @@ module Rigor
40
33
  end
41
34
  end
42
35
 
43
- # @param lockfile_path [String, Pathname, nil] explicit path
44
- # to the Gemfile.lock. When `nil`, falls back to
45
- # `auto_detect` if `auto_detect:` is true.
46
- # @param project_root [String] resolution base for a
47
- # relative `lockfile_path:` and the auto-detect search.
48
- # @param auto_detect [Boolean] when true and
49
- # `lockfile_path:` is nil, look for
36
+ # @param lockfile_path [String, Pathname, nil] explicit path to the Gemfile.lock. When `nil`, falls
37
+ # back to `auto_detect` if `auto_detect:` is true.
38
+ # @param project_root [String] resolution base for a relative `lockfile_path:` and the auto-detect
39
+ # search.
40
+ # @param auto_detect [Boolean] when true and `lockfile_path:` is nil, look for
50
41
  # `<project_root>/Gemfile.lock`.
51
- # @return [Hash{String => LockedGem}] frozen map of gem
52
- # name locked entry. Returns the empty frozen hash
53
- # when no lockfile is resolvable, when the file is
54
- # unreadable, or when Bundler refuses to parse it.
42
+ # @return [Hash{String => LockedGem}] frozen map of gem name → locked entry. Returns the empty frozen
43
+ # hash when no lockfile is resolvable, when the file is unreadable, or when Bundler refuses to parse
44
+ # it.
55
45
  def self.locked_gems(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
56
46
  resolved = resolve_lockfile_path(
57
47
  lockfile_path: lockfile_path,
@@ -63,9 +53,8 @@ module Rigor
63
53
  parse(resolved)
64
54
  end
65
55
 
66
- # Returns the resolved lockfile path (`Pathname`) or `nil`
67
- # when neither explicit nor auto-detect produces one.
68
- # Public so the stats banner can show what rigor picked up.
56
+ # Returns the resolved lockfile path (`Pathname`) or `nil` when neither explicit nor auto-detect
57
+ # produces one. Public so the stats banner can show what rigor picked up.
69
58
  def self.resolve_lockfile_path(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
70
59
  if lockfile_path
71
60
  path = Pathname.new(File.expand_path(lockfile_path.to_s, project_root))
@@ -83,12 +72,9 @@ module Rigor
83
72
  EMPTY = {}.freeze
84
73
  private_constant :EMPTY
85
74
 
86
- # Parses a Gemfile.lock at the given path. Bundler load
87
- # errors and malformed lockfile bytes both surface as the
88
- # empty frozen hash; analysis must not crash because a
89
- # lockfile is malformed. A single warning is emitted to
90
- # `$stderr` so the user can see why their lockfile was
91
- # ignored.
75
+ # Parses a Gemfile.lock at the given path. Bundler load errors and malformed lockfile bytes both
76
+ # surface as the empty frozen hash; analysis must not crash because a lockfile is malformed. A single
77
+ # warning is emitted to `$stderr` so the user can see why their lockfile was ignored.
92
78
  def self.parse(path)
93
79
  require "bundler"
94
80
  rescue LoadError => e
@@ -103,12 +89,10 @@ module Rigor
103
89
  body = File.read(path.to_s)
104
90
  parser = Bundler::LockfileParser.new(body)
105
91
  locked = parser.specs.each_with_object({}) do |spec, h|
106
- # `Bundler::LazySpecification` carries name, version,
107
- # platform. Platform is `Gem::Platform` or the symbol
108
- # `:ruby`; both stringify cleanly. The upstream
109
- # bundler RBS shim (references/rbs/sig/shims/bundler.rbs)
110
- # does NOT declare `LazySpecification#platform` so the
111
- # call site needs a suppression marker.
92
+ # `Bundler::LazySpecification` carries name, version, platform. Platform is `Gem::Platform` or the
93
+ # symbol `:ruby`; both stringify cleanly. The upstream bundler RBS shim
94
+ # (references/rbs/sig/shims/bundler.rbs) does NOT declare `LazySpecification#platform` so the call
95
+ # site needs a suppression marker.
112
96
  platform = spec.platform.to_s # rigor:disable undefined-method
113
97
  h[spec.name.to_s] = LockedGem.new(
114
98
  name: spec.name, version: spec.version.to_s, platform: platform