tapioca 0.4.17 → 0.4.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9293078ee1614e88f45586904e3d90cc556cab3218340699325902e224d94aed
4
- data.tar.gz: d14050953a3de23bfc4830b6b27282331dcac745090695cd2552a5e7a1ee6296
3
+ metadata.gz: b3709ab7f54589bdf97715ffc2cc7c3a98ad703a6e318a264db70429078cdf26
4
+ data.tar.gz: 3e38366a54f9b3286de28a2b6d557762b2cf564848b8184a76aadc75504a41fb
5
5
  SHA512:
6
- metadata.gz: bd3bbcbc3a7d5448e7ace8472258e96c654d8854f1c4ec3ffe498a5b06e136f46df526f5a83d4b7814bf1ed57eec24cb3694c8fce686dd2df02a6d6af6de8414
7
- data.tar.gz: 641e472a327784295d117481c9676b7fc92c320a764d06449ed9c5d65e1f5cc76cb63b0446e4269d85fd606df8ec19e519ce42cfe503403a1cf2197c042ad494
6
+ metadata.gz: 4ef5e31a85a71607340ad54e2fff59b1a5bd0d3f81b1481bd4830ca5e73e5ea9ed1504d38808d8bf49a9686276685167a35103769fdb30d66b61362c4d9c072b
7
+ data.tar.gz: e0b1daab32f024cc5f3f1fc019b5c1dcd040ce0657e59b81d736702e3cb380a9e7265397f108f4787163e50d9b31e793b960cd33f875cf45a061cee17fa1857e
data/README.md CHANGED
@@ -1,3 +1,5 @@
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
+
1
3
  # Tapioca
2
4
 
