tapioca 0.4.11 → 0.4.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ade782d63dbf52c361f3275c10366ece6db850a50a056bb6ef6ab73b3d8fe5c1
4
- data.tar.gz: db41740ac452ad69cecc453cf5fc6ff6ce2634e45ba461670132d386d2a5e938
3
+ metadata.gz: 80a1d62bade6d75e6ec3a7de5a06f96eb7bc4ea002a70a2503d74393e834e864
4
+ data.tar.gz: 235cd2477d3f07176bdbddd5653c7a5cff86516f81dd196a65043a5b2f7d68dc
5
5
  SHA512:
6
- metadata.gz: ca364aee459f30d35d0b62d4f48ee8bb9977ac458ca4aac74035cf97d2ff6f73571b8d6eb70689d7e389ce7ee67e5f8b7a8ad7773ef9f265c38d41aa030dd76c
7
- data.tar.gz: 7c67870e40188ad371c33eda3bc44ed92077e451d05100bc7427bd143377763064ae0dad50d02b341ce34ed61ee0e8e3e7ce2b81a4b5b240fb437540cd26dc48
6
+ metadata.gz: 23bc803e2b251dfb4ef4c82b0793336810ca9d551355ac1cd8d514c1484ef096d9b00a12cda214ce9b4b83ccf1620a32159035a5bf798a4c2d8a3e96b026e352
7
+ data.tar.gz: 606e2ad0e81c87e7f89b5933fa160d1b7825dee6f3f3451042ad2ec047a672041b559ed9cb79cf75aa0af7020b9aed2da85e1f52f36bb39b909f5037f8865717
data/Gemfile CHANGED
@@ -10,7 +10,6 @@ group(:deployment, :development) do
10
10
  gem("rake")
11
11
  end
12
12
 
13
- gem("bundler", "~> 1.17")
14
13
  gem("yard", "~> 0.9.25")
15
14
  gem("pry-byebug")
16
15
  gem("minitest")
@@ -24,6 +24,8 @@ module Tapioca
24
24
  # belongs_to :category
25
25
  # has_many :comments
26
26
  # has_one :author, class_name: "User"
27
+ #
28
+ # accepts_nested_attributes_for :category, :comments, :author
27
29
  # end
28
30
  # ~~~
29
31
  #
@@ -44,6 +46,9 @@ module Tapioca
44
46
  # sig { params(value: T.nilable(::User)).void }
45
47
  # def author=(value); end
46
48
  #
49
+ # sig { params(attributes: T.untyped).returns(T.untyped) }
50
+ # def author_attributes=(attributes); end
51
+ #
47
52
  # sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
48
53
  # def build_author(*args, &blk); end
49
54
  #
@@ -56,6 +61,9 @@ module Tapioca
56
61
  # sig { params(value: T.nilable(::Category)).void }
57
62
  # def category=(value); end
58
63
  #
64
+ # sig { params(attributes: T.untyped).returns(T.untyped) }
65
+ # def category_attributes=(attributes); end
66
+ #
59
67
  # sig { returns(T::Array[T.untyped]) }
60
68
  # def comment_ids; end
61
69
  #
@@ -68,6 +76,9 @@ module Tapioca
68
76
  # sig { params(value: T::Enumerable[::Comment]).void }
69
77
  # def comments=(value); end
70
78
  #
79
+ # sig { params(attributes: T.untyped).returns(T.untyped) }
80
+ # def comments_attributes=(attributes); end
81
+ #
71
82
  # sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
72
83
  # def create_author(*args, &blk); end
73
84
  #
@@ -103,13 +114,8 @@ module Tapioca
103
114
  module_name = "GeneratedAssociationMethods"
104
115
 
105
116
  model.create_module(module_name) do |mod|
106
- constant.reflections.each do |association_name, reflection|
107
- if reflection.collection?
108
- populate_collection_assoc_getter_setter(mod, constant, association_name, reflection)
109
- else
110
- populate_single_assoc_getter_setter(mod, constant, association_name, reflection)
111
- end
112
- end
117
+ populate_nested_attribute_writers(mod, constant)
118
+ populate_associations(mod, constant)
113
119
  end
114
120
 
115
121
  model.create_include(module_name)
@@ -123,6 +129,31 @@ module Tapioca
123
129
 
124
130
  private
125
131
 
132
+ sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
133
+ def populate_nested_attribute_writers(mod, constant)
134
+ constant.nested_attributes_options.keys.each do |association_name|
135
+ create_method(
136
+ mod,
137
+ "#{association_name}_attributes=",
138
+ parameters: [
139
+ Parlour::RbiGenerator::Parameter.new("attributes", type: "T.untyped"),
140
+ ],
141
+ return_type: "T.untyped"
142
+ )
143
+ end
144
+ end
145
+
146
+ sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
147
+ def populate_associations(mod, constant)
148
+ constant.reflections.each do |association_name, reflection|
149
+ if reflection.collection?
150
+ populate_collection_assoc_getter_setter(mod, constant, association_name, reflection)
151
+ else
152
+ populate_single_assoc_getter_setter(mod, constant, association_name, reflection)
153
+ end
154
+ end
155
+ end
156
+
126
157
  sig do
