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,74 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_support/core_ext/class/attribute"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Compilers
12
+ module Dsl
13
+ # `Tapioca::Compilers::Dsl::MixedInClassAttributes` generates RBI files for modules that dynamically use
14
+ # `class_attribute` on classes.
15
+ #
16
+ # For example, given the following concern
17
+ #
18
+ # ~~~rb
19
+ # module Taggeable
20
+ # extend ActiveSupport::Concern
21
+ #
22
+ # included do
23
+ # class_attribute :tag
24
+ # end
25
+ # end
26
+ # ~~~
27
+ #
28
+ # this generator will produce the RBI file `taggeable.rbi` with the following content:
29
+ #
30
+ # ~~~rbi
31
+ # # typed: strong
32
+ #
33
+ # module Taggeable
34
+ # include GeneratedInstanceMethods
35
+ #
36
+ # mixes_in_class_methods GeneratedClassMethods
37
+ #
38
+ # module GeneratedClassMethods
39
+ # def tag; end
40
+ # def tag=(value); end
41
+ # def tag?; end
42
+ # end
43
+ #
44
+ # module GeneratedInstanceMethods
45
+ # def tag; end
46
+ # def tag=(value); end
47
+ # def tag?; end
48
+ # end
49
+ # end
50
+ # ~~~
51
+ class MixedInClassAttributes < Base
52
+ extend T::Sig
53
+
54
+ sig { override.params(root: RBI::Tree, constant: Module).void }
55
+ def decorate(root, constant)
56
+ mixin_compiler = DynamicMixinCompiler.new(constant)
57
+ return if mixin_compiler.empty_attributes?
58
+
59
+ root.create_path(constant) do |mod|
60
+ mixin_compiler.compile_class_attributes(mod)
61
+ end
62
+ end
63
+
64
+ sig { override.returns(T::Enumerable[Module]) }
65
+ def gather_constants
66
+ # Select all non-anonymous modules that have overridden Module.included
67
+ all_modules.select do |mod|
68
+ !mod.is_a?(Class) && name_of(mod) && Tapioca::Reflection.method_of(mod, :included).owner != Module
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -84,10 +84,10 @@ module Tapioca
84
84
 
85
85
  sig { override.returns(T::Enumerable[Module]) }
86
86
  def gather_constants
87
- all_classes.select do |c|
88
- c < ::SmartProperties
89
- end.reject do |c|
90
- name_of(c).nil? || c == ::SmartProperties::Validations::Ancestor
87
+ all_modules.select do |c|
88
+ name_of(c) &&
89
+ c != ::SmartProperties::Validations::Ancestor &&
90
+ c < ::SmartProperties && ::SmartProperties::ClassMethods === c
91
91
  end
92
92
  end
93
93
 
@@ -34,7 +34,7 @@ module Tapioca
34
34
  @error_handler = T.let(error_handler || $stderr.method(:puts), T.proc.params(error: String).void)
35
35
  end
36
36
 
37
- sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
37
+ sig { params(blk: T.proc.params(constant: Module, rbi: RBI::File).void).void }
38
38
  def run(&blk)
39
39
  constants_to_process = gather_constants(requested_constants)
40
40
 
@@ -51,6 +51,10 @@ module Tapioca
51
51
 
52
52
  blk.call(constant, rbi)
53
53
  end
54
+
55
+ generators.flat_map(&:errors).each do |msg|
56
+ report_error(msg)
57
+ end
54
58
  end
55
59
 
56
60
  private
@@ -77,7 +81,7 @@ module Tapioca
77
81
  constants
78
82
  end
79
83
 
80
- sig { params(constant: Module).returns(T.nilable(String)) }
84
+ sig { params(constant: Module).returns(T.nilable(RBI::File)) }
81
85
  def rbi_for_constant(constant)
82
86
  file = RBI::File.new(strictness: "true")
83
87
 
@@ -88,10 +92,7 @@ module Tapioca
88
92
 
89
93
  return if file.root.empty?
90
94
 
91
- file.root.nest_non_public_methods!
92
- file.root.group_nodes!
93
- file.root.sort_nodes!
94
- file.string
95
+ file
95
96
  end
96
97
 
97
98
  sig { params(error: String).returns(T.noreturn) }