3
5
  ![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
@@ -14,7 +16,7 @@ For gems that have a normal default `require` and load all of their constants th
14
16
 
15
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:
16
18
 
17
- ```ruby
19
+ ```shell
18
20
  $ bundle exec pry
19
21
  [1] pry(main)> require 'better_html'
20
22
  => true
@@ -110,7 +112,7 @@ This will generate DSL RBIs for specified constants (or for all handled constant
110
112
  - `--prerequire [file]`: A file to be required before `Bundler.require` is called.
111
113
  - `--postrequire [file]`: A file to be required after `Bundler.require` is called.
112
114
  - `--out [directory]`: The output directory for generated RBI files, default to `sorbet/rbi/gems`.
113
- - `--generate-command [command]`: The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
115
+ - `--generate-command [command]`: **[DEPRECATED]** The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
114
116
  - `--typed-overrides [gem:level]`: Overrides typed sigils for generated gem RBIs for gem `gem` to level `level` (`level` can be one of `ignore`, `false`, `true`, `strict`, or `strong`, see [the Sorbet docs](https://sorbet.org/docs/static#file-level-granularity-strictness-levels) for more details).
115
117
 
116
118
  ## Contributing
data/exe/tapioca CHANGED
@@ -1,6 +1,21 @@
1
1
  #! /usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/tapioca"
4
+ require 'sorbet-runtime'
5
5
 
6
- Tapioca::Cli.start(ARGV)
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
9
+ T::Configuration.inline_type_error_handler = ->(*) {}
10
+ # Suppresses errors caused by incorrect parameter ordering
11
+ T::Configuration.sig_validation_error_handler = ->(*) {}
12
+ rescue
13
+ # Need this rescue so that if another gem has
14
+ # already set the checked level by the time we
15
+ # get to it, we don't fail outright.
16
+ nil
17
+ end
18
+
19
+ require_relative "../lib/tapioca/internal"
20
+
21
+ Tapioca::Cli::Main.start(ARGV)
data/lib/tapioca.rb CHANGED
@@ -17,31 +17,5 @@ module Tapioca
17
17
  class Error < StandardError; end
18
18
  end
19
19
 
20
- begin
21
- T::Configuration.default_checked_level = :never
22
- # Suppresses errors caused by T.cast, T.let, T.must, etc.
23
- T::Configuration.inline_type_error_handler = ->(*) {}
24
- # Suppresses errors caused by incorrect parameter ordering
25
- T::Configuration.sig_validation_error_handler = ->(*) {}
26
- rescue
27
- # Need this rescue so that if another gem has
28
- # already set the checked level by the time we
29
- # get to it, we don't fail outright.
30
- nil
31
- end
32
-
33
- require "tapioca/loader"
34
- require "tapioca/constant_locator"
35
- require "tapioca/config"
36
- require "tapioca/config_builder"
37
- require "tapioca/generator"
38
- require "tapioca/cli"
39
- require "tapioca/gemfile"
40
- require "tapioca/compilers/sorbet"
41
- require "tapioca/compilers/requires_compiler"
42
- require "tapioca/compilers/symbol_table_compiler"
43
- require "tapioca/compilers/symbol_table/symbol_generator"
44
- require "tapioca/compilers/symbol_table/symbol_loader"
45
- require "tapioca/compilers/todos_compiler"
46
- require "tapioca/compilers/dsl_compiler"
20
+ require "tapioca/compilers/dsl/base"
47
21
  require "tapioca/version"
data/lib/tapioca/cli.rb CHANGED
@@ -4,112 +4,5 @@
4
4
  require 'thor'
5
5
 
6
6
  module Tapioca
7
- class Cli < Thor
8
- include(Thor::Actions)
9
-
10
- class_option :prerequire,
11
- aliases: ["--pre", "-b"],
12
- banner: "file",
13
- desc: "A file to be required before Bundler.require is called"
14
- class_option :postrequire,
15
- aliases: ["--post", "-a"],
16
- banner: "file",
17
- desc: "A file to be required after Bundler.require is called"
18
- class_option :outdir,
19
- aliases: ["--out", "-o"],
20
- banner: "directory",
21
- desc: "The output directory for generated RBI files"
22
- class_option :generate_command,
23
- aliases: ["--cmd", "-c"],
24
- banner: "command",
25
- desc: "The command to run to regenerate RBI files"
26
- class_option :exclude,
27
- aliases: ["-x"],
28
- type: :array,
29
- banner: "gem [gem ...]",
30
- desc: "Excludes the given gem(s) from RBI generation"
31
- class_option :typed_overrides,
32
- aliases: ["--typed", "-t"],
33
- type: :hash,
34
- banner: "gem:level [gem:level ...]",
35
- desc: "Overrides for typed sigils for generated gem RBIs"
36
-
37
- map T.unsafe(%w[--version -v] => :__print_version)
38
-
39
- desc "init", "initializes folder structure"
40
- def init
41
- create_file(Config::SORBET_CONFIG, skip: true) do
42
- <<~CONTENT
43
- --dir
44
- .
45
- CONTENT
46
- end
47
- create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
48
- <<~CONTENT
49
- # typed: false
50
- # frozen_string_literal: true
51
-
52
- # Add your extra requires here
53
- CONTENT
54
- end
55
- end
56
-
57
- desc "require", "generate the list of files to be required by tapioca"
58
- def require
59
- Tapioca.silence_warnings do
60
- generator.build_requires
61
- end
62
- end
63
-
64
- desc "todo", "generate the list of unresolved constants"
65
- def todo
66
- Tapioca.silence_warnings do
67
- generator.build_todos
68
- end
69
- end
70
-
71
- desc "dsl [constant...]", "generate RBIs for dynamic methods"
72
- option :generators,
73
- type: :array,
74
- aliases: ["--gen", "-g"],
75
- banner: "generator [generator ...]",
76
- desc: "Only run supplied DSL generators"
77
- def dsl(*constants)
78
- Tapioca.silence_warnings do
79
- generator.build_dsl(constants)
80
- end
81
- end
82
-
83
- desc "generate [gem...]", "generate RBIs from gems"
84
- def generate(*gems)
85
- Tapioca.silence_warnings do
86
- generator.build_gem_rbis(gems)
87
- end
88
- end
89
-
90
- desc "sync", "sync RBIs to Gemfile"
91
- def sync
92
- Tapioca.silence_warnings do
93
- generator.sync_rbis_with_gemfile
94
- end
95
- end
96
-
97
- desc "--version, -v", "show version"
98
- def __print_version
99
- puts "Tapioca v#{Tapioca::VERSION}"
100
- end
101
-
102
- no_commands do
103
- def self.exit_on_failure?
104
- true
105
- end
106
-
107
- def generator
108
- current_command = T.must(current_command_chain.first)
109
- @generator ||= Generator.new(
110
- ConfigBuilder.from_options(current_command, options)
111
- )
112
- end
113
- end
114
- end
7
+ module Cli; end
115
8
  end
@@ -0,0 +1,136 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Cli
6
+ class Main < Thor
7
+ include(Thor::Actions)
8
+
9
+ class_option :prerequire,
10
+ aliases: ["--pre", "-b"],
11
+ banner: "file",
12
+ desc: "A file to be required before Bundler.require is called"
13
+ class_option :postrequire,
14
+ aliases: ["--post", "-a"],
15
+ banner: "file",
16
+ desc: "A file to be required after Bundler.require is called"
17
+ class_option :outdir,
18
+ aliases: ["--out", "-o"],
19
+ banner: "directory",
20
+ desc: "The output directory for generated RBI files"
21
+ class_option :generate_command,
22
+ aliases: ["--cmd", "-c"],
23
+ banner: "command",
24
+ desc: "The command to run to regenerate RBI files"
25
+ class_option :exclude,
26
+ aliases: ["-x"],
27
+ type: :array,
28
+ banner: "gem [gem ...]",
29
+ desc: "Excludes the given gem(s) from RBI generation"
30
+ class_option :typed_overrides,
31
+ aliases: ["--typed", "-t"],
32
+ type: :hash,
33
+ banner: "gem:level [gem:level ...]",
34
+ desc: "Overrides for typed sigils for generated gem RBIs"
35
+
36
+ map T.unsafe(%w[--version -v] => :__print_version)
37
+
38
+ desc "init", "initializes folder structure"
39
+ def init
40
+ create_config
41
+ create_post_require
42
+ generate_binstub
43
+ end
44
+
45
+ desc "require", "generate the list of files to be required by tapioca"
46
+ def require
47
+ Tapioca.silence_warnings do
48
+ generator.build_requires
49
+ end
50
+ end
51
+
52
+ desc "todo", "generate the list of unresolved constants"
53
+ def todo
54
+ Tapioca.silence_warnings do
55
+ generator.build_todos
56
+ end
57
+ end
58
+
59
+ desc "dsl [constant...]", "generate RBIs for dynamic methods"
60
+ option :generators,
61
+ type: :array,
62
+ aliases: ["--gen", "-g"],
63
+ banner: "generator [generator ...]",
64
+ desc: "Only run supplied DSL generators"
65
+ option :verify,
66
+ type: :boolean,
67
+ default: false,
68
+ desc: "Verifies RBIs are up-to-date"
69
+ def dsl(*constants)
70
+ Tapioca.silence_warnings do
71
+ generator.build_dsl(constants, should_verify: options[:verify])
72
+ end
73
+ end
74
+
75
+ desc "generate [gem...]", "generate RBIs from gems"
76
+ def generate(*gems)
77
+ Tapioca.silence_warnings do
78
+ generator.build_gem_rbis(gems)
79
+ end
80
+ end
81
+
82
+ desc "sync", "sync RBIs to Gemfile"
83
+ def sync
84
+ Tapioca.silence_warnings do
85
+ generator.sync_rbis_with_gemfile
86
+ end
87
+ end
88
+
89
+ desc "--version, -v", "show version"
90
+ def __print_version
91
+ puts "Tapioca v#{Tapioca::VERSION}"
92
+ end
93
+
94
+ private
95
+
96
+ def create_config
97
+ create_file(Config::SORBET_CONFIG, skip: true) do
98
+ <<~CONTENT
99
+ --dir
100
+ .
101
+ CONTENT
102
+ end
103
+ end
104
+
105
+ def create_post_require
106
+ create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
107
+ <<~CONTENT
108
+ # typed: false
109
+ # frozen_string_literal: true
110
+
111
+ # Add your extra requires here
112
+ CONTENT
113
+ end
114
+ end
115
+
116
+ def generate_binstub
117
+ installer = Bundler::Installer.new(Bundler.root, Bundler.definition)
118
+ spec = Bundler.definition.specs.find { |s| s.name == "tapioca" }
119
+ installer.generate_bundler_executable_stubs(spec, { force: true })
120
+ end
121
+
122
+ no_commands do
123
+ def self.exit_on_failure?
124
+ true
125
+ end
126
+
127
+ def generator
128
+ current_command = T.must(current_command_chain.first)
129
+ @generator ||= Generator.new(
130
+ ConfigBuilder.from_options(current_command, options)
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -371,7 +371,6 @@ module Tapioca
371
371
  return unless signature
372
372
 
373
373
  return_type = signature.return_type
374
- return if T::Types::Simple === return_type && T::Generic === return_type.raw_type
375
374
  return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
376
375
 
377
376
  return_type.to_s
@@ -382,10 +381,11 @@ module Tapioca
382
381
  signature = T::Private::Methods.signature_for_method(column_type.method(method))
383
382
  return unless signature
384
383
 
385
- arg_type = signature.arg_types.first.last
386
- return if T::Types::Simple === arg_type && T::Generic === arg_type.raw_type
384
+ # Arg types is an array [name, type] entries, so we desctructure the type of
385
+ # first argument to get the first argument type
386
+ _, first_argument_type = signature.arg_types.first
387
387
 
388
- arg_type.to_s
388
+ first_argument_type.to_s
389
389
  end
390
390
 
391
391
  sig { params(type: String).returns(String) }
@@ -60,7 +60,7 @@ module Tapioca
60
60
 
61
61
  model.create_module(module_name) do |mod|
62
62
  scope_method_names.each do |scope_method|
63
- generate_scope_method(scope_method, mod)
63
+ generate_scope_method(scope_method.to_s, mod)
64
64
  end
65
65
  end
66
66
 
@@ -123,7 +123,7 @@ module Tapioca
123
123
  # Compile a Ruby method return type into a Parlour type
124
124
  sig do
125
125
  params(method_def: T.any(Method, UnboundMethod))
126
- .returns(String)
126
+ .returns(T.nilable(String))
127
127
  end
128
128
  def compile_method_return_type_to_parlour(method_def)
129
129
  signature = T::Private::Methods.signature_for_method(method_def)
@@ -89,7 +89,7 @@ module Tapioca
89
89
  class UrlHelpers < Base
90
90
  extend T::Sig
91
91
 
92
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
92
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
93
93
  def decorate(root, constant)
94
94
  case constant
95
95
  when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
@@ -127,7 +127,7 @@ module Tapioca
127
127
 
128
128
  private
129
129
 
130
- sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
130
+ sig { params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
131
131
  def generate_module_for(root, constant)
132
132
  root.create_module(T.must(constant.name)) do |mod|
133
133
  mod.create_include("::ActionDispatch::Routing::UrlFor")
@@ -143,7 +143,7 @@ module Tapioca
143
143
  end
144
144
  end
145
145
 
146
- sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module), helper_module: Module).void }
146
+ sig { params(mod: Parlour::RbiGenerator::Namespace, constant: Module, helper_module: Module).void }
147
147
  def create_mixins_for(mod, constant, helper_module)
148
148
  include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
149
149
  extend_helper = constant.singleton_class.ancestors.include?(helper_module)
@@ -74,9 +74,15 @@ module Tapioca
74
74
  compile(symbol, constant)
75
75
  end
76
76
 
77
- sig { params(symbol: String, inherit: T::Boolean).returns(BasicObject).checked(:never) }
78
- def resolve_constant(symbol, inherit: false)
79
- Object.const_get(symbol, inherit)
77
+ sig do
78
+ params(
79
+ symbol: String,
80
+ inherit: T::Boolean,
81
+ namespace: Module
82
+ ).returns(BasicObject).checked(:never)
83
+ end
84
+ def resolve_constant(symbol, inherit: false, namespace: Object)
85
+ namespace.const_get(symbol, inherit)
80
86
  rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
81
87
  nil
82
88
  end
@@ -142,9 +148,11 @@ module Tapioca
142
148
  def compile_object(name, value)
143
149
  return if symbol_ignored?(name)
144
150
  klass = class_of(value)
145
- return if name_of(klass)&.start_with?("T::Types::", "T::Private::")
151
+ klass_name = name_of(klass)
152
+
153
+ return if klass_name&.start_with?("T::Types::", "T::Private::")
146
154
 
147
- type_name = public_module?(klass) && name_of(klass) || "T.untyped"
155
+ type_name = public_module?(klass) && klass_name || "T.untyped"
148
156
  indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
149
157
  end
150
158
 
@@ -181,16 +189,17 @@ module Tapioca
181
189
 
182
190
  [
183
191
  compile_module_helpers(constant),
192
+ compile_type_variables(constant),
184
193
  compile_mixins(constant),
185
194
  compile_mixes_in_class_methods(constant),
186
195
  compile_props(constant),
187
196
  compile_enums(constant),
188
197
  methods,
189
- ].select { |b| b != "" }.join("\n\n")
198
+ ].select { |b| b && !b.empty? }.join("\n\n")
190
199
  end
191
200
  end
192
201
 
193
- sig { params(constant: Module).returns(String) }
202
+ sig { params(constant: Module).returns(T.nilable(String)) }
194
203
  def compile_module_helpers(constant)
195
204
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
196
205
 
@@ -199,12 +208,14 @@ module Tapioca
199
208
  helpers << indented("final!") if T::Private::Final.final_module?(constant)
200
209
  helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
201
210
 
211
+ return if helpers.empty?
212
+
202
213
  helpers.join("\n")
203
214
  end
204
215
 
205
- sig { params(constant: Module).returns(String) }
216
+ sig { params(constant: Module).returns(T.nilable(String)) }
206
217
  def compile_props(constant)
207
- return "" unless T::Props::ClassMethods === constant
218
+ return unless T::Props::ClassMethods === constant
208
219
 
209
220
  constant.props.map do |name, prop|
210
221
  method = "prop"
@@ -219,11 +230,11 @@ module Tapioca
219
230
  end.join("\n")
220
231
  end
221
232
 
222
- sig { params(constant: Module).returns(String) }
233
+ sig { params(constant: Module).returns(T.nilable(String)) }
223
234
  def compile_enums(constant)
224
- return "" unless T::Enum > constant
235
+ return unless T::Enum > constant
225
236
 
226
- enums = T.cast(constant, T::Enum).values.map do |enum_type|
237
+ enums = T.unsafe(constant).values.map do |enum_type|
227
238
  enum_type.instance_variable_get(:@const_name).to_s
228
239
  end
229
240
 
@@ -250,11 +261,71 @@ module Tapioca
250
261
  compile(symbol, subconstant)
251
262
  end.compact
252
263
 
253
- return "" if output.empty?
264
+ return if output.empty?
254
265
 
255
266
  "\n" + output.join("\n\n")
256
267
  end
257
268
 
269
+ sig { params(constant: Module).returns(T.nilable(String)) }
270
+ def compile_type_variables(constant)
271
+ type_variables = compile_type_variable_declarations(constant)
272
+ singleton_class_type_variables = compile_type_variable_declarations(singleton_class_of(constant))
273
+
274
+ return if !type_variables && !singleton_class_type_variables
275
+
276
+ type_variables += "\n" if type_variables
277
+ singleton_class_type_variables += "\n" if singleton_class_type_variables
278
+
279
+ [
280
+ type_variables,
281
+ singleton_class_type_variables,
282
+ ].compact.join("\n").rstrip
283
+ end
284
+
285
+ sig { params(constant: Module).returns(T.nilable(String)) }
286
+ def compile_type_variable_declarations(constant)
287
+ with_indentation_for_constant(constant) do
288
+ # Try to find the type variables defined on this constant, bail if we can't
289
+ type_variables = GenericTypeRegistry.lookup_type_variables(constant)
290
+ return unless type_variables
291
+
292
+ # Create a map of subconstants (via their object ids) to their names.
293
+ # We need this later when we want to lookup the name of the registered type
294
+ # variable via the value of the type variable constant.
295
+ subconstant_to_name_lookup = constants_of(constant).map do |constant_name|
296
+ [
297
+ object_id_of(resolve_constant(constant_name.to_s, namespace: constant)),
298
+ constant_name,
299
+ ]
300
+ end.to_h
301
+
302
+ # Map each type variable to its string representation.
303
+ #
304
+ # Each entry of `type_variables` maps an object_id to a String,
305
+ # and the order they are inserted into the hash is the order they should be
306
+ # defined in the source code.
307
+ #
308
+ # By looping over these entries and then getting the actual constant name
309
+ # from the `subconstant_to_name_lookup` we defined above, gives us all the
310
+ # information we need to serialize type variable definitions.
311
+ type_variable_declarations = type_variables.map do |type_variable_id, serialized_type_variable|
312
+ constant_name = subconstant_to_name_lookup[type_variable_id]
313
+ # Here, we know that constant_value will be an instance of
314
+ # T::Types::CustomTypeVariable, which knows how to serialize
315
+ # itself to a type_member/type_template
316
+ indented("#{constant_name} = #{serialized_type_variable}")
317
+ end.compact
318
+
319
+ return if type_variable_declarations.empty?
320
+
321
+ [
322
+ indented("extend T::Generic"),
323
+ "",
324
+ *type_variable_declarations,
325
+ ].compact.join("\n")
326
+ end
327
+ end
328
+
258
329
  sig { params(constant: Class).returns(String) }
259
330
  def compile_superclass(constant)
260
331
  superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
@@ -431,29 +502,19 @@ module Tapioca
431
502
  instance_methods = compile_directly_owned_methods(name, constant)
432
503
  singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
433
504
 
434
- return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
505
+ return if symbol_ignored?(name) && !instance_methods && !singleton_methods
435
506
 
436
507
  [
437
- initialize_method || "",
508
+ initialize_method,
438
509
  instance_methods,
439
510
  singleton_methods,
440
- ].select { |b| b.strip != "" }.join("\n\n")
511
+ ].select { |b| b && !b.strip.empty? }.join("\n\n")
441
512
  end
442
513
 
443
- sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
514
+ sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(T.nilable(String)) }
444
515
  def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
