rigortype 0.1.18 → 0.2.0

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -224
  3. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
  4. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
  5. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
  6. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
  7. data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
  8. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
  9. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
  10. data/lib/rigor/analysis/check_rules.rb +756 -132
  11. data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
  12. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
  13. data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
  14. data/lib/rigor/analysis/diagnostic.rb +8 -0
  15. data/lib/rigor/analysis/fact_store.rb +5 -4
  16. data/lib/rigor/analysis/rule_catalog.rb +153 -6
  17. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +19 -18
  18. data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
  19. data/lib/rigor/analysis/runner.rb +75 -27
  20. data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
  21. data/lib/rigor/analysis/worker_session.rb +31 -25
  22. data/lib/rigor/bleeding_edge.rb +123 -0
  23. data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
  24. data/lib/rigor/cache/descriptor.rb +86 -8
  25. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  26. data/lib/rigor/cache/store.rb +5 -3
  27. data/lib/rigor/cli/annotate_command.rb +122 -16
  28. data/lib/rigor/cli/baseline_command.rb +4 -3
  29. data/lib/rigor/cli/check_command.rb +118 -16
  30. data/lib/rigor/cli/coverage_command.rb +148 -16
  31. data/lib/rigor/cli/coverage_scan.rb +57 -0
  32. data/lib/rigor/cli/explain_command.rb +2 -0
  33. data/lib/rigor/cli/lsp_command.rb +3 -7
  34. data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
  35. data/lib/rigor/cli/mutation_protection_report.rb +73 -0
  36. data/lib/rigor/cli/options.rb +9 -0
  37. data/lib/rigor/cli/plugins_command.rb +4 -5
  38. data/lib/rigor/cli/plugins_renderer.rb +0 -2
  39. data/lib/rigor/cli/protection_renderer.rb +63 -0
  40. data/lib/rigor/cli/protection_report.rb +68 -0
  41. data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
  42. data/lib/rigor/cli/sig_gen_command.rb +2 -1
  43. data/lib/rigor/cli/trace_command.rb +2 -1
  44. data/lib/rigor/cli/triage_command.rb +8 -4
  45. data/lib/rigor/cli/triage_renderer.rb +15 -1
  46. data/lib/rigor/cli/type_of_command.rb +1 -1
  47. data/lib/rigor/cli/type_scan_command.rb +2 -1
  48. data/lib/rigor/cli.rb +12 -3
  49. data/lib/rigor/configuration/dependencies.rb +2 -4
  50. data/lib/rigor/configuration/severity_profile.rb +13 -1
  51. data/lib/rigor/configuration.rb +100 -6
  52. data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
  53. data/lib/rigor/environment/class_registry.rb +4 -3
  54. data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
  55. data/lib/rigor/environment/lockfile_resolver.rb +1 -1
  56. data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
  57. data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
  58. data/lib/rigor/environment/rbs_loader.rb +74 -5
  59. data/lib/rigor/environment.rb +17 -7
  60. data/lib/rigor/flow_contribution/fact.rb +1 -1
  61. data/lib/rigor/flow_contribution.rb +3 -5
  62. data/lib/rigor/inference/acceptance.rb +17 -9
  63. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  64. data/lib/rigor/inference/body_fixpoint.rb +89 -0
  65. data/lib/rigor/inference/budget_trace.rb +29 -2
  66. data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
  67. data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
  68. data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
  69. data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
  70. data/lib/rigor/inference/expression_typer.rb +1072 -71
  71. data/lib/rigor/inference/hkt_body.rb +8 -11
  72. data/lib/rigor/inference/hkt_body_parser.rb +10 -12
  73. data/lib/rigor/inference/hkt_registry.rb +10 -11
  74. data/lib/rigor/inference/macro_block_self_type.rb +2 -2
  75. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
  76. data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
  77. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
  78. data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
  79. data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
  80. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
  81. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
  82. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
  83. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
  84. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
  85. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
  86. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
  87. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
  88. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
  89. data/lib/rigor/inference/method_dispatcher.rb +112 -49
  90. data/lib/rigor/inference/method_parameter_binder.rb +56 -2
  91. data/lib/rigor/inference/multi_target_binder.rb +46 -3
  92. data/lib/rigor/inference/mutation_widening.rb +147 -11
  93. data/lib/rigor/inference/narrowing.rb +284 -53
  94. data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
  95. data/lib/rigor/inference/project_patched_methods.rb +4 -7
  96. data/lib/rigor/inference/project_patched_scanner.rb +2 -13
  97. data/lib/rigor/inference/protection_scanner.rb +86 -0
  98. data/lib/rigor/inference/scope_indexer.rb +821 -76
  99. data/lib/rigor/inference/statement_evaluator.rb +1179 -102
  100. data/lib/rigor/inference/struct_fold_safety.rb +181 -0
  101. data/lib/rigor/inference/synthetic_method.rb +7 -7
  102. data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
  103. data/lib/rigor/language_server/completion_provider.rb +6 -12
  104. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  105. data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
  106. data/lib/rigor/language_server/hover_provider.rb +2 -3
  107. data/lib/rigor/language_server/hover_renderer.rb +2 -11
  108. data/lib/rigor/language_server/server.rb +9 -17
  109. data/lib/rigor/language_server.rb +4 -5
  110. data/lib/rigor/plugin/base.rb +245 -87
  111. data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
  112. data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
  113. data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
  114. data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
  115. data/lib/rigor/plugin/macro.rb +6 -8
  116. data/lib/rigor/plugin/manifest.rb +49 -90
  117. data/lib/rigor/plugin/node_rule_walk.rb +59 -14
  118. data/lib/rigor/plugin/registry.rb +18 -18
  119. data/lib/rigor/plugin/type_node_resolver.rb +6 -8
  120. data/lib/rigor/protection/mutation_scanner.rb +120 -0
  121. data/lib/rigor/protection/mutator.rb +246 -0
  122. data/lib/rigor/rbs_extended.rb +24 -36
  123. data/lib/rigor/reflection.rb +4 -7
  124. data/lib/rigor/scope/discovery_index.rb +16 -2
  125. data/lib/rigor/scope.rb +185 -16
  126. data/lib/rigor/sig_gen/generator.rb +8 -0
  127. data/lib/rigor/sig_gen/observed_call.rb +3 -3
  128. data/lib/rigor/sig_gen/writer.rb +40 -2
  129. data/lib/rigor/source/constant_path.rb +62 -0
  130. data/lib/rigor/source.rb +1 -0
  131. data/lib/rigor/triage/catalogue.rb +4 -19
  132. data/lib/rigor/triage.rb +69 -1
  133. data/lib/rigor/type/bound_method.rb +2 -11
  134. data/lib/rigor/type/combinator.rb +45 -3
  135. data/lib/rigor/type/constant.rb +2 -11
  136. data/lib/rigor/type/data_class.rb +2 -11
  137. data/lib/rigor/type/data_instance.rb +2 -11
  138. data/lib/rigor/type/hash_shape.rb +2 -11
  139. data/lib/rigor/type/integer_range.rb +2 -11
  140. data/lib/rigor/type/intersection.rb +2 -11
  141. data/lib/rigor/type/nominal.rb +2 -11
  142. data/lib/rigor/type/plain_lattice.rb +37 -0
  143. data/lib/rigor/type/refined.rb +72 -13
  144. data/lib/rigor/type/singleton.rb +2 -11
  145. data/lib/rigor/type/struct_class.rb +75 -0
  146. data/lib/rigor/type/struct_instance.rb +93 -0
  147. data/lib/rigor/type/tuple.rb +5 -15
  148. data/lib/rigor/type.rb +2 -0
  149. data/lib/rigor/version.rb +1 -1
  150. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  151. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
  152. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +16 -32
  153. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
  154. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
  155. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
  156. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
  157. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
  158. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
  159. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
  160. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
  161. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
  162. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
  163. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
  164. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
  165. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
  166. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
  167. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
  168. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
  169. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
  170. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +18 -49
  171. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
  172. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
  173. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +4 -4
  174. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
  175. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
  176. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
  177. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
  178. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +22 -35
  179. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
  180. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
  181. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +16 -23
  182. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
  183. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
  184. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
  185. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  186. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +21 -27
  187. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
  188. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
  189. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
  190. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
  191. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
  192. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
  193. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
  194. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
  195. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
  196. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
  197. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +52 -40
  198. data/sig/rigor/analysis/fact_store.rbs +3 -0
  199. data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
  200. data/sig/rigor/plugin/base.rbs +5 -2
  201. data/sig/rigor/plugin/manifest.rbs +1 -2
  202. data/sig/rigor/scope.rbs +18 -1
  203. data/sig/rigor/type.rbs +37 -1
  204. data/sig/rigor.rbs +1 -1
  205. data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
  206. data/skills/rigor-plugin-author/SKILL.md +6 -4
  207. data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
  208. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
  209. metadata +25 -2
  210. data/lib/rigor/plugin/macro/external_file.rb +0 -143
