tapioca 0.16.9 → 0.17.7

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -0
  3. data/exe/tapioca +6 -1
  4. data/lib/ruby_lsp/tapioca/addon.rb +73 -43
  5. data/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +43 -43
  6. data/lib/ruby_lsp/tapioca/server_addon.rb +13 -10
  7. data/lib/tapioca/bundler_ext/auto_require_hook.rb +6 -14
  8. data/lib/tapioca/cli.rb +16 -8
  9. data/lib/tapioca/commands/abstract_dsl.rb +39 -66
  10. data/lib/tapioca/commands/abstract_gem.rb +25 -46
  11. data/lib/tapioca/commands/annotations.rb +28 -34
  12. data/lib/tapioca/commands/check_shims.rb +6 -15
  13. data/lib/tapioca/commands/command.rb +12 -26
  14. data/lib/tapioca/commands/command_without_tracker.rb +2 -5
  15. data/lib/tapioca/commands/configure.rb +11 -16
  16. data/lib/tapioca/commands/dsl_compiler_list.rb +2 -1
  17. data/lib/tapioca/commands/dsl_generate.rb +2 -1
  18. data/lib/tapioca/commands/dsl_verify.rb +2 -1
  19. data/lib/tapioca/commands/gem_generate.rb +5 -9
  20. data/lib/tapioca/commands/gem_sync.rb +2 -1
  21. data/lib/tapioca/commands/gem_verify.rb +3 -2
  22. data/lib/tapioca/commands/require.rb +3 -7
  23. data/lib/tapioca/commands/todo.rb +6 -10
  24. data/lib/tapioca/dsl/compiler.rb +36 -63
  25. data/lib/tapioca/dsl/compilers/aasm.rb +33 -44
  26. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +8 -7
  27. data/lib/tapioca/dsl/compilers/action_mailer.rb +6 -5
  28. data/lib/tapioca/dsl/compilers/action_text.rb +6 -5
  29. data/lib/tapioca/dsl/compilers/active_job.rb +6 -10
  30. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +10 -11
  31. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +5 -6
  32. data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +5 -12
  33. data/lib/tapioca/dsl/compilers/active_record_associations.rb +17 -44
  34. data/lib/tapioca/dsl/compilers/active_record_columns.rb +20 -26
  35. data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +9 -8
  36. data/lib/tapioca/dsl/compilers/active_record_enum.rb +7 -6
  37. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +54 -62
  38. data/lib/tapioca/dsl/compilers/active_record_relations.rb +148 -209
  39. data/lib/tapioca/dsl/compilers/active_record_scope.rb +8 -13
  40. data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +5 -4
  41. data/lib/tapioca/dsl/compilers/active_record_store.rb +5 -4
  42. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +19 -28
  43. data/lib/tapioca/dsl/compilers/active_resource.rb +19 -21
  44. data/lib/tapioca/dsl/compilers/active_storage.rb +6 -14
  45. data/lib/tapioca/dsl/compilers/active_support_concern.rb +9 -8
  46. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +8 -7
  47. data/lib/tapioca/dsl/compilers/active_support_time_ext.rb +5 -4
  48. data/lib/tapioca/dsl/compilers/config.rb +5 -4
  49. data/lib/tapioca/dsl/compilers/frozen_record.rb +7 -11
  50. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +9 -10
  51. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +6 -10
  52. data/lib/tapioca/dsl/compilers/identity_cache.rb +11 -39
  53. data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +9 -18
  54. data/lib/tapioca/dsl/compilers/kredis.rb +7 -8
  55. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +5 -4
  56. data/lib/tapioca/dsl/compilers/protobuf.rb +13 -26
  57. data/lib/tapioca/dsl/compilers/rails_generators.rb +9 -11
  58. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +23 -13
  59. data/lib/tapioca/dsl/compilers/smart_properties.rb +32 -38
  60. data/lib/tapioca/dsl/compilers/state_machines.rb +15 -26
  61. data/lib/tapioca/dsl/compilers/url_helpers.rb +10 -9
  62. data/lib/tapioca/dsl/compilers.rb +4 -7
  63. data/lib/tapioca/dsl/helpers/active_model_type_helper.rb +13 -16
  64. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +13 -28
  65. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +19 -15
  66. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +5 -24
  67. data/lib/tapioca/dsl/pipeline.rb +30 -58
  68. data/lib/tapioca/executor.rb +6 -12
  69. data/lib/tapioca/gem/events.rb +24 -34
  70. data/lib/tapioca/gem/listeners/base.rb +7 -10
  71. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +4 -2
  72. data/lib/tapioca/gem/listeners/foreign_constants.rb +5 -7
  73. data/lib/tapioca/gem/listeners/methods.rb +36 -47
  74. data/lib/tapioca/gem/listeners/mixins.rb +6 -18
  75. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +4 -2
  76. data/lib/tapioca/gem/listeners/sorbet_enums.rb +4 -2
  77. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +4 -2
  78. data/lib/tapioca/gem/listeners/sorbet_props.rb +4 -2
  79. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +4 -2
  80. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +7 -5
  81. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +6 -4
  82. data/lib/tapioca/gem/listeners/source_location.rb +15 -8
  83. data/lib/tapioca/gem/listeners/subconstants.rb +5 -4
  84. data/lib/tapioca/gem/listeners/yard_doc.rb +30 -23
  85. data/lib/tapioca/gem/pipeline.rb +107 -91
  86. data/lib/tapioca/gem_info.rb +1 -1
  87. data/lib/tapioca/gemfile.rb +64 -73
  88. data/lib/tapioca/helpers/cli_helper.rb +4 -7
  89. data/lib/tapioca/helpers/config_helper.rb +17 -29
  90. data/lib/tapioca/helpers/env_helper.rb +2 -5
  91. data/lib/tapioca/helpers/gem_helper.rb +5 -5
  92. data/lib/tapioca/helpers/git_attributes.rb +3 -3
  93. data/lib/tapioca/helpers/rbi_files_helper.rb +76 -73
  94. data/lib/tapioca/helpers/rbi_helper.rb +14 -22
  95. data/lib/tapioca/helpers/sorbet_helper.rb +9 -18
  96. data/lib/tapioca/helpers/source_uri.rb +15 -25
  97. data/lib/tapioca/helpers/test/content.rb +7 -10
  98. data/lib/tapioca/helpers/test/dsl_compiler.rb +20 -33
  99. data/lib/tapioca/helpers/test/isolation.rb +10 -14
  100. data/lib/tapioca/helpers/test/template.rb +6 -11
  101. data/lib/tapioca/internal.rb +18 -8
  102. data/lib/tapioca/loaders/dsl.rb +11 -19
  103. data/lib/tapioca/loaders/gem.rb +6 -21
  104. data/lib/tapioca/loaders/loader.rb +21 -39
  105. data/lib/tapioca/rbi_ext/model.rb +12 -37
  106. data/lib/tapioca/rbi_formatter.rb +10 -19
  107. data/lib/tapioca/rbs/rewriter.rb +55 -0
  108. data/lib/tapioca/repo_index.rb +7 -9
  109. data/lib/tapioca/runtime/attached_class_of_32.rb +1 -1
  110. data/lib/tapioca/runtime/attached_class_of_legacy.rb +2 -5
  111. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +23 -23
  112. data/lib/tapioca/runtime/generic_type_registry.rb +13 -23
  113. data/lib/tapioca/runtime/reflection.rb +81 -60
  114. data/lib/tapioca/runtime/source_location.rb +44 -0
  115. data/lib/tapioca/runtime/trackers/autoload.rb +7 -9
  116. data/lib/tapioca/runtime/trackers/constant_definition.rb +18 -14
  117. data/lib/tapioca/runtime/trackers/method_definition.rb +65 -0
  118. data/lib/tapioca/runtime/trackers/mixin.rb +8 -11
  119. data/lib/tapioca/runtime/trackers/required_ancestor.rb +3 -3
  120. data/lib/tapioca/runtime/trackers/tracker.rb +3 -6
  121. data/lib/tapioca/runtime/trackers.rb +5 -8
  122. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +9 -15
  123. data/lib/tapioca/sorbet_ext/name_patch.rb +2 -2
  124. data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +1 -1
  125. data/lib/tapioca/static/requires_compiler.rb +6 -6
  126. data/lib/tapioca/static/symbol_loader.rb +14 -16
  127. data/lib/tapioca/static/symbol_table_parser.rb +8 -8
  128. data/lib/tapioca/version.rb +1 -1
  129. data/lib/tapioca.rb +22 -29
  130. metadata +27 -10
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
@@ -13,12 +13,13 @@ module Tapioca
13
13
  class << self
