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
@@ -5,75 +5,54 @@ require "rigor/plugin"
5
5
 
6
6
  module Rigor
7
7
  module Plugin
8
- # rigor-mangrove — precision plugin for the
9
- # [Mangrove](https://github.com/kazzix14/mangrove) functional
8
+ # rigor-mangrove — precision plugin for the [Mangrove](https://github.com/kazzix14/mangrove) functional
10
9
  # toolkit (Result / Option carriers).
11
10
  #
12
11
  # ## Why this plugin exists (and what it deliberately is NOT)
13
12
  #
14
- # Mangrove is a Sorbet-first library: every carrier method is
15
- # annotated with an inline `sig { ... }`, and the Enum DSL's
16
- # dynamic variants are materialised by a bundled Tapioca DSL
17
- # compiler. So the *type source* for Mangrove is already
18
- # covered by `rigor-sorbet` (sig ingestion + RBI walking) —
19
- # this plugin is NOT a parallel type source. See
20
- # `docs/notes/20260530-mangrove-library-survey.md`.
13
+ # Mangrove is a Sorbet-first library: every carrier method is annotated with an inline `sig { ... }`, and
14
+ # the Enum DSL's dynamic variants are materialised by a bundled Tapioca DSL compiler. So the *type source*
15
+ # for Mangrove is already covered by `rigor-sorbet` (sig ingestion + RBI walking) — this plugin is NOT a
16
+ # parallel type source. See `docs/notes/20260530-mangrove-library-survey.md`.
21
17
  #
22
- # What sig-ingestion (Sorbet-level precision) structurally
23
- # cannot do is **instantiate the carrier's generic
24
- # type-member at the unwrap call site**. Mangrove's `Result`
25
- # interface declares:
18
+ # What sig-ingestion (Sorbet-level precision) structurally cannot do is **instantiate the carrier's generic
19
+ # type-member at the unwrap call site**. Mangrove's `Result` interface declares:
26
20
  #
27
21
  # sig { abstract.returns(OkType) }
28
22
  # def unwrap!; end
29
23
  #
30
- # `OkType` is a `type_member(:out)`. When the receiver's
31
- # static type is `Mangrove::Result[String, StandardError]`,
32
- # `unwrap!` *should* yield `String` but resolving the
33
- # abstract `OkType` to the receiver's first type argument is
34
- # generic instantiation, which neither `rigor-sorbet` (it
35
- # contributes the cataloged sig's return type verbatim) nor a
36
- # bare-`def unwrap!: () -> untyped` RBS stub performs. The
37
- # call degrades to `Dynamic[top]`.
24
+ # `OkType` is a `type_member(:out)`. When the receiver's static type is
25
+ # `Mangrove::Result[String, StandardError]`, `unwrap!` *should* yield `String` — but resolving the abstract
26
+ # `OkType` to the receiver's first type argument is generic instantiation, which neither `rigor-sorbet` (it
27
+ # contributes the cataloged sig's return type verbatim) nor a bare-`def unwrap!: () -> untyped` RBS stub
28
+ # performs. The call degrades to `Dynamic[top]`.
38
29
  #
39
- # This plugin closes exactly that gap. At every recognised
40
- # unwrap-family call on a known Mangrove carrier, it reads the
41
- # receiver's `Rigor::Type::Nominal#type_args` and contributes
42
- # the first argument (the `OkType` / `InnerType`) as the
43
- # call's return type. The receiver type is already known, so
44
- # this only ever *sharpens* an existing `Dynamic[top]` into a
45
- # concrete type — it never invents a receiver type and never
46
- # emits a diagnostic of its own, so it cannot frighten working
47
- # code (Rigor's false-positive discipline).
30
+ # This plugin closes exactly that gap. At every recognised unwrap-family call on a known Mangrove carrier,
31
+ # it reads the receiver's `Rigor::Type::Nominal#type_args` and contributes the first argument (the
32
+ # `OkType` / `InnerType`) as the call's return type. The receiver type is already known, so this only ever
33
+ # *sharpens* an existing `Dynamic[top]` into a concrete type — it never invents a receiver type and never
34
+ # emits a diagnostic of its own, so it cannot frighten working code (Rigor's false-positive discipline).
48
35
  #
49
36
  # ## Scope (slice 1)
50
37
  #