@@ -98,9 +98,11 @@ AST walk per file — hands every matching node to the block along with a
98
98
  `Rigor::Analysis::Diagnostic` (built via the `diagnostic` helper).
99
99
  Optionally the plugin also declares `dynamic_return(receivers:)` /
100
100
  `type_specifier(methods:)` to *supply* a return type or narrowing facts
101
- for call sites the core analyzer types as `Dynamic`. (The older
102
- `#diagnostics_for_file` / `#flow_contribution_for` fat hooks remain as
103
- deprecated escape valves see Phase 2.)
101
+ for call sites the core analyzer types as `Dynamic`. `#diagnostics_for_file`
102
+ is the file-rule surface for whole-file diagnostics a per-node walk can't
103
+ express. (`flow_contribution_for` was removed pre-1.0 in ADR-52 WD3 —
104
+ defining it now raises `ArgumentError`; use `dynamic_return` /
105
+ `type_specifier`. See Phase 2.)
104
106
 
105
107
  ## Phase outline
106
108
 
@@ -115,5 +117,5 @@ deprecated escape valves — see Phase 2.)
115
117
  | Module | Read | Covers |
116
118
  | --- | --- | --- |
117
119
  | 1 | [`references/01-plan-and-scaffold.md`](references/01-plan-and-scaffold.md) | **Phase 1.** The gem vs project-private packaging split, directory trees for both, gemspec template, project-private path-gem / `RUBYLIB` activation, the `Rigor::Plugin::Base` skeleton, `.rigor.yml` `plugins:` wiring. |
