rigortype 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -56,16 +56,16 @@ module Rigor
56
56
 
57
57
  module_function
58
58
 
59
- # @param table [EffectTable] the run's propagated graph.
60
- # @param superclasses [Hash{String => Array<String>}] the collector's as-written superclass
59
+ # @param table the run's propagated graph.
60
+ # @param superclasses the collector's as-written superclass
61
61
  # candidate lists (`FileCollection#superclasses`).
62
- # @param method_envelopes [Hash{String => Envelope}] per-method envelopes, as written.
63
- # @param class_envelopes [Hash{String => Envelope}] class- / module-level envelopes, to distribute.
64
- # @param config_envelopes [Hash{String => Envelope}] `effects.envelopes:` entries already resolved
62
+ # @param method_envelopes per-method envelopes, as written.
63
+ # @param class_envelopes class- / module-level envelopes, to distribute.
64
+ # @param config_envelopes `effects.envelopes:` entries already resolved
65
65
  # to the classes they select.
66
- # @param positions [EnvelopeCheck::Positions] where the override's `def` is.
67
- # @param apply_tolerated [Boolean] false judges against `proven` — `--no-tolerated-effects`.
68
- # @return [Array<Finding>] sorted by position then key then label.
66
+ # @param positions where the override's `def` is.
67
+ # @param apply_tolerated false judges against `proven` — `--no-tolerated-effects`.
68
+ # @return sorted by position then key then label.
69
69
  def run(table:, superclasses:, method_envelopes:, class_envelopes:, config_envelopes: {},
70
70
  positions: EnvelopeCheck::Positions.empty, apply_tolerated: true)
71
71
  # The distributed strata, over a base of the raw per-method annotations: a base class whose method
@@ -14,7 +14,7 @@ module Rigor
14
14
  module MethodKey
15
15
  module_function
16
16
 
17
- # @return [Array(String, String, String), nil] `[owner, separator, selector]`, or nil when `key` is
17
+ # @return `[owner, separator, selector]`, or nil when `key` is
18
18
  # not a method key at all.
19
19
  def split(key)
20
20
  text = key.to_s
@@ -77,11 +77,10 @@ module Rigor
77
77
  @empty ||= new(contributions: [], superclasses: NO_ROWS, includes: NO_ROWS)
78
78
  end
79
79
 
80
- # @param plugin_registry [Rigor::Plugin::Registry, nil]
81
- # @param superclasses [Hash{String=>String,Array<String>}] the project's as-written superclass table
80
+ # @param superclasses the project's as-written superclass table
82
81
  # (`Scope::DiscoveryIndex#discovered_superclasses`). Empty is legal and simply means no row matches
83
82
  # through inheritance.
84
- # @param includes [Hash{String=>Array<String>}] the project's as-written include / prepend table
83
+ # @param includes the project's as-written include / prepend table
85
84
  # (`Scope::DiscoveryIndex#discovered_includes`), for the frameworks whose contract is a module
86
85
  # rather than a base class (#456).
87
86
  def self.build(plugin_registry, superclasses: NO_ROWS, includes: NO_ROWS)
@@ -144,9 +143,8 @@ module Rigor
144
143
 
145
144
  # The row colouring `owner`'s `selector`, found on `owner` itself or on a project ancestor of it.
146
145
  #
147
- # @param owner [String, nil] the receiver's class name as the syntax or the typer named it
148
- # @param singleton [Boolean] whether the call is `Owner.selector`
149
- # @return [Row, nil]
146
+ # @param owner the receiver's class name as the syntax or the typer named it
147
+ # @param singleton whether the call is `Owner.selector`
150
148
  def class_row(owner, singleton, selector)
151
149
  return nil if owner.nil? || @class_rows.empty?
152
150
 
@@ -45,10 +45,9 @@ module Rigor
45
45
 
46
46
  module_function
47
47
 
