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 +4 -4
- data/Gemfile +0 -1
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +38 -7
- data/lib/tapioca/compilers/dsl/base.rb +1 -1
- data/lib/tapioca/compilers/sorbet.rb +4 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +38 -15
- data/lib/tapioca/gemfile.rb +30 -22
- data/lib/tapioca/generator.rb +55 -6
- data/lib/tapioca/loader.rb +13 -2
- data/lib/tapioca/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80a1d62bade6d75e6ec3a7de5a06f96eb7bc4ea002a70a2503d74393e834e864
|
4
|
+
data.tar.gz: 235cd2477d3f07176bdbddd5653c7a5cff86516f81dd196a65043a5b2f7d68dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23bc803e2b251dfb4ef4c82b0793336810ca9d551355ac1cd8d514c1484ef096d9b00a12cda214ce9b4b83ccf1620a32159035a5bf798a4c2d8a3e96b026e352
|
7
|
+
data.tar.gz: 606e2ad0e81c87e7f89b5933fa160d1b7825dee6f3f3451042ad2ec047a672041b559ed9cb79cf75aa0af7020b9aed2da85e1f52f36bb39b909f5037f8865717
|
data/Gemfile
CHANGED
@@ -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
|
-
|
107
|
-
|
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
|
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
|
-
|
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} = #{
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
349
|
-
|
350
|
-
|
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
|
|
data/lib/tapioca/gemfile.rb
CHANGED
@@ -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
|
-
@
|
25
|
-
|
26
|
-
|
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 + "/..")
|
data/lib/tapioca/generator.rb
CHANGED
@@ -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.
|
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).
|
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
|
data/lib/tapioca/loader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 }
|
data/lib/tapioca/version.rb
CHANGED
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.
|
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-
|
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
|