rigortype 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -6,49 +6,36 @@ require_relative "rspec_rails/have_http_status_analyzer"
6
6
 
7
7
  module Rigor
8
8
  module Plugin
9
- # rigor-rspec-rails — validates rspec-rails matchers whose
10
- # arguments are statically checkable.
9
+ # rigor-rspec-rails — validates rspec-rails matchers whose arguments are statically checkable.
11
10
  #
12
11
  # v0.1.0 covers `have_http_status(int_or_symbol)`:
13
12
  #
14
13
  # - Integer: must be in `100..599`.
15
- # - Symbol: must be one of the
16
- # `Rack::Utils::SYMBOL_TO_STATUS_CODE` keys or one of
17
- # Rails' status-group aliases (`:success`, `:successful`,
18
- # `:missing`, `:redirect`, `:error`, `:client_error`,
14
+ # - Symbol: must be one of the `Rack::Utils::SYMBOL_TO_STATUS_CODE` keys or one of Rails' status-group
15
+ # aliases (`:success`, `:successful`, `:missing`, `:redirect`, `:error`, `:client_error`,
19
16
  # `:server_error`, `:informational`).
20
17
  #
21
18
  # ## Why this is a separate plugin from rigor-rspec
22
19
  #
23
- # `rigor-rspec` handles the **type-narrowing** matchers
24
- # (`be_a`, `be_kind_of`, `be_nil`, `eq(literal)`, etc.)
25
- # ones that refine a local's static type so downstream
26
- # calls in the same `it` body resolve at the narrowed
27
- # type. `rigor-rspec-rails` handles the **behavioral**
28
- # matchers — ones that assert runtime state (HTTP status,
29
- # rendered template, route shape) without narrowing a
30
- # type. The two are activated independently in `.rigor.yml`.
20
+ # `rigor-rspec` handles the **type-narrowing** matchers (`be_a`, `be_kind_of`, `be_nil`, `eq(literal)`,
21
+ # etc.) ones that refine a local's static type so downstream calls in the same `it` body resolve at
22
+ # the narrowed type. `rigor-rspec-rails` handles the **behavioral** matchers — ones that assert runtime
23
+ # state (HTTP status, rendered template, route shape) without narrowing a type. The two are activated
24
+ # independently in `.rigor.yml`.
31
25
  #
32
26
  # ## Deferred matchers
33
27
  #
34
- # - `render_template(...)` — overlaps with
35
- # `rigor-actionpack`'s render-target validation
36
- # (`render :show` against the view tree). A separate
37
- # slice would coordinate the two to avoid
38
- # double-firing.
39
- # - `route_to(...)` / `redirect_to(...)` — needs the
40
- # routes table from `rigor-rails-routes`
28
+ # - `render_template(...)` — overlaps with `rigor-actionpack`'s render-target validation (`render
29
+ # :show` against the view tree). A separate slice would coordinate the two to avoid double-firing.
30
+ # - `route_to(...)` / `redirect_to(...)` needs the routes table from `rigor-rails-routes`
41
31
  # (`:helper_table` cross-plugin fact). Future slice.
42
- # - `have_enqueued_job(JobClass)` /
43
- # `have_enqueued_mail(MailerClass)` class-existence
44
- # check overlaps with engine's
45
- # `inference.unresolved-constant`; queued behind a
46
- # decision on which surface owns the diagnostic.
47
- # - `have_received(:method)` — overlaps with engine's
48
- # `call.undefined-method`; same coordination question.
32
+ # - `have_enqueued_job(JobClass)` / `have_enqueued_mail(MailerClass)` — class-existence check overlaps
33
+ # with engine's `inference.unresolved-constant`; queued behind a decision on which surface owns the
34
+ # diagnostic.
35
+ # - `have_received(:method)` — overlaps with engine's `call.undefined-method`; same coordination
36
+ # question.
49
37
  # - `be_routable` — needs routes table.