445
- indent_step = 0
446
- preamble = nil
447
- postamble = nil
448
-
449
- if mod.singleton_class?
450
- indent_step = 1
451
- preamble = indented("class << self")
452
- postamble = indented("end")
453
- end
454
-
455
- methods = with_indentation(indent_step) do
456
- method_names_by_visibility(mod)
516
+ with_indentation_for_constant(mod) do
517
+ methods = method_names_by_visibility(mod)
457
518
  .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
458
519
  .flat_map do |visibility, method_list|
459
520
  compiled = method_list.sort!.map do |name|
@@ -470,16 +531,11 @@ module Tapioca
470
531
  compiled
471
532
  end
472
533
  .compact
473
- .join("\n")
474
- end
475
534
 
476
- return "" if methods.strip == ""
535
+ return if methods.empty?
477
536
 
478
- [
479
- preamble,
480
- methods,
481
- postamble,
482
- ].compact.join("\n")
537
+ methods.join("\n")
538
+ end
483
539
  end
484
540
 
485
541
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -659,6 +715,33 @@ module Tapioca
659
715
  @indent -= 2 * step
660
716
  end
661
717
 
718
+ sig do
719
+ params(
720
+ constant: Module,
721
+ blk: T.proc
722
+ .returns(T.nilable(String))
723
+ )
724
+ .returns(T.nilable(String))
725
+ end
726
+ def with_indentation_for_constant(constant, &blk)
727
+ step = if constant.singleton_class?
728
+ 1
729
+ else
730
+ 0
731
+ end
732
+
733
+ result = with_indentation(step, &blk)
734
+
735
+ return result unless result
736
+ return result unless constant.singleton_class?
737
+
738
+ [
739
+ indented("class << self"),
740
+ result,
741
+ indented("end"),
742
+ ].compact.join("\n")
743
+ end
744
+
662
745
  sig { params(str: String).returns(String) }
