tapioca 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +491 -73
  4. data/lib/tapioca/cli.rb +40 -3
  5. data/lib/tapioca/commands/annotations.rb +154 -0
  6. data/lib/tapioca/commands/dsl.rb +20 -1
  7. data/lib/tapioca/commands/gem.rb +17 -57
  8. data/lib/tapioca/commands/init.rb +1 -0
  9. data/lib/tapioca/commands/todo.rb +4 -2
  10. data/lib/tapioca/commands.rb +1 -0
  11. data/lib/tapioca/dsl/compiler.rb +2 -2
  12. data/lib/tapioca/dsl/compilers/aasm.rb +1 -1
  13. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
  14. data/lib/tapioca/dsl/compilers/action_mailer.rb +1 -1
  15. data/lib/tapioca/dsl/compilers/active_job.rb +1 -1
  16. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
  17. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +1 -1
  18. data/lib/tapioca/dsl/compilers/active_record_associations.rb +1 -1
  19. data/lib/tapioca/dsl/compilers/active_record_columns.rb +1 -1
  20. data/lib/tapioca/dsl/compilers/active_record_enum.rb +1 -1
  21. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +3 -1
  22. data/lib/tapioca/dsl/compilers/active_record_relations.rb +8 -8
  23. data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
  24. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +1 -1
  25. data/lib/tapioca/dsl/compilers/active_resource.rb +1 -1
  26. data/lib/tapioca/dsl/compilers/active_storage.rb +6 -2
  27. data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
  28. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +1 -1
  29. data/lib/tapioca/dsl/compilers/config.rb +2 -2
  30. data/lib/tapioca/dsl/compilers/frozen_record.rb +1 -1
  31. data/lib/tapioca/dsl/compilers/identity_cache.rb +1 -1
  32. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +1 -1
  33. data/lib/tapioca/dsl/compilers/protobuf.rb +27 -3
  34. data/lib/tapioca/dsl/compilers/rails_generators.rb +1 -1
  35. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +1 -1
  36. data/lib/tapioca/dsl/compilers/smart_properties.rb +1 -1
  37. data/lib/tapioca/dsl/compilers/state_machines.rb +1 -1
  38. data/lib/tapioca/dsl/compilers/url_helpers.rb +5 -2
  39. data/lib/tapioca/dsl/helpers/param_helper.rb +4 -1
  40. data/lib/tapioca/dsl/pipeline.rb +32 -1
  41. data/lib/tapioca/dsl.rb +6 -0
  42. data/lib/tapioca/executor.rb +4 -46
  43. data/lib/tapioca/gem/listeners/methods.rb +26 -1
  44. data/lib/tapioca/gem/listeners/sorbet_props.rb +1 -1
  45. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +1 -0
  46. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +1 -1
  47. data/lib/tapioca/gem/pipeline.rb +5 -1
  48. data/lib/tapioca/gemfile.rb +50 -3
  49. data/lib/tapioca/helpers/config_helper.rb +13 -0
  50. data/lib/tapioca/helpers/rbi_helper.rb +114 -7
  51. data/lib/tapioca/helpers/shims_helper.rb +36 -8
  52. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  53. data/lib/tapioca/helpers/sorbet_helper.rb +5 -11
  54. data/lib/tapioca/helpers/test/content.rb +1 -0
  55. data/lib/tapioca/helpers/test/dsl_compiler.rb +1 -0
  56. data/lib/tapioca/helpers/test/template.rb +1 -0
  57. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  58. data/lib/tapioca/internal.rb +4 -1
  59. data/lib/tapioca/rbi_ext/model.rb +14 -2
  60. data/lib/tapioca/repo_index.rb +41 -0
  61. data/lib/tapioca/runtime/generic_type_registry.rb +4 -2
  62. data/lib/tapioca/runtime/loader.rb +3 -0
  63. data/lib/tapioca/runtime/reflection.rb +17 -13
  64. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +38 -21
  65. data/lib/tapioca/static/symbol_table_parser.rb +2 -0
  66. data/lib/tapioca/version.rb +1 -1
  67. data/lib/tapioca.rb +5 -0
  68. metadata +26 -21
data/README.md CHANGED
@@ -1,60 +1,48 @@
1
1
  > :warning: **Note**: This software is currently under active development. The API and interface should be considered unstable until a v1.0.0 release.
2
2
 
3
- # Tapioca
3
+ <p align="center">
4
+ <img alt="Tapioca logo" width="200" src="misc/tapioca-logo.svg" />
5
+ </p>
4
6
 