48
- # @param collection [FileCollection] the run's merged per-file collections
49
- # @param discharge [Discharge] the `effects.tolerated:` policy the undischarged lane is computed
48
+ # @param collection the run's merged per-file collections
49
+ # @param discharge the `effects.tolerated:` policy the undischarged lane is computed
50
50
  # under; {Discharge.none} makes the two lanes equal.
51
- # @return [EffectTable]
52
51
  def propagate(collection, discharge: Discharge.none)
53
52
  return EffectTable.empty if collection.summaries.empty?
54
53
 
@@ -184,7 +184,7 @@ module Rigor
184
184
  # per unit (ADR-103 WD13): a unit the scanner cannot finish is recorded as non-exhaustive with
185
185
  # `collector-error` and its siblings are unaffected.
186
186
  #
187
- # @return [UnitScan, nil] the finished scan, or nil when the unit failed soft
187
+ # @return the finished scan, or nil when the unit failed soft
188
188
  def add_unit(class_name, method_name, singleton, body, parameters)
189
189
  key = "#{class_name}#{singleton ? '.' : '#'}#{method_name}"
190
190
  names = parameter_names(parameters)
@@ -52,9 +52,8 @@ module Rigor
52
52
  stripped.start_with?(root) ? stripped[root.length..] : stripped
53
53
  end
54
54
 
55
- # @param signature_paths [Array<String>, nil] the configured roots; empty / nil takes the default.
56
- # @param virtual_rbs [Array<Array(String, String)>, nil] the loader's virtual entries.
57
- # @return [Array<Array(String, String)>]
55
+ # @param signature_paths the configured roots; empty / nil takes the default.
56
+ # @param virtual_rbs the loader's virtual entries.
58
57
  def collect(signature_paths:, virtual_rbs: nil)
59
58
  files = roots(signature_paths).flat_map { |root| Dir.glob(File.join(root.to_s, "**", "*.rbs")).sort }
60
59
  sources = files.filter_map do |path|
@@ -76,8 +75,7 @@ module Rigor
76
75
  # residual reports one annotation per run, so the carrier a run holds past the environment's own
77
76
  # lifetime is a single synthesized buffer rather than the project's whole virtual tree.
78
77
  #
79
- # @param virtual_rbs [Array<Array(String, String)>, nil]
80
- # @return [Array<Array(String, String)>] zero or one pair.
78
+ # @return zero or one pair.
81
79
  def annotated_carrier(virtual_rbs)
82
80
  Array(virtual_rbs).each do |name, content|
83
81
  return [[name.to_s, content.to_s]] if ANNOTATION_HINT.match?(content)
@@ -117,12 +117,12 @@ module Rigor
117
117
  class << self
118
118
  # Builds the snapshot a run's effect table describes.
119
119
  #
120
- # @param table [EffectTable] the propagated graph
121
- # @param configuration [Rigor::Configuration] supplies the reach globs and the digested block
122
- # @param sources [Hash{String=>Array<String>}] `Runner#effect_sources` — where each unit is defined
123
- # @param full [Boolean] keep the rows {#omit?} drops
124
- # @param registry [Registry] the vocabulary whose version the header carries
125
- # @param project_root [String] what `sources` paths are relativised against for glob matching
120
+ # @param table the propagated graph
121
+ # @param configuration supplies the reach globs and the digested block
122
+ # @param sources `Runner#effect_sources` — where each unit is defined
123
+ # @param full keep the rows {#omit?} drops
124
+ # @param registry the vocabulary whose version the header carries
125
+ # @param project_root what `sources` paths are relativised against for glob matching
126
126
  def build(table:, configuration:, sources: {}, full: false, registry: Registry.default,
127
127
  project_root: Dir.pwd)
128
128
  globs = expand_reach(configuration.effects_snapshot_reach)
@@ -146,10 +146,10 @@ module Rigor
146
146
  # It is built beside the snapshot and never inside it: the file stays flat and undischarged, and
147
147
  # this index exists only for the duration of one comparison ({SnapshotDiff}).
148
148
  #