663
746
  def indented(str)
664
747
  " " * @indent + str
@@ -859,12 +942,12 @@ module Tapioca
859
942
  nil
860
943
  end
861
944
 
862
- sig { params(constant: Module).returns(String) }
945
+ sig { params(constant: T::Types::Base).returns(String) }
863
946
  def type_of(constant)
864
947
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
865
948
  end
866
949
 
867
- sig { params(object: Object).returns(T::Boolean).checked(:never) }
950
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
868
951
  def object_id_of(object)
869
952
  Object.instance_method(:object_id).bind(object).call
870
953
  end
@@ -8,7 +8,6 @@ module Tapioca
8
8
  const(:outdir, String)
9
9
  const(:prerequire, T.nilable(String))
10
10
  const(:postrequire, String)
11
- const(:generate_command, String)
12
11
  const(:exclude, T::Array[String])
13
12
  const(:typed_overrides, T::Hash[String, String])
14
13
  const(:todos_path, String)
@@ -27,6 +26,7 @@ module Tapioca
27
26
  TAPIOCA_PATH = T.let("#{SORBET_PATH}/tapioca", String)
28
27
  TAPIOCA_CONFIG = T.let("#{TAPIOCA_PATH}/config.yml", String)
29
28
 
29
+ DEFAULT_COMMAND = T.let("bin/tapioca", String)
30
30
  DEFAULT_POSTREQUIRE = T.let("#{TAPIOCA_PATH}/require.rb", String)
