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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -23
  3. data/lib/tapioca/cli.rb +188 -64
  4. data/lib/tapioca/compilers/dsl/active_record_associations.rb +94 -8
  5. data/lib/tapioca/compilers/dsl/active_record_columns.rb +5 -4
  6. data/lib/tapioca/compilers/dsl/active_record_relations.rb +703 -0
  7. data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
  8. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +2 -4
  9. data/lib/tapioca/compilers/dsl/base.rb +25 -42
  10. data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
  11. data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
  12. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
  13. data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
  14. data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
  15. data/lib/tapioca/compilers/dsl_compiler.rb +32 -6
  16. data/lib/tapioca/compilers/sorbet.rb +2 -0
  17. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +47 -46
  18. data/lib/tapioca/executor.rb +79 -0
  19. data/lib/tapioca/gemfile.rb +23 -0
  20. data/lib/tapioca/generators/base.rb +11 -18
  21. data/lib/tapioca/generators/dsl.rb +33 -38
  22. data/lib/tapioca/generators/gem.rb +50 -29
  23. data/lib/tapioca/generators/init.rb +41 -16
  24. data/lib/tapioca/generators/todo.rb +6 -6
  25. data/lib/tapioca/helpers/cli_helper.rb +26 -0
  26. data/lib/tapioca/helpers/config_helper.rb +84 -0
  27. data/lib/tapioca/helpers/test/content.rb +51 -0
  28. data/lib/tapioca/helpers/test/isolation.rb +125 -0
  29. data/lib/tapioca/helpers/test/template.rb +34 -0
  30. data/lib/tapioca/internal.rb +3 -2
  31. data/lib/tapioca/rbi_ext/model.rb +12 -9
  32. data/lib/tapioca/reflection.rb +13 -0
  33. data/lib/tapioca/trackers/autoload.rb +70 -0
  34. data/lib/tapioca/trackers/constant_definition.rb +42 -0
  35. data/lib/tapioca/trackers/mixin.rb +78 -0
  36. data/lib/tapioca/trackers.rb +14 -0
  37. data/lib/tapioca/version.rb +1 -1
  38. data/lib/tapioca.rb +28 -2
  39. metadata +19 -7
  40. data/lib/tapioca/config.rb +0 -45
  41. data/lib/tapioca/config_builder.rb +0 -73
  42. 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
- gem_excludes: T::Array[String],
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
- file_writer: Thor::Actions
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
- gem_excludes:,
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
- file_writer: FileWriter.new
33
+ include_exported_rbis:,
34
+ file_writer: FileWriter.new,
35
+ number_of_workers: nil
32
36
  )
33
37
  @gem_names = gem_names
34
- @gem_excludes = gem_excludes
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
- gems_to_process = gems_to_generate(@gem_names).reject { |gem| @gem_excludes.include?(gem.name) }
55
-
60
+ gem_queue = gems_to_generate(@gem_names).reject { |gem| @exclude.include?(gem.name) }
56
61
  anything_done = [
57
62
  perform_removals,
58
- gems_to_process.any?,
63
+ gem_queue.any?,
59
64
  ].any?
60
65
 
61
- gems_to_process.each do |gem|
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("Done (empty output)", :yellow)
168
+ say("Compiled #{gem_name} (empty output)", :yellow)
161
169
  else
162
- say("Done", :green)
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
- remove(file) unless file.basename.to_s == gem.rbi_file_name
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
- remove(filename)
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.each do |gem_name|
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(" `#{Config::DEFAULT_COMMAND} #{command}`", [:green, :bold])
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| @gem_excludes.include?(gem.name) }
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
- filenames = files.map do |file|
347
- @outpath / file
348
- end.join("\n - ")
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
- " File(s) #{cause}:\n - #{filenames}"
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(sorbet_config:, default_postrequire:, default_command:, file_writer: FileWriter.new)
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
- create_config
35
+ create_sorbet_config
36
+ create_tapioca_config
28
37
  create_post_require
29
- if File.exist?(@default_command)
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 create_config
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 generate_binstub!
58
- installer.generate_bundler_executable_stubs(spec, { force: true })
59
- say_status(:force, @default_command, :yellow)
60
- end
81
+ def create_binstub
82
+ force = File.exist?(@default_command)
61
83
 
62
- sig { void }
63
- def generate_binstub
64
- installer.generate_bundler_executable_stubs(spec)
65
- say_status(:create, @default_command, :green)
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
- todos_path: String,
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(todos_path:, file_header:, default_command:, file_writer: FileWriter.new)
16
- @todos_path = todos_path
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(@todos_path) if File.exist?(@todos_path)
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(@todos_path, content, verbose: false)
47
+ create_file(@todo_file, content, verbose: false)
48
48
 
49
- name = set_color(@todos_path, :yellow, :bold)
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
@@ -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/config"
10
- require "tapioca/config_builder"
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"