@@ -0,0 +1,198 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ class DynamicMixinCompiler
5
+ extend T::Sig
6
+ include Tapioca::Reflection
7
+
8
+ sig { returns(T::Array[Module]) }
9
+ attr_reader :dynamic_extends, :dynamic_includes
10
+
11
+ sig { returns(T::Array[Symbol]) }
12
+ attr_reader :class_attribute_readers, :class_attribute_writers, :class_attribute_predicates
13
+
14
+ sig { returns(T::Array[Symbol]) }
15
+ attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
16
+
17
+ sig { params(constant: Module).void }
18
+ def initialize(constant)
19
+ @constant = constant
20
+ mixins_from_modules = {}.compare_by_identity
21
+ class_attribute_readers = T.let([], T::Array[Symbol])
22
+ class_attribute_writers = T.let([], T::Array[Symbol])
23
+ class_attribute_predicates = T.let([], T::Array[Symbol])
24
+
25
+ instance_attribute_readers = T.let([], T::Array[Symbol])
26
+ instance_attribute_writers = T.let([], T::Array[Symbol])
27
+ instance_attribute_predicates = T.let([], T::Array[Symbol])
28
+
29
+ Class.new do
30
+ # Override the `self.include` method
31
+ define_singleton_method(:include) do |mod|
32
+ # Take a snapshot of the list of singleton class ancestors
33
+ # before the actual include
34
+ before = singleton_class.ancestors
35
+ # Call the actual `include` method with the supplied module
36
+ super(mod).tap do
37
+ # Take a snapshot of the list of singleton class ancestors
38
+ # after the actual include
39
+ after = singleton_class.ancestors
40
+ # The difference is the modules that are added to the list
41
+ # of ancestors of the singleton class. Those are all the
42
+ # modules that were `extend`ed due to the `include` call.
43
+ #
44
+ # We record those modules on our lookup table keyed by
45
+ # the included module with the values being all the modules
46
+ # that that module pulls into the singleton class.
47
+ #
48
+ # We need to reverse the order, since the extend order should
49
+ # be the inverse of the ancestor order. That is, earlier
50
+ # extended modules would be later in the ancestor chain.
51
+ mixins_from_modules[mod] = (after - before).reverse!
52
+ end
53
+ rescue Exception # rubocop:disable Lint/RescueException
54
+ # this is a best effort, bail if we can't perform this
55
+ end
56
+
57
+ define_singleton_method(:class_attribute) do |*attrs, **kwargs|
58
+ class_attribute_readers.concat(attrs)
59
+ class_attribute_writers.concat(attrs)
60
+
61
+ instance_predicate = kwargs.fetch(:instance_predicate, true)
62
+ instance_accessor = kwargs.fetch(:instance_accessor, true)
63
+ instance_reader = kwargs.fetch(:instance_reader, instance_accessor)
64
+ instance_writer = kwargs.fetch(:instance_writer, instance_accessor)
65
+
66
+ if instance_reader
67
+ instance_attribute_readers.concat(attrs)
68
+ end
69
+
70
+ if instance_writer
71
+ instance_attribute_writers.concat(attrs)
72
+ end
73
+
74
+ if instance_predicate
75
+ class_attribute_predicates.concat(attrs)
76
+
77
+ if instance_reader
78
+ instance_attribute_predicates.concat(attrs)
79
+ end
80
+ end
81
+
82
+ super(*attrs, **kwargs) if defined?(super)
83
+ end
84
+
85
+ # rubocop:disable Style/MissingRespondToMissing
86
+ T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
87
+ def method_missing(symbol, *args)
88
+ # We need this here so that we can handle any random instance
89
+ # method calls on the fake including class that may be done by
90
+ # the included module during the `self.included` hook.
91
+ end
92
+
93
+ class << self
94
+ extend T::Sig
95
+
96
+ T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
97
+ def method_missing(symbol, *args)
98
+ # Similarly, we need this here so that we can handle any
99
+ # random class method calls on the fake including class
100
+ # that may be done by the included module during the
101
+ # `self.included` hook.
102
+ end
103
+ end
104
+ # rubocop:enable Style/MissingRespondToMissing
105
+ end.include(constant)
106
+
107
+ # The value that corresponds to the original included constant
108
+ # is the list of all dynamically extended modules because of that
109
+ # constant. We grab that value by deleting the key for the original
110
+ # constant.
111
+ @dynamic_extends = T.let(mixins_from_modules.delete(constant) || [], T::Array[Module])
112
+
113
+ # Since we deleted the original constant from the list of keys, all
114
+ # the keys that remain are the ones that are dynamically included modules
115
+ # during the include of the original constant.
116
+ @dynamic_includes = T.let(mixins_from_modules.keys, T::Array[Module])
117
+
118
+ @class_attribute_readers = T.let(class_attribute_readers, T::Array[Symbol])
119
+ @class_attribute_writers = T.let(class_attribute_writers, T::Array[Symbol])
120
+ @class_attribute_predicates = T.let(class_attribute_predicates, T::Array[Symbol])
121
+
122
+ @instance_attribute_readers = T.let(instance_attribute_readers, T::Array[Symbol])
123
+ @instance_attribute_writers = T.let(instance_attribute_writers, T::Array[Symbol])
124
+ @instance_attribute_predicates = T.let(instance_attribute_predicates, T::Array[Symbol])
125
+ end
126
+
127
+ sig { returns(T::Boolean) }
128
+ def empty_attributes?
129
+ @class_attribute_readers.empty? && @class_attribute_writers.empty?
130
+ end
131
+
132
+ sig { params(tree: RBI::Tree).void }
133
+ def compile_class_attributes(tree)
134
+ return if empty_attributes?
135
+
136
+ # Create a synthetic module to hold the generated class methods
137
+ tree << RBI::Module.new("GeneratedClassMethods") do |mod|
138
+ class_attribute_readers.each do |attribute|
139
+ mod << RBI::Method.new(attribute.to_s)
140
+ end
141
+
142
+ class_attribute_writers.each do |attribute|
143
+ mod << RBI::Method.new("#{attribute}=") do |method|
144
+ method << RBI::Param.new("value")
145
+ end
146
+ end
147
+
148
+ class_attribute_predicates.each do |attribute|
149
+ mod << RBI::Method.new("#{attribute}?")
150
+ end
151
+ end
152
+
153
+ # Create a synthetic module to hold the generated instance methods
154
+ tree << RBI::Module.new("GeneratedInstanceMethods") do |mod|
155
+ instance_attribute_readers.each do |attribute|
156
+ mod << RBI::Method.new(attribute.to_s)
157
+ end
158
+
159
+ instance_attribute_writers.each do |attribute|
160
+ mod << RBI::Method.new("#{attribute}=") do |method|
161
+ method << RBI::Param.new("value")
162
+ end
163
+ end
164
+
165
+ instance_attribute_predicates.each do |attribute|
166
+ mod << RBI::Method.new("#{attribute}?")
167
+ end
168
+ end
169
+
170
+ # Add a mixes_in_class_methods and include for the generated modules
171
+ tree << RBI::MixesInClassMethods.new("GeneratedClassMethods")
172
+ tree << RBI::Include.new("GeneratedInstanceMethods")
173
+ end
174
+
175
+ sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
176
+ def compile_mixes_in_class_methods(tree)
177
+ includes = dynamic_includes.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
178
+ includes.each do |mod|
179
+ qname = qualified_name_of(mod)
180
+ tree << RBI::Include.new(T.must(qname))
181
+ end
182
+
183
+ # If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the
184
+ # constant itself
185
+ mixed_in_class_methods = dynamic_extends.select { |mod| mod != @constant }
186
+ return [[], []] if mixed_in_class_methods.empty?
187
+
188
+ mixed_in_class_methods.each do |mod|
189
+ qualified_name = qualified_name_of(mod)
190
+ next if qualified_name.nil? || qualified_name.empty?
191
+ tree << RBI::MixesInClassMethods.new(qualified_name)
192
+ end
193
+
194
+ [mixed_in_class_methods, includes]
195
+ rescue
196
+ [[], []] # silence errors
197
+ end
198
+ end
@@ -18,7 +18,6 @@ module Tapioca
18
18
  EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