31
31
  DEFAULT_RBIDIR = T.let("#{SORBET_PATH}/rbi", String)
32
32
  DEFAULT_DSLDIR = T.let("#{DEFAULT_RBIDIR}/dsl", String)
@@ -10,9 +10,13 @@ module Tapioca
10
10
 
11
11
  sig { params(command: Symbol, options: T::Hash[String, T.untyped]).returns(Config) }
12
12
  def from_options(command, options)
13
- Config.from_hash(
14
- merge_options(default_options(command), config_options, options)
15
- )
13
+ merged_options = merge_options(default_options(command), config_options, options)
14
+
15
+ puts(<<~MSG) if merged_options.include?("generate_command")
16
+ DEPRECATION: The `-c` and `--cmd` flags will be removed in a future release.
17
+ MSG
18
+
19
+ Config.from_hash(merged_options)
16
20
  end
17
21
 
18
22
  private
@@ -40,14 +44,6 @@ module Tapioca
40
44
  DEFAULT_OPTIONS.merge("outdir" => default_outdir)
41
45
  end
42
46
 
43
- sig { returns(String) }
44
- def default_command
45
- command = File.basename($PROGRAM_NAME)
46
- args = ARGV.join(" ")
47
-
48
- "#{command} #{args}".strip
49
- end
50
-
51
47
  sig { params(options: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
52
48
  def merge_options(*options)
53
49
  options.each_with_object({}) do |option, result|
@@ -65,7 +61,6 @@ module Tapioca
65
61
  DEFAULT_OPTIONS = T.let({
66
62
  "postrequire" => Config::DEFAULT_POSTREQUIRE,
67
63
  "outdir" => nil,
68
- "generate_command" => default_command,
69
64
  "exclude" => [],
70
65
  "typed_overrides" => Config::DEFAULT_OVERRIDES,
71
66
  "todos_path" => Config::DEFAULT_TODOSPATH,
@@ -63,7 +63,7 @@ module Tapioca
63
63
 
64
64
  content = String.new
65
65
  content << rbi_header(
66
- config.generate_command,
66
+ "#{Config::DEFAULT_COMMAND} require",
67
67
  reason: "explicit gem requires",
68
68
  strictness: "false"
69
69
  )
@@ -76,8 +76,8 @@ module Tapioca
76
76
  say("Done", :green)
77
77
 
78
78
  say("All requires from this application have been written to #{name}.", [:green, :bold])
79
- cmd = set_color("tapioca sync", :yellow, :bold)
80
- say("Please review changes and commit them, then run #{cmd}.", [:green, :bold])
79
+ cmd = set_color("#{Config::DEFAULT_COMMAND} sync", :yellow, :bold)
80
+ say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold])
81
81
  end
82
82
 
83
83
  sig { void }
@@ -99,7 +99,7 @@ module Tapioca
99
99
 
100
100
  content = String.new
101
101
  content << rbi_header(
102
- config.generate_command,
102
+ "#{Config::DEFAULT_COMMAND} todo",
103
103
  reason: "unresolved constants",
104
104
  strictness: "false"
105
105
  )
@@ -116,16 +116,26 @@ module Tapioca
116
116
  say("Please review changes and commit them.", [:green, :bold])
117
117
  end
118
118
 
119
- sig { params(requested_constants: T::Array[String]).void }
120
- def build_dsl(requested_constants)
119
+ sig do
120
+ params(
121
+ requested_constants: T::Array[String],
122
+ should_verify: T::Boolean,
123
+ ).void
124
+ end
125
+ def build_dsl(requested_constants, should_verify: false)
121
126
  load_application(eager_load: requested_constants.empty?)
122
127
  load_dsl_generators
123
128
 
124
- rbi_files_to_purge = existing_rbi_filenames(requested_constants)
125
-
126
- say("Compiling DSL RBI files...")
129
+ if should_verify
130
+ say("Checking for out-of-date RBIs...")
131
+ else
132
+ say("Compiling DSL RBI files...")
133
+ end
127
134
  say("")
128
135
 
136
+ outpath = should_verify ? Dir.mktmpdir : config.outpath
137
+ rbi_files_to_purge = existing_rbi_filenames(requested_constants)
138
+
129
139
  compiler = Compilers::DslCompiler.new(
130
140
  requested_constants: constantize(requested_constants),
131
141
  requested_generators: config.generators,
@@ -135,24 +145,21 @@ module Tapioca
135
145
  )
136
146
 
137
147
  compiler.run do |constant, contents|
138
- filename = compile_dsl_rbi(constant, contents)
148
+ filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath))
139
149
  rbi_files_to_purge.delete(filename) if filename
