tapioca 0.5.1 → 0.5.5

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 (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"