5
- ![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
6
-
7
- Tapioca is a library used to generate RBI (Ruby interface) files for use with [Sorbet](https://sorbet.org). RBI files provide the structure (classes, modules, methods, parameters) of the gem/library to Sorbet to assist with typechecking.
8
-
9
- As yet, no gem exports type information in a consumable format and it would be a huge effort to manually maintain such an interface file for all the gems that your codebase depends on. Thus, there is a need for an automated way to generate the appropriate RBI file for a given gem. The `tapioca` gem, developed at Shopify, is able to do exactly that to almost 99% accuracy. It can generate the definitions for all statically defined types and most of the runtime defined types exported from Ruby gems (non-Ruby gems are not handled yet).
10
-
11
- When you run `tapioca gem` in a project, `tapioca` loads all the gems that are in your dependency list from the Gemfile into memory. It then performs runtime introspection on the loaded types to understand their structure and generates an appropriate RBI file for each gem with a versioned filename.
12
-
13
- ## Manual gem requires
14
-
15
- For gems that have a normal default `require` and load all of their constants through such a require, everything works seamlessly. However, for gems that are marked as `require: false` in the Gemfile, or for gems that export optionally loaded types via different requires, where a single require does not load the whole gem code into memory, `tapioca` will not be able to load some of the types into memory and, thus, won't be able to generate complete RBIs for them. For this reason, we need to keep a small external file named `sorbet/tapioca/require.rb` that is executed after all the gems in the Gemfile have been required and before generation of gem RBIs have started. This file is responsible for adding the requires for additional files from gems, which are not covered by the default require.
16
-
17
- 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:
7
+ # Tapioca - The swiss army knife of RBI generation
18
8
 
19
- ```shell
20
- $ bundle exec pry
21
- [1] pry(main)> require 'better_html'
22
- => true
23
- [2] pry(main)> BetterHtml
24
- => BetterHtml
25
- [3] pry(main)> BetterHtml::Parser
26
- NameError: uninitialized constant BetterHtml::Parser
27
- from (pry):3:in `__pry__`
28
- [4] pry(main)> require 'better_html/parser'
29
- => true
30
- [5] pry(main)> BetterHtml::Parser
31
- => BetterHtml::Parser
32
- ```
33
-
34
- In order to make sure that `tapioca` can reflect on that type, we need to add the line `require "better_html/parser"` to the `sorbet/tapioca/require.rb` file. This will make sure `BetterHtml::Parser` is loaded into memory and a type annotation is generated for it in the `better_html.rbi` file. If this extra `require` line is not added to `sorbet/tapioca/require.rb` file, then the definition for that type will be missing from the RBI file.
35
-
36
- If you ever run into a case, where you add a gem or update the version of a gem and run `tapioca gem` but don't have some types you expect in the generated gem RBI files, you will need to make sure you have added the necessary requires to the `sorbet/tapioca/require.rb` file.
37
-
38
- You can use the command `tapioca require` to auto-populate the `sorbet/tapioca/require.rb` file with all the requires found
39
- in your application. Once the file generated, you should review it, remove all unnecessary requires and commit it.
40
-
41
- ## How does tapioca compare to "srb rbi gems" ?
9
+ ![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
42
10
 
43
- [Please see the detailed answer on our wiki](https://github.com/Shopify/tapioca/wiki/How-does-tapioca-compare-to-%22srb-rbi-gems%22-%3F)
11
+ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase. It surfaces types and methods from many sources that Sorbet cannot otherwise see such as gems, Rails and other DSLs – compiles them into [RBI files](https://sorbet.org/docs/rbi) and makes it easy for you to add gradual typing to your application.
12
+
13
+ **Features**:
14
+
15
+ * Easy installation and configuration
16
+ * Generation of RBI files for the gems used in your application
17
+ * Automatic generation from your application's Gemfile
18
+ * Importing of signatures from the source code of gems
19
+ * Importing of documentation from the source code of gems
20
+ * Synchronization validation for your CI
21
+ * Generation of RBI files for various DSL patterns that relies on meta-programming
22
+ * Automatic generation from your application's content
23
+ * Support many DSL patterns such as Rails, Google Protobuf, SmartProperties and more out of the box
24
+ * Extensible interface that allows you to write your own DSL compilers for other DSL patterns
25
+ * Automatic generation of signatures for methods from known DSLs
26
+ * Synchronization validation for your CI
27
+ * Management of shim RBI files
28
+ * Find useless definitions in shim RBI files from gems generated RBI files
29
+ * Find useless definitions in shim RBI files from DSL generated RBI files
30
+ * Find useless definitions in shim RBI files from Sorbet's embedded RBI for core and stdlib
31
+ * Synchronization validation for your CI
44
32
 
45
33
  ## Installation
46
34
 
47
35
  Add this line to your application's `Gemfile`:
48
36
 
49
- ```ruby
37
+ ```rb
50
38
  group :development do
51
39
  gem 'tapioca', require: false
52
40
  end
53
41
  ```
54
42
 
55
- and do not forget to execute `tapioca` using `bundler`:
43
+ Run `bundle install` and make sure Tapioca is properly installed:
56
44
 
57
- ```shell
45
+ ```sh
58
46
  $ bundle exec tapioca help
59
47
  Commands:
60
48
  tapioca --version, -v # show version
@@ -72,16 +60,27 @@ Options:
72
60
  -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
73
61
  ```
74
62
 
75
- ## Usage
63
+ ## Getting started
76
64
 
77
- ### Initialize folder structure
65
+ Execute this command to get started:
78
66
 
79
- Command: `tapioca init`
67
+ ```sh
68
+ $ bundle exec tapioca init
69
+ create sorbet/config
70
+ create sorbet/tapioca/config.yml
71
+ create sorbet/tapioca/require.rb
72
+ create bin/tapioca
73
+ ```
80
74
 
81
- This will create the `sorbet/config` and `sorbet/tapioca/require.rb` files for you, if they don't exist. If any of the files already exist, they will not be changed.
75
+ This will:
76
+
77
+ * create the [configuration file for Sorbet](https://sorbet.org/docs/cli#config-file), the [configuration file for Tapioca](#Configuration) and the [require.rb file](#manually-requiring-parts-of-a-gem)
78
+ * install the [binstub](https://bundler.io/man/bundle-binstubs.1.html#DESCRIPTION) for Tapioca in your app's `bin/` folder, so that you can use `bin/tapioca` to run commands in your app
82
79
 
83
80
  <!-- START_HELP_COMMAND_INIT -->
84
81
  ```shell
82
+ $ tapioca help init
83
+
85
84
  Usage:
86
85
  tapioca init
87
86
 
@@ -94,14 +93,40 @@ initializes folder structure
94
93
  ```
95
94
  <!-- END_HELP_COMMAND_INIT -->
96
95
 
97
- ### Generate RBI files for gems
96
+ ## Usage
97
+
98
+ ### Generating RBI files for gems
99
+
100
+ Sorbet does not read the code in your gem dependencies, so it does not know the constants and methods declared inside gems. Tapioca is able to load your gem dependencies from your application's `Gemfile` and compile RBI files to represent their content.
101
+
102
+ In order to generate the RBI files for the gems used in your application, run the following command:
103
+
104
+ ```sh
105
+ $ bin/tapioca gems [gems...]
106
+
107
+ Removing RBI files of gems that have been removed:
98
108
 
99
- Command: `tapioca gem [gems...]`
109
+ Nothing to do.
100
110
 
101
- This will generate RBIs for the specified gems and place them in the RBI directory.
111
+ Generating RBI files of gems that are added or updated:
112
+
113
+ Requiring all gems to prepare for compiling... Done
114
+
115
+ Compiled ansi
116
+ create sorbet/rbi/gems/ansi@1.5.0.rbi
117
+
118
+ ...
119
+
120
+ All operations performed in working directory.
121
+ Please review changes and commit them.
122
+ ```
123
+
124
+ This will load your application, find all the gems required by it and generate an RBI file for each gem under the `sorbet/rbi/gems` directory for each of those gems. This process will also import signatures that can be found inside each gem sources, and, optionally, any YARD documentation inside the gem.
102
125
 
103
126
  <!-- START_HELP_COMMAND_GEM -->
104
127
  ```shell
128
+ $ tapioca help gem
129
+
105
130
  Usage:
106
131
  tapioca gem [gem...]
107
132
 
@@ -119,6 +144,7 @@ Options:
119
144
  # Default: {"activesupport"=>"false"}
120
145
  [--verify], [--no-verify] # Verify RBIs are up-to-date
121
146
  [--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
147
+ # Default: true
122
148
  [--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
123
149
  # Default: true
124
150
  -w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
@@ -137,38 +163,142 @@ generate RBIs from gems
137
163
  ```
138
164
  <!-- END_HELP_COMMAND_GEM -->
139
165
 
140
- ### Generate the list of all unresolved constants
166
+ > Are you coming from `srb rbi`? [See how `tapioca gem` compares to `srb rbi`](https://github.com/Shopify/tapioca/wiki/How-does-tapioca-compare-to-%22srb-rbi-gems%22-%3F).
141
167
 
142
- Command: `tapioca todo`
168
+ #### Manually requiring parts of a gem
143
169
 
144
- This will generate the file `sorbet/rbi/todo.rbi` defining all unresolved constants as empty modules.
170
+ It may happen that the RBI file generated for a gem listed inside your `Gemfile.lock` is missing some definitions taht you would expect it to be exporting.
171
+
172
+ For gems that have a normal default `require` and that load all of their constants through that, everything should work seamlessly. However, for gems that are marked as `require: false` in the `Gemfile`, or for gems that export constants optionally via different requires, where a single require does not load the whole gem code into memory, Tapioca will not be able to load some of the types into memory and, thus, won't be able to generate complete RBIs for them. For this reason, we need to keep a small external file named `sorbet/tapioca/require.rb` that is executed after all the gems in the `Gemfile` have been required and before generation of gem RBIs have started. This file is responsible for adding the requires for additional files from gems, which are not covered by the default require.
173
+
174
+ 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:
145
175
 
146
- <!-- START_HELP_COMMAND_TODO -->
147
176
  ```shell
148
- Usage:
149
- tapioca todo
177
+ $ bundle exec pry
150
178
 
151
- Options:
152
- [--todo-file=TODO_FILE] # Path to the generated todo RBI file
153
- # Default: sorbet/rbi/todo.rbi
154
- [--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
155
- # Default: true
156
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
157
- # Default: sorbet/tapioca/config.yml
158
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
179
+ [1] pry(main)> require 'better_html'
180
+ => true
181
+ [2] pry(main)> BetterHtml
182
+ => BetterHtml
183
+ [3] pry(main)> BetterHtml::Parser
184
+ NameError: uninitialized constant BetterHtml::Parser
185
+ from (pry):3:in `__pry__`
186
+ [4] pry(main)> require 'better_html/parser'
187
+ => true
188
+ [5] pry(main)> BetterHtml::Parser
189
+ => BetterHtml::Parser
190
+ ```
159
191
 
160
- generate the list of unresolved constants
192
+ In order to make sure that `tapioca` can reflect on that type, we need to add the line `require "better_html/parser"` to the `sorbet/tapioca/require.rb` file. This will make sure `BetterHtml::Parser` is loaded into memory and a type annotation is generated for it in the `better_html.rbi` file. If this extra `require` line is not added to `sorbet/tapioca/require.rb` file, then Tapioca will be able to generate definitions for `BetterHtml` and other constants, but not for `BetterHtml::Parser`, which will be missing from the RBI file.
193
+
194
+ For example, you can take a look at Tapioca's own [`require.rb` file](https://github.com/Shopify/tapioca/blob/main/sorbet/tapioca/require.rb):
195
+
196
+ ```rb
197
+ # typed: strict
198
+ # frozen_string_literal: true
199
+
200
+ require "ansi/code"
201
+ require "google/protobuf"
202
+ require "rails/all"
203
+ require "rails/generators"
204
+ require "rails/generators/app_base"
205
+ require "rake/testtask"
206
+ require "rubocop/rake_task"
207
+ ```
208
+
209
+ If you ever run into a case, where you add a gem or update the version of a gem and run `tapioca gem` but don't have some types you expect in the generated gem RBI files, you will need to make sure you have added the necessary requires to the `sorbet/tapioca/require.rb` file and regenerate the RBI file for that gem explicitly using `bin/tapioca gem <gem-name>`.
210
+
211
+ To help you get started, you can use the command `tapioca require` to auto-populate the contents of the `sorbet/tapioca/require.rb` file with all the requires found in your application:
212
+
213
+ ```sh
214
+ $ bin/tapioca require
215
+
216
+ Compiling sorbet/tapioca/require.rb, this may take a few seconds... Done
217
+
218
+ All requires from this application have been written to sorbet/tapioca/require.rb.
219
+ Please review changes and commit them, then run `bin/tapioca gem`.
220
+ ```
221
+
222
+ Once the file is generated, you should review it, remove all unnecessary requires and commit it.
223
+
224
+ #### Excluding a gem from RBI generation
225
+
226
+ It may be useful to exclude some gems from the generation process. For example for gems that are in Bundle's debug group or gems of which the contents are dependent on the architecture they are loaded on. A typical example is `fakefs`, which, if loaded into memory, changes `File` operations to be no-ops and breaks Tapioca RBI file generation altogether.
227
+
228
+ To do so you can pass the list of gems you want to exclude in the command line with the `--exclude` option:
229
+
230
+ ```sh
231
+ $ bin/tapioca gems --exclude gemA gemB
161
232
  ```
162
- <!-- END_HELP_COMMAND_TODO -->
163
233
 
164
- ### Generate DSL RBI files
234
+ Or through the configuration file:
165
235
 
166
- Command: `tapioca dsl [constant...]`
236
+ ```yaml
237
+ gem:
238
+ exclude:
239
+ - gemA
240
+ - gemB
241
+ ```
242
+
243
+ #### Changing the strictness level of the RBI for a gem
244
+
245
+ By default, all RBI files for gems are generated with the [strictness level](https://sorbet.org/docs/static#file-level-granularity-strictness-levels) `typed: true`. Sometimes, this strictness level can create type-checking errors when a gem contains definitions that conflict with [Sorbet internal definitions for Ruby core and standard library](https://sorbet.org/docs/faq#it-looks-like-sorbets-types-for-the-stdlib-are-wrong).
246
+
247
+ Tapioca comes with an automatic detection (option `--auto-strictness`, enabled by default) of such cases and will switch the strictness level to `typed: false` in RBI files containing conflicts with the core and standard library definitions. It is nonetheless possible to manually switch the strictness level for a gem using the `--typed-overrides` option:
248
+
249
+ ```sh
250
+ $ bin/tapioca gems --typed-overrides gemA:false gemB:false
251
+ ```
252
+
253
+ Or through the configuration file:
254
+
255
+ ```yaml
256
+ gem:
257
+ typed_overrides:
258
+ gemA: "false"
259
+ gemB: "false"
260
+ ```
261
+
262
+ #### Keeping RBI files for gems up-to-date
263
+
264
+ To ensure all RBI files for gems are up-to-date with the latest changes in your `Gemfile.lock`, Tapioca provides a `--verify` option:
265
+
266
+ ```sh
267
+ $ bin/tapioca gems --verify
268
+
269
+ Checking for out-of-date RBIs...
270
+
271
+ Nothing to do, all RBIs are up-to-date.
272
+ ```
273
+
274
+ 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.
275
+
276
+ ### Generating RBI files for Rails and other DSLs
277
+
278
+ Sorbet by itself does not understand DSLs involving meta-programming, such as Rails. This means that Sorbet won't know about constants and methods generated by `ActiveRecord` or `ActiveSupport`.
279
+ To solve this, Tapioca can load your application and introspect it to find the constants and methods that would exist at runtime and compile them into RBI files.
280
+
281
+ To generate the RBI files for the DSLs used in your application, run the following command:
282
+
283
+ ```sh
284
+ $ bin/tapioca dsl
285
+
286
+ Loading Rails application... Done
287
+ Loading DSL compiler classes... Done
288
+ Compiling DSL RBI files...
289
+
290
+ create sorbet/rbi/dsl/my_model.rbi
291
+ ...
292
+
293
+ Done
294
+ ```
167
295
 
168
296
  This will generate DSL RBIs for specified constants (or for all handled constants, if a constant name is not supplied). You can read about DSL RBI compilers supplied by `tapioca` in [the manual](manual/compilers.md).
169
297
 
170
298
  <!-- START_HELP_COMMAND_DSL -->
171
299
  ```shell
300
+ $ tapioca help dsl
301
+
172
302
  Usage:
173
303
  tapioca dsl [constant...]
174
304
 
@@ -180,7 +310,7 @@ Options:
180
310
  [--only=compiler [compiler ...]] # Only run supplied DSL compiler(s)
181
311
  [--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s)
182
312
  [--verify], [--no-verify] # Verifies RBIs are up-to-date
183
- -q, [--quiet], [--no-quiet] # Supresses file creation output
313
+ -q, [--quiet], [--no-quiet] # Suppresses file creation output
184
314
  -w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
185
315
  # Default: 1
186
316
  [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
@@ -193,13 +323,298 @@ generate RBIs for dynamic methods
193
323
  ```
194
324
  <!-- END_HELP_COMMAND_DSL -->
195
325
 
196
- ## Configuration
326
+ #### Keeping RBI files for DSLs up-to-date
327
+
328
+ To ensure all RBI files for DSLs are up-to-date with the latest changes in your application or database, Tapioca provide a `--verify` option:
329
+
330
+ ```sh
331
+ $ bin/tapioca dsl --verify
332
+
333
+ Loading Rails application... Done
334
+ Loading DSL compiler classes... Done
335
+ Checking for out-of-date RBIs...
336
+
337
+
338
+ RBI files are out-of-date. In your development environment, please run:
339
+ `bin/tapioca dsl`
340
+ Once it is complete, be sure to commit and push any changes
341
+
342
+ Reason:
343
+ File(s) changed:
344
+ - sorbet/rbi/dsl/my_model.rbi
345
+ ```
346
+
347
+ This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking.
348
+
349
+ #### Writing custom DSL compilers
350
+
351
+ It is possible to create your own compilers for DSLs not supported by Tapioca out of the box.
352
+
353
+ Let's take for example this `Encryptable` module that uses the [`included` hook](https://ruby-doc.org/core-3.1.1/Module.html#method-i-included) to dynamically add a few methods to the classes that include it:
354
+
355
+ ```rb
356
+ module Encryptable
357
+ def self.included(base)
358
+ base.extend(ClassMethods)
359
+ end
360
+
361
+ module ClassMethods
362
+ def attr_encrypted(attr_name)
363
+ encrypted_attributes << attr_name
364
+
365
+ attr_accessor(attr_name)
366
+
367
+ encrypted_attr_name = :"#{attr_name}_encrypted"
368
+
369
+ define_method(encrypted_attr_name) do
370
+ value = send(attr_name)
371
+ encrypt(value)
372
+ end
373
+
374
+ define_method("#{encrypted_attr_name}=") do |value|
375
+ send("#{attr_name}=", decrypt(value))
376
+ end
377
+ end
378
+
379
+ def encrypted_attributes
380
+ @encrypted_attributes ||= []
381
+ end
382
+ end
383
+
384
+ private
385
+
386
+ def encrypt(value)
387
+ value.unpack("H*").first
388
+ end
389
+
390
+ def decrypt(value)
391
+ [value].pack("H*")
392
+ end
393
+ end
394
+ ```
395
+
396
+ When `Encryptable` is included in a class like this one, it makes it possible to call `attr_encrypted` to define an attribute, its accessors and its encrypted accessors:
397
+
398
+ ```rb
399
+ class CreditCard
400
+ include Encryptable
401
+
402
+ attr_encrypted :number
403
+ end
404
+ ```
405
+
406
+ These accessors can then be used on the `CreditCard` instance without having to define them in the class:
407
+
408
+ ```rb
409
+ # typed: true
410
+ # file: example.rb
411
+
412
+ card = CreditCard.new
413
+ card.number = "1234 5678 9012 3456"
414
+
415
+ p card.number # => "1234 5678 9012 3456"
416
+ p card.number_encrypted # => "31323334203536373820393031322033343536"
417
+
418
+ card.number_encrypted = "31323334203536373820393031322033343536"
419
+ p card.number # => "1234 5678 9012 3456"
420
+ ```
421
+
422
+ Sadly, since these methods have been created dynamically at runtime, when our `attr_encryptable` method was run, there are no static traces of the `number`, `number=`, `number_encrypted` and `number_encrypted=` methods. Since Sorbet does not run the Ruby code but analyses it statically, it can't see these methods and running type-checking will show a bunch of errors:
423
+
424
+ ```shell
425
+ $ bundle exec srb tc
426
+
427
+ lib/example.rb:5: Method number= does not exist on CreditCard https://srb.help/7003
428
+ lib/example.rb:7: Method number does not exist on CreditCard https://srb.help/7003
429
+ lib/example.rb:8: Method number_encrypted does not exist on CreditCard https://srb.help/7003
430
+ lib/example.rb:10: Method number_encrypted= does not exist on CreditCard https://srb.help/7003
431
+ lib/example.rb:11: Method number does not exist on CreditCard https://srb.help/7003
432
+
433
+ Errors: 5
434
+ ```
435
+
436
+ To solve this you will have to create your own DSL compiler able that understands the `Encryptable` DSL and can generate the RBI definitions representing the actual shape of `CreditCard` at runtime.
437
+
438
+ To do so, create the new DSL compiler inside the `sorbet/tapioca/compilers` directory of your application with the following contents:
439
+
440
+ ```rb
441
+ module Tapioca
442
+ module Compilers
443
+ class Encryptable < Tapioca::Dsl::Compiler
444
+ extend T::Sig
445
+
446
+ ConstantType = type_member {{ fixed: T.class_of(Encryptable) }}
447
+
448
+ sig { override.returns(T::Enumerable[Module]) }
449
+ def self.gather_constants
450
+ # Collect all the classes that include Encryptable
451
+ all_classes.select { |c| c < ::Encryptable }
452
+ end
453
+
454
+ sig { override.void }
455
+ def decorate
456
+ # Create a RBI definition for each class that includes Encryptable
457
+ root.create_path(constant) do |klass|
458
+ # For each encrypted attribute we find in the class
459
+ constant.encrypted_attributes.each do |attr_name|
460
+ # Create the RBI definitions for all the missing methods
461
+ klass.create_method(attr_name, return_type: "String")
462
+ klass.create_method("#{attr_name}=", parameters: [ create_param("value", type: "String") ], return_type: "void")
463
+ klass.create_method("#{attr_name}_encrypted", return_type: "String")
464
+ klass.create_method("#{attr_name}_encrypted=", parameters: [ create_param("value", type: "String") ], return_type: "void")
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end
471
+ ```
472
+
473
+ There are two main parts to the DSL compiler API: `gather_constants` and `decorate`:
474
+
475
+ * The `gather_constants` class method collects all classes (or modules) that should be processed by this specific DSL compiler.
476
+ * The `decorate` method defines how to generate the necessary RBI definitions for the gathered constants.
477
+
478
+ You can now run the new RBI compiler through the normal DSL generation process (your custom compiler will be loaded automatically by Tapioca):
479
+
480
+ ```shell
481
+ $ bin/tapioca dsl
482
+
483
+ Loading Rails application... Done
484
+ Loading DSL compiler classes... Done
485
+ Compiling DSL RBI files...
486
+
487
+ create sorbet/rbi/dsl/credit_card.rbi
488
+
489
+ Done
490
+ ```
491
+
492
+ And then run Sorbet without error:
493
+
494
+ ```shell
495
+ $ bundle exec srb tc
496
+
497
+ No errors! Great job.
498
+ ```
499
+
500
+ For more concrete and advanced examples, take a look at [Tapioca's default DSL compilers](https://github.com/Shopify/tapioca/tree/main/lib/tapioca/dsl/compilers).
501
+
502
+ ### RBI files for missing constants and methods
197
503
 
198
- Tapioca supports loading command defaults from a configuration file. The default configuration
199
- file location is `sorbet/tapioca/config.yml` but this default can be changed using the `--config` flag
200
- and supplying an alternative configuration file path.
504
+ Even after generating the RBIs, it is possible that some constants or methods are still undefined for Sorbet.
201
505
 
202
- A configuration file must be a well-formed YAML file with top-level keys for the various Tapioca commands. Keys under each such top-level command should be the underscore version of a long option name for that command and the value for that key should be the value of the option.
506
+ This might be for multiple reasons, with the most frequents ones being:
507
+
508
+ * The constant or method comes from a part of the gem that Tapioca cannot load (optional dependency, wrong architecture, etc.)
509
+ * The constant or method comes from a DSL or meta-programming that Tapioca doesn't support yet
510
+ * The constant or method only exists when a specific code path is executed
511
+
512
+ The best way to deal with such occurrences is to manually create RBI files (shims) for them so you can also add types but depending on the amount of meta-programming used in your project this can mean an overwhelming amount of manual work.
513
+
514
+ #### Generating the RBI file for missing constants
515
+
516
+ To get you started quickly, Tapioca can create a RBI file containing a stub of all the missing constants so you can typecheck your project without missing constants and shim them later as you need them.
517
+
518
+ To generate the RBI file for the missing constants used in your application run the following command:
519
+
520
+ ```sh
521
+ $ bin/tapioca todo
522
+
523
+ Compiling sorbet/rbi/todo.rbi, this may take a few seconds... Done
524
+ All unresolved constants have been written to sorbet/rbi/todo.rbi.
525
+ Please review changes and commit them.
526
+ ```
527
+
528
+ This will generate the file `sorbet/rbi/todo.rbi` defining all unresolved constants as empty modules. Since the constants are "missing", Tapioca does not know if they should be marked as modules or classes and will use modules as a safer default. This file should be reviewed, corrected, if necessary, and then committed in your repository.
529
+
530
+ <!-- START_HELP_COMMAND_TODO -->
531
+ ```shell
532
+ $ tapioca help todo
533
+
534
+ Usage:
535
+ tapioca todo
536
+
537
+ Options:
538
+ [--todo-file=TODO_FILE] # Path to the generated todo RBI file
539
+ # Default: sorbet/rbi/todo.rbi
540
+ [--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
541
+ # Default: true
542
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
543
+ # Default: sorbet/tapioca/config.yml
544
+ -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
545
+
546
+ generate the list of unresolved constants
547
+ ```
548
+ <!-- END_HELP_COMMAND_TODO -->
549
+
550
+ #### Manually writing RBI definitions (shims)
551
+
552
+ A _shim_ is a hand-crafted RBI file that tells Sorbet about constants, ancestors, methods, etc. that it can't understand statically and aren't already generated by Tapioca.
553
+
554
+ These shims are usually placed in the `sorbet/rbi/shims` directory. From there, conventionally, you should follow the directory structure of the project to the file you'd like to shim. For example, say you had a `person.rb` file found at `app/models/person.rb`. If you were to add a shim for it, you'd want to create your RBI file at `sorbet/rbi/shims/app/models/person.rbi`.
555
+
556
+ A shim might be as simple as the class definition with an empty method body as below:
557
+
558
+ ```ruby
559
+ # typed: true
560
+
561
+ class Person
562
+ sig { void }
563
+ def some_method_sorbet_cannot_find; end
564
+ end
565
+ ```
566
+
567
+ As you migrate to newer versions of Sorbet or Tapioca, some shims may become useless as Sorbet's internal definitions for Ruby's core and standard library is enhanced or Tapioca is able to generate definitions for new DSLs. To avoid keeping outdated or useless definitions inside your application shims, Tapioca provides the `check-shims` command:
568
+
569
+ ```sh
570
+ $ bin/tapioca check-shims
571
+
572
+ Loading Sorbet payload... Done
573
+ Loading shim RBIs from sorbet/rbi/shims... Done
574
+ Loading gem RBIs from sorbet/rbi/gems... Done
575
+ Loading gem RBIs from sorbet/rbi/dsl... Done
576
+ Looking for duplicates... Done
577
+
578
+ Duplicated RBI for ::MyModel#title:
579
+ * sorbet/rbi/shims/my_model.rbi:2:2-2:14
580
+ * sorbet/rbi/dsl/my_model.rbi:2:2-2:14
581
+
582
+ Duplicated RBI for ::String#capitalize:
583
+ * https://github.com/sorbet/sorbet/tree/master/rbi/core/string.rbi#L406
584
+ * sorbet/rbi/shims/core/string.rbi:3:2-3:23
585
+
586
+ Please remove the duplicated definitions from the sorbet/rbi/shims directory.
587
+ ```
588
+
589
+ This command can be used on CI to make sure the RBI shims are always up-to-date and non-redundant with generated files.
590
+
591
+ <!-- START_HELP_COMMAND_CHECK-SHIMS -->
592
+ ```shell
593
+ $ bin/tapioca help check-shims
594
+
595
+ Usage:
596
+ tapioca check-shims
597
+
598
+ Options:
599
+ [--gem-rbi-dir=GEM_RBI_DIR] # Path to gem RBIs
600
+ # Default: sorbet/rbi/gems
601
+ [--dsl-rbi-dir=DSL_RBI_DIR] # Path to DSL RBIs
602
+ # Default: sorbet/rbi/dsl
603
+ [--shim-rbi-dir=SHIM_RBI_DIR] # Path to shim RBIs
604
+ # Default: sorbet/rbi/shims
605
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
606
+ # Default: sorbet/tapioca/config.yml
607
+ -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
608
+
609
+ check duplicated definitions in shim RBIs
610
+ ```
611
+ <!-- END_HELP_COMMAND_CHECK-SHIMS -->
612
+
613
+ ### Configuration
614
+
615
+ Tapioca supports loading command defaults from a configuration file. The default configuration file location is `sorbet/tapioca/config.yml` but this default can be changed using the `--config` flag and supplying an alternative configuration file path.
616
+
617
+ Tapioca's configuration file must be a well-formed YAML file with top-level keys for the various Tapioca commands. Keys under each such top-level command should be the underscore version of a long option name for that command and the value for that key should be the value of the option.
203
618
 
204
619
  For example, if you always want to generate gem RBIs with inline documentation, then you would create the file `sorbet/tapioca/config.yml` as:
205
620
 
@@ -247,7 +662,7 @@ gem:
247
662
  typed_overrides:
248
663
  activesupport: 'false'
249
664
  verify: false
250
- doc: false
665
+ doc: true
251
666
  exported_gem_rbis: true
252
667
  workers: 1
253
668
  auto_strictness: true
@@ -257,6 +672,9 @@ check_shims:
257
672
  gem_rbi_dir: sorbet/rbi/gems
258
673
  dsl_rbi_dir: sorbet/rbi/dsl
259
674
  shim_rbi_dir: sorbet/rbi/shims
675
+ payload: true
676
+ annotations:
677
+ repo_uri: https://raw.githubusercontent.com/Shopify/rbi-central/main
260
678
  ```
261
679
  <!-- END_CONFIG_TEMPLATE -->
262
680