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
@@ -8,82 +8,61 @@ require_relative "../../source/constant_path"
8
8
  module Rigor
9
9
  module Analysis
10
10
  module DependencySourceInference
11
- # Walks a resolved gem's `roots:` and collects the
12
- # `(class_name, method_name) CatalogEntry(kind,
13
- # return_type)` method catalog. The walker is the source
14
- # of facts the dispatcher tier consults to recognise a
15
- # method as defined by an opt-in gem and contribute a
16
- # `Type::Dynamic`-wrapped return at the call site.
11
+ # Walks a resolved gem's `roots:` and collects the `(class_name, method_name) → CatalogEntry(kind,
12
+ # return_type)` method catalog. The walker is the source of facts the dispatcher tier consults to
13
+ # recognise a method as defined by an opt-in gem and contribute a `Type::Dynamic`-wrapped return at
14
+ # the call site.
17
15
  #
18
- # The dispatcher tier wraps every walker-contributed return
19
- # in `Dynamic[T]` per ADR-10's gem-boundary contract. When
20
- # the heuristic ({ReturnTypeHeuristic}) recognises the
21
- # method body's tail expression, the dispatcher uses the
22
- # heuristic's static facet; otherwise it falls back to
23
- # `Dynamic[top]` (the pre-heuristic behaviour). The
24
- # heuristic is intentionally narrow — only literal-tail
25
- # method bodies fold; everything else degrades silently.
16
+ # The dispatcher tier wraps every walker-contributed return in `Dynamic[T]` per ADR-10's gem-boundary
17
+ # contract. When the heuristic ({ReturnTypeHeuristic}) recognises the method body's tail expression,
18
+ # the dispatcher uses the heuristic's static facet; otherwise it falls back to `Dynamic[top]` (the
19
+ # pre-heuristic behaviour). The heuristic is intentionally narrow — only literal-tail method bodies
20
+ # fold; everything else degrades silently.
26
21
  #
27
- # Hard exclusions are NOT user-configurable, per ADR-10
28
- # § "Hard exclusions": top-level `spec/`, `test/`, `bin/`,
29
- # plus any non-`.rb` source. C extensions fall out
30
- # automatically because the walker only loads `.rb` files.
22
+ # Hard exclusions are NOT user-configurable, per ADR-10 § "Hard exclusions": top-level `spec/`,
23
+ # `test/`, `bin/`, plus any non-`.rb` source. C extensions fall out automatically because the walker
24
+ # only loads `.rb` files.
31
25
  module Walker
32
- # Top-level directories that MUST NOT participate in
33
- # gem-source inference even when the user lists them
34
- # under `roots:`. The check is case-insensitive against
35
- # the first segment of `roots:`; nested `spec/` /
36
- # `test/` directories deeper inside `lib/` are NOT
37
- # filtered (a few gems legitimately ship `lib/.../spec/`).
26
+ # Top-level directories that MUST NOT participate in gem-source inference even when the user lists
27
+ # them under `roots:`. The check is case-insensitive against the first segment of `roots:`; nested
28
+ # `spec/` / `test/` directories deeper inside `lib/` are NOT filtered (a few gems legitimately ship
29
+ # `lib/.../spec/`).
38
30
  HARD_EXCLUDED_ROOTS = %w[spec test bin].freeze
39
31
 
40
- # Walker outcome wrapping the harvested method catalog
41
- # plus a budget-exceeded flag. ADR-10 slice 4 introduces
42
- # the cap; the Walker stops appending to the accumulator
43
- # once `catalog.size` reaches `budget`, and `truncated?`
44
- # reports whether the cap was reached. The Index records
45
- # this per-gem so the Runner can surface a single
46
- # `dynamic.dependency-source.budget-exceeded` warning
47
- # naming the affected gem(s).
32
+ # Walker outcome wrapping the harvested method catalog plus a budget-exceeded flag. ADR-10 slice 4
33
+ # introduces the cap; the Walker stops appending to the accumulator once `catalog.size` reaches
34
+ # `budget`, and `truncated?` reports whether the cap was reached. The Index records this per-gem so
35
+ # the Runner can surface a single `dynamic.dependency-source.budget-exceeded` warning naming the
36
+ # affected gem(s).
48
37
  class Outcome < Data.define(:catalog, :truncated)