50
- # - `match_response_schema(...)` (rswag / OpenAPI) — out
51
- # of scope, separate plugin.
38
+ # - `match_response_schema(...)` (rswag / OpenAPI) — out of scope, separate plugin.
52
39
  class RspecRails < Rigor::Plugin::Base
53
40
  manifest(
54
41
  id: "rspec-rails",
@@ -56,10 +43,9 @@ module Rigor
56
43
  description: "Validates rspec-rails behavioral matchers (have_http_status floor)."
57
44
  )
58
45
 
59
- # ADR-37 — the engine walks each file and hands us every
60
- # CallNode; the analyzer is now pure per-node logic. The
61
- # diagnostic points at the matcher name (`message_loc`),
62
- # reproducing the previous `message_loc || location` positioning.
46
+ # ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is now pure per-node
47
+ # logic. The diagnostic points at the matcher name (`message_loc`), reproducing the previous
48
+ # `message_loc || location` positioning.
63
49
  node_rule Prism::CallNode do |node, _scope, path|
64
50
  violation = HaveHttpStatusAnalyzer.violation_for(node)
65
51
  next [] unless violation
@@ -5,17 +5,13 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class ShouldaMatchers < Rigor::Plugin::Base
8
- # Walks every `RSpec.describe <ModelConst> do ... end` /
9
- # `describe <ModelConst> do ... end` block and validates
10
- # the shoulda-matchers calls inside its body (any depth
11
- # of nested `describe` / `context`) against the
12
- # `:model_index` published by `rigor-activerecord`.
8
+ # Walks every `RSpec.describe <ModelConst> do ... end` / `describe <ModelConst> do ... end` block and
9
+ # validates the shoulda-matchers calls inside its body (any depth of nested `describe` / `context`)
10
+ # against the `:model_index` published by `rigor-activerecord`.
13
11
  #
14
- # The "anchor" for cross-checking is the OUTERMOST
15
- # describe block whose argument is a constant that
16
- # constant names the model being specced. Nested
17
- # `describe ".some_method"` (String / Symbol args) does
18
- # NOT change the anchor.
12
+ # The "anchor" for cross-checking is the OUTERMOST describe block whose argument is a constant —
13
+ # that constant names the model being specced. Nested `describe ".some_method"` (String / Symbol
14
+ # args) does NOT change the anchor.
19
15
  #
20
16
  # ## Recognised matcher calls (v0.1.0)
21
17
  #
@@ -34,8 +30,7 @@ module Rigor
34
30
  # have_db_column(:col)
35
31
  # have_db_index(:col)
36
32
  #
37
- # All look up `:col` against the model's columns
38
- # (`Entry#column?`). Unknown columns fire
33
+ # All look up `:col` against the model's columns (`Entry#column?`). Unknown columns fire
39
34
  # `unknown-column`.
40
35
  #
41
36
  # ### Association matchers
@@ -45,19 +40,15 @@ module Rigor
45
40
  # have_many(:assoc) ← expects :collection
46
41
  # have_and_belong_to_many(:assoc) ← expects :collection
47
42
  #
48
- # Unknown associations fire
49
- # `unknown-association`. Known
50
- # associations with mismatched kind (`should belong_to(:posts)`
51
- # where `:posts` is `has_many`) fire
52
- # `association-kind-mismatch`.
43
+ # Unknown associations fire `unknown-association`. Known associations with mismatched kind (`should
44
+ # belong_to(:posts)` where `:posts` is `has_many`) fire `association-kind-mismatch`.
53
45
  module Analyzer
54
- # One matcher violation (always `:warning`). Carries no
55
- # path/location the caller (the `node_rule` block) positions it
56
- # at the matcher name (`message_loc`) via `Plugin::Base#diagnostic`.
46
+ # One matcher violation (always `:warning`). Carries no path/location — the caller (the
47
+ # `node_rule` block) positions it at the matcher name (`message_loc`) via `Plugin::Base#diagnostic`.
57
48
  Violation = Struct.new(:rule, :message, keyword_init: true)
