tapioca 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +84 -0
- data/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +1 -1
- data/lib/tapioca/cli.rb +9 -0
- data/lib/tapioca/commands/abstract_dsl.rb +76 -20
- data/lib/tapioca/commands/abstract_gem.rb +1 -0
- data/lib/tapioca/commands/annotations.rb +1 -1
- data/lib/tapioca/commands/gem_generate.rb +20 -4
- data/lib/tapioca/dsl/compiler.rb +24 -10
- data/lib/tapioca/dsl/compilers/aasm.rb +24 -17
- data/lib/tapioca/dsl/compilers/active_job.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +64 -16
- data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +17 -11
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +2 -2
- data/lib/tapioca/dsl/compilers/config.rb +1 -1
- data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +1 -1
- data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +1 -1
- data/lib/tapioca/dsl/compilers/url_helpers.rb +312 -8
- data/lib/tapioca/dsl/compilers.rb +1 -1
- data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +10 -3
- data/lib/tapioca/dsl/pipeline.rb +6 -27
- data/lib/tapioca/gem/events.rb +1 -1
- data/lib/tapioca/gem/listeners/documentation.rb +13 -8
- data/lib/tapioca/gem/listeners/methods.rb +79 -13
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +1 -1
- data/lib/tapioca/gem/pipeline.rb +3 -3
- data/lib/tapioca/gemfile.rb +13 -0
- data/lib/tapioca/helpers/env_helper.rb +1 -1
- data/lib/tapioca/helpers/file_helper.rb +42 -0
- data/lib/tapioca/helpers/rbi_files_helper.rb +96 -5
- data/lib/tapioca/helpers/rbi_helper.rb +17 -3
- data/lib/tapioca/helpers/sorbet_helper.rb +1 -1
- data/lib/tapioca/internal.rb +2 -0
- data/lib/tapioca/loaders/gem.rb +33 -0
- data/lib/tapioca/loaders/loader.rb +0 -13
- data/lib/tapioca/rbi_ext/model.rb +16 -2
- data/lib/tapioca/rbs/bootsnap_cache.rb +52 -0
- data/lib/tapioca/rbs/rewriter.rb +91 -25
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +1 -2
- data/lib/tapioca/runtime/generic_type_registry.rb +38 -14
- data/lib/tapioca/runtime/reflection.rb +18 -1
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +0 -20
- data/lib/tapioca/sorbet_ext/generic_type_patch.rb +48 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +11 -10
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 16f43e72a4dc002c9e6dcfa922274e3aff4de57318d48774eb7592ca4d548d8e
|
|
4
|
+
data.tar.gz: '03464479e0edf22b0abfe21536b674fd9000d2e7bd980585b4df01ab17f176e9'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ffbfb1deb3d1e54ed3d3d4a0301a6adcd81dbdba981f187223065d2e9bf67c406db3bde657103dd66b12e8a96ecb4953e0491a3c79feb104b95cbc5608270af
|
|
7
|
+
data.tar.gz: 548838d69fd2fe5f1be747aef82748362ce4394a3844fefca09037ab8bc33972cc70bcd869c4e22fb09112e2483e8703b3738e96c4e58f5e75004f7729e83087
|
data/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase
|
|
|
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
43
|
* [Importing hand written signatures from gem's `rbi/` folder](#importing-hand-written-signatures-from-gems-rbi-folder)
|
|
44
|
+
* [Writing custom gem extensions](#writing-custom-gem-extensions)
|
|
44
45
|
* [Pulling RBI annotations from remote sources](#pulling-rbi-annotations-from-remote-sources)
|
|
45
46
|
* [Basic authentication](#basic-authentication)
|
|
46
47
|
* [Using a .netrc file](#using-a-netrc-file)
|
|
@@ -50,6 +51,8 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase
|
|
|
50
51
|
* [Using DSL compiler options](#using-dsl-compiler-options)
|
|
51
52
|
* [Writing custom DSL compilers](#writing-custom-dsl-compilers)
|
|
52
53
|
* [Writing custom DSL extensions](#writing-custom-dsl-extensions)
|
|
54
|
+
* [Rewriting RBS comments to Sorbet signatures](#rewriting-rbs-comments-to-sorbet-signatures)
|
|
55
|
+
* [Caching rewrites with Bootsnap](#caching-rewrites-with-bootsnap)
|
|
53
56
|
* [RBI files for missing constants and methods](#rbi-files-for-missing-constants-and-methods)
|
|
54
57
|
* [Configuration](#configuration)
|
|
55
58
|
* [Editor Integration](#editor-integration)
|
|
@@ -343,6 +346,55 @@ This option can be used in CI to make sure the RBI files are *up-to-date* and en
|
|
|
343
346
|
|
|
344
347
|
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.
|
|
345
348
|
|
|
349
|
+
#### Writing custom gem extensions
|
|
350
|
+
|
|
351
|
+
Similar to [DSL extensions](#writing-custom-dsl-extensions), it is sometimes useful to patch and modify certain behavior
|
|
352
|
+
from gems to ensure that RBIs are generated correctly. Gem RBIs are generated by reflecting over what the gem defines at
|
|
353
|
+
runtime, which means that anything the gem never really defines is invisible to Tapioca. A common example is methods
|
|
354
|
+
handled by `method_missing`:
|
|
355
|
+
|
|
356
|
+
```ruby
|
|
357
|
+
# my_gem/lib/my_gem/settings.rb
|
|
358
|
+
|
|
359
|
+
module MyGem
|
|
360
|
+
class Settings
|
|
361
|
+
SETTINGS = [:host, :port, :timeout]
|
|
362
|
+
|
|
363
|
+
def method_missing(name, *args)
|
|
364
|
+
SETTINGS.include?(name) ? @config[name] : super
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def respond_to_missing?(name, include_private = false)
|
|
368
|
+
SETTINGS.include?(name) || super
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Since `host`, `port` and `timeout` are never defined as real methods, they will be missing from the generated RBI. An
|
|
375
|
+
extension can define them, so that Tapioca is able to see them:
|
|
376
|
+
|
|
377
|
+
```ruby
|
|
378
|
+
# my_gem/lib/tapioca/gem/extensions/my_gem.rb
|
|
379
|
+
|
|
380
|
+
require "my_gem"
|
|
381
|
+
|
|
382
|
+
module MyGem
|
|
383
|
+
class Settings
|
|
384
|
+
SETTINGS.each do |name|
|
|
385
|
+
define_method(name) { @config[name] }
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Extensions are only loaded during RBI generation, so they never change the behavior of the gem for applications using
|
|
392
|
+
it. They are also loaded before the bundle is required, which is why the extension has to require what it wants to
|
|
393
|
+
patch.
|
|
394
|
+
|
|
395
|
+
Applications can also define their own gem extensions by placing them inside the `sorbet/tapioca/gem/extensions`
|
|
396
|
+
directory.
|
|
397
|
+
|
|
346
398
|
### Pulling RBI annotations from remote sources
|
|
347
399
|
|
|
348
400
|
Since Tapioca does not perform any type inference, the RBI files generated for the gems do not contain any type signatures. Instead, Tapioca relies on the community to provide high-quality, manually written RBI annotations for public gems.
|
|
@@ -502,6 +554,8 @@ Options:
|
|
|
502
554
|
-w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
|
|
503
555
|
[--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
|
|
504
556
|
# Default: 120
|
|
557
|
+
[--max-diff-lines=N] # Max number of diff lines to include in the `dsl --verify` output
|
|
558
|
+
# Default: 250
|
|
505
559
|
-e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
|
|
506
560
|
# Default: development
|
|
507
561
|
-l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers
|
|
@@ -836,6 +890,35 @@ In order for DSL extensions to be discovered by Tapioca, they either needs to be
|
|
|
836
890
|
|
|
837
891
|
For more concrete and advanced examples, take a look at [Tapioca's default DSL extensions](https://github.com/Shopify/tapioca/tree/main/lib/tapioca/dsl/extensions).
|
|
838
892
|
|
|
893
|
+
### Rewriting RBS comments to Sorbet signatures
|
|
894
|
+
|
|
895
|
+
Tapioca translates [RBS comments](https://sorbet.org/docs/rbs-comments) into Sorbet `sig {}` blocks at file load time, so `sorbet-runtime` wraps the methods as if they had been written with native sigs. This is what lets the DSL command introspect signatures that were originally documented as RBS comments.
|
|
896
|
+
|
|
897
|
+
The rewriting is automatic on every `tapioca` invocation: [`require-hooks`](https://github.com/Shopify/require-hooks) intercepts `.rb` loads and `Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs` translates the source before Ruby compiles it to bytecode.
|
|
898
|
+
|
|
899
|
+
#### Caching rewrites with Bootsnap
|
|
900
|
+
|
|
901
|
+
`tapioca dsl` boots the app and eager-loads source files for introspection, so the rewrite runs across the whole codebase. On large applications this adds noticeable overhead. To cache the rewrite output across runs using [bootsnap](https://github.com/Shopify/bootsnap)'s iseq cache, you can set `TAPIOCA_RBS_CACHE=1`:
|
|
902
|
+
|
|
903
|
+
```shell
|
|
904
|
+
$ TAPIOCA_RBS_CACHE=1 bin/tapioca dsl
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
Tapioca configures Bootsnap's iseq cache against a dedicated directory (`tmp/cache/bootsnap-tapioca-rbs` by
|
|
908
|
+
default; override with `TAPIOCA_BOOTSNAP_CACHE_DIR`).
|
|
909
|
+
|
|
910
|
+
Tapioca writes the current `Gemfile.lock` digest to `.gemfile-lock-digest` inside that cache directory. When the
|
|
911
|
+
lockfile changes, Tapioca sees the digest mismatch and resets Bootsnap's cache payload before configuring Bootsnap.
|
|
912
|
+
This lets gem bumps that affect rewriting, such as `tapioca`, start from a fresh cache without accumulating old cache
|
|
913
|
+
directories.
|
|
914
|
+
|
|
915
|
+
`Bootsnap.setup` mutates a process-wide singleton, and a second call would overwrite Tapioca's dedicated cache directory and start writing rewritten iseqs into the host's normal cache. Tapioca enforces this under `TAPIOCA_RBS_CACHE=1`: after its own setup runs, any subsequent `Bootsnap.setup` raises a clear error pointing at the fix. Gate your host's `Bootsnap.setup` on the same env var. Rails apps do this in `config/boot.rb`:
|
|
916
|
+
|
|
917
|
+
```ruby
|
|
918
|
+
# e.g. config/boot.rb
|
|
919
|
+
require "bootsnap/setup" unless ENV["TAPIOCA_RBS_CACHE"] == "1"
|
|
920
|
+
```
|
|
921
|
+
|
|
839
922
|
### RBI files for missing constants and methods
|
|
840
923
|
|
|
841
924
|
Even after generating the RBIs, it is possible that some constants or methods are still undefined for Sorbet.
|
|
@@ -960,6 +1043,7 @@ dsl:
|
|
|
960
1043
|
quiet: false
|
|
961
1044
|
workers: 1
|
|
962
1045
|
rbi_max_line_length: 120
|
|
1046
|
+
max_diff_lines: 250
|
|
963
1047
|
environment: development
|
|
964
1048
|
list_compilers: false
|
|
965
1049
|
app_root: "."
|
|
@@ -116,7 +116,7 @@ module RubyLsp
|
|
|
116
116
|
|
|
117
117
|
#: (*untyped flags) -> Array[String]
|
|
118
118
|
def git_ls_gem_rbis(*flags)
|
|
119
|
-
self #: as untyped
|
|
119
|
+
self #: as untyped
|
|
120
120
|
.execute_in_project_path("git", "ls-files", *flags, "sorbet/rbi/gems/")
|
|
121
121
|
.lines
|
|
122
122
|
.map(&:strip)
|
data/lib/tapioca/cli.rb
CHANGED
|
@@ -116,6 +116,10 @@ module Tapioca
|
|
|
116
116
|
type: :numeric,
|
|
117
117
|
desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
|
|
118
118
|
default: DEFAULT_RBI_MAX_LINE_LENGTH
|
|
119
|
+
option :max_diff_lines,
|
|
120
|
+
type: :numeric,
|
|
121
|
+
desc: "Max number of diff lines to include in the `dsl --verify` output",
|
|
122
|
+
default: DEFAULT_MAX_DIFF_LINES
|
|
119
123
|
option :environment,
|
|
120
124
|
aliases: ["-e"],
|
|
121
125
|
type: :string,
|
|
@@ -149,6 +153,10 @@ module Tapioca
|
|
|
149
153
|
# Assume anything starting with a capital letter or colon is a class, otherwise a path
|
|
150
154
|
constants, paths = constant_or_paths.partition { |c| c =~ /\A[A-Z:]/ }
|
|
151
155
|
|
|
156
|
+
unless options[:max_diff_lines].positive?
|
|
157
|
+
raise MalformattedArgumentError, "Option '--max-diff-lines' must be a positive number"
|
|
158
|
+
end
|
|
159
|
+
|
|
152
160
|
command_args = {
|
|
153
161
|
requested_constants: constants,
|
|
154
162
|
requested_paths: paths.map { |p| Pathname.new(p) },
|
|
@@ -166,6 +174,7 @@ module Tapioca
|
|
|
166
174
|
halt_upon_load_error: options[:halt_upon_load_error],
|
|
167
175
|
compiler_options: options[:compiler_options],
|
|
168
176
|
lsp_addon: self.class.addon_mode,
|
|
177
|
+
max_diff_lines: options[:max_diff_lines],
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
command = if options[:verify]
|
|
@@ -5,6 +5,7 @@ module Tapioca
|
|
|
5
5
|
module Commands
|
|
6
6
|
# @abstract
|
|
7
7
|
class AbstractDsl < CommandWithoutTracker
|
|
8
|
+
include FileHelper
|
|
8
9
|
include SorbetHelper
|
|
9
10
|
include RBIFilesHelper
|
|
10
11
|
|
|
@@ -26,7 +27,8 @@ module Tapioca
|
|
|
26
27
|
#| ?app_root: String,
|
|
27
28
|
#| ?halt_upon_load_error: bool,
|
|
28
29
|
#| ?compiler_options: Hash[String, untyped],
|
|
29
|
-
#| ?lsp_addon: bool
|
|
30
|
+
#| ?lsp_addon: bool,
|
|
31
|
+
#| ?max_diff_lines: Integer
|
|
30
32
|
#| ) -> void
|
|
31
33
|
def initialize(
|
|
32
34
|
requested_constants:,
|
|
@@ -46,7 +48,8 @@ module Tapioca
|
|
|
46
48
|
app_root: ".",
|
|
47
49
|
halt_upon_load_error: true,
|
|
48
50
|
compiler_options: {},
|
|
49
|
-
lsp_addon: false
|
|
51
|
+
lsp_addon: false,
|
|
52
|
+
max_diff_lines: DEFAULT_MAX_DIFF_LINES
|
|
50
53
|
)
|
|
51
54
|
@requested_constants = requested_constants
|
|
52
55
|
@requested_paths = requested_paths
|
|
@@ -66,6 +69,7 @@ module Tapioca
|
|
|
66
69
|
@skip_constant = skip_constant
|
|
67
70
|
@compiler_options = compiler_options
|
|
68
71
|
@lsp_addon = lsp_addon
|
|
72
|
+
@max_diff_lines = max_diff_lines
|
|
69
73
|
|
|
70
74
|
super()
|
|
71
75
|
end
|
|
@@ -95,6 +99,11 @@ module Tapioca
|
|
|
95
99
|
)
|
|
96
100
|
end.compact
|
|
97
101
|
|
|
102
|
+
if @only.any?
|
|
103
|
+
say("Skipping stale RBI removal because `--only` is set.", :yellow)
|
|
104
|
+
return Set.new
|
|
105
|
+
end
|
|
106
|
+
|
|
98
107
|
files_to_purge = existing_rbi_files - generated_files
|
|
99
108
|
|
|
100
109
|
files_to_purge
|
|
@@ -242,7 +251,7 @@ module Tapioca
|
|
|
242
251
|
def perform_dsl_verification(dir)
|
|
243
252
|
diff = verify_dsl_rbi(tmp_dir: dir)
|
|
244
253
|
|
|
245
|
-
report_diff_and_exit_if_out_of_date(diff, :dsl)
|
|
254
|
+
report_diff_and_exit_if_out_of_date(diff, tmp_dir: dir, command: :dsl)
|
|
246
255
|
ensure
|
|
247
256
|
FileUtils.remove_entry(dir)
|
|
248
257
|
end
|
|
@@ -264,7 +273,7 @@ module Tapioca
|
|
|
264
273
|
@outpath / "#{underscore(constant_name)}.rbi"
|
|
265
274
|
end
|
|
266
275
|
|
|
267
|
-
#: (tmp_dir: Pathname) -> Hash[
|
|
276
|
+
#: (tmp_dir: Pathname) -> Hash[Pathname, Symbol]
|
|
268
277
|
def verify_dsl_rbi(tmp_dir:)
|
|
269
278
|
diff = {}
|
|
270
279
|
|
|
@@ -296,7 +305,7 @@ module Tapioca
|
|
|
296
305
|
diff
|
|
297
306
|
end
|
|
298
307
|
|
|
299
|
-
#: (Symbol cause, Array[
|
|
308
|
+
#: (Symbol cause, Array[Pathname] files) -> String
|
|
300
309
|
def build_error_for_files(cause, files)
|
|
301
310
|
filenames = files.map do |file|
|
|
302
311
|
@outpath / file
|
|
@@ -305,26 +314,73 @@ module Tapioca
|
|
|
305
314
|
" File(s) #{cause}:\n - #{filenames}"
|
|
306
315
|
end
|
|
307
316
|
|
|
308
|
-
#: (Hash[
|
|
309
|
-
def report_diff_and_exit_if_out_of_date(diff, command)
|
|
317
|
+
#: (Hash[Pathname, Symbol] diff, tmp_dir: Pathname, command: Symbol) -> void
|
|
318
|
+
def report_diff_and_exit_if_out_of_date(diff, tmp_dir:, command:)
|
|
310
319
|
if diff.empty?
|
|
311
320
|
say("Nothing to do, all RBIs are up-to-date.")
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
return
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
reasons = diff.group_by(&:last).sort.map do |cause, diff_for_cause|
|
|
325
|
+
build_error_for_files(cause, diff_for_cause.map(&:first))
|
|
326
|
+
end.join("\n")
|
|
327
|
+
|
|
328
|
+
diff_output, diff_truncated = build_diff_output(diff, tmp_dir)
|
|
329
|
+
|
|
330
|
+
diff_section =
|
|
331
|
+
if diff_truncated
|
|
332
|
+
"#{set_color("Diff truncated to #{@max_diff_lines} lines.\nChange the number of displayed lines using the `--max-diff-lines` flag.", :red)}\n#{diff_output.rstrip}"
|
|
333
|
+
elsif !diff_output.empty?
|
|
334
|
+
"#{set_color("Diff:", :red)}\n#{diff_output.rstrip}"
|
|
335
|
+
else
|
|
336
|
+
""
|
|
337
|
+
end
|
|
316
338
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
339
|
+
raise Tapioca::Error, <<~ERROR.rstrip
|
|
340
|
+
#{set_color("RBI files are out-of-date. In your development environment, please run:", :green)}
|
|
341
|
+
#{set_color("`#{default_command(command)}`", :green, :bold)}
|
|
342
|
+
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
|
|
343
|
+
If you don't observe any changes after running the command locally, ensure your database is in a good
|
|
344
|
+
state e.g. run `bin/rails db:reset`
|
|
323
345
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
346
|
+
#{set_color("Reason:", :red)}
|
|
347
|
+
#{reasons}
|
|
348
|
+
|
|
349
|
+
#{diff_section}
|
|
350
|
+
ERROR
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
#: (Hash[Pathname, Symbol] diff, Pathname tmp_dir) -> [String, bool]
|
|
354
|
+
def build_diff_output(diff, tmp_dir)
|
|
355
|
+
out = String.new
|
|
356
|
+
truncated = false #: bool
|
|
357
|
+
line_count = 0
|
|
358
|
+
|
|
359
|
+
diff.each do |file, status|
|
|
360
|
+
old_path = (@outpath / file)
|
|
361
|
+
new_path = (tmp_dir / file)
|
|
362
|
+
|
|
363
|
+
chunk = case status
|
|
364
|
+
when :added then file_diff(file, File::NULL, new_path)
|
|
365
|
+
when :removed then file_diff(file, old_path, File::NULL)
|
|
366
|
+
when :changed then file_diff(file, old_path, new_path)
|
|
367
|
+
else ""
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
next if chunk.nil?
|
|
371
|
+
|
|
372
|
+
remaining_lines = @max_diff_lines - line_count
|
|
373
|
+
if chunk.lines.count > remaining_lines
|
|
374
|
+
out << chunk.lines.first(remaining_lines).join
|
|
375
|
+
truncated = true
|
|
376
|
+
break
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
out << chunk
|
|
380
|
+
line_count += chunk.lines.count
|
|
327
381
|
end
|
|
382
|
+
|
|
383
|
+
[out, truncated]
|
|
328
384
|
end
|
|
329
385
|
|
|
330
386
|
#: (Pathname path) -> Array[Pathname]
|
|
@@ -30,6 +30,18 @@ module Tapioca
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
unless @skipped_gems.empty?
|
|
34
|
+
say("\nNote: Tapioca skipped RBI generation for the following gems because they are ignored by default:", [:yellow, :bold])
|
|
35
|
+
say(@skipped_gems.join(", "), [:yellow, :bold])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
user_excluded_gems = @gem_names & @exclude
|
|
39
|
+
|
|
40
|
+
unless user_excluded_gems.empty?
|
|
41
|
+
say("\nNote: Tapioca skipped RBI generation for the following gems because they were excluded:", [:yellow, :bold])
|
|
42
|
+
say(user_excluded_gems.join(", "), [:yellow, :bold])
|
|
43
|
+
end
|
|
44
|
+
|
|
33
45
|
if anything_done
|
|
34
46
|
validate_rbi_files(
|
|
35
47
|
command: default_command(:gem, @gem_names.join(" ")),
|
|
@@ -56,11 +68,15 @@ module Tapioca
|
|
|
56
68
|
gem = @bundle.gem(gem_name)
|
|
57
69
|
|
|
58
70
|
if gem.nil?
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
if @lsp_addon
|
|
72
|
+
next
|
|
73
|
+
elsif Gemfile::GemSpec::IGNORED_GEMS.include?(gem_name)
|
|
74
|
+
@skipped_gems << gem_name
|
|
75
|
+
next
|
|
76
|
+
else
|
|
77
|
+
raise Tapioca::Error, set_color("Error: Cannot find gem '#{gem_name}'", :red)
|
|
78
|
+
end
|
|
62
79
|
end
|
|
63
|
-
|
|
64
80
|
gems.concat(gem_dependencies(gem)) if @include_dependencies
|
|
65
81
|
gems << gem
|
|
66
82
|
end
|
data/lib/tapioca/dsl/compiler.rb
CHANGED
|
@@ -74,9 +74,21 @@ module Tapioca
|
|
|
74
74
|
@all_modules ||= if @@requested_constants.any?
|
|
75
75
|
@@requested_constants.grep(Module)
|
|
76
76
|
else
|
|
77
|
-
ObjectSpace.each_object(Module).to_a
|
|
77
|
+
ObjectSpace.each_object(Module).reject { |mod| deprecated_constant_proxy?(mod) }.to_a
|
|
78
78
|
end.freeze #: Enumerable[Module[top]]?
|
|
79
79
|
end
|
|
80
|
+
|
|
81
|
+
# Rails 8.1+ wraps deprecated constants in DeprecatedConstantProxy, which
|
|
82
|
+
# undefines most instance methods and warns from method_missing. Inspecting
|
|
83
|
+
# those modules during DSL discovery (is_a?, singleton_class, etc.) emits
|
|
84
|
+
# deprecation warnings even though Tapioca is only enumerating ObjectSpace.
|
|
85
|
+
# class_of uses Kernel#class so we can recognize the proxy without warning.
|
|
86
|
+
#: (Module[top] mod) -> bool
|
|
87
|
+
def deprecated_constant_proxy?(mod)
|
|
88
|
+
proxy_class_name = name_of(class_of(mod))
|
|
89
|
+
proxy_class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy" ||
|
|
90
|
+
proxy_class_name == "ActiveSupport::DeprecatedConstantProxy"
|
|
91
|
+
end
|
|
80
92
|
end
|
|
81
93
|
|
|
82
94
|
#: (
|
|
@@ -163,25 +175,27 @@ module Tapioca
|
|
|
163
175
|
parameters.each_with_index.map do |(type, name), index|
|
|
164
176
|
fallback_arg_name = "_arg#{index}"
|
|
165
177
|
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
sig_name = name ? name.to_s : fallback_arg_name
|
|
179
|
+
is_anonymous_parameter = anonymous_parameter_name?(type, sig_name)
|
|
180
|
+
sig_name = fallback_arg_name unless is_anonymous_parameter || valid_parameter_name?(sig_name)
|
|
181
|
+
param_name = is_anonymous_parameter ? nil : sig_name
|
|
168
182
|
method_type = T.must(method_types[index])
|
|
169
183
|
|
|
170
184
|
case type
|
|
171
185
|
when :req
|
|
172
|
-
create_param(
|
|
186
|
+
create_param(sig_name, type: method_type)
|
|
173
187
|
when :opt
|
|
174
|
-
create_opt_param(
|
|
188
|
+
create_opt_param(sig_name, type: method_type, default: "T.unsafe(nil)")
|
|
175
189
|
when :rest
|
|
176
|
-
create_rest_param(
|
|
190
|
+
create_rest_param(param_name, type: method_type)
|
|
177
191
|
when :keyreq
|
|
178
|
-
create_kw_param(
|
|
192
|
+
create_kw_param(sig_name, type: method_type)
|
|
179
193
|
when :key
|
|
180
|
-
create_kw_opt_param(
|
|
194
|
+
create_kw_opt_param(sig_name, type: method_type, default: "T.unsafe(nil)")
|
|
181
195
|
when :keyrest
|
|
182
|
-
create_kw_rest_param(
|
|
196
|
+
create_kw_rest_param(param_name, type: method_type)
|
|
183
197
|
when :block
|
|
184
|
-
create_block_param(
|
|
198
|
+
create_block_param(param_name, type: method_type)
|
|
185
199
|
else
|
|
186
200
|
raise "Unknown type `#{type}`."
|
|
187
201
|
end
|
|
@@ -10,18 +10,20 @@ module Tapioca
|
|
|
10
10
|
# This gem dynamically defines constants and methods at runtime. For
|
|
11
11
|
# example, given a class:
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
13
|
+
# ~~~rb
|
|
14
|
+
# class MyClass
|
|
15
|
+
# include AASM
|
|
15
16
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
17
|
+
# aasm do
|
|
18
|
+
# state :sleeping, initial: true
|
|
19
|
+
# state :running, :cleaning
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# end
|
|
21
|
+
# event :run do
|
|
22
|
+
# transitions from: :sleeping, to: :running
|
|
23
23
|
# end
|
|
24
24
|
# end
|
|
25
|
+
# end
|
|
26
|
+
# ~~~
|
|
25
27
|
#
|
|
26
28
|
# This will result in the following constants being defined:
|
|
27
29
|
#
|
|
@@ -94,14 +96,19 @@ module Tapioca
|
|
|
94
96
|
create_rest_param("opts", type: "T.untyped"),
|
|
95
97
|
create_block_param("block", type: "T.nilable(T.proc.void)"),
|
|
96
98
|
]
|
|
99
|
+
aasm_6_or_newer = ::Gem::Requirement.new(">= 6").satisfied_by?(::Gem::Version.new(::AASM::VERSION))
|
|
100
|
+
|
|
97
101
|
state_machine.events.each do |event|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
# As of aasm 6.0 a namespaced state machine defines only the
|
|
103
|
+
# namespaced event methods (e.g. `run_foo!`) and not the plain
|
|
104
|
+
# methods (`run!`) as aliases.
|
|
105
|
+
unless namespace && aasm_6_or_newer
|
|
106
|
+
model.create_method(event.name.to_s, parameters: parameters)
|
|
107
|
+
model.create_method("#{event.name}!", parameters: parameters)
|
|
108
|
+
model.create_method("#{event.name}_without_validation!", parameters: parameters)
|
|
109
|
+
model.create_method("may_#{event.name}?", return_type: "T::Boolean")
|
|
110
|
+
end
|
|
102
111
|
|
|
103
|
-
# For events, if there's a namespace the default methods are created in addition to
|
|
104
|
-
# namespaced ones.
|
|
105
112
|
next unless namespace
|
|
106
113
|
|
|
107
114
|
name = "#{event.name}_#{namespace}"
|
|
@@ -110,9 +117,9 @@ module Tapioca
|
|
|
110
117
|
model.create_method("#{name}!", parameters: parameters)
|
|
111
118
|
model.create_method("may_#{name}?", return_type: "T::Boolean")
|
|
112
119
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
120
|
+
# aasm < 6 does not define a namespaced `_without_validation!`
|
|
121
|
+
# method.
|
|
122
|
+
model.create_method("#{name}_without_validation!", parameters: parameters) if aasm_6_or_newer
|
|
116
123
|
end
|
|
117
124
|
end
|
|
118
125
|
|
|
@@ -43,7 +43,7 @@ module Tapioca
|
|
|
43
43
|
# @override
|
|
44
44
|
#: -> void
|
|
45
45
|
def decorate
|
|
46
|
-
return unless constant.
|
|
46
|
+
return unless constant.method_defined?(:perform, false)
|
|
47
47
|
|
|
48
48
|
root.create_path(constant) do |job|
|
|
49
49
|
method = constant.instance_method(:perform)
|
|
@@ -67,7 +67,7 @@ module Tapioca
|
|
|
67
67
|
|
|
68
68
|
private
|
|
69
69
|
|
|
70
|
-
HANDLED_METHOD_TARGETS = ["attribute", "attribute="] #: Array[String]
|
|
70
|
+
HANDLED_METHOD_TARGETS = ["attribute", "attribute="].freeze #: Array[String]
|
|
71
71
|
|
|
72
72
|
#: -> Array[[::String, ::String]]
|
|
73
73
|
def attribute_methods_for_constant
|