140
150
  end
151
+ say("")
141
152
 
142
- unless rbi_files_to_purge.empty?
143
- say("")
144
- say("Removing stale RBI files...")
145
-
146
- rbi_files_to_purge.sort.each do |filename|
147
- remove(filename)
148
- end
149
- end
153
+ if should_verify
154
+ perform_dsl_verification(outpath)
155
+ else
156
+ purge_stale_dsl_rbi_files(rbi_files_to_purge)
150
157
 
151
- say("")
152
- say("Done", :green)
158
+ say("Done", :green)
153
159
 
154
- say("All operations performed in working directory.", [:green, :bold])
155
- say("Please review changes and commit them.", [:green, :bold])
160
+ say("All operations performed in working directory.", [:green, :bold])
161
+ say("Please review changes and commit them.", [:green, :bold])
162
+ end
156
163
  end
157
164
 
158
165
  sig { void }
@@ -218,7 +225,7 @@ module Tapioca
218
225
  say_error("If you populated ", :yellow)
219
226
  say_error("#{file} ", :bold, :blue)
220
227
  say_error("with ", :yellow)
221
- say_error("tapioca require", :bold, :blue)
228
+ say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue)
222
229
  say_error("you should probably review it and remove the faulty line.", :yellow)
223
230
  end
224
231
 
@@ -286,10 +293,10 @@ module Tapioca
286
293
  constant_map.values
287
294
  end
288
295
 
289
- sig { params(requested_constants: T::Array[String]).returns(T::Set[Pathname]) }
290
- def existing_rbi_filenames(requested_constants)
296
+ sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
297
+ def existing_rbi_filenames(requested_constants, path: config.outpath)
291
298
  filenames = if requested_constants.empty?
292
- Pathname.glob(config.outpath / "**/*.rbi")
299
+ Pathname.glob(path / "**/*.rbi")
293
300
  else
294
301
  requested_constants.map do |constant_name|
295
302
  dsl_rbi_filename(constant_name)
@@ -477,7 +484,7 @@ module Tapioca
477
484
  rbi_body_content = compiler.compile(gem)
478
485
  content = String.new
479
486
  content << rbi_header(
480
- config.generate_command,
487
+ "#{Config::DEFAULT_COMMAND} sync",
481
488
  reason: "types exported from the `#{gem.name}` gem",
482
489
  strictness: strictness
483
490
  )
@@ -494,23 +501,22 @@ module Tapioca
494
501
  end
495
502
  File.write(filename.to_s, content)
496
503
 
497
- Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
504
+ T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
498
505
  remove(file) unless file.basename.to_s == gem.rbi_file_name
499
506
  end
500
507
  end
501
508
 
502
- sig { params(constant: Module, contents: String).returns(T.nilable(Pathname)) }
503
- def compile_dsl_rbi(constant, contents)
509
+ sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) }
510
+ def compile_dsl_rbi(constant, contents, outpath: config.outpath)
504
511
  return if contents.nil?
505
512
 
506
- command = format(config.generate_command, constant.name)
507
513
  constant_name = Module.instance_method(:name).bind(constant).call
508
514
  rbi_name = constant_name.underscore + ".rbi"
509
- filename = config.outpath / rbi_name
515
+ filename = outpath / rbi_name
510
516
 
511
517
  out = String.new
512
518
  out << rbi_header(
513
- command,
519
+ "#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
514
520
  reason: "dynamic methods in `#{constant.name}`"
515
521
  )
516
522
  out << contents
@@ -522,5 +528,53 @@ module Tapioca
522
528
 
523
529
  filename
524
530
  end
531
+
532
+ sig { params(tmp_dir: Pathname).returns(T.nilable(String)) }
533
+ def verify_dsl_rbi(tmp_dir:)
534
+ existing_rbis = existing_rbi_filenames([]).sort
535
+ new_rbis = existing_rbi_filenames([], path: tmp_dir).grep_v(/gem|shim/).sort
536
+
537
+ return "New file(s) introduced." if existing_rbis.length != new_rbis.length
538
+
539
+ desynced_files = []
540
+
541
+ (0..existing_rbis.length - 1).each do |i|
542
+ desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i])
543
+ end
544
+
545
+ unless desynced_files.empty?
546
+ filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ")
547
+
548
+ return "File(s) updated:\n - #{filenames}"
549
+ end
550
+
551
+ nil
552
+ end
553
+
554
+ sig { params(dir: String).void }
555
+ def perform_dsl_verification(dir)
556
+ if (error = verify_dsl_rbi(tmp_dir: Pathname.new(dir)))
557
+ say("RBI files are out-of-date, please run `#{Config::DEFAULT_COMMAND} dsl` to update.")
558
+ say("Reason: ", [:red])
559
+ say(error)
560
+ exit(1)
561
+ else
562
+ say("Nothing to do, all RBIs are up-to-date.")
563
+ end
564
+ ensure
565
+ FileUtils.remove_entry(dir)
566
+ end
567
+
568
+ sig { params(files: T::Set[Pathname]).void }
569
+ def purge_stale_dsl_rbi_files(files)
570
+ if files.any?
571
+ say("Removing stale RBI files...")
572
+
573
+ files.sort.each do |filename|
574
+ remove(filename)
575
+ end
576
+ say("")
577
+ end
578
+ end
525
579
  end