58
49
 
59
- # `(matcher_name) => (:column | :association_singular | :association_collection)`
60
- # — the validation lane each matcher routes to.
50
+ # `(matcher_name) => (:column | :association_singular | :association_collection)` — the
51
+ # validation lane each matcher routes to.
61
52
  MATCHER_TABLE = {
62
53
  # Column matchers — validate the named column exists on the model.
63
54
  validate_presence_of: :column,
@@ -72,8 +63,7 @@ module Rigor
72
63
  validate_confirmation_of: :column,
73
64
  have_db_column: :column,
74
65
  have_db_index: :column,
75
- # Association matchers — validate the association
76
- # exists AND its kind matches the matcher.
66
+ # Association matchers — validate the association exists AND its kind matches the matcher.
77
67
  belong_to: :association_singular,
78
68
  have_one: :association_singular,
79
69
  have_many: :association_collection,
@@ -84,17 +74,13 @@ module Rigor
84
74
 
85
75
  # @param path [String]
86
76
  # @param root [Prism::Node]
87
- # @param model_index [Object, nil] the `:model_index`
88
- # fact value. When nil the analyzer falls silent.
77
+ # @param model_index [Object, nil] the `:model_index` fact value. When nil the analyzer falls silent.
89
78
  # @return [Array<Diagnostic>]
90
- # The matcher violations for a single call node (0..1), or `[]`
91
- # when it is not a shoulda matcher, sits outside any
92
- # `describe <ModelConst>` block, or the model is unknown.
93
- # ADR-37: the engine owns the walk; the model anchor comes from
94
- # the node-rule `NodeContext` ancestors (the innermost enclosing
95
- # `describe`-with-constant — nested const describes override,
96
- # `describe ".method"` string blocks inherit), exactly as the
97
- # former recursive anchor propagation did.
79
+ # The matcher violations for a single call node (0..1), or `[]` when it is not a shoulda matcher,
80
+ # sits outside any `describe <ModelConst>` block, or the model is unknown. ADR-37: the engine owns
81
+ # the walk; the model anchor comes from the node-rule `NodeContext` ancestors (the innermost
82
+ # enclosing `describe`-with-constant nested const describes override, `describe ".method"`
83
+ # string blocks inherit), exactly as the former recursive anchor propagation did.
98
84
  #
99
85
  # @param matcher_call [Prism::Node]
100
86
  # @param ancestors [Array<Prism::Node>]
@@ -114,20 +100,17 @@ module Rigor
114
100
  violation ? [violation] : []
115
101
  end
116
102
 
117
- # The model anchor in effect at a node: the constant named by the
118
- # innermost enclosing `describe <Const> do … end`, or nil.
103
+ # The model anchor in effect at a node: the constant named by the innermost enclosing `describe
104
+ # <Const> do … end`, or nil.
119
105
  def anchor_for(ancestors)
120
106
  describe = ancestors.rfind { |n| describe_with_constant?(n) }
121
107
  describe && describe_const_name(describe)
122
108
  end
123
109
 
124
- # A direct matcher invocation is a `CallNode` whose
125
- # `name` is in `MATCHER_TABLE` and whose first argument
126
- # is a `SymbolNode`. The chain shape (`should`,
127
- # `expect(...).to`, `is_expected.to`) is irrelevant
128
- # we always recurse to the inner matcher, so a
129
- # diagnostic fires on the matcher regardless of the
130
- # surrounding chain.
110
+ # A direct matcher invocation is a `CallNode` whose `name` is in `MATCHER_TABLE` and whose first
111
+ # argument is a `SymbolNode`. The chain shape (`should`, `expect(...).to`, `is_expected.to`) is
112
+ # irrelevant — we always recurse to the inner matcher, so a diagnostic fires on the matcher
113
+ # regardless of the surrounding chain.
131
114
  def matcher_invocation?(node)
132
115
  node.is_a?(Prism::CallNode) && MATCHER_TABLE.key?(node.name) && symbol_first_arg?(node)
133
116
  end
@@ -137,10 +120,8 @@ module Rigor
137
120
  !args.empty? && args.first.is_a?(Prism::SymbolNode)
138
121
  end
139
122
 
140
- # Detects `RSpec.describe(Const) do ... end` and
141
- # `describe(Const) do ... end`. Either form opens a
142
- # scope whose anchor is `Const`. The receiver shape
143
- # (RSpec vs nil) is allowed in both cases.
123
+ # Detects `RSpec.describe(Const) do ... end` and `describe(Const) do ... end`. Either form opens a
124
+ # scope whose anchor is `Const`. The receiver shape (RSpec vs nil) is allowed in both cases.
144
125
  def describe_with_constant?(node)
145
126
  return false unless node.is_a?(Prism::CallNode)
146
127
  return false unless node.name == :describe
@@ -6,18 +6,15 @@ require_relative "shoulda_matchers/analyzer"
6
6
 
7
7
  module Rigor
8
8
  module Plugin
9
- # rigor-shoulda-matchers — validates shoulda-matchers
10
- # matchers against the `:model_index` cross-plugin fact
11
- # (ADR-9) published by `rigor-activerecord`.
9
+ # rigor-shoulda-matchers — validates shoulda-matchers matchers against the `:model_index` cross-plugin
10
+ # fact (ADR-9) published by `rigor-activerecord`.
12
11
  #
13
- # The plugin walks every `RSpec.describe <ModelConst> do
14
- # ... end` block and validates the matchers inside
15
- # against the model's known columns / associations.
12
+ # The plugin walks every `RSpec.describe <ModelConst> do ... end` block and validates the matchers
13
+ # inside against the model's known columns / associations.
16
14
  #
17
15
  # ## Recognised matchers (v0.1.0)
18
16
  #
19
- # **Column matchers** (validate the named column exists on
20
- # the model):
17
+ # **Column matchers** (validate the named column exists on the model):
21
18
  #
22
19
  # validate_presence_of(:col), validate_uniqueness_of(:col),
23
20
  # validate_length_of(:col), validate_numericality_of(:col),
@@ -26,38 +23,27 @@ module Rigor
26
23
  # validate_format_of(:col), validate_confirmation_of(:col),
27
24
  # have_db_column(:col), have_db_index(:col)
28
25
  #
29
- # **Association matchers** (validate the association exists
30
- # AND its kind matches):
26
+ # **Association matchers** (validate the association exists AND its kind matches):
31
27
  #
32
28
  # belong_to(:assoc), have_one(:assoc) ← :singular
33
29
  # have_many(:assoc), have_and_belong_to_many(:assoc) ← :collection
34
30
  #
35
31
  # ## Cross-plugin dependency
36
32
  #
37
- # The plugin consumes `:model_index` from `rigor-activerecord`.
38
- # When `rigor-activerecord` is NOT loaded (or hasn't
39
- # published an index for the analysed model), the plugin
40
- # falls silent — the cross-check is opt-in. Adding
41
- # `rigor-activerecord` to `.rigor.yml` unlocks the
42
- # diagnostics.
33
+ # The plugin consumes `:model_index` from `rigor-activerecord`. When `rigor-activerecord` is NOT
34
+ # loaded (or hasn't published an index for the analysed model), the plugin falls silent — the
35
+ # cross-check is opt-in. Adding `rigor-activerecord` to `.rigor.yml` unlocks the diagnostics.
43
36
  #
44
37
  # ## Limitations (v0.1.0)
45
38
  #
46
- # - **No chained-matcher arg validation.** The chain
47
- # options on `validate_length_of(:col).is_at_most(50)`,
48
- # `validate_inclusion_of(:col).in_array([...])`,
49
- # `allow_value("foo").for(:col)`, etc. are NOT validated
50
- # (the `.is_at_most` etc. terminals are runtime-only).
51
- # - **No polymorphic / through validation.** `belong_to(:user).polymorphic`,
52
- # `have_many(:posts, through: :memberships)` only check
53
- # the named association; the chain modifiers are
54
- # ignored.
55
- # - **No nested-attribute matchers.** `accept_nested_attributes_for(:posts)`
56
- # not yet covered.
57
- # - **No callback matchers.** `callback(:before_save).before(:save)`
58
- # would need a separate slice (overlaps with the
59
- # model_index's `callbacks` column already exposed but
60
- # no rspec-side recogniser yet).
39
+ # - **No chained-matcher arg validation.** The chain options on `validate_length_of(:col).is_at_most(50)`,
40
+ # `validate_inclusion_of(:col).in_array([...])`, `allow_value("foo").for(:col)`, etc. are NOT
41
+ # validated (the `.is_at_most` etc. terminals are runtime-only).
42
+ # - **No polymorphic / through validation.** `belong_to(:user).polymorphic`, `have_many(:posts,
43
+ # through: :memberships)` only check the named association; the chain modifiers are ignored.
44
+ # - **No nested-attribute matchers.** `accept_nested_attributes_for(:posts)` not yet covered.
45
+ # - **No callback matchers.** `callback(:before_save).before(:save)` would need a separate slice
46
+ # (overlaps with the model_index's `callbacks` column already exposed but no rspec-side recogniser yet).
61
47
  class ShouldaMatchers < Rigor::Plugin::Base
62
48
  manifest(
63
49
  id: "shoulda-matchers",
@@ -70,12 +56,10 @@ module Rigor
70
56
  ]
71
57
  )
