tapioca 0.5.1 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/lib/tapioca/cli.rb +54 -139
  4. data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
  5. data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
  6. data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
  7. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +41 -33
  8. data/lib/tapioca/compilers/dsl/active_support_concern.rb +0 -2
  9. data/lib/tapioca/compilers/dsl/base.rb +12 -0
  10. data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
  11. data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
  12. data/lib/tapioca/compilers/dsl/smart_properties.rb +4 -4
  13. data/lib/tapioca/compilers/dsl_compiler.rb +7 -6
  14. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
  15. data/lib/tapioca/compilers/sorbet.rb +0 -1
  16. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +84 -153
  17. data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
  18. data/lib/tapioca/config.rb +1 -0
  19. data/lib/tapioca/config_builder.rb +1 -0
  20. data/lib/tapioca/constant_locator.rb +7 -1
  21. data/lib/tapioca/gemfile.rb +11 -5
  22. data/lib/tapioca/generators/base.rb +61 -0
  23. data/lib/tapioca/generators/dsl.rb +362 -0
  24. data/lib/tapioca/generators/gem.rb +345 -0
  25. data/lib/tapioca/generators/init.rb +79 -0
  26. data/lib/tapioca/generators/require.rb +52 -0
  27. data/lib/tapioca/generators/todo.rb +76 -0
  28. data/lib/tapioca/generators.rb +9 -0
  29. data/lib/tapioca/internal.rb +1 -2
  30. data/lib/tapioca/loader.rb +2 -2
  31. data/lib/tapioca/rbi_ext/model.rb +44 -0
  32. data/lib/tapioca/reflection.rb +8 -1
  33. data/lib/tapioca/version.rb +1 -1
  34. data/lib/tapioca.rb +2 -0
  35. metadata +34 -12
  36. data/lib/tapioca/generator.rb +0 -717
