tapioca 0.12.0 → 0.13.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.
- checksums.yaml +4 -4
- data/README.md +122 -100
- data/lib/tapioca/commands/abstract_dsl.rb +3 -2
- data/lib/tapioca/commands/abstract_gem.rb +3 -1
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +3 -6
- data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +9 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +252 -61
- data/lib/tapioca/dsl/compilers/config.rb +1 -1
- data/lib/tapioca/dsl/compilers/protobuf.rb +7 -0
- data/lib/tapioca/dsl/helpers/active_model_type_helper.rb +70 -0
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +3 -35
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +2 -0
- data/lib/tapioca/dsl/pipeline.rb +1 -1
- data/lib/tapioca/gemfile.rb +12 -1
- data/lib/tapioca/rbi_ext/model.rb +38 -6
- data/lib/tapioca/version.rb +1 -1
- metadata +6 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f655f69697a5c375a3e04eef733d55213d2ea83b7392f35e3f81ed61ef4f93e4
|
|
4
|
+
data.tar.gz: 1ba60b67539a5034080dd9247f2f005593f25bcb2f01d72b72bccfb9a1f77f73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99e5157e6d3b82922fb321d9eed18813bd8964cd02a9e253653c73ae6fff56529638e129532bf165dae9ec43e191e9186b4e6a7e4481ea0e4f11d5e2675b2f37
|
|
7
|
+
data.tar.gz: 22e178bf5afe57e2fa348606bf3cdbc8085cb4e8f735cdf1ad06665e27e6b5e08085d1eef74bba823eab87b3e1f76f12c02df5455a4fd182f11455744a5ce27c
|
data/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase
|
|
|
40
40
|
* [Excluding a gem from RBI generation](#excluding-a-gem-from-rbi-generation)
|
|
41
41
|
* [Changing the strictness level of the RBI for a gem](#changing-the-strictness-level-of-the-rbi-for-a-gem)
|
|
42
42
|
* [Keeping RBI files for gems up-to-date](#keeping-rbi-files-for-gems-up-to-date)
|
|
43
|
+
* [Importing hand written signatures from gem's `rbi/` folder](#importing-hand-written-signatures-from-gems-rbi-folder)
|
|
43
44
|
* [Pulling RBI annotations from remote sources](#pulling-rbi-annotations-from-remote-sources)
|
|
44
45
|
* [Basic authentication](#basic-authentication)
|
|
45
46
|
* [Using a .netrc file](#using-a-netrc-file)
|
|
@@ -83,10 +84,10 @@ Commands:
|
|
|
83
84
|
tapioca todo # Generate the list of unresolved constants
|
|
84
85
|
|
|
85
86
|
Options:
|
|
86
|
-
-c, [--config=<config file path>]
|
|
87
|
-
|
|
88
|
-
-V, [--verbose], [--no-verbose]
|
|
89
|
-
|
|
87
|
+
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
88
|
+
# Default: sorbet/tapioca/config.yml
|
|
89
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
90
|
+
# Default: false
|
|
90
91
|
|
|
91
92
|
```
|
|
92
93
|
<!-- END_HELP -->
|
|
@@ -117,10 +118,10 @@ Usage:
|
|
|
117
118
|
tapioca init
|
|
118
119
|
|
|
119
120
|
Options:
|
|
120
|
-
-c, [--config=<config file path>]
|
|
121
|
-
|
|
122
|
-
-V, [--verbose], [--no-verbose]
|
|
123
|
-
|
|
121
|
+
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
122
|
+
# Default: sorbet/tapioca/config.yml
|
|
123
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
124
|
+
# Default: false
|
|
124
125
|
|
|
125
126
|
Get project ready for type checking
|
|
126
127
|
```
|
|
@@ -164,43 +165,43 @@ Usage:
|
|
|
164
165
|
tapioca gem [gem...]
|
|
165
166
|
|
|
166
167
|
Options:
|
|
167
|
-
--out, -o, [--outdir=directory]
|
|
168
|
-
|
|
169
|
-
[--file-header], [--no-file-header]
|
|
170
|
-
|
|
171
|
-
[--all], [--no-all]
|
|
172
|
-
|
|
173
|
-
--pre, -b, [--prerequire=file]
|
|
174
|
-
--post, -a, [--postrequire=file]
|
|
175
|
-
|
|
176
|
-
-x, [--exclude=gem [gem ...]]
|
|
177
|
-
[--include-dependencies], [--no-include-dependencies] # Generate RBI files for dependencies of the given gem(s)
|
|
178
|
-
|
|
179
|
-
--typed, -t, [--typed-overrides=gem:level [gem:level ...]]
|
|
180
|
-
|
|
181
|
-
[--verify], [--no-verify]
|
|
182
|
-
|
|
183
|
-
[--doc], [--no-doc]
|
|
184
|
-
|
|
185
|
-
[--loc], [--no-loc]
|
|
186
|
-
|
|
187
|
-
[--exported-gem-rbis], [--no-exported-gem-rbis]
|
|
188
|
-
|
|
189
|
-
-w, [--workers=N]
|
|
190
|
-
[--auto-strictness], [--no-auto-strictness]
|
|
191
|
-
|
|
192
|
-
--dsl-dir, [--dsl-dir=directory]
|
|
193
|
-
|
|
194
|
-
[--rbi-max-line-length=N]
|
|
195
|
-
|
|
196
|
-
-e, [--environment=ENVIRONMENT]
|
|
197
|
-
|
|
198
|
-
[--halt-upon-load-error], [--no-halt-upon-load-error] # Halt upon a load error while loading the Rails application
|
|
199
|
-
|
|
200
|
-
-c, [--config=<config file path>]
|
|
201
|
-
|
|
202
|
-
-V, [--verbose], [--no-verbose]
|
|
203
|
-
|
|
168
|
+
--out, -o, [--outdir=directory] # The output directory for generated gem RBI files
|
|
169
|
+
# Default: sorbet/rbi/gems
|
|
170
|
+
[--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
|
171
|
+
# Default: true
|
|
172
|
+
[--all], [--no-all], [--skip-all] # Regenerate RBI files for all gems
|
|
173
|
+
# Default: false
|
|
174
|
+
--pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
|
|
175
|
+
--post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
|
|
176
|
+
# Default: sorbet/tapioca/require.rb
|
|
177
|
+
-x, [--exclude=gem [gem ...]] # Exclude the given gem(s) from RBI generation
|
|
178
|
+
[--include-dependencies], [--no-include-dependencies], [--skip-include-dependencies] # Generate RBI files for dependencies of the given gem(s)
|
|
179
|
+
# Default: false
|
|
180
|
+
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for generated gem RBIs
|
|
181
|
+
# Default: {"activesupport"=>"false"}
|
|
182
|
+
[--verify], [--no-verify], [--skip-verify] # Verify RBIs are up-to-date
|
|
183
|
+
# Default: false
|
|
184
|
+
[--doc], [--no-doc], [--skip-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
|
|
185
|
+
# Default: true
|
|
186
|
+
[--loc], [--no-loc], [--skip-loc] # Include comments with source location when generating RBIs
|
|
187
|
+
# Default: true
|
|
188
|
+
[--exported-gem-rbis], [--no-exported-gem-rbis], [--skip-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
|
|
189
|
+
# Default: true
|
|
190
|
+
-w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
|
|
191
|
+
[--auto-strictness], [--no-auto-strictness], [--skip-auto-strictness] # Autocorrect strictness in gem RBIs in case of conflict with the DSL RBIs
|
|
192
|
+
# Default: true
|
|
193
|
+
--dsl-dir, [--dsl-dir=directory] # The DSL directory used to correct gems strictnesses
|
|
194
|
+
# Default: sorbet/rbi/dsl
|
|
195
|
+
[--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
|
|
196
|
+
# Default: 120
|
|
197
|
+
-e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
|
|
198
|
+
# Default: development
|
|
199
|
+
[--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application
|
|
200
|
+
# Default: true
|
|
201
|
+
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
202
|
+
# Default: sorbet/tapioca/config.yml
|
|
203
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
204
|
+
# Default: false
|
|
204
205
|
|
|
205
206
|
Generate RBIs from gems
|
|
206
207
|
```
|
|
@@ -223,18 +224,18 @@ For gems that have a normal default `require` and that load all of their constan
|
|
|
223
224
|
For example, suppose you are using the class `BetterHtml::Parser` exported from the `better_html` gem. Just doing a `require "better_html"` (which is the default require) does not load that type:
|
|
224
225
|
|
|
225
226
|
```shell
|
|
226
|
-
$ bundle exec
|
|
227
|
+
$ bundle exec irb
|
|
227
228
|
|
|
228
|
-
|
|
229
|
+
irb(main):001> require 'better_html'
|
|
229
230
|
=> true
|
|
230
|
-
|
|
231
|
+
irb(main):002> BetterHtml
|
|
231
232
|
=> BetterHtml
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
irb(main):003> BetterHtml::Parser
|
|
234
|
+
(irb):3:in '<main>': uninitialized constant BetterHtml::Parser (NameError)
|
|
235
|
+
Did you mean? BetterHtml::ParserError
|
|
236
|
+
irb(main):004> require 'better_html/parser'
|
|
236
237
|
=> true
|
|
237
|
-
|
|
238
|
+
irb(main):005> BetterHtml::Parser
|
|
238
239
|
=> BetterHtml::Parser
|
|
239
240
|
```
|
|
240
241
|
|
|
@@ -315,7 +316,7 @@ gem:
|
|
|
315
316
|
|
|
316
317
|
#### Keeping RBI files for gems up-to-date
|
|
317
318
|
|
|
318
|
-
To ensure all RBI files for gems are
|
|
319
|
+
To ensure all RBI files for gems are present and have the correct version based on your `Gemfile.lock`, Tapioca provides a `--verify` option:
|
|
319
320
|
|
|
320
321
|
```shell
|
|
321
322
|
$ bin/tapioca gems --verify
|
|
@@ -325,7 +326,16 @@ Checking for out-of-date RBIs...
|
|
|
325
326
|
Nothing to do, all RBIs are up-to-date.
|
|
326
327
|
```
|
|
327
328
|
|
|
328
|
-
This option can be used
|
|
329
|
+
This option can be used in CI to make sure the RBI files are *up-to-date* and ensure accurate type checking.
|
|
330
|
+
|
|
331
|
+
**Warning**: doing so will break your normal automated dependency update workflow as every pull request opened to bump a gem version will fail CI since the RBI will be out-of-date. You will need to either set up additional automation (eg [Dependabot](https://github.com/dependabot/dependabot-core/issues/5962#issuecomment-1303781931)), or manually run `bin/tapioca gems` and commit the results.
|
|
332
|
+
|
|
333
|
+
**Warning**: Verification ONLY ensures the RBI files are present, used and have the correct version based on the gem version in your `Gemfile.lock`. It's possible for your RBIs to be out-of-date if RBIs were not regenerated following an update to tapioca itself or if a another gem that injects functionality (e.g. `turbo-rails`) was installed/updated/removed. To ensure RBIs are completely up-to-date, you must run `bin/tapioca gems --all` but it's not recommended to do this in CI as it's an expensive operation.
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
#### Importing hand written signatures from gem's `rbi/` folder
|
|
337
|
+
|
|
338
|
+
Tapioca will import any signatures found in the `rbi/` folder of a given gem and combine them with the RBIs it generates. This is useful when a gem doesn't want to depend on `sorbet-runtime` but still wants to provide type safety to users during static checks. Note that the `rbi/` folder needs to be included in the gem release using the `.gemspec` file. Applications can choose not to import these signatures using the `--no-exported-gem-rbis` flag.
|
|
329
339
|
|
|
330
340
|
### Pulling RBI annotations from remote sources
|
|
331
341
|
|
|
@@ -357,14 +367,14 @@ Usage:
|
|
|
357
367
|
Options:
|
|
358
368
|
[--sources=one two three] # URIs of the sources to pull gem RBI annotations from
|
|
359
369
|
# Default: "https://raw.githubusercontent.com/Shopify/rbi-central/main"
|
|
360
|
-
[--netrc], [--no-netrc]
|
|
370
|
+
[--netrc], [--no-netrc], [--skip-netrc] # Use .netrc to authenticate to private sources
|
|
361
371
|
# Default: true
|
|
362
372
|
[--netrc-file=NETRC_FILE] # Path to .netrc file
|
|
363
373
|
[--auth=AUTH] # HTTP authorization header for private sources
|
|
364
374
|
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for pulled annotations
|
|
365
375
|
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
366
376
|
# Default: sorbet/tapioca/config.yml
|
|
367
|
-
-V, [--verbose], [--no-verbose]
|
|
377
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
368
378
|
# Default: false
|
|
369
379
|
|
|
370
380
|
Pull gem RBI annotations from remote sources
|
|
@@ -467,32 +477,32 @@ Usage:
|
|
|
467
477
|
tapioca dsl [constant...]
|
|
468
478
|
|
|
469
479
|
Options:
|
|
470
|
-
--out, -o, [--outdir=directory]
|
|
471
|
-
|
|
472
|
-
[--file-header], [--no-file-header]
|
|
473
|
-
|
|
474
|
-
[--only=compiler [compiler ...]]
|
|
475
|
-
[--exclude=compiler [compiler ...]]
|
|
476
|
-
[--verify], [--no-verify]
|
|
477
|
-
|
|
478
|
-
-q, [--quiet], [--no-quiet]
|
|
479
|
-
|
|
480
|
-
-w, [--workers=N]
|
|
481
|
-
|
|
482
|
-
[--rbi-max-line-length=N]
|
|
483
|
-
|
|
484
|
-
-e, [--environment=ENVIRONMENT]
|
|
485
|
-
|
|
486
|
-
-l, [--list-compilers], [--no-list-compilers]
|
|
487
|
-
|
|
488
|
-
[--app-root=APP_ROOT]
|
|
489
|
-
|
|
490
|
-
[--halt-upon-load-error], [--no-halt-upon-load-error] # Halt upon a load error while loading the Rails application
|
|
491
|
-
|
|
492
|
-
-c, [--config=<config file path>]
|
|
493
|
-
|
|
494
|
-
-V, [--verbose], [--no-verbose]
|
|
495
|
-
|
|
480
|
+
--out, -o, [--outdir=directory] # The output directory for generated DSL RBI files
|
|
481
|
+
# Default: sorbet/rbi/dsl
|
|
482
|
+
[--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
|
483
|
+
# Default: true
|
|
484
|
+
[--only=compiler [compiler ...]] # Only run supplied DSL compiler(s)
|
|
485
|
+
[--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s)
|
|
486
|
+
[--verify], [--no-verify], [--skip-verify] # Verifies RBIs are up-to-date
|
|
487
|
+
# Default: false
|
|
488
|
+
-q, [--quiet], [--no-quiet], [--skip-quiet] # Suppresses file creation output
|
|
489
|
+
# Default: false
|
|
490
|
+
-w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: 2)
|
|
491
|
+
# Default: 2
|
|
492
|
+
[--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
|
|
493
|
+
# Default: 120
|
|
494
|
+
-e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
|
|
495
|
+
# Default: development
|
|
496
|
+
-l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers
|
|
497
|
+
# Default: false
|
|
498
|
+
[--app-root=APP_ROOT] # The path to the Rails application
|
|
499
|
+
# Default: .
|
|
500
|
+
[--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application
|
|
501
|
+
# Default: true
|
|
502
|
+
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
503
|
+
# Default: sorbet/tapioca/config.yml
|
|
504
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
505
|
+
# Default: false
|
|
496
506
|
|
|
497
507
|
Generate RBIs for dynamic methods
|
|
498
508
|
```
|
|
@@ -521,6 +531,18 @@ Reason:
|
|
|
521
531
|
|
|
522
532
|
This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking.
|
|
523
533
|
|
|
534
|
+
If you are using Rails, you can configure `tapioca dsl` to run after each migration:
|
|
535
|
+
|
|
536
|
+
```ruby
|
|
537
|
+
# Rakefile
|
|
538
|
+
if Rails.env.development?
|
|
539
|
+
namespace :db do
|
|
540
|
+
task :migrate do # Appends to the existing `db:migrate` task
|
|
541
|
+
system("bundle exec tapioca dsl", exception: true)
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
```
|
|
545
|
+
|
|
524
546
|
#### Writing custom DSL compilers
|
|
525
547
|
|
|
526
548
|
It is possible to create your own compilers for DSLs not supported by Tapioca out of the box.
|
|
@@ -840,23 +862,23 @@ Usage:
|
|
|
840
862
|
tapioca check-shims
|
|
841
863
|
|
|
842
864
|
Options:
|
|
843
|
-
[--gem-rbi-dir=GEM_RBI_DIR]
|
|
844
|
-
|
|
845
|
-
[--dsl-rbi-dir=DSL_RBI_DIR]
|
|
846
|
-
|
|
847
|
-
[--shim-rbi-dir=SHIM_RBI_DIR]
|
|
848
|
-
|
|
849
|
-
[--annotations-rbi-dir=ANNOTATIONS_RBI_DIR]
|
|
850
|
-
|
|
851
|
-
[--todo-rbi-file=TODO_RBI_FILE]
|
|
852
|
-
|
|
853
|
-
[--payload], [--no-payload]
|
|
854
|
-
|
|
855
|
-
-w, [--workers=N]
|
|
856
|
-
-c, [--config=<config file path>]
|
|
857
|
-
|
|
858
|
-
-V, [--verbose], [--no-verbose]
|
|
859
|
-
|
|
865
|
+
[--gem-rbi-dir=GEM_RBI_DIR] # Path to gem RBIs
|
|
866
|
+
# Default: sorbet/rbi/gems
|
|
867
|
+
[--dsl-rbi-dir=DSL_RBI_DIR] # Path to DSL RBIs
|
|
868
|
+
# Default: sorbet/rbi/dsl
|
|
869
|
+
[--shim-rbi-dir=SHIM_RBI_DIR] # Path to shim RBIs
|
|
870
|
+
# Default: sorbet/rbi/shims
|
|
871
|
+
[--annotations-rbi-dir=ANNOTATIONS_RBI_DIR] # Path to annotations RBIs
|
|
872
|
+
# Default: sorbet/rbi/annotations
|
|
873
|
+
[--todo-rbi-file=TODO_RBI_FILE] # Path to the generated todo RBI file
|
|
874
|
+
# Default: sorbet/rbi/todo.rbi
|
|
875
|
+
[--payload], [--no-payload], [--skip-payload] # Check shims against Sorbet's payload
|
|
876
|
+
# Default: true
|
|
877
|
+
-w, [--workers=N] # Number of parallel workers (default: auto)
|
|
878
|
+
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
879
|
+
# Default: sorbet/tapioca/config.yml
|
|
880
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
881
|
+
# Default: false
|
|
860
882
|
|
|
861
883
|
Check duplicated definitions in shim RBIs
|
|
862
884
|
```
|
|
@@ -175,10 +175,11 @@ module Tapioca
|
|
|
175
175
|
|
|
176
176
|
unless unprocessable_compilers.empty?
|
|
177
177
|
message = unprocessable_compilers.map do |name, _|
|
|
178
|
-
set_color("
|
|
178
|
+
set_color("Warning: Cannot find compiler '#{name}'", :yellow)
|
|
179
179
|
end.join("\n")
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
say(message)
|
|
182
|
+
say("")
|
|
182
183
|
end
|
|
183
184
|
|
|
184
185
|
T.cast(compiler_map.values, T::Array[T.class_of(Tapioca::Dsl::Compiler)])
|
|
@@ -118,7 +118,9 @@ module Tapioca
|
|
|
118
118
|
reason: "types exported from the `#{gem.name}` gem",
|
|
119
119
|
) if @file_header
|
|
120
120
|
|
|
121
|
-
rbi.root =
|
|
121
|
+
rbi.root = Runtime::Trackers::Autoload.with_disabled_exits do
|
|
122
|
+
Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
|
|
123
|
+
end
|
|
122
124
|
|
|
123
125
|
merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
|
|
124
126
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
return unless defined?(ActiveModel::Attributes)
|
|
5
5
|
|
|
6
|
+
require "tapioca/dsl/helpers/active_model_type_helper"
|
|
7
|
+
|
|
6
8
|
module Tapioca
|
|
7
9
|
module Dsl
|
|
8
10
|
module Compilers
|
|
@@ -99,11 +101,6 @@ module Tapioca
|
|
|
99
101
|
|
|
100
102
|
sig { params(attribute_type_value: T.untyped).returns(::String) }
|
|
101
103
|
def type_for(attribute_type_value)
|
|
102
|
-
# This guarantees that the type will remain as T.untyped for attributes in the following form:
|
|
103
|
-
# attribute :name
|
|
104
|
-
# This is because for a generic attribute with no specified type, ActiveModel::Type::Value.new is returned
|
|
105
|
-
return "T.untyped" if attribute_type_value.instance_of?(ActiveModel::Type::Value)
|
|
106
|
-
|
|
107
104
|
type = case attribute_type_value
|
|
108
105
|
when ActiveModel::Type::Boolean
|
|
109
106
|
"T::Boolean"
|
|
@@ -120,7 +117,7 @@ module Tapioca
|
|
|
120
117
|
when ActiveModel::Type::String
|
|
121
118
|
"::String"
|
|
122
119
|
else
|
|
123
|
-
attribute_type_value
|
|
120
|
+
Helpers::ActiveModelTypeHelper.type_for(attribute_type_value)
|
|
124
121
|
end
|
|
125
122
|
|
|
126
123
|
as_nilable_type(type)
|
|
@@ -78,7 +78,15 @@ module Tapioca
|
|
|
78
78
|
|
|
79
79
|
root.create_path(constant) do |klass|
|
|
80
80
|
methods.each do |method|
|
|
81
|
-
|
|
81
|
+
if method == :authenticate || method.start_with?("authenticate_")
|
|
82
|
+
klass.create_method(
|
|
83
|
+
method.to_s,
|
|
84
|
+
parameters: [create_param("unencrypted_password", type: "T.untyped")],
|
|
85
|
+
return_type: "T.any(#{constant}, FalseClass)",
|
|
86
|
+
)
|
|
87
|
+
else
|
|
88
|
+
create_method_from_def(klass, constant.instance_method(method))
|
|
89
|
+
end
|
|
82
90
|
end
|
|
83
91
|
end
|
|
84
92
|
end
|
|
@@ -58,16 +58,6 @@ module Tapioca
|
|
|
58
58
|
# `Model::PrivateRelation` modules, so that, for example, `find_by` and `all` can be chained off of the
|
|
59
59
|
# `Model` class.
|
|
60
60
|
#
|
|
61
|
-
# **A note on find**: `find` is typed as `T.untyped` by default.
|
|
62
|
-
#
|
|
63
|
-
# While it is often used in the manner of `Model.find(id)`, Rails does support pasing in an array to find, which
|
|
64
|
-
# would then return a `T::Enumerable[Model]`. This would force a static cast everywhere find is used to avoid type
|
|
65
|
-
# errors. This is not ideal considering very few users of find use the array syntax over a where. With untyped,
|
|
66
|
-
# this cast is optional and so it was decided to avoid typing it. If you need runtime guarentees when using `find`
|
|
67
|
-
# the best method of doing so is by casting the return value to the model: `T.cast(Model.find(id), Model)`.
|
|
68
|
-
# `find_by` does guarentee a return value of `Model`, so find can can be refactored accordingly:
|
|
69
|
-
# `Model.find_by!(id: id)`. This will avoid the cast requirement at runtime.
|
|
70
|
-
#
|
|
71
61
|
# **CAUTION**: The generated relation classes are named `PrivateXXX` intentionally to reflect the fact
|
|
72
62
|
# that they represent private subconstants of the Active Record model. As such, these types do not
|
|
73
63
|
# exist at runtime, and their counterparts that do exist at runtime are marked `private_constant` anyway.
|
|
@@ -200,6 +190,8 @@ module Tapioca
|
|
|
200
190
|
query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
|
|
201
191
|
# Remove the ones we know are private API
|
|
202
192
|
query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
|
|
193
|
+
# Remove "group" which needs a custom return type for GroupChains
|
|
194
|
+
query_methods -= [:group]
|
|
203
195
|
# Remove "where" which needs a custom return type for WhereChains
|
|
204
196
|
query_methods -= [:where]
|
|
205
197
|
# Remove the methods that ...
|
|
@@ -304,6 +296,7 @@ module Tapioca
|
|
|
304
296
|
end
|
|
305
297
|
end
|
|
306
298
|
|
|
299
|
+
create_relation_group_chain_class
|
|
307
300
|
create_relation_where_chain_class
|
|
308
301
|
end
|
|
309
302
|
|
|
@@ -322,9 +315,87 @@ module Tapioca
|
|
|
322
315
|
end
|
|
323
316
|
end
|
|
324
317
|
|
|
318
|
+
create_association_relation_group_chain_class
|
|
325
319
|
create_association_relation_where_chain_class
|
|
326
320
|
end
|
|
327
321
|
|
|
322
|
+
sig { void }
|
|
323
|
+
def create_relation_group_chain_class
|
|
324
|
+
model.create_class(RelationGroupChainClassName, superclass_name: RelationClassName) do |klass|
|
|
325
|
+
create_group_chain_methods(klass)
|
|
326
|
+
klass.create_type_variable("Elem", type: "type_member", fixed: constant_name)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
sig { void }
|
|
331
|
+
def create_association_relation_group_chain_class
|
|
332
|
+
model.create_class(
|
|
333
|
+
AssociationRelationGroupChainClassName,
|
|
334
|
+
superclass_name: AssociationRelationClassName,
|
|
335
|
+
) do |klass|
|
|
336
|
+
create_group_chain_methods(klass)
|
|
337
|
+
klass.create_type_variable("Elem", type: "type_member", fixed: constant_name)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
sig { params(klass: RBI::Scope).void }
|
|
342
|
+
def create_group_chain_methods(klass)
|
|
343
|
+
# Calculation methods used with `group` return a hash where the keys cannot be typed
|
|
344
|
+
# but the values can. Technically a `group` anywhere in the query chain produces
|
|
345
|
+
# this behavior but to avoid needing to re-type every query method inside this module
|
|
346
|
+
# we make a simplifying assumption that the calculation method is called immediately
|
|
347
|
+
# after the group (e.g. `group().count` and not `group().where().count`). The one
|
|
348
|
+
# exception is `group().having().count` which is fairly idiomatic so that gets handled
|
|
349
|
+
# without breaking the chain.
|
|
350
|
+
klass.create_method(
|
|
351
|
+
"having",
|
|
352
|
+
parameters: [
|
|
353
|
+
create_rest_param("args", type: "T.untyped"),
|
|
354
|
+
create_block_param("blk", type: "T.untyped"),
|
|
355
|
+
],
|
|
356
|
+
return_type: "T.self_type",
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
CALCULATION_METHODS.each do |method_name|
|
|
360
|
+
case method_name
|
|
361
|
+
when :average, :maximum, :minimum
|
|
362
|
+
klass.create_method(
|
|
363
|
+
method_name.to_s,
|
|
364
|
+
parameters: [
|
|
365
|
+
create_param("column_name", type: "T.any(String, Symbol)"),
|
|
366
|
+
],
|
|
367
|
+
return_type: "T::Hash[T.untyped, #{method_name == :average ? "Numeric" : "T.untyped"}]",
|
|
368
|
+
)
|
|
369
|
+
when :calculate
|
|
370
|
+
klass.create_method(
|
|
371
|
+
"calculate",
|
|
372
|
+
parameters: [
|
|
373
|
+
create_param("operation", type: "Symbol"),
|
|
374
|
+
create_param("column_name", type: "T.any(String, Symbol)"),
|
|
375
|
+
],
|
|
376
|
+
return_type: "T::Hash[T.untyped, Numeric]",
|
|
377
|
+
)
|
|
378
|
+
when :count
|
|
379
|
+
klass.create_method(
|
|
380
|
+
"count",
|
|
381
|
+
parameters: [
|
|
382
|
+
create_opt_param("column_name", type: "T.untyped", default: "nil"),
|
|
383
|
+
],
|
|
384
|
+
return_type: "T::Hash[T.untyped, Integer]",
|
|
385
|
+
)
|
|
386
|
+
when :sum
|
|
387
|
+
klass.create_method(
|
|
388
|
+
"sum",
|
|
389
|
+
parameters: [
|
|
390
|
+
create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
|
|
391
|
+
create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
|
|
392
|
+
],
|
|
393
|
+
return_type: "T::Hash[T.untyped, Numeric]",
|
|
394
|
+
)
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
328
399
|
sig { void }
|
|
329
400
|
def create_relation_where_chain_class
|
|
330
401
|
model.create_class(RelationWhereChainClassName, superclass_name: RelationClassName) do |klass|
|
|
@@ -468,6 +539,15 @@ module Tapioca
|
|
|
468
539
|
sig { void }
|
|
469
540
|
def create_relation_methods
|
|
470
541
|
create_relation_method("all")
|
|
542
|
+
create_relation_method(
|
|
543
|
+
"group",
|
|
544
|
+
parameters: [
|
|
545
|
+
create_rest_param("args", type: "T.untyped"),
|
|
546
|
+
create_block_param("blk", type: "T.untyped"),
|
|
547
|
+
],
|
|
548
|
+
relation_return_type: RelationGroupChainClassName,
|
|
549
|
+
association_return_type: AssociationRelationGroupChainClassName,
|
|
550
|
+
)
|
|
471
551
|
create_relation_method(
|
|
472
552
|
"where",
|
|
473
553
|
parameters: [
|
|
@@ -479,13 +559,29 @@ module Tapioca
|
|
|
479
559
|
)
|
|
480
560
|
|
|
481
561
|
QUERY_METHODS.each do |method_name|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
parameters:
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
562
|
+
case method_name
|
|
563
|
+
when :extract_associated
|
|
564
|
+
parameters = [create_param("association", type: "Symbol")]
|
|
565
|
+
return_type = "T::Array[T.untyped]"
|
|
566
|
+
relation_methods_module.create_method(
|
|
567
|
+
method_name.to_s,
|
|
568
|
+
parameters: parameters,
|
|
569
|
+
return_type: return_type,
|
|
570
|
+
)
|
|
571
|
+
association_relation_methods_module.create_method(
|
|
572
|
+
method_name.to_s,
|
|
573
|
+
parameters: parameters,
|
|
574
|
+
return_type: return_type,
|
|
575
|
+
)
|
|
576
|
+
else
|
|
577
|
+
create_relation_method(
|
|
578
|
+
method_name,
|
|
579
|
+
parameters: [
|
|
580
|
+
create_rest_param("args", type: "T.untyped"),
|
|
581
|
+
create_block_param("blk", type: "T.untyped"),
|
|
582
|
+
],
|
|
583
|
+
)
|
|
584
|
+
end
|
|
489
585
|
end
|
|
490
586
|
end
|
|
491
587
|
|
|
@@ -560,12 +656,29 @@ module Tapioca
|
|
|
560
656
|
return_type: "T::Boolean",
|
|
561
657
|
)
|
|
562
658
|
when :find
|
|
563
|
-
|
|
659
|
+
# From ActiveRecord::ConnectionAdapter::Quoting#quote, minus nil
|
|
660
|
+
id_types = "T.any(String, Symbol, ::ActiveSupport::Multibyte::Chars, T::Boolean, BigDecimal, Numeric, " \
|
|
661
|
+
"::ActiveRecord::Type::Binary::Data, ::ActiveRecord::Type::Time::Value, Date, Time, " \
|
|
662
|
+
"::ActiveSupport::Duration, T::Class[T.anything])"
|
|
663
|
+
array_type = if constant.try(:composite_primary_key?)
|
|
664
|
+
"T::Array[T::Array[#{id_types}]"
|
|
665
|
+
else
|
|
666
|
+
"T::Array[#{id_types}]"
|
|
667
|
+
end
|
|
668
|
+
sigs = [
|
|
669
|
+
common_relation_methods_module.create_sig(
|
|
670
|
+
parameters: [create_param("args", type: id_types)],
|
|
671
|
+
return_type: constant_name,
|
|
672
|
+
),
|
|
673
|
+
common_relation_methods_module.create_sig(
|
|
674
|
+
parameters: [create_param("args", type: array_type)],
|
|
675
|
+
return_type: "T::Enumerable[#{constant_name}]",
|
|
676
|
+
),
|
|
677
|
+
]
|
|
678
|
+
common_relation_methods_module.create_method_with_sigs(
|
|
564
679
|
"find",
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
],
|
|
568
|
-
return_type: "T.untyped",
|
|
680
|
+
sigs: sigs,
|
|
681
|
+
parameters: [RBI::ReqParam.new("args")],
|
|
569
682
|
)
|
|
570
683
|
when :find_by
|
|
571
684
|
create_common_method(
|
|
@@ -599,12 +712,20 @@ module Tapioca
|
|
|
599
712
|
return_type: constant_name,
|
|
600
713
|
)
|
|
601
714
|
when :first, :last, :take
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
715
|
+
sigs = [
|
|
716
|
+
common_relation_methods_module.create_sig(
|
|
717
|
+
parameters: [create_opt_param("limit", type: "NilClass", default: "nil")],
|
|
718
|
+
return_type: as_nilable_type(constant_name),
|
|
719
|
+
),
|
|
720
|
+
common_relation_methods_module.create_sig(
|
|
721
|
+
parameters: [create_param("limit", type: "Integer")],
|
|
722
|
+
return_type: "T::Array[#{constant_name}]",
|
|
723
|
+
),
|
|
724
|
+
]
|
|
725
|
+
common_relation_methods_module.create_method_with_sigs(
|
|
726
|
+
method_name.to_s,
|
|
727
|
+
sigs: sigs,
|
|
728
|
+
parameters: [RBI::OptParam.new("limit", "nil")],
|
|
608
729
|
)
|
|
609
730
|
when :raise_record_not_found_exception!
|
|
610
731
|
# skip
|
|
@@ -653,7 +774,7 @@ module Tapioca
|
|
|
653
774
|
parameters: [
|
|
654
775
|
create_param("column_name", type: "T.any(String, Symbol)"),
|
|
655
776
|
],
|
|
656
|
-
return_type: "T.untyped",
|
|
777
|
+
return_type: method_name == :average ? "Numeric" : "T.untyped",
|
|
657
778
|
)
|
|
658
779
|
when :calculate
|
|
659
780
|
create_common_method(
|
|
@@ -662,7 +783,7 @@ module Tapioca
|
|
|
662
783
|
create_param("operation", type: "Symbol"),
|
|
663
784
|
create_param("column_name", type: "T.any(String, Symbol)"),
|
|
664
785
|
],
|
|
665
|
-
return_type: "
|
|
786
|
+
return_type: "Numeric",
|
|
666
787
|
)
|
|
667
788
|
when :count
|
|
668
789
|
create_common_method(
|
|
@@ -670,7 +791,7 @@ module Tapioca
|
|
|
670
791
|
parameters: [
|
|
671
792
|
create_opt_param("column_name", type: "T.untyped", default: "nil"),
|
|
672
793
|
],
|
|
673
|
-
return_type: "
|
|
794
|
+
return_type: "Integer",
|
|
674
795
|
)
|
|
675
796
|
when :ids
|
|
676
797
|
create_common_method("ids", return_type: "Array")
|
|
@@ -689,7 +810,7 @@ module Tapioca
|
|
|
689
810
|
create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
|
|
690
811
|
create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
|
|
691
812
|
],
|
|
692
|
-
return_type: "
|
|
813
|
+
return_type: "Numeric",
|
|
693
814
|
)
|
|
694
815
|
end
|
|
695
816
|
end
|
|
@@ -698,51 +819,121 @@ module Tapioca
|
|
|
698
819
|
case method_name
|
|
699
820
|
when :find_each
|
|
700
821
|
order = ActiveRecord::Batches.instance_method(:find_each).parameters.include?([:key, :order])
|
|
701
|
-
|
|
822
|
+
sigs = [
|
|
823
|
+
common_relation_methods_module.create_sig(
|
|
824
|
+
parameters: [
|
|
825
|
+
create_kw_opt_param("start", type: "T.untyped", default: "nil"),
|
|
826
|
+
create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
|
|
827
|
+
create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
|
|
828
|
+
create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
|
|
829
|
+
*(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
|
|
830
|
+
create_block_param("block", type: "T.proc.params(object: #{constant_name}).void"),
|
|
831
|
+
],
|
|
832
|
+
return_type: "void",
|
|
833
|
+
),
|
|
834
|
+
common_relation_methods_module.create_sig(
|
|
835
|
+
parameters: [
|
|
836
|
+
create_kw_opt_param("start", type: "T.untyped", default: "nil"),
|
|
837
|
+
create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
|
|
838
|
+
create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
|
|
839
|
+
create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
|
|
840
|
+
*(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
|
|
841
|
+
],
|
|
842
|
+
return_type: "T::Enumerator[#{constant_name}]",
|
|
843
|
+
),
|
|
844
|
+
]
|
|
845
|
+
common_relation_methods_module.create_method_with_sigs(
|
|
702
846
|
"find_each",
|
|
847
|
+
sigs: sigs,
|
|
703
848
|
parameters: [
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
*(
|
|
709
|
-
|
|
849
|
+
RBI::KwOptParam.new("start", "nil"),
|
|
850
|
+
RBI::KwOptParam.new("finish", "nil"),
|
|
851
|
+
RBI::KwOptParam.new("batch_size", "1000"),
|
|
852
|
+
RBI::KwOptParam.new("error_on_ignore", "nil"),
|
|
853
|
+
*(RBI::KwOptParam.new("order", ":asc") if order),
|
|
854
|
+
RBI::BlockParam.new("block"),
|
|
710
855
|
],
|
|
711
|
-
return_type: "T.nilable(T::Enumerator[#{constant_name}])",
|
|
712
856
|
)
|
|
713
857
|
when :find_in_batches
|
|
714
858
|
order = ActiveRecord::Batches.instance_method(:find_in_batches).parameters.include?([:key, :order])
|
|
715
|
-
|
|
859
|
+
sigs = [
|
|
860
|
+
common_relation_methods_module.create_sig(
|
|
861
|
+
parameters: [
|
|
862
|
+
create_kw_opt_param("start", type: "T.untyped", default: "nil"),
|
|
863
|
+
create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
|
|
864
|
+
create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
|
|
865
|
+
create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
|
|
866
|
+
*(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
|
|
867
|
+
create_block_param("block", type: "T.proc.params(object: T::Array[#{constant_name}]).void"),
|
|
868
|
+
],
|
|
869
|
+
return_type: "void",
|
|
870
|
+
),
|
|
871
|
+
common_relation_methods_module.create_sig(
|
|
872
|
+
parameters: [
|
|
873
|
+
create_kw_opt_param("start", type: "T.untyped", default: "nil"),
|
|
874
|
+
create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
|
|
875
|
+
create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
|
|
876
|
+
create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
|
|
877
|
+
*(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
|
|
878
|
+
],
|
|
879
|
+
return_type: "T::Enumerator[T::Enumerator[#{constant_name}]]",
|
|
880
|
+
),
|
|
881
|
+
]
|
|
882
|
+
common_relation_methods_module.create_method_with_sigs(
|
|
716
883
|
"find_in_batches",
|
|
884
|
+
sigs: sigs,
|
|
717
885
|
parameters: [
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
*(
|
|
723
|
-
|
|
724
|
-
"block",
|
|
725
|
-
type: "T.nilable(T.proc.params(object: T::Array[#{constant_name}]).void)",
|
|
726
|
-
),
|
|
886
|
+
RBI::KwOptParam.new("start", "nil"),
|
|
887
|
+
RBI::KwOptParam.new("finish", "nil"),
|
|
888
|
+
RBI::KwOptParam.new("batch_size", "1000"),
|
|
889
|
+
RBI::KwOptParam.new("error_on_ignore", "nil"),
|
|
890
|
+
*(RBI::KwOptParam.new("order", ":asc") if order),
|
|
891
|
+
RBI::BlockParam.new("block"),
|
|
727
892
|
],
|
|
728
|
-
return_type: "T.nilable(T::Enumerator[T::Enumerator[#{constant_name}]])",
|
|
729
893
|
)
|
|
730
894
|
when :in_batches
|
|
731
895
|
order = ActiveRecord::Batches.instance_method(:in_batches).parameters.include?([:key, :order])
|
|
732
896
|
use_ranges = ActiveRecord::Batches.instance_method(:in_batches).parameters.include?([:key, :use_ranges])
|
|
733
|
-
|
|
897
|
+
sigs = [
|
|
898
|
+
common_relation_methods_module.create_sig(
|
|
899
|
+
parameters: [
|
|
900
|
+
create_kw_opt_param("of", type: "Integer", default: "1000"),
|
|
901
|
+
create_kw_opt_param("start", type: "T.untyped", default: "nil"),
|
|
902
|
+
create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
|
|
903
|
+
create_kw_opt_param("load", type: "T.untyped", default: "false"),
|
|
904
|
+
create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
|
|
905
|
+
*(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
|
|
906
|
+
*(create_kw_opt_param("use_ranges", type: "T.untyped", default: "nil") if use_ranges),
|
|
907
|
+
create_block_param("block", type: "T.proc.params(object: #{RelationClassName}).void"),
|
|
908
|
+
],
|
|
909
|
+
return_type: "void",
|
|
910
|
+
),
|
|
911
|
+
common_relation_methods_module.create_sig(
|
|
912
|
+
parameters: [
|
|
913
|
+
create_kw_opt_param("of", type: "Integer", default: "1000"),
|
|
914
|
+
create_kw_opt_param("start", type: "T.untyped", default: "nil"),
|
|
915
|
+
create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
|
|
916
|
+
create_kw_opt_param("load", type: "T.untyped", default: "false"),
|
|
917
|
+
create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
|
|
918
|
+
*(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
|
|
919
|
+
*(create_kw_opt_param("use_ranges", type: "T.untyped", default: "nil") if use_ranges),
|
|
920
|
+
],
|
|
921
|
+
return_type: "::ActiveRecord::Batches::BatchEnumerator",
|
|
922
|
+
),
|
|
923
|
+
]
|
|
924
|
+
common_relation_methods_module.create_method_with_sigs(
|
|
734
925
|
"in_batches",
|
|
926
|
+
sigs: sigs,
|
|
735
927
|
parameters: [
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
*(
|
|
742
|
-
*(
|
|
743
|
-
|
|
928
|
+
RBI::KwOptParam.new("of", "1000"),
|
|
929
|
+
RBI::KwOptParam.new("start", "nil"),
|
|
930
|
+
RBI::KwOptParam.new("finish", "nil"),
|
|
931
|
+
RBI::KwOptParam.new("load", "false"),
|
|
932
|
+
RBI::KwOptParam.new("error_on_ignore", "nil"),
|
|
933
|
+
*(RBI::KwOptParam.new("order", ":asc") if order),
|
|
934
|
+
*(RBI::KwOptParam.new("use_ranges", "nil") if use_ranges),
|
|
935
|
+
RBI::BlockParam.new("block"),
|
|
744
936
|
],
|
|
745
|
-
return_type: "T.nilable(::ActiveRecord::Batches::BatchEnumerator)",
|
|
746
937
|
)
|
|
747
938
|
end
|
|
748
939
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
module Dsl
|
|
6
|
+
module Helpers
|
|
7
|
+
module ActiveModelTypeHelper
|
|
8
|
+
class << self
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
# Returns the type indicated by the custom ActiveModel::Type::Value.
|
|
12
|
+
# Accepts subclasses of ActiveModel::Type::Value as well as classes that implement similar methods.
|
|
13
|
+
sig { params(type_value: T.untyped).returns(String) }
|
|
14
|
+
def type_for(type_value)
|
|
15
|
+
return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(type_value)
|
|
16
|
+
|
|
17
|
+
type = lookup_return_type_of_method(type_value, :deserialize) ||
|
|
18
|
+
lookup_return_type_of_method(type_value, :cast) ||
|
|
19
|
+
lookup_return_type_of_method(type_value, :cast_value) ||
|
|
20
|
+
lookup_arg_type_of_method(type_value, :serialize) ||
|
|
21
|
+
T.untyped
|
|
22
|
+
type.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
MEANINGLESS_TYPES = T.let(
|
|
28
|
+
[
|
|
29
|
+
T.untyped,
|
|
30
|
+
T.noreturn,
|
|
31
|
+
T::Private::Types::Void,
|
|
32
|
+
T::Private::Types::NotTyped,
|
|
33
|
+
].freeze,
|
|
34
|
+
T::Array[Object],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
sig { params(type: T.untyped).returns(T::Boolean) }
|
|
38
|
+
def meaningful_type?(type)
|
|
39
|
+
!MEANINGLESS_TYPES.include?(type)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { params(obj: T.untyped, method: Symbol).returns(T.nilable(T::Types::Base)) }
|
|
43
|
+
def lookup_return_type_of_method(obj, method)
|
|
44
|
+
return_type = lookup_signature_of_method(obj, method)&.return_type
|
|
45
|
+
return unless return_type && meaningful_type?(return_type)
|
|
46
|
+
|
|
47
|
+
return_type
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sig { params(obj: T.untyped, method: Symbol).returns(T.nilable(T::Types::Base)) }
|
|
51
|
+
def lookup_arg_type_of_method(obj, method)
|
|
52
|
+
# Arg types is an array of [name, type] entries, so we dig into first entry (index 0)
|
|
53
|
+
# and then into the type which is the last element (index 1)
|
|
54
|
+
first_arg_type = lookup_signature_of_method(obj, method)&.arg_types&.dig(0, 1)
|
|
55
|
+
return unless first_arg_type && meaningful_type?(first_arg_type)
|
|
56
|
+
|
|
57
|
+
first_arg_type
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
sig { params(obj: T.untyped, method: Symbol).returns(T.untyped) }
|
|
61
|
+
def lookup_signature_of_method(obj, method)
|
|
62
|
+
Runtime::Reflection.signature_of(obj.method(method))
|
|
63
|
+
rescue NameError
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "tapioca/dsl/helpers/active_model_type_helper"
|
|
5
|
+
|
|
4
6
|
module Tapioca
|
|
5
7
|
module Dsl
|
|
6
8
|
module Helpers
|
|
@@ -98,7 +100,7 @@ module Tapioca
|
|
|
98
100
|
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array
|
|
99
101
|
"T::Array[#{type_for_activerecord_value(column_type.subtype)}]"
|
|
100
102
|
else
|
|
101
|
-
|
|
103
|
+
ActiveModelTypeHelper.type_for(column_type)
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
|
|
@@ -108,40 +110,6 @@ module Tapioca
|
|
|
108
110
|
!(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
|
|
109
111
|
end
|
|
110
112
|
|
|
111
|
-
sig { params(column_type: BasicObject).returns(String) }
|
|
112
|
-
def handle_unknown_type(column_type)
|
|
113
|
-
return "T.untyped" unless ActiveModel::Type::Value === column_type
|
|
114
|
-
return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(column_type)
|
|
115
|
-
|
|
116
|
-
lookup_return_type_of_method(column_type, :deserialize) ||
|
|
117
|
-
lookup_return_type_of_method(column_type, :cast) ||
|
|
118
|
-
lookup_arg_type_of_method(column_type, :serialize) ||
|
|
119
|
-
"T.untyped"
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
|
|
123
|
-
def lookup_return_type_of_method(column_type, method)
|
|
124
|
-
signature = Runtime::Reflection.signature_of(column_type.method(method))
|
|
125
|
-
return unless signature
|
|
126
|
-
|
|
127
|
-
return_type = signature.return_type
|
|
128
|
-
return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
|
|
129
|
-
|
|
130
|
-
return_type.to_s
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
|
|
134
|
-
def lookup_arg_type_of_method(column_type, method)
|
|
135
|
-
signature = Runtime::Reflection.signature_of(column_type.method(method))
|
|
136
|
-
return unless signature
|
|
137
|
-
|
|
138
|
-
# Arg types is an array [name, type] entries, so we desctructure the type of
|
|
139
|
-
# first argument to get the first argument type
|
|
140
|
-
_, first_argument_type = signature.arg_types.first
|
|
141
|
-
|
|
142
|
-
first_argument_type.to_s
|
|
143
|
-
end
|
|
144
|
-
|
|
145
113
|
sig { params(column_type: ActiveRecord::Enum::EnumType).returns(String) }
|
|
146
114
|
def enum_setter_type(column_type)
|
|
147
115
|
# In Rails < 7 this method is private. When support for that is dropped we can call the method directly
|
|
@@ -22,8 +22,10 @@ module Tapioca
|
|
|
22
22
|
CommonRelationMethodsModuleName = T.let("CommonRelationMethods", String)
|
|
23
23
|
|
|
24
24
|
RelationClassName = T.let("PrivateRelation", String)
|
|
25
|
+
RelationGroupChainClassName = T.let("PrivateRelationGroupChain", String)
|
|
25
26
|
RelationWhereChainClassName = T.let("PrivateRelationWhereChain", String)
|
|
26
27
|
AssociationRelationClassName = T.let("PrivateAssociationRelation", String)
|
|
28
|
+
AssociationRelationGroupChainClassName = T.let("PrivateAssociationRelationGroupChain", String)
|
|
27
29
|
AssociationRelationWhereChainClassName = T.let("PrivateAssociationRelationWhereChain", String)
|
|
28
30
|
AssociationsCollectionProxyClassName = T.let("PrivateCollectionProxy", String)
|
|
29
31
|
end
|
data/lib/tapioca/dsl/pipeline.rb
CHANGED
|
@@ -61,7 +61,7 @@ module Tapioca
|
|
|
61
61
|
.sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
|
|
62
62
|
|
|
63
63
|
# It's OK if there are no constants to process if we received a valid file/path.
|
|
64
|
-
if constants_to_process.empty? && requested_paths.
|
|
64
|
+
if constants_to_process.empty? && requested_paths.none? { |p| File.exist?(p) }
|
|
65
65
|
report_error(<<~ERROR)
|
|
66
66
|
No classes/modules can be matched for RBI generation.
|
|
67
67
|
Please check that the requested classes/modules include processable DSL methods.
|
data/lib/tapioca/gemfile.rb
CHANGED
|
@@ -178,7 +178,18 @@ module Tapioca
|
|
|
178
178
|
|
|
179
179
|
sig { void }
|
|
180
180
|
def parse_yard_docs
|
|
181
|
-
files.each
|
|
181
|
+
files.each do |path|
|
|
182
|
+
YARD.parse(path.to_s, [], Logger::Severity::FATAL)
|
|
183
|
+
rescue RangeError
|
|
184
|
+
# In some circumstances, YARD will raise an error when parsing a file
|
|
185
|
+
# that is actually valid Ruby. We don't want tapioca to halt in these
|
|
186
|
+
# cases, so we'll rescue the error, pretend like there was no
|
|
187
|
+
# documentation, and move on.
|
|
188
|
+
#
|
|
189
|
+
# This can be removed when https://github.com/lsegal/yard/issues/1536
|
|
190
|
+
# is resolved and released.
|
|
191
|
+
[]
|
|
192
|
+
end
|
|
182
193
|
end
|
|
183
194
|
|
|
184
195
|
sig { returns(T::Array[String]) }
|
|
@@ -87,24 +87,56 @@ module RBI
|
|
|
87
87
|
).void
|
|
88
88
|
end
|
|
89
89
|
def create_method(name, parameters: [], return_type: "T.untyped", class_method: false, visibility: RBI::Public.new,
|
|
90
|
+
comments: [])
|
|
91
|
+
sig = create_sig(parameters: parameters, return_type: return_type)
|
|
92
|
+
create_method_with_sigs(
|
|
93
|
+
name,
|
|
94
|
+
sigs: [sig],
|
|
95
|
+
parameters: parameters.map(&:param),
|
|
96
|
+
class_method: class_method,
|
|
97
|
+
visibility: visibility,
|
|
98
|
+
comments: comments,
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
sig do
|
|
103
|
+
params(
|
|
104
|
+
name: String,
|
|
105
|
+
sigs: T::Array[RBI::Sig],
|
|
106
|
+
parameters: T::Array[RBI::Param],
|
|
107
|
+
class_method: T::Boolean,
|
|
108
|
+
visibility: RBI::Visibility,
|
|
109
|
+
comments: T::Array[RBI::Comment],
|
|
110
|
+
).void
|
|
111
|
+
end
|
|
112
|
+
def create_method_with_sigs(name, sigs:, parameters: [], class_method: false, visibility: RBI::Public.new,
|
|
90
113
|
comments: [])
|
|
91
114
|
return unless Tapioca::RBIHelper.valid_method_name?(name)
|
|
92
115
|
|
|
93
|
-
sig = RBI::Sig.new(return_type: return_type)
|
|
94
116
|
method = RBI::Method.new(
|
|
95
117
|
name,
|
|
96
|
-
sigs:
|
|
118
|
+
sigs: sigs,
|
|
119
|
+
params: parameters,
|
|
97
120
|
is_singleton: class_method,
|
|
98
121
|
visibility: visibility,
|
|
99
122
|
comments: comments,
|
|
100
123
|
)
|
|
101
|
-
parameters.each do |param|
|
|
102
|
-
method << param.param
|
|
103
|
-
sig << RBI::SigParam.new(param.param.name, param.type)
|
|
104
|
-
end
|
|
105
124
|
self << method
|
|
106
125
|
end
|
|
107
126
|
|
|
127
|
+
sig do
|
|
128
|
+
params(
|
|
129
|
+
parameters: T::Array[RBI::TypedParam],
|
|
130
|
+
return_type: String,
|
|
131
|
+
).returns(RBI::Sig)
|
|
132
|
+
end
|
|
133
|
+
def create_sig(parameters: [], return_type: "T.untyped")
|
|
134
|
+
params = parameters.map do |param|
|
|
135
|
+
RBI::SigParam.new(param.param.name, param.type)
|
|
136
|
+
end
|
|
137
|
+
RBI::Sig.new(params: params, return_type: return_type)
|
|
138
|
+
end
|
|
139
|
+
|
|
108
140
|
private
|
|
109
141
|
|
|
110
142
|
sig { returns(T::Hash[String, RBI::Node]) }
|
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tapioca
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ufuk Kayserilioglu
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: exe
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2024-
|
|
14
|
+
date: 2024-03-25 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: bundler
|
|
@@ -81,21 +81,18 @@ dependencies:
|
|
|
81
81
|
requirements:
|
|
82
82
|
- - ">="
|
|
83
83
|
- !ruby/object:Gem::Version
|
|
84
|
-
version: 0.5.
|
|
84
|
+
version: 0.5.11087
|
|
85
85
|
type: :runtime
|
|
86
86
|
prerelease: false
|
|
87
87
|
version_requirements: !ruby/object:Gem::Requirement
|
|
88
88
|
requirements:
|
|
89
89
|
- - ">="
|
|
90
90
|
- !ruby/object:Gem::Version
|
|
91
|
-
version: 0.5.
|
|
91
|
+
version: 0.5.11087
|
|
92
92
|
- !ruby/object:Gem::Dependency
|
|
93
93
|
name: spoom
|
|
94
94
|
requirement: !ruby/object:Gem::Requirement
|
|
95
95
|
requirements:
|
|
96
|
-
- - "~>"
|
|
97
|
-
- !ruby/object:Gem::Version
|
|
98
|
-
version: 1.2.0
|
|
99
96
|
- - ">="
|
|
100
97
|
- !ruby/object:Gem::Version
|
|
101
98
|
version: 1.2.0
|
|
@@ -103,9 +100,6 @@ dependencies:
|
|
|
103
100
|
prerelease: false
|
|
104
101
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
102
|
requirements:
|
|
106
|
-
- - "~>"
|
|
107
|
-
- !ruby/object:Gem::Version
|
|
108
|
-
version: 1.2.0
|
|
109
103
|
- - ">="
|
|
110
104
|
- !ruby/object:Gem::Version
|
|
111
105
|
version: 1.2.0
|
|
@@ -209,6 +203,7 @@ files:
|
|
|
209
203
|
- lib/tapioca/dsl/extensions/active_record.rb
|
|
210
204
|
- lib/tapioca/dsl/extensions/frozen_record.rb
|
|
211
205
|
- lib/tapioca/dsl/extensions/kredis.rb
|
|
206
|
+
- lib/tapioca/dsl/helpers/active_model_type_helper.rb
|
|
212
207
|
- lib/tapioca/dsl/helpers/active_record_column_type_helper.rb
|
|
213
208
|
- lib/tapioca/dsl/helpers/active_record_constants_helper.rb
|
|
214
209
|
- lib/tapioca/dsl/helpers/graphql_type_helper.rb
|
|
@@ -293,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
293
288
|
- !ruby/object:Gem::Version
|
|
294
289
|
version: '0'
|
|
295
290
|
requirements: []
|
|
296
|
-
rubygems_version: 3.5.
|
|
291
|
+
rubygems_version: 3.5.6
|
|
297
292
|
signing_key:
|
|
298
293
|
specification_version: 4
|
|
299
294
|
summary: A Ruby Interface file generator for gems, core types and the Ruby standard
|