72
58
 
73
- # ADR-37 — per-matcher validation over the engine-owned walk. The
74
- # model anchor (the enclosing `describe <Model>` const) comes from
75
- # the node-rule NodeContext ancestors; the diagnostic points at the
76
- # matcher name (message_loc). The :model_index fact (from
77
- # rigor-activerecord) is read lazily via `read_fact`; without it
78
- # the rule is silent.
59
+ # ADR-37 — per-matcher validation over the engine-owned walk. The model anchor (the enclosing
60
+ # `describe <Model>` const) comes from the node-rule NodeContext ancestors; the diagnostic points at
61
+ # the matcher name (message_loc). The :model_index fact (from rigor-activerecord) is read lazily via
62
+ # `read_fact`; without it the rule is silent.
79
63
  node_rule Prism::CallNode do |node, _scope, path, _fc, context|
80
64
  index = read_fact(plugin_id: "activerecord", name: :model_index)
81
65
  next [] if index.nil?
@@ -5,43 +5,35 @@ require "prism"
5
5
  module Rigor
6
6
  module Plugin
7
7
  class Sidekiq < Rigor::Plugin::Base
8
- # Walks a parsed file's AST looking for
9
- # `<WorkerClass>.perform_async(...)` /
10
- # `.perform_inline(...)` / `.perform_in(time, ...)` /
11
- # `.perform_at(time, ...)` calls and validates each
12
- # against the {WorkerIndex}.
8
+ # Walks a parsed file's AST looking for `<WorkerClass>.perform_async(...)` / `.perform_inline(...)` /
9
+ # `.perform_in(time, ...)` / `.perform_at(time, ...)` calls and validates each against the
10
+ # {WorkerIndex}.
13
11
  #