14
14
  extend T::Sig
15
15
 
16
- sig { returns(T::Boolean) }
16
+ #: -> bool
17
17
  def forking_env?
18
18
  !ENV["NO_FORK"] && Process.respond_to?(:fork)
19
19
  end
20
20
  end
21
21
 
22
+ #: -> Object
22
23
  def run
23
24
  serialized = T.unsafe(self).run_in_isolation do
24
25
  super
@@ -27,13 +28,10 @@ module Tapioca
27
28
  Marshal.load(serialized)
28
29
  end
29
30
 
31
+ # @requires_ancestor: Kernel
30
32
  module Forking
31
33
  extend T::Sig
32
- extend T::Helpers
33
-
34
- requires_ancestor { Kernel }
35
-
36
- sig { params(_blk: T.untyped).returns(String) }
34
+ #: ?{ (?) -> untyped } -> String
37
35
  def run_in_isolation(&_blk)
38
36
  read, write = IO.pipe
39
37
  read.binmode
@@ -67,22 +65,20 @@ module Tapioca
67
65
  result = read.read
68
66
  read.close
69
67
 
70
- Process.wait2(T.must(pid))
71
- T.must(result).unpack1("m")
68
+ Process.wait2(pid)
69
+ result.unpack1("m")
72
70
  end
