tapioca 0.19.2 → 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 +87 -45
- data/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +1 -1
- data/lib/tapioca/cli.rb +10 -11
- data/lib/tapioca/commands/abstract_dsl.rb +71 -20
- data/lib/tapioca/commands/abstract_gem.rb +1 -0
- data/lib/tapioca/commands/annotations.rb +1 -1
- data/lib/tapioca/commands/dsl_generate.rb +0 -15
- 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_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 +7 -2
- data/lib/tapioca/dsl/pipeline.rb +3 -0
- 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 +1 -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 +32 -21
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +1 -2
- data/lib/tapioca/runtime/generic_type_registry.rb +27 -2
- data/lib/tapioca/runtime/reflection.rb +18 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +11 -10
- metadata +6 -4
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)
|
|
@@ -52,7 +53,6 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase
|
|
|
52
53
|
* [Writing custom DSL extensions](#writing-custom-dsl-extensions)
|
|
53
54
|
* [Rewriting RBS comments to Sorbet signatures](#rewriting-rbs-comments-to-sorbet-signatures)
|
|
54
55
|
* [Caching rewrites with Bootsnap](#caching-rewrites-with-bootsnap)
|
|
55
|
-
* [Priming the cache from CI](#priming-the-cache-from-ci)
|
|
56
56
|
* [RBI files for missing constants and methods](#rbi-files-for-missing-constants-and-methods)
|
|
57
57
|
* [Configuration](#configuration)
|
|
58
58
|
* [Editor Integration](#editor-integration)
|
|
@@ -346,6 +346,55 @@ This option can be used in CI to make sure the RBI files are *up-to-date* and en
|
|
|
346
346
|
|
|
347
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.
|
|
348
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
|
+
|
|
349
398
|
### Pulling RBI annotations from remote sources
|
|
350
399
|
|
|
351
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.
|
|
@@ -492,35 +541,35 @@ Usage:
|
|
|
492
541
|
tapioca dsl [constant...]
|
|
493
542
|
|
|
494
543
|
Options:
|
|
495
|
-
--out, -o, [--outdir=directory]
|
|
496
|
-
|
|
497
|
-
[--file-header], [--no-file-header], [--skip-file-header]
|
|
498
|
-
|
|
499
|
-
[--only=compiler [compiler ...]]
|
|
500
|
-
[--exclude=compiler [compiler ...]]
|
|
501
|
-
[--verify], [--no-verify], [--skip-verify]
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
[--
|
|
509
|
-
|
|
510
|
-
-e, [--environment=ENVIRONMENT]
|
|
511
|
-
|
|
512
|
-
-l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers]
|
|
513
|
-
|
|
514
|
-
[--app-root=APP_ROOT]
|
|
515
|
-
|
|
516
|
-
[--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error]
|
|
517
|
-
|
|
518
|
-
[--skip-constant=constant [constant ...]]
|
|
519
|
-
[--compiler-options=key:value]
|
|
520
|
-
-c, [--config=<config file path>]
|
|
521
|
-
|
|
522
|
-
-V, [--verbose], [--no-verbose], [--skip-verbose]
|
|
523
|
-
|
|
544
|
+
--out, -o, [--outdir=directory] # The output directory for generated DSL RBI files
|
|
545
|
+
# Default: sorbet/rbi/dsl
|
|
546
|
+
[--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
|
547
|
+
# Default: true
|
|
548
|
+
[--only=compiler [compiler ...]] # Only run supplied DSL compiler(s)
|
|
549
|
+
[--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s)
|
|
550
|
+
[--verify], [--no-verify], [--skip-verify] # Verifies RBIs are up-to-date
|
|
551
|
+
# Default: false
|
|
552
|
+
-q, [--quiet], [--no-quiet], [--skip-quiet] # Suppresses file creation output
|
|
553
|
+
# Default: false
|
|
554
|
+
-w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
|
|
555
|
+
[--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
|
|
556
|
+
# Default: 120
|
|
557
|
+
[--max-diff-lines=N] # Max number of diff lines to include in the `dsl --verify` output
|
|
558
|
+
# Default: 250
|
|
559
|
+
-e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
|
|
560
|
+
# Default: development
|
|
561
|
+
-l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers
|
|
562
|
+
# Default: false
|
|
563
|
+
[--app-root=APP_ROOT] # The path to the Rails application
|
|
564
|
+
# Default: .
|
|
565
|
+
[--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application
|
|
566
|
+
# Default: true
|
|
567
|
+
[--skip-constant=constant [constant ...]] # Do not generate RBI definitions for the given application constant(s)
|
|
568
|
+
[--compiler-options=key:value] # Options to pass to the DSL compilers
|
|
569
|
+
-c, [--config=<config file path>] # Path to the Tapioca configuration file
|
|
570
|
+
# Default: sorbet/tapioca/config.yml
|
|
571
|
+
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
|
|
572
|
+
# Default: false
|
|
524
573
|
|
|
525
574
|
Generate RBIs for dynamic methods
|
|
526
575
|
```
|
|
@@ -855,7 +904,13 @@ The rewriting is automatic on every `tapioca` invocation: [`require-hooks`](http
|
|
|
855
904
|
$ TAPIOCA_RBS_CACHE=1 bin/tapioca dsl
|
|
856
905
|
```
|
|
857
906
|
|
|
858
|
-
Tapioca configures Bootsnap's iseq cache against a dedicated directory (`tmp/cache/bootsnap-tapioca-rbs` by
|
|
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.
|
|
859
914
|
|
|
860
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`:
|
|
861
916
|
|
|
@@ -864,19 +919,6 @@ Tapioca configures Bootsnap's iseq cache against a dedicated directory (`tmp/cac
|
|
|
864
919
|
require "bootsnap/setup" unless ENV["TAPIOCA_RBS_CACHE"] == "1"
|
|
865
920
|
```
|
|
866
921
|
|
|
867
|
-
#### Priming the cache from CI
|
|
868
|
-
|
|
869
|
-
For CI pipelines that want to populate the cache once and have downstream jobs read from a warm copy, use `--only-bootsnap-rbs-cache`. This pattern lets you scope cache writes to a single job (the prime) so PR-side jobs read from it without uploading on every successful build:
|
|
870
|
-
|
|
871
|
-
```shell
|
|
872
|
-
# Prime: populate the cache.
|
|
873
|
-
$ TAPIOCA_RBS_CACHE=1 bin/tapioca dsl --only-bootsnap-rbs-cache
|
|
874
|
-
|
|
875
|
-
# Consumer: read from the populated cache.
|
|
876
|
-
# BOOTSNAP_READONLY=1 prevents bootsnap from writing back to a read-only mount.
|
|
877
|
-
$ TAPIOCA_RBS_CACHE=1 BOOTSNAP_READONLY=1 bin/tapioca dsl
|
|
878
|
-
```
|
|
879
|
-
|
|
880
922
|
### RBI files for missing constants and methods
|
|
881
923
|
|
|
882
924
|
Even after generating the RBIs, it is possible that some constants or methods are still undefined for Sorbet.
|
|
@@ -998,10 +1040,10 @@ dsl:
|
|
|
998
1040
|
only: []
|
|
999
1041
|
exclude: []
|
|
1000
1042
|
verify: false
|
|
1001
|
-
only_bootsnap_rbs_cache: false
|
|
1002
1043
|
quiet: false
|
|
1003
1044
|
workers: 1
|
|
1004
1045
|
rbi_max_line_length: 120
|
|
1046
|
+
max_diff_lines: 250
|
|
1005
1047
|
environment: development
|
|
1006
1048
|
list_compilers: false
|
|
1007
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
|
@@ -103,10 +103,6 @@ module Tapioca
|
|
|
103
103
|
type: :boolean,
|
|
104
104
|
default: false,
|
|
105
105
|
desc: "Verifies RBIs are up-to-date"
|
|
106
|
-
option :only_bootsnap_rbs_cache,
|
|
107
|
-
type: :boolean,
|
|
108
|
-
default: false,
|
|
109
|
-
desc: "Only boot the application and load DSL extensions/compilers to populate the bootsnap iseq cache, then exit. Skips compiler execution and RBI generation. Mutually exclusive with --verify and --list-compilers."
|
|
110
106
|
option :quiet,
|
|
111
107
|
aliases: ["-q"],
|
|
112
108
|
type: :boolean,
|
|
@@ -120,6 +116,10 @@ module Tapioca
|
|
|
120
116
|
type: :numeric,
|
|
121
117
|
desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
|
|
122
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
|
|
123
123
|
option :environment,
|
|
124
124
|
aliases: ["-e"],
|
|
125
125
|
type: :string,
|
|
@@ -150,15 +150,13 @@ module Tapioca
|
|
|
150
150
|
def dsl(*constant_or_paths)
|
|
151
151
|
set_environment(options)
|
|
152
152
|
|
|
153
|
-
if options[:only_bootsnap_rbs_cache] && (options[:verify] || options[:list_compilers])
|
|
154
|
-
conflicting = options[:verify] ? "--verify" : "--list-compilers"
|
|
155
|
-
raise MalformattedArgumentError,
|
|
156
|
-
"Options '--only-bootsnap-rbs-cache' and '#{conflicting}' are mutually exclusive"
|
|
157
|
-
end
|
|
158
|
-
|
|
159
153
|
# Assume anything starting with a capital letter or colon is a class, otherwise a path
|
|
160
154
|
constants, paths = constant_or_paths.partition { |c| c =~ /\A[A-Z:]/ }
|
|
161
155
|
|
|
156
|
+
unless options[:max_diff_lines].positive?
|
|
157
|
+
raise MalformattedArgumentError, "Option '--max-diff-lines' must be a positive number"
|
|
158
|
+
end
|
|
159
|
+
|
|
162
160
|
command_args = {
|
|
163
161
|
requested_constants: constants,
|
|
164
162
|
requested_paths: paths.map { |p| Pathname.new(p) },
|
|
@@ -176,6 +174,7 @@ module Tapioca
|
|
|
176
174
|
halt_upon_load_error: options[:halt_upon_load_error],
|
|
177
175
|
compiler_options: options[:compiler_options],
|
|
178
176
|
lsp_addon: self.class.addon_mode,
|
|
177
|
+
max_diff_lines: options[:max_diff_lines],
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
command = if options[:verify]
|
|
@@ -183,7 +182,7 @@ module Tapioca
|
|
|
183
182
|
elsif options[:list_compilers]
|
|
184
183
|
Commands::DslCompilerList.new(**command_args)
|
|
185
184
|
else
|
|
186
|
-
Commands::DslGenerate.new(**command_args
|
|
185
|
+
Commands::DslGenerate.new(**command_args)
|
|
187
186
|
end
|
|
188
187
|
|
|
189
188
|
command.run
|
|
@@ -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
|
|
@@ -247,7 +251,7 @@ module Tapioca
|
|
|
247
251
|
def perform_dsl_verification(dir)
|
|
248
252
|
diff = verify_dsl_rbi(tmp_dir: dir)
|
|
249
253
|
|
|
250
|
-
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)
|
|
251
255
|
ensure
|
|
252
256
|
FileUtils.remove_entry(dir)
|
|
253
257
|
end
|
|
@@ -269,7 +273,7 @@ module Tapioca
|
|
|
269
273
|
@outpath / "#{underscore(constant_name)}.rbi"
|
|
270
274
|
end
|
|
271
275
|
|
|
272
|
-
#: (tmp_dir: Pathname) -> Hash[
|
|
276
|
+
#: (tmp_dir: Pathname) -> Hash[Pathname, Symbol]
|
|
273
277
|
def verify_dsl_rbi(tmp_dir:)
|
|
274
278
|
diff = {}
|
|
275
279
|
|
|
@@ -301,7 +305,7 @@ module Tapioca
|
|
|
301
305
|
diff
|
|
302
306
|
end
|
|
303
307
|
|
|
304
|
-
#: (Symbol cause, Array[
|
|
308
|
+
#: (Symbol cause, Array[Pathname] files) -> String
|
|
305
309
|
def build_error_for_files(cause, files)
|
|
306
310
|
filenames = files.map do |file|
|
|
307
311
|
@outpath / file
|
|
@@ -310,26 +314,73 @@ module Tapioca
|
|
|
310
314
|
" File(s) #{cause}:\n - #{filenames}"
|
|
311
315
|
end
|
|
312
316
|
|
|
313
|
-
#: (Hash[
|
|
314
|
-
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:)
|
|
315
319
|
if diff.empty?
|
|
316
320
|
say("Nothing to do, all RBIs are up-to-date.")
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
338
|
+
|
|
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`
|
|
321
345
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
#{set_color("`#{default_command(command)}`", :green, :bold)}
|
|
325
|
-
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
|
|
326
|
-
If you don't observe any changes after running the command locally, ensure your database is in a good
|
|
327
|
-
state e.g. run `bin/rails db:reset`
|
|
346
|
+
#{set_color("Reason:", :red)}
|
|
347
|
+
#{reasons}
|
|
328
348
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
332
381
|
end
|
|
382
|
+
|
|
383
|
+
[out, truncated]
|
|
333
384
|
end
|
|
334
385
|
|
|
335
386
|
#: (Pathname path) -> Array[Pathname]
|
|
@@ -4,12 +4,6 @@
|
|
|
4
4
|
module Tapioca
|
|
5
5
|
module Commands
|
|
6
6
|
class DslGenerate < AbstractDsl
|
|
7
|
-
#: (?only_bootsnap_rbs_cache: bool, **untyped) -> void
|
|
8
|
-
def initialize(only_bootsnap_rbs_cache: false, **kwargs)
|
|
9
|
-
@only_bootsnap_rbs_cache = only_bootsnap_rbs_cache
|
|
10
|
-
super(**T.unsafe(kwargs))
|
|
11
|
-
end
|
|
12
|
-
|
|
13
7
|
private
|
|
14
8
|
|
|
15
9
|
# @override
|
|
@@ -17,15 +11,6 @@ module Tapioca
|
|
|
17
11
|
def execute
|
|
18
12
|
load_application
|
|
19
13
|
|
|
20
|
-
if @only_bootsnap_rbs_cache
|
|
21
|
-
if ENV["TAPIOCA_RBS_CACHE"] == "1"
|
|
22
|
-
say("Bootsnap RBS cache populated, exiting before RBI generation.", :green)
|
|
23
|
-
else
|
|
24
|
-
say_error("Warning: --only-bootsnap-rbs-cache requires TAPIOCA_RBS_CACHE=1 to populate the cache", :yellow)
|
|
25
|
-
end
|
|
26
|
-
return
|
|
27
|
-
end
|
|
28
|
-
|
|
29
14
|
say("Compiling DSL RBI files...")
|
|
30
15
|
say("")
|
|
31
16
|
|
|
@@ -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
|