14
12
  # Argument-shape rules:
15
13
  #
16
- # - `perform_async` / `perform_inline` — every
17
- # argument is forwarded to `#perform`. Validate
18
- # `actual == #perform.arity`.
19
- # - `perform_in(interval, ...args)` /
20
- # `perform_at(time, ...args)` — the FIRST argument
21
- # is the schedule (a Time / Integer / ActiveSupport
22
- # duration); the rest are forwarded to `#perform`.
14
+ # - `perform_async` / `perform_inline` — every argument is forwarded to `#perform`. Validate `actual
15
+ # == #perform.arity`.
16
+ # - `perform_in(interval, ...args)` / `perform_at(time, ...args)` — the FIRST argument is the
17
+ # schedule (a Time / Integer / ActiveSupport duration); the rest are forwarded to `#perform`.
23
18
  # Validate `actual_args - 1 == #perform.arity`.
24
19
  module Analyzer
25
20
  # Methods that delegate to `#perform` 1:1.
26
21
  DIRECT_ENTRY_METHODS = %i[perform_async perform_inline].freeze
27
22
 
28
- # Methods whose first argument is a schedule (the
29
- # remaining args are forwarded to `#perform`).
23
+ # Methods whose first argument is a schedule (the remaining args are forwarded to `#perform`).
30
24
  SCHEDULED_ENTRY_METHODS = %i[perform_in perform_at].freeze
