tapioca 0.6.1 → 0.7.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +13 -2
  3. data/README.md +79 -25
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +66 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +17 -10
  7. data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
  8. data/lib/tapioca/{generators → commands}/gem.rb +93 -30
  9. data/lib/tapioca/{generators → commands}/init.rb +9 -13
  10. data/lib/tapioca/{generators → commands}/require.rb +8 -10
  11. data/lib/tapioca/commands/todo.rb +84 -0
  12. data/lib/tapioca/commands.rb +13 -0
  13. data/lib/tapioca/dsl/compiler.rb +185 -0
  14. data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
  15. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
  16. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
  17. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
  18. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +32 -24
  19. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
  20. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +29 -35
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +26 -24
  22. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
  23. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +10 -8
  24. data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
  25. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
  26. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +12 -17
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +11 -11
  29. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
  30. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
  31. data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +10 -8
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +28 -25
  34. data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
  35. data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +10 -8
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +13 -14
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +12 -13
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +16 -14
  41. data/lib/tapioca/dsl/compilers.rb +31 -0
  42. data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
  43. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
  44. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
  45. data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +2 -2
  46. data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
  47. data/lib/tapioca/gem/events.rb +120 -0
  48. data/lib/tapioca/gem/listeners/base.rb +48 -0
  49. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
  50. data/lib/tapioca/gem/listeners/methods.rb +183 -0
  51. data/lib/tapioca/gem/listeners/mixins.rb +101 -0
  52. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
  53. data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
  54. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
  55. data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
  56. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
  57. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
  58. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
  59. data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
  60. data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
  61. data/lib/tapioca/gem/listeners.rb +16 -0
  62. data/lib/tapioca/gem/pipeline.rb +365 -0
  63. data/lib/tapioca/gemfile.rb +44 -20
  64. data/lib/tapioca/helpers/cli_helper.rb +16 -8
  65. data/lib/tapioca/helpers/config_helper.rb +113 -0
  66. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  67. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  68. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  69. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  70. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  71. data/lib/tapioca/helpers/test/template.rb +13 -2
  72. data/lib/tapioca/internal.rb +17 -10
  73. data/lib/tapioca/rbi_ext/model.rb +2 -48
  74. data/lib/tapioca/rbi_formatter.rb +37 -0
  75. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  76. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +153 -0
  79. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  80. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  81. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  82. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  83. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  84. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +110 -54
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +5 -12
  87. data/lib/tapioca/static/symbol_loader.rb +83 -0
  88. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  89. data/lib/tapioca/version.rb +1 -1
  90. data/lib/tapioca.rb +2 -7
  91. metadata +82 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -711
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -179
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -198
  96. data/lib/tapioca/compilers/sorbet.rb +0 -59
  97. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  98. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  99. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  100. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  101. data/lib/tapioca/generators/todo.rb +0 -76
  102. data/lib/tapioca/generators.rb +0 -9
  103. data/lib/tapioca/generic_type_registry.rb +0 -149
  104. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -98
  105. data/lib/tapioca/loader.rb +0 -119
  106. data/lib/tapioca/reflection.rb +0 -151
  107. data/lib/tapioca/trackers/autoload.rb +0 -70
  108. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  109. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -0,0 +1,101 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Mixins < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ constant = event.constant
