rigortype 0.4.6 → 0.4.7

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 (417) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "optparse"
4
+
3
5
  require_relative "command"
4
6
  require_relative "../rooted_glob"
5
7
 
@@ -77,6 +79,28 @@ module Rigor
77
79
  # up.
78
80
  SKILLS_ROOT = File.expand_path("../../../skills", __dir__).freeze
79
81
 
82
+ # The parser of `rigor skill`, built without parsing anything: the catalogue reads its flags from it (ADR-126
83
+ # WD3). The positional grammar is read by hand in {#run}, so this describes the switches and parses nothing.
84
+ def option_parser(options = {})
85
+ OptionParser.new do |opts|
86
+ opts.banner = USAGE.lines.first.chomp
87
+ opts.on("--full NAME", "Print the SKILL.md body and its references/ inline") { |v| options[:full] = v }
88
+ opts.on("--path NAME", "Print the absolute path of the SKILL.md file") { |v| options[:path] = v }
89
+ opts.on("--list", "List bundled skills (name + absolute path)") { options[:list] = true }
90
+ opts.on("--describe", "Report project state and recommend the next skill") { options[:describe] = true }
91
+ end
92
+ end
93
+
94
+ # The parser of `rigor skill describe` and `rigor describe`; `banner` is the usage line of the spelling used.
95
+ def self.describe_option_parser(banner = "Usage: rigor skill describe [--deep]", options = {})
96
+ OptionParser.new do |opts|
97
+ opts.banner = banner
98
+ opts.on("--deep", "Run `rigor check` first and route the recommendation on its result") do
99
+ options[:deep] = true
100
+ end
101
+ end
102
+ end
103
+
80
104
  # @return CLI exit status.
81
105
  def run
82
106
  case @argv.first
@@ -87,7 +111,7 @@ module Rigor
87
111
  0
88
112
  when "describe", "--describe"
89
113
  @argv.shift
90
- run_describe
114
+ run_describe("Usage: rigor skill describe [--deep]")
91
115
  when "--list"
92
116
  @argv.shift
93
117
  run_list
@@ -176,7 +200,12 @@ module Rigor
176
200
  # `--deep` is the one opt-in that breaks the "never runs `rigor check`" guarantee — and only for the invocation
177
201
  # that asks for it (issue #148 / ADR-73 § "Field-trial follow-ups"). It runs a real check and lets the result
178
202
  # pick the headline; the un-flagged command is unchanged, down to loading no analysis code at all.
179
- def run_describe
203
+ def run_describe(usage)
204
+ if @argv.intersect?(%w[-h --help])
205
+ @out.puts(usage)
206
+ return 0
207
+ end
208
+
180
209
  deep = !@argv.delete("--deep").nil?
181
210
  unknown = @argv.first
182
211
  return usage_error("unknown option for `describe`: #{unknown}") unless unknown.nil?
@@ -222,7 +251,7 @@ module Rigor
222
251
  discover_skills.find { |s| s.fetch(:name) == name }
223
252
  end
224
253
 
225
- # The `references/*.md` files bundled alongside a skill, sorted so the `NN-` prefixes drive read order.
254
+ # The `references/*.md` files bundled alongside a skill.
226
255
  def reference_files(skill)
227
256
  dir = File.join(File.dirname(skill.fetch(:path)), "references")
228
257
  return [] unless File.directory?(dir)
@@ -96,7 +96,7 @@ module Rigor
96
96
  end
97
97
  end
98
98
 
99
- # The `rigor-doctor` signal, read exactly as `rigor doctor` itself reads it: an RBS environment that built to
99
+ # The `rigor-doctor` signal: an RBS environment that built to
100
100
  # zero classes (analysis is hollow, so every other count is meaningless), or a `configuration-error` the run
101
101
  # already emitted. Checked first — with a broken setup the remaining diagnostics are not evidence of anything.
102
102
  def broken_environment?(result)
@@ -361,7 +361,7 @@ module Rigor
361
361
  end
362
362
 
