tapioca 0.4.24 → 0.5.1
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 +34 -22
- 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 -86
- 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,8 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require "tapioca/core_ext/string"
|
4
|
+
require "pathname"
|
5
|
+
require "thor"
|
7
6
|
|
8
7
|
module Tapioca
|
9
8
|
class Generator < ::Thor::Shell::Color
|
@@ -63,11 +62,8 @@ module Tapioca
|
|
63
62
|
File.delete(requires_path) if File.exist?(requires_path)
|
64
63
|
|
65
64
|
content = String.new
|
66
|
-
content <<
|
67
|
-
|
68
|
-
reason: "explicit gem requires",
|
69
|
-
strictness: "false"
|
70
|
-
)
|
65
|
+
content << "# typed: true\n"
|
66
|
+
content << "# frozen_string_literal: true\n\n"
|
71
67
|
content << rb_string
|
72
68
|
|
73
69
|
outdir = File.dirname(requires_path)
|
@@ -121,11 +117,13 @@ module Tapioca
|
|
121
117
|
params(
|
122
118
|
requested_constants: T::Array[String],
|
123
119
|
should_verify: T::Boolean,
|
124
|
-
quiet: T::Boolean
|
120
|
+
quiet: T::Boolean,
|
121
|
+
verbose: T::Boolean
|
125
122
|
).void
|
126
123
|
end
|
127
|
-
def build_dsl(requested_constants, should_verify: false, quiet: false)
|
124
|
+
def build_dsl(requested_constants, should_verify: false, quiet: false, verbose: false)
|
128
125
|
load_application(eager_load: requested_constants.empty?)
|
126
|
+
abort_if_pending_migrations!
|
129
127
|
load_dsl_generators
|
130
128
|
|
131
129
|
if should_verify
|
@@ -140,20 +138,26 @@ module Tapioca
|
|
140
138
|
|
141
139
|
compiler = Compilers::DslCompiler.new(
|
142
140
|
requested_constants: constantize(requested_constants),
|
143
|
-
requested_generators: config.generators,
|
141
|
+
requested_generators: constantize_generators(config.generators),
|
142
|
+
excluded_generators: constantize_generators(config.exclude_generators),
|
144
143
|
error_handler: ->(error) {
|
145
144
|
say_error(error, :bold, :red)
|
146
145
|
}
|
147
146
|
)
|
148
147
|
|
149
148
|
compiler.run do |constant, contents|
|
150
|
-
constant_name =
|
149
|
+
constant_name = T.must(Reflection.name_of(constant))
|
150
|
+
|
151
|
+
if verbose && !quiet
|
152
|
+
say("Processing: ", [:yellow])
|
153
|
+
say(constant_name)
|
154
|
+
end
|
151
155
|
|
152
156
|
filename = compile_dsl_rbi(
|
153
157
|
constant_name,
|
154
158
|
contents,
|
155
159
|
outpath: outpath,
|
156
|
-
quiet: should_verify || quiet
|
160
|
+
quiet: should_verify || quiet && !verbose
|
157
161
|
)
|
158
162
|
|
159
163
|
if filename
|
@@ -174,8 +178,15 @@ module Tapioca
|
|
174
178
|
end
|
175
179
|
end
|
176
180
|
|
177
|
-
sig { void }
|
178
|
-
def sync_rbis_with_gemfile
|
181
|
+
sig { params(should_verify: T::Boolean).void }
|
182
|
+
def sync_rbis_with_gemfile(should_verify: false)
|
183
|
+
if should_verify
|
184
|
+
say("Checking for out-of-date RBIs...")
|
185
|
+
say("")
|
186
|
+
perform_sync_verification
|
187
|
+
return
|
188
|
+
end
|
189
|
+
|
179
190
|
anything_done = [
|
180
191
|
perform_removals,
|
181
192
|
perform_additions,
|
@@ -195,7 +206,7 @@ module Tapioca
|
|
195
206
|
|
196
207
|
EMPTY_RBI_COMMENT = <<~CONTENT
|
197
208
|
# THIS IS AN EMPTY RBI FILE.
|
198
|
-
# see https://github.com/Shopify/tapioca/
|
209
|
+
# see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires
|
199
210
|
CONTENT
|
200
211
|
|
201
212
|
sig { returns(Gemfile) }
|
@@ -205,7 +216,7 @@ module Tapioca
|
|
205
216
|
|
206
217
|
sig { returns(Loader) }
|
207
218
|
def loader
|
208
|
-
@loader ||= Loader.new
|
219
|
+
@loader ||= Loader.new
|
209
220
|
end
|
210
221
|
|
211
222
|
sig { returns(Compilers::SymbolTableCompiler) }
|
@@ -217,7 +228,7 @@ module Tapioca
|
|
217
228
|
def require_gem_file
|
218
229
|
say("Requiring all gems to prepare for compiling... ")
|
219
230
|
begin
|
220
|
-
loader.load_bundle(config.prerequire, config.postrequire)
|
231
|
+
loader.load_bundle(bundle, config.prerequire, config.postrequire)
|
221
232
|
rescue LoadError => e
|
222
233
|
explain_failed_require(config.postrequire, e)
|
223
234
|
exit(1)
|
@@ -225,7 +236,7 @@ module Tapioca
|
|
225
236
|
say(" Done", :green)
|
226
237
|
unless bundle.missing_specs.empty?
|
227
238
|
say(" completed with missing specs: ")
|
228
|
-
say(bundle.missing_specs.join(
|
239
|
+
say(bundle.missing_specs.join(", "), :yellow)
|
229
240
|
end
|
230
241
|
puts
|
231
242
|
end
|
@@ -260,7 +271,7 @@ module Tapioca
|
|
260
271
|
def load_application(eager_load:)
|
261
272
|
say("Loading Rails application... ")
|
262
273
|
|
263
|
-
loader.
|
274
|
+
loader.load_rails_application(
|
264
275
|
environment_load: true,
|
265
276
|
eager_load: eager_load
|
266
277
|
)
|
@@ -285,11 +296,9 @@ module Tapioca
|
|
285
296
|
sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
|
286
297
|
def constantize(constant_names)
|
287
298
|
constant_map = constant_names.map do |name|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
[name, nil]
|
292
|
-
end
|
299
|
+
[name, Object.const_get(name)]
|
300
|
+
rescue NameError
|
301
|
+
[name, nil]
|
293
302
|
end.to_h
|
294
303
|
|
295
304
|
unprocessable_constants = constant_map.select { |_, v| v.nil? }
|
@@ -305,6 +314,32 @@ module Tapioca
|
|
305
314
|
constant_map.values
|
306
315
|
end
|
307
316
|
|
317
|
+
sig { params(generator_names: T::Array[String]).returns(T::Array[T.class_of(Compilers::Dsl::Base)]) }
|
318
|
+
def constantize_generators(generator_names)
|
319
|
+
generator_map = generator_names.map do |name|
|
320
|
+
# Try to find built-in tapioca generator first, then globally defined generator. The
|
321
|
+
# explicit `break` ensures the class is returned, not the `potential_name`.
|
322
|
+
generator_klass = ["Tapioca::Compilers::Dsl::#{name}", name].find do |potential_name|
|
323
|
+
break Object.const_get(potential_name)
|
324
|
+
rescue NameError
|
325
|
+
# Skip if we can't find generator by the potential name
|
326
|
+
end
|
327
|
+
|
328
|
+
[name, generator_klass]
|
329
|
+
end.to_h
|
330
|
+
|
331
|
+
unprocessable_generators = generator_map.select { |_, v| v.nil? }
|
332
|
+
unless unprocessable_generators.empty?
|
333
|
+
unprocessable_generators.each do |name, _|
|
334
|
+
say("Error: Cannot find generator '#{name}'", :red)
|
335
|
+
end
|
336
|
+
|
337
|
+
exit(1)
|
338
|
+
end
|
339
|
+
|
340
|
+
generator_map.values
|
341
|
+
end
|
342
|
+
|
308
343
|
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
309
344
|
def existing_rbi_filenames(requested_constants, path: config.outpath)
|
310
345
|
filenames = if requested_constants.empty?
|
@@ -321,7 +356,7 @@ module Tapioca
|
|
321
356
|
sig { returns(T::Hash[String, String]) }
|
322
357
|
def existing_rbis
|
323
358
|
@existing_rbis ||= Pathname.glob((config.outpath / "*@*.rbi").to_s)
|
324
|
-
.map { |f| T.cast(f.basename(".*").to_s.split(
|
359
|
+
.map { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
|
325
360
|
.to_h
|
326
361
|
end
|
327
362
|
|
@@ -335,7 +370,7 @@ module Tapioca
|
|
335
370
|
|
336
371
|
sig { params(constant_name: String).returns(Pathname) }
|
337
372
|
def dsl_rbi_filename(constant_name)
|
338
|
-
config.outpath / "#{constant_name
|
373
|
+
config.outpath / "#{underscore(constant_name)}.rbi"
|
339
374
|
end
|
340
375
|
|
341
376
|
sig { params(gem_name: String, version: String).returns(Pathname) }
|
@@ -456,7 +491,7 @@ module Tapioca
|
|
456
491
|
|
457
492
|
sig do
|
458
493
|
params(gem_names: T::Array[String])
|
459
|
-
.returns(T::Array[Gemfile::
|
494
|
+
.returns(T::Array[Gemfile::GemSpec])
|
460
495
|
end
|
461
496
|
def gems_to_generate(gem_names)
|
462
497
|
return bundle.dependencies if gem_names.empty?
|
@@ -483,10 +518,16 @@ module Tapioca
|
|
483
518
|
# typed: #{strictness}
|
484
519
|
SIGIL
|
485
520
|
|
486
|
-
|
521
|
+
if config.file_header
|
522
|
+
[statement, sigil].compact.join("\n").strip.concat("\n\n")
|
523
|
+
elsif sigil
|
524
|
+
sigil.strip.concat("\n\n")
|
525
|
+
else
|
526
|
+
""
|
527
|
+
end
|
487
528
|
end
|
488
529
|
|
489
|
-
sig { params(gem: Gemfile::
|
530
|
+
sig { params(gem: Gemfile::GemSpec).void }
|
490
531
|
def compile_gem_rbi(gem)
|
491
532
|
compiler = Compilers::SymbolTableCompiler.new
|
492
533
|
gem_name = set_color(gem.name, :yellow, :bold)
|
@@ -525,7 +566,7 @@ module Tapioca
|
|
525
566
|
def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
|
526
567
|
return if contents.nil?
|
527
568
|
|
528
|
-
rbi_name = constant_name
|
569
|
+
rbi_name = underscore(constant_name) + ".rbi"
|
529
570
|
filename = outpath / rbi_name
|
530
571
|
|
531
572
|
out = String.new
|
@@ -598,11 +639,47 @@ module Tapioca
|
|
598
639
|
def perform_dsl_verification(dir)
|
599
640
|
diff = verify_dsl_rbi(tmp_dir: dir)
|
600
641
|
|
642
|
+
report_diff_and_exit_if_out_of_date(diff, "dsl")
|
643
|
+
ensure
|
644
|
+
FileUtils.remove_entry(dir)
|
645
|
+
end
|
646
|
+
|
647
|
+
sig { params(files: T::Set[Pathname]).void }
|
648
|
+
def purge_stale_dsl_rbi_files(files)
|
649
|
+
if files.any?
|
650
|
+
say("Removing stale RBI files...")
|
651
|
+
|
652
|
+
files.sort.each do |filename|
|
653
|
+
remove(filename)
|
654
|
+
end
|
655
|
+
say("")
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
sig { void }
|
660
|
+
def perform_sync_verification
|
661
|
+
diff = {}
|
662
|
+
|
663
|
+
removed_rbis.each do |gem_name|
|
664
|
+
filename = existing_rbi(gem_name)
|
665
|
+
diff[filename] = :removed
|
666
|
+
end
|
667
|
+
|
668
|
+
added_rbis.each do |gem_name|
|
669
|
+
filename = expected_rbi(gem_name)
|
670
|
+
diff[filename] = gem_rbi_exists?(gem_name) ? :changed : :added
|
671
|
+
end
|
672
|
+
|
673
|
+
report_diff_and_exit_if_out_of_date(diff, "sync")
|
674
|
+
end
|
675
|
+
|
676
|
+
sig { params(diff: T::Hash[String, Symbol], command: String).void }
|
677
|
+
def report_diff_and_exit_if_out_of_date(diff, command)
|
601
678
|
if diff.empty?
|
602
679
|
say("Nothing to do, all RBIs are up-to-date.")
|
603
680
|
else
|
604
681
|
say("RBI files are out-of-date. In your development environment, please run:", :green)
|
605
|
-
say(" `#{Config::DEFAULT_COMMAND}
|
682
|
+
say(" `#{Config::DEFAULT_COMMAND} #{command}`", [:green, :bold])
|
606
683
|
say("Once it is complete, be sure to commit and push any changes", :green)
|
607
684
|
|
608
685
|
say("")
|
@@ -614,20 +691,27 @@ module Tapioca
|
|
614
691
|
|
615
692
|
exit(1)
|
616
693
|
end
|
617
|
-
ensure
|
618
|
-
FileUtils.remove_entry(dir)
|
619
694
|
end
|
620
695
|
|
621
|
-
sig {
|
622
|
-
def
|
623
|
-
|
624
|
-
|
696
|
+
sig { void }
|
697
|
+
def abort_if_pending_migrations!
|
698
|
+
return unless File.exist?("config/application.rb")
|
699
|
+
return unless defined?(::Rake)
|
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
|