rigortype 0.1.19 → 0.2.1

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 (197) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -6
  3. data/data/core_overlay/numeric.rbs +33 -0
  4. data/data/core_overlay/pathname.rbs +25 -0
  5. data/data/core_overlay/string_scanner.rbs +28 -0
  6. data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
  7. data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
  8. data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
  9. data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
  10. data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
  11. data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
  12. data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
  13. data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
  14. data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
  15. data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
  16. data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
  17. data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
  18. data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
  19. data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
  20. data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
  21. data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
  22. data/data/vendored_gem_sigs/redis/future.rbs +5 -0
  23. data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
  24. data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
  25. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
  26. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -23
  27. data/lib/rigor/analysis/check_rules/rule_walk.rb +3 -21
  28. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
  29. data/lib/rigor/analysis/check_rules.rb +492 -71
  30. data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
  31. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
  32. data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
  33. data/lib/rigor/analysis/fact_store.rb +5 -4
  34. data/lib/rigor/analysis/rule_catalog.rb +153 -6
  35. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +17 -17
  36. data/lib/rigor/analysis/runner/project_pre_passes.rb +9 -8
  37. data/lib/rigor/analysis/runner.rb +17 -6
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
  39. data/lib/rigor/analysis/worker_session.rb +10 -14
  40. data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
  41. data/lib/rigor/cache/store.rb +5 -3
  42. data/lib/rigor/cli/annotate_command.rb +28 -7
  43. data/lib/rigor/cli/baseline_command.rb +4 -3
  44. data/lib/rigor/cli/check_command.rb +138 -16
  45. data/lib/rigor/cli/coverage_command.rb +138 -31
  46. data/lib/rigor/cli/coverage_mutation.rb +149 -0
  47. data/lib/rigor/cli/coverage_scan.rb +57 -0
  48. data/lib/rigor/cli/explain_command.rb +2 -0
  49. data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
  50. data/lib/rigor/cli/fused_protection_report.rb +76 -0
  51. data/lib/rigor/cli/lsp_command.rb +3 -7
  52. data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
  53. data/lib/rigor/cli/mutation_protection_report.rb +73 -0
  54. data/lib/rigor/cli/options.rb +9 -0
  55. data/lib/rigor/cli/plugins_command.rb +2 -1
  56. data/lib/rigor/cli/protection_renderer.rb +63 -0
  57. data/lib/rigor/cli/protection_report.rb +68 -0
  58. data/lib/rigor/cli/sig_gen_command.rb +2 -1
  59. data/lib/rigor/cli/trace_command.rb +2 -1
  60. data/lib/rigor/cli/triage_command.rb +2 -1
  61. data/lib/rigor/cli/type_of_command.rb +1 -1
  62. data/lib/rigor/cli/type_scan_command.rb +2 -1
  63. data/lib/rigor/cli.rb +3 -2
  64. data/lib/rigor/config_audit.rb +152 -0
  65. data/lib/rigor/configuration/dependencies.rb +2 -4
  66. data/lib/rigor/configuration.rb +57 -7
  67. data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
  68. data/lib/rigor/environment/class_registry.rb +4 -3
  69. data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
  70. data/lib/rigor/environment/lockfile_resolver.rb +1 -1
  71. data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
  72. data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
  73. data/lib/rigor/environment/rbs_loader.rb +76 -5
  74. data/lib/rigor/environment.rb +66 -8
  75. data/lib/rigor/flow_contribution/fact.rb +1 -1
  76. data/lib/rigor/flow_contribution.rb +3 -5
  77. data/lib/rigor/inference/acceptance.rb +17 -9
  78. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  79. data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
  80. data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
  81. data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
  82. data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
  83. data/lib/rigor/inference/expression_typer.rb +20 -28
  84. data/lib/rigor/inference/hkt_body.rb +8 -11
  85. data/lib/rigor/inference/hkt_body_parser.rb +10 -12
  86. data/lib/rigor/inference/hkt_registry.rb +10 -11
  87. data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
  88. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +169 -24
  89. data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
  90. data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
  91. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
  92. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
  93. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
  94. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
  95. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
  96. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +90 -15
  97. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
  98. data/lib/rigor/inference/method_dispatcher.rb +40 -48
  99. data/lib/rigor/inference/mutation_widening.rb +5 -11
  100. data/lib/rigor/inference/narrowing.rb +14 -16
  101. data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
  102. data/lib/rigor/inference/project_patched_methods.rb +4 -7
  103. data/lib/rigor/inference/project_patched_scanner.rb +2 -13
  104. data/lib/rigor/inference/protection_scanner.rb +86 -0
  105. data/lib/rigor/inference/scope_indexer.rb +129 -55
  106. data/lib/rigor/inference/statement_evaluator.rb +271 -114
  107. data/lib/rigor/inference/struct_fold_safety.rb +181 -0
  108. data/lib/rigor/inference/synthetic_method.rb +7 -7
  109. data/lib/rigor/language_server/completion_provider.rb +6 -12
  110. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  111. data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
  112. data/lib/rigor/language_server/hover_provider.rb +2 -3
  113. data/lib/rigor/language_server/hover_renderer.rb +2 -11
  114. data/lib/rigor/language_server/server.rb +9 -17
  115. data/lib/rigor/language_server.rb +4 -5
  116. data/lib/rigor/plugin/base.rb +10 -8
  117. data/lib/rigor/plugin/macro/block_as_method.rb +3 -4
  118. data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
  119. data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
  120. data/lib/rigor/plugin/macro.rb +4 -5
  121. data/lib/rigor/plugin/manifest.rb +45 -66
  122. data/lib/rigor/plugin/registry.rb +6 -7
  123. data/lib/rigor/plugin/type_node_resolver.rb +6 -8
  124. data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
  125. data/lib/rigor/protection/mutation_scanner.rb +180 -0
  126. data/lib/rigor/protection/mutator.rb +267 -0
  127. data/lib/rigor/protection/test_suite_oracle.rb +68 -0
  128. data/lib/rigor/rbs_extended.rb +24 -36
  129. data/lib/rigor/reflection.rb +4 -7
  130. data/lib/rigor/scope/discovery_index.rb +14 -2
  131. data/lib/rigor/scope.rb +54 -11
  132. data/lib/rigor/sig_gen/observed_call.rb +3 -3
  133. data/lib/rigor/sig_gen/writer.rb +40 -2
  134. data/lib/rigor/signature_path_audit.rb +92 -0
  135. data/lib/rigor/source/constant_path.rb +62 -0
  136. data/lib/rigor/source.rb +1 -0
  137. data/lib/rigor/type/bound_method.rb +2 -11
  138. data/lib/rigor/type/combinator.rb +16 -3
  139. data/lib/rigor/type/constant.rb +2 -11
  140. data/lib/rigor/type/data_class.rb +2 -11
  141. data/lib/rigor/type/data_instance.rb +2 -11
  142. data/lib/rigor/type/hash_shape.rb +2 -11
  143. data/lib/rigor/type/integer_range.rb +2 -11
  144. data/lib/rigor/type/intersection.rb +2 -11
  145. data/lib/rigor/type/nominal.rb +2 -11
  146. data/lib/rigor/type/plain_lattice.rb +37 -0
  147. data/lib/rigor/type/refined.rb +72 -13
  148. data/lib/rigor/type/singleton.rb +2 -11
  149. data/lib/rigor/type/struct_class.rb +75 -0
  150. data/lib/rigor/type/struct_instance.rb +93 -0
  151. data/lib/rigor/type/tuple.rb +5 -15
  152. data/lib/rigor/type.rb +2 -0
  153. data/lib/rigor/version.rb +1 -1
  154. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  155. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
  156. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +3 -3
  157. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
  158. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
  159. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +7 -10
  160. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
  161. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
  162. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +6 -8
  163. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
  164. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -2
  165. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
  166. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
  167. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
  168. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
  169. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
  170. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
  171. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
  172. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +7 -9
  173. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
  174. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
  175. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +3 -3
  176. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
  177. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
  178. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
  179. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -1
  180. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
  181. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
  182. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +5 -5
  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 +19 -14
  187. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
  188. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
  189. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
  190. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
  191. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
  192. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
  193. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
  194. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +28 -41
  195. data/sig/rigor/scope.rbs +9 -1
  196. data/sig/rigor/type.rbs +36 -1
  197. metadata +49 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e2fede9ea7407238b1f9c8f5784cc43f22724ee86decdf81f4a148c4ddff763