31
25
 
32
26
  ENTRY_METHODS = (DIRECT_ENTRY_METHODS + SCHEDULED_ENTRY_METHODS).freeze
33
27
 
34
- # One worker-call observation: the kebab-case `rule`, `severity`,
35
- # and human-readable `message`. Carries no path/location — the
36
- # caller (the `node_rule` block) positions it via
28
+ # One worker-call observation: the kebab-case `rule`, `severity`, and human-readable `message`.
29
+ # Carries no path/location — the caller (the `node_rule` block) positions it via
37
30
  # `Plugin::Base#diagnostic`.
38
31
  Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
39
32
 
40
33
  module_function
41
34
 
42
- # The worker-call violations for a single call node (0..2), or
43
- # `[]` when the node is not a `<Worker>.perform_*` entry call on a
44
- # known worker. ADR-37: the engine owns the walk.
35
+ # The worker-call violations for a single call node (0..2), or `[]` when the node is not a
36
+ # `<Worker>.perform_*` entry call on a known worker. ADR-37: the engine owns the walk.
45
37
  #
46
38
  # @param call_node [Prism::Node]
47
39
  # @param worker_index [WorkerIndex]
@@ -77,8 +69,7 @@ module Rigor
77
69
 
78
70
  def arity_violation(call_node, entry)
79
71
  all_args = (call_node.arguments&.arguments || []).size
80
- # Scheduled entries consume the first arg as the
81
- # schedule; the rest are forwarded.
72
+ # Scheduled entries consume the first arg as the schedule; the rest are forwarded.
82
73
  forwarded_count = SCHEDULED_ENTRY_METHODS.include?(call_node.name) ? all_args - 1 : all_args
83
74
 
84
75
  if SCHEDULED_ENTRY_METHODS.include?(call_node.name) && all_args.zero?
@@ -7,28 +7,20 @@ require_relative "worker_index"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class Sidekiq < Rigor::Plugin::Base
10
- # Walks the configured worker-search paths via the
11
- # plugin's `IoBoundary`, parses each `.rb` file with
12
- # Prism, and collects classes that `include
13
- # Sidekiq::Job` (or one of the configured marker
14
- # modules). For each discovered class, the discoverer
15
- # also reads the `#perform` method's parameter list
16
- # and computes the arity envelope.
10
+ # Walks the configured worker-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
11
+ # Prism, and collects classes that `include Sidekiq::Job` (or one of the configured marker modules).
12
+ # For each discovered class, the discoverer also reads the `#perform` method's parameter list and
13
+ # computes the arity envelope.
17
14
  #
18
15
  # Limitations (intentional for v0.1.0):
19
16
  #