51
- # - Result unwrap family → `OkType` (`type_args[0]`):
52
- # `unwrap!`, `unwrap_in`, `expect!`, `expect_with!`,
53
- # `unwrap_or_raise!`, `unwrap_or_raise_with!`,
54
- # `unwrap_or_raise_inner!`.
55
- # - Option unwrap family → `InnerType` (`type_args[0]`):
56
- # `unwrap`, `unwrap!`, `unwrap_or`, `expect!`,
38
+ # - Result unwrap family → `OkType` (`type_args[0]`): `unwrap!`, `unwrap_in`, `expect!`, `expect_with!`,
39
+ # `unwrap_or_raise!`, `unwrap_or_raise_with!`, `unwrap_or_raise_inner!`.
40
+ # - Option unwrap family → `InnerType` (`type_args[0]`): `unwrap`, `unwrap!`, `unwrap_or`, `expect!`,
57
41
  # `expect_with!`.
58
42
  #
59
- # The contribution fires only when the receiver resolves to a
60
- # carrier Nominal carrying a non-empty `type_args` — i.e. when
61
- # the Result/Option came from a method whose declared return
62
- # type is an applied generic (`-> Result[String, E]`), the
63
- # realistic Mangrove shape. The bare-constructor shape
64
- # (`Result::Ok.new("x")`) currently yields a raw Nominal with
65
- # no `type_args` (the engine does not infer generics from
66
- # constructor arguments), so the plugin no-ops there rather
67
- # than guessing — a conservative floor, not a regression.
43
+ # The contribution fires only when the receiver resolves to a carrier Nominal carrying a non-empty
44
+ # `type_args` — i.e. when the Result/Option came from a method whose declared return type is an applied
45
+ # generic (`-> Result[String, E]`), the realistic Mangrove shape. The bare-constructor shape
46
+ # (`Result::Ok.new("x")`) currently yields a raw Nominal with no `type_args` (the engine does not infer
47
+ # generics from constructor arguments), so the plugin no-ops there rather than guessing — a conservative
48
+ # floor, not a regression.
68
49
  #
69
50
  # ## Out of scope (tracked elsewhere)
70
51
  #
71
- # - `is_a?(Result::Ok)` / `Some` / `None` exhaustive
72
- # narrowing — core control-flow analysis over a sealed
52
+ # - `is_a?(Result::Ok)` / `Some` / `None` exhaustive narrowing — core control-flow analysis over a sealed
73
53
  # hierarchy, not a plugin surface.
74
- # - The `variants do variant Const, Type end` Enum DSL is handled
75
- # via ADR-36 `nested_class_templates:` in this plugin's manifest
76
- # (Slice A — see `nested_class_templates:` block below).
54
+ # - The `variants do variant Const, Type end` Enum DSL is handled via ADR-36 `nested_class_templates:` in
55
+ # this plugin's manifest (Slice A — see `nested_class_templates:` block below).
77
56
  class Mangrove < Rigor::Plugin::Base