49
38
  def truncated? = truncated
50
39
  end
51
40
 
52
- # Per-method catalog entry. `kind` is `:instance` or
53
- # `:singleton`; `return_type` is the
54
- # {ReturnTypeHeuristic}-extracted static facet (a
55
- # `Rigor::Type::*`) or `nil` when the heuristic declined.
56
- # The dispatcher wraps a non-nil `return_type` in
57
- # `Dynamic[T]`; a `nil` `return_type` falls back to
58
- # `Dynamic[top]`.
41
+ # Per-method catalog entry. `kind` is `:instance` or `:singleton`; `return_type` is the
42
+ # {ReturnTypeHeuristic}-extracted static facet (a `Rigor::Type::*`) or `nil` when the heuristic
43
+ # declined. The dispatcher wraps a non-nil `return_type` in `Dynamic[T]`; a `nil` `return_type` falls
44
+ # back to `Dynamic[top]`.
59
45
  class CatalogEntry < Data.define(:kind, :return_type)
60
46
  def initialize(kind:, return_type: nil)
61
47
  super
62
48
  end
63
49
  end
64
50
 
65
- # Sentinel for "no cap" — used by callers that don't
66
- # care about the budget (specs, tooling). Production
67
- # code MUST pass an integer.
51
+ # Sentinel for "no cap" — used by callers that don't care about the budget (specs, tooling).
52
+ # Production code MUST pass an integer.
68
53
  UNBOUNDED = Float::INFINITY
69
54
 
70
55
  module_function
71
56
 
72
- # @param gem_dir [String, Pathname] absolute path to the
73
- # gem's installation directory.
74
- # @param roots [Array<String>] subdirectory names within
75
- # the gem to walk (defaults to `["lib"]` per
57
+ # @param gem_dir [String, Pathname] absolute path to the gem's installation directory.
58
+ # @param roots [Array<String>] subdirectory names within the gem to walk (defaults to `["lib"]` per
76
59
  # `Configuration::Dependencies::Entry`).
77
- # @param budget [Integer, Float] per-gem catalog cap
78
- # (method-definition count). When unset, defaults to
79
- # `UNBOUNDED` for backwards-compatible test paths.
80
- # @return [Outcome] frozen wrapper carrying the catalog
81
- # (`Hash{[class_name, method_name] => :instance |
82
- # :singleton}`) and a `truncated?` flag set when the
83
- # walker stopped harvesting because the budget was
84
- # reached. Methods of identical name on the same class
85
- # with different kinds (rare; private API mostly)
86
- # carry the kind that wins the per-class first walk.
60
+ # @param budget [Integer, Float] per-gem catalog cap (method-definition count). When unset, defaults
61
+ # to `UNBOUNDED` for backwards-compatible test paths.
62
+ # @return [Outcome] frozen wrapper carrying the catalog (`Hash{[class_name, method_name] =>
63
+ # :instance | :singleton}`) and a `truncated?` flag set when the walker stopped harvesting because
64
+ # the budget was reached. Methods of identical name on the same class with different kinds (rare;
65
+ # private API mostly) carry the kind that wins the per-class first walk.
87
66
  def walk(gem_dir:, roots:, budget: UNBOUNDED)
88
67
  accumulator = {}
89
68
  truncated = false
@@ -95,18 +74,14 @@ module Rigor
95
74
  Outcome.new(catalog: accumulator.freeze, truncated: truncated)
96
75
  end
97
76
 
