tapioca 0.16.11 → 0.17.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_lsp/tapioca/addon.rb +23 -19
  3. data/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +20 -20
  4. data/lib/tapioca/bundler_ext/auto_require_hook.rb +5 -10
  5. data/lib/tapioca/commands/abstract_dsl.rb +26 -59
  6. data/lib/tapioca/commands/abstract_gem.rb +23 -43
  7. data/lib/tapioca/commands/annotations.rb +27 -33
  8. data/lib/tapioca/commands/check_shims.rb +4 -13
  9. data/lib/tapioca/commands/command.rb +8 -20
  10. data/lib/tapioca/commands/command_without_tracker.rb +1 -1
  11. data/lib/tapioca/commands/configure.rb +11 -16
  12. data/lib/tapioca/commands/dsl_compiler_list.rb +2 -1
  13. data/lib/tapioca/commands/dsl_generate.rb +2 -1
  14. data/lib/tapioca/commands/dsl_verify.rb +2 -1
  15. data/lib/tapioca/commands/gem_generate.rb +4 -8
  16. data/lib/tapioca/commands/gem_sync.rb +2 -1
  17. data/lib/tapioca/commands/gem_verify.rb +3 -2
  18. data/lib/tapioca/commands/require.rb +3 -7
  19. data/lib/tapioca/commands/todo.rb +6 -10
  20. data/lib/tapioca/dsl/compiler.rb +28 -53
  21. data/lib/tapioca/dsl/compilers/aasm.rb +31 -41
  22. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +7 -5
  23. data/lib/tapioca/dsl/compilers/action_mailer.rb +5 -3
  24. data/lib/tapioca/dsl/compilers/action_text.rb +5 -3
  25. data/lib/tapioca/dsl/compilers/active_job.rb +5 -8
  26. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +9 -7
  27. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +4 -2
  28. data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +4 -2
  29. data/lib/tapioca/dsl/compilers/active_record_associations.rb +16 -42
  30. data/lib/tapioca/dsl/compilers/active_record_columns.rb +19 -24
  31. data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +7 -5
  32. data/lib/tapioca/dsl/compilers/active_record_enum.rb +6 -4
  33. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +53 -61
  34. data/lib/tapioca/dsl/compilers/active_record_relations.rb +86 -119
  35. data/lib/tapioca/dsl/compilers/active_record_scope.rb +7 -11
  36. data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +4 -2
  37. data/lib/tapioca/dsl/compilers/active_record_store.rb +4 -2
  38. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +18 -26
  39. data/lib/tapioca/dsl/compilers/active_resource.rb +18 -19
  40. data/lib/tapioca/dsl/compilers/active_storage.rb +5 -5
  41. data/lib/tapioca/dsl/compilers/active_support_concern.rb +8 -6
  42. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +7 -5
  43. data/lib/tapioca/dsl/compilers/active_support_time_ext.rb +4 -2
  44. data/lib/tapioca/dsl/compilers/config.rb +4 -2
  45. data/lib/tapioca/dsl/compilers/frozen_record.rb +6 -9
  46. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +8 -8
  47. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +5 -8
  48. data/lib/tapioca/dsl/compilers/identity_cache.rb +10 -37
  49. data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +8 -16
  50. data/lib/tapioca/dsl/compilers/kredis.rb +6 -4
  51. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +4 -2
  52. data/lib/tapioca/dsl/compilers/protobuf.rb +12 -24
  53. data/lib/tapioca/dsl/compilers/rails_generators.rb +8 -9
  54. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +22 -11
  55. data/lib/tapioca/dsl/compilers/smart_properties.rb +11 -20
  56. data/lib/tapioca/dsl/compilers/state_machines.rb +14 -24
  57. data/lib/tapioca/dsl/compilers/url_helpers.rb +9 -7
  58. data/lib/tapioca/dsl/compilers.rb +4 -7
  59. data/lib/tapioca/dsl/helpers/active_model_type_helper.rb +13 -16
  60. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +13 -28
  61. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +19 -15
  62. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +5 -24
  63. data/lib/tapioca/dsl/pipeline.rb +23 -55
  64. data/lib/tapioca/executor.rb +6 -12
  65. data/lib/tapioca/gem/events.rb +22 -28
  66. data/lib/tapioca/gem/listeners/base.rb +6 -6
  67. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +4 -2
  68. data/lib/tapioca/gem/listeners/foreign_constants.rb +5 -7
  69. data/lib/tapioca/gem/listeners/methods.rb +15 -34
  70. data/lib/tapioca/gem/listeners/mixins.rb +6 -18
  71. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +4 -2
  72. data/lib/tapioca/gem/listeners/sorbet_enums.rb +4 -2
  73. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +4 -2
  74. data/lib/tapioca/gem/listeners/sorbet_props.rb +4 -2
  75. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +4 -2
  76. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +7 -5
  77. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +6 -4
  78. data/lib/tapioca/gem/listeners/source_location.rb +7 -4
  79. data/lib/tapioca/gem/listeners/subconstants.rb +4 -2
  80. data/lib/tapioca/gem/listeners/yard_doc.rb +23 -22
  81. data/lib/tapioca/gem/pipeline.rb +57 -72
  82. data/lib/tapioca/gem_info.rb +1 -1
  83. data/lib/tapioca/gemfile.rb +64 -73
  84. data/lib/tapioca/helpers/cli_helper.rb +3 -3
  85. data/lib/tapioca/helpers/config_helper.rb +15 -24
  86. data/lib/tapioca/helpers/env_helper.rb +1 -1
  87. data/lib/tapioca/helpers/gem_helper.rb +5 -5
  88. data/lib/tapioca/helpers/git_attributes.rb +3 -3
  89. data/lib/tapioca/helpers/rbi_files_helper.rb +73 -67
  90. data/lib/tapioca/helpers/rbi_helper.rb +14 -22
  91. data/lib/tapioca/helpers/sorbet_helper.rb +9 -18
  92. data/lib/tapioca/helpers/source_uri.rb +15 -25
  93. data/lib/tapioca/helpers/test/content.rb +6 -6
  94. data/lib/tapioca/helpers/test/dsl_compiler.rb +19 -29
  95. data/lib/tapioca/helpers/test/isolation.rb +4 -4
  96. data/lib/tapioca/helpers/test/template.rb +5 -7
  97. data/lib/tapioca/internal.rb +5 -1
  98. data/lib/tapioca/loaders/dsl.rb +11 -19
  99. data/lib/tapioca/loaders/gem.rb +6 -21
  100. data/lib/tapioca/loaders/loader.rb +15 -27
  101. data/lib/tapioca/rbi_ext/model.rb +12 -37
  102. data/lib/tapioca/rbi_formatter.rb +10 -19
  103. data/lib/tapioca/rbs/rewriter.rb +55 -0
  104. data/lib/tapioca/repo_index.rb +7 -7
  105. data/lib/tapioca/runtime/attached_class_of_32.rb +1 -1
  106. data/lib/tapioca/runtime/attached_class_of_legacy.rb +1 -1
  107. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +23 -23
  108. data/lib/tapioca/runtime/generic_type_registry.rb +13 -23
  109. data/lib/tapioca/runtime/reflection.rb +48 -56
  110. data/lib/tapioca/runtime/trackers/autoload.rb +4 -8
  111. data/lib/tapioca/runtime/trackers/mixin.rb +6 -10
  112. data/lib/tapioca/runtime/trackers/required_ancestor.rb +3 -3
  113. data/lib/tapioca/runtime/trackers/tracker.rb +2 -2
  114. data/lib/tapioca/runtime/trackers.rb +4 -8
  115. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +9 -15
  116. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  117. data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +1 -1
  118. data/lib/tapioca/static/requires_compiler.rb +6 -6
  119. data/lib/tapioca/static/symbol_loader.rb +14 -16
  120. data/lib/tapioca/static/symbol_table_parser.rb +8 -8
  121. data/lib/tapioca/version.rb +1 -1
  122. data/lib/tapioca.rb +22 -29
  123. metadata +25 -10