73
71
  end
74
72
 
73
+ # @requires_ancestor: Kernel
75
74
  module Subprocess
76
75
  extend T::Sig
77
- extend T::Helpers
78
-
79
- requires_ancestor { Kernel }
80
76
 
81
- ORIG_ARGV = T.let(ARGV.dup, T::Array[T.untyped]) unless defined?(ORIG_ARGV)
77
+ ORIG_ARGV = ARGV.dup #: Array[String]
82
78
 
83
79
  # Crazy H4X to get this working in windows / jruby with
84
80
  # no forking.
85
- sig { params(_blk: T.untyped).returns(String) }
81
+ #: ?{ (?) -> untyped } -> String
86
82
  def run_in_isolation(&_blk)
87
83
  this = T.cast(self, Minitest::Test)
88
84
  require "tempfile"
@@ -4,27 +4,22 @@
4
4
  module Tapioca
5
5
  module Helpers
6
6
  module Test
7
+ # @requires_ancestor: Kernel
7
8
  module Template
8
9
  extend T::Sig
9
- extend T::Helpers
10
+ ERB_SUPPORTS_KVARGS = ::ERB.instance_method(:initialize).parameters.assoc(:key) #: [Symbol, Symbol]?
10
11
 
11
- requires_ancestor { Kernel }
12
-
13
- ERB_SUPPORTS_KVARGS = T.let(
14
- ::ERB.instance_method(:initialize).parameters.assoc(:key), T.nilable([Symbol, Symbol])
15
- )
16
-
17
- sig { params(selector: String).returns(T::Boolean) }
12
+ #: (String selector) -> bool
18
13
  def ruby_version(selector)
19
14
  ::Gem::Requirement.new(selector).satisfied_by?(::Gem::Version.new(RUBY_VERSION))
20
15
  end
21
16
 
22
- sig { params(selector: String).returns(T::Boolean) }
17
+ #: (String selector) -> bool
23
18
  def rails_version(selector)
24
19
  ::Gem::Requirement.new(selector).satisfied_by?(ActiveSupport.gem_version)
25
20
  end
26
21
 
27
- sig { params(src: String, trim_mode: String).returns(String) }
22
+ #: (String src, ?trim_mode: String) -> String
28
23
  def template(src, trim_mode: ">")
29
24
  erb = if ERB_SUPPORTS_KVARGS
30
25
  ::ERB.new(src, trim_mode: trim_mode)
@@ -35,7 +30,7 @@ module Tapioca
35
30
  erb.result(binding)
36
31
  end
37
32
 
38
- sig { params(str: String, indent: Integer).returns(String) }
33
+ #: (String str, Integer indent) -> String
39
34
  def indented(str, indent)
40
35
  str.lines.map! do |line|
41
36
  next line if line.chomp.empty?
@@ -7,6 +7,24 @@ require "tapioca"
7
7
  require "tapioca/runtime/reflection"
8
8
  require "tapioca/runtime/trackers"
9
9
 