127
158
  params(
128
159
  klass: Parlour::RbiGenerator::Namespace,
@@ -61,7 +61,7 @@ module Tapioca
61
61
  end
62
62
  def create_method(namespace, name, options = {})
63
63
  return unless valid_method_name?(name)
64
- T.unsafe(namespace).create_method(name, options)
64
+ T.unsafe(namespace).create_method(name, **options)
65
65
  end
66
66
 
67
67
  # Create a Parlour method inside `namespace` from its Ruby definition
@@ -8,6 +8,7 @@ module Tapioca
8
8
  module Compilers
9
9
  module Sorbet
10
10
  SORBET = Pathname.new(Gem::Specification.find_by_name("sorbet-static").full_gem_path) / "libexec" / "sorbet"
11
+ EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
11
12
 
12
13
  class << self
13
14
  extend(T::Sig)
@@ -26,7 +27,9 @@ module Tapioca
26
27
 
27
28
  sig { returns(String) }
28
29
  def sorbet_path
29
- SORBET.to_s.shellescape
30
+ sorbet_path = ENV.fetch(EXE_PATH_ENV_VAR, SORBET)
31
+ sorbet_path = SORBET if sorbet_path.empty?
32
+ sorbet_path.to_s.shellescape
30
33
  end
31
34
  end
32
35
  end
@@ -95,6 +95,7 @@ module Tapioca
95
95
  return if alias_namespaced?(name)
96
96
  return if seen?(name)
97
97
  return unless parent_declares_constant?(name)
98
+ return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
98
99
 
99
100
  mark_seen(name)
100
101
  compile_constant(name, constant)
@@ -122,12 +123,15 @@ module Tapioca
122
123
  def compile_alias(name, constant)
123
124
  return if symbol_ignored?(name)
124
125
 
125
- constant_name = name_of(constant)
126
+ target = name_of(constant)
127
+ # If target has no name, let's make it an anonymous class or module with `Class.new` or `Module.new`
128
+ target = "#{constant.class}.new" unless target
129
+
126
130
  add_to_alias_namespace(name)
127
131
 
128
132
  return if IGNORED_SYMBOLS.include?(name)
129
133
 
130
- indented("#{name} = #{constant_name}")
134
+ indented("#{name} = #{target}")
131
135
  end
132
136
 
133
137
  sig do
@@ -180,6 +184,7 @@ module Tapioca
180
184
  compile_mixins(constant),
181
185
  compile_mixes_in_class_methods(constant),
182
186
  compile_props(constant),
187
+ compile_enums(constant),
183
188
  methods,
184
189
  ].select { |b| b != "" }.join("\n\n")
185
190
  end
@@ -189,15 +194,12 @@ module Tapioca
189
194
  def compile_module_helpers(constant)
190
195
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
191
196
 
192
- if abstract_type
193
- indented("#{abstract_type}!")
194
- elsif T::Private::Final.final_module?(constant)
195
- indented("final!")
196
- elsif T::Private::Sealed.sealed_module?(constant)
197
- indented("sealed!")
198
- else
199
- ""
200
- end
197
+ helpers = []
198
+ helpers << indented("#{abstract_type}!") if abstract_type
199
+ helpers << indented("final!") if T::Private::Final.final_module?(constant)
200
+ helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
201
+
202
+ helpers.join("\n")
201
203
  end
202
204
 
203
205
  sig { params(constant: Module).returns(String) }
@@ -207,7 +209,7 @@ module Tapioca
207
209
  constant.props.map do |name, prop|
208
210
  method = "prop"
209
211
  method = "const" if prop.fetch(:immutable, false)
210
- type = prop.fetch(:type_object, "T.untyped")
212
+ type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
211
213
 
212
214
  if prop.key?(:default)
213
215
  indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
@@ -217,6 +219,23 @@ module Tapioca
217
219
  end.join("\n")
218
220
  end
219
221
 
222
+ sig { params(constant: Module).returns(String) }
223
+ def compile_enums(constant)
224
+ return "" unless constant < T::Enum
225
+
226
+ enums = T.cast(constant, T::Enum).values.map do |enum_type|
227
+ enum_type.instance_variable_get(:@const_name).to_s
228
+ end
229
+
230
+ content = [
231
+ indented('enums do'),
232
+ *enums.map { |e| indented(" #{e} = new") }.join("\n"),
233
+ indented('end'),
234
+ ]
235
+
236
+ content.join("\n")
237
+ end
238
+
220
239
  sig { params(name: String, constant: Module).returns(T.nilable(String)) }