19
19
 
20
20
  FEATURE_REQUIREMENTS = T.let({
21
- mixes_in_class_methods_multiple_args: Gem::Requirement.new("> 0.5.6200"),
22
21
  }.freeze, T::Hash[Symbol, Gem::Requirement])
23
22
 
24
23
  class << self
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "pathname"
@@ -10,36 +10,47 @@ module Tapioca
10
10
  extend(T::Sig)
11
11
  include(Reflection)
12
12
 
13
- IGNORED_SYMBOLS = ["YAML", "MiniTest", "Mutex"]
14
-
15
- attr_reader(:gem, :indent)
16
-
17
- sig { params(gem: Gemfile::GemSpec, indent: Integer).void }
18
- def initialize(gem, indent = 0)
13
+ IGNORED_SYMBOLS = T.let(["YAML", "MiniTest", "Mutex"], T::Array[String])
14
+ IGNORED_COMMENTS = T.let([
15
+ ":doc:",
16
+ ":nodoc:",
17
+ "typed:",
18
+ "frozen_string_literal:",
19
+ "encoding:",
20
+ "warn_indent:",
21
+ "shareable_constant_value:",
22
+ "rubocop:",
23
+ ], T::Array[String])
24
+
25
+ sig { returns(Gemfile::GemSpec) }
26
+ attr_reader :gem
27
+
28
+ sig { returns(Integer) }
29
+ attr_reader :indent
30
+
31
+ sig { params(gem: Gemfile::GemSpec, indent: Integer, include_doc: T::Boolean).void }
32
+ def initialize(gem, indent = 0, include_doc = false)
19
33
  @gem = gem