98
- # Drops hard-excluded entries before any filesystem
99
- # walk happens. Reasoning: we never want a gem's
100
- # `spec/` to participate even if the user requested
101
- # it — the noise from RSpec-style globals plus the
102
- # cost of walking test fixtures isn't worth the
103
- # marginal coverage.
77
+ # Drops hard-excluded entries before any filesystem walk happens. Reasoning: we never want a gem's
78
+ # `spec/` to participate even if the user requested it — the noise from RSpec-style globals plus
79
+ # the cost of walking test fixtures isn't worth the marginal coverage.
104
80
  def accepted_roots(roots)
105
81
  roots.reject { |root| HARD_EXCLUDED_ROOTS.include?(root.downcase) }
106
82
  end
107
83
 
108
- # Returns true when the budget tripped during this
109
- # root's walk so the caller can stop iterating
84
+ # Returns true when the budget tripped during this root's walk so the caller can stop iterating
110
85
  # subsequent roots.
111
86
  def walk_root(root_dir, accumulator, budget) # rubocop:disable Naming/PredicateMethod
112
87
  return false unless File.directory?(root_dir)
@@ -124,19 +99,15 @@ module Rigor
124
99
 
125
100
  walk_node(parse_result.value, [], false, accumulator, budget)
126
101
  rescue StandardError
127
- # Gem source we can't parse / read silently degrades
128
- # to "no contribution from this file". The user-facing
129
- # diagnostic stream is reserved for the project source;
130
- # opt-in gem source MUST NOT pollute it with parse
131
- # errors the user cannot fix.
102
+ # Gem source we can't parse / read silently degrades to "no contribution from this file". The
103
+ # user-facing diagnostic stream is reserved for the project source; opt-in gem source MUST NOT
104
+ # pollute it with parse errors the user cannot fix.
132
105
  nil
133
106
  end
134
107
 
135
- # Walks a Prism subtree, accumulating method definitions
136
- # under their qualified class name. Mirrors the shape of
137
- # `Inference::ScopeIndexer#walk_methods` but stays
138
- # decoupled from `Scope` because gem-source inference
139
- # runs without a scope context.
108
+ # Walks a Prism subtree, accumulating method definitions under their qualified class name. Mirrors
109
+ # the shape of `Inference::ScopeIndexer#walk_methods` but stays decoupled from `Scope` because
110
+ # gem-source inference runs without a scope context.
140
111
  def walk_node(node, qualified_prefix, in_singleton_class, accumulator, budget)
141
112
  return unless node.is_a?(Prism::Node)
142
113
  return if accumulator.size >= budget
@@ -161,11 +132,9 @@ module Rigor
161
132
  end
162
133
  end
163
134
 
164
- # `class Foo` / `module Bar`. The dynamic-prefix shape
165
- # (`module ::Foo`-rooted variants whose left side is a
166
- # runtime expression) is treated as opaque we walk the
167
- # children under the same prefix so any inner class
168
- # definitions are still recorded under their own name.
135
+ # `class Foo` / `module Bar`. The dynamic-prefix shape (`module ::Foo`-rooted variants whose left
136
+ # side is a runtime expression) is treated as opaque — we walk the children under the same prefix
137
+ # so any inner class definitions are still recorded under their own name.
169
138
  def descend_class_or_module(node, qualified_prefix, in_singleton_class, accumulator, budget)
170
139
  name = Source::ConstantPath.qualified_name_or_nil(node.constant_path)
171
140
  if name && node.body
@@ -175,10 +144,8 @@ module Rigor
175
144
  end
176
145
  end
177
146
 
178
- # `class << self` only — `class << expr` for any other
179
- # `expr` is treated as opaque so we don't accidentally
180
- # record per-instance singleton methods under the
181
- # surrounding class.
147
+ # `class << self` only — `class << expr` for any other `expr` is treated as opaque so we don't
148
+ # accidentally record per-instance singleton methods under the surrounding class.
182
149
  def descend_singleton_class(node, qualified_prefix, accumulator, budget)