363
363
  # The one-line essence of a skill's frontmatter `description`, read live from the SKILL.md so the catalogue can
364
- # never go stale relative to the shipped skill. Drops the `Triggers:` / `NOT for` tail and caps the length.
364
+ # never go stale relative to the shipped skill. Drops the `Triggers:` tail and caps the length.
365
365
  def catalog_blurb(path)
366
366
  text = frontmatter(path).fetch("description", "").to_s.strip.tr("\n", " ").squeeze(" ")
367
367
  head = text.split(/\s*Triggers?:/, 2).first.to_s.strip
@@ -12,6 +12,7 @@ require_relative "../inference/flow_tracer"
12
12
  require_relative "../inference/scope_indexer"
13
13
  require_relative "command"
14
14
  require_relative "trace_renderer"
15
+ require_relative "json_document"
15
16
  require_relative "../project_environment"
16
17
 
17
18
  module Rigor
@@ -43,15 +44,12 @@ module Rigor
43
44
  execute(file: file, options: options)
44
45
  end
45
46
 
46
- private
47
-
48
- def parse_options
49
- options = { format: "text", delay: nil, verbose: false, line: nil, config: nil }
50
- parser = OptionParser.new do |opts|
47
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
48
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
49
+ def option_parser(options = {})
50
+ OptionParser.new do |opts|
51
51
  opts.banner = USAGE
52
- opts.on("--format=FORMAT", "Output format: text (animation) or json (raw event stream)") do |value|
53
- options[:format] = value
54
- end
52
+ Options.add_format(opts, options, "Output format: text (animation) or json (raw event stream)")
55
53
  opts.on("--delay=SECONDS", Float,
56
54
  "Autoplay with SECONDS between frames (default: step on key press)") do |value|
57
55
  options[:delay] = value
@@ -62,7 +60,13 @@ module Rigor
62
60
  opts.on("--verbose", "Include every expression enter/result frame") { options[:verbose] = true }
63
61
  Options.add_config(opts, options)
64
62
  end
65
- parser.parse!(@argv)
63
+ end
64
+
65
+ private
66
+
67
+ def parse_options
68
+ options = { format: "text", delay: nil, verbose: false, line: nil, config: nil }
69
+ option_parser(options).parse!(@argv)
66
70
  options
67
71
  end
68
72
 
@@ -82,6 +86,7 @@ module Rigor
82
86
 
83
87
  if options.fetch(:format) == "json"
84
88
  @out.puts(JSON.pretty_generate(frames.map { |event| event_to_h(event) }))
89
+ @err.puts(JsonDocument::SHAPE_NOTICES.fetch(:trace))
85
90
  return 0
86
91
  end
87
92
 
@@ -36,14 +36,13 @@ module Rigor
36
36
  0
37
37
  end
38
38
 
39
- private
40
-
41
- def parse_options
42
- options = { config: nil, format: "text", top: 10, sections: DEFAULT_SECTIONS, include_info: false }
39
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
40
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
41
+ def option_parser(options = {})
43
42
  OptionParser.new do |opts|
44
43
  opts.banner = USAGE
45
44
  Options.add_config(opts, options)
46
- opts.on("--format=FORMAT", "Output format: text (default) or json") { |v| options[:format] = v }
45
+ Options.add_format(opts, options, "Output format: text (default) or json")
47
46
  opts.on("--top=N", Integer, "Hotspot-file count (default 10)") { |v| options[:top] = v }
