tapioca 0.4.16 → 0.4.21
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/README.md +4 -2
- data/exe/tapioca +17 -2
- data/lib/tapioca.rb +1 -27
- data/lib/tapioca/cli.rb +1 -108
- data/lib/tapioca/cli/main.rb +146 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +4 -4
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +1 -1
- data/lib/tapioca/compilers/dsl/base.rb +1 -1
- data/lib/tapioca/compilers/dsl/protobuf.rb +132 -16
- data/lib/tapioca/compilers/dsl/url_helpers.rb +3 -3
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -39
- data/lib/tapioca/config.rb +1 -1
- data/lib/tapioca/config_builder.rb +7 -12
- data/lib/tapioca/generator.rb +141 -36
- data/lib/tapioca/generic_type_registry.rb +222 -0
- data/lib/tapioca/internal.rb +21 -0
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +66 -0
- data/lib/tapioca/sorbet_ext/name_patch.rb +16 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +7 -2
@@ -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:
|
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:
|
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:
|
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
|
78
|
-
|
79
|
-
|
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
|
-
|
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) &&
|
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
|
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
|
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
|
235
|
+
return unless T::Enum > constant
|
225
236
|
|
226
|
-
enums = T.
|
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
|
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
|
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
|
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
|
-
|
446
|
-
|
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
|
-
|
535
|
+
return if methods.empty?
|
477
536
|
|
478
|
-
|
479
|
-
|
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:
|
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:
|
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
|
data/lib/tapioca/config.rb
CHANGED
@@ -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
|
-
|
14
|
-
|
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,
|
data/lib/tapioca/generator.rb
CHANGED
@@ -63,7 +63,7 @@ module Tapioca
|
|
63
63
|
|
64
64
|
content = String.new
|
65
65
|
content << rbi_header(
|
66
|
-
|
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("
|
80
|
-
say("Please review changes and commit them, then run
|
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
|
-
|
102
|
+
"#{Config::DEFAULT_COMMAND} todo",
|
103
103
|
reason: "unresolved constants",
|
104
104
|
strictness: "false"
|
105
105
|
)
|
@@ -116,16 +116,27 @@ module Tapioca
|
|
116
116
|
say("Please review changes and commit them.", [:green, :bold])
|
117
117
|
end
|
118
118
|
|
119
|
-
sig
|
120
|
-
|
119
|
+
sig do
|
120
|
+
params(
|
121
|
+
requested_constants: T::Array[String],
|
122
|
+
should_verify: T::Boolean,
|
123
|
+
quiet: T::Boolean
|
124
|
+
).void
|
125
|
+
end
|
126
|
+
def build_dsl(requested_constants, should_verify: false, quiet: false)
|
121
127
|
load_application(eager_load: requested_constants.empty?)
|
122
128
|
load_dsl_generators
|
123
129
|
|
124
|
-
|
125
|
-
|
126
|
-
|
130
|
+
if should_verify
|
131
|
+
say("Checking for out-of-date RBIs...")
|
132
|
+
else
|
133
|
+
say("Compiling DSL RBI files...")
|
134
|
+
end
|
127
135
|
say("")
|
128
136
|
|
137
|
+
outpath = should_verify ? Pathname.new(Dir.mktmpdir) : config.outpath
|
138
|
+
rbi_files_to_purge = existing_rbi_filenames(requested_constants)
|
139
|
+
|
129
140
|
compiler = Compilers::DslCompiler.new(
|
130
141
|
requested_constants: constantize(requested_constants),
|
131
142
|
requested_generators: config.generators,
|
@@ -135,24 +146,31 @@ module Tapioca
|
|
135
146
|
)
|
136
147
|
|
137
148
|
compiler.run do |constant, contents|
|
138
|
-
|
139
|
-
rbi_files_to_purge.delete(filename) if filename
|
140
|
-
end
|
149
|
+
constant_name = Module.instance_method(:name).bind(constant).call
|
141
150
|
|
142
|
-
|
143
|
-
|
144
|
-
|
151
|
+
filename = compile_dsl_rbi(
|
152
|
+
constant_name,
|
153
|
+
contents,
|
154
|
+
outpath: outpath,
|
155
|
+
quiet: should_verify || quiet
|
156
|
+
)
|
145
157
|
|
146
|
-
|
147
|
-
|
158
|
+
if filename
|
159
|
+
rbi_files_to_purge.delete(filename)
|
148
160
|
end
|
149
161
|
end
|
150
|
-
|
151
162
|
say("")
|
152
|
-
say("Done", :green)
|
153
163
|
|
154
|
-
|
155
|
-
|
164
|
+
if should_verify
|
165
|
+
perform_dsl_verification(outpath)
|
166
|
+
else
|
167
|
+
purge_stale_dsl_rbi_files(rbi_files_to_purge)
|
168
|
+
|
169
|
+
say("Done", :green)
|
170
|
+
|
171
|
+
say("All operations performed in working directory.", [:green, :bold])
|
172
|
+
say("Please review changes and commit them.", [:green, :bold])
|
173
|
+
end
|
156
174
|
end
|
157
175
|
|
158
176
|
sig { void }
|
@@ -218,7 +236,7 @@ module Tapioca
|
|
218
236
|
say_error("If you populated ", :yellow)
|
219
237
|
say_error("#{file} ", :bold, :blue)
|
220
238
|
say_error("with ", :yellow)
|
221
|
-
say_error("
|
239
|
+
say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue)
|
222
240
|
say_error("you should probably review it and remove the faulty line.", :yellow)
|
223
241
|
end
|
224
242
|
|
@@ -286,10 +304,10 @@ module Tapioca
|
|
286
304
|
constant_map.values
|
287
305
|
end
|
288
306
|
|
289
|
-
sig { params(requested_constants: T::Array[String]).returns(T::Set[Pathname]) }
|
290
|
-
def existing_rbi_filenames(requested_constants)
|
307
|
+
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
308
|
+
def existing_rbi_filenames(requested_constants, path: config.outpath)
|
291
309
|
filenames = if requested_constants.empty?
|
292
|
-
Pathname.glob(
|
310
|
+
Pathname.glob(path / "**/*.rbi")
|
293
311
|
else
|
294
312
|
requested_constants.map do |constant_name|
|
295
313
|
dsl_rbi_filename(constant_name)
|
@@ -477,7 +495,7 @@ module Tapioca
|
|
477
495
|
rbi_body_content = compiler.compile(gem)
|
478
496
|
content = String.new
|
479
497
|
content << rbi_header(
|
480
|
-
|
498
|
+
"#{Config::DEFAULT_COMMAND} sync",
|
481
499
|
reason: "types exported from the `#{gem.name}` gem",
|
482
500
|
strictness: strictness
|
483
501
|
)
|
@@ -494,33 +512,120 @@ module Tapioca
|
|
494
512
|
end
|
495
513
|
File.write(filename.to_s, content)
|
496
514
|
|
497
|
-
Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
515
|
+
T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
498
516
|
remove(file) unless file.basename.to_s == gem.rbi_file_name
|
499
517
|
end
|
500
518
|
end
|
501
519
|
|
502
|
-
sig
|
503
|
-
|
520
|
+
sig do
|
521
|
+
params(constant_name: String, contents: String, outpath: Pathname, quiet: T::Boolean)
|
522
|
+
.returns(T.nilable(Pathname))
|
523
|
+
end
|
524
|
+
def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
|
504
525
|
return if contents.nil?
|
505
526
|
|
506
|
-
command = format(config.generate_command, constant.name)
|
507
|
-
constant_name = Module.instance_method(:name).bind(constant).call
|
508
527
|
rbi_name = constant_name.underscore + ".rbi"
|
509
|
-
filename =
|
528
|
+
filename = outpath / rbi_name
|
510
529
|
|
511
530
|
out = String.new
|
512
531
|
out << rbi_header(
|
513
|
-
|
514
|
-
reason: "dynamic methods in `#{
|
532
|
+
"#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
|
533
|
+
reason: "dynamic methods in `#{constant_name}`"
|
515
534
|
)
|
516
535
|
out << contents
|
517
536
|
|
518
537
|
FileUtils.mkdir_p(File.dirname(filename))
|
519
538
|
File.write(filename, out)
|
520
|
-
|
521
|
-
|
539
|
+
|
540
|
+
unless quiet
|
541
|
+
say("Wrote: ", [:green])
|
542
|
+
say(filename)
|
543
|
+
end
|
522
544
|
|
523
545
|
filename
|
524
546
|
end
|
547
|
+
|
548
|
+
sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
|
549
|
+
def verify_dsl_rbi(tmp_dir:)
|
550
|
+
diff = {}
|
551
|
+
|
552
|
+
existing_rbis = rbi_files_in(config.outpath)
|
553
|
+
new_rbis = rbi_files_in(tmp_dir)
|
554
|
+
|
555
|
+
added_files = (new_rbis - existing_rbis)
|
556
|
+
|
557
|
+
added_files.each do |file|
|
558
|
+
diff[file] = :added
|
559
|
+
end
|
560
|
+
|
561
|
+
removed_files = (existing_rbis - new_rbis)
|
562
|
+
|
563
|
+
removed_files.each do |file|
|
564
|
+
diff[file] = :removed
|
565
|
+
end
|
566
|
+
|
567
|
+
common_files = (existing_rbis & new_rbis)
|
568
|
+
|
569
|
+
changed_files = common_files.map do |filename|
|
570
|
+
filename unless FileUtils.identical?(config.outpath / filename, tmp_dir / filename)
|
571
|
+
end.compact
|
572
|
+
|
573
|
+
changed_files.each do |file|
|
574
|
+
diff[file] = :changed
|
575
|
+
end
|
576
|
+
|
577
|
+
diff
|
578
|
+
end
|
579
|
+
|
580
|
+
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
|
581
|
+
def build_error_for_files(cause, files)
|
582
|
+
filenames = files.map do |file|
|
583
|
+
config.outpath / file
|
584
|
+
end.join("\n - ")
|
585
|
+
|
586
|
+
" File(s) #{cause}:\n - #{filenames}"
|
587
|
+
end
|
588
|
+
|
589
|
+
sig { params(path: Pathname).returns(T::Array[Pathname]) }
|
590
|
+
def rbi_files_in(path)
|
591
|
+
Pathname.glob(path / "**/*.rbi").map do |file|
|
592
|
+
file.relative_path_from(path)
|
593
|
+
end.sort
|
594
|
+
end
|
595
|
+
|
596
|
+
sig { params(dir: Pathname).void }
|
597
|
+
def perform_dsl_verification(dir)
|
598
|
+
diff = verify_dsl_rbi(tmp_dir: dir)
|
599
|
+
|
600
|
+
if diff.empty?
|
601
|
+
say("Nothing to do, all RBIs are up-to-date.")
|
602
|
+
else
|
603
|
+
say("RBI files are out-of-date, please run:")
|
604
|
+
say(" `#{Config::DEFAULT_COMMAND} dsl`")
|
605
|
+
|
606
|
+
say("")
|
607
|
+
|
608
|
+
say("Reason:", [:red])
|
609
|
+
diff.group_by(&:last).sort.each do |cause, diff_for_cause|
|
610
|
+
say(build_error_for_files(cause, diff_for_cause.map(&:first)))
|
611
|
+
end
|
612
|
+
|
613
|
+
exit(1)
|
614
|
+
end
|
615
|
+
ensure
|
616
|
+
FileUtils.remove_entry(dir)
|
617
|
+
end
|
618
|
+
|
619
|
+
sig { params(files: T::Set[Pathname]).void }
|
620
|
+
def purge_stale_dsl_rbi_files(files)
|
621
|
+
if files.any?
|
622
|
+
say("Removing stale RBI files...")
|
623
|
+
|
624
|
+
files.sort.each do |filename|
|
625
|
+
remove(filename)
|
626
|
+
end
|
627
|
+
say("")
|
628
|
+
end
|
629
|
+
end
|
525
630
|
end
|
526
631
|
end
|