tapioca 0.5.6 → 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 -64
- 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_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 +2 -4
- data/lib/tapioca/compilers/dsl/base.rb +25 -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/param_helper.rb +52 -0
- data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +32 -6
- data/lib/tapioca/compilers/sorbet.rb +2 -0
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +47 -46
- data/lib/tapioca/executor.rb +79 -0
- data/lib/tapioca/gemfile.rb +23 -0
- data/lib/tapioca/generators/base.rb +11 -18
- data/lib/tapioca/generators/dsl.rb +33 -38
- data/lib/tapioca/generators/gem.rb +50 -29
- 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 +12 -9
- 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 +19 -7
- data/lib/tapioca/config.rb +0 -45
- data/lib/tapioca/config_builder.rb +0 -73
- data/lib/tapioca/constant_locator.rb +0 -40
@@ -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,21 +50,20 @@ 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
|
-
|
55
|
-
|
60
|
+
gem_queue = gems_to_generate(@gem_names).reject { |gem| @exclude.include?(gem.name) }
|
56
61
|
anything_done = [
|
57
62
|
perform_removals,
|
58
|
-
|
63
|
+
gem_queue.any?,
|
59
64
|
].any?
|
60
65
|
|
61
|
-
|
62
|
-
say("Processing '#{gem.name}' gem:", :green)
|
66
|
+
Executor.new(gem_queue, number_of_workers: @number_of_workers).run_in_parallel do |gem|
|
63
67
|
shell.indent do
|
64
68
|
compile_gem_rbi(gem)
|
65
69
|
puts
|
@@ -119,6 +123,9 @@ module Tapioca
|
|
119
123
|
explain_failed_require(@postrequire, e)
|
120
124
|
exit(1)
|
121
125
|
end
|
126
|
+
|
127
|
+
Tapioca::Trackers::Autoload.eager_load_all!
|
128
|
+
|
122
129
|
say(" Done", :green)
|
123
130
|
unless bundle.missing_specs.empty?
|
124
131
|
say(" completed with missing specs: ")
|
@@ -144,7 +151,6 @@ module Tapioca
|
|
144
151
|
sig { params(gem: Gemfile::GemSpec).void }
|
145
152
|
def compile_gem_rbi(gem)
|
146
153
|
gem_name = set_color(gem.name, :yellow, :bold)
|
147
|
-
say("Compiling #{gem_name}, this may take a few seconds... ")
|
148
154
|
|
149
155
|
rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
|
150
156
|
rbi.set_file_header(
|
@@ -155,17 +161,19 @@ module Tapioca
|
|
155
161
|
|
156
162
|
Compilers::SymbolTableCompiler.new.compile(gem, rbi, 0, @doc)
|
157
163
|
|
164
|
+
merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
|
165
|
+
|
158
166
|
if rbi.empty?
|
159
167
|
rbi.set_empty_body_content
|
160
|
-
say("
|
168
|
+
say("Compiled #{gem_name} (empty output)", :yellow)
|
161
169
|
else
|
162
|
-
say("
|
170
|
+
say("Compiled #{gem_name}", :green)
|
163
171
|
end
|
164
172
|
|
165
173
|
create_file(@outpath / gem.rbi_file_name, rbi.transformed_string)
|
166
174
|
|
167
175
|
T.unsafe(Pathname).glob((@outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
168
|
-
|
176
|
+
remove_file(file) unless file.basename.to_s == gem.rbi_file_name
|
169
177
|
end
|
170
178
|
end
|
171
179
|
|
@@ -201,7 +209,7 @@ module Tapioca
|
|
201
209
|
else
|
202
210
|
gems.each do |removed|
|
203
211
|
filename = existing_rbi(removed)
|
204
|
-
|
212
|
+
remove_file(filename)
|
205
213
|
end
|
206
214
|
|
207
215
|
anything_done = true
|
@@ -228,7 +236,7 @@ module Tapioca
|
|
228
236
|
else
|
229
237
|
require_gem_file
|
230
238
|
|
231
|
-
gems.
|
239
|
+
Executor.new(gems, number_of_workers: @number_of_workers).run_in_parallel do |gem_name|
|
232
240
|
filename = expected_rbi(gem_name)
|
233
241
|
|
234
242
|
if gem_rbi_exists?(gem_name)
|
@@ -261,13 +269,6 @@ module Tapioca
|
|
261
269
|
say_error("you should probably review it and remove the faulty line.", :yellow)
|
262
270
|
end
|
263
271
|
|
264
|
-
sig { params(filename: Pathname).void }
|
265
|
-
def remove(filename)
|
266
|
-
return unless filename.exist?
|
267
|
-
say("-- Removing: #{filename}")
|
268
|
-
filename.unlink
|
269
|
-
end
|
270
|
-
|
271
272
|
sig { returns(T::Array[String]) }
|
272
273
|
def removed_rbis
|
273
274
|
(existing_rbis.keys - expected_rbis.keys).sort
|
@@ -301,7 +302,7 @@ module Tapioca
|
|
301
302
|
say("Nothing to do, all RBIs are up-to-date.")
|
302
303
|
else
|
303
304
|
say("RBI files are out-of-date. In your development environment, please run:", :green)
|
304
|
-
say(" `#{
|
305
|
+
say(" `#{@default_command} #{command}`", [:green, :bold])
|
305
306
|
say("Once it is complete, be sure to commit and push any changes", :green)
|
306
307
|
|
307
308
|
say("")
|
@@ -331,7 +332,7 @@ module Tapioca
|
|
331
332
|
sig { returns(T::Hash[String, String]) }
|
332
333
|
def expected_rbis
|
333
334
|
@expected_rbis ||= bundle.dependencies
|
334
|
-
.reject { |gem| @
|
335
|
+
.reject { |gem| @exclude.include?(gem.name) }
|
335
336
|
.map { |gem| [gem.name, gem.version.to_s] }
|
336
337
|
.to_h
|
337
338
|
end
|
@@ -343,11 +344,31 @@ module Tapioca
|
|
343
344
|
|
344
345
|
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
|
345
346
|
def build_error_for_files(cause, files)
|
346
|
-
|
347
|
-
|
348
|
-
|
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
|
349
367
|
|
350
|
-
|
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})")
|
351
372
|
end
|
352
373
|
end
|
353
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"
|