118
- | 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `type_specifier` return-type hooks (and the deprecated `flow_contribution_for` escape valve), calling the target library's pure methods directly rather than reimplementing them (ADR-39: `Plugin::Inflector` over the real `ActiveSupport::Inflector`; `Base.suggest` for did-you-mean), and shipping `sig/*.rbs` so the DSL's types are visible. |
120
+ | 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `type_specifier` return-type hooks (`flow_contribution_for` was removed pre-1.0 in ADR-52 WD3), calling the target library's pure methods directly rather than reimplementing them (ADR-39: `Plugin::Inflector` over the real `ActiveSupport::Inflector`; `Base.suggest` for did-you-mean), and shipping `sig/*.rbs` so the DSL's types are visible. |
119
121
  | 3 | [`references/03-test-and-ship.md`](references/03-test-and-ship.md) | **Phase 3.** Testing a plugin from outside the monorepo — fixture projects driven through `rigor check --format json`, plus pure unit tests of dispatch tables — with RSpec or Minitest. Version pinning against the pre-1.0 contract. README. Publishing to RubyGems or keeping the plugin private. |
@@ -37,12 +37,15 @@ rows the rule positions with `diagnostic` (the bundled plugins follow
37
37
  this `Analyzer.violations_for` split, which also makes the logic
38
38
  unit-testable without running the whole engine).
39
39
 