20
34
  @indent = indent
21
- @seen = Set.new
22
- @alias_namespace ||= Set.new
35
+ @seen = T.let(Set.new, T::Set[String])
36
+ @alias_namespace = T.let(Set.new, T::Set[String])
23
37
  @symbol_queue = T.let(symbols.sort.dup, T::Array[String])
24
- end
25
-
26
- sig { returns(String) }
27
- def generate
28
- rbi = RBI::Tree.new
38
+ @symbols = T.let(nil, T.nilable(T::Set[String]))
39
+ @include_doc = include_doc
29
40
 
30
- generate_from_symbol(rbi, T.must(@symbol_queue.shift)) until @symbol_queue.empty?
41
+ gem.parse_yard_docs if include_doc
42
+ end
31
43
 
32
- rbi.nest_singleton_methods!
33
- rbi.nest_non_public_methods!
34
- rbi.group_nodes!
35
- rbi.sort_nodes!
36
- rbi.string
44
+ sig { params(rbi: RBI::File).void }
45
+ def generate(rbi)
46
+ generate_from_symbol(rbi.root, T.must(@symbol_queue.shift)) until @symbol_queue.empty?
37
47
  end
38
48
 
39
49
  private
40
50
 
51
+ sig { params(name: T.nilable(String)).void }
41
52
  def add_to_symbol_queue(name)
42
- @symbol_queue << name unless symbols.include?(name) || symbol_ignored?(name)
53
+ @symbol_queue << name unless name.nil? || symbols.include?(name) || symbol_ignored?(name)
43
54
  end
44
55
 
45
56
  sig { returns(T::Set[String]) }
@@ -154,28 +165,33 @@ module Tapioca
154
165
  name_of(klass)
155
166
  end
156
167
 
168
+ comments = documentation_comments(name)
169
+
157
170
  if klass_name == "T::Private::Types::TypeAlias"
158
- tree << RBI::Const.new(name, "T.type_alias { #{T.unsafe(value).aliased_type} }")
171
+ constant = RBI::Const.new(name, "T.type_alias { #{T.unsafe(value).aliased_type} }", comments: comments)
172
+ tree << constant
159
173
  return
160
174
  end
161
175
 
162
176
  return if klass_name&.start_with?("T::Types::", "T::Private::")
163
177
 
164
178
  type_name = klass_name || "T.untyped"
179
+ constant = RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})", comments: comments)
165
180
 
166
- tree << RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})")
181
+ tree << constant
167
182
  end
168
183
 
169
184
  sig { params(tree: RBI::Tree, name: String, constant: Module).void }
170
185
  def compile_module(tree, name, constant)
171
186
  return unless defined_in_gem?(constant, strict: false)
172
187
 
188
+ comments = documentation_comments(name)
173
189
  scope =
174
190
  if constant.is_a?(Class)
175
191
  superclass = compile_superclass(constant)
176
- RBI::Class.new(name, superclass_name: superclass)
192
+ RBI::Class.new(name, superclass_name: superclass, comments: comments)
177
193
  else
178
- RBI::Module.new(name)
194
+ RBI::Module.new(name, comments: comments)
179
195
  end
180
196
 
181
197
  compile_body(scope, name, constant)
@@ -193,9 +209,22 @@ module Tapioca
193
209
  compile_methods(tree, name, constant)
194
210
  compile_module_helpers(tree, constant)
195
211
  compile_mixins(tree, constant)
196
- compile_mixes_in_class_methods(tree, constant)
197
212
  compile_props(tree, constant)
198
213
  compile_enums(tree, constant)
