tapioca 0.5.1 → 0.5.5
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 +1 -1
- data/lib/tapioca/cli.rb +54 -139
- data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
- data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +41 -33
- data/lib/tapioca/compilers/dsl/active_support_concern.rb +0 -2
- data/lib/tapioca/compilers/dsl/base.rb +12 -0
- data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
- data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +4 -4
- data/lib/tapioca/compilers/dsl_compiler.rb +7 -6
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
- data/lib/tapioca/compilers/sorbet.rb +0 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +84 -153
- data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
- data/lib/tapioca/config.rb +1 -0
- data/lib/tapioca/config_builder.rb +1 -0
- data/lib/tapioca/constant_locator.rb +7 -1
- data/lib/tapioca/gemfile.rb +11 -5
- 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/internal.rb +1 -2
- data/lib/tapioca/loader.rb +2 -2
- data/lib/tapioca/rbi_ext/model.rb +44 -0
- data/lib/tapioca/reflection.rb +8 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -0
- metadata +34 -12
- data/lib/tapioca/generator.rb +0 -717
@@ -0,0 +1,345 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Generators
|
6
|
+
class Gem < Base
|
7
|
+
sig do
|
8
|
+
params(
|
9
|
+
gem_names: T::Array[String],
|
10
|
+
gem_excludes: T::Array[String],
|
11
|
+
prerequire: T.nilable(String),
|
12
|
+
postrequire: String,
|
13
|
+
typed_overrides: T::Hash[String, String],
|
14
|
+
default_command: String,
|
15
|
+
outpath: Pathname,
|
16
|
+
file_header: T::Boolean,
|
17
|
+
doc: T::Boolean,
|
18
|
+
file_writer: Thor::Actions
|
19
|
+
).void
|
20
|
+
end
|
21
|
+
def initialize(
|
22
|
+
gem_names:,
|
23
|
+
gem_excludes:,
|
24
|
+
prerequire:,
|
25
|
+
postrequire:,
|
26
|
+
typed_overrides:,
|
27
|
+
default_command:,
|
28
|
+
outpath:,
|
29
|
+
file_header:,
|
30
|
+
doc:,
|
31
|
+
file_writer: FileWriter.new
|
32
|
+
)
|
33
|
+
@gem_names = gem_names
|
34
|
+
@gem_excludes = gem_excludes
|
35
|
+
@prerequire = prerequire
|
36
|
+
@postrequire = postrequire
|
37
|
+
@typed_overrides = typed_overrides
|
38
|
+
@outpath = outpath
|
39
|
+
@file_header = file_header
|
40
|
+
|
41
|
+
super(default_command: default_command, file_writer: file_writer)
|
42
|
+
|
43
|
+
@loader = T.let(nil, T.nilable(Loader))
|
44
|
+
@bundle = T.let(nil, T.nilable(Gemfile))
|
45
|
+
@existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
46
|
+
@expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
47
|
+
@doc = T.let(doc, T::Boolean)
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { override.void }
|
51
|
+
def generate
|
52
|
+
require_gem_file
|
53
|
+
|
54
|
+
gems_to_generate(@gem_names)
|
55
|
+
.reject { |gem| @gem_excludes.include?(gem.name) }
|
56
|
+
.each do |gem|
|
57
|
+
say("Processing '#{gem.name}' gem:", :green)
|
58
|
+
shell.indent do
|
59
|
+
compile_gem_rbi(gem)
|
60
|
+
puts
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
say("All operations performed in working directory.", [:green, :bold])
|
65
|
+
say("Please review changes and commit them.", [:green, :bold])
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(should_verify: T::Boolean).void }
|
69
|
+
def sync(should_verify: false)
|
70
|
+
if should_verify
|
71
|
+
say("Checking for out-of-date RBIs...")
|
72
|
+
say("")
|
73
|
+
perform_sync_verification
|
74
|
+
return
|
75
|
+
end
|
76
|
+
|
77
|
+
anything_done = [
|
78
|
+
perform_removals,
|
79
|
+
perform_additions,
|
80
|
+
].any?
|
81
|
+
|
82
|
+
if anything_done
|
83
|
+
say("All operations performed in working directory.", [:green, :bold])
|
84
|
+
say("Please review changes and commit them.", [:green, :bold])
|
85
|
+
else
|
86
|
+
say("No operations performed, all RBIs are up-to-date.", [:green, :bold])
|
87
|
+
end
|
88
|
+
|
89
|
+
puts
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
sig { returns(Loader) }
|
95
|
+
def loader
|
96
|
+
@loader ||= Loader.new
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { returns(Gemfile) }
|
100
|
+
def bundle
|
101
|
+
@bundle ||= Gemfile.new
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { void }
|
105
|
+
def require_gem_file
|
106
|
+
say("Requiring all gems to prepare for compiling... ")
|
107
|
+
begin
|
108
|
+
loader.load_bundle(bundle, @prerequire, @postrequire)
|
109
|
+
rescue LoadError => e
|
110
|
+
explain_failed_require(@postrequire, e)
|
111
|
+
exit(1)
|
112
|
+
end
|
113
|
+
say(" Done", :green)
|
114
|
+
unless bundle.missing_specs.empty?
|
115
|
+
say(" completed with missing specs: ")
|
116
|
+
say(bundle.missing_specs.join(", "), :yellow)
|
117
|
+
end
|
118
|
+
puts
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { params(gem_names: T::Array[String]).returns(T::Array[Gemfile::GemSpec]) }
|
122
|
+
def gems_to_generate(gem_names)
|
123
|
+
return bundle.dependencies if gem_names.empty?
|
124
|
+
|
125
|
+
gem_names.map do |gem_name|
|
126
|
+
gem = bundle.gem(gem_name)
|
127
|
+
if gem.nil?
|
128
|
+
say("Error: Cannot find gem '#{gem_name}'", :red)
|
129
|
+
exit(1)
|
130
|
+
end
|
131
|
+
gem
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
sig { params(gem: Gemfile::GemSpec).void }
|
136
|
+
def compile_gem_rbi(gem)
|
137
|
+
gem_name = set_color(gem.name, :yellow, :bold)
|
138
|
+
say("Compiling #{gem_name}, this may take a few seconds... ")
|
139
|
+
|
140
|
+
rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
|
141
|
+
rbi.set_file_header(
|
142
|
+
"#{@default_command} gem #{gem.name}",
|
143
|
+
reason: "types exported from the `#{gem.name}` gem",
|
144
|
+
display_heading: @file_header
|
145
|
+
)
|
146
|
+
|
147
|
+
Compilers::SymbolTableCompiler.new.compile(gem, rbi, 0, @doc)
|
148
|
+
|
149
|
+
if rbi.empty?
|
150
|
+
rbi.set_empty_body_content
|
151
|
+
say("Done (empty output)", :yellow)
|
152
|
+
else
|
153
|
+
say("Done", :green)
|
154
|
+
end
|
155
|
+
|
156
|
+
create_file(@outpath / gem.rbi_file_name, rbi.transformed_string)
|
157
|
+
|
158
|
+
T.unsafe(Pathname).glob((@outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
159
|
+
remove(file) unless file.basename.to_s == gem.rbi_file_name
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
sig { void }
|
164
|
+
def perform_sync_verification
|
165
|
+
diff = {}
|
166
|
+
|
167
|
+
removed_rbis.each do |gem_name|
|
168
|
+
filename = existing_rbi(gem_name)
|
169
|
+
diff[filename] = :removed
|
170
|
+
end
|
171
|
+
|
172
|
+
added_rbis.each do |gem_name|
|
173
|
+
filename = expected_rbi(gem_name)
|
174
|
+
diff[filename] = gem_rbi_exists?(gem_name) ? :changed : :added
|
175
|
+
end
|
176
|
+
|
177
|
+
report_diff_and_exit_if_out_of_date(diff, "gem")
|
178
|
+
end
|
179
|
+
|
180
|
+
sig { void }
|
181
|
+
def perform_removals
|
182
|
+
say("Removing RBI files of gems that have been removed:", [:blue, :bold])
|
183
|
+
puts
|
184
|
+
|
185
|
+
anything_done = T.let(false, T::Boolean)
|
186
|
+
|
187
|
+
gems = removed_rbis
|
188
|
+
|
189
|
+
shell.indent do
|
190
|
+
if gems.empty?
|
191
|
+
say("Nothing to do.")
|
192
|
+
else
|
193
|
+
gems.each do |removed|
|
194
|
+
filename = existing_rbi(removed)
|
195
|
+
remove(filename)
|
196
|
+
end
|
197
|
+
|
198
|
+
anything_done = true
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
puts
|
203
|
+
|
204
|
+
anything_done
|
205
|
+
end
|
206
|
+
|
207
|
+
sig { void }
|
208
|
+
def perform_additions
|
209
|
+
say("Generating RBI files of gems that are added or updated:", [:blue, :bold])
|
210
|
+
puts
|
211
|
+
|
212
|
+
anything_done = T.let(false, T::Boolean)
|
213
|
+
|
214
|
+
gems = added_rbis
|
215
|
+
|
216
|
+
shell.indent do
|
217
|
+
if gems.empty?
|
218
|
+
say("Nothing to do.")
|
219
|
+
else
|
220
|
+
require_gem_file
|
221
|
+
|
222
|
+
gems.each do |gem_name|
|
223
|
+
filename = expected_rbi(gem_name)
|
224
|
+
|
225
|
+
if gem_rbi_exists?(gem_name)
|
226
|
+
old_filename = existing_rbi(gem_name)
|
227
|
+
move(old_filename, filename) unless old_filename == filename
|
228
|
+
end
|
229
|
+
|
230
|
+
gem = T.must(bundle.gem(gem_name))
|
231
|
+
compile_gem_rbi(gem)
|
232
|
+
puts
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
anything_done = true
|
237
|
+
end
|
238
|
+
|
239
|
+
puts
|
240
|
+
|
241
|
+
anything_done
|
242
|
+
end
|
243
|
+
|
244
|
+
sig { params(file: String, error: LoadError).void }
|
245
|
+
def explain_failed_require(file, error)
|
246
|
+
say_error("\n\nLoadError: #{error}", :bold, :red)
|
247
|
+
say_error("\nTapioca could not load all the gems required by your application.", :yellow)
|
248
|
+
say_error("If you populated ", :yellow)
|
249
|
+
say_error("#{file} ", :bold, :blue)
|
250
|
+
say_error("with ", :yellow)
|
251
|
+
say_error("`#{@default_command} require`", :bold, :blue)
|
252
|
+
say_error("you should probably review it and remove the faulty line.", :yellow)
|
253
|
+
end
|
254
|
+
|
255
|
+
sig { params(filename: Pathname).void }
|
256
|
+
def remove(filename)
|
257
|
+
return unless filename.exist?
|
258
|
+
say("-- Removing: #{filename}")
|
259
|
+
filename.unlink
|
260
|
+
end
|
261
|
+
|
262
|
+
sig { returns(T::Array[String]) }
|
263
|
+
def removed_rbis
|
264
|
+
(existing_rbis.keys - expected_rbis.keys).sort
|
265
|
+
end
|
266
|
+
|
267
|
+
sig { params(gem_name: String).returns(Pathname) }
|
268
|
+
def existing_rbi(gem_name)
|
269
|
+
gem_rbi_filename(gem_name, T.must(existing_rbis[gem_name]))
|
270
|
+
end
|
271
|
+
|
272
|
+
sig { returns(T::Array[String]) }
|
273
|
+
def added_rbis
|
274
|
+
expected_rbis.select do |name, value|
|
275
|
+
existing_rbis[name] != value
|
276
|
+
end.keys.sort
|
277
|
+
end
|
278
|
+
|
279
|
+
sig { params(gem_name: String).returns(Pathname) }
|
280
|
+
def expected_rbi(gem_name)
|
281
|
+
gem_rbi_filename(gem_name, T.must(expected_rbis[gem_name]))
|
282
|
+
end
|
283
|
+
|
284
|
+
sig { params(gem_name: String).returns(T::Boolean) }
|
285
|
+
def gem_rbi_exists?(gem_name)
|
286
|
+
existing_rbis.key?(gem_name)
|
287
|
+
end
|
288
|
+
|
289
|
+
sig { params(diff: T::Hash[String, Symbol], command: String).void }
|
290
|
+
def report_diff_and_exit_if_out_of_date(diff, command)
|
291
|
+
if diff.empty?
|
292
|
+
say("Nothing to do, all RBIs are up-to-date.")
|
293
|
+
else
|
294
|
+
say("RBI files are out-of-date. In your development environment, please run:", :green)
|
295
|
+
say(" `#{Config::DEFAULT_COMMAND} #{command}`", [:green, :bold])
|
296
|
+
say("Once it is complete, be sure to commit and push any changes", :green)
|
297
|
+
|
298
|
+
say("")
|
299
|
+
|
300
|
+
say("Reason:", [:red])
|
301
|
+
diff.group_by(&:last).sort.each do |cause, diff_for_cause|
|
302
|
+
say(build_error_for_files(cause, diff_for_cause.map(&:first)))
|
303
|
+
end
|
304
|
+
|
305
|
+
exit(1)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
sig { params(old_filename: Pathname, new_filename: Pathname).void }
|
310
|
+
def move(old_filename, new_filename)
|
311
|
+
say("-> Moving: #{old_filename} to #{new_filename}")
|
312
|
+
old_filename.rename(new_filename.to_s)
|
313
|
+
end
|
314
|
+
|
315
|
+
sig { returns(T::Hash[String, String]) }
|
316
|
+
def existing_rbis
|
317
|
+
@existing_rbis ||= Pathname.glob((@outpath / "*@*.rbi").to_s)
|
318
|
+
.map { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
|
319
|
+
.to_h
|
320
|
+
end
|
321
|
+
|
322
|
+
sig { returns(T::Hash[String, String]) }
|
323
|
+
def expected_rbis
|
324
|
+
@expected_rbis ||= bundle.dependencies
|
325
|
+
.reject { |gem| @gem_excludes.include?(gem.name) }
|
326
|
+
.map { |gem| [gem.name, gem.version.to_s] }
|
327
|
+
.to_h
|
328
|
+
end
|
329
|
+
|
330
|
+
sig { params(gem_name: String, version: String).returns(Pathname) }
|
331
|
+
def gem_rbi_filename(gem_name, version)
|
332
|
+
@outpath / "#{gem_name}@#{version}.rbi"
|
333
|
+
end
|
334
|
+
|
335
|
+
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
|
336
|
+
def build_error_for_files(cause, files)
|
337
|
+
filenames = files.map do |file|
|
338
|
+
@outpath / file
|
339
|
+
end.join("\n - ")
|
340
|
+
|
341
|
+
" File(s) #{cause}:\n - #{filenames}"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Generators
|
6
|
+
class Init < Base
|
7
|
+
sig do
|
8
|
+
params(
|
9
|
+
sorbet_config: String,
|
10
|
+
default_postrequire: String,
|
11
|
+
default_command: String,
|
12
|
+
file_writer: Thor::Actions
|
13
|
+
).void
|
14
|
+
end
|
15
|
+
def initialize(sorbet_config:, default_postrequire:, default_command:, file_writer: FileWriter.new)
|
16
|
+
@sorbet_config = sorbet_config
|
17
|
+
@default_postrequire = default_postrequire
|
18
|
+
|
19
|
+
super(default_command: default_command, file_writer: file_writer)
|
20
|
+
|
21
|
+
@installer = T.let(nil, T.nilable(Bundler::Installer))
|
22
|
+
@spec = T.let(nil, T.nilable(Bundler::StubSpecification))
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { override.void }
|
26
|
+
def generate
|
27
|
+
create_config
|
28
|
+
create_post_require
|
29
|
+
if File.exist?(@default_command)
|
30
|
+
generate_binstub!
|
31
|
+
else
|
32
|
+
generate_binstub
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
sig { void }
|
39
|
+
def create_config
|
40
|
+
create_file(@sorbet_config, <<~CONTENT, skip: true, force: false)
|
41
|
+
--dir
|
42
|
+
.
|
43
|
+
CONTENT
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { void }
|
47
|
+
def create_post_require
|
48
|
+
create_file(@default_postrequire, <<~CONTENT, skip: true, force: false)
|
49
|
+
# typed: true
|
50
|
+
# frozen_string_literal: true
|
51
|
+
|
52
|
+
# Add your extra requires here (`#{@default_command} require` can be used to boostrap this list)
|
53
|
+
CONTENT
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { void }
|
57
|
+
def generate_binstub!
|
58
|
+
installer.generate_bundler_executable_stubs(spec, { force: true })
|
59
|
+
say_status(:force, @default_command, :yellow)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { void }
|
63
|
+
def generate_binstub
|
64
|
+
installer.generate_bundler_executable_stubs(spec)
|
65
|
+
say_status(:create, @default_command, :green)
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { returns(Bundler::Installer) }
|
69
|
+
def installer
|
70
|
+
@installer ||= Bundler::Installer.new(Bundler.root, Bundler.definition)
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { returns(Bundler::StubSpecification) }
|
74
|
+
def spec
|
75
|
+
@spec ||= Bundler.definition.specs.find { |s| s.name == "tapioca" }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Generators
|
6
|
+
class Require < Base
|
7
|
+
sig do
|
8
|
+
params(
|
9
|
+
requires_path: String,
|
10
|
+
sorbet_config_path: String,
|
11
|
+
default_command: String,
|
12
|
+
file_writer: Thor::Actions
|
13
|
+
).void
|
14
|
+
end
|
15
|
+
def initialize(requires_path:, sorbet_config_path:, default_command:, file_writer: FileWriter.new)
|
16
|
+
@requires_path = requires_path
|
17
|
+
@sorbet_config_path = sorbet_config_path
|
18
|
+
|
19
|
+
super(default_command: default_command, file_writer: file_writer)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.void }
|
23
|
+
def generate
|
24
|
+
compiler = Compilers::RequiresCompiler.new(@sorbet_config_path)
|
25
|
+
name = set_color(@requires_path, :yellow, :bold)
|
26
|
+
say("Compiling #{name}, this may take a few seconds... ")
|
27
|
+
|
28
|
+
rb_string = compiler.compile
|
29
|
+
if rb_string.empty?
|
30
|
+
say("Nothing to do", :green)
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
# Clean all existing requires before regenerating the list so we update
|
35
|
+
# it with the new one found in the client code and remove the old ones.
|
36
|
+
File.delete(@requires_path) if File.exist?(@requires_path)
|
37
|
+
|
38
|
+
content = +"# typed: true\n"
|
39
|
+
content << "# frozen_string_literal: true\n\n"
|
40
|
+
content << rb_string
|
41
|
+
|
42
|
+
create_file(@requires_path, content, verbose: false)
|
43
|
+
|
44
|
+
say("Done", :green)
|
45
|
+
|
46
|
+
say("All requires from this application have been written to #{name}.", [:green, :bold])
|
47
|
+
cmd = set_color("#{@default_command} gem", :yellow, :bold)
|
48
|
+
say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Generators
|
6
|
+
class Todo < Base
|
7
|
+
sig do
|
8
|
+
params(
|
9
|
+
todos_path: String,
|
10
|
+
file_header: T::Boolean,
|
11
|
+
default_command: String,
|
12
|
+
file_writer: Thor::Actions
|
13
|
+
).void
|
14
|
+
end
|
15
|
+
def initialize(todos_path:, file_header:, default_command:, file_writer: FileWriter.new)
|
16
|
+
@todos_path = todos_path
|
17
|
+
@file_header = file_header
|
18
|
+
|
19
|
+
super(default_command: default_command, file_writer: file_writer)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.void }
|
23
|
+
def generate
|
24
|
+
compiler = Compilers::TodosCompiler.new
|
25
|
+
say("Finding all unresolved constants, this may take a few seconds... ")
|
26
|
+
|
27
|
+
# Clean all existing unresolved constants before regenerating the list
|
28
|
+
# so Sorbet won't grab them as already resolved.
|
29
|
+
File.delete(@todos_path) if File.exist?(@todos_path)
|
30
|
+
|
31
|
+
rbi_string = compiler.compile
|
32
|
+
if rbi_string.empty?
|
33
|
+
say("Nothing to do", :green)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
content = String.new
|
38
|
+
content << rbi_header(
|
39
|
+
"#{@default_command} todo",
|
40
|
+
reason: "unresolved constants",
|
41
|
+
strictness: "false"
|
42
|
+
)
|
43
|
+
content << rbi_string
|
44
|
+
content << "\n"
|
45
|
+
|
46
|
+
say("Done", :green)
|
47
|
+
create_file(@todos_path, content, verbose: false)
|
48
|
+
|
49
|
+
name = set_color(@todos_path, :yellow, :bold)
|
50
|
+
say("\nAll unresolved constants have been written to #{name}.", [:green, :bold])
|
51
|
+
say("Please review changes and commit them.", [:green, :bold])
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(command: String, reason: T.nilable(String), strictness: T.nilable(String)).returns(String) }
|
55
|
+
def rbi_header(command, reason: nil, strictness: nil)
|
56
|
+
statement = <<~HEAD
|
57
|
+
# DO NOT EDIT MANUALLY
|
58
|
+
# This is an autogenerated file for #{reason}.
|
59
|
+
# Please instead update this file by running `#{command}`.
|
60
|
+
HEAD
|
61
|
+
|
62
|
+
sigil = <<~SIGIL if strictness
|
63
|
+
# typed: #{strictness}
|
64
|
+
SIGIL
|
65
|
+
|
66
|
+
if @file_header
|
67
|
+
[statement, sigil].compact.join("\n").strip.concat("\n\n")
|
68
|
+
elsif sigil
|
69
|
+
sigil.strip.concat("\n\n")
|
70
|
+
else
|
71
|
+
""
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "generators/base"
|
5
|
+
require_relative "generators/dsl"
|
6
|
+
require_relative "generators/init"
|
7
|
+
require_relative "generators/gem"
|
8
|
+
require_relative "generators/require"
|
9
|
+
require_relative "generators/todo"
|
data/lib/tapioca/internal.rb
CHANGED
@@ -3,13 +3,12 @@
|
|
3
3
|
|
4
4
|
require "tapioca"
|
5
5
|
require "tapioca/loader"
|
6
|
-
require "tapioca/constant_locator"
|
7
6
|
require "tapioca/sorbet_ext/generic_name_patch"
|
8
7
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
9
8
|
require "tapioca/generic_type_registry"
|
10
9
|
require "tapioca/config"
|
11
10
|
require "tapioca/config_builder"
|
12
|
-
require "tapioca/
|
11
|
+
require "tapioca/generators"
|
13
12
|
require "tapioca/cli"
|
14
13
|
require "tapioca/gemfile"
|
15
14
|
require "tapioca/compilers/sorbet"
|
data/lib/tapioca/loader.rb
CHANGED
@@ -9,10 +9,10 @@ module Tapioca
|
|
9
9
|
def load_bundle(gemfile, initialize_file, require_file)
|
10
10
|
require_helper(initialize_file)
|
11
11
|
|
12
|
-
gemfile.require_bundle
|
13
|
-
|
14
12
|
load_rails_application
|
15
13
|
|
14
|
+
gemfile.require_bundle
|
15
|
+
|
16
16
|
require_helper(require_file)
|
17
17
|
|
18
18
|
load_rails_engines
|
@@ -4,6 +4,50 @@
|
|
4
4
|
require "rbi"
|
5
5
|
|
6
6
|
module RBI
|
7
|
+
class File
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(String) }
|
11
|
+
def transformed_string
|
12
|
+
transform_rbi!
|
13
|
+
string
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { void }
|
17
|
+
def transform_rbi!
|
18
|
+
root.nest_singleton_methods!
|
19
|
+
root.nest_non_public_methods!
|
20
|
+
root.group_nodes!
|
21
|
+
root.sort_nodes!
|
22
|
+
end
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(
|
26
|
+
command: String,
|
27
|
+
reason: T.nilable(String),
|
28
|
+
display_heading: T::Boolean
|
29
|
+
).void
|
30
|
+
end
|
31
|
+
def set_file_header(command, reason: nil, display_heading: true)
|
32
|
+
return unless display_heading
|
33
|
+
comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
|
34
|
+
comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
|
35
|
+
comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { void }
|
39
|
+
def set_empty_body_content
|
40
|
+
comments << RBI::BlankLine.new unless comments.empty?
|
41
|
+
comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
|
42
|
+
comments << RBI::Comment.new("see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires")
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(T::Boolean) }
|
46
|
+
def empty?
|
47
|
+
root.empty?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
7
51
|
class Tree
|
8
52
|
extend T::Sig
|
9
53
|
|
data/lib/tapioca/reflection.rb
CHANGED
@@ -17,6 +17,7 @@ module Tapioca
|
|
17
17
|
PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
|
18
18
|
PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
|
19
19
|
PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
|
20
|
+
METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
|
20
21
|
|
21
22
|
sig { params(object: BasicObject).returns(Class).checked(:never) }
|
22
23
|
def class_of(object)
|
@@ -30,7 +31,8 @@ module Tapioca
|
|
30
31
|
|
31
32
|
sig { params(constant: Module).returns(T.nilable(String)) }
|
32
33
|
def name_of(constant)
|
33
|
-
NAME_METHOD.bind(constant).call
|
34
|
+
name = NAME_METHOD.bind(constant).call
|
35
|
+
name&.start_with?("#<") ? nil : name
|
34
36
|
end
|
35
37
|
|
36
38
|
sig { params(constant: Module).returns(Class) }
|
@@ -106,6 +108,11 @@ module Tapioca
|
|
106
108
|
type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
|
107
109
|
end
|
108
110
|
|
111
|
+
sig { params(constant: Module, method: Symbol).returns(Method) }
|
112
|
+
def method_of(constant, method)
|
113
|
+
METHOD_METHOD.bind(constant).call(method)
|
114
|
+
end
|
115
|
+
|
109
116
|
# Returns an array with all classes that are < than the supplied class.
|
110
117
|
#
|
111
118
|
# class C; end
|
data/lib/tapioca/version.rb
CHANGED
data/lib/tapioca.rb
CHANGED
@@ -18,6 +18,8 @@ module Tapioca
|
|
18
18
|
end
|
19
19
|
|
20
20
|
require "tapioca/reflection"
|
21
|
+
require "tapioca/constant_locator"
|
21
22
|
require "tapioca/compilers/dsl/base"
|
23
|
+
require "tapioca/compilers/dynamic_mixin_compiler"
|
22
24
|
require "tapioca/helpers/active_record_column_type_helper"
|
23
25
|
require "tapioca/version"
|