183
150
  if node.expression.is_a?(Prism::SelfNode) && node.body
184
151
  walk_node(node.body, qualified_prefix, true, accumulator, budget)
@@ -13,16 +13,12 @@ module Rigor
13
13
  #
14
14
  # The namespace coordinates three components:
15
15
  #
16
- # - {GemResolver} maps a
17
- # `Configuration::Dependencies::Entry` to either a frozen
18
- # `Resolved(gem_name, version, gem_dir, mode, roots)` or an
19
- # `Unresolvable(gem_name, reason)` value.
20
- # - {Builder.build} folds a `Configuration::Dependencies`
21
- # into a frozen {Index} carrying the partitioned outcomes.
22
- # - {Index} holds the per-run state the dispatcher tier
23
- # consults via `#contribution_for`; the method table is
24
- # fully populated by {Walker} walking each resolved gem's
25
- # `roots:`.
16
+ # - {GemResolver} maps a `Configuration::Dependencies::Entry` to either a frozen
17
+ # `Resolved(gem_name, version, gem_dir, mode, roots)` or an `Unresolvable(gem_name, reason)` value.
18
+ # - {Builder.build} folds a `Configuration::Dependencies` into a frozen {Index} carrying the partitioned
19
+ # outcomes.
20
+ # - {Index} holds the per-run state the dispatcher tier consults via `#contribution_for`; the method table
21
+ # is fully populated by {Walker} walking each resolved gem's `roots:`.
26
22
  module DependencySourceInference
27
23
  end
28
24
  end
@@ -3,47 +3,34 @@
3
3
  module Rigor
4
4
  module Analysis
5
5
  class Diagnostic
6
- # The default source family. Matches the existing analyzer-
7
- # internal rule families; serialised as `"builtin"` and is the
8
- # baseline against which non-default families are recognised.
6
+ # The default source family. Matches the existing analyzer-internal rule families; serialised as
7
+ # `"builtin"` and is the baseline against which non-default families are recognised.
9
8
  DEFAULT_SOURCE_FAMILY = :builtin
10
9
 
11
10
  attr_reader :path, :line, :column, :message, :severity, :rule, :source_family,
12
11
  :receiver_type, :method_name, :project_definition_site
13
12
 
14
- # `rule:` is the stable identifier (a kebab-case string)
15
- # of the diagnostic's source rule. It is used by the
16
- # configuration and the in-source `# rigor:disable <rule>`
17
- # suppression comment system to identify diagnostics by
18
- # category. Diagnostics not produced by `CheckRules`
19
- # (parse errors, path errors, internal analyzer errors)
20
- # may leave `rule` as nil and stay unsuppressible.
13
+ # `rule:` is the stable identifier (a kebab-case string) of the diagnostic's source rule. It is used by
14
+ # the configuration and the in-source `# rigor:disable <rule>` suppression comment system to identify
15
+ # diagnostics by category. Diagnostics not produced by `CheckRules` (parse errors, path errors, internal
16
+ # analyzer errors) may leave `rule` as nil and stay unsuppressible.
21
17
  #
22
- # `source_family:` names the producer of the rule. The default
23
- # `:builtin` covers analyzer-internal rules; future families
24
- # like `:rbs_extended`, `:generated`, or `"plugin.<id>"` (per
25
- # ADR-2 § "Plugin Diagnostic Provenance") let consumers
26
- # distinguish where a diagnostic originated without committing
27
- # to the plugin API itself.
18
+ # `source_family:` names the producer of the rule. The default `:builtin` covers analyzer-internal
19
+ # rules; future families like `:rbs_extended`, `:generated`, or `"plugin.<id>"` (per ADR-2 § "Plugin
20
+ # Diagnostic Provenance") let consumers distinguish where a diagnostic originated without committing to
21
+ # the plugin API itself.
28
22
  #