@@ -0,0 +1,345 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Generators
6
+ class Gem < Base
7
+ sig do
8
+ params(
9
+ gem_names: T::Array[String],
10
+ gem_excludes: T::Array[String],
11
+ prerequire: T.nilable(String),
12
+ postrequire: String,
13
+ typed_overrides: T::Hash[String, String],
14
+ default_command: String,
15
+ outpath: Pathname,
16
+ file_header: T::Boolean,
17
+ doc: T::Boolean,
18
+ file_writer: Thor::Actions
19
+ ).void
20
+ end
21
+ def initialize(
22
+ gem_names:,
23
+ gem_excludes:,
24
+ prerequire:,
25
+ postrequire:,
26
+ typed_overrides:,
27
+ default_command:,
28
+ outpath:,
29
+ file_header:,
30
+ doc:,
31
+ file_writer: FileWriter.new
32
+ )
33
+ @gem_names = gem_names
34
+ @gem_excludes = gem_excludes
35
+ @prerequire = prerequire
36
+ @postrequire = postrequire
37
+ @typed_overrides = typed_overrides
38
+ @outpath = outpath
39
+ @file_header = file_header
40
+
41
+ super(default_command: default_command, file_writer: file_writer)
42
+
43
+ @loader = T.let(nil, T.nilable(Loader))
44
+ @bundle = T.let(nil, T.nilable(Gemfile))
45
+ @existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
46
+ @expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
47
+ @doc = T.let(doc, T::Boolean)
48
+ end
49
+
50
+ sig { override.void }
51
+ def generate
52
+ require_gem_file
53
+
54
+ gems_to_generate(@gem_names)
55
+ .reject { |gem| @gem_excludes.include?(gem.name) }
56
+ .each do |gem|
57
+ say("Processing '#{gem.name}' gem:", :green)
58
+ shell.indent do
59
+ compile_gem_rbi(gem)
60
+ puts
61
+ end
62
+ end
63
+
64
+ say("All operations performed in working directory.", [:green, :bold])
65
+ say("Please review changes and commit them.", [:green, :bold])
66
+ end
67
+
68
+ sig { params(should_verify: T::Boolean).void }
69
+ def sync(should_verify: false)
70
+ if should_verify
71
+ say("Checking for out-of-date RBIs...")
72
+ say("")
73
+ perform_sync_verification
74
+ return
75
+ end
76
+
77
+ anything_done = [
78
+ perform_removals,
79
+ perform_additions,
80
+ ].any?
81
+
82
+ if anything_done
83
+ say("All operations performed in working directory.", [:green, :bold])
84
+ say("Please review changes and commit them.", [:green, :bold])
85
+ else
86
+ say("No operations performed, all RBIs are up-to-date.", [:green, :bold])
87
+ end
88
+
89
+ puts
90
+ end
91
+
92
+ private
93
+
94
+ sig { returns(Loader) }
95
+ def loader
96
+ @loader ||= Loader.new
97
+ end
98
+
99
+ sig { returns(Gemfile) }
100
+ def bundle
101
+ @bundle ||= Gemfile.new
102
+ end
103
+
104
+ sig { void }
105
+ def require_gem_file
106
+ say("Requiring all gems to prepare for compiling... ")
107
+ begin
108
+ loader.load_bundle(bundle, @prerequire, @postrequire)
109
+ rescue LoadError => e
110
+ explain_failed_require(@postrequire, e)
111
+ exit(1)
112
+ end
113
+ say(" Done", :green)
114
+ unless bundle.missing_specs.empty?
115
+ say(" completed with missing specs: ")
116
+ say(bundle.missing_specs.join(", "), :yellow)
117
+ end
118
+ puts
119
+ end
120
+
121
+ sig { params(gem_names: T::Array[String]).returns(T::Array[Gemfile::GemSpec]) }
122
+ def gems_to_generate(gem_names)
123
+ return bundle.dependencies if gem_names.empty?
124
+
125
+ gem_names.map do |gem_name|
126
+ gem = bundle.gem(gem_name)
127
+ if gem.nil?
128
+ say("Error: Cannot find gem '#{gem_name}'", :red)
129
+ exit(1)
130
+ end
131
+ gem
132
+ end
133
+ end
134
+
135
+ sig { params(gem: Gemfile::GemSpec).void }
136
+ def compile_gem_rbi(gem)
137
+ gem_name = set_color(gem.name, :yellow, :bold)
138
+ say("Compiling #{gem_name}, this may take a few seconds... ")
139
+
140
+ rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
141
+ rbi.set_file_header(
142
+ "#{@default_command} gem #{gem.name}",
143
+ reason: "types exported from the `#{gem.name}` gem",
144
+ display_heading: @file_header
145
+ )
146
+
147
+ Compilers::SymbolTableCompiler.new.compile(gem, rbi, 0, @doc)
148
+
149
+ if rbi.empty?
150
+ rbi.set_empty_body_content
151
+ say("Done (empty output)", :yellow)
152
+ else
153
+ say("Done", :green)
154
+ end
155
+
156
+ create_file(@outpath / gem.rbi_file_name, rbi.transformed_string)
157
+
158
+ T.unsafe(Pathname).glob((@outpath / "#{gem.name}@*.rbi").to_s) do |file|
159
+ remove(file) unless file.basename.to_s == gem.rbi_file_name
160
+ end
161
+ end
162
+
163
+ sig { void }
164
+ def perform_sync_verification
165
+ diff = {}
166
+
167
+ removed_rbis.each do |gem_name|
168
+ filename = existing_rbi(gem_name)
169
+ diff[filename] = :removed
170
+ end
171
+
172
+ added_rbis.each do |gem_name|
173
+ filename = expected_rbi(gem_name)
174
+ diff[filename] = gem_rbi_exists?(gem_name) ? :changed : :added
175
+ end
176
+
177
+ report_diff_and_exit_if_out_of_date(diff, "gem")
178
+ end
179
+
180
+ sig { void }
181
+ def perform_removals
182
+ say("Removing RBI files of gems that have been removed:", [:blue, :bold])
183
+ puts
184
+
185
+ anything_done = T.let(false, T::Boolean)
186
+
187
+ gems = removed_rbis
188
+
189
+ shell.indent do
190
+ if gems.empty?
191
+ say("Nothing to do.")
192
+ else
193
+ gems.each do |removed|
194
+ filename = existing_rbi(removed)
195
+ remove(filename)
196
+ end
197
+
198
+ anything_done = true
199
+ end
200
+ end
201
+
202
+ puts
203
+
204
+ anything_done
205
+ end
206
+
207
+ sig { void }
208
+ def perform_additions
209
+ say("Generating RBI files of gems that are added or updated:", [:blue, :bold])
210
+ puts
211
+
212
+ anything_done = T.let(false, T::Boolean)
213
+
214
+ gems = added_rbis
215
+
216
+ shell.indent do
217
+ if gems.empty?
218
+ say("Nothing to do.")
219
+ else
220
+ require_gem_file
221
+
222
+ gems.each do |gem_name|
223
+ filename = expected_rbi(gem_name)
224
+
225
+ if gem_rbi_exists?(gem_name)
226
+ old_filename = existing_rbi(gem_name)
227
+ move(old_filename, filename) unless old_filename == filename
228
+ end
229
+
230
+ gem = T.must(bundle.gem(gem_name))
231
+ compile_gem_rbi(gem)
232
+ puts
233
+ end
234
+ end
235
+
236
+ anything_done = true
237
+ end
238
+
239
+ puts
240
+
241
+ anything_done
242
+ end
243
+
244
+ sig { params(file: String, error: LoadError).void }
245
+ def explain_failed_require(file, error)
246
+ say_error("\n\nLoadError: #{error}", :bold, :red)
247
+ say_error("\nTapioca could not load all the gems required by your application.", :yellow)
248
+ say_error("If you populated ", :yellow)
249
+ say_error("#{file} ", :bold, :blue)
250
+ say_error("with ", :yellow)
251
+ say_error("`#{@default_command} require`", :bold, :blue)
252
+ say_error("you should probably review it and remove the faulty line.", :yellow)
253
+ end
254
+
255
+ sig { params(filename: Pathname).void }
256
+ def remove(filename)
257
+ return unless filename.exist?
258
+ say("-- Removing: #{filename}")
259
+ filename.unlink
260
+ end
261
+
262
+ sig { returns(T::Array[String]) }
263
+ def removed_rbis
264
+ (existing_rbis.keys - expected_rbis.keys).sort
265
+ end
266
+
267
+ sig { params(gem_name: String).returns(Pathname) }
268
+ def existing_rbi(gem_name)
269
+ gem_rbi_filename(gem_name, T.must(existing_rbis[gem_name]))
270
+ end
271
+
272
+ sig { returns(T::Array[String]) }
273
+ def added_rbis
274
+ expected_rbis.select do |name, value|
275
+ existing_rbis[name] != value
276
+ end.keys.sort
277
+ end
278
+
279
+ sig { params(gem_name: String).returns(Pathname) }
280
+ def expected_rbi(gem_name)
281
+ gem_rbi_filename(gem_name, T.must(expected_rbis[gem_name]))
282
+ end
283
+
284
+ sig { params(gem_name: String).returns(T::Boolean) }
285
+ def gem_rbi_exists?(gem_name)
286
+ existing_rbis.key?(gem_name)
287
+ end
288
+
289
+ sig { params(diff: T::Hash[String, Symbol], command: String).void }
290
+ def report_diff_and_exit_if_out_of_date(diff, command)
291
+ if diff.empty?
292
+ say("Nothing to do, all RBIs are up-to-date.")
293
+ else
294
+ say("RBI files are out-of-date. In your development environment, please run:", :green)
295
+ say(" `#{Config::DEFAULT_COMMAND} #{command}`", [:green, :bold])
296
+ say("Once it is complete, be sure to commit and push any changes", :green)
297
+
298
+ say("")
299
+
300
+ say("Reason:", [:red])
301
+ diff.group_by(&:last).sort.each do |cause, diff_for_cause|
302
+ say(build_error_for_files(cause, diff_for_cause.map(&:first)))
303
+ end
304
+
305
+ exit(1)
306
+ end
307
+ end
308
+
309
+ sig { params(old_filename: Pathname, new_filename: Pathname).void }
310
+ def move(old_filename, new_filename)
311
+ say("-> Moving: #{old_filename} to #{new_filename}")
312
+ old_filename.rename(new_filename.to_s)
313
+ end
314
+
315
+ sig { returns(T::Hash[String, String]) }
316
+ def existing_rbis
317
+ @existing_rbis ||= Pathname.glob((@outpath / "*@*.rbi").to_s)
318
+ .map { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
319
+ .to_h
320
+ end
321
+
322
+ sig { returns(T::Hash[String, String]) }
323
+ def expected_rbis
324
+ @expected_rbis ||= bundle.dependencies
325
+ .reject { |gem| @gem_excludes.include?(gem.name) }
326
+ .map { |gem| [gem.name, gem.version.to_s] }
327
+ .to_h
328
+ end
329
+
330
+ sig { params(gem_name: String, version: String).returns(Pathname) }
331
+ def gem_rbi_filename(gem_name, version)
332
+ @outpath / "#{gem_name}@#{version}.rbi"
333
+ end
334
+
335
+ sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
336
+ def build_error_for_files(cause, files)
337
+ filenames = files.map do |file|
338
+ @outpath / file
339
+ end.join("\n - ")
340
+
341
+ " File(s) #{cause}:\n - #{filenames}"
342
+ end
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Generators
6
+ class Init < Base
7
+ sig do
8
+ params(
9
+ sorbet_config: String,
10
+ default_postrequire: String,
11
+ default_command: String,
12
+ file_writer: Thor::Actions
13
+ ).void
14
+ end
15
+ def initialize(sorbet_config:, default_postrequire:, default_command:, file_writer: FileWriter.new)
16
+ @sorbet_config = sorbet_config
17
+ @default_postrequire = default_postrequire
18
+
19
+ super(default_command: default_command, file_writer: file_writer)
20
+
21
+ @installer = T.let(nil, T.nilable(Bundler::Installer))
22
+ @spec = T.let(nil, T.nilable(Bundler::StubSpecification))
23
+ end
24
+
25
+ sig { override.void }
26
+ def generate
27
+ create_config
28
+ create_post_require
29
+ if File.exist?(@default_command)
30
+ generate_binstub!
31
+ else
32
+ generate_binstub
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ sig { void }
39
+ def create_config
40
+ create_file(@sorbet_config, <<~CONTENT, skip: true, force: false)
41
+ --dir
42
+ .
43
+ CONTENT
44
+ end
45
+
46
+ sig { void }
47
+ def create_post_require
48
+ create_file(@default_postrequire, <<~CONTENT, skip: true, force: false)
49
+ # typed: true
50
+ # frozen_string_literal: true
51
+
52
+ # Add your extra requires here (`#{@default_command} require` can be used to boostrap this list)
53
+ CONTENT
54
+ end
55
+
56
+ sig { void }
57
+ def generate_binstub!
58
+ installer.generate_bundler_executable_stubs(spec, { force: true })
59
+ say_status(:force, @default_command, :yellow)
60
+ end
61
+
62
+ sig { void }
63
+ def generate_binstub
64
+ installer.generate_bundler_executable_stubs(spec)
65
+ say_status(:create, @default_command, :green)
66
+ end
67
+
68
+ sig { returns(Bundler::Installer) }
69
+ def installer
70
+ @installer ||= Bundler::Installer.new(Bundler.root, Bundler.definition)
71
+ end
72
+
73
+ sig { returns(Bundler::StubSpecification) }
74
+ def spec
75
+ @spec ||= Bundler.definition.specs.find { |s| s.name == "tapioca" }
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Generators
6
+ class Require < Base
7
+ sig do
8
+ params(
9
+ requires_path: String,
10
+ sorbet_config_path: String,
11
+ default_command: String,
12
+ file_writer: Thor::Actions
13
+ ).void
14
+ end
15
+ def initialize(requires_path:, sorbet_config_path:, default_command:, file_writer: FileWriter.new)
16
+ @requires_path = requires_path
17
+ @sorbet_config_path = sorbet_config_path
18
+
19
+ super(default_command: default_command, file_writer: file_writer)
20
+ end
21
+
22
+ sig { override.void }
23
+ def generate
24
+ compiler = Compilers::RequiresCompiler.new(@sorbet_config_path)
25
+ name = set_color(@requires_path, :yellow, :bold)
26
+ say("Compiling #{name}, this may take a few seconds... ")
27
+
28
+ rb_string = compiler.compile
29
+ if rb_string.empty?
30
+ say("Nothing to do", :green)
31
+ return
32
+ end
33
+
34
+ # Clean all existing requires before regenerating the list so we update
35
+ # it with the new one found in the client code and remove the old ones.
36
+ File.delete(@requires_path) if File.exist?(@requires_path)
37
+
38
+ content = +"# typed: true\n"
39
+ content << "# frozen_string_literal: true\n\n"
40
+ content << rb_string
41
+
42
+ create_file(@requires_path, content, verbose: false)
43
+
44
+ say("Done", :green)
45
+
46
+ say("All requires from this application have been written to #{name}.", [:green, :bold])
47
+ cmd = set_color("#{@default_command} gem", :yellow, :bold)
48
+ say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold])
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Generators
6
+ class Todo < Base
7
+ sig do
8
+ params(
9
+ todos_path: String,
10
+ file_header: T::Boolean,
11
+ default_command: String,
12
+ file_writer: Thor::Actions
13
+ ).void
14
+ end
15
+ def initialize(todos_path:, file_header:, default_command:, file_writer: FileWriter.new)
16
+ @todos_path = todos_path
17
+ @file_header = file_header
18
+
19
+ super(default_command: default_command, file_writer: file_writer)
20
+ end
21
+
22
+ sig { override.void }
23
+ def generate
24
+ compiler = Compilers::TodosCompiler.new
25
+ say("Finding all unresolved constants, this may take a few seconds... ")
26
+
27
+ # Clean all existing unresolved constants before regenerating the list
28
+ # so Sorbet won't grab them as already resolved.
29
+ File.delete(@todos_path) if File.exist?(@todos_path)
30
+
31
+ rbi_string = compiler.compile
32
+ if rbi_string.empty?
33
+ say("Nothing to do", :green)
34
+ return
35
+ end
36
+
37
+ content = String.new
38
+ content << rbi_header(
39
+ "#{@default_command} todo",
40
+ reason: "unresolved constants",
41
+ strictness: "false"
42
+ )
43
+ content << rbi_string
44
+ content << "\n"
45
+
46
+ say("Done", :green)
47
+ create_file(@todos_path, content, verbose: false)
48
+
49
+ name = set_color(@todos_path, :yellow, :bold)
50
+ say("\nAll unresolved constants have been written to #{name}.", [:green, :bold])
51
+ say("Please review changes and commit them.", [:green, :bold])
52
+ end
53
+
54
+ sig { params(command: String, reason: T.nilable(String), strictness: T.nilable(String)).returns(String) }
55
+ def rbi_header(command, reason: nil, strictness: nil)
56
+ statement = <<~HEAD
57
+ # DO NOT EDIT MANUALLY
58
+ # This is an autogenerated file for #{reason}.
59
+ # Please instead update this file by running `#{command}`.
60
+ HEAD
61
+
62
+ sigil = <<~SIGIL if strictness
63
+ # typed: #{strictness}
64
+ SIGIL
65
+
66
+ if @file_header
67
+ [statement, sigil].compact.join("\n").strip.concat("\n\n")
68
+ elsif sigil
69
+ sigil.strip.concat("\n\n")
70
+ else
71
+ ""
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,9 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "generators/base"
5
+ require_relative "generators/dsl"
6
+ require_relative "generators/init"
7
+ require_relative "generators/gem"
8
+ require_relative "generators/require"
9
+ require_relative "generators/todo"
@@ -3,13 +3,12 @@
3
3
 