48
47
  opts.on("--include-info",
49
48
  "Route info diagnostics into distribution / selectors / hotspots " \
@@ -57,15 +56,15 @@ module Rigor
57
56
  opts.on("--selectors-only", "Print only the class/method selectors section") do
58
57
  options[:sections] = %i[selectors]
59
58
  end
60
- end.parse!(@argv)
61
- validate!(options)
62
- options
59
+ end
63
60
  end
64
61
 
65
- def validate!(options)
66
- return if %w[text json].include?(options.fetch(:format))
62
+ private
67
63
 
68
- raise OptionParser::InvalidArgument, "unsupported format: #{options.fetch(:format)}"
64
+ def parse_options
65
+ options = { config: nil, format: "text", top: 10, sections: DEFAULT_SECTIONS, include_info: false }
66
+ option_parser(options).parse!(@argv)
67
+ options
69
68
  end
70
69
 
71
70
  # Sequential, cache-backed, no run-stats: triage only needs the diagnostic stream, and sequential keeps the rule
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
 
5
5
  require_relative "../triage"
6
+ require_relative "json_document"
6
7
 
7
8
  module Rigor
8
9
  class CLI
@@ -17,7 +18,7 @@ module Rigor
17
18
  end
18
19
 
19
20
  def json
20
- JSON.pretty_generate(Triage.report_to_h(@report))
21
+ JSON.pretty_generate(JsonDocument.stamp(Triage.report_to_h(@report)))
21
22
  end
22
23
 
23
24
  def text
@@ -13,6 +13,7 @@ require_relative "../inference/scope_indexer"
13
13
  require_relative "../inference/precision_scanner"
14
14
  require_relative "../source/node_children"
15
15
  require_relative "type_of_renderer"
16
+ require_relative "json_document"
16
17
  require_relative "type_of_template_probe"
17
18
  require_relative "command"
18
19
  require_relative "options"
@@ -29,7 +30,7 @@ module Rigor
29
30
  # Encapsulating the command in its own class keeps `Rigor::CLI` focused on dispatching and lets us evolve the
30
31
  # type-of UX (extra flags, watch mode, streaming output) without bloating the CLI shell. Output formatting is
31
32
  # delegated to {TypeOfRenderer}.
32
- class TypeOfCommand < Command
33
+ class TypeOfCommand < Command # rubocop:disable Metrics/ClassLength
33
34
  include TypeOfTemplateProbe
34
35
 
35
36
  USAGE = "Usage: rigor type-of [options] FILE:LINE[:COL] [FILE:LINE[:COL] ...]"
@@ -65,19 +66,24 @@ module Rigor
65
66
  execute(targets: targets, options: options, buffer: buffer)
66
67
  end
67
68
 
68
- private
69
-
70
- def parse_options
71
- options = { format: "text", trace: false, config: nil, tmp_file: nil, instead_of: nil }
72
-
73
- parser = OptionParser.new do |opts|
69
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
70
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
71
+ def option_parser(options = {})
72
+ OptionParser.new do |opts|
74
73
  opts.banner = USAGE
75
- opts.on("--format=FORMAT", "Output format: text or json") { |value| options[:format] = value }
74
+ Options.add_format(opts, options, "Output format: text or json")
76
75
  opts.on("--trace", "Record fail-soft fallbacks via FallbackTracer") { options[:trace] = true }
77
76
  Options.add_config(opts, options)
78
77
  Options.add_editor_mode(opts, options)
79
78
  end
80
- parser.parse!(@argv)
79
+ end
80
+
81
+ private
82
+
83
+ def parse_options
84
+ options = { format: "text", trace: false, config: nil, tmp_file: nil, instead_of: nil }
85
+
86
+ option_parser(options).parse!(@argv)
81
87
 
82
88
  options
83
89
  end
@@ -101,7 +107,7 @@ module Rigor
101
107
  results = results_by_target.compact.flatten(1)
102
108
  return 1 if results.empty?
103
109
 
104
- TypeOfRenderer.new(out: @out).render(results, format: options.fetch(:format))
110
+ TypeOfRenderer.new(out: @out, err: @err).render(results, format: options.fetch(:format))
105
111
  0
106
112
  end
107
113
 
@@ -206,7 +212,6 @@ module Rigor
206
212
  end
207
213
  end
208
214
 
209
- # `presentation` is the Result's optional `enumeration:` / `location_mapper:`.
210
215
  def type_result(file, line, column, node, scope_index, options, **presentation)
211
216
  tracer = options[:trace] ? Inference::FallbackTracer.new : nil
212
217
  type = scope_index[node].type_of(node, tracer: tracer)
@@ -16,11 +16,8 @@ module Rigor
16
16
  module_function
17
17
 
18
18
  def build(environment:, configuration:, buffer: nil)
19
- base = Scope.empty(environment: environment)
20
19
  seed = Protection::DiscoverySeed.from_snapshot(configuration: configuration, buffer: buffer)
21
- return base if seed.nil? || seed.empty?
22
-
23
- base.with_discovery(base.discovery.with(**seed))
20
+ Protection::DiscoverySeed.seeded_scope(environment: environment, seed: seed)
24
21
  end
25
22
  end
26
23
  end
@@ -4,6 +4,7 @@ require "json"
4
4
  require "optionparser"
5
5
 
6
6
  require_relative "renderable"
7
+ require_relative "json_document"
7
8
 
8
9
  module Rigor
9
10
  class CLI
@@ -14,8 +15,9 @@ module Rigor
14
15
  class TypeOfRenderer
15
16
  include Renderable
16
17
 
17
- def initialize(out:)
18
+ def initialize(out:, err: nil)
18
19
  @out = out
20
+ @err = err
19
21
  end
20
22
 
21
23
  private
@@ -96,6 +98,7 @@ module Rigor
96
98
  enumerations = line_enumerations(list)
97
99
  payload[:line_enumerations] = enumerations unless enumerations.empty?
98
100
  @out.puts(JSON.pretty_generate(payload))
101
+ @err&.puts(JsonDocument::SHAPE_NOTICES.fetch(:type_of))
99
102
  end
100
103
 
101
104
  def line_enumerations(results)
@@ -38,15 +38,12 @@ module Rigor
38
38
  determine_exit(report, options)
39
39
  end
40
40
 
41
- private
42
-
43
- def parse_options
44
- options = { format: "text", limit: 10, show_recognized: false, threshold: nil,
45
- config: nil }
46
-
47
- parser = OptionParser.new do |opts|
41
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
42
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
43
+ def option_parser(options = {})
44
+ OptionParser.new do |opts|
48
45
  opts.banner = USAGE
49
- opts.on("--format=FORMAT", "Output format: text or json") { |value| options[:format] = value }
46
+ Options.add_format(opts, options, "Output format: text or json")
50
47
  Options.add_config(opts, options)
51
48
  opts.on("--limit=N", Integer, "Max example events to print (text only)") do |value|
52
49
  options[:limit] = value
@@ -58,7 +55,15 @@ module Rigor
58
55
  options[:threshold] = value
59
56
  end
60
57
  end
61
- parser.parse!(@argv)
58
+ end
59
+
60
+ private
61
+
62
+ def parse_options
63
+ options = { format: "text", limit: 10, show_recognized: false, threshold: nil,
64
+ config: nil }
65
+
66
+ option_parser(options).parse!(@argv)
62
67
 
63
68
  options
64
69
  end
@@ -4,6 +4,7 @@ require "json"
4
4
  require "optionparser"
5
5
 
6
6
  require_relative "renderable"
7
+ require_relative "json_document"
7
8
 
8
9
  module Rigor
9
10
  class CLI
@@ -100,7 +101,7 @@ module Rigor
100
101
  end
101
102
 
102
103
  def render_json(report)
103
- @out.puts(JSON.pretty_generate(json_payload(report)))
104
+ @out.puts(JSON.pretty_generate(JsonDocument.stamp(json_payload(report))))
104
105
  end
105
106
 
106
107
  def json_payload(report)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "optionparser"
4
+ require_relative "json_document"
4
5
  require "json"
5
6
 
6
7
  require_relative "../configuration"
@@ -67,6 +68,23 @@ module Rigor
67
68
  0
68
69
  end
69
70
 
71
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
72
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
73
+ def option_parser(options = {})
74
+ OptionParser.new do |opts|
75
+ opts.banner = USAGE
76
+ Options.add_config(opts, options)
77
+ Options.add_format(opts, options, "Output format: text (default) or json")
78
+ opts.on("--entry-point=GLOB", "Treat declarations in files matching GLOB as roots (repeatable)") do |v|
79
+ options[:entry_points] << v
80
+ end
81
+ opts.on("--limit=N", Integer, "Print at most N candidates (default: all)") { |v| options[:limit] = v }
82
+ # WD5 — refuse rather than silently absorb. A reachability answer is sound only over a full run, and a
83
+ # user who asked for the fast path must not silently get the slow one.
84
+ opts.on("--incremental", "(unsupported)") { options[:incremental] = true }
85
+ end
86
+ end
87
+
70
88
  private
71
89
 
72
90
  # A scan's role and the spelling it records depend on the root and the working directory (#1760): a role
@@ -120,20 +138,8 @@ module Rigor
120
138
 
121
139
  def parse_options
122
140
  options = { config: nil, format: "text", entry_points: [], limit: nil }
123
- parser = OptionParser.new do |opts|
124
- opts.banner = USAGE
125
- Options.add_config(opts, options)
126
- opts.on("--format=FORMAT", "Output format: text (default) or json") { |v| options[:format] = v }
127
- opts.on("--entry-point=GLOB", "Treat declarations in files matching GLOB as roots (repeatable)") do |v|
128
- options[:entry_points] << v
129
- end
130
- opts.on("--limit=N", Integer, "Print at most N candidates (default: all)") { |v| options[:limit] = v }
131
- # WD5 — refuse rather than silently absorb. A reachability answer is sound only over a full run, and a
132
- # user who asked for the fast path must not silently get the slow one.
133
- opts.on("--incremental", "(unsupported)") { options[:incremental] = true }
134
- end
135
- parser.parse!(@argv)
136
- return usage_error(parser) if options[:incremental]
141
+ option_parser(options).parse!(@argv)
142
+ return usage_error if options[:incremental]
137
143
 
138
144
  options
139
145
  rescue OptionParser::ParseError => e
@@ -141,7 +147,7 @@ module Rigor
141
147
  :usage_error
142
148
  end
143
149
 
144
- def usage_error(_parser)
150
+ def usage_error
145
151
  @err.puts("rigor unused does not support --incremental: reachability is only sound over a whole-project " \
146
152
  "run, so an incremental pass would report constants as unused merely because the files that " \
147
153
  "reference them were served from cache. Re-run without --incremental.")
@@ -379,6 +385,9 @@ module Rigor
379
385
  required_features: ProjectEnvironment.required_features(configuration),
380
386
  **ProjectEnvironment.dependency_discovery_options(configuration))