526
580
  end
@@ -0,0 +1,170 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ # This class is responsible for storing and looking up information related to generic types.
6
+ #
7
+ # The class stores 2 different kinds of data, in two separate lookup tables:
8
+ # 1. a lookup of generic type instances by name: `@generic_instances`
9
+ # 2. a lookup of type variable serializer by constant and type variable
10
+ # instance: `@type_variables`
11
+ #
12
+ # By storing the above data, we can cheaply query each constant against this registry
13
+ # to see if it declares any generic type variables. This becomes a simple lookup in the
14
+ # `@type_variables` hash table with the given constant.
15
+ #
16
+ # If there is no entry, then we can cheaply know that we can skip generic type
17
+ # information generation for this type.
18
+ #
19
+ # On the other hand, if we get a result, then the result will be a hash of type
20
+ # variable to type variable serializers. This allows us to associate type variables
21
+ # to the constant names that represent them, easily.
22
+ module GenericTypeRegistry
23
+ @generic_instances = T.let(
24
+ {},
25
+ T::Hash[String, Module]
26
+ )
27
+
28
+ @type_variables = T.let(
29
+ {},
30
+ T::Hash[Integer, T::Hash[Integer, String]]
31
+ )
32
+
33
+ class << self
34
+ extend T::Sig
35
+
36
+ # This method is responsible for building the name of the instantiated concrete type
37
+ # and cloning the given constant so that we can return a type that is the same
38
+ # as the current type but is a different instance and has a different name method.
39
+ #
40
+ # We cache those cloned instances by their name in `@generic_instances`, so that
41
+ # we don't keep instantiating a new type every single time it is referenced.
42
+ # For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only
43
+ # result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and
44
+ # 2 hash lookups (for the other two `Foo[Integer]`s).
45
+ #
46
+ # This method returns the created or cached clone of the constant.
47
+ sig { params(constant: T.untyped, types: T.untyped).returns(Module) }
48
+ def register_type(constant, types)
49
+ # Build the name of the instantiated generic type,
50
+ # something like `"Foo[X, Y, Z]"`
51
+ type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
52
+ name = "#{name_of(constant)}[#{type_list}]"
53
+
54
+ # Create a clone of the constant with an overridden `name`
55
+ # method that returns the name we constructed above.
56
+ #
57
+ # Also, we try to memoize the clone based on the name, so that
58
+ # we don't have to keep recreating clones all the time.
59
+ @generic_instances[name] ||= constant.clone.tap do |clone|
60
+ clone.define_singleton_method(:name) { name }
61
+ end
62
+ end
63
+
64
+ sig do
65
+ params(
66
+ constant: T.untyped,
67
+ type_member: T::Types::TypeVariable,
68
+ fixed: T.untyped,
69
+ lower: T.untyped,
70
+ upper: T.untyped
71
+ ).void
72
+ end
73
+ def register_type_member(constant, type_member, fixed, lower, upper)
74
+ register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
75
+ end
76
+
77
+ sig do
78
+ params(
79
+ constant: T.untyped,
80
+ type_template: T::Types::TypeVariable,
81
+ fixed: T.untyped,
82
+ lower: T.untyped,
83
+ upper: T.untyped
84
+ ).void
85
+ end
86
+ def register_type_template(constant, type_template, fixed, lower, upper)
87
+ register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
88
+ end
89
+
90
+ sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
91
+ def lookup_type_variables(constant)
92
+ @type_variables[object_id_of(constant)]
93
+ end
94
+
95
+ private
96
+
97
+ # This method is called from intercepted calls to `type_member` and `type_template`.
98
+ # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
99
+ # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
100
+ #
101
+ # This method creates a `String` with that data and stores it in the
102
+ # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
103
+ #
104
+ # Finally, the original `type_variable` is returned from this method, so that the caller
105
+ # can return it from the original methods as well.
106
+ sig do
107
+ params(
108
+ constant: T.untyped,
109
+ type_variable_type: T.enum([:type_member, :type_template]),
110
+ type_variable: T::Types::TypeVariable,
111
+ fixed: T.untyped,
112
+ lower: T.untyped,
113
+ upper: T.untyped
114
+ ).void
115
+ end
116
+ # rubocop:disable Metrics/ParameterLists
117
+ def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
118
+ # rubocop:enable Metrics/ParameterLists
119
+ type_variables = lookup_or_initialize_type_variables(constant)
120
+
121
+ type_variables[object_id_of(type_variable)] = serialize_type_variable(
122
+ type_variable_type,
123
+ type_variable.variance,
124
+ fixed,
125
+ lower,
126
+ upper
127
+ )
128
+ end
129
+
130
+ sig { params(constant: Module).returns(T::Hash[Integer, String]) }
131
+ def lookup_or_initialize_type_variables(constant)
132
+ @type_variables[object_id_of(constant)] ||= {}
133
+ end
134
+
135
+ sig do
136
+ params(
137
+ type_variable_type: Symbol,
138
+ variance: Symbol,
139
+ fixed: T.untyped,
140
+ lower: T.untyped,
141
+ upper: T.untyped
142
+ ).returns(String)
143
+ end
144
+ def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
145
+ parts = []
146
+ parts << ":#{variance}" unless variance == :invariant
147
+ parts << "fixed: #{fixed}" if fixed
148
+ parts << "lower: #{lower}" unless lower == T.untyped
149
+ parts << "upper: #{upper}" unless upper == BasicObject
150
+
151
+ parameters = parts.join(", ")
152
+
153
+ serialized = T.let(type_variable_type.to_s, String)
154
+ serialized += "(#{parameters})" unless parameters.empty?
155
+
156
+ serialized
157
+ end
158
+
159
+ sig { params(constant: Module).returns(T.nilable(String)) }
160
+ def name_of(constant)
161
+ Module.instance_method(:name).bind(constant).call
162
+ end
163
+
164
+ sig { params(object: BasicObject).returns(Integer) }
165
+ def object_id_of(object)
166
+ Object.instance_method(:object_id).bind(object).call
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca"
5
+ require "tapioca/loader"
6
+ require "tapioca/constant_locator"
7
+ require "tapioca/generic_type_registry"
8
+ require "tapioca/sorbet_ext/generic_name_patch"
9
+ require "tapioca/config"
10
+ require "tapioca/config_builder"
11
+ require "tapioca/generator"
12
+ require "tapioca/cli"
13
+ require "tapioca/cli/main"
14
+ require "tapioca/gemfile"
15
+ require "tapioca/compilers/sorbet"
16
+ require "tapioca/compilers/requires_compiler"
17
+ require "tapioca/compilers/symbol_table_compiler"
18
+ require "tapioca/compilers/symbol_table/symbol_generator"
19
+ require "tapioca/compilers/symbol_table/symbol_loader"
20
+ require "tapioca/compilers/todos_compiler"
21
+ require "tapioca/compilers/dsl_compiler"
@@ -0,0 +1,66 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/sorbet_ext/name_patch"
5
+
6
+ module T
7
+ module Generic
8
+ # This module intercepts calls to generic type instantiations and type variable definitions.
9
+ # Tapioca stores the data from those calls in a `GenericTypeRegistry` which can then be used
10
+ # to look up the original call details when we are trying to do code generation.
11
+ #
12
+ # We are interested in the data of the `[]`, `type_member` and `type_template` calls which
13
+ # are all needed to generate good generic information at runtime.
14
+ module TypeStoragePatch
15
+ def [](*types)
16
+ # `T::Generic#[]` just returns `self`, so let's call and store it.
17
+ constant = super
18
+ # `register_type` method builds and returns an instantiated clone of the generic type
19
+ # so, we just return that from this method as well.
20
+ Tapioca::GenericTypeRegistry.register_type(constant, types)
21
+ end
22
+
23
+ def type_member(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
24
+ # `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it.
25
+ # We use that when registering the type member and then later return it from this method.
26
+ type_member = super
27
+ Tapioca::GenericTypeRegistry.register_type_member(self, type_member, fixed, lower, upper)
28
+ type_member
29
+ end
30
+
31
+ def type_template(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
32
+ # `T::Generic#type_template` just instantiates a `T::Type::TypeTemplate` instance and returns it.
33
+ # We use that when registering the type template and then later return it from this method.
34
+ type_template = super
35
+ Tapioca::GenericTypeRegistry.register_type_template(self, type_template, fixed, lower, upper)
36
+ type_template
37
+ end
38
+ end
39
+
40
+ prepend TypeStoragePatch
41
+ end
42
+
43
+ module Types
44
+ class Simple
45
+ # This module intercepts calls to the `name` method for
46
+ # simple types, so that it can ask the name to the type if
47
+ # the type is generic, since, by this point, we've created
48
+ # a clone of that type with the `name` method returning the
49
+ # appropriate name for that specific concrete type.
50
+ module GenericNamePatch
51
+ def name
52
+ if T::Generic === @raw_type
53
+ # for types that are generic, use the name
54
+ # returned by the "name" method of this instance
55
+ @name ||= T.unsafe(@raw_type).name.freeze
56
+ else
57
+ # otherwise, fallback to the normal name lookup
58
+ super
59
+ end
60
+ end
61
+ end
62
+
63
+ prepend GenericNamePatch
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,16 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module T
5
+ module Types
6
+ class Simple
7
+ module NamePatch
8
+ def name
9
+ @name ||= Module.instance_method(:name).bind(@raw_type).call.freeze
10
+ end
11
+ end
12
+
13
+ prepend NamePatch
14
+ end
15
+ end
16
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.17"
5
+ VERSION = "0.4.18"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.17
4
+ version: 0.4.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-03-11 00:00:00.000000000 Z
14
+ date: 2021-03-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -125,6 +125,7 @@ files:
125
125
  - exe/tapioca
126
126
  - lib/tapioca.rb
127
127
  - lib/tapioca/cli.rb
128
+ - lib/tapioca/cli/main.rb
128
129
  - lib/tapioca/compilers/dsl/action_controller_helpers.rb
129
130
  - lib/tapioca/compilers/dsl/action_mailer.rb
130
131
  - lib/tapioca/compilers/dsl/active_record_associations.rb
@@ -155,7 +156,11 @@ files:
155
156
  - lib/tapioca/core_ext/class.rb
156
157
  - lib/tapioca/gemfile.rb
157
158
  - lib/tapioca/generator.rb
159
+ - lib/tapioca/generic_type_registry.rb
160
+ - lib/tapioca/internal.rb
158
161
  - lib/tapioca/loader.rb
162
+ - lib/tapioca/sorbet_ext/generic_name_patch.rb
163
+ - lib/tapioca/sorbet_ext/name_patch.rb
159
164
  - lib/tapioca/version.rb
160
165
  homepage: https://github.com/Shopify/tapioca
161
166
  licenses: