tapioca 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|