@@ -10,21 +10,19 @@ module Tapioca
10
10
 
11
11
  requires_ancestor { Kernel }
12
12
 
13
- ERB_SUPPORTS_KVARGS = T.let(
14
- ::ERB.instance_method(:initialize).parameters.assoc(:key), T.nilable([Symbol, Symbol])
15
- )
13
+ ERB_SUPPORTS_KVARGS = ::ERB.instance_method(:initialize).parameters.assoc(:key) #: [Symbol, Symbol]?
16
14
 
17
- sig { params(selector: String).returns(T::Boolean) }
15
+ #: (String selector) -> bool
18
16
  def ruby_version(selector)
19
17
  ::Gem::Requirement.new(selector).satisfied_by?(::Gem::Version.new(RUBY_VERSION))
20
18
  end
21
19
 
22
- sig { params(selector: String).returns(T::Boolean) }
20
+ #: (String selector) -> bool
23
21
  def rails_version(selector)
24
22
  ::Gem::Requirement.new(selector).satisfied_by?(ActiveSupport.gem_version)
25
23
  end
26
24
 
27
- sig { params(src: String, trim_mode: String).returns(String) }
25
+ #: (String src, ?trim_mode: String) -> String
28
26
  def template(src, trim_mode: ">")
29
27
  erb = if ERB_SUPPORTS_KVARGS