221
240
  def compile_subconstants(name, constant)
222
241
  output = constants_of(constant).sort.uniq.map do |constant_name|
@@ -345,9 +364,13 @@ module Tapioca
345
364
  end
346
365
 
347
366
  define_singleton_method(:include) do |mod|
348
- before = singleton_class.ancestors
349
- super(mod).tap do
350
- mixins_from_modules[mod] = singleton_class.ancestors - before
367
+ begin
368
+ before = singleton_class.ancestors
369
+ super(mod).tap do
370
+ mixins_from_modules[mod] = singleton_class.ancestors - before
371
+ end
372
+ rescue Exception # rubocop:disable Lint/RescueException
373
+ # this is a best effort, bail if we can't perform this
351
374
  end
352
375
  end
353
376
 
@@ -17,27 +17,23 @@ module Tapioca
17
17
  )
18
18
  end
19
19
 
20
+ sig { returns(Bundler::Definition) }
21
+ attr_reader(:definition)
22
+
23
+ sig { returns(T::Array[Gem]) }
24
+ attr_reader(:dependencies)
25
+
26
+ sig { returns(T::Array[String]) }
27
+ attr_reader(:missing_specs)
28
+
20
29
  sig { void }
21
30
  def initialize
22
31
  @gemfile = T.let(File.new(Bundler.default_gemfile), File)
23
32
  @lockfile = T.let(File.new(Bundler.default_lockfile), File)
24
- @dependencies = T.let(nil, T.nilable(T::Array[Gem]))
25
- @definition = T.let(nil, T.nilable(Bundler::Definition))
26
- end
27
-
28
- sig { returns(T::Array[Gem]) }
29
- def dependencies
30
- @dependencies ||= begin
31
- specs = definition.locked_gems.specs.to_a
32
-
33
- definition
34
- .resolve
35
- .materialize(specs)
36
- .map { |spec| Gem.new(spec) }
37
- .reject { |gem| gem.ignore?(dir) }
38
- .uniq(&:rbi_file_name)
39
- .sort_by(&:rbi_file_name)
40
- end
33
+ @definition = T.let(Bundler::Dsl.evaluate(gemfile, lockfile, {}), Bundler::Definition)
34
+ dependencies, missing_specs = load_dependencies
35
+ @dependencies = T.let(dependencies, T::Array[Gem])
36
+ @missing_specs = T.let(missing_specs, T::Array[String])
41
37
  end
42
38
 
43
39
  sig { params(gem_name: String).returns(T.nilable(Gem)) }
@@ -55,6 +51,23 @@ module Tapioca
55
51
  sig { returns(File) }
56
52
  attr_reader(:gemfile, :lockfile)
57
53
 
54
+ sig { returns([T::Array[Gem], T::Array[String]]) }
55
+ def load_dependencies
56
+ deps = definition.locked_gems.dependencies.values
57
+
58
+ missing_specs = T::Array[String].new
59
+
60
+ dependencies = definition
61
+ .resolve
62
+ .materialize(deps, missing_specs)
63
+ .map { |spec| Gem.new(spec) }
64
+ .reject { |gem| gem.ignore?(dir) }
65
+ .uniq(&:rbi_file_name)
66
+ .sort_by(&:rbi_file_name)
67
+
68
+ [dependencies, missing_specs]
69
+ end
70
+
58
71
  sig { returns(Bundler::Runtime) }
59
72
  def runtime
60
73
  Bundler::Runtime.new(File.dirname(gemfile.path), definition)
@@ -65,11 +78,6 @@ module Tapioca
65
78
  definition.groups
66
79
  end
67
80
 
68
- sig { returns(Bundler::Definition) }
69
- def definition
70
- @definition ||= Bundler::Dsl.evaluate(gemfile, lockfile, {})
71
- end
72
-
73
81
  sig { returns(String) }
74
82
  def dir
75
83
  File.expand_path(gemfile.path + "/..")
@@ -121,6 +121,8 @@ module Tapioca
121
121
  load_application(eager_load: requested_constants.empty?)
122
122
  load_dsl_generators
123
123
 
124
+ rbi_files_to_purge = existing_rbi_filenames(requested_constants)
125
+
124
126
  say("Compiling DSL RBI files...")
125
127
  say("")
126
128
 
@@ -133,7 +135,17 @@ module Tapioca
133
135
  )
134
136
 
135
137
  compiler.run do |constant, contents|
136
- compile_dsl_rbi(constant, contents)
138
+ filename = compile_dsl_rbi(constant, contents)
139
+ rbi_files_to_purge.delete(filename) if filename
140
+ end
141
+
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
137
149
  end
138
150
 
139
151
  say("")
@@ -192,6 +204,10 @@ module Tapioca
192
204
  exit(1)