40
- > The legacy `diagnostics_for_file(path:, scope:, root:)` hook where
41
- > you hand-rolled a `def walk` / `compact_child_nodes.each` recursion
42
- > over `root` yourself is the **deprecated whole-file escape valve**.
43
- > Use it only for a genuinely file-scoped result (a single load-error
44
- > row, or a check that needs the whole parsed file at once). New
45
- > node-scoped checks use `node_rule`.
40
+ > `diagnostics_for_file(path:, scope:, root:)` is the **file-rule**
41
+ > surface use it for a genuinely file-scoped result (a single
42
+ > load-error row, a cross-file aggregation, or a check that needs the
43
+ > whole parsed file at once). It is not deprecated; it is the right tool
44
+ > when a check isn't per-node. Per-node checks use `node_rule` (you don't
45
+ > hand-roll a `def walk` / `compact_child_nodes.each` recursion — the
46
+ > engine owns the walk). Map a violation array to diagnostics with
47
+ > `diagnostics_for(violations, path:, node:)` rather than a hand-rolled
48
+ > `.map { diagnostic(...) }`.
46
49
 
47
50
  ### Recognising call sites
48
51
 
@@ -59,7 +62,9 @@ re-deriving the `unescaped.to_sym` shape.
59
62
  validate references): declare `node_file_context { |root, scope| … }`.
60
63
  It runs once per file before the walk and its return value is threaded
61
64
  to every rule as `file_context`. (A *cross-file* collect belongs in
62
- `#prepare` + `services.fact_store` instead see
65
+ `#prepare` + `services.fact_store.publish`; the consuming plugin reads
66
+ it with `read_fact(plugin_id:, name:)` — the nil-inclusive memo means
67
+ you never hand-roll an `@x_resolved` flag. See
63
68
  [`01-plan-and-scaffold.md`](01-plan-and-scaffold.md).)
64
69
  - **Where the node sits**: `context` (a `Rigor::Plugin::NodeContext`)
65
70
  carries the lexical ancestor chain — `context.enclosing_def`,
@@ -213,16 +218,16 @@ plugin is confident; a wrong contribution propagates downstream.
213
218
  `rigor plugins --capabilities` catalogue enumerates — run it to see
214
219
  exactly what each loaded plugin contributes.
215
220
 