10
+ require "tapioca/runtime/dynamic_mixin_compiler"
11
+ require "tapioca/sorbet_ext/backcompat_patches"
12
+ require "tapioca/sorbet_ext/name_patch"
13
+ require "tapioca/sorbet_ext/generic_name_patch"
14
+ require "tapioca/sorbet_ext/proc_bind_patch"
15
+ require "tapioca/runtime/generic_type_registry"
16
+
17
+ # The rewriter needs to be loaded very early so RBS comments within Tapioca itself are rewritten
18
+ require "spoom"
19
+ # Eager load all the autoloads at this point, so that we don't enter into
20
+ # a weird loop when the autoloads get triggered and we try to require the file.
21
+ # This is especially important since Prism has a few autoloaded constants that
22
+ # should NOT be rewritten (since they are needed for the rewriting itself), so
23
+ # should be loaded as early as possible.
24
+ Tapioca::Runtime::Trackers::Autoload.eager_load_all!
25
+ require "tapioca/rbs/rewriter"
26
+ # ^ Do not change the order of these requires
27
+
10
28
  require "benchmark"
11
29
  require "bundler"
12
30
  require "erb"
@@ -25,14 +43,6 @@ require "yaml"
25
43
  require "yard-sorbet"
26
44
  require "prism"
27
45
 
28
- require "tapioca/runtime/dynamic_mixin_compiler"
29
- require "tapioca/sorbet_ext/backcompat_patches"
30
- require "tapioca/sorbet_ext/name_patch"
31
- require "tapioca/sorbet_ext/generic_name_patch"
32
- require "tapioca/sorbet_ext/proc_bind_patch"
33
- require "tapioca/runtime/generic_type_registry"
34
-
35
- require "spoom"
36
46
  require "tapioca/helpers/gem_helper"
37
47
  require "tapioca/helpers/git_attributes"
38
48
  require "tapioca/helpers/sorbet_helper"
@@ -9,14 +9,7 @@ module Tapioca
9
9
  class << self
10
10
  extend T::Sig
11
11
 
