tapioca 0.5.3 → 0.6.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/README.md +114 -23
- data/lib/tapioca/cli.rb +188 -65
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +94 -8
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +5 -4
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +703 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +39 -33
- data/lib/tapioca/compilers/dsl/base.rb +26 -42
- data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
- data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
- data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
- data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +34 -6
- data/lib/tapioca/compilers/sorbet.rb +2 -0
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +51 -48
- data/lib/tapioca/executor.rb +79 -0
- data/lib/tapioca/gemfile.rb +28 -4
- data/lib/tapioca/generators/base.rb +11 -18
- data/lib/tapioca/generators/dsl.rb +33 -38
- data/lib/tapioca/generators/gem.rb +64 -34
- data/lib/tapioca/generators/init.rb +41 -16
- data/lib/tapioca/generators/todo.rb +6 -6
- data/lib/tapioca/helpers/cli_helper.rb +26 -0
- data/lib/tapioca/helpers/config_helper.rb +84 -0
- data/lib/tapioca/helpers/test/content.rb +51 -0
- data/lib/tapioca/helpers/test/isolation.rb +125 -0
- data/lib/tapioca/helpers/test/template.rb +34 -0
- data/lib/tapioca/internal.rb +3 -2
- data/lib/tapioca/rbi_ext/model.rb +13 -10
- data/lib/tapioca/reflection.rb +13 -0
- data/lib/tapioca/trackers/autoload.rb +70 -0
- data/lib/tapioca/trackers/constant_definition.rb +42 -0
- data/lib/tapioca/trackers/mixin.rb +78 -0
- data/lib/tapioca/trackers.rb +14 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +28 -2
- metadata +37 -13
- data/lib/tapioca/config.rb +0 -45
- data/lib/tapioca/config_builder.rb +0 -73
- data/lib/tapioca/constant_locator.rb +0 -34
@@ -7,7 +7,7 @@ module Tapioca
|
|
7
7
|
sig do
|
8
8
|
params(
|
9
9
|
gem_names: T::Array[String],
|
10
|
-
|
10
|
+
exclude: T::Array[String],
|
11
11
|
prerequire: T.nilable(String),
|
12
12
|
postrequire: String,
|
13
13
|
typed_overrides: T::Hash[String, String],
|
@@ -15,12 +15,14 @@ module Tapioca
|
|
15
15
|
outpath: Pathname,
|
16
16
|
file_header: T::Boolean,
|
17
17
|
doc: T::Boolean,
|
18
|
-
|
18
|
+
include_exported_rbis: T::Boolean,
|
19
|
+
file_writer: Thor::Actions,
|
20
|
+
number_of_workers: T.nilable(Integer)
|
19
21
|
).void
|
20
22
|
end
|
21
23
|
def initialize(
|
22
24
|
gem_names:,
|
23
|
-
|
25
|
+
exclude:,
|
24
26
|
prerequire:,
|
25
27
|
postrequire:,
|
26
28
|
typed_overrides:,
|
@@ -28,15 +30,18 @@ module Tapioca
|
|
28
30
|
outpath:,
|
29
31
|
file_header:,
|
30
32
|
doc:,
|
31
|
-
|
33
|
+
include_exported_rbis:,
|
34
|
+
file_writer: FileWriter.new,
|
35
|
+
number_of_workers: nil
|
32
36
|
)
|
33
37
|
@gem_names = gem_names
|
34
|
-
@
|
38
|
+
@exclude = exclude
|
35
39
|
@prerequire = prerequire
|
36
40
|
@postrequire = postrequire
|
37
41
|
@typed_overrides = typed_overrides
|
38
42
|
@outpath = outpath
|
39
43
|
@file_header = file_header
|
44
|
+
@number_of_workers = number_of_workers
|
40
45
|
|
41
46
|
super(default_command: default_command, file_writer: file_writer)
|
42
47
|
|
@@ -45,24 +50,32 @@ module Tapioca
|
|
45
50
|
@existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
46
51
|
@expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
|
47
52
|
@doc = T.let(doc, T::Boolean)
|
53
|
+
@include_exported_rbis = include_exported_rbis
|
48
54
|
end
|
49
55
|
|
50
56
|
sig { override.void }
|
51
57
|
def generate
|
52
58
|
require_gem_file
|
53
59
|
|
54
|
-
gems_to_generate(@gem_names)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
gem_queue = gems_to_generate(@gem_names).reject { |gem| @exclude.include?(gem.name) }
|
61
|
+
anything_done = [
|
62
|
+
perform_removals,
|
63
|
+
gem_queue.any?,
|
64
|
+
].any?
|
65
|
+
|
66
|
+
Executor.new(gem_queue, number_of_workers: @number_of_workers).run_in_parallel do |gem|
|
67
|
+
shell.indent do
|
68
|
+
compile_gem_rbi(gem)
|
69
|
+
puts
|
62
70
|
end
|
71
|
+
end
|
63
72
|
|
64
|
-
|
65
|
-
|
73
|
+
if anything_done
|
74
|
+
say("All operations performed in working directory.", [:green, :bold])
|
75
|
+
say("Please review changes and commit them.", [:green, :bold])
|
76
|
+
else
|
77
|
+
say("No operations performed, all RBIs are up-to-date.", [:green, :bold])
|
78
|
+
end
|
66
79
|
end
|
67
80
|
|
68
81
|
sig { params(should_verify: T::Boolean).void }
|
@@ -110,6 +123,9 @@ module Tapioca
|
|
110
123
|
explain_failed_require(@postrequire, e)
|
111
124
|
exit(1)
|
112
125
|
end
|
126
|
+
|
127
|
+
Tapioca::Trackers::Autoload.eager_load_all!
|
128
|
+
|
113
129
|
say(" Done", :green)
|
114
130
|
unless bundle.missing_specs.empty?
|
115
131
|
say(" completed with missing specs: ")
|
@@ -135,7 +151,6 @@ module Tapioca
|
|
135
151
|
sig { params(gem: Gemfile::GemSpec).void }
|
136
152
|
def compile_gem_rbi(gem)
|
137
153
|
gem_name = set_color(gem.name, :yellow, :bold)
|
138
|
-
say("Compiling #{gem_name}, this may take a few seconds... ")
|
139
154
|
|
140
155
|
rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
|
141
156
|
rbi.set_file_header(
|
@@ -146,17 +161,19 @@ module Tapioca
|
|
146
161
|
|
147
162
|
Compilers::SymbolTableCompiler.new.compile(gem, rbi, 0, @doc)
|
148
163
|
|
164
|
+
merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
|
165
|
+
|
149
166
|
if rbi.empty?
|
150
167
|
rbi.set_empty_body_content
|
151
|
-
say("
|
168
|
+
say("Compiled #{gem_name} (empty output)", :yellow)
|
152
169
|
else
|
153
|
-
say("
|
170
|
+
say("Compiled #{gem_name}", :green)
|
154
171
|
end
|
155
172
|
|
156
173
|
create_file(@outpath / gem.rbi_file_name, rbi.transformed_string)
|
157
174
|
|
158
175
|
T.unsafe(Pathname).glob((@outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
159
|
-
|
176
|
+
remove_file(file) unless file.basename.to_s == gem.rbi_file_name
|
160
177
|
end
|
161
178
|
end
|
162
179
|
|
@@ -192,7 +209,7 @@ module Tapioca
|
|
192
209
|
else
|
193
210
|
gems.each do |removed|
|
194
211
|
filename = existing_rbi(removed)
|
195
|
-
|
212
|
+
remove_file(filename)
|
196
213
|
end
|
197
214
|
|
198
215
|
anything_done = true
|
@@ -219,7 +236,7 @@ module Tapioca
|
|
219
236
|
else
|
220
237
|
require_gem_file
|
221
238
|
|
222
|
-
gems.
|
239
|
+
Executor.new(gems, number_of_workers: @number_of_workers).run_in_parallel do |gem_name|
|
223
240
|
filename = expected_rbi(gem_name)
|
224
241
|
|
225
242
|
if gem_rbi_exists?(gem_name)
|
@@ -252,13 +269,6 @@ module Tapioca
|
|
252
269
|
say_error("you should probably review it and remove the faulty line.", :yellow)
|
253
270
|
end
|
254
271
|
|
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
272
|
sig { returns(T::Array[String]) }
|
263
273
|
def removed_rbis
|
264
274
|
(existing_rbis.keys - expected_rbis.keys).sort
|
@@ -292,7 +302,7 @@ module Tapioca
|
|
292
302
|
say("Nothing to do, all RBIs are up-to-date.")
|
293
303
|
else
|
294
304
|
say("RBI files are out-of-date. In your development environment, please run:", :green)
|
295
|
-
say(" `#{
|
305
|
+
say(" `#{@default_command} #{command}`", [:green, :bold])
|
296
306
|
say("Once it is complete, be sure to commit and push any changes", :green)
|
297
307
|
|
298
308
|
say("")
|
@@ -322,7 +332,7 @@ module Tapioca
|
|
322
332
|
sig { returns(T::Hash[String, String]) }
|
323
333
|
def expected_rbis
|
324
334
|
@expected_rbis ||= bundle.dependencies
|
325
|
-
.reject { |gem| @
|
335
|
+
.reject { |gem| @exclude.include?(gem.name) }
|
326
336
|
.map { |gem| [gem.name, gem.version.to_s] }
|
327
337
|
.to_h
|
328
338
|
end
|
@@ -334,11 +344,31 @@ module Tapioca
|
|
334
344
|
|
335
345
|
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
|
336
346
|
def build_error_for_files(cause, files)
|
337
|
-
|
338
|
-
|
339
|
-
|
347
|
+
" File(s) #{cause}:\n - #{files.join("\n - ")}"
|
348
|
+
end
|
349
|
+
|
350
|
+
sig { params(gem: Gemfile::GemSpec, file: RBI::File).void }
|
351
|
+
def merge_with_exported_rbi(gem, file)
|
352
|
+
return file unless gem.export_rbi_files?
|
353
|
+
tree = gem.exported_rbi_tree
|
354
|
+
|
355
|
+
unless tree.conflicts.empty?
|
356
|
+
say_error("\n\n RBIs exported by `#{gem.name}` contain conflicts and can't be used:", :yellow)
|
357
|
+
|
358
|
+
tree.conflicts.each do |conflict|
|
359
|
+
say_error("\n #{conflict}", :yellow)
|
360
|
+
say_error(" Found at:", :yellow)
|
361
|
+
say_error(" #{conflict.left.loc}", :yellow)
|
362
|
+
say_error(" #{conflict.right.loc}", :yellow)
|
363
|
+
end
|
364
|
+
|
365
|
+
return file
|
366
|
+
end
|
340
367
|
|
341
|
-
|
368
|
+
file.root = RBI::Rewriters::Merge.merge_trees(file.root, tree, keep: RBI::Rewriters::Merge::Keep::LEFT)
|
369
|
+
rescue RBI::ParseError => e
|
370
|
+
say_error("\n\n RBIs exported by `#{gem.name}` contain errors and can't be used:", :yellow)
|
371
|
+
say_error("Cause: #{e.message} (#{e.location})")
|
342
372
|
end
|
343
373
|
end
|
344
374
|
end
|
@@ -7,13 +7,21 @@ module Tapioca
|
|
7
7
|
sig do
|
8
8
|
params(
|
9
9
|
sorbet_config: String,
|
10
|
+
tapioca_config: String,
|
10
11
|
default_postrequire: String,
|
11
12
|
default_command: String,
|
12
13
|
file_writer: Thor::Actions
|
13
14
|
).void
|
14
15
|
end
|
15
|
-
def initialize(
|
16
|
+
def initialize(
|
17
|
+
sorbet_config:,
|
18
|
+
tapioca_config:,
|
19
|
+
default_postrequire:,
|
20
|
+
default_command:,
|
21
|
+
file_writer: FileWriter.new
|
22
|
+
)
|
16
23
|
@sorbet_config = sorbet_config
|
24
|
+
@tapioca_config = tapioca_config
|
17
25
|
@default_postrequire = default_postrequire
|
18
26
|
|
19
27
|
super(default_command: default_command, file_writer: file_writer)
|
@@ -24,25 +32,41 @@ module Tapioca
|
|
24
32
|
|
25
33
|
sig { override.void }
|
26
34
|
def generate
|
27
|
-
|
35
|
+
create_sorbet_config
|
36
|
+
create_tapioca_config
|
28
37
|
create_post_require
|
29
|
-
|
30
|
-
generate_binstub!
|
31
|
-
else
|
32
|
-
generate_binstub
|
33
|
-
end
|
38
|
+
create_binstub
|
34
39
|
end
|
35
40
|
|
36
41
|
private
|
37
42
|
|
38
43
|
sig { void }
|
39
|
-
def
|
44
|
+
def create_sorbet_config
|
40
45
|
create_file(@sorbet_config, <<~CONTENT, skip: true, force: false)
|
41
46
|
--dir
|
42
47
|
.
|
43
48
|
CONTENT
|
44
49
|
end
|
45
50
|
|
51
|
+
sig { void }
|
52
|
+
def create_tapioca_config
|
53
|
+
create_file(@tapioca_config, <<~YAML, skip: true, force: false)
|
54
|
+
gem:
|
55
|
+
# Add your `gem` command parameters here:
|
56
|
+
#
|
57
|
+
# exclude:
|
58
|
+
# - gem_name
|
59
|
+
# doc: true
|
60
|
+
# workers: 5
|
61
|
+
dsl:
|
62
|
+
# Add your `dsl` command parameters here:
|
63
|
+
#
|
64
|
+
# exclude:
|
65
|
+
# - SomeGeneratorName
|
66
|
+
# workers: 5
|
67
|
+
YAML
|
68
|
+
end
|
69
|
+
|
46
70
|
sig { void }
|
47
71
|
def create_post_require
|
48
72
|
create_file(@default_postrequire, <<~CONTENT, skip: true, force: false)
|
@@ -54,15 +78,16 @@ module Tapioca
|
|
54
78
|
end
|
55
79
|
|
56
80
|
sig { void }
|
57
|
-
def
|
58
|
-
|
59
|
-
say_status(:force, @default_command, :yellow)
|
60
|
-
end
|
81
|
+
def create_binstub
|
82
|
+
force = File.exist?(@default_command)
|
61
83
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
84
|
+
installer.generate_bundler_executable_stubs(spec, { force: force })
|
85
|
+
|
86
|
+
say_status(
|
87
|
+
force ? :force : :create,
|
88
|
+
@default_command,
|
89
|
+
force ? :yellow : :green
|
90
|
+
)
|
66
91
|
end
|
67
92
|
|
68
93
|
sig { returns(Bundler::Installer) }
|
@@ -6,14 +6,14 @@ module Tapioca
|
|
6
6
|
class Todo < Base
|
7
7
|
sig do
|
8
8
|
params(
|
9
|
-
|
9
|
+
todo_file: String,
|
10
10
|
file_header: T::Boolean,
|
11
11
|
default_command: String,
|
12
12
|
file_writer: Thor::Actions
|
13
13
|
).void
|
14
14
|
end
|
15
|
-
def initialize(
|
16
|
-
@
|
15
|
+
def initialize(todo_file:, file_header:, default_command:, file_writer: FileWriter.new)
|
16
|
+
@todo_file = todo_file
|
17
17
|
@file_header = file_header
|
18
18
|
|
19
19
|
super(default_command: default_command, file_writer: file_writer)
|
@@ -26,7 +26,7 @@ module Tapioca
|
|
26
26
|
|
27
27
|
# Clean all existing unresolved constants before regenerating the list
|
28
28
|
# so Sorbet won't grab them as already resolved.
|
29
|
-
File.delete(@
|
29
|
+
File.delete(@todo_file) if File.exist?(@todo_file)
|
30
30
|
|
31
31
|
rbi_string = compiler.compile
|
32
32
|
if rbi_string.empty?
|
@@ -44,9 +44,9 @@ module Tapioca
|
|
44
44
|
content << "\n"
|
45
45
|
|
46
46
|
say("Done", :green)
|
47
|
-
create_file(@
|
47
|
+
create_file(@todo_file, content, verbose: false)
|
48
48
|
|
49
|
-
name = set_color(@
|
49
|
+
name = set_color(@todo_file, :yellow, :bold)
|
50
50
|
say("\nAll unresolved constants have been written to #{name}.", [:green, :bold])
|
51
51
|
say("Please review changes and commit them.", [:green, :bold])
|
52
52
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "thor"
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module CliHelper
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
requires_ancestor { Thor::Shell }
|
12
|
+
|
13
|
+
sig { params(message: String, color: T.any(Symbol, T::Array[Symbol])).void }
|
14
|
+
def say_error(message = "", *color)
|
15
|
+
force_new_line = (message.to_s !~ /( |\t)\Z/)
|
16
|
+
# NOTE: This is a hack. We're no longer subclassing from Thor::Shell::Color
|
17
|
+
# so we no longer have access to the prepare_message call.
|
18
|
+
# We should update this to remove this.
|
19
|
+
buffer = shell.send(:prepare_message, *T.unsafe([message, *T.unsafe(color)]))
|
20
|
+
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
21
|
+
|
22
|
+
$stderr.print(buffer)
|
23
|
+
$stderr.flush
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module ConfigHelper
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(String) }
|
11
|
+
attr_reader :command_name
|
12
|
+
|
13
|
+
sig { returns(Thor::CoreExt::HashWithIndifferentAccess) }
|
14
|
+
attr_reader :defaults
|
15
|
+
|
16
|
+
sig { params(args: T.untyped, local_options: T.untyped, config: T.untyped).void }
|
17
|
+
def initialize(args = [], local_options = {}, config = {})
|
18
|
+
# Store current command
|
19
|
+
command = config[:current_command]
|
20
|
+
command_options = config[:command_options]
|
21
|
+
@command_name = T.let(command.name, String)
|
22
|
+
@merged_options = T.let(nil, T.nilable(Thor::CoreExt::HashWithIndifferentAccess))
|
23
|
+
@defaults = T.let(Thor::CoreExt::HashWithIndifferentAccess.new, Thor::CoreExt::HashWithIndifferentAccess)
|
24
|
+
|
25
|
+
# Filter command options unless we are handling the help command.
|
26
|
+
# This is so that the defaults are printed
|
27
|
+
filter_defaults(command_options) unless command_name == "help"
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { returns(Thor::CoreExt::HashWithIndifferentAccess) }
|
33
|
+
def options
|
34
|
+
@merged_options ||= begin
|
35
|
+
original_options = super
|
36
|
+
config_options = config_options(original_options)
|
37
|
+
|
38
|
+
merge_options(defaults, config_options, original_options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
sig { params(options: T::Hash[Symbol, Thor::Option]).void }
|
45
|
+
def filter_defaults(options)
|
46
|
+
options.each do |key, option|
|
47
|
+
# Store the value of the current default in our defaults hash
|
48
|
+
defaults[key] = option.default
|
49
|
+
# Remove the default value from the option
|
50
|
+
option.instance_variable_set(:@default, nil)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(options: Thor::CoreExt::HashWithIndifferentAccess).returns(Thor::CoreExt::HashWithIndifferentAccess) }
|
55
|
+
def config_options(options)
|
56
|
+
config_file = options[:config]
|
57
|
+
config = {}
|
58
|
+
|
59
|
+
if File.exist?(config_file)
|
60
|
+
config = YAML.load_file(config_file, fallback: {})
|
61
|
+
end
|
62
|
+
|
63
|
+
Thor::CoreExt::HashWithIndifferentAccess.new(config[command_name] || {})
|
64
|
+
end
|
65
|
+
|
66
|
+
sig do
|
67
|
+
params(options: T.nilable(Thor::CoreExt::HashWithIndifferentAccess))
|
68
|
+
.returns(Thor::CoreExt::HashWithIndifferentAccess)
|
69
|
+
end
|
70
|
+
def merge_options(*options)
|
71
|
+
merged = options.each_with_object({}) do |option, result|
|
72
|
+
result.merge!(option || {}) do |_, this_val, other_val|
|
73
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
74
|
+
Thor::CoreExt::HashWithIndifferentAccess.new(this_val.merge(other_val))
|
75
|
+
else
|
76
|
+
other_val
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
Thor::CoreExt::HashWithIndifferentAccess.new(merged)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Helpers
|
8
|
+
module Test
|
9
|
+
module Content
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
requires_ancestor { Kernel }
|
14
|
+
|
15
|
+
sig { void }
|
16
|
+
def teardown
|
17
|
+
super
|
18
|
+
remove_tmp_path
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(args: String).returns(String) }
|
22
|
+
def tmp_path(*args)
|
23
|
+
@tmp_path = T.let(@tmp_path, T.nilable(String))
|
24
|
+
@tmp_path ||= Dir.mktmpdir
|
25
|
+
T.unsafe(File).join(@tmp_path, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { void }
|
29
|
+
def remove_tmp_path
|
30
|
+
FileUtils.rm_rf(tmp_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { params(name: String, content: String, require_file: T::Boolean).returns(String) }
|
34
|
+
def add_ruby_file(name, content, require_file: true)
|
35
|
+
add_content_file(name, content).tap do |file_name|
|
36
|
+
Tapioca.silence_warnings { require(file_name) } if require_file
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(name: String, content: String).returns(String) }
|
41
|
+
def add_content_file(name, content)
|
42
|
+
file_name = tmp_path("lib/#{name}")
|
43
|
+
raise ArgumentError, "a file named '#{name}' was already added; cannot overwrite." if File.exist?(file_name)
|
44
|
+
FileUtils.mkdir_p(File.dirname(file_name))
|
45
|
+
File.write(file_name, content)
|
46
|
+
file_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Helpers
|
6
|
+
module Test
|
7
|
+
# Copied from ActiveSupport::Testing::Isolation since we cannot require
|
8
|
+
# constants from ActiveSupport without polluting the global namespace.
|
9
|
+
module Isolation
|
10
|
+
extend T::Sig
|
11
|
+
require "thread"
|
12
|
+
|
13
|
+
sig { returns(T::Boolean) }
|
14
|
+
def self.forking_env?
|
15
|
+
!ENV["NO_FORK"] && Process.respond_to?(:fork)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
serialized = T.unsafe(self).run_in_isolation do
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
Marshal.load(serialized)
|
24
|
+
end
|
25
|
+
|
26
|
+
module Forking
|
27
|
+
extend T::Sig
|
28
|
+
include Kernel
|
29
|
+
|
30
|
+
sig { params(_blk: T.untyped).returns(String) }
|
31
|
+
def run_in_isolation(&_blk)
|
32
|
+
read, write = IO.pipe
|
33
|
+
read.binmode
|
34
|
+
write.binmode
|
35
|
+
|
36
|
+
this = T.cast(self, Minitest::Test)
|
37
|
+
pid = fork do
|
38
|
+
read.close
|
39
|
+
yield
|
40
|
+
begin
|
41
|
+
if this.error?
|
42
|
+
this.failures.map! do |e|
|
43
|
+
Marshal.dump(e)
|
44
|
+
e
|
45
|
+
rescue TypeError
|
46
|
+
ex = Exception.new(e.message)
|
47
|
+
ex.set_backtrace(e.backtrace)
|
48
|
+
Minitest::UnexpectedError.new(ex)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : this.dup
|
52
|
+
result = Marshal.dump(test_result)
|
53
|
+
end
|
54
|
+
|
55
|
+
write.puts [result].pack("m")
|
56
|
+
write.close
|
57
|
+
exit!(false)
|
58
|
+
end
|
59
|
+
|
60
|
+
write.close
|
61
|
+
result = read.read
|
62
|
+
read.close
|
63
|
+
|
64
|
+
Process.wait2(T.must(pid))
|
65
|
+
T.must(result).unpack1("m")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Subprocess
|
70
|
+
extend T::Sig
|
71
|
+
include Kernel
|
72
|
+
ORIG_ARGV = T.let(ARGV.dup, T::Array[T.untyped]) unless defined?(ORIG_ARGV)
|
73
|
+
|
74
|
+
# Crazy H4X to get this working in windows / jruby with
|
75
|
+
# no forking.
|
76
|
+
sig { params(_blk: T.untyped).returns(String) }
|
77
|
+
def run_in_isolation(&_blk)
|
78
|
+
this = T.cast(self, Minitest::Test)
|
79
|
+
require "tempfile"
|
80
|
+
|
81
|
+
if ENV["ISOLATION_TEST"]
|
82
|
+
yield
|
83
|
+
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : this.dup
|
84
|
+
File.open(T.must(ENV["ISOLATION_OUTPUT"]), "w") do |file|
|
85
|
+
file.puts [Marshal.dump(test_result)].pack("m")
|
86
|
+
end
|
87
|
+
exit!(false)
|
88
|
+
else
|
89
|
+
Tempfile.open("isolation") do |tmpfile|
|
90
|
+
env = {
|
91
|
+
"ISOLATION_TEST" => this.class.name,
|
92
|
+
"ISOLATION_OUTPUT" => tmpfile.path,
|
93
|
+
}
|
94
|
+
|
95
|
+
test_opts = "-n#{this.class.name}##{this.name}"
|
96
|
+
|
97
|
+
load_path_args = []
|
98
|
+
$-I.each do |p|
|
99
|
+
load_path_args << "-I"
|
100
|
+
load_path_args << File.expand_path(p)
|
101
|
+
end
|
102
|
+
|
103
|
+
child = IO.popen([env, Gem.ruby, *load_path_args, $PROGRAM_NAME, *ORIG_ARGV, test_opts])
|
104
|
+
|
105
|
+
begin
|
106
|
+
Process.wait(child.pid)
|
107
|
+
rescue Errno::ECHILD # The child process may exit before we wait
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
return T.must(tmpfile.read).unpack1("m")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
if forking_env?
|
118
|
+
include(Forking)
|
119
|
+
else
|
120
|
+
include(Subprocess)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "erb"
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Helpers
|
8
|
+
module Test
|
9
|
+
module Template
|
10
|
+
include Kernel
|
11
|
+
extend T::Sig
|
12
|
+
ERB_SUPPORTS_KVARGS = T.let(
|
13
|
+
::ERB.instance_method(:initialize).parameters.assoc(:key), T.nilable([Symbol, Symbol])
|
14
|
+
)
|
15
|
+
|
16
|
+
sig { params(selector: String).returns(T::Boolean) }
|
17
|
+
def ruby_version(selector)
|
18
|
+
Gem::Requirement.new(selector).satisfied_by?(Gem::Version.new(RUBY_VERSION))
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(src: String).returns(String) }
|
22
|
+
def template(src)
|
23
|
+
erb = if ERB_SUPPORTS_KVARGS
|
24
|
+
::ERB.new(src, trim_mode: ">")
|
25
|
+
else
|
26
|
+
::ERB.new(src, nil, ">")
|
27
|
+
end
|
28
|
+
|
29
|
+
erb.result(binding)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/tapioca/internal.rb
CHANGED
@@ -6,11 +6,12 @@ require "tapioca/loader"
|
|
6
6
|
require "tapioca/sorbet_ext/generic_name_patch"
|
7
7
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
8
8
|
require "tapioca/generic_type_registry"
|
9
|
-
require "tapioca/
|
10
|
-
require "tapioca/
|
9
|
+
require "tapioca/helpers/cli_helper"
|
10
|
+
require "tapioca/helpers/config_helper"
|
11
11
|
require "tapioca/generators"
|
12
12
|
require "tapioca/cli"
|
13
13
|
require "tapioca/gemfile"
|
14
|
+
require "tapioca/executor"
|
14
15
|
require "tapioca/compilers/sorbet"
|
15
16
|
require "tapioca/compilers/requires_compiler"
|
16
17
|
require "tapioca/compilers/symbol_table_compiler"
|