17
+ singleton_class = singleton_class_of(constant)
18
+
19
+ interesting_ancestors = interesting_ancestors_of(constant)
20
+ interesting_singleton_class_ancestors = interesting_ancestors_of(singleton_class)
21
+
22
+ prepends = interesting_ancestors.take_while { |c| !are_equal?(constant, c) }
23
+ includes = interesting_ancestors.drop(prepends.size + 1)
24
+ extends = interesting_singleton_class_ancestors.reject do |mod|
25
+ Module != class_of(mod) || are_equal?(mod, singleton_class)
26
+ end
27
+
28
+ node = event.node
29
+ add_mixins(node, prepends.reverse, Runtime::Trackers::Mixin::Type::Prepend)
30
+ add_mixins(node, includes.reverse, Runtime::Trackers::Mixin::Type::Include)
31
+ add_mixins(node, extends.reverse, Runtime::Trackers::Mixin::Type::Extend)
32
+ end
33
+
34
+ sig do
35
+ params(
36
+ tree: RBI::Tree,
37
+ mods: T::Array[Module],
38
+ mixin_type: Runtime::Trackers::Mixin::Type
39
+ ).void
40
+ end
41
+ def add_mixins(tree, mods, mixin_type)
42
+ mods
43
+ .select do |mod|
44
+ name = @pipeline.name_of(mod)
45
+
46
+ name && !filtered_mixin?(name)
47
+ end
48
+ .map do |mod|
49
+ name = @pipeline.name_of(mod)
50
+ @pipeline.push_symbol(name) if name
51
+
52
+ qname = qualified_name_of(mod)
53
+ case mixin_type
54
+ # TODO: Sorbet currently does not handle prepend
55
+ # properly for method resolution, so we generate an
56
+ # include statement instead
57
+ when Runtime::Trackers::Mixin::Type::Include, Runtime::Trackers::Mixin::Type::Prepend
58
+ tree << RBI::Include.new(T.must(qname))
59
+ when Runtime::Trackers::Mixin::Type::Extend
60
+ tree << RBI::Extend.new(T.must(qname))
61
+ end
62
+ end
63
+ end
64
+
65
+ sig { params(mixin_name: String).returns(T::Boolean) }
66
+ def filtered_mixin?(mixin_name)
67
+ # filter T:: namespace mixins that aren't T::Props
68
+ # T::Props and subconstants have semantic value
69
+ mixin_name.start_with?("T::") && !mixin_name.start_with?("T::Props")
70
+ end
71
+
72
+ sig { params(constant: Module).returns(T::Array[Module]) }
73
+ def interesting_ancestors_of(constant)
74
+ inherited_ancestors_ids = Set.new(
75
+ inherited_ancestors_of(constant).map { |mod| object_id_of(mod) }
76
+ )
77
+ # TODO: There is actually a bug here where this will drop modules that
78
+ # may be included twice. For example:
79
+ #
80
+ # ```ruby
81
+ # class Foo
82
+ # prepend Kernel
83
+ # end
84
+ # ````
85
+ # would give:
86
+ # ```ruby
87
+ # Foo.ancestors #=> [Kernel, Foo, Object, Kernel, BasicObject]
88
+ # ````
89
+ # but since we drop `Kernel` whenever we match it, we would miss
90
+ # the `prepend Kernel` in the output.
91
+ #
92
+ # Instead, we should only drop the tail matches of the ancestors and
93
+ # inherited ancestors, past the location of the constant itself.
94
+ constant.ancestors.reject do |mod|
95
+ inherited_ancestors_ids.include?(object_id_of(mod))
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class RemoveEmptyPayloadScopes < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ event.node.detach if @pipeline.symbol_in_payload?(event.symbol) && event.node.empty?
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetEnums < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ constant = event.constant
15
+ return unless T::Enum > event.constant
16
+
17
+ enums = T.unsafe(constant).values.map do |enum_type|
18
+ enum_type.instance_variable_get(:@const_name).to_s
19
+ end
20
+
21
+ event.node << RBI::TEnumBlock.new(enums)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetHelpers < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ constant = event.constant
17
+ node = event.node
18
+
19
+ abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type) ||
20
+ T::Private::Abstract::Data.get(singleton_class_of(constant), :abstract_type)
21
+
22
+ node << RBI::Helper.new(abstract_type.to_s) if abstract_type
23
+ node << RBI::Helper.new("final") if T::Private::Final.final_module?(constant)
24
+ node << RBI::Helper.new("sealed") if T::Private::Sealed.sealed_module?(constant)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetProps < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ constant = event.constant
15
+ node = event.node
16
+
17
+ return unless T::Props::ClassMethods === constant
18
+
19
+ constant.props.map do |name, prop|
20
+ type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
21
+
22
+ default = prop.key?(:default) ? "T.unsafe(nil)" : nil
23
+ node << if prop.fetch(:immutable, false)
24
+ RBI::TStructConst.new(name.to_s, type, default: default)
25
+ else
26
+ RBI::TStructProp.new(name.to_s, type, default: default)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetRequiredAncestors < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ScopeNodeAdded).void }
13
+ def on_scope(event)
14
+ ancestors = Runtime::Trackers::RequiredAncestor.required_ancestors_by(event.constant)
15
+ ancestors.each do |ancestor|
16
+ next unless ancestor # TODO: We should have a way to warn from here
17
+ event.node << RBI::RequiresAncestor.new(ancestor.to_s)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetSignatures < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+ include RBIHelper
12
+
13
+ TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
14
+
15
+ private
16
+
17
+ sig { override.params(event: MethodNodeAdded).void }
18
+ def on_method(event)
19
+ signature = event.signature
20
+ return unless signature
21
+
22
+ event.node.sigs << compile_signature(signature, event.parameters)
23
+ end
24
+
25
+ sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(RBI::Sig) }
26
+ def compile_signature(signature, parameters)
27
+ parameter_types = T.let(signature.arg_types.to_h, T::Hash[Symbol, T::Types::Base])
28
+ parameter_types.merge!(signature.kwarg_types)
29
+ parameter_types[signature.rest_name] = signature.rest_type if signature.has_rest
30
+ parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
31
+ parameter_types[signature.block_name] = signature.block_type if signature.block_name
32
+
33
+ sig = RBI::Sig.new
34
+
35
+ parameters.each do |_, name|
36
+ type = sanitize_signature_types(parameter_types[name.to_sym].to_s)
37
+ @pipeline.push_symbol(type)
38
+ sig << RBI::SigParam.new(name, type)
39
+ end
40
+
41
+ return_type = name_of_type(signature.return_type)
42
+ return_type = sanitize_signature_types(return_type)
43
+ sig.return_type = return_type
44
+ @pipeline.push_symbol(return_type)
45
+
46
+ parameter_types.values.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq.each do |k, _|
47
+ sig.type_params << k
48
+ end
49
+
50
+ case signature.mode
51
+ when "abstract"
52
+ sig.is_abstract = true
53
+ when "override"
54
+ sig.is_override = true
55
+ when "overridable_override"
56
+ sig.is_overridable = true
57
+ sig.is_override = true
58
+ when "overridable"
59
+ sig.is_overridable = true
60
+ end
61
+
62
+ sig.is_final = signature_final?(signature)
63
+
64
+ sig
65
+ end
66
+
67
+ sig { params(signature: T.untyped).returns(T::Boolean) }
68
+ def signature_final?(signature)
69
+ modules_with_final = T::Private::Methods.instance_variable_get(:@modules_with_final)
70
+ final_methods = modules_with_final[signature.owner.object_id]
71
+
72
+ return false unless final_methods
73
+
74
+ final_methods.include?(signature.method_name)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SorbetTypeVariables < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ constant = event.constant
17
+ node = event.node
18
+
19
+ compile_type_variable_declarations(node, constant)
20
+
21
+ sclass = RBI::SingletonClass.new
22
+ compile_type_variable_declarations(sclass, singleton_class_of(constant))
23
+ node << sclass if sclass.nodes.length > 1
24
+ end
25
+
26
+ sig { params(tree: RBI::Tree, constant: Module).void }
27
+ def compile_type_variable_declarations(tree, constant)
28
+ # Try to find the type variables defined on this constant, bail if we can't
29
+ type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant)
30
+ return unless type_variables
31
+
32
+ # Map each type variable to its string representation.
33
+ #
34
+ # Each entry of `type_variables` maps a Module to a String,
35
+ # and the order they are inserted into the hash is the order they should be
36
+ # defined in the source code.
37
+ type_variable_declarations = type_variables.map do |type_variable|
38
+ type_variable_name = type_variable.name
39
+ next unless type_variable_name
40
+
41
+ tree << RBI::TypeMember.new(type_variable_name, type_variable.serialize)
42
+ end
43
+
44
+ return if type_variable_declarations.empty?
45
+
46
+ tree << RBI::Extend.new("T::Generic")
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Subconstants < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ private
13
+
14
+ sig { override.params(event: ScopeNodeAdded).void }
15
+ def on_scope(event)
16
+ symbol = event.symbol
17
+ return if @pipeline.symbol_in_payload?(symbol) && event.node.empty?
18
+
19
+ prefix = symbol == "Object" ? "" : symbol
20
+
21
+ constant = event.constant
22
+ constants_of(constant).sort.uniq.map do |constant_name|
23
+ name = "#{prefix}::#{constant_name}"
24
+ subconstant = constantize(name)
25
+
26
+ # Don't compile modules of Object because Object::Foo == Foo
27
+ # Don't compile modules of BasicObject because BasicObject::BasicObject == BasicObject
28
+ next if (Object == constant || BasicObject == constant) && Module === subconstant
29
+ next unless subconstant
30
+
31
+ @pipeline.push_constant(name, subconstant)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,96 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class YardDoc < Base
8
+ extend T::Sig
9
+
10
+ IGNORED_COMMENTS = T.let([
11
+ ":doc:",
12
+ ":nodoc:",
13
+ "typed:",
14
+ "frozen_string_literal:",
15
+ "encoding:",
16
+ "warn_indent:",
17
+ "shareable_constant_value:",
18
+ "rubocop:",
19
+ ], T::Array[String])
20
+
21
+ IGNORED_SIG_TAGS = T.let(["param", "return"], T::Array[String])
22
+
23
+ sig { params(pipeline: Pipeline).void }
24
+ def initialize(pipeline)
25
+ super(pipeline)
26
+ pipeline.gem.parse_yard_docs
27
+ end
28
+
29
+ private
30
+
31
+ sig { override.params(event: ConstNodeAdded).void }
32
+ def on_const(event)
33
+ event.node.comments = documentation_comments(event.symbol)
34
+ end
35
+
36
+ sig { override.params(event: ScopeNodeAdded).void }
37
+ def on_scope(event)
38
+ event.node.comments = documentation_comments(event.symbol)
39
+ end
40
+
41
+ sig { override.params(event: MethodNodeAdded).void }
42
+ def on_method(event)
43
+ separator = event.constant.singleton_class? ? "." : "#"
44
+ event.node.comments = documentation_comments(
45
+ "#{event.symbol}#{separator}#{event.node.name}",
46
+ sigs: event.node.sigs
47
+ )
48
+ end
49
+
50
+ sig { params(name: String, sigs: T::Array[RBI::Sig]).returns(T::Array[RBI::Comment]) }
51
+ def documentation_comments(name, sigs: [])
52
+ yard_docs = YARD::Registry.at(name)
53
+ return [] unless yard_docs
54
+
55
+ docstring = yard_docs.docstring
56
+ return [] if /(copyright|license)/i.match?(docstring)
57
+
58
+ comments = docstring.lines
59
+ .reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } }
60
+ .map! { |line| RBI::Comment.new(line) }
61
+
62
+ tags = yard_docs.tags
63
+ tags.reject! { |tag| IGNORED_SIG_TAGS.include?(tag.tag_name) } unless sigs.empty?
64
+
65
+ comments << RBI::Comment.new("") if comments.any? && tags.any?
66
+
67
+ tags.sort_by(&:tag_name).each do |tag|
68
+ line = +"@#{tag.tag_name}"
69
+
70
+ tag_name = tag.name
71
+ line << " #{tag_name}" if tag_name
72
+
73
+ tag_types = tag.types
74
+ line << " [#{tag_types.join(", ")}]" if tag_types&.any?
75
+
76
+ tag_text = tag.text
77
+ if tag_text && !tag_text.empty?
78
+ text_lines = tag_text.lines
79
+
80
+ # Example are a special case because we want the text to start on the next line
81
+ line << " #{text_lines.shift&.strip}" unless tag.tag_name == "example"
82
+
83
+ text_lines.each do |text_line|
84
+ line << "\n #{text_line.strip}"
85
+ end
86
+ end
87
+
88
+ comments << RBI::Comment.new(line)
89
+ end
90
+
91
+ comments
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/gem/listeners/base"
5
+ require "tapioca/gem/listeners/dynamic_mixins"
6
+ require "tapioca/gem/listeners/methods"
7
+ require "tapioca/gem/listeners/mixins"
8
+ require "tapioca/gem/listeners/remove_empty_payload_scopes"
9
+ require "tapioca/gem/listeners/sorbet_enums"
10
+ require "tapioca/gem/listeners/sorbet_helpers"
11
+ require "tapioca/gem/listeners/sorbet_props"
12
+ require "tapioca/gem/listeners/sorbet_required_ancestors"
13
+ require "tapioca/gem/listeners/sorbet_signatures"
14
+ require "tapioca/gem/listeners/sorbet_type_variables"
15
+ require "tapioca/gem/listeners/subconstants"
16
+ require "tapioca/gem/listeners/yard_doc"