tapioca 0.4.23 → 0.5.0
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 +14 -14
- data/README.md +2 -2
- data/Rakefile +5 -7
- data/exe/tapioca +2 -2
- data/lib/tapioca/cli.rb +256 -2
- data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
- data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
- data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
- data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
- data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
- data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
- data/lib/tapioca/compilers/dsl/active_support_concern.rb +108 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
- data/lib/tapioca/compilers/dsl/base.rb +96 -82
- data/lib/tapioca/compilers/dsl/config.rb +111 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
- data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
- data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
- data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
- data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
- data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
- data/lib/tapioca/compilers/dsl_compiler.rb +22 -38
- data/lib/tapioca/compilers/requires_compiler.rb +2 -2
- data/lib/tapioca/compilers/sorbet.rb +26 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +139 -154
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/config.rb +2 -0
- data/lib/tapioca/config_builder.rb +4 -2
- data/lib/tapioca/constant_locator.rb +6 -8
- data/lib/tapioca/gemfile.rb +26 -19
- data/lib/tapioca/generator.rb +127 -43
- data/lib/tapioca/generic_type_registry.rb +25 -98
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
- data/lib/tapioca/internal.rb +1 -9
- data/lib/tapioca/loader.rb +14 -48
- data/lib/tapioca/rbi_ext/model.rb +122 -0
- data/lib/tapioca/reflection.rb +131 -0
- data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
- data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -0
- metadata +35 -23
- data/lib/tapioca/cli/main.rb +0 -146
- data/lib/tapioca/core_ext/class.rb +0 -28
- data/lib/tapioca/core_ext/string.rb +0 -18
- data/lib/tapioca/rbi/model.rb +0 -405
- data/lib/tapioca/rbi/printer.rb +0 -410
- data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
- data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
- data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
- data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -82
- data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -1,8 +1,8 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "json"
|
5
|
+
require "tempfile"
|
6
6
|
|
7
7
|
module Tapioca
|
8
8
|
module Compilers
|
@@ -25,7 +25,7 @@ module Tapioca
|
|
25
25
|
|
26
26
|
sig { params(paths: T::Array[String]).returns(T::Set[String]) }
|
27
27
|
def load_symbols(paths)
|
28
|
-
output = T.cast(Tempfile.create(
|
28
|
+
output = T.cast(Tempfile.create("sorbet") do |file|
|
29
29
|
file.write(Array(paths).join("\n"))
|
30
30
|
file.flush
|
31
31
|
|
@@ -69,7 +69,7 @@ module Tapioca
|
|
69
69
|
# TODO: CLASS is removed since v0.4.4730 of Sorbet
|
70
70
|
# but keeping here for backward compatibility. Remove
|
71
71
|
# once the minimum version is moved past that.
|
72
|
-
next unless
|
72
|
+
next unless ["CLASS", "CLASS_OR_MODULE", "STATIC_FIELD"].include?(kind)
|
73
73
|
next if name =~ /[<>()$]/
|
74
74
|
next if name =~ /^[0-9]+$/
|
75
75
|
next if name == "T::Helpers"
|
@@ -13,7 +13,7 @@ module Tapioca
|
|
13
13
|
def compile
|
14
14
|
list_todos.each_line.map do |line|
|
15
15
|
next if line.include?("<") || line.include?("class_of")
|
16
|
-
"module #{line.strip.gsub(
|
16
|
+
"module #{line.strip.gsub("T.untyped::", "")}; end"
|
17
17
|
end.compact.join("\n")
|
18
18
|
end
|
19
19
|
|
data/lib/tapioca/config.rb
CHANGED
@@ -9,9 +9,11 @@ module Tapioca
|
|
9
9
|
const(:prerequire, T.nilable(String))
|
10
10
|
const(:postrequire, String)
|
11
11
|
const(:exclude, T::Array[String])
|
12
|
+
const(:exclude_generators, T::Array[String])
|
12
13
|
const(:typed_overrides, T::Hash[String, String])
|
13
14
|
const(:todos_path, String)
|
14
15
|
const(:generators, T::Array[String])
|
16
|
+
const(:file_header, T::Boolean, default: true)
|
15
17
|
|
16
18
|
sig { returns(Pathname) }
|
17
19
|
def outpath
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
4
|
+
require "yaml"
|
5
5
|
|
6
6
|
module Tapioca
|
7
7
|
class ConfigBuilder
|
@@ -33,7 +33,7 @@ module Tapioca
|
|
33
33
|
sig { params(command: Symbol).returns(T::Hash[String, T.untyped]) }
|
34
34
|
def default_options(command)
|
35
35
|
default_outdir = case command
|
36
|
-
when :sync, :generate
|
36
|
+
when :sync, :generate, :gem
|
37
37
|
Config::DEFAULT_GEMDIR
|
38
38
|
when :dsl
|
39
39
|
Config::DEFAULT_DSLDIR
|
@@ -62,9 +62,11 @@ module Tapioca
|
|
62
62
|
"postrequire" => Config::DEFAULT_POSTREQUIRE,
|
63
63
|
"outdir" => nil,
|
64
64
|
"exclude" => [],
|
65
|
+
"exclude_generators" => [],
|
65
66
|
"typed_overrides" => Config::DEFAULT_OVERRIDES,
|
66
67
|
"todos_path" => Config::DEFAULT_TODOSPATH,
|
67
68
|
"generators" => [],
|
69
|
+
"file_header" => true,
|
68
70
|
}.freeze, T::Hash[String, T.untyped])
|
69
71
|
end
|
70
72
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
4
|
+
require "set"
|
5
5
|
|
6
6
|
module Tapioca
|
7
7
|
# Registers a TracePoint immediately upon load to track points at which
|
@@ -9,15 +9,14 @@ module Tapioca
|
|
9
9
|
# correspondence between classes/modules and files, as this information isn't
|
10
10
|
# available in the ruby runtime without extra accounting.
|
11
11
|
module ConstantLocator
|
12
|
-
|
12
|
+
extend Reflection
|
13
13
|
|
14
|
-
|
15
|
-
private_constant :NAME
|
14
|
+
@class_files = {}
|
16
15
|
|
17
16
|
# Immediately activated upon load. Observes class/module definition.
|
18
17
|
TracePoint.trace(:class) do |tp|
|
19
18
|
unless tp.self.singleton_class?
|
20
|
-
key =
|
19
|
+
key = name_of(tp.self)
|
21
20
|
@class_files[key] ||= Set.new
|
22
21
|
@class_files[key] << tp.path
|
23
22
|
end
|
@@ -26,11 +25,10 @@ module Tapioca
|
|
26
25
|
# Returns the files in which this class or module was opened. Doesn't know
|
27
26
|
# about situations where the class was opened prior to +require+ing,
|
28
27
|
# or where metaprogramming was used via +eval+, etc.
|
29
|
-
def files_for(klass)
|
30
|
-
name = String === klass ? klass :
|
28
|
+
def self.files_for(klass)
|
29
|
+
name = String === klass ? klass : name_of(klass)
|
31
30
|
files = @class_files[name]
|
32
31
|
files || Set.new
|
33
32
|
end
|
34
|
-
module_function :files_for
|
35
33
|
end
|
36
34
|
end
|
data/lib/tapioca/gemfile.rb
CHANGED
@@ -20,7 +20,7 @@ module Tapioca
|
|
20
20
|
sig { returns(Bundler::Definition) }
|
21
21
|
attr_reader(:definition)
|
22
22
|
|
23
|
-
sig { returns(T::Array[
|
23
|
+
sig { returns(T::Array[GemSpec]) }
|
24
24
|
attr_reader(:dependencies)
|
25
25
|
|
26
26
|
sig { returns(T::Array[String]) }
|
@@ -32,18 +32,18 @@ module Tapioca
|
|
32
32
|
@lockfile = T.let(File.new(Bundler.default_lockfile), File)
|
33
33
|
@definition = T.let(Bundler::Dsl.evaluate(gemfile, lockfile, {}), Bundler::Definition)
|
34
34
|
dependencies, missing_specs = load_dependencies
|
35
|
-
@dependencies = T.let(dependencies, T::Array[
|
35
|
+
@dependencies = T.let(dependencies, T::Array[GemSpec])
|
36
36
|
@missing_specs = T.let(missing_specs, T::Array[String])
|
37
37
|
end
|
38
38
|
|
39
|
-
sig { params(gem_name: String).returns(T.nilable(
|
39
|
+
sig { params(gem_name: String).returns(T.nilable(GemSpec)) }
|
40
40
|
def gem(gem_name)
|
41
41
|
dependencies.detect { |dep| dep.name == gem_name }
|
42
42
|
end
|
43
43
|
|
44
44
|
sig { void }
|
45
|
-
def
|
46
|
-
T.unsafe(runtime).
|
45
|
+
def require_bundle
|
46
|
+
T.unsafe(runtime).require(*groups)
|
47
47
|
end
|
48
48
|
|
49
49
|
private
|
@@ -51,23 +51,32 @@ module Tapioca
|
|
51
51
|
sig { returns(File) }
|
52
52
|
attr_reader(:gemfile, :lockfile)
|
53
53
|
|
54
|
-
sig { returns([T::Array[
|
54
|
+
sig { returns([T::Array[GemSpec], T::Array[String]]) }
|
55
55
|
def load_dependencies
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
dependencies = definition
|
61
|
-
.resolve
|
62
|
-
.materialize(deps, missing_specs)
|
63
|
-
.map { |spec| Gem.new(spec) }
|
56
|
+
materialized_dependencies, missing_specs = materialize_deps
|
57
|
+
dependencies = materialized_dependencies
|
58
|
+
.map { |spec| GemSpec.new(spec) }
|
64
59
|
.reject { |gem| gem.ignore?(dir) }
|
65
60
|
.uniq(&:rbi_file_name)
|
66
61
|
.sort_by(&:rbi_file_name)
|
67
|
-
|
68
62
|
[dependencies, missing_specs]
|
69
63
|
end
|
70
64
|
|
65
|
+
sig { returns([T::Enumerable[Spec], T::Array[String]]) }
|
66
|
+
def materialize_deps
|
67
|
+
deps = definition.locked_gems.dependencies.values
|
68
|
+
missing_specs = T::Array[String].new
|
69
|
+
materialized_dependencies = if definition.resolve.method(:materialize).arity == 1 # Support bundler >= v2.2.25
|
70
|
+
md = definition.resolve.materialize(deps)
|
71
|
+
missing_spec_names = md.missing_specs.map(&:name)
|
72
|
+
missing_specs = T.cast(md.missing_specs.map { |spec| "#{spec.name} (#{spec.version})" }, T::Array[String])
|
73
|
+
md.to_a.reject { |spec| missing_spec_names.include?(spec.name) }
|
74
|
+
else
|
75
|
+
definition.resolve.materialize(deps, missing_specs)
|
76
|
+
end
|
77
|
+
[materialized_dependencies, missing_specs]
|
78
|
+
end
|
79
|
+
|
71
80
|
sig { returns(Bundler::Runtime) }
|
72
81
|
def runtime
|
73
82
|
Bundler::Runtime.new(File.dirname(gemfile.path), definition)
|
@@ -83,12 +92,10 @@ module Tapioca
|
|
83
92
|
File.expand_path(gemfile.path + "/..")
|
84
93
|
end
|
85
94
|
|
86
|
-
class
|
95
|
+
class GemSpec
|
87
96
|
extend(T::Sig)
|
88
97
|
|
89
|
-
IGNORED_GEMS = T.let(
|
90
|
-
sorbet sorbet-static sorbet-runtime
|
91
|
-
}.freeze, T::Array[String])
|
98
|
+
IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime"].freeze, T::Array[String])
|
92
99
|
|
93
100
|
sig { returns(String) }
|
94
101
|
attr_reader :full_gem_path, :version
|
data/lib/tapioca/generator.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require "
|
4
|
+
require "pathname"
|
5
|
+
require "thor"
|
6
|
+
require "rake"
|
7
7
|
|
8
8
|
module Tapioca
|
9
9
|
class Generator < ::Thor::Shell::Color
|
@@ -63,11 +63,8 @@ module Tapioca
|
|
63
63
|
File.delete(requires_path) if File.exist?(requires_path)
|
64
64
|
|
65
65
|
content = String.new
|
66
|
-
content <<
|
67
|
-
|
68
|
-
reason: "explicit gem requires",
|
69
|
-
strictness: "false"
|
70
|
-
)
|
66
|
+
content << "# typed: true\n"
|
67
|
+
content << "# frozen_string_literal: true\n\n"
|
71
68
|
content << rb_string
|
72
69
|
|
73
70
|
outdir = File.dirname(requires_path)
|
@@ -121,11 +118,13 @@ module Tapioca
|
|
121
118
|
params(
|
122
119
|
requested_constants: T::Array[String],
|
123
120
|
should_verify: T::Boolean,
|
124
|
-
quiet: T::Boolean
|
121
|
+
quiet: T::Boolean,
|
122
|
+
verbose: T::Boolean
|
125
123
|
).void
|
126
124
|
end
|
127
|
-
def build_dsl(requested_constants, should_verify: false, quiet: false)
|
125
|
+
def build_dsl(requested_constants, should_verify: false, quiet: false, verbose: false)
|
128
126
|
load_application(eager_load: requested_constants.empty?)
|
127
|
+
abort_if_pending_migrations!
|
129
128
|
load_dsl_generators
|
130
129
|
|
131
130
|
if should_verify
|
@@ -140,20 +139,26 @@ module Tapioca
|
|
140
139
|
|
141
140
|
compiler = Compilers::DslCompiler.new(
|
142
141
|
requested_constants: constantize(requested_constants),
|
143
|
-
requested_generators: config.generators,
|
142
|
+
requested_generators: constantize_generators(config.generators),
|
143
|
+
excluded_generators: constantize_generators(config.exclude_generators),
|
144
144
|
error_handler: ->(error) {
|
145
145
|
say_error(error, :bold, :red)
|
146
146
|
}
|
147
147
|
)
|
148
148
|
|
149
149
|
compiler.run do |constant, contents|
|
150
|
-
constant_name =
|
150
|
+
constant_name = T.must(Reflection.name_of(constant))
|
151
|
+
|
152
|
+
if verbose && !quiet
|
153
|
+
say("Processing: ", [:yellow])
|
154
|
+
say(constant_name)
|
155
|
+
end
|
151
156
|
|
152
157
|
filename = compile_dsl_rbi(
|
153
158
|
constant_name,
|
154
159
|
contents,
|
155
160
|
outpath: outpath,
|
156
|
-
quiet: should_verify || quiet
|
161
|
+
quiet: should_verify || quiet && !verbose
|
157
162
|
)
|
158
163
|
|
159
164
|
if filename
|
@@ -174,8 +179,15 @@ module Tapioca
|
|
174
179
|
end
|
175
180
|
end
|
176
181
|
|
177
|
-
sig { void }
|
178
|
-
def sync_rbis_with_gemfile
|
182
|
+
sig { params(should_verify: T::Boolean).void }
|
183
|
+
def sync_rbis_with_gemfile(should_verify: false)
|
184
|
+
if should_verify
|
185
|
+
say("Checking for out-of-date RBIs...")
|
186
|
+
say("")
|
187
|
+
perform_sync_verification
|
188
|
+
return
|
189
|
+
end
|
190
|
+
|
179
191
|
anything_done = [
|
180
192
|
perform_removals,
|
181
193
|
perform_additions,
|
@@ -195,7 +207,7 @@ module Tapioca
|
|
195
207
|
|
196
208
|
EMPTY_RBI_COMMENT = <<~CONTENT
|
197
209
|
# THIS IS AN EMPTY RBI FILE.
|
198
|
-
# see https://github.com/Shopify/tapioca/
|
210
|
+
# see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires
|
199
211
|
CONTENT
|
200
212
|
|
201
213
|
sig { returns(Gemfile) }
|
@@ -205,7 +217,7 @@ module Tapioca
|
|
205
217
|
|
206
218
|
sig { returns(Loader) }
|
207
219
|
def loader
|
208
|
-
@loader ||= Loader.new
|
220
|
+
@loader ||= Loader.new
|
209
221
|
end
|
210
222
|
|
211
223
|
sig { returns(Compilers::SymbolTableCompiler) }
|
@@ -217,7 +229,7 @@ module Tapioca
|
|
217
229
|
def require_gem_file
|
218
230
|
say("Requiring all gems to prepare for compiling... ")
|
219
231
|
begin
|
220
|
-
loader.load_bundle(config.prerequire, config.postrequire)
|
232
|
+
loader.load_bundle(bundle, config.prerequire, config.postrequire)
|
221
233
|
rescue LoadError => e
|
222
234
|
explain_failed_require(config.postrequire, e)
|
223
235
|
exit(1)
|
@@ -225,7 +237,7 @@ module Tapioca
|
|
225
237
|
say(" Done", :green)
|
226
238
|
unless bundle.missing_specs.empty?
|
227
239
|
say(" completed with missing specs: ")
|
228
|
-
say(bundle.missing_specs.join(
|
240
|
+
say(bundle.missing_specs.join(", "), :yellow)
|
229
241
|
end
|
230
242
|
puts
|
231
243
|
end
|
@@ -260,7 +272,7 @@ module Tapioca
|
|
260
272
|
def load_application(eager_load:)
|
261
273
|
say("Loading Rails application... ")
|
262
274
|
|
263
|
-
loader.
|
275
|
+
loader.load_rails_application(
|
264
276
|
environment_load: true,
|
265
277
|
eager_load: eager_load
|
266
278
|
)
|
@@ -285,11 +297,9 @@ module Tapioca
|
|
285
297
|
sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
|
286
298
|
def constantize(constant_names)
|
287
299
|
constant_map = constant_names.map do |name|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
[name, nil]
|
292
|
-
end
|
300
|
+
[name, Object.const_get(name)]
|
301
|
+
rescue NameError
|
302
|
+
[name, nil]
|
293
303
|
end.to_h
|
294
304
|
|
295
305
|
unprocessable_constants = constant_map.select { |_, v| v.nil? }
|
@@ -305,6 +315,32 @@ module Tapioca
|
|
305
315
|
constant_map.values
|
306
316
|
end
|
307
317
|
|
318
|
+
sig { params(generator_names: T::Array[String]).returns(T::Array[T.class_of(Compilers::Dsl::Base)]) }
|
319
|
+
def constantize_generators(generator_names)
|
320
|
+
generator_map = generator_names.map do |name|
|
321
|
+
# Try to find built-in tapioca generator first, then globally defined generator. The
|
322
|
+
# explicit `break` ensures the class is returned, not the `potential_name`.
|
323
|
+
generator_klass = ["Tapioca::Compilers::Dsl::#{name}", name].find do |potential_name|
|
324
|
+
break Object.const_get(potential_name)
|
325
|
+
rescue NameError
|
326
|
+
# Skip if we can't find generator by the potential name
|
327
|
+
end
|
328
|
+
|
329
|
+
[name, generator_klass]
|
330
|
+
end.to_h
|
331
|
+
|
332
|
+
unprocessable_generators = generator_map.select { |_, v| v.nil? }
|
333
|
+
unless unprocessable_generators.empty?
|
334
|
+
unprocessable_generators.each do |name, _|
|
335
|
+
say("Error: Cannot find generator '#{name}'", :red)
|
336
|
+
end
|
337
|
+
|
338
|
+
exit(1)
|
339
|
+
end
|
340
|
+
|
341
|
+
generator_map.values
|
342
|
+
end
|
343
|
+
|
308
344
|
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
309
345
|
def existing_rbi_filenames(requested_constants, path: config.outpath)
|
310
346
|
filenames = if requested_constants.empty?
|
@@ -321,7 +357,7 @@ module Tapioca
|
|
321
357
|
sig { returns(T::Hash[String, String]) }
|
322
358
|
def existing_rbis
|
323
359
|
@existing_rbis ||= Pathname.glob((config.outpath / "*@*.rbi").to_s)
|
324
|
-
.map { |f| T.cast(f.basename(".*").to_s.split(
|
360
|
+
.map { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
|
325
361
|
.to_h
|
326
362
|
end
|
327
363
|
|
@@ -335,7 +371,7 @@ module Tapioca
|
|
335
371
|
|
336
372
|
sig { params(constant_name: String).returns(Pathname) }
|
337
373
|
def dsl_rbi_filename(constant_name)
|
338
|
-
config.outpath / "#{constant_name
|
374
|
+
config.outpath / "#{underscore(constant_name)}.rbi"
|
339
375
|
end
|
340
376
|
|
341
377
|
sig { params(gem_name: String, version: String).returns(Pathname) }
|
@@ -456,7 +492,7 @@ module Tapioca
|
|
456
492
|
|
457
493
|
sig do
|
458
494
|
params(gem_names: T::Array[String])
|
459
|
-
.returns(T::Array[Gemfile::
|
495
|
+
.returns(T::Array[Gemfile::GemSpec])
|
460
496
|
end
|
461
497
|
def gems_to_generate(gem_names)
|
462
498
|
return bundle.dependencies if gem_names.empty?
|
@@ -483,10 +519,16 @@ module Tapioca
|
|
483
519
|
# typed: #{strictness}
|
484
520
|
SIGIL
|
485
521
|
|
486
|
-
|
522
|
+
if config.file_header
|
523
|
+
[statement, sigil].compact.join("\n").strip.concat("\n\n")
|
524
|
+
elsif sigil
|
525
|
+
sigil.strip.concat("\n\n")
|
526
|
+
else
|
527
|
+
""
|
528
|
+
end
|
487
529
|
end
|
488
530
|
|
489
|
-
sig { params(gem: Gemfile::
|
531
|
+
sig { params(gem: Gemfile::GemSpec).void }
|
490
532
|
def compile_gem_rbi(gem)
|
491
533
|
compiler = Compilers::SymbolTableCompiler.new
|
492
534
|
gem_name = set_color(gem.name, :yellow, :bold)
|
@@ -525,7 +567,7 @@ module Tapioca
|
|
525
567
|
def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
|
526
568
|
return if contents.nil?
|
527
569
|
|
528
|
-
rbi_name = constant_name
|
570
|
+
rbi_name = underscore(constant_name) + ".rbi"
|
529
571
|
filename = outpath / rbi_name
|
530
572
|
|
531
573
|
out = String.new
|
@@ -598,11 +640,47 @@ module Tapioca
|
|
598
640
|
def perform_dsl_verification(dir)
|
599
641
|
diff = verify_dsl_rbi(tmp_dir: dir)
|
600
642
|
|
643
|
+
report_diff_and_exit_if_out_of_date(diff, "dsl")
|
644
|
+
ensure
|
645
|
+
FileUtils.remove_entry(dir)
|
646
|
+
end
|
647
|
+
|
648
|
+
sig { params(files: T::Set[Pathname]).void }
|
649
|
+
def purge_stale_dsl_rbi_files(files)
|
650
|
+
if files.any?
|
651
|
+
say("Removing stale RBI files...")
|
652
|
+
|
653
|
+
files.sort.each do |filename|
|
654
|
+
remove(filename)
|
655
|
+
end
|
656
|
+
say("")
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
sig { void }
|
661
|
+
def perform_sync_verification
|
662
|
+
diff = {}
|
663
|
+
|
664
|
+
removed_rbis.each do |gem_name|
|
665
|
+
filename = existing_rbi(gem_name)
|
666
|
+
diff[filename] = :removed
|
667
|
+
end
|
668
|
+
|
669
|
+
added_rbis.each do |gem_name|
|
670
|
+
filename = expected_rbi(gem_name)
|
671
|
+
diff[filename] = gem_rbi_exists?(gem_name) ? :changed : :added
|
672
|
+
end
|
673
|
+
|
674
|
+
report_diff_and_exit_if_out_of_date(diff, "sync")
|
675
|
+
end
|
676
|
+
|
677
|
+
sig { params(diff: T::Hash[String, Symbol], command: String).void }
|
678
|
+
def report_diff_and_exit_if_out_of_date(diff, command)
|
601
679
|
if diff.empty?
|
602
680
|
say("Nothing to do, all RBIs are up-to-date.")
|
603
681
|
else
|
604
682
|
say("RBI files are out-of-date. In your development environment, please run:", :green)
|
605
|
-
say(" `#{Config::DEFAULT_COMMAND}
|
683
|
+
say(" `#{Config::DEFAULT_COMMAND} #{command}`", [:green, :bold])
|
606
684
|
say("Once it is complete, be sure to commit and push any changes", :green)
|
607
685
|
|
608
686
|
say("")
|
@@ -614,20 +692,26 @@ module Tapioca
|
|
614
692
|
|
615
693
|
exit(1)
|
616
694
|
end
|
617
|
-
ensure
|
618
|
-
FileUtils.remove_entry(dir)
|
619
695
|
end
|
620
696
|
|
621
|
-
sig {
|
622
|
-
def
|
623
|
-
|
624
|
-
say("Removing stale RBI files...")
|
697
|
+
sig { void }
|
698
|
+
def abort_if_pending_migrations!
|
699
|
+
return unless File.exist?("config/application.rb")
|
625
700
|
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
701
|
+
Rails.application.load_tasks
|
702
|
+
Rake::Task["db:abort_if_pending_migrations"].invoke if Rake::Task.task_defined?("db:abort_if_pending_migrations")
|
703
|
+
end
|
704
|
+
|
705
|
+
sig { params(class_name: String).returns(String) }
|
706
|
+
def underscore(class_name)
|
707
|
+
return class_name unless /[A-Z-]|::/.match?(class_name)
|
708
|
+
|
709
|
+
word = class_name.to_s.gsub("::", "/")
|
710
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
711
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
712
|
+
word.tr!("-", "_")
|
713
|
+
word.downcase!
|
714
|
+
word
|
631
715
|
end
|
632
716
|
end
|
633
717
|
end
|