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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a3ddc2c5d6e123488c0c4f2d0aad9a5de62a8145f2c3eb2d6b73de6f814afc0
4
- data.tar.gz: 908740542e026711e9ee85d46ce3828533f965c302039b645213adc016d74258
3
+ metadata.gz: f655f69697a5c375a3e04eef733d55213d2ea83b7392f35e3f81ed61ef4f93e4
4
+ data.tar.gz: 1ba60b67539a5034080dd9247f2f005593f25bcb2f01d72b72bccfb9a1f77f73
5
5
  SHA512:
6
- metadata.gz: 0622e47a67918e24c8ec4434892265039b38c0de522b3e2ad1ca156d62b1487317384a7dcc928249c7d5cec62493bee5e36d0cbd2342b97a39011056485d6f30
7
- data.tar.gz: 3082afd8096436fee4ecf4cb46b3a4232f77e34fa53bfb60b3182d12a7fcb5b3df58fe8ce67d1c8dcfb9b4a68dd859429bb6f0cf65eb8708991dfb2ff018720b
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>] # Path to the Tapioca configuration file
87
- # Default: sorbet/tapioca/config.yml
88
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
89
- # Default: false
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>] # Path to the Tapioca configuration file
121
- # Default: sorbet/tapioca/config.yml
122
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
123
- # Default: false
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] # The output directory for generated gem RBI files
168
- # Default: sorbet/rbi/gems
169
- [--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
170
- # Default: true
171
- [--all], [--no-all] # Regenerate RBI files for all gems
172
- # Default: false
173
- --pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
174
- --post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
175
- # Default: sorbet/tapioca/require.rb
176
- -x, [--exclude=gem [gem ...]] # Exclude the given gem(s) from RBI generation
177
- [--include-dependencies], [--no-include-dependencies] # Generate RBI files for dependencies of the given gem(s)
178
- # Default: false
179
- --typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for generated gem RBIs
180
- # Default: {"activesupport"=>"false"}
181
- [--verify], [--no-verify] # Verify RBIs are up-to-date
182
- # Default: false
183
- [--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
184
- # Default: true
185
- [--loc], [--no-loc] # Include comments with source location when generating RBIs
186
- # Default: true
187
- [--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
188
- # Default: true
189
- -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
190
- [--auto-strictness], [--no-auto-strictness] # Autocorrect strictness in gem RBIs in case of conflict with the DSL RBIs
191
- # Default: true
192
- --dsl-dir, [--dsl-dir=directory] # The DSL directory used to correct gems strictnesses
193
- # Default: sorbet/rbi/dsl
194
- [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
195
- # Default: 120
196
- -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
197
- # Default: development
198
- [--halt-upon-load-error], [--no-halt-upon-load-error] # Halt upon a load error while loading the Rails application
199
- # Default: true
200
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
201
- # Default: sorbet/tapioca/config.yml
202
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
203
- # Default: false
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 pry
227
+ $ bundle exec irb
227
228
 
228
- [1] pry(main)> require 'better_html'
229
+ irb(main):001> require 'better_html'
229
230
  => true
230
- [2] pry(main)> BetterHtml
231
+ irb(main):002> BetterHtml
231
232
  => BetterHtml
232
- [3] pry(main)> BetterHtml::Parser
233
- NameError: uninitialized constant BetterHtml::Parser
234
- from (pry):3:in `__pry__`
235
- [4] pry(main)> require 'better_html/parser'
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
- [5] pry(main)> BetterHtml::Parser
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 up-to-date with the latest changes in your `Gemfile.lock`, Tapioca provides a `--verify` option:
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 on CI to make sure the RBI files are always up-to-date and ensure accurate type checking. **Warning**: doing so will break your normal Dependabot workflow as every pull-request opened to bump a gem version will fail CI since the RBI will be out-of-date and will require you to manually run `bin/tapioca gems` to update them.
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] # Use .netrc to authenticate to private sources
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] # Verbose output for debugging purposes
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] # The output directory for generated DSL RBI files
471
- # Default: sorbet/rbi/dsl
472
- [--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
473
- # Default: true
474
- [--only=compiler [compiler ...]] # Only run supplied DSL compiler(s)
475
- [--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s)
476
- [--verify], [--no-verify] # Verifies RBIs are up-to-date
477
- # Default: false
478
- -q, [--quiet], [--no-quiet] # Suppresses file creation output
479
- # Default: false
480
- -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: 2)
481
- # Default: 2
482
- [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
483
- # Default: 120
484
- -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
485
- # Default: development
486
- -l, [--list-compilers], [--no-list-compilers] # List all loaded compilers
487
- # Default: false
488
- [--app-root=APP_ROOT] # The path to the Rails application
489
- # Default: .
490
- [--halt-upon-load-error], [--no-halt-upon-load-error] # Halt upon a load error while loading the Rails application
491
- # Default: true
492
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
493
- # Default: sorbet/tapioca/config.yml
494
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
495
- # Default: false
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] # Path to gem RBIs
844
- # Default: sorbet/rbi/gems
845
- [--dsl-rbi-dir=DSL_RBI_DIR] # Path to DSL RBIs
846
- # Default: sorbet/rbi/dsl
847
- [--shim-rbi-dir=SHIM_RBI_DIR] # Path to shim RBIs
848
- # Default: sorbet/rbi/shims
849
- [--annotations-rbi-dir=ANNOTATIONS_RBI_DIR] # Path to annotations RBIs
850
- # Default: sorbet/rbi/annotations
851
- [--todo-rbi-file=TODO_RBI_FILE] # Path to the generated todo RBI file
852
- # Default: sorbet/rbi/todo.rbi
853
- [--payload], [--no-payload] # Check shims against Sorbet's payload
854
- # Default: true
855
- -w, [--workers=N] # Number of parallel workers (default: auto)
856
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
857
- # Default: sorbet/tapioca/config.yml
858
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
859
- # Default: false
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("Error: Cannot find compiler '#{name}'", :red)
178
+ set_color("Warning: Cannot find compiler '#{name}'", :yellow)
179
179
  end.join("\n")
180
180
 
181
- raise Thor::Error, message
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 = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
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.class.name.to_s
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
- create_method_from_def(klass, constant.instance_method(method))
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
- create_relation_method(
483
- method_name,
484
- parameters: [
485
- create_rest_param("args", type: "T.untyped"),
486
- create_block_param("blk", type: "T.untyped"),
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
- create_common_method(
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
- parameters: [
566
- create_rest_param("args", type: "T.untyped"),
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
- create_common_method(
603
- method_name,
604
- parameters: [
605
- create_opt_param("limit", type: "T.untyped", default: "nil"),
606
- ],
607
- return_type: "T.untyped",
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: "T.untyped",
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: "T.untyped",
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: "T.untyped",
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
- create_common_method(
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
- create_kw_opt_param("start", type: "T.untyped", default: "nil"),
705
- create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
706
- create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
707
- create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
708
- *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
709
- create_block_param("block", type: "T.nilable(T.proc.params(object: #{constant_name}).void)"),
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
- create_common_method(
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
- create_kw_opt_param("start", type: "T.untyped", default: "nil"),
719
- create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
720
- create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
721
- create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
722
- *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
723
- create_block_param(
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
- create_common_method(
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
- create_kw_opt_param("of", type: "Integer", default: "1000"),
737
- create_kw_opt_param("start", type: "T.untyped", default: "nil"),
738
- create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
739
- create_kw_opt_param("load", type: "T.untyped", default: "false"),
740
- create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
741
- *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
742
- *(create_kw_opt_param("use_ranges", type: "T.untyped", default: "nil") if use_ranges),
743
- create_block_param("block", type: "T.nilable(T.proc.params(object: #{RelationClassName}).void)"),
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
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- return unless defined?(Config)
4
+ return unless defined?(Config) && defined?(Config::VERSION) && defined?(Config.const_name)
5
5
 
6
6
  module Tapioca
7
7
  module Dsl
@@ -302,6 +302,13 @@ module Tapioca
302
302
  return_type: "void",
303
303
  )
304
304
 
305
+ if desc.has_presence?
306
+ klass.create_method(
307
+ "has_#{field.name}?",
308
+ return_type: "Object",
309
+ )
310
+ end
311
+
305
312
  field
306
313
  end
307
314
 
@@ -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
- handle_unknown_type(column_type)
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
@@ -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.select { |p| File.exist?(p) }.empty?
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.
@@ -178,7 +178,18 @@ module Tapioca
178
178
 
179
179
  sig { void }
180
180
  def parse_yard_docs
181
- files.each { |path| YARD.parse(path.to_s, [], Logger::Severity::FATAL) }
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: [sig],
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]) }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.12.0"
5
+ VERSION = "0.13.0"
6
6
  end
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.12.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-01-25 00:00:00.000000000 Z
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.10820
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.10820
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.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