78
57
  manifest(
79
58
  id: "mangrove",
@@ -90,10 +69,9 @@ module Rigor
90
69
  # end
91
70
  # end
92
71
  #
93
- # mints `Shape::Circle < Shape` with `#inner : Float`. The
94
- # substrate resolves the variant constant + its `#inner`
95
- # reader statically so `Shape::Circle.new(1.0).inner`
96
- # types as `Float` without running Mangrove's `const_missing`.
72
+ # mints `Shape::Circle < Shape` with `#inner : Float`. The substrate resolves the variant constant +
73
+ # its `#inner` reader statically so `Shape::Circle.new(1.0).inner` types as `Float` without running
74
+ # Mangrove's `const_missing`.
97
75
  nested_class_templates: [
98
76
  Rigor::Plugin::Macro::NestedClassTemplate.new(
99
77
  receiver_constraint: "Mangrove::Enum",
@@ -106,11 +84,9 @@ module Rigor
106
84
  ]
107
85
  )
108
86
 
109
- # Carrier class names whose FIRST type argument is the
110
- # value the unwrap family yields (`OkType` for Result,
111
- # `InnerType` for Option). Matched with and without a
112
- # leading `::` because `scope.type_of` reports the lexical
113
- # form while user code may root the constant.
87
+ # Carrier class names whose FIRST type argument is the value the unwrap family yields (`OkType` for
88
+ # Result, `InnerType` for Option). Matched with and without a leading `::` because `scope.type_of`
89
+ # reports the lexical form while user code may root the constant.
114
90
  RESULT_CARRIERS = [
115
91
  "Mangrove::Result",
116
92
  "Mangrove::Result::Ok",
@@ -123,23 +99,19 @@ module Rigor
123
99
  "Mangrove::Option::None"
124
100
  ].freeze
125
101
 
126
- # Methods on the Result carriers that return `OkType`
127
- # (`type_args[0]`). All of these are documented as yielding
128
- # the success value, raising / short-circuiting on `Err`.
102
+ # Methods on the Result carriers that return `OkType` (`type_args[0]`). All of these are documented as
103
+ # yielding the success value, raising / short-circuiting on `Err`.
129
104
  RESULT_UNWRAP_METHODS = %i[
130
105
  unwrap! unwrap_in expect! expect_with!
131
106
  unwrap_or_raise! unwrap_or_raise_with! unwrap_or_raise_inner!
132
107
  ].freeze
133
108
 
134
- # Methods on the Option carriers that return `InnerType`
135
- # (`type_args[0]`).
109
+ # Methods on the Option carriers that return `InnerType` (`type_args[0]`).
136
110
  OPTION_UNWRAP_METHODS = %i[unwrap unwrap! unwrap_or expect! expect_with!].freeze
137
111
 
138
- # ADR-37 slice 2 — a pure return-type contributor. The engine
139
- # gates on the receiver being a known carrier class (so the block
140
- # only runs on Result/Option receivers); the block then confirms
141
- # the call is an unwrap method on an instantiated carrier and
142
- # yields the carried `type_args[0]`.
112
+ # ADR-37 slice 2 — a pure return-type contributor. The engine gates on the receiver being a known
113
+ # carrier class (so the block only runs on Result/Option receivers); the block then confirms the call
114
+ # is an unwrap method on an instantiated carrier and yields the carried `type_args[0]`.
143
115
  dynamic_return receivers: (RESULT_CARRIERS + OPTION_CARRIERS) do |call_node, scope|
144
116
  receiver = call_node.receiver
145
117
  next nil if receiver.nil?
@@ -152,18 +124,16 @@ module Rigor
152
124
 
153
125
  private
154
126
 
155
- # @return [Rigor::Type, nil] the receiver's inferred type, or
156
- # nil when the engine raises on a synthetic / unrecognised
157
- # node (mirrors rigor-sorbet's defensive degrade).
127
+ # @return [Rigor::Type, nil] the receiver's inferred type, or nil when the engine raises on a synthetic
128
+ # / unrecognised node (mirrors rigor-sorbet's defensive degrade).
158
129
  def receiver_type_of(receiver, scope)
159
130
  scope.type_of(receiver)
160
131
  rescue StandardError
161
132
  nil
162
133
  end
163
134
 
164
- # The value the unwrap family yields for this receiver, or
165
- # nil when the call is not an unwrap on a known carrier, or
166
- # the carrier is raw (no `type_args` to instantiate).
135
+ # The value the unwrap family yields for this receiver, or nil when the call is not an unwrap on a
136
+ # known carrier, or the carrier is raw (no `type_args` to instantiate).
167
137
  def carried_type(receiver_type, method_name)
168
138
  class_name = normalize(receiver_type.class_name)
169
139
  type_args = receiver_type.type_args
@@ -8,9 +8,8 @@ require "rigor/flow_contribution/fact"
8
8
  module Rigor
9
9
  module Plugin
10
10
  class Minitest < Rigor::Plugin::Base
11
- # Recognises the four shapes of Minitest / Test::Unit
12
- # assertions and emits a `post_return_fact` that narrows
13
- # the named local on the post-call edge.
11
+ # Recognises the four shapes of Minitest / Test::Unit assertions and emits a `post_return_fact` that
12
+ # narrows the named local on the post-call edge.
14
13
  #
15
14
  # ## Recognised call shapes
16
15
  #
@@ -29,9 +28,8 @@ module Rigor
29
28
  # refute_nil(x) → narrow x AWAY from nil
30
29
  # refute_equal(literal, x) → narrow x AWAY from Constant<literal>
31
30
  #
32
- # Test::Unit's `assert_not_nil(x)` / `assert_not_equal(...)` /
33
- # `assert_not_kind_of(...)` / `assert_not_instance_of(...)`
34
- # share the recognizer with `refute_*` (they're aliases).
31
+ # Test::Unit's `assert_not_nil(x)` / `assert_not_equal(...)` / `assert_not_kind_of(...)` /
32
+ # `assert_not_instance_of(...)` share the recognizer with `refute_*` (they're aliases).
35
33
  #
36
34
  # ### (3) Minitest/spec `_(x).must_*` (positive) ###
37
35
  #
@@ -41,12 +39,10 @@ module Rigor
41
39
  # _(x).must_equal(literal) → narrow x to Constant<literal>
42
40
  # _(x).must_match(regex) → narrow x to String
43
41
  #
44
- # The legacy bare `x.must_be_kind_of(T)` form (Minitest <
45
- # 6.0 monkey-patched onto Object) is intentionally NOT
46
- # recognised the receiver is the value itself rather
47
- # than a wrapping `_(value)`, so the analyzer has nothing
48
- # to narrow against. Users who still rely on the legacy
49
- # form should migrate to `_(x).must_*`.
42
+ # The legacy bare `x.must_be_kind_of(T)` form (Minitest < 6.0 monkey-patched onto Object) is
43
+ # intentionally NOT recognised the receiver is the value itself rather than a wrapping `_(value)`,
44
+ # so the analyzer has nothing to narrow against. Users who still rely on the legacy form should
45
+ # migrate to `_(x).must_*`.
50
46
  #
51
47
  # ### (4) Minitest/spec `_(x).wont_*` (negative) ###
52
48
  #
@@ -56,14 +52,10 @@ module Rigor
56
52
  #
57
53
  # ## Not yet recognised
58
54
  #
59
- # `assert_predicate(x, :foo?)` (custom predicate) /
60
- # `assert_respond_to(x, :method)` / `assert_includes` /
61
- # `assert_operator` / `assert_throws` /
62
- # `assert_raises(T) { ... }` etc. each either needs a
63
- # carrier Rigor doesn't model today (predicate-state,
64
- # respond-to-set) or a multi-edge fact that the
65
- # `post_return_facts` slot can't express. Queued for
66
- # follow-up slices.
55
+ # `assert_predicate(x, :foo?)` (custom predicate) / `assert_respond_to(x, :method)` / `assert_includes`
56
+ # / `assert_operator` / `assert_throws` / `assert_raises(T) { ... }` etc. — each either needs a
57
+ # carrier Rigor doesn't model today (predicate-state, respond-to-set) or a multi-edge fact that the
58
+ # `post_return_facts` slot can't express. Queued for follow-up slices.
67
59
  module AssertionAnalyzer
68
60
  module_function
69
61
 
@@ -83,16 +75,12 @@ module Rigor
83
75
 
84
76
  # --- assert_* / refute_* / assert_not_* form ---
85
77
 
86
- # Maps each recognised assertion name to a tuple
87
- # `[shape, negative]`. `shape` is one of:
78
+ # Maps each recognised assertion name to a tuple `[shape, negative]`. `shape` is one of:
88
79
  #
89
- # - :class_then_local — `assert_kind_of(T, x)`, T at
90
- # args[0], local at args[1].
80
+ # - :class_then_local — `assert_kind_of(T, x)`, T at args[0], local at args[1].
91
81
  # - :nil_local — `assert_nil(x)`, local at args[0].
92
- # - :literal_then_local — `assert_equal(literal, x)`,
93
- # literal at args[0], local at args[1].
94
- # - :regex_then_local — `assert_match(regex, x)`,
95
- # regex at args[0], local at args[1].
82
+ # - :literal_then_local — `assert_equal(literal, x)`, literal at args[0], local at args[1].
83
+ # - :regex_then_local `assert_match(regex, x)`, regex at args[0], local at args[1].
96
84
  ASSERT_FORM = {
97
85
  assert_kind_of: [:class_then_local, false],
98
86
  assert_instance_of: [:class_then_local, false],
@@ -123,8 +111,7 @@ module Rigor
123
111
 
124
112
  # --- _(x).must_* / .wont_* form ---
125
113
 
126
- # Maps the spec-style matcher names to `[shape,
127
- # negative]`. `shape`:
114
+ # Maps the spec-style matcher names to `[shape, negative]`. `shape`:
128
115
  # - :class_arg — `_(x).must_be_kind_of(T)`, T at args[0].
129
116
  # - :no_arg_nil — `_(x).must_be_nil`, no args.
130
117
  # - :literal_arg — `_(x).must_equal(literal)`, literal at args[0].
@@ -144,8 +131,8 @@ module Rigor
144
131
  }.freeze
145
132
  private_constant :SPEC_MATCHER_FORM
146
133
 
147
- # ADR-37 slice 2 — the method names this analyzer narrows on,
148
- # for the plugin's `narrowing_facts methods:` gate.
134
+ # ADR-37 slice 2 — the method names this analyzer narrows on, for the plugin's
135
+ # `narrowing_facts methods:` gate.
149
136
  SUPPORTED_METHODS = (ASSERT_FORM.keys + SPEC_MATCHER_FORM.keys).freeze
150
137
 
151
138
  def spec_form_fact(call_node, environment:)
@@ -160,10 +147,9 @@ module Rigor
160
147
  fact_for_spec_shape(shape, target_local, args, negative: negative, environment: environment)
161
148
  end
162
149
 
163
- # `_(x)` returns a `Minitest::Expectation` wrapping x.
164
- # Some specs use `value(x)` or `expect(x)` interchangeably
165
- # (Minitest provides all three as aliases). Recognises the
166
- # local-variable arg in any of those receiver-call shapes.
150
+ # `_(x)` returns a `Minitest::Expectation` wrapping x. Some specs use `value(x)` or `expect(x)`
151
+ # interchangeably (Minitest provides all three as aliases). Recognises the local-variable arg in
152
+ # any of those receiver-call shapes.
167
153
  SPEC_WRAPPER_NAMES = %i[_ value expect].freeze
168
154
  private_constant :SPEC_WRAPPER_NAMES
169
155
 
@@ -6,61 +6,44 @@ require_relative "minitest/assertion_analyzer"
6
6
 
7
7
  module Rigor
8
8
  module Plugin
9
- # rigor-minitest — narrows locals through Minitest /
10
- # Test::Unit assertions.
9
+ # rigor-minitest — narrows locals through Minitest / Test::Unit assertions.
11
10
  #
12
- # Recognises every supported `assert_*` / `refute_*` /
13
- # `assert_not_*` shape (Minitest core + Test::Unit aliases),
14
- # plus the Minitest/spec `_(x).must_*` / `.wont_*` matchers
15
- # (`expect(x)` / `value(x)` wrappers covered too). For each
16
- # recognised assertion the plugin emits a Rigor
17
- # `post_return_fact` whose `:local`-kind target narrows the
18
- # named local on the post-call edge so downstream calls in
19
- # the same `it` / `test_` / `def test_*` body resolve
20
- # against the narrowed type.
11
+ # Recognises every supported `assert_*` / `refute_*` / `assert_not_*` shape (Minitest core + Test::Unit
12
+ # aliases), plus the Minitest/spec `_(x).must_*` / `.wont_*` matchers (`expect(x)` / `value(x)` wrappers
13
+ # covered too). For each recognised assertion the plugin emits a Rigor `post_return_fact` whose
14
+ # `:local`-kind target narrows the named local on the post-call edge so downstream calls in the same
15
+ # `it` / `test_` / `def test_*` body resolve against the narrowed type.
21
16
  #
22
17
  # ## Why a single plugin covers two frameworks
23
18
  #
24
- # Minitest and Test::Unit share the `assert_*` / `refute_*`
25
- # surface (Test::Unit's `assert_not_*` aliases are
26
- # recognised by the same rule table). The matchers_vaccine
27
- # gem layers RSpec-style matcher composition on top of
28
- # Minitest's `must` API and is covered by the spec-style
29
- # recogniser without additional wiring.
19
+ # Minitest and Test::Unit share the `assert_*` / `refute_*` surface (Test::Unit's `assert_not_*` aliases
20
+ # are recognised by the same rule table). The matchers_vaccine gem layers RSpec-style matcher composition
21
+ # on top of Minitest's `must` API and is covered by the spec-style recogniser without additional wiring.
30
22
  #
31
23
  # ## Configuration
32
24
  #
33
- # No configuration knobs. Activate via
34
- # `plugins: ["rigor-minitest"]` in `.rigor.yml`.
25
+ # No configuration knobs. Activate via `plugins: ["rigor-minitest"]` in `.rigor.yml`.
35
26
  #
36
27
  # ## Limitations
37
28
  #
38
- # - **No `assert_raises(T) { ... }`** — that's a block-shape
39
- # matcher and Rigor's narrowing model is for
29
+ # - **No `assert_raises(T) { ... }`** — that's a block-shape matcher and Rigor's narrowing model is for
40
30
  # straight-line locals.
41
- # - **No `assert_predicate(x, :foo?)`** — needs a
42
- # predicate-state carrier Rigor doesn't model today.
43
- # - **No `assert_respond_to(x, :m)`** — same shape gap
44
- # (respond-to-set carrier).
45
- # - **No legacy bare `x.must_be_kind_of(T)`** — the
46
- # receiver IS the value, so the analyzer has nothing to
31
+ # - **No `assert_predicate(x, :foo?)`** — needs a predicate-state carrier Rigor doesn't model today.
32
+ # - **No `assert_respond_to(x, :m)`** same shape gap (respond-to-set carrier).
33
+ # - **No legacy bare `x.must_be_kind_of(T)`** — the receiver IS the value, so the analyzer has nothing to
47
34
  # narrow against. Users should migrate to `_(x).must_*`.
48
- # - **No `assert_in_delta` / `assert_operator`** — float-
49
- # range / generic operator narrowing is future work.
50
- # - **No `assert_throws(:tag) { ... }` / `assert_raises`** —
51
- # block-form expectations need a separate slice.
35
+ # - **No `assert_in_delta` / `assert_operator`** — float-range / generic operator narrowing is future work.
36
+ # - **No `assert_throws(:tag) { ... }` / `assert_raises`** block-form expectations need a separate slice.
52
37
  class Minitest < Rigor::Plugin::Base
53
38
  manifest(
54
39
  id: "minitest",
55
40
  version: "0.1.0",
56
41
  description: "Narrows locals through Minitest / Test::Unit `assert_*` / `refute_*` " \
57
42
  "and Minitest/spec `_(x).must_*` / `.wont_*` matchers.",
58
- # ADR-38 — `def setup` runs before every `def test_*`, so
59
- # ivars it assigns are initialised by the time a test body
60
- # reads them. Declaring it an additional initializer stops
61
- # the read-before-write nil widening that would otherwise
62
- # type `@conn` (set in `setup`, read in a test) as
63
- # `T | nil` and surface a false nil-receiver diagnostic.
43
+ # ADR-38 — `def setup` runs before every `def test_*`, so ivars it assigns are initialised by the
44
+ # time a test body reads them. Declaring it an additional initializer stops the read-before-write
45
+ # nil widening that would otherwise type `@conn` (set in `setup`, read in a test) as `T | nil` and
46
+ # surface a false nil-receiver diagnostic.
64
47
  additional_initializers: [
65
48
  Rigor::Plugin::AdditionalInitializer.new(
66
49
  receiver_constraint: "Minitest::Test", methods: [:setup]
@@ -74,10 +57,8 @@ module Rigor
74
57
  ]
75
58
  )
76
59
 
77
- # ADR-37 slice 2 — emits `post_return_facts` for every recognised
78
- # assertion, method-gated by the engine. The engine routes
79
- # `:local`-kind facts through
80
- # `StatementEvaluator#apply_local_post_return_fact`.
60
+ # ADR-37 slice 2 — emits `post_return_facts` for every recognised assertion, method-gated by the
61
+ # engine. The engine routes `:local`-kind facts through `StatementEvaluator#apply_local_post_return_fact`.
81
62
  narrowing_facts methods: AssertionAnalyzer::SUPPORTED_METHODS do |call_node, scope|
82
63
  AssertionAnalyzer.contribution_for(call_node, environment: scope&.environment)&.post_return_facts
83
64
  end
@@ -6,41 +6,32 @@ require "prism"
6
6
  module Rigor
7
7
  module Plugin
8
8
  class Pundit < Rigor::Plugin::Base
9
- # Walks a parsed file's AST looking for Pundit
10
- # entry-point calls and validates each against the
9
+ # Walks a parsed file's AST looking for Pundit entry-point calls and validates each against the
11
10
  # {PolicyIndex}.
12
11
  #
13
12
  # Recognised shapes:
14
13
  #
15
- # - `authorize(record, :action)` — record's inferred
16
- # type → `<Type>Policy#<action>?` lookup. Both the
14
+ # - `authorize(record, :action)` — record's inferred type → `<Type>Policy#<action>?` lookup. Both the
17
15
  # policy class and the predicate must exist.
18
- # - `authorize(record)` — without an action argument,
19
- # we only validate that `<Type>Policy` exists. The
20
- # action name is determined at runtime from the
21
- # controller's current action; static validation
16
+ # - `authorize(record)` — without an action argument, we only validate that `<Type>Policy` exists. The
17
+ # action name is determined at runtime from the controller's current action; static validation
22
18
  # isn't possible without controller context.
23
- # - `policy(record)` / `policy_scope(scope)` — same
24
- # `<Type>Policy` existence check.
19
+ # - `policy(record)` / `policy_scope(scope)` — same `<Type>Policy` existence check.
25
20
  #
26
- # When the first argument's inferred type is NOT a
27
- # `Nominal[T]` (e.g. an untyped local variable), the
28
- # call is silently passed through. The plugin only
29
- # validates what it can prove from the static type
21
+ # When the first argument's inferred type is NOT a `Nominal[T]` (e.g. an untyped local variable), the
22
+ # call is silently passed through. The plugin only validates what it can prove from the static type
30
23
  # carrier.
31
24
  module Analyzer
32
25
  ENTRY_METHODS = %i[authorize policy policy_scope].freeze
33
26
 
34
- # One policy violation: the kebab-case `rule`, `severity`, and
35
- # human-readable `message`. Carries no path/location — the caller
36
- # (the `node_rule` block) positions it via `Plugin::Base#diagnostic`.
27
+ # One policy violation: the kebab-case `rule`, `severity`, and human-readable `message`. Carries no
28
+ # path/location — the caller (the `node_rule` block) positions it via `Plugin::Base#diagnostic`.
37
29
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
38
30
 
39
31
  module_function
40
32
 
41
- # The policy violations for a single call node (0..2 of them), or
42
- # `[]` when the node is not a Pundit entry call or its record type
43
- # is not statically determinable. ADR-37: the engine owns the
33
+ # The policy violations for a single call node (0..2 of them), or `[]` when the node is not a
34
+ # Pundit entry call or its record type is not statically determinable. ADR-37: the engine owns the
44
35
  # walk, so this is per-node logic.
45
36
  #
46
37
  # @param call_node [Prism::Node]
@@ -71,15 +62,11 @@ module Rigor
71
62
  ENTRY_METHODS.include?(node.name) && node.receiver.nil?
72
63
  end
73
64
 
74
- # Resolves the first-argument expression to a policy
75
- # class name. The candidates are:
65
+ # Resolves the first-argument expression to a policy class name. The candidates are:
76
66
  # - `Foo` (a constant) → `FooPolicy`
77
- # - `Foo.method(...)` whose inferred type is
78
- # `Nominal[Bar]` → `BarPolicy`
79
- # - any other expression whose inferred type is
80
- # `Nominal[Bar]` → `BarPolicy`
81
- # Returns `nil` when the type isn't statically
82
- # determinable.
67
+ # - `Foo.method(...)` whose inferred type is `Nominal[Bar]` → `BarPolicy`
68
+ # - any other expression whose inferred type is `Nominal[Bar]` → `BarPolicy`
69
+ # Returns `nil` when the type isn't statically determinable.
83
70
  def derive_policy_class_name(record_node, scope)
84
71
  if record_node.is_a?(Prism::ConstantReadNode) || record_node.is_a?(Prism::ConstantPathNode)
85
72
  constant_name = constant_receiver_name(record_node)
@@ -118,10 +105,8 @@ module Rigor
118
105
  )
119
106
  end
120
107
 
121
- # Validates the `authorize(record, :action)` form.
122
- # Returns nil when the call has no second argument
123
- # (the runtime infers it from the controller — out
124
- # of scope here) or when the second argument isn't
108
+ # Validates the `authorize(record, :action)` form. Returns nil when the call has no second argument
109
+ # (the runtime infers it from the controller out of scope here) or when the second argument isn't
125
110
  # a literal symbol / string.
126
111
  def action_violation(call_node, policy_entry)
127
112
  args = call_node.arguments&.arguments || []
@@ -7,22 +7,17 @@ require_relative "policy_index"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class Pundit < Rigor::Plugin::Base
10
- # Walks the configured policy-search paths via the
11
- # plugin's `IoBoundary`, parses each `.rb` file with
12
- # Prism, and collects classes whose immediate
13
- # superclass is one of the configured base classes.
10
+ # Walks the configured policy-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
11
+ # Prism, and collects classes whose immediate superclass is one of the configured base classes.
14
12
  #
15
- # For each discovered policy class, the discoverer
16
- # collects every instance-side `def name?` predicate
17
- # method. Non-predicate methods (`initialize`,
18
- # `resolve`, helper methods) are ignored — Pundit's
13
+ # For each discovered policy class, the discoverer collects every instance-side `def name?` predicate
14
+ # method. Non-predicate methods (`initialize`, `resolve`, helper methods) are ignored — Pundit's
19
15
  # `authorize` only ever calls predicate methods.
20
16
  #
21
17
  # Limitations (intentional for v0.1.0):
22
18
  #
23
19
  # - Direct-superclass match only.
24
- # - Predicate methods are read from the syntactic
25
- # `def` list. Methods built via `define_method` /
20
+ # - Predicate methods are read from the syntactic `def` list. Methods built via `define_method` /
26
21
  # inherited from a sibling concern are out of scope.
27
22
  class PolicyDiscoverer
28
23
  def initialize(io_boundary:, search_paths:, base_classes:)
@@ -120,8 +115,7 @@ module Rigor
120
115
  end
121
116
  end
122
117
 
123
- # Returns symbolic predicate names (`:update?`,
124
- # `:show?`, …) defined on the policy. Only
118
+ # Returns symbolic predicate names (`:update?`, `:show?`, …) defined on the policy. Only
125
119
  # instance-side names that end in `?` are recorded.
126
120
  def collect_predicate_methods(body)
127
121
  return [] if body.nil?
@@ -3,14 +3,11 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Pundit < Rigor::Plugin::Base
6
- # Frozen catalogue of discovered Pundit policy classes
7
- # keyed by policy class name (e.g. `"PostPolicy"`).
8
- # Each entry tracks the set of predicate methods
9
- # defined on the policy (instance-side `def name?`)
6
+ # Frozen catalogue of discovered Pundit policy classes keyed by policy class name (e.g. `"PostPolicy"`).
7
+ # Each entry tracks the set of predicate methods defined on the policy (instance-side `def name?`)
10
8
  # plus the source file path.
11
9
  #
12
- # The analyzer maps a record's inferred type
13
- # (`Nominal[Post]`) to the policy class name
10
+ # The analyzer maps a record's inferred type (`Nominal[Post]`) to the policy class name
14
11
  # (`"PostPolicy"`) and looks up the predicate.
15
12
  class PolicyIndex
16
13
  Entry = Data.define(:policy_class_name, :file_path, :predicate_methods) do
@@ -22,8 +19,7 @@ module Rigor
22
19
  predicate_methods.to_a.sort
23
20
  end
24
21
 
25
- # Normalises an action symbol / string by ensuring
26
- # a trailing `?`. `:update` and `:update?` both
22
+ # Normalises an action symbol / string by ensuring a trailing `?`. `:update` and `:update?` both
27
23
  # resolve to `update?`.
28
24
  def normalize(name)
29
25
  string = name.to_s
@@ -8,13 +8,11 @@ require_relative "pundit/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-pundit — validates Pundit `authorize` /
12
- # `policy` / `policy_scope` calls against the project's
11
+ # rigor-pundit — validates Pundit `authorize` / `policy` / `policy_scope` calls against the project's
13
12
  # `app/policies/` tree.
14
13
  #
15
14
  # Tier 3B of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
16
- # Statically discovers policy classes by walking
17
- # `policy_search_paths` and parsing each file with
15
+ # Statically discovers policy classes by walking `policy_search_paths` and parsing each file with
18
16
  # Prism — no `pundit` runtime dependency.
19
17
  #
20
18
  # ## Configuration
@@ -27,34 +25,22 @@ module Rigor
27
25
  #
28
26
  # ## What it checks
29
27
  #
30
- # 1. **Policy class existence** — `authorize(record, ...)`
31
- # looks up `<inferred-type>Policy` in the index.
32
- # Missing policies emit `unknown-policy-class` with
33
- # a did-you-mean suggestion.
34
- # 2. **Predicate method existence** — for the
35
- # `authorize(record, :action)` form, validates that
36
- # `<Policy>#<action>?` is defined. Missing methods
37
- # emit `unknown-policy-method` listing the known
28
+ # 1. **Policy class existence** — `authorize(record, ...)` looks up `<inferred-type>Policy` in the index.
29
+ # Missing policies emit `unknown-policy-class` with a did-you-mean suggestion.
30
+ # 2. **Predicate method existence** — for the `authorize(record, :action)` form, validates that
31
+ # `<Policy>#<action>?` is defined. Missing methods emit `unknown-policy-method` listing the known
38
32
  # predicates.
39
33
  #
40
34
  # ## Limitations (v0.1.0)
41
35
  #
42
- # - Records whose inferred type is NOT a `Nominal[T]`
43
- # (untyped local variables, untyped instance
44
- # variables) are silently passed through. The plugin
45
- # only validates what it can prove from the static
46
- # carrier.
47
- # - The two-argument form
48
- # `authorize(record, :action_symbol)` is the only
49
- # one validated. The implicit form
50
- # `authorize(record)` (which Pundit resolves at
51
- # runtime against the controller's current action) is
52
- # passed through with the policy-class check only.
53
- # - Direct-superclass match for `policy_base_classes`.
54
- # Indirect inheritance (`AdminPolicy <
55
- # ApplicationPolicy`) needs `AdminPolicy` listed in
56
- # `policy_base_classes` if subclasses inherit from
57
- # it.
36
+ # - Records whose inferred type is NOT a `Nominal[T]` (untyped local variables, untyped instance
37
+ # variables) are silently passed through. The plugin only validates what it can prove from the
38
+ # static carrier.
39
+ # - The two-argument form `authorize(record, :action_symbol)` is the only one validated. The implicit
40
+ # form `authorize(record)` (which Pundit resolves at runtime against the controller's current action)
41
+ # is passed through with the policy-class check only.
42
+ # - Direct-superclass match for `policy_base_classes`. Indirect inheritance (`AdminPolicy <
43
+ # ApplicationPolicy`) needs `AdminPolicy` listed in `policy_base_classes` if subclasses inherit from it.
58
44
  class Pundit < Rigor::Plugin::Base
59
45
  manifest(
60
46
  id: "pundit",
@@ -79,10 +65,9 @@ module Rigor
79
65
  @policy_base_classes = Array(config.fetch("policy_base_classes")).map(&:to_s)
80
66
  end
81
67
 
82
- # File-level only: the load-error emission. The per-call policy
83
- # validation runs over the engine-owned walk via the node_rule
84
- # below (ADR-37). The index is lazily loaded + memoised by
85
- # `producer_value`, so both surfaces share one load.
68
+ # File-level only: the load-error emission. The per-call policy validation runs over the engine-owned
69
+ # walk via the node_rule below (ADR-37). The index is lazily loaded + memoised by `producer_value`,
70
+ # so both surfaces share one load.
86
71
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
87
72
  index = producer_value(:policy_index)
88
73
  return [load_error_diagnostic(path)] if index.nil? && producer_error(:policy_index)