216
- > **The deprecated escape valve.** The original fat hook,
217
- > `flow_contribution_for(call_node:, scope:)`, returns a single
218
- > `Rigor::FlowContribution` and is still consulted alongside the narrow
219
- > DSLs. It is retained only for the two shapes the narrow DSLs do not
220
- > express: a **method-gated return type** (an RSpec `let(:x) { … }`
221
- > binding, a Sorbet `sig`-driven return — keyed on the method, not a
222
- > fixed receiver class) and a **dynamic per-project receiver set**
223
- > (ActiveStorage's `Attached::One` on discovered model classes). If your
224
- > plugin needs one of those, use `flow_contribution_for`; otherwise
225
- > prefer `dynamic_return` / `type_specifier`. These return-type surfaces
221
+ > **The removed fat hook.** The original `flow_contribution_for(call_node:,
222
+ > scope:)` was **deleted pre-1.0 in ADR-52 WD3** — defining it now raises
223
+ > `ArgumentError` at load time. The two shapes it used to cover are
224
+ > expressed by `dynamic_return`'s callable gates: a **method-gated return
225
+ > type** (an RSpec `let(:x) { … }` binding, a Sorbet `sig`-driven return —
226
+ > keyed on the method, not a fixed receiver class) uses
227
+ > `dynamic_return methods: -> { [...] }` or `file_methods: ->(path) { [...] }`;
228
+ > a **dynamic per-project receiver set** (ActiveStorage's `Attached::One`
229
+ > on discovered model classes) uses `dynamic_return receivers: -> { [...] }`,
230
+ > the callable resolved once after `#prepare`. These return-type surfaces
226
231
  > are the most contract-sensitive part of the API — implement one only
227
232
  > if the plugin genuinely needs to sharpen call-site types; a
228
233
  > diagnostics-only plugin skips them entirely.
@@ -23,6 +23,8 @@ The JSON shape:
23
23
  {
24
24
  "summary": { "total": 489, "error": 480, "warning": 9, "info": 0 },
25
25
  "distribution": [ { "rule": "call.undefined-method", "count": 437 } ],
26
+ "selectors": [ { "receiver": "String", "method": "squish", "count": 31,
27
+ "files": 12, "rules": { "call.undefined-method": 31 } } ],
26
28
  "hotspots": [ { "file": "app/models/status.rb", "count": 42,
27
29
  "by_rule": { "call.undefined-method": 40 } } ],
28
30
  "hints": [
@@ -32,11 +34,26 @@ The JSON shape:
32
34
  }
33
35
  ```
34
36
 
35
- Use the three sections like this:
37
+ Use the sections like this:
36
38
 
37
39
  - **`summary` / `distribution`** — the scale, and which rules
38
40
  dominate. Decides nothing on its own; feeds the mode sanity-check
39
41
  (>100 errors → acknowledge mode is the right default).
42
+ - **`selectors`** — the by-(class, method) axis. Each row is a
43
+ dispatch target (`String#squish`) with its `count`, the `files` it
44
+ spans, and the `rules` that fired. Read it with `jq` to find the
45
+ *shape* of the problem before touching code — these are structured
46
+ fields, never parse the `message`:
47
+ ```sh
48
+ # the 10 methods responsible for the most diagnostics
49
+ rigor triage --format json | jq -r '.selectors[:10][] | "\(.count)\t\(.receiver)#\(.method)"'
50
+ # methods missing on the same receiver across many files = one config
51
+ # gap (an unloaded core-ext / unseen monkey-patch), not many bugs
52
+ rigor triage --format json | jq '.selectors[] | select(.files >= 5)'
53
+ ```
54
+ A high `count` + high `files` selector is almost always a *systemic
55
+ cause* (a plugin / `pre_eval:` fix clears it in bulk); a low `count`
56
+ selector is a candidate genuine bug.
40
57
  - **`hotspots`** — files carrying the most diagnostics. A single hot
41
58
  file is often one structural cause, not many bugs.
42
59
  - **`hints`** — the heuristic catalogue. Each hint names a *likely
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.18
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -289,6 +289,7 @@ files:
289
289
  - lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb
290
290
  - lib/rigor/analysis/check_rules/dead_assignment_collector.rb
291
291
  - lib/rigor/analysis/check_rules/ivar_write_collector.rb
292
+ - lib/rigor/analysis/check_rules/main_pass_collector.rb
292
293
  - lib/rigor/analysis/check_rules/rule_walk.rb
293
294
  - lib/rigor/analysis/check_rules/self_closedness_scanner.rb
294
295
  - lib/rigor/analysis/check_rules/unreachable_clause_collector.rb
@@ -318,8 +319,10 @@ files:
318
319
  - lib/rigor/analysis/worker_session.rb
319
320
  - lib/rigor/ast.rb
320
321
  - lib/rigor/ast/type_node.rb
322
+ - lib/rigor/bleeding_edge.rb
321
323
  - lib/rigor/builtins/hkt_builtins.rb
322
324
  - lib/rigor/builtins/imported_refinements.rb
325
+ - lib/rigor/builtins/predefined_constant_refinements.rb
323
326
  - lib/rigor/builtins/regex_refinement.rb
324
327
  - lib/rigor/builtins/static_return_refinements.rb
325
328
  - lib/rigor/cache/descriptor.rb
@@ -342,17 +345,23 @@ files:
342
345
  - lib/rigor/cli/coverage_command.rb
343
346
  - lib/rigor/cli/coverage_renderer.rb
344
347
  - lib/rigor/cli/coverage_report.rb
348
+ - lib/rigor/cli/coverage_scan.rb
345
349
  - lib/rigor/cli/diagnostic_formats.rb
346
350
  - lib/rigor/cli/diff_command.rb
347
351
  - lib/rigor/cli/explain_command.rb
348
352
  - lib/rigor/cli/lsp_command.rb
349
353
  - lib/rigor/cli/mcp_command.rb
354
+ - lib/rigor/cli/mutation_protection_renderer.rb
355
+ - lib/rigor/cli/mutation_protection_report.rb
350
356
  - lib/rigor/cli/options.rb
351
357
  - lib/rigor/cli/plugin_command.rb
352
358
  - lib/rigor/cli/plugins_command.rb
353
359
  - lib/rigor/cli/plugins_renderer.rb
354
360
  - lib/rigor/cli/prism_colorizer.rb
361
+ - lib/rigor/cli/protection_renderer.rb
362
+ - lib/rigor/cli/protection_report.rb
355
363
  - lib/rigor/cli/renderable.rb
364
+ - lib/rigor/cli/show_bleedingedge_command.rb
356
365
  - lib/rigor/cli/sig_gen_command.rb
357
366
  - lib/rigor/cli/skill_command.rb
358
367
  - lib/rigor/cli/trace_command.rb
@@ -370,6 +379,7 @@ files:
370
379
  - lib/rigor/environment.rb
371
380
  - lib/rigor/environment/bundle_sig_discovery.rb
372
381
  - lib/rigor/environment/class_registry.rb
382
+ - lib/rigor/environment/constant_type_cache_holder.rb
373
383
  - lib/rigor/environment/hkt_registry_holder.rb
374
384
  - lib/rigor/environment/lockfile_resolver.rb
375
385
  - lib/rigor/environment/rbs_collection_discovery.rb
@@ -386,6 +396,7 @@ files:
386
396
  - lib/rigor/flow_contribution/merger.rb
387
397
  - lib/rigor/inference/acceptance.rb
388
398
  - lib/rigor/inference/block_parameter_binder.rb
399
+ - lib/rigor/inference/body_fixpoint.rb
389
400
  - lib/rigor/inference/budget_trace.rb
390
401
  - lib/rigor/inference/builtins/array_catalog.rb
391
402
  - lib/rigor/inference/builtins/comparable_catalog.rb
@@ -421,6 +432,7 @@ files:
421
432
  - lib/rigor/inference/indexed_narrowing.rb
422
433
  - lib/rigor/inference/macro_block_self_type.rb
423
434
  - lib/rigor/inference/method_dispatcher.rb
435
+ - lib/rigor/inference/method_dispatcher/array_to_h_folding.rb
424
436
  - lib/rigor/inference/method_dispatcher/block_folding.rb
425
437
  - lib/rigor/inference/method_dispatcher/call_context.rb
426
438
  - lib/rigor/inference/method_dispatcher/cgi_folding.rb
@@ -431,27 +443,33 @@ files:
431
443
  - lib/rigor/inference/method_dispatcher/kernel_dispatch.rb
432
444
  - lib/rigor/inference/method_dispatcher/literal_string_folding.rb
433
445
  - lib/rigor/inference/method_dispatcher/math_folding.rb
446
+ - lib/rigor/inference/method_dispatcher/member_shape_projection.rb
434
447
  - lib/rigor/inference/method_dispatcher/method_folding.rb
435
448
  - lib/rigor/inference/method_dispatcher/overload_selector.rb
436
449
  - lib/rigor/inference/method_dispatcher/rbs_dispatch.rb
437
450
  - lib/rigor/inference/method_dispatcher/receiver_affinity.rb
451
+ - lib/rigor/inference/method_dispatcher/reduce_folding.rb
438
452
  - lib/rigor/inference/method_dispatcher/regexp_folding.rb
439
453
  - lib/rigor/inference/method_dispatcher/set_folding.rb
440
454
  - lib/rigor/inference/method_dispatcher/shape_dispatch.rb
441
455
  - lib/rigor/inference/method_dispatcher/shellwords_folding.rb
442
456
  - lib/rigor/inference/method_dispatcher/singleton_folding.rb
457
+ - lib/rigor/inference/method_dispatcher/struct_folding.rb
443
458
  - lib/rigor/inference/method_dispatcher/time_folding.rb
444
459
  - lib/rigor/inference/method_dispatcher/uri_folding.rb
445
460
  - lib/rigor/inference/method_parameter_binder.rb
446
461
  - lib/rigor/inference/multi_target_binder.rb
447
462
  - lib/rigor/inference/mutation_widening.rb
448
463
  - lib/rigor/inference/narrowing.rb
464
+ - lib/rigor/inference/parameter_inference_collector.rb
449
465
  - lib/rigor/inference/precision_scanner.rb
450
466
  - lib/rigor/inference/project_patched_methods.rb
451
467
  - lib/rigor/inference/project_patched_scanner.rb
468
+ - lib/rigor/inference/protection_scanner.rb
452
469
  - lib/rigor/inference/rbs_type_translator.rb
453
470
  - lib/rigor/inference/scope_indexer.rb
454
471
  - lib/rigor/inference/statement_evaluator.rb
472
+ - lib/rigor/inference/struct_fold_safety.rb
455
473
  - lib/rigor/inference/synthetic_method.rb
456
474
  - lib/rigor/inference/synthetic_method_index.rb
457
475
  - lib/rigor/inference/synthetic_method_scanner.rb
@@ -489,7 +507,6 @@ files:
489
507
  - lib/rigor/plugin/loader.rb
490
508
  - lib/rigor/plugin/macro.rb
491
509
  - lib/rigor/plugin/macro/block_as_method.rb
492
- - lib/rigor/plugin/macro/external_file.rb
493
510
  - lib/rigor/plugin/macro/heredoc_template.rb
494
511
  - lib/rigor/plugin/macro/nested_class_template.rb
495
512
  - lib/rigor/plugin/macro/trait_registry.rb
@@ -502,6 +519,8 @@ files:
502
519
  - lib/rigor/plugin/source_rbs_synthesis_reporter.rb
503
520
  - lib/rigor/plugin/trust_policy.rb
504
521
  - lib/rigor/plugin/type_node_resolver.rb
522
+ - lib/rigor/protection/mutation_scanner.rb
523
+ - lib/rigor/protection/mutator.rb
505
524
  - lib/rigor/rbs_extended.rb
506
525
  - lib/rigor/rbs_extended/conformance_checker.rb
507
526
  - lib/rigor/rbs_extended/hkt_directives.rb
@@ -522,6 +541,7 @@ files:
522
541
  - lib/rigor/sig_gen/write_result.rb
523
542
  - lib/rigor/sig_gen/writer.rb
524
543
  - lib/rigor/source.rb
544
+ - lib/rigor/source/constant_path.rb
525
545
  - lib/rigor/source/literals.rb
526
546
  - lib/rigor/source/node_locator.rb
527
547
  - lib/rigor/source/node_walker.rb
@@ -546,8 +566,11 @@ files:
546
566
  - lib/rigor/type/integer_range.rb
547
567
  - lib/rigor/type/intersection.rb
548
568
  - lib/rigor/type/nominal.rb
569
+ - lib/rigor/type/plain_lattice.rb
549
570
  - lib/rigor/type/refined.rb
550
571
  - lib/rigor/type/singleton.rb
572
+ - lib/rigor/type/struct_class.rb
573
+ - lib/rigor/type/struct_instance.rb
551
574
  - lib/rigor/type/top.rb
552
575
  - lib/rigor/type/tuple.rb
553
576
  - lib/rigor/type/union.rb
@@ -1,143 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rigor
4
- module Plugin
5
- module Macro
6
- # ADR-16 Tier D declaration: "files matching `glob` are
7
- # analysed as if their body were pasted at a call site whose
8
- # `self` is an instance of `receiver_type` (and whose `@ivar`
9
- # facts come from `bound_ivars`)."
10
- #
11
- # Worked motivating cases (per the per-library survey):
12
- #
13
- # - Redmine's `WebhookPayload#instance_eval(File.read(path), path, 1)`
14
- # at `app/models/webhook_payload.rb:71`. The payload templates
15
- # under `config/webhooks/*.rb` run with `self` typed as
16
- # `Redmine::WebhookPayload` and ivars like `@event` / `@issue`
17
- # / `@user` pre-bound by the caller.
18
- # - tDiary Core's plugin loader pattern — `misc/plugin/*.rb`
19
- # files loaded under `instance_eval` with the tDiary plugin
20
- # instance as `self`.
21
- #
22
- # ## Authoring shape
23
- #
24
- # manifest(
25
- # id: "redmine-webhook-payloads",
26
- # version: "0.1.0",
27
- # external_files: [
28
- # Rigor::Plugin::Macro::ExternalFile.new(
29
- # glob: "config/webhooks/*.rb",
30
- # receiver_type: "Redmine::WebhookPayload",
31
- # bound_ivars: {
32
- # "@event" => "Symbol",
33
- # "@issue" => "Issue?",
34
- # "@user" => "User"
35
- # }
36
- # )
37
- # ]
38
- # )
39
- #
40
- # ## Fields
41
- #
42
- # - `glob` — non-empty String pattern. Interpreted relative
43
- # to the project root (the directory containing `.rigor.yml`)
44
- # at scan time. Slice 5a accepts any non-empty glob
45
- # pattern syntactically; the engine integration (slice 5b)
46
- # pins the resolution rule.
47
- # - `receiver_type` — non-empty String. The class name `self`
48
- # inside the loaded file binds to. Engine integration (slice
49
- # 5b) narrows the file-entry scope's `self_type` to
50
- # `Nominal[receiver_type]`.
51
- # - `bound_ivars` — Hash<String, String>. Each key MUST start
52
- # with `@`; each value is a non-empty type-name String. The
53
- # engine pre-binds these as ivar facts in the file-entry
54
- # scope (slice 5b).
55
- #
56
- # ## Slice 5a scope
57
- #
58
- # **This file ships the value class + manifest hook ONLY.**
59
- # The engine integration that (a) adds matched files to the
60
- # analysis set, (b) narrows the file-entry `self_type`, and
61
- # (c) pre-binds `bound_ivars` as ivar facts is **queued for
62
- # slice 5b**, gated on demonstrated demand. The survey
63
- # identifies only Redmine + tDiary as concrete consumers;
64
- # premature engine work is deferred until those cases (or
65
- # equivalents) materialise as committed plugin targets.
66
- #
67
- # With only this slice landed, plugin authors CAN declare a
68
- # Tier D manifest entry today — the declaration round-trips
69
- # through `Manifest#to_h` (cache-key stable) and is exposed
70
- # on `Manifest#external_files` — but the substrate does not
71
- # yet act on it. The contract is forward-compatible: when
72
- # slice 5b lands, the engine reads the same declarations and
73
- # plugin gems do not need to change.
74
- class ExternalFile
75
- attr_reader :glob, :receiver_type, :bound_ivars
76
-
77
- def initialize(glob:, receiver_type:, bound_ivars: {})
78
- validate_glob!(glob)
79
- validate_receiver_type!(receiver_type)
80
- validate_bound_ivars!(bound_ivars)
81
-
82
- @glob = glob.dup.freeze
83
- @receiver_type = receiver_type.dup.freeze
84
- @bound_ivars = bound_ivars.to_h { |k, v| [k.dup.freeze, v.dup.freeze] }.freeze
85
- freeze
86
- end
87
-
88
- def to_h
89
- {
90
- "glob" => glob,
91
- "receiver_type" => receiver_type,
92
- "bound_ivars" => bound_ivars
93
- }
94
- end
95
-
96
- def ==(other)
97
- other.is_a?(ExternalFile) && to_h == other.to_h
98
- end
99
- alias eql? ==
100
-
101
- def hash
102
- to_h.hash
103
- end
104
-
105
- private
106
-
107
- def validate_glob!(value)
108
- return if value.is_a?(String) && !value.empty?
109
-
110
- raise ArgumentError,
111
- "Plugin::Macro::ExternalFile#glob must be a non-empty String, got #{value.inspect}"
112
- end
113
-
114
- def validate_receiver_type!(value)
115
- return if value.is_a?(String) && !value.empty?
116
-
117
- raise ArgumentError,
118
- "Plugin::Macro::ExternalFile#receiver_type must be a non-empty String, got #{value.inspect}"
119
- end
120
-
121
- def validate_bound_ivars!(value)
122
- unless value.is_a?(Hash)
123
- raise ArgumentError,
124
- "Plugin::Macro::ExternalFile#bound_ivars must be a Hash, got #{value.inspect}"
125
- end
126
-
127
- value.each do |k, v|
128
- unless k.is_a?(String) && k.start_with?("@") && k.length > 1
129
- raise ArgumentError,
130
- "Plugin::Macro::ExternalFile#bound_ivars key must be a String starting with `@`, " \
131
- "got #{k.inspect}"
132
- end
133
- next if v.is_a?(String) && !v.empty?
134
-
135
- raise ArgumentError,
136
- "Plugin::Macro::ExternalFile#bound_ivars value must be a non-empty String, " \
137
- "got #{v.inspect}"
138
- end
139
- end
140
- end
141
- end
142
- end
143
- end