193
205
  end
194
206
  say(" Done", :green)
207
+ unless bundle.missing_specs.empty?
208
+ say(" completed with missing specs: ")
209
+ say(bundle.missing_specs.join(', '), :yellow)
210
+ end
195
211
  puts
196
212
  end
197
213
 
@@ -249,13 +265,38 @@ module Tapioca
249
265
 
250
266
  sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
251
267
  def constantize(constant_names)
252
- constant_names.map do |name|
268
+ constant_map = constant_names.map do |name|
253
269
  begin
254
- name.constantize
270
+ [name, name.constantize]
255
271
  rescue NameError
256
- nil
272
+ [name, nil]
257
273
  end
258
- end.compact
274
+ end.to_h
275
+
276
+ unprocessable_constants = constant_map.select { |_, v| v.nil? }
277
+ unless unprocessable_constants.empty?
278
+ unprocessable_constants.each do |name, _|
279
+ say("Error: Cannot find constant '#{name}'", :red)
280
+ remove(dsl_rbi_filename(name))
281
+ end
282
+
283
+ exit(1)
284
+ end
285
+
286
+ constant_map.values
287
+ end
288
+
289
+ sig { params(requested_constants: T::Array[String]).returns(T::Set[Pathname]) }
290
+ def existing_rbi_filenames(requested_constants)
291
+ filenames = if requested_constants.empty?
292
+ Pathname.glob(config.outpath / "**/*.rbi")
293
+ else
294
+ requested_constants.map do |constant_name|
295
+ dsl_rbi_filename(constant_name)
296
+ end
297
+ end
298
+
299
+ filenames.to_set
259
300
  end
260
301
 
261
302
  sig { returns(T::Hash[String, String]) }
@@ -273,6 +314,11 @@ module Tapioca
273
314
  .to_h
274
315
  end
275
316
 
317
+ sig { params(constant_name: String).returns(Pathname) }
318
+ def dsl_rbi_filename(constant_name)
319
+ config.outpath / "#{constant_name.underscore}.rbi"
320
+ end
321
+
276
322
  sig { params(gem_name: String, version: String).returns(Pathname) }
277
323
  def gem_rbi_filename(gem_name, version)
278
324
  config.outpath / "#{gem_name}@#{version}.rbi"
@@ -312,6 +358,7 @@ module Tapioca
312
358
 
313
359
  sig { params(filename: Pathname).void }
314
360
  def remove(filename)
361
+ return unless filename.exist?
315
362
  say("-- Removing: #{filename}")
316
363
  filename.unlink
317
364
  end
@@ -452,7 +499,7 @@ module Tapioca
452
499
  end
453
500
  end
454
501
 
455
- sig { params(constant: Module, contents: String).void }
502
+ sig { params(constant: Module, contents: String).returns(T.nilable(Pathname)) }
456
503
  def compile_dsl_rbi(constant, contents)
457
504
  return if contents.nil?
458
505
 
@@ -472,6 +519,8 @@ module Tapioca
472
519
  File.write(filename, out)
473
520
  say("Wrote: ", [:green])
474
521
  say(filename)
522
+
523
+ filename
475
524
  end
476
525
  end
477
526
  end
@@ -99,17 +99,28 @@ module Tapioca
99
99
 
100
100
  sig { void }
101
101
  def eager_load_rails_app
102
+ rails = Object.const_get("Rails")
103
+ application = rails.application
104
+
102
105
  if Object.const_defined?("ActiveSupport")
103
106
  Object.const_get("ActiveSupport").run_load_hooks(
104
107
  :before_eager_load,
105
- Object.const_get("Rails").application
108
+ application
106
109
  )
107
110
  end
111
+
108
112
  if Object.const_defined?("Zeitwerk::Loader")
109
113
  zeitwerk_loader = Object.const_get("Zeitwerk::Loader")
110
114
  zeitwerk_loader.eager_load_all
111
115
  end
112
- Object.const_get("Rails").autoloaders.each(&:eager_load)
116
+
117
+ if rails.respond_to?(:autoloaders) && rails.autoloaders.zeitwerk_enabled?
118
+ rails.autoloaders.each(&:eager_load)
119
+ end
120
+
121
+ if application.config.respond_to?(:eager_load_namespaces)
122
+ application.config.eager_load_namespaces.each(&:eager_load!)
123
+ end
113
124
  end
114
125
 
115
126
  sig { void }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.11"
5
+ VERSION = "0.4.16"
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.11
4
+ version: 0.4.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,8 +11,22 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-01-12 00:00:00.000000000 Z
14
+ date: 2021-03-09 00:00:00.000000000 Z
15
15
  dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: bundler
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.17.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.17.3
16
30
  - !ruby/object:Gem::Dependency
17
31
  name: pry
18
32
  requirement: !ruby/object:Gem::Requirement