4
4
  require "tapioca"
5
5
  require "tapioca/loader"
6
- require "tapioca/constant_locator"
7
6
  require "tapioca/sorbet_ext/generic_name_patch"
8
7
  require "tapioca/sorbet_ext/fixed_hash_patch"
9
8
  require "tapioca/generic_type_registry"
10
9
  require "tapioca/config"
11
10
  require "tapioca/config_builder"
12
- require "tapioca/generator"
11
+ require "tapioca/generators"
13
12
  require "tapioca/cli"
14
13
  require "tapioca/gemfile"
15
14
  require "tapioca/compilers/sorbet"
@@ -9,10 +9,10 @@ module Tapioca
9
9
  def load_bundle(gemfile, initialize_file, require_file)
10
10
  require_helper(initialize_file)
11
11
 
12
- gemfile.require_bundle
13
-
14
12
  load_rails_application
15
13
 
14
+ gemfile.require_bundle
15
+
16
16
  require_helper(require_file)
17
17
 
18
18
  load_rails_engines
@@ -4,6 +4,50 @@
4
4
  require "rbi"
5
5
 
6
6
  module RBI
7
+ class File
8
+ extend T::Sig
9
+
10
+ sig { returns(String) }
11
+ def transformed_string
12
+ transform_rbi!
13
+ string
14
+ end
15
+
16
+ sig { void }
17
+ def transform_rbi!
18
+ root.nest_singleton_methods!
19
+ root.nest_non_public_methods!
20
+ root.group_nodes!
21
+ root.sort_nodes!
22
+ end
23
+
24
+ sig do
25
+ params(
26
+ command: String,
27
+ reason: T.nilable(String),
28
+ display_heading: T::Boolean
29
+ ).void
30
+ end
31
+ def set_file_header(command, reason: nil, display_heading: true)
32
+ return unless display_heading
33
+ comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
34
+ comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
35
+ comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
36
+ end
37
+
38
+ sig { void }
39
+ def set_empty_body_content
40
+ comments << RBI::BlankLine.new unless comments.empty?
41
+ comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
42
+ comments << RBI::Comment.new("see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires")
43
+ end
44
+
45
+ sig { returns(T::Boolean) }
46
+ def empty?
47
+ root.empty?
48
+ end
49
+ end
50
+
7
51
  class Tree
