tapioca 0.4.11 → 0.4.16

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: 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