12
- sig do
13
- params(
14
- tapioca_path: String,
15
- eager_load: T::Boolean,
16
- app_root: String,
17
- halt_upon_load_error: T::Boolean,
18
- ).void
19
- end
12
+ #: (tapioca_path: String, ?eager_load: bool, ?app_root: String, ?halt_upon_load_error: bool) -> void
20
13
  def load_application(
21
14
  tapioca_path:,
22
15
  eager_load: true,
@@ -32,20 +25,21 @@ module Tapioca
32
25
  end
33
26
  end
34
27
 
35
- sig { override.void }
28
+ # @override
29
+ #: -> void
36
30
  def load
37
31
  load_dsl_extensions
38
32
  load_application
39
33
  load_dsl_compilers
40
34
  end
41
35
 
42
- sig { void }
36
+ #: -> void
43
37
  def load_dsl_extensions_and_compilers
44
38
  load_dsl_extensions
45
39
  load_dsl_compilers
46
40
  end
47
41
 
48
- sig { void }
42
+ #: -> void
49
43
  def reload_custom_compilers
50
44
  # Remove all loaded custom compilers
51
45
  ::Tapioca::Dsl::Compiler.descendants.each do |compiler|
@@ -68,9 +62,7 @@ module Tapioca
68
62
 
69
63
  protected
70
64
 
71
- sig do
72
- params(tapioca_path: String, eager_load: T::Boolean, app_root: String, halt_upon_load_error: T::Boolean).void
73
- end
65
+ #: (tapioca_path: String, ?eager_load: bool, ?app_root: String, ?halt_upon_load_error: bool) -> void
74
66
  def initialize(tapioca_path:, eager_load: true, app_root: ".", halt_upon_load_error: true)
75
67
  super()
76
68
 
@@ -78,10 +70,10 @@ module Tapioca
78
70
  @eager_load = eager_load
79
71
  @app_root = app_root
80
72
  @halt_upon_load_error = halt_upon_load_error
81
- @custom_compiler_paths = T.let([], T::Array[String])
73
+ @custom_compiler_paths = [] #: Array[String]
82
74
  end
83
75
 
84
- sig { void }
76
+ #: -> void
85
77
  def load_dsl_extensions
86
78
  say("Loading DSL extension classes... ")
87
79
 
@@ -96,7 +88,7 @@ module Tapioca
96
88
  say("Done", :green)
97
89
  end
98
90
 
99
- sig { void }
91
+ #: -> void
100
92
  def load_dsl_compilers
101
93
  say("Loading DSL compiler classes... ")
102
94
 
@@ -116,7 +108,7 @@ module Tapioca
116
108
  say("Done", :green)
117
109
  end
118
110
 
119
- sig { void }
111
+ #: -> void
120
112
  def load_application
121
113
  say("Loading Rails application... ")
122
114
 
@@ -132,7 +124,7 @@ module Tapioca
132
124
 
133
125
  private
134
126
 
135
- sig { void }
127
+ #: -> void
136
128
  def load_custom_dsl_compilers
137
129
  @custom_compiler_paths = Dir.glob([
138
130
  "#{@tapioca_path}/generators/**/*.rb", # TODO: Here for backcompat, remove later
@@ -9,15 +9,7 @@ module Tapioca
9
9
  class << self
10
10
  extend T::Sig
11
11
 
12
- sig do
13
- params(
14
- bundle: Gemfile,
15
- prerequire: T.nilable(String),
16
- postrequire: String,
17
- default_command: String,
18
- halt_upon_load_error: T::Boolean,
19
- ).void
20
- end
12
+ #: (bundle: Gemfile, prerequire: String?, postrequire: String, default_command: String, halt_upon_load_error: bool) -> void
21
13
  def load_application(bundle:, prerequire:, postrequire:, default_command:, halt_upon_load_error:)
22
14
  loader = new(
23
15
  bundle: bundle,
@@ -30,22 +22,15 @@ module Tapioca
30
22
  end
31
23
  end
32
24
 
33
- sig { override.void }
25
+ # @override
26
+ #: -> void
34
27
  def load
35
28
  require_gem_file
36
29
  end
37
30
 
38
31
  protected
39
32
 
40
- sig do
41
- params(
42
- bundle: Gemfile,
43
- prerequire: T.nilable(String),
44
- postrequire: String,
45
- default_command: String,
46
- halt_upon_load_error: T::Boolean,
47
- ).void
48
- end
33
+ #: (bundle: Gemfile, prerequire: String?, postrequire: String, default_command: String, halt_upon_load_error: bool) -> void
49
34
  def initialize(bundle:, prerequire:, postrequire:, default_command:, halt_upon_load_error:)
50
35
  super()
51
36
 
@@ -56,7 +41,7 @@ module Tapioca
56
41
  @halt_upon_load_error = halt_upon_load_error
57
42
  end
58
43
 
59
- sig { void }
44
+ #: -> void
60
45
  def require_gem_file
61
46
  say("Requiring all gems to prepare for compiling... ")
62
47
  begin
@@ -76,7 +61,7 @@ module Tapioca
76
61
  puts
77
62
  end
78
63
 
79
- sig { params(file: String, error: LoadError).void }
64
+ #: (String file, LoadError error) -> void
80
65
  def explain_failed_require(file, error)
81
66
  say_error("\n\nLoadError: #{error}", :bold, :red)
82
67
  say_error("\nTapioca could not load all the gems required by your application.", :yellow)
@@ -3,29 +3,20 @@
3
3
 
4
4
  module Tapioca
5
5
  module Loaders
6
+ # @abstract
6
7
  class Loader
7
8
  extend T::Sig
8
- extend T::Helpers
9
-
10
9
  include Thor::Base
11
10
  include CliHelper
12
11
  include Tapioca::GemHelper
13
12
 
14
- abstract!
15
-
16
- sig { abstract.void }
17
- def load; end
13
+ # @abstract
14
+ #: -> void
15
+ def load = raise NotImplementedError, "Abstract method called"
18
16
 
19
17
  private
20
18
 
21
- sig do
22
- params(
23
- gemfile: Tapioca::Gemfile,
24
- initialize_file: T.nilable(String),
25
- require_file: T.nilable(String),
26
- halt_upon_load_error: T::Boolean,
27
- ).void
28
- end
19
+ #: (Tapioca::Gemfile gemfile, String? initialize_file, String? require_file, bool halt_upon_load_error) -> void
29
20
  def load_bundle(gemfile, initialize_file, require_file, halt_upon_load_error)
30
21
  require_helper(initialize_file)
31
22
 
@@ -38,14 +29,7 @@ module Tapioca
38
29
  load_rails_engines
39
30
  end
40
31
 
41
- sig do
42
- params(
43
- environment_load: T::Boolean,
44
- eager_load: T::Boolean,
45
- app_root: String,
46
- halt_upon_load_error: T::Boolean,
47
- ).void
48
- end
32
+ #: (?environment_load: bool, ?eager_load: bool, ?app_root: String, ?halt_upon_load_error: bool) -> void
49
33
  def load_rails_application(environment_load: false, eager_load: false, app_root: ".", halt_upon_load_error: true)
50
34
  return unless File.exist?(File.expand_path("config/application.rb", app_root))
51
35
 
@@ -85,7 +69,7 @@ module Tapioca
85
69
  say("Continuing RBI generation without loading the Rails application.")
86
70
  end
87
71
 
88
- sig { void }
72
+ #: -> void
89
73
  def load_rails_engines
90
74
  return if engines.empty?
91
75
 
@@ -110,29 +94,25 @@ module Tapioca
110
94
  end
111
95
  end
112
96
 
113
- sig { void }
97
+ #: -> void
114
98
  def load_engines_in_zeitwerk_mode
115
- # Collect all the directories that are already managed by all existing Zeitwerk loaders.
116
- managed_dirs = Zeitwerk::Registry.loaders.flat_map(&:dirs).to_set
117
99
  # We use a fresh loader to load the engine directories, so that we don't interfere with
118
100
  # any of the existing loaders.
119
101
  autoloader = Zeitwerk::Loader.new
120
102
 
121
103
  engines.each do |engine|
122
104
  eager_load_paths(engine).each do |path|
123
- # Zeitwerk only accepts existing directories in `push_dir`.
124
- next unless File.directory?(path)
125
- # We should not add directories that are already managed by a Zeitwerk loader.
126
- next if managed_dirs.member?(path)
127
-
128
105
  autoloader.push_dir(path)
106
+ rescue Zeitwerk::Error
107
+ # The path is not an existing directory, or it is managed by
108
+ # some other loader, ..., it is fine, just skip it.
129
109
  end
130
110
  end
131
111
 
132
112
  autoloader.setup
133
113
  end
134
114
 
135
- sig { void }
115
+ #: -> void
136
116
  def load_engines_in_classic_mode
137
117
  # This is code adapted from `Rails::Engine#eager_load!` in
138
118
  # https://github.com/rails/rails/blob/d9e188dbab81b412f73dfb7763318d52f360af49/railties/lib/rails/engine.rb#L489-L495
@@ -150,14 +130,14 @@ module Tapioca
150
130
  end
151
131
  end
152
132
 
153
- sig { returns(T::Boolean) }
133
+ #: -> bool
154
134
  def zeitwerk_mode?
155
135
  Rails.respond_to?(:autoloaders) &&
156
136
  Rails.autoloaders.respond_to?(:zeitwerk_enabled?) &&
157
137
  Rails.autoloaders.zeitwerk_enabled?
158
138
  end
159
139
 
160
- sig { params(blk: T.proc.void).void }
140
+ #: { -> void } -> void
161
141
  def with_rails_application(&blk)
162
142
  # Store the current Rails.application object so that we can restore it
163
143
  rails_application = T.unsafe(Rails.application)
@@ -174,7 +154,8 @@ module Tapioca
174
154
  Rails.app_class = Rails.application = rails_application
175
155
  end
176
156
 
177
- T::Sig::WithoutRuntime.sig { returns(T::Array[T.class_of(Rails::Engine)]) }
157
+ # @without_runtime
158
+ #: -> Array[singleton(Rails::Engine)]
178
159
  def engines
179
160
  return [] unless defined?(Rails::Engine)
180
161
 
@@ -188,14 +169,14 @@ module Tapioca
188
169
  .reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) }
189
170
  end
190
171
 
191
- sig { params(path: String).void }
172
+ #: (String path) -> void
192
173
  def safe_require(path)
193
174
  require path
194
175
  rescue LoadError
195
176
  nil
196
177
  end
197
178
 
198
- sig { void }
179
+ #: -> void
199
180
  def eager_load_rails_app
200
181
  application = Rails.application
201
182
 
@@ -216,7 +197,7 @@ module Tapioca
216
197
  end
217
198
  end
218
199
 
219
- sig { params(file: T.nilable(String)).void }
200
+ #: (String? file) -> void
220
201
  def require_helper(file)
221
202
  return unless file
222
203
 
@@ -230,7 +211,8 @@ module Tapioca
230
211
  # The `eager_load_paths` method still exists, but doesn't return all paths anymore and causes Tapioca to miss some
231
212
  # engine paths. The following commit is the change:
232
213
  # https://github.com/rails/rails/commit/ebfca905db14020589c22e6937382e6f8f687664
233
- T::Sig::WithoutRuntime.sig { params(engine: T.class_of(Rails::Engine)).returns(T::Array[String]) }
214
+ # @without_runtime
215
+ #: (singleton(Rails::Engine) engine) -> Array[String]
234
216
  def eager_load_paths(engine)
235
217
  config = engine.config
236
218
 
@@ -5,7 +5,7 @@ module RBI
5
5
  class Tree
6
6
  extend T::Sig
7
7
 
8
- sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).returns(Scope) }
8
+ #: (::Module constant) ?{ (Scope scope) -> void } -> Scope
9
9
  def create_path(constant, &block)