20
- # - Only direct `include` matches against the
21
- # configured marker modules. `class MyWorker;
22
- # include Concerns::Sidekiqable; end` where
23
- # `Concerns::Sidekiqable` re-includes `Sidekiq::Job`
24
- # is NOT discovered. Add the intermediate module to
25
- # `worker_marker_modules` if needed.
26
- # - The qualified class name is the lexical path
27
- # (`Admin::WelcomeWorker` for a class declared
28
- # inside `module Admin`).
29
- # - `#perform` arity is read from the syntactic
30
- # parameter list. Methods built via
31
- # `define_method` are out of scope.
17
+ # - Only direct `include` matches against the configured marker modules. `class MyWorker; include
18
+ # Concerns::Sidekiqable; end` where `Concerns::Sidekiqable` re-includes `Sidekiq::Job` is NOT
19
+ # discovered. Add the intermediate module to `worker_marker_modules` if needed.
20
+ # - The qualified class name is the lexical path (`Admin::WelcomeWorker` for a class declared inside
21
+ # `module Admin`).
22
+ # - `#perform` arity is read from the syntactic parameter list. Methods built via `define_method`
23
+ # are out of scope.
32
24
  class WorkerDiscoverer
33
25
  def initialize(io_boundary:, search_paths:, marker_modules:)
34
26
  @io_boundary = io_boundary
@@ -101,9 +93,8 @@ module Rigor
101
93
  walk_for_workers(node.body, inner_path, &) if node.body
102
94
  end
103
95
 
104
- # Returns true if the class body contains a top-level
105
- # `include <Module>` call where `<Module>` matches
106
- # one of the configured marker modules.
96
+ # Returns true if the class body contains a top-level `include <Module>` call where `<Module>`
97
+ # matches one of the configured marker modules.
107
98
  def includes_marker_module?(body)
108
99
  return false if body.nil?
109
100
 
@@ -137,9 +128,8 @@ module Rigor
137
128
  end
138
129
  end
139
130
 
140
- # Returns the instance-side `def perform(...)` node
141
- # from a class body, or `nil` when the class doesn't
142
- # override `#perform`.
131
+ # Returns the instance-side `def perform(...)` node from a class body, or `nil` when the class
132
+ # doesn't override `#perform`.
143
133
  def lookup_perform_def(body)
144
134
  return nil if body.nil?
145
135
 
@@ -152,12 +142,9 @@ module Rigor
152
142
  nil
153
143
  end
154
144
 
155
- # Builds a `WorkerIndex::Entry` from the discovered
156
- # class's `#perform` def. When the class doesn't
157
- # override `#perform`, we record an "any-arity"
158
- # entry — Sidekiq itself doesn't supply a default
159
- # `#perform`, so calling `perform_async` on a
160
- # worker without one is the user's bug, not the
145
+ # Builds a `WorkerIndex::Entry` from the discovered class's `#perform` def. When the class doesn't
146
+ # override `#perform`, we record an "any-arity" entry — Sidekiq itself doesn't supply a default
147
+ # `#perform`, so calling `perform_async` on a worker without one is the user's bug, not the
161
148
  # plugin's call to flag without runtime context.
162
149
  def build_entry(class_name, perform_def)
163
150
  if perform_def.nil?
@@ -3,17 +3,13 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Sidekiq < Rigor::Plugin::Base
6
- # Frozen catalogue of discovered Sidekiq worker
7
- # classes keyed by qualified class name. Each entry
8
- # holds the `#perform` method's arity envelope so the
9
- # analyzer can validate `Worker.perform_async(...)`
10
- # call sites.
6
+ # Frozen catalogue of discovered Sidekiq worker classes keyed by qualified class name. Each entry
7
+ # holds the `#perform` method's arity envelope so the analyzer can validate
8
+ # `Worker.perform_async(...)` call sites.
11
9
  #
12
- # Uses the same `min_arity` / `max_arity` closed-range
13
- # envelope as `rigor-activejob`'s `JobIndex::Entry`
14
- # (`Float::INFINITY` upper bound when `*args` is present);
15
- # Sidekiq workers serialize args to JSON so keyword arity
16
- # is not tracked here.
10
+ # Uses the same `min_arity` / `max_arity` closed-range envelope as `rigor-activejob`'s
11
+ # `JobIndex::Entry` (`Float::INFINITY` upper bound when `*args` is present); Sidekiq workers
12
+ # serialize args to JSON so keyword arity is not tracked here.
17
13
  class WorkerIndex
18
14
  Entry = Data.define(:class_name, :min_arity, :max_arity) do