29
- # `receiver_type:` / `method_name:` are optional structured
30
- # fields populated by the call-related rules (`call.undefined-
31
- # method`) the rendered receiver type and the called method
32
- # name as plain strings. ADR-23 WD3 / slice 4: `rigor triage`'s
33
- # heuristic recognisers read these directly instead of parsing
34
- # the diagnostic message, so the catalogue no longer couples to
35
- # message wording. Both stay nil for rules that have no such
36
- # pair; a consumer that finds them nil falls back to message
37
- # parsing.
23
+ # `receiver_type:` / `method_name:` are optional structured fields populated by the call-related rules
24
+ # (`call.undefined-method`) the rendered receiver type and the called method name as plain strings.
25
+ # ADR-23 WD3 / slice 4: `rigor triage`'s heuristic recognisers read these directly instead of parsing
26
+ # the diagnostic message, so the catalogue no longer couples to message wording. Both stay nil for
27
+ # rules that have no such pair; a consumer that finds them nil falls back to message parsing.
38
28
  #
39
- # `project_definition_site:` is an optional `"path:line"` string
40
- # set by `call.undefined-method` when the project itself defines
41
- # the called method on the receiver class somewhere in the
42
- # analyzed file set (a reopened core/stdlib/gem class the
43
- # dispatcher does not apply cross-file see ADR-17). Its presence
44
- # is the high-confidence "this is a project monkey-patch, not a
45
- # bug" signal `rigor triage` keys on to recommend `pre_eval:`.
46
- # Nil for every other diagnostic.
29
+ # `project_definition_site:` is an optional `"path:line"` string set by `call.undefined-method` when
30
+ # the project itself defines the called method on the receiver class somewhere in the analyzed file set
31
+ # (a reopened core/stdlib/gem class the dispatcher does not apply cross-file — see ADR-17). Its
32
+ # presence is the high-confidence "this is a project monkey-patch, not a bug" signal `rigor triage` keys
33
+ # on to recommend `pre_eval:`. Nil for every other diagnostic.
47
34
  def initialize(path:, line:, column:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
48
35
  source_family: DEFAULT_SOURCE_FAMILY,
49
36
  receiver_type: nil, method_name: nil, project_definition_site: nil)
@@ -62,16 +49,13 @@ module Rigor
62
49
  @project_definition_site = project_definition_site
63
50
  end
64
51
 
65
- # Builds a Diagnostic positioned at a Prism node. Internalises
66
- # the load-bearing convention every caller otherwise repeats:
67
- # the line is the node's 1-based `start_line` and the column is
68
- # `start_column + 1` (Prism columns are 0-based; Rigor reports
69
- # 1-based). Pass any node responding to `#location`; all other
70
- # fields forward to `#initialize` unchanged.
52
+ # Builds a Diagnostic positioned at a Prism node. Internalises the load-bearing convention every caller
53
+ # otherwise repeats: the line is the node's 1-based `start_line` and the column is `start_column + 1`
54
+ # (Prism columns are 0-based; Rigor reports 1-based). Pass any node responding to `#location`; all
55
+ # other fields forward to `#initialize` unchanged.
71
56
  #
72
- # `Plugin::Base#diagnostic` wraps this for plugin authors (who
73
- # must not set `source_family` the runner stamps it); core
74
- # rules and other producers call it directly.
57
+ # `Plugin::Base#diagnostic` wraps this for plugin authors (who must not set `source_family` — the
58
+ # runner stamps it); core rules and other producers call it directly.
75
59
  def self.from_node(node, path:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
76
60
  source_family: DEFAULT_SOURCE_FAMILY,
77
61
  receiver_type: nil, method_name: nil, project_definition_site: nil)
@@ -82,12 +66,10 @@ module Rigor
82
66
  )
83
67
  end
84
68
 