214
+ compile_dynamic_mixins(tree, constant)
215
+ end
216
+
217
+ sig { params(tree: RBI::Tree, constant: Module).void }
218
+ def compile_dynamic_mixins(tree, constant)
219
+ return if constant.is_a?(Class)
220
+
221
+ mixin_compiler = DynamicMixinCompiler.new(constant)
222
+ mixin_compiler.compile_class_attributes(tree)
223
+ dynamic_extends, dynamic_includes = mixin_compiler.compile_mixes_in_class_methods(tree)
224
+
225
+ (dynamic_includes + dynamic_extends).each do |mod|
226
+ add_to_symbol_queue(name_of(mod))
227
+ end
199
228
  end
200
229
 
201
230
  sig { params(tree: RBI::Tree, constant: Module).void }
@@ -391,130 +420,6 @@ module Tapioca
391
420
  end
392
421
  end
393
422
 
394
- sig { params(constant: Module).returns([T::Array[Module], T::Array[Module]]) }
395
- def collect_dynamic_mixins_of(constant)
396
- mixins_from_modules = {}.compare_by_identity
397
-
398
- Class.new do
399
- # Override the `self.include` method
400
- define_singleton_method(:include) do |mod|
401
- # Take a snapshot of the list of singleton class ancestors
402
- # before the actual include
403
- before = singleton_class.ancestors
404
- # Call the actual `include` method with the supplied module
405
- include_result = super(mod)
406
- # Take a snapshot of the list of singleton class ancestors
407
- # after the actual include
408
- after = singleton_class.ancestors
409
- # The difference is the modules that are added to the list
410
- # of ancestors of the singleton class. Those are all the
411
- # modules that were `extend`ed due to the `include` call.
412
- #
413
- # We record those modules on our lookup table keyed by
414
- # the included module with the values being all the modules
415
- # that that module pulls into the singleton class.
416
- #
417
- # We need to reverse the order, since the extend order should
418
- # be the inverse of the ancestor order. That is, earlier
419
- # extended modules would be later in the ancestor chain.
420
- mixins_from_modules[mod] = (after - before).reverse!
421
-
422
- include_result
423
- rescue Exception # rubocop:disable Lint/RescueException
424
- # this is a best effort, bail if we can't perform this
425
- end
426
-
427
- # rubocop:disable Style/MissingRespondToMissing
428
- def method_missing(symbol, *args)
429
- # We need this here so that we can handle any random instance
430
- # method calls on the fake including class that may be done by
431
- # the included module during the `self.included` hook.
432
- end
433
-
434
- class << self
435
- def method_missing(symbol, *args)
436
- # Similarly, we need this here so that we can handle any
437
- # random class method calls on the fake including class
438
- # that may be done by the included module during the
439
- # `self.included` hook.
440
- end
441
- end
442
- # rubocop:enable Style/MissingRespondToMissing
443
- end.include(constant)
444
-
445
- [
446
- # The value that corresponds to the original included constant
447
- # is the list of all dynamically extended modules because of that
448
- # constant. We grab that value by deleting the key for the original
449
- # constant.
450
- T.must(mixins_from_modules.delete(constant)),
451
- # Since we deleted the original constant from the list of keys, all
452
- # the keys that remain are the ones that are dynamically included modules
453
- # during the include of the original constant.
454
- mixins_from_modules.keys,
455
- ]
456
- end
457
-
458
- sig { params(constant: Module, dynamic_extends: T::Array[Module]).returns(T::Array[Module]) }
459
- def collect_mixed_in_class_methods(constant, dynamic_extends)
460
- if Tapioca::Compilers::Sorbet.supports?(:mixes_in_class_methods_multiple_args)
461
- # If we can generate multiple mixes_in_class_methods, then
462
- # we want to use all dynamic extends that are not the constant itself
463
- return dynamic_extends.select { |mod| mod != constant }
464
- end
465
-
466
- # For older Sorbet version, we do an explicit check for an AS::Concern
467
- # related ClassMethods module.
468
- ancestors = singleton_class_of(constant).ancestors
469
- extends_as_concern = ancestors.any? do |mod|
470
- qualified_name_of(mod) == "::ActiveSupport::Concern"
471
- end
472
- class_methods_module = resolve_constant("#{name_of(constant)}::ClassMethods")
473
-
474
- mixed_in_module = if extends_as_concern && Module === class_methods_module
475
- # If this module is a concern and the ClassMethods module exists
476
- # then, we prefer to generate a mixes_in_class_methods call for
477
- # that module only, since we only have a single shot.
478
- class_methods_module
479
- else
480
- # Otherwise, we use the first dynamic extend module that is not
481
- # the constant itself. We don't have a better heuristic in the
482
- # absence of being able to supply multiple arguments.
483
- dynamic_extends.find { |mod| mod != constant }
484
- end
485
-
486
- Array(mixed_in_module)
487
- end
488
-
489
- sig { params(tree: RBI::Tree, constant: Module).void }
490
- def compile_mixes_in_class_methods(tree, constant)
491
- return if constant.is_a?(Class)
492
-
493
- dynamic_extends, dynamic_includes = collect_dynamic_mixins_of(constant)
494
-
495
- dynamic_includes
496
- .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
497
- .map do |mod|
498
- add_to_symbol_queue(name_of(mod))
499
-
500
- qname = qualified_name_of(mod)
501
- tree << RBI::Include.new(T.must(qname))
502
- end
503
-
504
- mixed_in_class_methods = collect_mixed_in_class_methods(constant, dynamic_extends)
505
- return if mixed_in_class_methods.empty?
506
-
507
- mixed_in_class_methods.each do |mod|
508
- add_to_symbol_queue(name_of(mod))
509
-
510
- qualified_name = qualified_name_of(mod)
511
- next if qualified_name.nil? || qualified_name.empty?
512
- tree << RBI::MixesInClassMethods.new(qualified_name)
513
- end
514
- rescue
515
- nil # silence errors
516
- end
517
-
518
423
  sig { params(tree: RBI::Tree, name: String, constant: Module).void }