8
52
  extend T::Sig
9
53
 
@@ -17,6 +17,7 @@ module Tapioca
17
17
  PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
18
18
  PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
19
19
  PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
20
+ METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
20
21
 
21
22
  sig { params(object: BasicObject).returns(Class).checked(:never) }
22
23
  def class_of(object)
@@ -30,7 +31,8 @@ module Tapioca
30
31
 
31
32
  sig { params(constant: Module).returns(T.nilable(String)) }
32
33
  def name_of(constant)
33
- NAME_METHOD.bind(constant).call
34
+ name = NAME_METHOD.bind(constant).call
35
+ name&.start_with?("#<") ? nil : name
34
36
  end
35
37
 
36
38
  sig { params(constant: Module).returns(Class) }
@@ -106,6 +108,11 @@ module Tapioca
106
108
  type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
107
109
  end
108
110
 
111
+ sig { params(constant: Module, method: Symbol).returns(Method) }
112
+ def method_of(constant, method)
113
+ METHOD_METHOD.bind(constant).call(method)
114
+ end
115
+
109
116
  # Returns an array with all classes that are < than the supplied class.
110
117
  #
111
118
  # class C; end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.5.1"
5
+ VERSION = "0.5.5"
6
6
  end
data/lib/tapioca.rb CHANGED
@@ -18,6 +18,8 @@ module Tapioca
18
18
  end
19
19
 
20
20
  require "tapioca/reflection"
21
+ require "tapioca/constant_locator"
21
22
  require "tapioca/compilers/dsl/base"
23
+ require "tapioca/compilers/dynamic_mixin_compiler"
22
24
  require "tapioca/helpers/active_record_column_type_helper"
23
25
  require "tapioca/version"