10
10
  constant_name = Tapioca::Runtime::Reflection.name_of(constant)
11
11
  raise "given constant does not have a name" unless constant_name
@@ -21,72 +21,47 @@ module RBI
21
21
  end
22
22
  end
23
23
 
24
- sig { params(name: String, block: T.nilable(T.proc.params(scope: Scope).void)).returns(Scope) }
24
+ #: (String name) ?{ (Scope scope) -> void } -> Scope
25
25
  def create_module(name, &block)
26
26
  T.cast(create_node(RBI::Module.new(name)), RBI::Scope).tap do |node|
27
27
  block&.call(node)
28
28
  end
29
29
  end
30
30
 
31
- sig do
32
- params(
33
- name: String,
34
- superclass_name: T.nilable(String),
35
- block: T.nilable(T.proc.params(scope: RBI::Scope).void),
36
- ).returns(Scope)
37
- end
31
+ #: (String name, ?superclass_name: String?) ?{ (RBI::Scope scope) -> void } -> Scope
38
32
  def create_class(name, superclass_name: nil, &block)
39
33
  T.cast(create_node(RBI::Class.new(name, superclass_name: superclass_name)), RBI::Scope).tap do |node|
40
34
  block&.call(node)
41
35
  end
42
36
  end
43
37
 