381
387
  ->(fqn) { !env.singleton_for_name(fqn).nil? }
388
+ rescue Environment::CoreRootError
389
+ # Issue #2517 — answering "unknown" for every name would report against no core at all.
390
+ raise
382
391
  rescue StandardError
383
392
  ->(_fqn) { false }
384
393
  end
@@ -413,7 +422,7 @@ module Rigor
413
422
  end
414
423
 
415
424
  def json(report, options, supply)
416
- JSON.pretty_generate(
425
+ payload = {
417
426
  declared: report.declared, reachable: report.reachable, roots: report.roots, edges: report.edges,
418
427
  namespaces: report.namespaces,
419
428
  plugin_roots: supply.supplied, plugin_roots_unmatched: supply.unmatched,
@@ -421,7 +430,8 @@ module Rigor
421
430
  undecidable: limited(report.undecidable, options).map do |u|
422
431
  { name: u.fqn, path: u.path, line: u.line, reason: u.reason }
423
432
  end
424
- )
433
+ }
434
+ JSON.pretty_generate(JsonDocument.stamp(payload))
425
435
  end
426
436
 
427
437
  def rows_json(rows, options)
@@ -15,6 +15,11 @@ module Rigor
15
15
 
16
16
  # @return CLI exit status.
17
17
  def run
18
+ if @argv.intersect?(%w[-h --help])
19
+ @out.puts(USAGE)
20
+ return 0
21
+ end
22
+
18
23
  @out.puts("rigor upgrade: No migration target available yet (ADR-50 WD7, queued).")
19
24
  @out.puts("Current version: #{Rigor::VERSION}")
20
25
  0