4
- data.tar.gz: 57e9757b838f29185d9b936ddff586d372126540afe12cefb1e98dbc7e0a6109
3
+ metadata.gz: 11db7e9c0140dbb303b0644587e80cc683e5a9ee32791c007234dd12e37ecb21
4
+ data.tar.gz: 375cbd7387b2f8cac6d668db993b34e627d19ebacf74e5092c17c8717191b401
5
5
  SHA512:
6
- metadata.gz: 33b889562fefed7516dec175b7d85cc9f725ebcc7d94df30b5a58a436322709517a02bda2a0de10d746004c8c2d198aaa673f12577ddfe3ffeb37405508e1d9c
7
- data.tar.gz: f5773b727c0fb718df1ab1163add87f0dd7e94913fefd0599e7465e5a40afbfa201b2b14a09eb524b2e98dc6e30bd84ab0424355802ded977386f5a706d84837
6
+ metadata.gz: 42a9121e67acbf27afc287c586c7b3cd9fbd271a0de9e0a7f28a1e57703ffb578572213402e37f57b7327572c944f799a186ccc0a13dbe1fba49eca02c39df6b
7
+ data.tar.gz: d5b333586309d0b25b17880608797e005a2083bad60d91e2a20163265cb31b948a08363ef4f37920862dc7ee5da3fb0cd1d7c67a7af6f64b1bc37d8721469f6c
data/README.md CHANGED
@@ -204,12 +204,15 @@ the reference companions.
204
204
 