44
- sig { params(name: String, value: String).void }
38
+ #: (String name, value: String) -> void
45
39
  def create_constant(name, value:)
46
40
  create_node(RBI::Const.new(name, value))
47
41
  end
48
42
 
49
- sig { params(name: String).void }
43
+ #: (String name) -> void
50
44
  def create_include(name)
51
45
  create_node(RBI::Include.new(name))
52
46
  end
53
47
 
54
- sig { params(name: String).void }
48
+ #: (String name) -> void
55
49
  def create_extend(name)
56
50
  create_node(RBI::Extend.new(name))
57
51
  end
58
52
 
59
- sig { params(name: String).void }
53
+ #: (String name) -> void
60
54
  def create_mixes_in_class_methods(name)
61
55
  create_node(RBI::MixesInClassMethods.new(name))
62
56
  end
63
57
 
64
- sig do
65
- params(
66
- name: String,
67
- type: String,
68
- variance: Symbol,
69
- fixed: T.nilable(String),
70
- upper: T.nilable(String),
71
- lower: T.nilable(String),
72
- ).void
73
- end
58
+ #: (String name, type: String, ?variance: Symbol, ?fixed: String?, ?upper: String?, ?lower: String?) -> void
74
59
  def create_type_variable(name, type:, variance: :invariant, fixed: nil, upper: nil, lower: nil)
75
60
  value = Tapioca::RBIHelper.serialize_type_variable(type, variance, fixed, upper, lower)
76
61
  create_node(RBI::TypeMember.new(name, value))
77
62
  end
78
63
 
79
- sig do
80
- params(
81
- name: String,
82
- parameters: T::Array[TypedParam],
83
- return_type: T.nilable(String),
84
- class_method: T::Boolean,
85
- visibility: RBI::Visibility,
86
- comments: T::Array[RBI::Comment],
87
- block: T.nilable(T.proc.params(node: RBI::Method).void),
88
- ).void
89
- end
64
+ #: (String name, ?parameters: Array[TypedParam], ?return_type: String?, ?class_method: bool, ?visibility: RBI::Visibility, ?comments: Array[RBI::Comment]) ?{ (RBI::Method node) -> void } -> void
90
65
  def create_method(name, parameters: [], return_type: nil, class_method: false, visibility: RBI::Public.new,
91
66
  comments: [], &block)
92
67
  return unless Tapioca::RBIHelper.valid_method_name?(name)
@@ -114,12 +89,12 @@ module RBI
114
89
 
115
90
  private
116
91
 
117
- sig { returns(T::Hash[String, RBI::Node]) }
92
+ #: -> Hash[String, RBI::Node]
118
93
  def nodes_cache
119
- @nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node]))
94
+ @nodes_cache ||= {} #: Hash[String, Node]?
120
95
  end
121
96
 