19
15
  def arity_label
@@ -8,14 +8,11 @@ require_relative "sidekiq/analyzer"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
- # rigor-sidekiq — validates `Worker.perform_async(...)`
12
- # / `.perform_in(...)` / `.perform_at(...)` /
13
- # `.perform_inline(...)` argument arity against the
14
- # discovered `#perform` definitions.
11
+ # rigor-sidekiq — validates `Worker.perform_async(...)` / `.perform_in(...)` / `.perform_at(...)` /
12
+ # `.perform_inline(...)` argument arity against the discovered `#perform` definitions.
15
13
  #
16
14
  # Tier 3C of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
17
- # Statically discovers Sidekiq workers by walking
18
- # `worker_search_paths` and parsing each file with
15
+ # Statically discovers Sidekiq workers by walking `worker_search_paths` and parsing each file with
19
16
  # Prism — no `sidekiq` runtime dependency.
20
17
  #
21
18
  # ## Configuration
@@ -28,30 +25,19 @@ module Rigor
28
25
  #
29
26
  # ## What it checks
30
27
  #
31
- # 1. **Argument arity** — `perform_async(args)` /
32
- # `perform_inline(args)` forward every argument to
33
- # `#perform`; `perform_in(t, args)` /
34
- # `perform_at(t, args)` consume the first argument
35
- # as the schedule and forward the rest. Mismatches
36
- # emit `wrong-arity`.
37
- # 2. **Missing schedule** — `perform_in()` /
38
- # `perform_at()` with zero arguments emit
39
- # `missing-schedule`.
28
+ # 1. **Argument arity** — `perform_async(args)` / `perform_inline(args)` forward every argument to
29
+ # `#perform`; `perform_in(t, args)` / `perform_at(t, args)` consume the first argument as the
30
+ # schedule and forward the rest. Mismatches emit `wrong-arity`.
31
+ # 2. **Missing schedule** — `perform_in()` / `perform_at()` with zero arguments emit `missing-schedule`.
40
32
  #
41
33
  # ## Limitations (v0.1.0)
42
34
  #
43
- # - Direct `include` matches only against the
44
- # configured marker modules. Indirect includes via a
35
+ # - Direct `include` matches only against the configured marker modules. Indirect includes via a
45
36
  # custom concern are out of scope.
46
- # - `#perform` arity is read from the syntactic
47
- # parameter list. `define_method` actions are out of
48
- # scope.
49
- # - Required keyword arguments are not validated at
50
- # the call site (positional-only for v0.1.0). Sidekiq
51
- # serialises arguments to JSON, so keyword args are
52
- # uncommon in practice.
53
- # - The schedule argument's type isn't validated (no
54
- # "is this a Time?" check); we just consume it.
37
+ # - `#perform` arity is read from the syntactic parameter list. `define_method` actions are out of scope.
38
+ # - Required keyword arguments are not validated at the call site (positional-only for v0.1.0).
39
+ # Sidekiq serialises arguments to JSON, so keyword args are uncommon in practice.
40
+ # - The schedule argument's type isn't validated (no "is this a Time?" check); we just consume it.
55
41
  class Sidekiq < Rigor::Plugin::Base
56
42
  manifest(
57
43
  id: "sidekiq",
@@ -76,9 +62,8 @@ module Rigor
76
62
  @worker_marker_modules = Array(config.fetch("worker_marker_modules")).map(&:to_s)
77
63
  end
78
64
 
79
- # File-level only: the load-error emission. The per-call arity
80
- # validation runs over the engine-owned walk via the node_rule
81
- # below (ADR-37). The worker index is lazily loaded + memoised by
65
+ # File-level only: the load-error emission. The per-call arity validation runs over the engine-owned
66
+ # walk via the node_rule below (ADR-37). The worker index is lazily loaded + memoised by
82
67
  # `producer_value`, shared by both surfaces.
83
68
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
84
69
  index = producer_value(:worker_index)