519
424
  def compile_methods(tree, name, constant)
520
425
  compile_method(
@@ -630,7 +535,15 @@ module Tapioca
630
535
  [type, name]
631
536
  end
632
537
 
633
- rbi_method = RBI::Method.new(method_name, is_singleton: constant.singleton_class?, visibility: visibility)
538
+ separator = constant.singleton_class? ? "." : "#"
539
+ comments = documentation_comments("#{symbol_name}#{separator}#{method_name}")
540
+ rbi_method = RBI::Method.new(
541
+ method_name,
542
+ is_singleton: constant.singleton_class?,
543
+ visibility: visibility,
544
+ comments: comments
545
+ )
546
+
634
547
  rbi_method.sigs << compile_signature(signature, sanitized_parameters) if signature
635
548
 
636
549
  sanitized_parameters.each do |type, name|
@@ -710,8 +623,10 @@ module Tapioca
710
623
  SymbolLoader.ignore_symbol?(symbol_name)
711
624
  end
712
625
 
713
- SPECIAL_METHOD_NAMES = ["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<",
714
- "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"]
626
+ SPECIAL_METHOD_NAMES = T.let([
627
+ "!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^",
628
+ "<", "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"
629
+ ], T::Array[String])
715
630
 
716
631
  sig { params(name: String).returns(T::Boolean) }
717
632
  def valid_method_name?(name)
@@ -770,6 +685,7 @@ module Tapioca
770
685
  @seen.include?(name)
771
686
  end
772
687
 
688
+ sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
773
689
  def initialize_method_for(constant)
774
690
  constant.instance_method(:initialize)
775
691
  rescue
@@ -840,6 +756,21 @@ module Tapioca
840
756
 
841
757
  name_of(target)
842
758
  end
759
+
760
+ sig { params(name: String).returns(T::Array[RBI::Comment]) }
761
+ def documentation_comments(name)
762
+ return [] unless @include_doc
763
+
764
+ yard_docs = YARD::Registry.at(name)
765
+ return [] unless yard_docs
766
+
767
+ docstring = yard_docs.docstring
768
+ return [] if /(copyright|license)/i.match?(docstring)
769
+
770
+ docstring.lines
771
+ .reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } }
772
+ .map! { |line| RBI::Comment.new(line) }
773
+ end
843
774
  end
844
775
  end
845
776
  end
@@ -6,17 +6,11 @@ module Tapioca
6
6
  class SymbolTableCompiler
7
7
  extend(T::Sig)
8
8
 
9
- sig do
10
- params(
11
- gem: Gemfile::GemSpec,
12
- indent: Integer
13
- ).returns(String)
14
- end
15
- def compile(
16
- gem,
17
- indent = 0
18
- )
19
- Tapioca::Compilers::SymbolTable::SymbolGenerator.new(gem, indent).generate
9
+ sig { params(gem: Gemfile::GemSpec, rbi: RBI::File, indent: Integer, include_docs: T::Boolean).void }
10
+ def compile(gem, rbi, indent = 0, include_docs = false)
11
+ Tapioca::Compilers::SymbolTable::SymbolGenerator
12
+ .new(gem, indent, include_docs)
13
+ .generate(rbi)
20
14
  end
21
15
  end
22
16
  end