85
- # Builds a Diagnostic from an explicit Prism location, applying
86
- # the same 1-based `line` / `start_column + 1` convention as
87
- # {.from_node}. Use this when the diagnostic should point at a
88
- # *sub-location* rather than the whole node most often a call's
89
- # `message_loc` (the matcher / method name) instead of the
90
- # receiver-spanning `node.location`. {.from_node} is sugar for
69
+ # Builds a Diagnostic from an explicit Prism location, applying the same 1-based `line` /
70
+ # `start_column + 1` convention as {.from_node}. Use this when the diagnostic should point at a
71
+ # *sub-location* rather than the whole node most often a call's `message_loc` (the matcher / method
72
+ # name) instead of the receiver-spanning `node.location`. {.from_node} is sugar for
91
73
  # `from_location(node.location, …)`.
92
74
  def self.from_location(location, path:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
93
75
  source_family: DEFAULT_SOURCE_FAMILY,
@@ -102,20 +84,17 @@ module Rigor
102
84
  )
103
85
  end
104
86
 
105
- # Builds a Diagnostic at a call node's `message_loc` (the
106
- # method-name / matcher span), falling back to the receiver-
107
- # spanning `node.location` when no message location is available.
108
- # Absorbs the `node.message_loc || node.location` idiom the
109
- # call-related rules otherwise repeat; all other fields forward to
110
- # {.from_location}.
87
+ # Builds a Diagnostic at a call node's `message_loc` (the method-name / matcher span), falling back to
88
+ # the receiver-spanning `node.location` when no message location is available. Absorbs the
89
+ # `node.message_loc || node.location` idiom the call-related rules otherwise repeat; all other fields
90
+ # forward to {.from_location}.
111
91
  def self.from_message_loc(node, **)
112
92
  from_location(node.message_loc || node.location, **)
113
93
  end
114
94
 
115
- # Builds a Diagnostic at a definition / assignment node's
116
- # `name_loc` (the declared name span), falling back to
117
- # `node.location`. Absorbs the `node.name_loc || node.location`
118
- # idiom the def / write rules otherwise repeat.
95
+ # Builds a Diagnostic at a definition / assignment node's `name_loc` (the declared name span), falling
96
+ # back to `node.location`. Absorbs the `node.name_loc || node.location` idiom the def / write rules
97
+ # otherwise repeat.
119
98
  def self.from_name_loc(node, **)
120
99
  from_location(node.name_loc || node.location, **)
121
100
  end
@@ -124,10 +103,9 @@ module Rigor
124
103
  severity == :error
125
104
  end
126
105
 
127
- # The fully-qualified rule identifier — `<source_family>.<rule>`
128
- # when the source is non-default, or just `<rule>` for the
129
- # `:builtin` family. Returns nil when `rule` itself is nil
130
- # (e.g. parse errors and internal-analyzer errors).
106
+ # The fully-qualified rule identifier — `<source_family>.<rule>` when the source is non-default, or
107
+ # just `<rule>` for the `:builtin` family. Returns nil when `rule` itself is nil (e.g. parse errors and
108
+ # internal-analyzer errors).
131
109
  def qualified_rule
132
110
  return nil if rule.nil?
133
111
  return rule if source_family == DEFAULT_SOURCE_FAMILY
@@ -135,12 +113,10 @@ module Rigor
135
113
  "#{source_family}.#{rule}"
136
114
  end
137
115
 
138
- # `--format json` serialisation. The structured `receiver_type`
139
- # / `method_name` / `project_definition_site` fields are emitted
140
- # only when populated, so a consumer (`jq`, `rigor triage`, an AI
141
- # agent) can group a `rigor check --format json` stream by the
142
- # called class / method without parsing the human-readable
143
- # `message` — the message wording is presentation, not contract.
116
+ # `--format json` serialisation. The structured `receiver_type` / `method_name` /
117
+ # `project_definition_site` fields are emitted only when populated, so a consumer (`jq`, `rigor
118
+ # triage`, an AI agent) can group a `rigor check --format json` stream by the called class / method
119
+ # without parsing the human-readable `message` the message wording is presentation, not contract.
144
120
  def to_h
