tapioca 0.4.27 → 0.5.3
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 +15 -15
- data/README.md +2 -2
- data/Rakefile +5 -7
- data/exe/tapioca +2 -2
- data/lib/tapioca/cli.rb +172 -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_model_secure_password.rb +101 -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_fixtures.rb +86 -0
- 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 +106 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
- data/lib/tapioca/compilers/dsl/base.rb +108 -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/mixed_in_class_attributes.rb +74 -0
- 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 +21 -33
- 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 +25 -40
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
- data/lib/tapioca/compilers/requires_compiler.rb +2 -2
- data/lib/tapioca/compilers/sorbet.rb +25 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -206
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/config.rb +3 -0
- data/lib/tapioca/config_builder.rb +5 -2
- data/lib/tapioca/constant_locator.rb +6 -8
- data/lib/tapioca/gemfile.rb +14 -11
- data/lib/tapioca/generators/base.rb +61 -0
- data/lib/tapioca/generators/dsl.rb +362 -0
- data/lib/tapioca/generators/gem.rb +345 -0
- data/lib/tapioca/generators/init.rb +79 -0
- data/lib/tapioca/generators/require.rb +52 -0
- data/lib/tapioca/generators/todo.rb +76 -0
- data/lib/tapioca/generators.rb +9 -0
- 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 +2 -10
- data/lib/tapioca/loader.rb +11 -31
- data/lib/tapioca/rbi_ext/model.rb +166 -0
- data/lib/tapioca/reflection.rb +138 -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 +3 -0
- metadata +45 -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/generator.rb +0 -633
- 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
data/lib/tapioca/gemfile.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler"
|
5
|
+
require "logger"
|
6
|
+
require "yard-sorbet"
|
5
7
|
|
6
8
|
module Tapioca
|
7
9
|
class Gemfile
|
@@ -9,10 +11,7 @@ module Tapioca
|
|
9
11
|
|
10
12
|
Spec = T.type_alias do
|
11
13
|
T.any(
|
12
|
-
|
13
|
-
::Bundler::StubSpecification,
|
14
|
-
::Bundler::RemoteSpecification
|
15
|
-
),
|
14
|
+
::Bundler::StubSpecification,
|
16
15
|
::Gem::Specification
|
17
16
|
)
|
18
17
|
end
|
@@ -62,7 +61,7 @@ module Tapioca
|
|
62
61
|
[dependencies, missing_specs]
|
63
62
|
end
|
64
63
|
|
65
|
-
sig { returns([T::
|
64
|
+
sig { returns([T::Enumerable[Spec], T::Array[String]]) }
|
66
65
|
def materialize_deps
|
67
66
|
deps = definition.locked_gems.dependencies.values
|
68
67
|
missing_specs = T::Array[String].new
|
@@ -95,9 +94,7 @@ module Tapioca
|
|
95
94
|
class GemSpec
|
96
95
|
extend(T::Sig)
|
97
96
|
|
98
|
-
IGNORED_GEMS = T.let(
|
99
|
-
sorbet sorbet-static sorbet-runtime
|
100
|
-
}.freeze, T::Array[String])
|
97
|
+
IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime"].freeze, T::Array[String])
|
101
98
|
|
102
99
|
sig { returns(String) }
|
103
100
|
attr_reader :full_gem_path, :version
|
@@ -117,12 +114,13 @@ module Tapioca
|
|
117
114
|
|
118
115
|
sig { returns(T::Array[Pathname]) }
|
119
116
|
def files
|
120
|
-
|
121
|
-
|
117
|
+
spec = @spec
|
118
|
+
if default_gem? && spec.is_a?(::Gem::Specification)
|
119
|
+
spec.files.map do |file|
|
122
120
|
ruby_lib_dir.join(file)
|
123
121
|
end
|
124
122
|
else
|
125
|
-
|
123
|
+
spec.full_require_paths.flat_map do |path|
|
126
124
|
Pathname.glob((Pathname.new(path) / "**/*.rb").to_s)
|
127
125
|
end
|
128
126
|
end
|
@@ -147,6 +145,11 @@ module Tapioca
|
|
147
145
|
end
|
148
146
|
end
|
149
147
|
|
148
|
+
sig { void }
|
149
|
+
def parse_yard_docs
|
150
|
+
files.each { |path| YARD.parse(path.to_s, [], Logger::Severity::FATAL) }
|
151
|
+
end
|
152
|
+
|
150
153
|
private
|
151
154
|
|
152
155
|
sig { returns(T::Boolean) }
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# TODO: Remove me when logging logic has been abstracted.
|
5
|
+
require "thor"
|
6
|
+
|
7
|
+
module Tapioca
|
8
|
+
module Generators
|
9
|
+
class Base
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
class FileWriter < Thor
|
14
|
+
include Thor::Actions
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: Remove me when logging logic has been abstracted
|
18
|
+
include Thor::Base
|
19
|
+
|
20
|
+
abstract!
|
21
|
+
|
22
|
+
sig { params(default_command: String, file_writer: Thor::Actions).void }
|
23
|
+
def initialize(default_command:, file_writer: FileWriter.new)
|
24
|
+
@file_writer = file_writer
|
25
|
+
@default_command = default_command
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { abstract.void }
|
29
|
+
def generate; end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# TODO: Remove me when logging logic has been abstracted
|
34
|
+
sig { params(message: String, color: T.any(Symbol, T::Array[Symbol])).void }
|
35
|
+
def say_error(message = "", *color)
|
36
|
+
force_new_line = (message.to_s !~ /( |\t)\Z/)
|
37
|
+
# NOTE: This is a hack. We're no longer subclassing from Thor::Shell::Color
|
38
|
+
# so we no longer have access to the prepare_message call.
|
39
|
+
# We should update this to remove this.
|
40
|
+
buffer = shell.send(:prepare_message, *T.unsafe([message, *T.unsafe(color)]))
|
41
|
+
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
42
|
+
|
43
|
+
$stderr.print(buffer)
|
44
|
+
$stderr.flush
|
45
|
+
end
|
46
|
+
|
47
|
+
sig do
|
48
|
+
params(
|
49
|
+
path: T.any(String, Pathname),
|
50
|
+
content: String,
|
51
|
+
force: T::Boolean,
|
52
|
+
skip: T::Boolean,
|
53
|
+
verbose: T::Boolean
|
54
|
+
).void
|
55
|
+
end
|
56
|
+
def create_file(path, content, force: true, skip: false, verbose: true)
|
57
|
+
@file_writer.create_file(path, force: force, skip: skip, verbose: verbose) { content }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,362 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Generators
|
6
|
+
class Dsl < Base
|
7
|
+
sig do
|
8
|
+
params(
|
9
|
+
requested_constants: T::Array[String],
|
10
|
+
outpath: Pathname,
|
11
|
+
generators: T::Array[String],
|
12
|
+
exclude_generators: T::Array[String],
|
13
|
+
file_header: T::Boolean,
|
14
|
+
compiler_path: String,
|
15
|
+
tapioca_path: String,
|
16
|
+
default_command: String,
|
17
|
+
file_writer: Thor::Actions,
|
18
|
+
should_verify: T::Boolean,
|
19
|
+
quiet: T::Boolean,
|
20
|
+
verbose: T::Boolean
|
21
|
+
).void
|
22
|
+
end
|
23
|
+
def initialize(
|
24
|
+
requested_constants:,
|
25
|
+
outpath:,
|
26
|
+
generators:,
|
27
|
+
exclude_generators:,
|
28
|
+
file_header:,
|
29
|
+
compiler_path:,
|
30
|
+
tapioca_path:,
|
31
|
+
default_command:,
|
32
|
+
file_writer: FileWriter.new,
|
33
|
+
should_verify: false,
|
34
|
+
quiet: false,
|
35
|
+
verbose: false
|
36
|
+
)
|
37
|
+
@requested_constants = requested_constants
|
38
|
+
@outpath = outpath
|
39
|
+
@generators = generators
|
40
|
+
@exclude_generators = exclude_generators
|
41
|
+
@file_header = file_header
|
42
|
+
@compiler_path = compiler_path
|
43
|
+
@tapioca_path = tapioca_path
|
44
|
+
@should_verify = should_verify
|
45
|
+
@quiet = quiet
|
46
|
+
@verbose = verbose
|
47
|
+
|
48
|
+
super(default_command: default_command, file_writer: file_writer)
|
49
|
+
|
50
|
+
@loader = T.let(nil, T.nilable(Loader))
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { override.void }
|
54
|
+
def generate
|
55
|
+
load_application(eager_load: @requested_constants.empty?)
|
56
|
+
abort_if_pending_migrations!
|
57
|
+
load_dsl_generators
|
58
|
+
|
59
|
+
if @should_verify
|
60
|
+
say("Checking for out-of-date RBIs...")
|
61
|
+
else
|
62
|
+
say("Compiling DSL RBI files...")
|
63
|
+
end
|
64
|
+
say("")
|
65
|
+
|
66
|
+
outpath = @should_verify ? Pathname.new(Dir.mktmpdir) : @outpath
|
67
|
+
rbi_files_to_purge = existing_rbi_filenames(@requested_constants)
|
68
|
+
|
69
|
+
compiler = Compilers::DslCompiler.new(
|
70
|
+
requested_constants: constantize(@requested_constants),
|
71
|
+
requested_generators: constantize_generators(@generators),
|
72
|
+
excluded_generators: constantize_generators(@exclude_generators),
|
73
|
+
error_handler: ->(error) {
|
74
|
+
say_error(error, :bold, :red)
|
75
|
+
}
|
76
|
+
)
|
77
|
+
|
78
|
+
compiler.run do |constant, contents|
|
79
|
+
constant_name = T.must(Reflection.name_of(constant))
|
80
|
+
|
81
|
+
if @verbose && !@quiet
|
82
|
+
say_status(:processing, constant_name, :yellow)
|
83
|
+
end
|
84
|
+
|
85
|
+
filename = compile_dsl_rbi(
|
86
|
+
constant_name,
|
87
|
+
contents,
|
88
|
+
outpath: outpath,
|
89
|
+
quiet: @should_verify || @quiet && !@verbose
|
90
|
+
)
|
91
|
+
|
92
|
+
if filename
|
93
|
+
rbi_files_to_purge.delete(filename)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
say("")
|
97
|
+
|
98
|
+
if @should_verify
|
99
|
+
perform_dsl_verification(outpath)
|
100
|
+
else
|
101
|
+
purge_stale_dsl_rbi_files(rbi_files_to_purge)
|
102
|
+
|
103
|
+
say("Done", :green)
|
104
|
+
|
105
|
+
say("All operations performed in working directory.", [:green, :bold])
|
106
|
+
say("Please review changes and commit them.", [:green, :bold])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
sig { params(eager_load: T::Boolean).void }
|
113
|
+
def load_application(eager_load:)
|
114
|
+
say("Loading Rails application... ")
|
115
|
+
|
116
|
+
loader.load_rails_application(
|
117
|
+
environment_load: true,
|
118
|
+
eager_load: eager_load
|
119
|
+
)
|
120
|
+
|
121
|
+
say("Done", :green)
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { void }
|
125
|
+
def abort_if_pending_migrations!
|
126
|
+
return unless File.exist?("config/application.rb")
|
127
|
+
return unless defined?(::Rake)
|
128
|
+
|
129
|
+
Rails.application.load_tasks
|
130
|
+
if Rake::Task.task_defined?("db:abort_if_pending_migrations")
|
131
|
+
Rake::Task["db:abort_if_pending_migrations"].invoke
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
sig { void }
|
136
|
+
def load_dsl_generators
|
137
|
+
say("Loading DSL generator classes... ")
|
138
|
+
|
139
|
+
Dir.glob([
|
140
|
+
"#{@compiler_path}/*.rb",
|
141
|
+
"#{@tapioca_path}/generators/**/*.rb",
|
142
|
+
]).each do |generator|
|
143
|
+
require File.expand_path(generator)
|
144
|
+
end
|
145
|
+
|
146
|
+
say("Done", :green)
|
147
|
+
end
|
148
|
+
|
149
|
+
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
150
|
+
def existing_rbi_filenames(requested_constants, path: @outpath)
|
151
|
+
filenames = if requested_constants.empty?
|
152
|
+
Pathname.glob(path / "**/*.rbi")
|
153
|
+
else
|
154
|
+
requested_constants.map do |constant_name|
|
155
|
+
dsl_rbi_filename(constant_name)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
filenames.to_set
|
160
|
+
end
|
161
|
+
|
162
|
+
sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
|
163
|
+
def constantize(constant_names)
|
164
|
+
constant_map = constant_names.map do |name|
|
165
|
+
[name, Object.const_get(name)]
|
166
|
+
rescue NameError
|
167
|
+
[name, nil]
|
168
|
+
end.to_h
|
169
|
+
|
170
|
+
unprocessable_constants = constant_map.select { |_, v| v.nil? }
|
171
|
+
unless unprocessable_constants.empty?
|
172
|
+
unprocessable_constants.each do |name, _|
|
173
|
+
say("Error: Cannot find constant '#{name}'", :red)
|
174
|
+
remove(dsl_rbi_filename(name))
|
175
|
+
end
|
176
|
+
|
177
|
+
exit(1)
|
178
|
+
end
|
179
|
+
|
180
|
+
constant_map.values
|
181
|
+
end
|
182
|
+
|
183
|
+
sig { params(generator_names: T::Array[String]).returns(T::Array[T.class_of(Compilers::Dsl::Base)]) }
|
184
|
+
def constantize_generators(generator_names)
|
185
|
+
generator_map = generator_names.map do |name|
|
186
|
+
# Try to find built-in tapioca generator first, then globally defined generator. The
|
187
|
+
# explicit `break` ensures the class is returned, not the `potential_name`.
|
188
|
+
generator_klass = ["Tapioca::Compilers::Dsl::#{name}", name].find do |potential_name|
|
189
|
+
break Object.const_get(potential_name)
|
190
|
+
rescue NameError
|
191
|
+
# Skip if we can't find generator by the potential name
|
192
|
+
end
|
193
|
+
|
194
|
+
[name, generator_klass]
|
195
|
+
end.to_h
|
196
|
+
|
197
|
+
unprocessable_generators = generator_map.select { |_, v| v.nil? }
|
198
|
+
unless unprocessable_generators.empty?
|
199
|
+
unprocessable_generators.each do |name, _|
|
200
|
+
say("Error: Cannot find generator '#{name}'", :red)
|
201
|
+
end
|
202
|
+
|
203
|
+
exit(1)
|
204
|
+
end
|
205
|
+
|
206
|
+
generator_map.values
|
207
|
+
end
|
208
|
+
|
209
|
+
sig do
|
210
|
+
params(
|
211
|
+
constant_name: String,
|
212
|
+
rbi: RBI::File,
|
213
|
+
outpath: Pathname,
|
214
|
+
quiet: T::Boolean
|
215
|
+
).returns(T.nilable(Pathname))
|
216
|
+
end
|
217
|
+
def compile_dsl_rbi(constant_name, rbi, outpath: @outpath, quiet: false)
|
218
|
+
return if rbi.empty?
|
219
|
+
|
220
|
+
filename = outpath / rbi_filename_for(constant_name)
|
221
|
+
|
222
|
+
rbi.set_file_header(
|
223
|
+
generate_command_for(constant_name),
|
224
|
+
reason: "dynamic methods in `#{constant_name}`",
|
225
|
+
display_heading: @file_header
|
226
|
+
)
|
227
|
+
|
228
|
+
create_file(filename, rbi.transformed_string, verbose: !quiet)
|
229
|
+
|
230
|
+
filename
|
231
|
+
end
|
232
|
+
|
233
|
+
sig { params(dir: Pathname).void }
|
234
|
+
def perform_dsl_verification(dir)
|
235
|
+
diff = verify_dsl_rbi(tmp_dir: dir)
|
236
|
+
|
237
|
+
report_diff_and_exit_if_out_of_date(diff, "dsl")
|
238
|
+
ensure
|
239
|
+
FileUtils.remove_entry(dir)
|
240
|
+
end
|
241
|
+
|
242
|
+
sig { params(files: T::Set[Pathname]).void }
|
243
|
+
def purge_stale_dsl_rbi_files(files)
|
244
|
+
if files.any?
|
245
|
+
say("Removing stale RBI files...")
|
246
|
+
|
247
|
+
files.sort.each do |filename|
|
248
|
+
remove(filename)
|
249
|
+
end
|
250
|
+
say("")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
sig { params(constant_name: String).returns(Pathname) }
|
255
|
+
def dsl_rbi_filename(constant_name)
|
256
|
+
@outpath / "#{underscore(constant_name)}.rbi"
|
257
|
+
end
|
258
|
+
|
259
|
+
sig { params(filename: Pathname).void }
|
260
|
+
def remove(filename)
|
261
|
+
return unless filename.exist?
|
262
|
+
say("-- Removing: #{filename}")
|
263
|
+
filename.unlink
|
264
|
+
end
|
265
|
+
|
266
|
+
sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
|
267
|
+
def verify_dsl_rbi(tmp_dir:)
|
268
|
+
diff = {}
|
269
|
+
|
270
|
+
existing_rbis = rbi_files_in(@outpath)
|
271
|
+
new_rbis = rbi_files_in(tmp_dir)
|
272
|
+
|
273
|
+
added_files = (new_rbis - existing_rbis)
|
274
|
+
|
275
|
+
added_files.each do |file|
|
276
|
+
diff[file] = :added
|
277
|
+
end
|
278
|
+
|
279
|
+
removed_files = (existing_rbis - new_rbis)
|
280
|
+
|
281
|
+
removed_files.each do |file|
|
282
|
+
diff[file] = :removed
|
283
|
+
end
|
284
|
+
|
285
|
+
common_files = (existing_rbis & new_rbis)
|
286
|
+
|
287
|
+
changed_files = common_files.map do |filename|
|
288
|
+
filename unless FileUtils.identical?(@outpath / filename, tmp_dir / filename)
|
289
|
+
end.compact
|
290
|
+
|
291
|
+
changed_files.each do |file|
|
292
|
+
diff[file] = :changed
|
293
|
+
end
|
294
|
+
|
295
|
+
diff
|
296
|
+
end
|
297
|
+
|
298
|
+
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
|
299
|
+
def build_error_for_files(cause, files)
|
300
|
+
filenames = files.map do |file|
|
301
|
+
@outpath / file
|
302
|
+
end.join("\n - ")
|
303
|
+
|
304
|
+
" File(s) #{cause}:\n - #{filenames}"
|
305
|
+
end
|
306
|
+
|
307
|
+
sig { params(diff: T::Hash[String, Symbol], command: String).void }
|
308
|
+
def report_diff_and_exit_if_out_of_date(diff, command)
|
309
|
+
if diff.empty?
|
310
|
+
say("Nothing to do, all RBIs are up-to-date.")
|
311
|
+
else
|
312
|
+
say("RBI files are out-of-date. In your development environment, please run:", :green)
|
313
|
+
say(" `#{@default_command} #{command}`", [:green, :bold])
|
314
|
+
say("Once it is complete, be sure to commit and push any changes", :green)
|
315
|
+
|
316
|
+
say("")
|
317
|
+
|
318
|
+
say("Reason:", [:red])
|
319
|
+
diff.group_by(&:last).sort.each do |cause, diff_for_cause|
|
320
|
+
say(build_error_for_files(cause, diff_for_cause.map(&:first)))
|
321
|
+
end
|
322
|
+
|
323
|
+
exit(1)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
sig { params(path: Pathname).returns(T::Array[Pathname]) }
|
328
|
+
def rbi_files_in(path)
|
329
|
+
Pathname.glob(path / "**/*.rbi").map do |file|
|
330
|
+
file.relative_path_from(path)
|
331
|
+
end.sort
|
332
|
+
end
|
333
|
+
|
334
|
+
sig { returns(Loader) }
|
335
|
+
def loader
|
336
|
+
@loader ||= Loader.new
|
337
|
+
end
|
338
|
+
|
339
|
+
sig { params(class_name: String).returns(String) }
|
340
|
+
def underscore(class_name)
|
341
|
+
return class_name unless /[A-Z-]|::/.match?(class_name)
|
342
|
+
|
343
|
+
word = class_name.to_s.gsub("::", "/")
|
344
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
345
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
346
|
+
word.tr!("-", "_")
|
347
|
+
word.downcase!
|
348
|
+
word
|
349
|
+
end
|
350
|
+
|
351
|
+
sig { params(constant: String).returns(String) }
|
352
|
+
def rbi_filename_for(constant)
|
353
|
+
underscore(constant) + ".rbi"
|
354
|
+
end
|
355
|
+
|
356
|
+
sig { params(constant: String).returns(String) }
|
357
|
+
def generate_command_for(constant)
|
358
|
+
"#{@default_command} dsl #{constant}"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|