122
- sig { params(node: RBI::Node).returns(RBI::Node) }
97
+ #: (RBI::Node node) -> RBI::Node
123
98
  def create_node(node)
124
99
  cached = nodes_cache[node.to_s]
125
100
  return cached if cached
@@ -5,13 +5,7 @@ module Tapioca
5
5
  class RBIFormatter < RBI::Formatter
6
6
  extend T::Sig
7
7
 
8
- sig do
9
- params(
10
- file: RBI::File,
11
- command: String,
12
- reason: T.nilable(String),
13
- ).void
14
- end
8
+ #: (RBI::File file, String command, ?reason: String?) -> void
15
9
  def write_header!(file, command, reason: nil)
16
10
  file.comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
17
11
  file.comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
@@ -20,7 +14,7 @@ module Tapioca
20
14
  file.comments << RBI::BlankLine.new
21
15
  end
22
16
 
23
- sig { params(file: RBI::File).void }
17
+ #: (RBI::File file) -> void
24
18
  def write_empty_body_comment!(file)
25
19
  file.comments << RBI::BlankLine.new unless file.comments.empty?
26
20
  file.comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
@@ -28,15 +22,12 @@ module Tapioca
28
22
  end
29
23
  end
30
24
 
31
- DEFAULT_RBI_FORMATTER = T.let(
32
- RBIFormatter.new(
33
- add_sig_templates: false,
34
- group_nodes: true,
35
- max_line_length: nil,
36
- nest_singleton_methods: true,
37
- nest_non_public_members: true,
38
- sort_nodes: true,
39
- ),
40
- RBIFormatter,
41
- )
25
+ DEFAULT_RBI_FORMATTER = RBIFormatter.new(
26
+ add_sig_templates: false,
27
+ group_nodes: true,
28
+ max_line_length: nil,
29
+ nest_singleton_methods: true,
30
+ nest_non_public_members: true,
31
+ sort_nodes: true,
32
+ ) #: RBIFormatter
42
33
  end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "require-hooks/setup"
5
+
6
+ # This code rewrites RBS comments back into Sorbet's signatures as the files are being loaded.
7
+ # This will allow `sorbet-runtime` to wrap the methods as if they were originally written with the `sig{}` blocks.
8
+ # This will in turn allow Tapioca to use this signatures to generate typed RBI files.
9
+
10
+ begin
11
+ # When in a `bootsnap` environment, files are loaded from the cache and won't trigger the `source_transform` method.
12
+ # The `require-hooks` gem comes with a `bootsnap` mode that will disable the `bootsnap/compile_cache/iseq` caching.
13
+ # Sadly, we're way to early in the boot process to use it as bootsnap won't be loaded yet and the `require-hooks`
14
+ # setup won't pick it up.
15
+ #
16
+ # As a workaround, if we can preemptively require `bootsnap` and `bootsnap/compile_cache/iseq` we manually override
17
+ # the `load_iseq` method to disable the caching mechanism.
18
+ #
19
+ # This will make the Rails app load slower but allows us to trigger the RBS -> RBI source transform.
20
+ require "bootsnap"
21
+ require "bootsnap/compile_cache/iseq"
22
+
23
+ module Bootsnap
24
+ module CompileCache
25
+ module ISeq
26
+ module InstructionSequenceMixin
27
+ #: (String) -> RubyVM::InstructionSequence
28
+ def load_iseq(path)
29
+ super if defined?(super)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ rescue LoadError
36
+ # Bootsnap is not in the bundle, we don't need to do anything.
37
+ end
38
+
39
+ # We need to include `T::Sig` very early to make sure that the `sig` method is available since gems using RBS comments
40
+ # are unlikely to include `T::Sig` in their own classes.
41
+ Module.include(T::Sig)
42
+
43
+ # Trigger the source transformation for each Ruby file being loaded.
44
+ RequireHooks.source_transform(patterns: ["**/*.rb"]) do |path, source|
45
+ # The source is most likely nil since no `source_transform` hook was triggered before this one.
46
+ source ||= File.read(path, encoding: "UTF-8")
47
+
48
+ # For performance reasons, we only rewrite files that use Sorbet.
49
+ if source =~ /^\s*#\s*typed: (ignore|false|true|strict|strong|__STDLIB_INTERNAL)/
50
+ Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(source, file: path)
51
+ end
52
+ rescue Spoom::Sorbet::Translate::Error
53
+ # If we can't translate the RBS comments back into Sorbet's signatures, we just skip the file.
54
+ source
55
+ end