205
205
  ## Status
206
206
 
207
- Current release: **`v0.1.18`** (2026-06-11), with `v0.2.0` — the first
208
- evaluation release landing shortly. Rigor analyses real Ruby today:
209
- the `0.1.x` line has been hardened against Mastodon, Redmine, and
210
- GitLab FOSS, and the deliberately conservative rule catalogue grows
211
- only as fast as the zero-false-positive bar allows. Release history:
212
- [`CHANGELOG.md`](CHANGELOG.md) · forward-looking commitments:
207
+ Current release: **`v0.2.0`** (2026-06-17) — the first
208
+ publicly-announced (general / evaluation) release. It publishes an
209
+ enumerated [compatibility surface](docs/compatibility.md) as a
210
+ minor-non-break trial, rehearsing the contract that hard-freezes at
211
+ `v1.0.0`. Rigor analyses real Ruby today: it has been hardened against
212
+ Mastodon, Redmine, and GitLab FOSS, and the deliberately conservative
213
+ rule catalogue grows only as fast as the zero-false-positive bar
214
+ allows. Release history: [`CHANGELOG.md`](CHANGELOG.md) ·
215
+ forward-looking commitments:
213
216
  [Roadmap](https://rigor.typedduck.fail/reference/roadmap/).
214
217
 
215
218
  ## Contributing
@@ -221,3 +224,35 @@ contributors should read.
221
224
  ## License
222
225
 
223
226
  Mozilla Public License Version 2.0. See [`LICENSE`](LICENSE).
227
+
228
+ ## Sponsors
229
+
230
+ Rigor is built and maintained by one person, who has spent the better
231
+ part of the last month — nearly every spare hour of personal time — on
232
+ this project. Sustainable development depends on sustainable funding.
233
+
234
+ **The current monthly budget is well short of the $200 minimum needed
235
+ to keep this pace going; $600 or more would let me work on it
236
+ properly.** If Rigor is saving you time or catching real bugs, please
237
+ consider contributing:
238
+
239
+ - **GitHub Sponsors**: [github.com/sponsors/zonuexe](https://github.com/sponsors/zonuexe)
240
+ - **FANBOX** (credit-card / PayPal): [tadsan.fanbox.cc](https://tadsan.fanbox.cc/)
241
+
242
+ **For individuals** — any amount is welcome and genuinely appreciated.
243
+ Even a small recurring pledge (think of it as buying me one coffee a
244
+ month) makes a difference when it adds up across many users.
245
+
246
+ **For companies** using Rigor in production — please consider a
247
+ contribution that reflects the engineering hours Rigor is saving you.
248
+ A modest business sponsorship goes a long way toward keeping this
249
+ project alive and actively maintained.
250
+
251
+ **Sponsors at $50/month or more** are listed here with a banner.
252
+ *(This threshold will likely move to $100/month at some point — early
253
+ sponsors lock in the lower bar.)*
254
+
255
+ To the sponsors who have already contributed: **thank you sincerely.**
256
+ Your support makes the difference between a project that stalls and
257
+ one that ships.
258
+
@@ -0,0 +1,33 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # This directory holds Rigor-owned reopenings of Ruby core classes that
4
+ # add methods upstream `ruby/rbs` does not declare on a class but which
5
+ # every concrete value of that class answers at runtime. It is loaded
6
+ # LAST into the RBS environment (after core + stdlib + vendored gem
7
+ # sigs), so an upstream declaration always wins on conflict — these
8
+ # entries only fill genuine holes.
9
+ #
10
+ # Provenance: hand-authored against the documented CRuby behaviour, not
11
+ # extracted. Keep each addition justified by an observed false positive.
12
+ #
13
+ # --- Numeric coercion methods ---
14
+ #
15
+ # `ruby/rbs`'s `Numeric` declares only `to_c` and `to_int`; the three
16
+ # value-coercion selectors `to_f` / `to_i` / `to_r` are declared solely
17
+ # on the concrete subclasses (`Integer`, `Float`, `Rational`, `Complex`).
18
+ # But Rigor routinely *widens* an arithmetic chain to the abstract
19
+ # `Numeric` nominal — `Complex#abs -> Numeric`, or an overload join over
20
+ # `Integer | Float` — and the instant a widened value is asked for
21
+ # `.to_f`, dispatch misses and `call.undefined-method` fires on plain,
22
+ # correct arithmetic (observed four times across actionview's
23
+ # `date_helper`: `(distance_in_minutes.to_f / 60.0)`).
24
+ #
25
+ # Every concrete `Numeric` responds to all three, so the widening must
26
+ # not strip the capability. Returns mirror the concrete subclass
27
+ # signatures. `to_int` is already upstream and intentionally not
28
+ # duplicated here.
29
+ class Numeric
30
+ def to_f: () -> Float
31
+ def to_i: () -> Integer
32
+ def to_r: () -> Rational
33
+ end
@@ -0,0 +1,25 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # --- Pathname#expand_path ---
4
+ #
5
+ # Upstream `ruby/rbs` declares `Pathname#expand_path` as:
6
+ #
7
+ # def expand_path: (?String dir) -> Pathname
8
+ #
9
+ # But `Pathname#expand_path` delegates to `File.expand_path`, whose `dir`
10
+ # parameter is typed as `path` (= `string | _ToPath`). Passing a `Pathname`
11
+ # as the base directory is the idiomatic Bundler pattern:
12
+ #
13
+ # Pathname.new(path).expand_path(Bundler.root) # root → Pathname
14
+ #
15
+ # With the ADR-57 self-call adoption gate now resolving `Bundler.root` to
16
+ # `Pathname`, all five call sites in bundler.rb / bundler/source/git.rb fire
17
+ # `argument type mismatch at parameter 'dir'`. The upstream sig is simply
18
+ # under-typed: the runtime accepts any `to_path`-bearing object.
19
+ #
20
+ # The overlay widens `dir` to `String | Pathname`, matching the runtime
21
+ # delegation and the `File.expand_path` `path` alias used elsewhere in RBS.
22
+ class Pathname
23
+ def expand_path: (?String | Pathname dir) -> Pathname
24
+ | ...
25
+ end
@@ -0,0 +1,28 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # --- StringScanner#[] ---
4
+ #
5
+ # The pinned `rbs` gem (4.0.2) declares `StringScanner#[]` as only:
6
+ #
7
+ # def []: (Integer) -> String?
8
+ #
9
+ # But `StringScanner#[]` also accepts a capture-group *name* — a `String`
10
+ # or `Symbol` — and returns the named capture from the most recent match.
11
+ # Named-capture access (`scanner[:key]`) is the idiomatic form when the
12
+ # scan pattern uses named groups:
13
+ #
14
+ # scanner.skip(/(?<key>\w+)=/)
15
+ # scanner[:key] # => the captured String, or nil
16
+ #
17
+ # Newer `ruby/rbs` already widens the signature to
18
+ # `(Integer | String | Symbol) -> String?`; the pinned 4.0.2 gem lags.
19
+ # Without the wider overload, a named-capture access such as Mastodon's
20
+ # `scanner[:key]` false-fires `call.argument-type-mismatch` (the lone
21
+ # overload rejects a `Symbol`).
22
+ #
23
+ # The overlay supplies the runtime-accurate overload; drop it once the
24
+ # pinned `rbs` gem carries the wider signature.
25
+ class StringScanner
26
+ def []: (Integer | String | Symbol) -> String?
27
+ | ...
28
+ end
@@ -0,0 +1,473 @@
1
+ # ActiveSupport core_ext — Gemfile.lock-gated bundled RBS overlay (ADR-72).
2
+ #
3
+ # Rigor loads this overlay AUTOMATICALLY when `activesupport` is locked
4
+ # in the project's Gemfile.lock but ships no RBS through any resolution
5
+ # path (the `:missing` coverage class — no `rbs collection`, no bundled
6
+ # `sig/`, not a default library). It turns the systematic false
7
+ # `call.undefined-method` on Rails core-ext call sites (`3.minutes`,
8
+ # `"x".underscore`, `hash.symbolize_keys`, …) into a no-op, while a
9
+ # project WITHOUT activesupport still sees the genuine diagnostic.
10
+ #
11
+ # This is the auto-applied twin of the opt-in, hand-curated
12
+ # `rigor-activesupport-core-ext` plugin (`plugins/rigor-activesupport-
13
+ # core-ext/sig/active_support/core_ext.rbs`). The two files carry the
14
+ # same surface on purpose; the plugin remains the authoring home and
15
+ # may grow more exhaustive, and when its plugin id is loaded this
16
+ # overlay stands down (Environment.for_project) to avoid a duplicate
17
+ # declaration. Keep the two in sync when extending coverage.
18
+ #
19
+ # Scope: the most-frequently-flagged ActiveSupport extension methods
20
+ # from a four-project Rails survey (Redmine, Discourse, Mastodon,
21
+ # GitLab FOSS — see `docs/notes/20260515-real-world-rails-survey.md`),
22
+ # roughly the top ~40 selectors by `call.undefined-method` frequency.
23
+ # Not exhaustive.
24
+
25
+ # ---------------------------------------------------------------
26
+ # Object — universal predicates and helpers
27
+ # ---------------------------------------------------------------
28
+
29
+ class Object
30
+ # @return [bool]
31
+ def blank?: () -> bool
32
+
33
+ # @return [bool]
34
+ def present?: () -> bool
35
+
36
+ # @return [self?]
37
+ def presence: () -> self?
38
+
39
+ # `active_support/core_ext/object/json` monkey-patches
40
+ # `as_json` onto Object (default returns the object's
41
+ # `instance_values` hash) plus per-class overrides on
42
+ # NilClass / TrueClass / FalseClass / Numeric / String /
43
+ # Symbol / Time / Date / Array / Hash / Range / Regexp /
44
+ # Struct / Enumerable. Most call-site code uses the
45
+ # universal `obj.as_json` shape so a single Object row
46
+ # restores type coverage at the Mastodon-derived
47
+ # `as_json` call sites (`fan_out_on_write_service.rb`,
48
+ # `push_update_worker.rb`, `media_component_helper.rb`).
49
+ # `options` is the standard `only:` / `except:` / `methods:`
50
+ # / `include:` hash — left as untyped because the surface
51
+ # is large and per-call-site precision is rarely useful.
52
+ def as_json: (?untyped options) -> untyped
53
+
54
+ # `try` swallows NoMethodError; declared return is `untyped`
55
+ # because the called method's signature is not known to RBS
56
+ # at this call site.
57
+ def try: (Symbol | String) -> untyped
58
+ | (Symbol | String, *untyped) -> untyped
59
+ | (Symbol | String, *untyped) { (?) -> untyped } -> untyped
60
+
61
+ def try!: (Symbol | String) -> untyped
62
+ | (Symbol | String, *untyped) -> untyped
63
+ | (Symbol | String, *untyped) { (?) -> untyped } -> untyped
64
+
65
+ # `acts_like?(:string)` / `acts_like?(:date)` / `acts_like?(:time)`
66
+ # is ActiveSupport's "duck-typing helper" predicate.
67
+ def acts_like?: (Symbol | String) -> bool
68
+ end
69
+
70
+ # `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
71
+ # call sites the survey turned up.
72
+ class NilClass
73
+ def blank?: () -> true
74
+ def present?: () -> false
75
+ def presence: () -> nil
76
+ def try: (*untyped) -> nil
77
+ def try!: (*untyped) -> nil
78
+ end
79
+
80
+ class TrueClass
81
+ def blank?: () -> false
82
+ def present?: () -> true
83
+ end
84
+
85
+ class FalseClass
86
+ def blank?: () -> true
87
+ def present?: () -> false
88
+ end
89
+
90
+ # ---------------------------------------------------------------
91
+ # String — ActiveSupport `core_ext/string/*`
92
+ # ---------------------------------------------------------------
93
+
94
+ class String
95
+ # `core_ext/string/inflections`
96
+ def underscore: () -> String
97
+ def camelize: (?Symbol upper_case_first) -> String
98
+ def camelcase: (?Symbol upper_case_first) -> String
99
+ def classify: () -> String
100
+ def constantize: () -> untyped
101
+ def safe_constantize: () -> untyped
102
+ def demodulize: () -> String
103
+ def deconstantize: () -> String
104
+ def titleize: () -> String
105
+ def parameterize: (?separator: String, ?preserve_case: bool, ?locale: Symbol?) -> String
106
+ def tableize: () -> String
107
+ def foreign_key: (?bool separate_class_name_and_id_with_underscore) -> String
108
+ def pluralize: (?Integer count, ?Symbol locale) -> String
109
+ def singularize: (?Symbol locale) -> String
110
+ def humanize: (?capitalize: bool, ?keep_id_suffix: bool) -> String
111
+
112
+ # `core_ext/string/filters`
113
+ def squish: () -> String
114
+ def squish!: () -> String
115
+ def truncate: (Integer truncate_at, ?omission: String, ?separator: String | Regexp | nil) -> String
116
+ def truncate_bytes: (Integer truncate_at, ?omission: String) -> String
117
+ def truncate_words: (Integer words_count, ?omission: String, ?separator: String | Regexp | nil) -> String
118
+
119
+ # `core_ext/string/output_safety` — `html_safe` returns an
120
+ # `ActiveSupport::SafeBuffer` (a String subclass). The closest
121
+ # RBS-canonical carrier is plain `String`; precision lost is the
122
+ # `html_safe?` predicate value.
123
+ def html_safe: () -> String
124
+ def html_safe?: () -> bool
125
+
126
+ # `core_ext/string/indent`
127
+ def indent: (Integer amount, ?String indent_string, ?bool indent_empty_lines) -> String
128
+ def indent!: (Integer amount, ?String indent_string, ?bool indent_empty_lines) -> String?
129
+
130
+ # `core_ext/string/starts_ends_with` — also covered by core
131
+ # `start_with?` / `end_with?`; these are the ActiveSupport aliases.
132
+ def starts_with?: (*String prefixes) -> bool
133
+ def ends_with?: (*String suffixes) -> bool
134
+
135
+ # `core_ext/string/conversions` — `to_time` / `to_date` /
136
+ # `to_datetime` return Time / Date / DateTime.
137
+ def to_time: (?Symbol form) -> Time?
138
+ def to_date: () -> Date?
139
+ def to_datetime: () -> DateTime
140
+ def to_hours: () -> Float
141
+
142
+ # `core_ext/string/access`
143
+ def at: (Integer | Range[Integer] | Regexp position) -> String?
144
+ def from: (Integer position) -> String
145
+ def to: (Integer position) -> String
146
+ def first: (?Integer limit) -> String
147
+ def last: (?Integer limit) -> String
148
+
149
+ # `core_ext/string/strip` — leading-blank strip with shared margin.
150
+ def strip_heredoc: () -> String
151
+
152
+ # `core_ext/string/multibyte`
153
+ def mb_chars: () -> untyped
154
+
155
+ # `core_ext/string/inquiry`
156
+ def inquiry: () -> untyped # ActiveSupport::StringInquirer
157
+
158
+ # `core_ext/string/exclude` (an ActiveSupport addition)
159
+ def exclude?: (String) -> bool
160
+ end
161
+
162
+ # ---------------------------------------------------------------
163
+ # Integer — `core_ext/integer/*`
164
+ # ---------------------------------------------------------------
165
+
166
+ class Integer
167
+ # `core_ext/numeric/time` — Duration multipliers.
168
+ def second: () -> untyped # ActiveSupport::Duration
169
+ def seconds: () -> untyped
170
+ def minute: () -> untyped
171
+ def minutes: () -> untyped
172
+ def hour: () -> untyped
173
+ def hours: () -> untyped
174
+ def day: () -> untyped
175
+ def days: () -> untyped
176
+ def week: () -> untyped
177
+ def weeks: () -> untyped
178
+ def fortnight: () -> untyped
179
+ def fortnights: () -> untyped
180
+ def month: () -> untyped
181
+ def months: () -> untyped
182
+ def year: () -> untyped
183
+ def years: () -> untyped
184
+
185
+ # `core_ext/numeric/bytes`
186
+ def byte: () -> Integer
187
+ def bytes: () -> Integer
188
+ def kilobyte: () -> Integer
189
+ def kilobytes: () -> Integer
190
+ def megabyte: () -> Integer
191
+ def megabytes: () -> Integer
192
+ def gigabyte: () -> Integer
193
+ def gigabytes: () -> Integer
194
+ def terabyte: () -> Integer
195
+ def terabytes: () -> Integer
196
+ def petabyte: () -> Integer
197
+ def petabytes: () -> Integer
198
+ def exabyte: () -> Integer
199
+ def exabytes: () -> Integer
200
+
201
+ # `core_ext/integer/multiple`
202
+ def multiple_of?: (Integer) -> bool
203
+
204
+ # `core_ext/integer/inflections`
205
+ def ordinal: () -> String
206
+ def ordinalize: () -> String
207
+ end
208
+
209
+ # ---------------------------------------------------------------
210
+ # Float — same Duration / Bytes pattern as Integer
211
+ # ---------------------------------------------------------------
212
+
213
+ class Float
214
+ def second: () -> untyped
215
+ def seconds: () -> untyped
216
+ def minute: () -> untyped
217
+ def minutes: () -> untyped
218
+ def hour: () -> untyped
219
+ def hours: () -> untyped
220
+ def day: () -> untyped
221
+ def days: () -> untyped
222
+ def week: () -> untyped
223
+ def weeks: () -> untyped
224
+ def month: () -> untyped
225
+ def months: () -> untyped
226
+ def year: () -> untyped
227
+ def years: () -> untyped
228
+
229
+ def byte: () -> Float
230
+ def bytes: () -> Float
231
+ def kilobyte: () -> Float
232
+ def kilobytes: () -> Float
233
+ def megabyte: () -> Float
234
+ def megabytes: () -> Float
235
+ def gigabyte: () -> Float
236
+ def gigabytes: () -> Float
237
+ end
238
+
239
+ # ---------------------------------------------------------------
240
+ # Time — singleton + instance extensions
241
+ # ---------------------------------------------------------------
242
+
243
+ class Time
244
+ def self.current: () -> Time
245
+ def self.zone: () -> untyped # ActiveSupport::TimeZone | nil
246
+ def self.zone=: (String | Symbol | untyped) -> untyped
247
+
248
+ # `core_ext/time/conversions` — also `Time.parse` from stdlib
249
+ # `time`, which is already in core RBS.
250
+ def self.httpdate: (String) -> Time
251
+ def self.iso8601: (String) -> Time
252
+
253
+ # `core_ext/time/calculations`
254
+ def yesterday: () -> Time
255
+ def tomorrow: () -> Time
256
+ def beginning_of_day: () -> Time
257
+ def end_of_day: () -> Time
258
+ def beginning_of_hour: () -> Time
259
+ def end_of_hour: () -> Time
260
+ def beginning_of_minute: () -> Time
261
+ def end_of_minute: () -> Time
262
+ def beginning_of_week: (?Symbol start_day) -> Time
263
+ def end_of_week: (?Symbol start_day) -> Time
264
+ def beginning_of_month: () -> Time
265
+ def end_of_month: () -> Time
266
+ def beginning_of_year: () -> Time
267
+ def end_of_year: () -> Time
268
+ def ago: (Numeric seconds) -> Time
269
+ def since: (Numeric seconds) -> Time
270
+ def in: (Numeric seconds) -> Time
271
+ def change: (**untyped) -> Time
272
+ def at_beginning_of_day: () -> Time
273
+ def at_end_of_day: () -> Time
274
+ def at_beginning_of_week: () -> Time
275
+ def at_end_of_week: () -> Time
276
+ def at_midnight: () -> Time
277
+ def at_noon: () -> Time
278
+ def midday: () -> Time
279
+ def midnight: () -> Time
280
+ def noon: () -> Time
281
+ def utc?: () -> bool
282
+ def acts_like_time?: () -> true
283
+ end
284
+
285
+ # ---------------------------------------------------------------
286
+ # Date — `core_ext/date/*`
287
+ # ---------------------------------------------------------------
288
+
289
+ class Date
290
+ def self.current: () -> Date
291
+ def self.yesterday: () -> Date
292
+ def self.tomorrow: () -> Date
293
+ def self.beginning_of_week: () -> Date
294
+ def self.end_of_week: () -> Date
295
+ def self.beginning_of_month: () -> Date
296
+ def self.end_of_month: () -> Date
297
+ def self.beginning_of_year: () -> Date
298
+ def self.end_of_year: () -> Date
299
+
300
+ def yesterday: () -> Date
301
+ def tomorrow: () -> Date
302
+ def beginning_of_week: (?Symbol start_day) -> Date
303
+ def end_of_week: (?Symbol start_day) -> Date
304
+ def beginning_of_month: () -> Date
305
+ def end_of_month: () -> Date
306
+ def beginning_of_year: () -> Date
307
+ def end_of_year: () -> Date
308
+ def ago: (Numeric seconds) -> Time
309
+ def since: (Numeric seconds) -> Time
310
+ def acts_like_date?: () -> true
311
+
312
+ # `core_ext/date/calculations` — `Date#midnight` /
313
+ # `Date#at_midnight` are aliases for `beginning_of_day`.
314
+ # Rails' `beginning_of_day` on Date returns a Time at
315
+ # midnight of that day (not a Date). Mastodon's
316
+ # `Date.current.at_midnight` shape relies on this.
317
+ def beginning_of_day: () -> Time
318
+ def midnight: () -> Time
319
+ def at_midnight: () -> Time
320
+ def at_beginning_of_day: () -> Time
321
+ def end_of_day: () -> Time
322
+ def at_end_of_day: () -> Time
323
+ end
324
+
325
+ # ---------------------------------------------------------------
326
+ # Array — singleton wrapper + instance extensions
327
+ # ---------------------------------------------------------------
328
+
329
+ class Array[unchecked out Elem]
330
+ # `Array.wrap(x)` is the dominant Rails idiom: `nil → []`,
331
+ # `Array x → x`, `else → [x]`.
332
+ def self.wrap: (untyped) -> Array[untyped]
333
+
334
+ # `core_ext/array/access`
335
+ def from: (Integer position) -> Array[Elem]
336
+ def to: (Integer position) -> Array[Elem]
337
+ def second: () -> Elem?
338
+ def third: () -> Elem?
339
+ def fourth: () -> Elem?
340
+ def fifth: () -> Elem?
341
+ def forty_two: () -> Elem?
342
+
343
+ # `core_ext/array/grouping`
344
+ def in_groups_of: (Integer number, ?untyped fill_with) -> Array[Array[Elem]]
345
+ | (Integer number, ?untyped fill_with) { (Array[Elem]) -> void } -> Array[Elem]
346
+ def in_groups: (Integer number, ?untyped fill_with) -> Array[Array[Elem]]
347
+ | (Integer number, ?untyped fill_with) { (Array[Elem]) -> void } -> Array[Elem]
348
+ def split: (?untyped value) -> Array[Array[Elem]]
349
+ | () { (Elem) -> bool } -> Array[Array[Elem]]
350
+
351
+ # `core_ext/array/conversions`
352
+ def to_sentence: (?two_words_connector: String, ?last_word_connector: String, ?words_connector: String, ?locale: Symbol?) -> String
353
+ def to_formatted_s: (?Symbol format) -> String
354
+ def to_fs: (?Symbol format) -> String
355
+ def to_xml: (**untyped) -> String
356
+
357
+ # `core_ext/array/inquiry`
358
+ def inquiry: () -> untyped # ActiveSupport::ArrayInquirer
359
+
360
+ # `core_ext/array/extract`
361
+ def extract!: () { (Elem) -> bool } -> Array[Elem]
362
+
363
+ # `core_ext/object/blank` / `core_ext/enumerable` — both
364
+ # ActiveSupport additions, shipped on Array specifically.
365
+ def compact_blank: () -> Array[Elem]
366
+ def compact_blank!: () -> self
367
+ def exclude?: (Elem) -> bool
368
+ end
369
+
370
+ # ---------------------------------------------------------------
371
+ # Enumerable — `core_ext/enumerable`
372
+ # ---------------------------------------------------------------
373
+
374
+ module Enumerable[unchecked out Elem]
375
+ def index_by: () { (Elem) -> untyped } -> Hash[untyped, Elem]
376
+ def index_with: (?untyped default) { (Elem) -> untyped } -> Hash[Elem, untyped]
377
+ | (untyped default) -> Hash[Elem, untyped]
378
+ def exclude?: (Elem) -> bool
379
+ def including: (*Elem) -> Array[Elem]
380
+ def excluding: (*Elem) -> Array[Elem]
381
+ def without: (*Elem) -> Array[Elem]
382
+ def pluck: (Symbol | String) -> Array[untyped]
383
+ | (*Symbol | String) -> Array[Array[untyped]]
384
+ def pick: (Symbol | String) -> untyped
385
+ | (*Symbol | String) -> Array[untyped]?
386
+ def maximum: (Symbol | String) -> untyped
387
+ def minimum: (Symbol | String) -> untyped
388
+ def sole: () -> Elem
389
+ def compact_blank: () -> Array[Elem]
390
+ end
391
+
392
+ # ---------------------------------------------------------------
393
+ # Hash — `core_ext/hash/*`
394
+ # ---------------------------------------------------------------
395
+
396
+ class Hash[unchecked out K, unchecked out V]
397
+ # `core_ext/hash/keys`
398
+ def symbolize_keys: () -> Hash[Symbol, V]
399
+ def symbolize_keys!: () -> self
400
+ def deep_symbolize_keys: () -> Hash[untyped, untyped]
401
+ def deep_symbolize_keys!: () -> self
402
+ def stringify_keys: () -> Hash[String, V]
403
+ def stringify_keys!: () -> self
404
+ def deep_stringify_keys: () -> Hash[untyped, untyped]
405
+ def deep_stringify_keys!: () -> self
406
+ def assert_valid_keys: (*K | Array[K]) -> self
407
+ def deep_transform_keys: () { (K) -> K } -> Hash[K, untyped]
408
+ def deep_transform_keys!: () { (K) -> K } -> self
409
+ def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
410
+ def deep_transform_values!: () { (V) -> untyped } -> self
411
+
412
+ # `core_ext/hash/deep_dup`
413
+ def deep_dup: () -> Hash[K, V]
414
+
415
+ # `core_ext/hash/deep_merge`
416
+ def deep_merge: (Hash[K, V]) -> Hash[K, V]
417
+ | (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
418
+ def deep_merge!: (Hash[K, V]) -> self
419
+ | (Hash[K, V]) { (K, V, V) -> V } -> self
420
+
421
+ # `core_ext/hash/except` — `Hash#except` is in core RBS as of
422
+ # Ruby 3.0+; `except!` is ActiveSupport-only. ActiveSupport
423
+ # also aliases `Hash#without` to `Hash#except`, used by
424
+ # `Mastodon`-shaped `options.without('type').merge(...)` chains.
425
+ def except!: (*K) -> self
426
+ def without: (*K) -> Hash[K, V]
427
+
428
+ # `core_ext/hash/conversions`
429
+ def to_query: (?String namespace) -> String
430
+ def to_param: (?String namespace) -> String
431
+ def to_xml: (**untyped) -> String
432
+
433
+ # `core_ext/hash/indifferent_access`
434
+ def with_indifferent_access: () -> untyped
435
+
436
+ # `core_ext/hash/conversions`
437
+ def self.from_xml: (String, ?Symbol disallowed_types) -> Hash[String, untyped]
438
+ def self.from_trusted_xml: (String) -> Hash[String, untyped]
439
+
440
+ # `core_ext/object/blank` / `core_ext/hash`
441
+ def compact_blank: () -> Hash[K, V]
442
+ def compact_blank!: () -> self
443
+
444
+ # `core_ext/hash/reverse_merge`
445
+ def reverse_merge: (Hash[K, V]) -> Hash[K, V]
446
+ def reverse_merge!: (Hash[K, V]) -> self
447
+
448
+ # `core_ext/hash/slice` — `Hash#except` is in core RBS (Ruby 3.0+);
449
+ # `Hash#slice` is in core RBS (Ruby 2.5+); the bang variants are
450
+ # ActiveSupport-only.
451
+ def slice!: (*K) -> Hash[K, V]
452
+ end
453
+
454
+ # ---------------------------------------------------------------
455
+ # DateTime — `core_ext/date_time/*`
456
+ # ---------------------------------------------------------------
457
+
458
+ class DateTime
459
+ def utc: () -> Time
460
+ def to_time: () -> Time
461
+ def in_time_zone: (?String | Symbol zone) -> untyped
462
+ def yesterday: () -> DateTime
463
+ def tomorrow: () -> DateTime
464
+ def ago: (Numeric seconds) -> DateTime
465
+ def since: (Numeric seconds) -> DateTime
466
+ def beginning_of_day: () -> DateTime
467
+ def end_of_day: () -> DateTime
468
+ def beginning_of_hour: () -> DateTime
469
+ def end_of_hour: () -> DateTime
470
+ def beginning_of_minute: () -> DateTime
471
+ def end_of_minute: () -> DateTime
472
+ def acts_like_time?: () -> true
473
+ end