145
121
  base = {
146
122
  "path" => path,
@@ -157,13 +133,10 @@ module Rigor
157
133
  base
158
134
  end
159
135
 
160
- # Text rendering for `rigor check`. The qualified rule
161
- # identifier (per ADR-2 § "Plugin Diagnostic Provenance" —
162
- # `plugin.<id>.<rule>`, `rbs_extended.<rule>`,
163
- # `generated.<provider>.<rule>`) is appended in brackets
164
- # whenever the diagnostic carries a non-default `source_family`,
165
- # so plugin / RBS::Extended / generated provenance is visible
166
- # in the standard text output without changing the layout for
136
+ # Text rendering for `rigor check`. The qualified rule identifier (per ADR-2 § "Plugin Diagnostic
137
+ # Provenance" `plugin.<id>.<rule>`, `rbs_extended.<rule>`, `generated.<provider>.<rule>`) is appended
138
+ # in brackets whenever the diagnostic carries a non-default `source_family`, so plugin / RBS::Extended
139
+ # / generated provenance is visible in the standard text output without changing the layout for
167
140
  # built-in rules. Slice 5 (v0.1.0) wires this surface.
168
141
  def to_s
169
142
  base = "#{path}:#{line}:#{column}: #{severity}: #{message}"
@@ -2,31 +2,26 @@
2
2
 
3
3
  module Rigor
4
4
  module Analysis
5
- # Detects when a `.rb` file is actually an ERB template — a Rails
6
- # generator `templates/foo.rb` shape that uses `<%= ... %>` to
7
- # interpolate identifiers. Prism rejects the template as invalid
8
- # Ruby and the analyzer emits one parse-error diagnostic per scrap
9
- # the parser tripped over (≈ 20 on Redmine's
10
- # `lib/generators/redmine_plugin_model/templates/migration.rb`),
11
- # all of them noise from the user's perspective. The runner / worker
12
- # consults this detector before turning parse errors into
13
- # diagnostics; an ERB-shaped source is silently skipped.
5
+ # Detects when a `.rb` file is actually an ERB template — a Rails generator `templates/foo.rb` shape that
6
+ # uses `<%= ... %>` to interpolate identifiers. Prism rejects the template as invalid Ruby and the analyzer
7
+ # emits one parse-error diagnostic per scrap the parser tripped over (≈ 20 on Redmine's
8
+ # `lib/generators/redmine_plugin_model/templates/migration.rb`), all of them noise from the user's
9
+ # perspective. The runner / worker consults this detector before turning parse errors into diagnostics; an
10
+ # ERB-shaped source is silently skipped.
14
11
  #
15
- # Detection is byte-level on the raw source: any occurrence of the
16
- # `%>` closing marker proves the file is an ERB template. `%>`
17
- # cannot appear in valid Ruby `%` is a binary operator that
18
- # requires an operand on its right side, never `>`. The opening
19
- # `<%` is ambiguous in principle (`x<%y` parses as `x < %y`, a
20
- # comparison against a `%`-literal) but a real Ruby file with that
21
- # shape would still not contain the closing `%>`.
12
+ # Detection is byte-level on the raw source: any occurrence of the `%>` closing marker proves the file is
13
+ # an ERB template. `%>` cannot appear in valid Ruby — `%` is a binary operator that requires an operand on
14
+ # its right side, never `>`. The opening `<%` is ambiguous in principle (`x<%y` parses as `x < %y`, a
15
+ # comparison against a `%`-literal) but a real Ruby file with that shape would still not contain the
16
+ # closing `%>`.
22
17
  module ErbTemplateDetector
23
18
  ERB_CLOSING_MARKER = /%>/
24
19
 
25
20
  module_function
26
21
 
27
22
  # @param parse_result [Prism::ParseResult]
28
- # @return [Boolean] true when the parsed source looks like an
29
- # ERB template (parse errors expected; analysis should skip).
23
+ # @return [Boolean] true when the parsed source looks like an ERB template (parse errors expected;
24
+ # analysis should skip).
30
25
  def template?(parse_result)
31
26
  source = parse_result.source.source
32
27
  return false unless source.is_a?(String) && !source.empty?
@@ -4,11 +4,9 @@ module Rigor
4
4
  module Analysis
5
5
  # Immutable storage for flow-sensitive facts attached to a Scope snapshot.
6
6
  #
7
- # Six buckets (see BUCKETS): local_binding, captured_local,
8
- # object_content, global_storage, dynamic_origin, relational.
9
- # Callers can record facts, invalidate all facts that mention a target,
10
- # and conservatively join two stores by retaining only facts that both
11
- # incoming edges share.
7
+ # Six buckets (see BUCKETS): local_binding, captured_local, object_content, global_storage, dynamic_origin,
8
+ # relational. Callers can record facts, invalidate all facts that mention a target, and conservatively
9
+ # join two stores by retaining only facts that both incoming edges share.
12
10
  class FactStore
13
11
  BUCKETS = %i[
14
12
  local_binding
@@ -48,13 +46,9 @@ module Rigor
48
46
  attr_reader :facts
49
47
 
50
48
  class << self
51
- # ADR-15 Phase 4b.x — return the eagerly-loaded
52
- # singleton-class `@empty` ivar. Lazy `@empty ||= new`
53
- # would write to a class/module ivar from non-main
54
- # Ractors and trip `Ractor::IsolationError`. The
55
- # `@empty = new.freeze` at module body below
56
- # pre-populates the ivar on the main Ractor at load
57
- # time.
49
+ # ADR-15 Phase 4b.x — return the eagerly-loaded singleton-class `@empty` ivar. Lazy `@empty ||= new`
50
+ # would write to a class/module ivar from non-main Ractors and trip `Ractor::IsolationError`. The
51
+ # `@empty = new.freeze` at module body below pre-populates the ivar on the main Ractor at load time.
58
52
  attr_reader :empty
59
53
  end
60
54
 
@@ -131,11 +125,9 @@ module Rigor
131
125
  unique.freeze
132
126
  end
133
127
 
134
- # `fact.target` is `Target | Array[Target]` per the carrier
135
- # contract. Branching with an early return on the `Array`
136
- # arm lets type narrowing collapse the post-return value to
137
- # the bare `Target` case, so the wrapped tuple is `[Target]`
138
- # and the union of return paths is exactly `Array[Target]`.
128
+ # `fact.target` is `Target | Array[Target]` per the carrier contract. Branching with an early return on
129
+ # the `Array` arm lets type narrowing collapse the post-return value to the bare `Target` case, so the
130
+ # wrapped tuple is `[Target]` and the union of return paths is exactly `Array[Target]`.
139
131
  def fact_targets(fact)
140
132
  target = fact.target
141
133
  return target if target.is_a?(Array)
@@ -143,11 +135,9 @@ module Rigor
143
135
  [target]
144
136
  end
145
137
 
146
- # ADR-15 Phase 4b.x — eager-load the singleton `@empty`
147
- # on the main Ractor at module-load time. Workers then
148
- # READ the populated ivar without ever attempting a
149
- # class/module ivar WRITE (which non-main Ractors are
150
- # forbidden from doing).
138
+ # ADR-15 Phase 4b.x — eager-load the singleton `@empty` on the main Ractor at module-load time. Workers
139
+ # then READ the populated ivar without ever attempting a class/module ivar WRITE (which non-main
140
+ # Ractors are forbidden from doing).
151
141
  @empty = new.freeze
152
142
  end
153
143
  end