149
- # @param snapshot [Snapshot] the current side, already built
150
- # @param table [EffectTable] the run that produced it
151
- # @param discharge [Discharge] the policy
152
- # @return [Hash, nil] nil when the policy discharges nothing, which is the common case
149
+ # @param snapshot the current side, already built
150
+ # @param table the run that produced it
151
+ # @param discharge the policy
152
+ # @return nil when the policy discharges nothing, which is the common case
153
153
  def undischarged_index(snapshot:, table:, discharge:)
154
154
  return nil if discharge.inert?
155
155
 
@@ -86,7 +86,7 @@ module Rigor
86
86
  # Compares `current` against `recorded`. `recorded` may be nil — no snapshot on disk at all, which
87
87
  # is drift with a routed message rather than an error.
88
88
  #
89
- # @param undischarged [Hash] `{table name => {symbol => [label]}}` — the current side's labels that
89
+ # @param undischarged `{table name => {symbol => [label]}}` — the current side's labels that
90
90
  # survive per-origin discharge ({Snapshot.undischarged_index}). Optional; omitting it judges every
91
91
  # event by label.
92
92
  def self.compare(recorded:, current:, tolerated: [], gate: :symmetric, strict_tolerated: false,
@@ -87,7 +87,7 @@ module Rigor
87
87
  # the enclosing method contains. A non-literal name has no key to file the block under, so it stays
88
88
  # contained in the enclosing method and this returns nil.
89
89
  #
90
- # @return [Array, nil] `[name, singleton, body, parameters]`
90
+ # @return `[name, singleton, body, parameters]`
91
91
  def self.define_method_unit(node)
92
92
  return nil unless node.name == :define_method && node.receiver.nil?
93
93
 
@@ -100,17 +100,17 @@ module Rigor
100
100
  [first.unescaped, false, block.body, block.parameters]
101
101
  end
102
102
 
103
- # @param singleton [Boolean] whether the unit's `self` is the class object (`def self.x`,
103
+ # @param singleton whether the unit's `self` is the class object (`def self.x`,
104
104
  # `class << self`) — the axis that separates `mutate.self` from `mutate.static` on an ivar write
105
- # @param block_parameter [String, nil] the unit's `&blk` parameter name, if any; a call on it is
105
+ # @param block_parameter the unit's `&blk` parameter name, if any; a call on it is
106
106
  # forwarding, not an opaque callable
107
- # @param calls [Hash] node-identity table of {Collector::CallRecord}s
108
- # @param attribution [Attribution] the project's `effects.attribution:` table
109
- # @param envelopes [EnvelopeIndex] the envelopes a call site may import as a `≤` bound (#386)
110
- # @param plugin_facts [PluginFacts] the loaded plugins' `effect_attributions:` (#387)
111
- # @param owner_class [String, nil] the class this unit is defined on — the carrier an
107
+ # @param calls node-identity table of {Collector::CallRecord}s
108
+ # @param attribution the project's `effects.attribution:` table
109
+ # @param envelopes the envelopes a call site may import as a `≤` bound (#386)
110
+ # @param plugin_facts the loaded plugins' `effect_attributions:` (#387)
111
+ # @param owner_class the class this unit is defined on — the carrier an
112
112
  # implicit-self call's envelope is looked up under, since the syntax spells `Kernel#name`
113
- # @param method_name [String, nil] this unit's own selector — what a `super` in its body names as
113
+ # @param method_name this unit's own selector — what a `super` in its body names as
114
114
  # the target the propagator resolves above `owner_class` (#446). With no name to state, a `super`
115
115
  # taints instead.
116
116
  def initialize(singleton:, parameters:, block_parameter:, owned_locals:, calls:, # rubocop:disable Metrics/ParameterLists
@@ -283,7 +283,7 @@ module Rigor
283
283
  # Three receiver shapes, tried nearest-syntax first — a written receiver path (`Rails.cache.read`),
284
284
  # a receiver rooted at implicit self inside a framework class (`session[:x] = 1`), and the receiver's
285
285
  # class through the project's inheritance chain (`user.save`).
286
- # @return [PluginFacts::Row, nil] the row that claimed this call, for {#visit_call} to read as a bound
286
+ # @return the row that claimed this call, for {#visit_call} to read as a bound
287
287
  def attribute_plugin(node, record)
288
288
  return nil unless @plugin_facts.attributions?
289
289
 
@@ -30,10 +30,6 @@ module Rigor
30
30
 
31
31
  module_function
32
32
 
33
- # @param method_envelopes [Hash{String => Envelope}]
34
- # @param class_envelopes [Hash{String => Envelope}]
35
- # @param registry [Rigor::Effects::Registry]
36
- # @return [Array<Finding>]
37
33
  def for_envelopes(method_envelopes:, class_envelopes:, registry:)
38
34
  seen = {}
39
35
  declarations(method_envelopes, class_envelopes).each do |envelope|
@@ -49,10 +45,9 @@ module Rigor
49
45
  seen.values
50
46
  end
51
47
 
52
- # @param labels [Array<String>] the list as written, in source order.
53
- # @param key_path [String] the `.rigor.yml` key, for the message (`effects.tolerated`).
54
- # @param consequence [String] what the degradation cost, for the message.
55
- # @return [Array<Finding>]
48
+ # @param labels the list as written, in source order.
49
+ # @param key_path the `.rigor.yml` key, for the message (`effects.tolerated`).
50
+ # @param consequence what the degradation cost, for the message.
56
51
  def for_config(labels:, key_path:, consequence:, registry:)
57
52
  tokens = Array(labels).map(&:to_s)
58
53
  tokens.filter_map do |token|
@@ -17,10 +17,9 @@ module Rigor
17
17
  # {.for} answers `nil` unless {LabelIntent} says the spelling is evidently a label — the whole
18
18
  # point of the diagnostic is that it fires where intent is evident and nowhere else.
19
19
  class UnknownLabelReport < Data.define(:token, :suggestion, :retirement)
20
- # @param token [String] the spelling as written.
21
- # @param registry [Rigor::Effects::Registry, nil] the vocabulary after plugin load.
22
- # @param siblings [Array<String>] the other tokens written alongside it.
23
- # @return [UnknownLabelReport, nil]
20
+ # @param token the spelling as written.
21
+ # @param registry the vocabulary after plugin load.
22
+ # @param siblings the other tokens written alongside it.
24
23
  def self.for(token:, registry:, siblings: [])
25
24
  return nil unless LabelIntent.evident?(token, registry, siblings: siblings)
26
25
 
@@ -46,14 +46,14 @@ module Rigor
46
46
  "bundler", "rubygems"
47
47
  ].freeze
48
48
 
49
- # @param bundle_path [String, Pathname, nil] explicit path to the bundler install root. When `nil`,
49
+ # @param bundle_path explicit path to the bundler install root. When `nil`,
50
50
  # falls back to `auto_detect` if `auto_detect:` is true.
51
- # @param project_root [String] resolution base for relative `bundle_path:` and the auto-detect search.
52
- # @param auto_detect [Boolean] when true and `bundle_path:` is nil, try `.bundle/config`'s
51
+ # @param project_root resolution base for relative `bundle_path:` and the auto-detect search.
52
+ # @param auto_detect when true and `bundle_path:` is nil, try `.bundle/config`'s
53
53
  # `BUNDLE_PATH:` and `vendor/bundle/` under `project_root`.
54
- # @param skip_gems [Set<String>] gem names to exclude from discovery. Defaults to
54
+ # @param skip_gems gem names to exclude from discovery. Defaults to
55
55
  # {SKIPPED_GEMS_BY_DEFAULT}.
56
- # @param locked_gems [Hash{String => LockfileResolver::LockedGem}, nil] Optional O4-Layer-3 filter.
56
+ # @param locked_gems Optional O4-Layer-3 filter.
57
57
  # When non-nil and non-empty, only `sig/` directories whose gem `(name, version, platform)` tuple
58
58
  # matches a lockfile entry are returned. Bundle entries absent from the lockfile (or at a drifted
59
59
  # version) are silently dropped — the lockfile is treated as the source of truth for "what gems this
@@ -63,7 +63,7 @@ module Rigor
63
63
  # source keeps its stale `bundler/gems/` directory under the SAME name until a `bundle clean`. Pass
64
64
  # `nil` (the default) to keep the pre-Layer-3 behaviour of returning every non-skipped `sig/` under
65
65
  # the bundle.
66
- # @return [Array<Pathname>] every `<gem-dir>/sig` directory under the resolved bundle path, minus any
66
+ # @return every `<gem-dir>/sig` directory under the resolved bundle path, minus any
67
67
  # whose gem name is in `skip_gems` and (when `locked_gems` is supplied) minus any whose `(name,
68
68
  # version, platform)` does not match a lockfile entry.
69
69
  def self.discover(bundle_path:, project_root: Dir.pwd, auto_detect: true,
@@ -136,8 +136,10 @@ module Rigor
136
136
 
137
137
  private
138
138
 
139
+ # Allocates only when there is a prefix to strip (see `Environment#normalize_class_name`).
139
140
  def normalize_name(name)
140
- name.to_s.delete_prefix("::")
141
+ name = name.to_s
142
+ name.start_with?("::") ? name.delete_prefix("::") : name
141
143
  end
142
144
  end
143
145
  end
@@ -16,12 +16,12 @@ module Rigor
16
16
  @value = nil
17
17
  end
18
18
 
19
- # @param value [Array] a Marshal-clean tuple — the fork pool ships it back from the worker.
19
+ # @param value a Marshal-clean tuple — the fork pool ships it back from the worker.
20
20
  def record(value)
21
21
  @value = value.freeze if @value.nil?
22
22
  end
23
23
 
24
- # @return [Array, nil] the recorded tuple, or nil when the build never failed.
24
+ # @return the recorded tuple, or nil when the build never failed.
25
25
  attr_reader :value
26
26
  end
27
27
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lockfile_resolver"
4
+
5
+ module Rigor
6
+ class Environment
7
+ # Issue #530 — the gem set for a project that has NO `Gemfile.lock`.
8
+ #
9
+ # ADR-82 WD9's constant-ownership index is driven by the LOCKED gem set, so a project without a
10
+ # lockfile (a plain library, a `gemspec`-only gem, an app vendored outside Bundler) handed the index
11
+ # nothing at all: every constant reaching into a real, installed, RBS-less gem recorded the generic
12
+ # cause, and the entire gem boundary reported as `engine_gap` — "report this to Rigor" — where the
13
+ # honest story is `add_rbs`. Measured on slim, whose whole Temple boundary lands that way while haml,
14
+ # which has a lockfile, attributes the same gem correctly.
15
+ #
16
+ # Degrading to the gems that are actually INSTALLED keeps ADR-82's honesty criterion intact. A name
17
+ # here is a gem that exists on disk; the index still has to READ its entry file before claiming any
18
+ # constant, and its lack of RBS is established by the same {RbsCoverageReport.classify} pass the
19
+ # lock-driven path uses. The version is the one on disk rather than a declared one — which is the
20
+ # version a lockfile-less project would load anyway.
21
+ #
22
+ # Only the missing-gem provenance index consumes this. The ADR-72 gem overlays stay Gemfile.lock-gated:
23
+ # an overlay changes what type-checks, so it needs the project's own declaration, while this
24
+ # side-channel only changes a label.
25
+ module InstalledGemSet
26
+ module_function
27
+
28
+ # @param bundle_path — the target's resolved bundler install root, or nil.
29
+ # @return frozen `gem name => LockfileResolver::LockedGem`, the shape
30
+ # {RbsCoverageReport.classify} consumes. Empty when neither resolver sees anything, which leaves
31
+ # today's generic cause in place.
32
+ def gems(bundle_path: nil)
33
+ found = from_bundle_tree(bundle_path)
34
+ found = from_installed_specs if found.empty?
35
+ found.freeze
36
+ end
37
+
38
+ # `<bundle>/ruby/X.Y.Z/gems/<name>-<version>/` — the same pure-filesystem layout
39
+ # {MissingGemConstantIndex.bundle_gem_dirs} walks, preferred over the host's gems so a project that
40
+ # vendored its bundle without committing the lockfile is read from its own tree. A platform-tagged
41
+ # directory (`ffi-1.17.4-aarch64-linux-gnu`) does not split into a name/version pair and is skipped:
42
+ # those gems ship native code, not the pure-Ruby constants this feeds.
43
+ def from_bundle_tree(bundle_path)
44
+ return {} if bundle_path.nil?
45
+
46
+ base = Pathname.new(bundle_path)
47
+ return {} unless base.directory?
48
+
49
+ Dir.glob(base.join("ruby", "*", "gems", "*")).each_with_object({}) do |dir, acc|
50
+ name, version = split_name_version(File.basename(dir))
51
+ next unless name
52
+
53
+ acc[name] ||= locked_gem(name, version)
54
+ end
55
+ end
56
+
57
+ # The gems the RUNNING Ruby can load. For the shipped `gem install rigortype` distribution that IS a
58
+ # lockfile-less project's gem environment — the same set its own `require` would reach. Under
59
+ # `bundle exec` it is the analyzer's own bundle instead (the `Gem.paths`-no-ops-under-Bundler
60
+ # mechanism ADR-90 documents), which yields fewer names, never wrong ones. Highest version per name,
61
+ # because `stubs` promises no ordering.
62
+ def from_installed_specs
63
+ Gem::Specification.stubs.each_with_object({}) do |stub, acc|
64
+ previous = acc[stub.name]
65
+ next if previous && Gem::Version.new(previous.version) >= stub.version
66
+
67
+ acc[stub.name] = locked_gem(stub.name, stub.version.to_s)
68
+ end
69
+ rescue StandardError
70
+ {}
71
+ end
72
+
73
+ def locked_gem(name, version)
74
+ LockfileResolver::LockedGem.new(name: name, version: version, platform: "ruby")
75
+ end
76
+
77
+ # Splits a gem install directory's basename. The version tail is anchored on a leading digit and must
78
+ # carry no further hyphen, which is what excludes the platform-tagged variants above.
79
+ def split_name_version(basename)
80
+ match = /\A(.+)-(\d[^-]*)\z/.match(basename)
81
+ match ? [match[1], match[2]] : nil
82
+ end
83
+ end
84
+ end
85
+ end
@@ -39,13 +39,13 @@ module Rigor
39
39
  end
40
40
  end
41
41
 
42
- # @param lockfile_path [String, Pathname, nil] explicit path to the Gemfile.lock. When `nil`, falls
42
+ # @param lockfile_path explicit path to the Gemfile.lock. When `nil`, falls
43
43
  # back to `auto_detect` if `auto_detect:` is true.
44
- # @param project_root [String] resolution base for a relative `lockfile_path:` and the auto-detect
44
+ # @param project_root resolution base for a relative `lockfile_path:` and the auto-detect
45
45
  # search.
46
- # @param auto_detect [Boolean] when true and `lockfile_path:` is nil, look for
46
+ # @param auto_detect when true and `lockfile_path:` is nil, look for
47
47
  # `<project_root>/Gemfile.lock`.
48
- # @return [Hash{String => LockedGem}] frozen map of gem name → locked entry. Returns the empty frozen
48
+ # @return frozen map of gem name → locked entry. Returns the empty frozen
49
49
  # hash when no lockfile is resolvable, when the file is unreadable, or when Bundler refuses to parse
50
50
  # it.
51
51
  def self.locked_gems(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
@@ -59,6 +59,53 @@ module Rigor
59
59
  parse(resolved)
60
60
  end
61
61
 
62
+ # The gems the project declares for itself — the lockfile's `DEPENDENCIES` section, which is what a
63
+ # `Gemfile` actually asked for, as opposed to {locked_gems}'s full resolved graph. A consumer that
64
+ # advises the user about their own stack has to read this one: `minitest`, `i18n`, `activesupport` and
65
+ # `ffi` sit in nearly every Rails lock as somebody else's dependency, so matching against the resolved
66
+ # graph advises about gems the project never chose.
67
+ #
68
+ # @param lockfile_path — explicit path to the Gemfile.lock; `nil` falls back to `auto_detect`.
69
+ # @param project_root — resolution base for a relative `lockfile_path:` and the auto-detect search.
70
+ # @param auto_detect — when true and `lockfile_path:` is nil, look for `<project_root>/Gemfile.lock`.
71
+ # @return frozen set of gem names. Empty when no lockfile is resolvable, when it is unreadable, when
72
+ # Bundler refuses to parse it, or when the lockfile has no `DEPENDENCIES` section at all.
73
+ def self.direct_dependency_names(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
74
+ resolved = resolve_lockfile_path(
75
+ lockfile_path: lockfile_path,
76
+ project_root: project_root,
77
+ auto_detect: auto_detect
78
+ )
79
+ return EMPTY_NAMES unless resolved
80
+
81
+ parse_dependency_names(resolved)
82
+ end
83
+
84
+ EMPTY_NAMES = Set.new.freeze
85
+ private_constant :EMPTY_NAMES
86
+
87
+ def self.parse_dependency_names(path)
88
+ require "bundler"
89
+ rescue LoadError => e
90
+ warn "rigor: cannot read #{path}: bundler is not available (#{e.message})"
91
+ EMPTY_NAMES
92
+ else
93
+ do_parse_dependency_names(path)
94
+ end
95
+ private_class_method :parse_dependency_names
96
+
97
+ def self.do_parse_dependency_names(path)
98
+ parser = Bundler::LockfileParser.new(File.read(path.to_s))
99
+ # `#dependencies` is the parsed `DEPENDENCIES` section keyed by gem name, so the version constraint
100
+ # and the pinned-source `!` suffix are already stripped for us. The upstream bundler RBS shim does
101
+ # not declare it.
102
+ parser.dependencies.keys.to_set { |name| -name.to_s }.freeze # rigor:disable undefined-method
103
+ rescue StandardError => e
104
+ warn "rigor: ignoring malformed #{path} (#{e.class}: #{e.message})"
105
+ EMPTY_NAMES
106
+ end
107
+ private_class_method :do_parse_dependency_names
108
+
62
109
  # Returns the resolved lockfile path (`Pathname`) or `nil` when neither explicit nor auto-detect
63
110
  # produces one. Public so the stats banner can show what rigor picked up.
64
111
  def self.resolve_lockfile_path(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
@@ -37,12 +37,12 @@ module Rigor
37
37
  module MissingGemConstantIndex
38
38
  module_function
39
39
 
40
- # @param gems [Enumerable<Array(String, String)>] `[gem_name, version]` pairs (the `:missing` rows of
40
+ # @param gems `[gem_name, version]` pairs (the `:missing` rows of
41
41
  # {RbsCoverageReport}).
42
- # @param bundle_path [String, Pathname, nil] the target's resolved bundler install root, or nil.
43
- # @param spec_resolver [#call] `(name, version) -> String?` fallback dir resolver. Injectable for
42
+ # @param bundle_path the target's resolved bundler install root, or nil.
43
+ # @param spec_resolver `(name, version) -> String?` fallback dir resolver. Injectable for
44
44
  # specs; the default is the RubyGems-metadata lookup (no code load).
45
- # @return [Hash{String => String}] frozen `root constant name => gem name`. On a collision (two gems
45
+ # @return frozen `root constant name => gem name`. On a collision (two gems
46
46
  # declaring the same top-level constant) the first gem wins — the CAUSE recorded downstream
47
47
  # (external gem without RBS) is true under either owner.
48
48
  def build(gems, bundle_path: nil, spec_resolver: method(:installed_gem_dir))
@@ -37,17 +37,17 @@ module Rigor
37
37
  DEFAULT_COLLECTION_PATH = ".gem_rbs_collection"
38
38
  private_constant :DEFAULT_COLLECTION_PATH
39
39
 
40
- # @param lockfile_path [String, Pathname, nil] explicit path to `rbs_collection.lock.yaml`. When `nil`,
40
+ # @param lockfile_path explicit path to `rbs_collection.lock.yaml`. When `nil`,
41
41
  # falls back to `auto_detect` if `auto_detect:` is true.
42
- # @param project_root [String] resolution base for relative `lockfile_path:` and the auto-detect
42
+ # @param project_root resolution base for relative `lockfile_path:` and the auto-detect
43
43
  # search.
44
- # @param auto_detect [Boolean] when true and `lockfile_path:` is nil, look for
44
+ # @param auto_detect when true and `lockfile_path:` is nil, look for
45
45
  # `<project_root>/rbs_collection.lock.yaml`.
46
- # @param skip_gem_names [Array<String>, Set<String>] gem names rigor already loads from its bundled
46
+ # @param skip_gem_names gem names rigor already loads from its bundled
47
47
  # stdlib (the merged `DEFAULT_LIBRARIES + libraries:` set). Entries whose `name` is in this set are
48
48
  # dropped regardless of `source.type` to avoid `RBS::DuplicatedDeclarationError` on
49
49
  # stdlib-extracted default gems. Defaults to empty.
50
- # @return [Array<Pathname>] every `<collection_path>/<gem-name>/<gem-version>/` directory listed in the
50
+ # @return every `<collection_path>/<gem-name>/<gem-version>/` directory listed in the
51
51
  # lockfile whose entry has a non-skipped source type, whose `name` is not in `skip_gem_names:`, and
52
52
  # whose directory exists on disk. Returns `[]` when no lockfile is resolvable, when the YAML is
53
53
  # unreadable, or when the collection path doesn't exist.
@@ -35,16 +35,16 @@ module Rigor
35
35
  "idn-ruby", "mysql2", "nokogiri", "pg", "prism", "racc", "redis", "rubygems"
36
36
  ].freeze
37
37
 
38
- # @param locked_gems [Hash{String => LockfileResolver::LockedGem}] The lockfile-resolved gem set. Empty
38
+ # @param locked_gems The lockfile-resolved gem set. Empty
39
39
  # hash → no coverage analysis to do.
40
- # @param default_libraries [Array<String>] gem names rigor auto-loads through
40
+ # @param default_libraries gem names rigor auto-loads through
41
41
  # `RBS::EnvironmentLoader#add(library:)`. Pass `Rigor::Environment::DEFAULT_LIBRARIES` from callers
42
42
  # running in a project context.
43
- # @param bundle_sig_paths [Array<Pathname, String>] the discovered `<bundle>/.../gems/<name>-<ver>/sig`
43
+ # @param bundle_sig_paths the discovered `<bundle>/.../gems/<name>-<ver>/sig`
44
44
  # paths from {BundleSigDiscovery.discover}.
45
- # @param rbs_collection_paths [Array<Pathname, String>] the discovered `<collection>/<name>/<version>/`
45
+ # @param rbs_collection_paths the discovered `<collection>/<name>/<version>/`
46
46
  # paths from {RbsCollectionDiscovery.discover}.
47
- # @return [Array<Coverage>] one row per locked gem; sorted by gem name for deterministic output.
47
+ # @return one row per locked gem; sorted by gem name for deterministic output.
48
48
  def self.classify(locked_gems:, default_libraries:,
49
49
  bundle_sig_paths:, rbs_collection_paths:)
50
50
  default_set = default_libraries.to_set
@@ -63,8 +63,10 @@ module Rigor
63
63
  @ancestor_names_cache[key] = loader.ancestor_names_for(key)
64
64
  end
65
65
 
66
+ # Allocates only when there is a prefix to strip (see `Environment#normalize_class_name`).
66
67
  def normalize_name(name)
67
- name.to_s.delete_prefix("::")
68
+ name = name.to_s
69
+ name.start_with?("::") ? name.delete_prefix("::") : name
68
70
  end
69
71
  end
70
72
  end