30
28
  ::ERB.new(src, trim_mode: trim_mode)
@@ -35,7 +33,7 @@ module Tapioca
35
33
  erb.result(binding)
36
34
  end
37
35
 
38
- sig { params(str: String, indent: Integer).returns(String) }
36
+ #: (String str, Integer indent) -> String
39
37
  def indented(str, indent)
40
38
  str.lines.map! do |line|
41
39
  next line if line.chomp.empty?
@@ -7,6 +7,11 @@ require "tapioca"
7
7
  require "tapioca/runtime/reflection"
8
8
  require "tapioca/runtime/trackers"
9
9
 
10
+ # The rewriter needs to be loaded very early so RBS comments within Tapioca itself are rewritten
11
+ require "spoom"
12
+ require "tapioca/rbs/rewriter"
13
+ # ^ Do not change the order of these requires
14
+
10
15
  require "benchmark"
11
16
  require "bundler"
12
17
  require "erb"
@@ -32,7 +37,6 @@ require "tapioca/sorbet_ext/generic_name_patch"
32
37
  require "tapioca/sorbet_ext/proc_bind_patch"
33
38
  require "tapioca/runtime/generic_type_registry"
34
39
 
35
- require "spoom"
36
40
  require "tapioca/helpers/gem_helper"
37
41
  require "tapioca/helpers/git_attributes"
38
42
  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)
@@ -18,14 +18,7 @@ module Tapioca
18
18
 
19
19
  private
20
20
 
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
21
+ #: (Tapioca::Gemfile gemfile, String? initialize_file, String? require_file, bool halt_upon_load_error) -> void
29
22
  def load_bundle(gemfile, initialize_file, require_file, halt_upon_load_error)
30
23
  require_helper(initialize_file)
31
24
 
@@ -38,14 +31,7 @@ module Tapioca
38
31
  load_rails_engines
39
32
  end
40
33
 
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
34
+ #: (?environment_load: bool, ?eager_load: bool, ?app_root: String, ?halt_upon_load_error: bool) -> void
49
35
  def load_rails_application(environment_load: false, eager_load: false, app_root: ".", halt_upon_load_error: true)
50
36
  return unless File.exist?(File.expand_path("config/application.rb", app_root))
51
37
 
@@ -85,7 +71,7 @@ module Tapioca
85
71
  say("Continuing RBI generation without loading the Rails application.")
86
72
  end
87
73
 
88
- sig { void }
74
+ #: -> void
89
75
  def load_rails_engines
90
76
  return if engines.empty?
91
77
 
@@ -110,10 +96,10 @@ module Tapioca
110
96
  end
111
97
  end
112
98
 
113
- sig { void }
99
+ #: -> void
114
100
  def load_engines_in_zeitwerk_mode
115
101
  # Collect all the directories that are already managed by all existing Zeitwerk loaders.
116
- managed_dirs = Zeitwerk::Registry.loaders.flat_map(&:dirs).to_set
102
+ managed_dirs = Zeitwerk::Loader.all_dirs.to_set
117
103
  # We use a fresh loader to load the engine directories, so that we don't interfere with
118
104
  # any of the existing loaders.
119
105
  autoloader = Zeitwerk::Loader.new
@@ -132,7 +118,7 @@ module Tapioca
132
118
  autoloader.setup
133
119
  end
134
120
 
135
- sig { void }
121
+ #: -> void
136
122
  def load_engines_in_classic_mode
137
123
  # This is code adapted from `Rails::Engine#eager_load!` in
138
124
  # https://github.com/rails/rails/blob/d9e188dbab81b412f73dfb7763318d52f360af49/railties/lib/rails/engine.rb#L489-L495
@@ -150,14 +136,14 @@ module Tapioca
150
136
  end
151
137
  end
152
138
 
153
- sig { returns(T::Boolean) }
139
+ #: -> bool
154
140
  def zeitwerk_mode?
155
141
  Rails.respond_to?(:autoloaders) &&
156
142
  Rails.autoloaders.respond_to?(:zeitwerk_enabled?) &&
157
143
  Rails.autoloaders.zeitwerk_enabled?
158
144
  end
159
145
 
160
- sig { params(blk: T.proc.void).void }
146
+ #: { -> void } -> void
161
147
  def with_rails_application(&blk)
162
148
  # Store the current Rails.application object so that we can restore it
163
149
  rails_application = T.unsafe(Rails.application)
@@ -174,7 +160,8 @@ module Tapioca
174
160
  Rails.app_class = Rails.application = rails_application
175
161
  end
176
162
 
177
- T::Sig::WithoutRuntime.sig { returns(T::Array[T.class_of(Rails::Engine)]) }
163
+ # @without_runtime
164
+ #: -> Array[singleton(Rails::Engine)]
178
165
  def engines
179
166
  return [] unless defined?(Rails::Engine)
180
167
 
@@ -188,14 +175,14 @@ module Tapioca
188
175
  .reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) }
189
176
  end
190
177
 
191
- sig { params(path: String).void }
178
+ #: (String path) -> void
192
179
  def safe_require(path)
193
180
  require path
194
181
  rescue LoadError
195
182
  nil
196
183
  end
197
184
 
198
- sig { void }
185
+ #: -> void
199
186
  def eager_load_rails_app
200
187
  application = Rails.application
201
188
 
@@ -216,7 +203,7 @@ module Tapioca
216
203
  end
217
204
  end
218
205
 
219
- sig { params(file: T.nilable(String)).void }
206
+ #: (String? file) -> void
220
207
  def require_helper(file)
221
208
  return unless file
222
209
 
@@ -230,7 +217,8 @@ module Tapioca
230
217
  # The `eager_load_paths` method still exists, but doesn't return all paths anymore and causes Tapioca to miss some
231
218
  # engine paths. The following commit is the change:
232
219
  # https://github.com/rails/rails/commit/ebfca905db14020589c22e6937382e6f8f687664
233
- T::Sig::WithoutRuntime.sig { params(engine: T.class_of(Rails::Engine)).returns(T::Array[String]) }
220
+ # @without_runtime
221
+ #: (singleton(Rails::Engine) engine) -> Array[String]
234
222
  def eager_load_paths(engine)
235
223
  config = engine.config
236
224
 
@@ -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
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)
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 RBI::RBS::MethodTypeTranslator::Error
53
+ # If we can't translate the RBS comments back into Sorbet's signatures, we just skip the file.
54
+ source
55
+ end
@@ -9,12 +9,12 @@ module Tapioca
9
9
  class << self
10
10
  extend T::Sig
11
11
 
12
- sig { params(json: String).returns(RepoIndex) }
12
+ #: (String json) -> RepoIndex
13
13
  def from_json(json)
14
14
  RepoIndex.from_hash(JSON.parse(json))
15
15
  end
16
16
 
17
- sig { params(hash: T::Hash[String, T::Hash[T.untyped, T.untyped]]).returns(RepoIndex) }
17
+ #: (Hash[String, Hash[untyped, untyped]] hash) -> RepoIndex
18
18
  def from_hash(hash)
19
19
  hash.each_with_object(RepoIndex.new) do |(name, _), index|
20
20
  index << name
@@ -22,22 +22,22 @@ module Tapioca
22
22
  end
23
23
  end
24
24
 
25
- sig { void }
25
+ #: -> void
26
26
  def initialize
27
- @entries = T.let(Set.new, T::Set[String])
27
+ @entries = Set.new #: Set[String]
28
28
  end
29
29
 
30
- sig { params(gem_name: String).void }
30
+ #: (String gem_name) -> void
31
31
  def <<(gem_name)
32
32
  @entries.add(gem_name)
33
33
  end
34
34
 
35
- sig { returns(T::Enumerable[String]) }
35
+ #: -> T::Enumerable[String]
36
36
  def gems
37
37
  @entries.sort
38
38
  end
39
39
 
40
- sig { params(gem_name: String).returns(T::Boolean) }
40
+ #: (String gem_name) -> bool
41
41
  def has_gem?(gem_name)
42
42
  @entries.include?(gem_name)
43
43
  end
@@ -10,7 +10,7 @@ module Tapioca
10
10
  module AttachedClassOf
11
11
  extend T::Sig
12
12
 
13
- sig { params(singleton_class: Class).returns(T.nilable(Module)) }
13
+ #: (Class singleton_class) -> Module?
14
14
  def attached_class_of(singleton_class)
15
15
  result = singleton_class.attached_object
16
16
  Module === result ? result : nil
@@ -13,7 +13,7 @@ module Tapioca
13
13
 
14
14
  requires_ancestor { Tapioca::Runtime::Reflection }
15
15
 
16
- sig { params(singleton_class: Class).returns(T.nilable(Module)) }
16
+ #: (Class singleton_class) -> Module?
17
17
  def attached_class_of(singleton_class)
18
18
  # https://stackoverflow.com/a/36622320/98634
19
19
  result = ObjectSpace.each_object(singleton_class).find do |klass|