tapioca 0.6.4 → 0.7.2

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -2
  3. data/README.md +27 -15
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +65 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
  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 +86 -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 +13 -11
  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 +28 -34
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
  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 +12 -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 +11 -16
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
  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 +11 -9
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
  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 +22 -10
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
  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 +6 -3
  46. data/lib/tapioca/dsl/pipeline.rb +169 -0
  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/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  66. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  67. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  68. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  69. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  70. data/lib/tapioca/helpers/test/template.rb +13 -2
  71. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  72. data/lib/tapioca/internal.rb +18 -10
  73. data/lib/tapioca/rbi_ext/model.rb +14 -50
  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 +168 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +157 -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 +69 -34
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  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 +83 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
  96. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  97. data/lib/tapioca/compilers/sorbet.rb +0 -59
  98. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  99. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  100. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  101. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  102. data/lib/tapioca/generators/todo.rb +0 -76
  103. data/lib/tapioca/generators.rb +0 -9
  104. data/lib/tapioca/generic_type_registry.rb +0 -164
  105. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  106. data/lib/tapioca/loader.rb +0 -119
  107. data/lib/tapioca/reflection.rb +0 -151
  108. data/lib/tapioca/trackers/autoload.rb +0 -70
  109. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  110. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -1,11 +1,14 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "tapioca/helpers/signatures_helper"
5
+
4
6
  module Tapioca
5
- module Compilers
6
- module Dsl
7
+ module Dsl
8
+ module Helpers
7
9
  module ParamHelper
8
10
  extend T::Sig
11
+ include SignaturesHelper
9
12
 
10
13
  sig { params(name: String, type: String).returns(RBI::TypedParam) }
11
14
  def create_param(name, type:)
@@ -44,7 +47,7 @@ module Tapioca
44
47
 
45
48
  sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
46
49
  def create_typed_param(param, type)
47
- RBI::TypedParam.new(param: param, type: type)
50
+ RBI::TypedParam.new(param: param, type: sanitize_signature_types(type))
48
51
  end
49
52
  end
50
53
  end
@@ -0,0 +1,169 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/dsl/compilers"
5
+
6
+ module Tapioca
7
+ module Dsl
8
+ class Pipeline
9
+ extend T::Sig
10
+
11
+ sig { returns(T::Enumerable[T.class_of(Compiler)]) }
12
+ attr_reader :compilers
13
+
14
+ sig { returns(T::Array[Module]) }
15
+ attr_reader :requested_constants
16
+
17
+ sig { returns(T.proc.params(error: String).void) }
18
+ attr_reader :error_handler
19
+
20
+ sig { returns(T::Array[String]) }
21
+ attr_reader :errors
22
+
23
+ sig do
24
+ params(
25
+ requested_constants: T::Array[Module],
26
+ requested_compilers: T::Array[T.class_of(Compiler)],
27
+ excluded_compilers: T::Array[T.class_of(Compiler)],
28
+ error_handler: T.proc.params(error: String).void,
29
+ number_of_workers: T.nilable(Integer),
30
+ ).void
31
+ end
32
+ def initialize(
33
+ requested_constants:,
34
+ requested_compilers: [],
35
+ excluded_compilers: [],
36
+ error_handler: $stderr.method(:puts).to_proc,
37
+ number_of_workers: nil
38
+ )
39
+ @compilers = T.let(
40
+ gather_compilers(requested_compilers, excluded_compilers),
41
+ T::Enumerable[T.class_of(Compiler)]
42
+ )
43
+ @requested_constants = requested_constants
44
+ @error_handler = error_handler
45
+ @number_of_workers = number_of_workers
46
+ @errors = T.let([], T::Array[String])
47
+ end
48
+
49
+ sig do
50
+ type_parameters(:T).params(
51
+ blk: T.proc.params(constant: Module, rbi: RBI::File).returns(T.type_parameter(:T))
52
+ ).returns(T::Array[T.type_parameter(:T)])
53
+ end
54
+ def run(&blk)
55
+ constants_to_process = gather_constants(requested_constants)
56
+ .select { |c| Module === c } # Filter value constants out
57
+ .sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
58
+
59
+ if constants_to_process.empty?
60
+ report_error(<<~ERROR)
61
+ No classes/modules can be matched for RBI generation.
62
+ Please check that the requested classes/modules include processable DSL methods.
63
+ ERROR
64
+ end
65
+
66
+ result = Executor.new(
67
+ constants_to_process,
68
+ number_of_workers: @number_of_workers
69
+ ).run_in_parallel do |constant|
70
+ rbi = rbi_for_constant(constant)
71
+ next if rbi.nil?
72
+
73
+ blk.call(constant, rbi)
74
+ end
75
+
76
+ errors.each do |msg|
77
+ report_error(msg)
78
+ end
79
+
80
+ result.compact
81
+ end
82
+
83
+ sig { params(error: String).void }
84
+ def add_error(error)
85
+ @errors << error
86
+ end
87
+
88
+ sig { params(compiler_name: String).returns(T::Boolean) }
89
+ def compiler_enabled?(compiler_name)
90
+ potential_names = Compilers::NAMESPACES.map { |namespace| namespace + compiler_name }
91
+
92
+ @compilers.any? do |compiler|
93
+ potential_names.any?(compiler.name)
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ sig do
100
+ params(
101
+ requested_compilers: T::Array[T.class_of(Compiler)],
102
+ excluded_compilers: T::Array[T.class_of(Compiler)]
103
+ ).returns(T::Enumerable[T.class_of(Compiler)])
104
+ end
105
+ def gather_compilers(requested_compilers, excluded_compilers)
106
+ Runtime::Reflection.descendants_of(Compiler).select do |klass|
107
+ (requested_compilers.empty? || requested_compilers.include?(klass)) &&
108
+ !excluded_compilers.include?(klass)
109
+ end.sort_by { |klass| T.must(klass.name) }
110
+ end
111
+
112
+ sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
113
+ def gather_constants(requested_constants)
114
+ constants = compilers.map(&:processable_constants).reduce(Set.new, :union)
115
+ constants = filter_anonymous_and_reloaded_constants(constants)
116
+
117
+ constants &= requested_constants unless requested_constants.empty?
118
+ constants
119
+ end
120
+
121
+ sig { params(constants: T::Set[Module]).returns(T::Set[Module]) }
122
+ def filter_anonymous_and_reloaded_constants(constants)
123
+ # Group constants by their names
124
+ constants_by_name = constants
125
+ .group_by { |c| T.must(Runtime::Reflection.name_of(c)) }
126
+ .select { |name, _| !name.nil? }
127
+
128
+ # Find the constants that have been reloaded
129
+ reloaded_constants = constants_by_name.select { |_, constants| constants.size > 1 }.keys
130
+
131
+ unless reloaded_constants.empty?
132
+ reloaded_constant_names = reloaded_constants.map { |name| "`#{name}`" }.join(", ")
133
+
134
+ $stderr.puts("WARNING: Multiple constants with the same name: #{reloaded_constant_names}")
135
+ $stderr.puts("Make sure some object is not holding onto these constants during an app reload.")
136
+ end
137
+
138
+ # Look up all the constants back from their names. The resulting constant set will be the
139
+ # set of constants that are actually in memory with those names.
140
+ constants_by_name
141
+ .keys
142
+ .map { |name| T.cast(Runtime::Reflection.constantize(name), Module) }
143
+ .to_set
144
+ end
145
+
146
+ sig { params(constant: Module).returns(T.nilable(RBI::File)) }
147
+ def rbi_for_constant(constant)
148
+ file = RBI::File.new(strictness: "true")
149
+
150
+ compilers.each do |compiler_class|
151
+ next unless compiler_class.handles?(constant)
152
+ compiler = compiler_class.new(self, file.root, constant)
153
+ compiler.decorate
154
+ end
155
+
156
+ return if file.root.empty?
157
+
158
+ file
159
+ end
160
+
161
+ sig { params(error: String).returns(T.noreturn) }
162
+ def report_error(error)
163
+ handler = error_handler
164
+ handler.call(error)
165
+ exit(1)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,120 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+
6
+ module Tapioca
7
+ module Gem
8
+ class Event
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ abstract!
13
+ end
14
+
15
+ class SymbolFound < Event
16
+ extend T::Sig
17
+
18
+ sig { returns(String) }
19
+ attr_reader :symbol
20
+
21
+ sig { params(symbol: String).void }
22
+ def initialize(symbol)
23
+ super()
24
+ @symbol = symbol
25
+ end
26
+ end
27
+
28
+ class ConstantFound < Event
29
+ extend T::Sig
30
+
31
+ sig { returns(String) }
32
+ attr_reader :symbol
33
+
34
+ sig { returns(BasicObject).checked(:never) }
35
+ attr_reader :constant
36
+
37
+ sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
38
+ def initialize(symbol, constant)
39
+ super()
40
+ @symbol = symbol
41
+ @constant = constant
42
+ end
43
+ end
44
+
45
+ class NodeAdded < Event
46
+ extend T::Helpers
47
+ extend T::Sig
48
+
49
+ abstract!
50
+
51
+ sig { returns(String) }
52
+ attr_reader :symbol
53
+
54
+ sig { returns(Module).checked(:never) }
55
+ attr_reader :constant
56
+
57
+ sig { params(symbol: String, constant: Module).void.checked(:never) }
58
+ def initialize(symbol, constant)
59
+ super()
60
+ @symbol = symbol
61
+ @constant = constant
62
+ end
63
+ end
64
+
65
+ class ConstNodeAdded < NodeAdded
66
+ extend T::Sig
67
+
68
+ sig { returns(RBI::Const) }
69
+ attr_reader :node
70
+
71
+ sig { params(symbol: String, constant: Module, node: RBI::Const).void.checked(:never) }
72
+ def initialize(symbol, constant, node)
73
+ super(symbol, constant)
74
+ @node = node
75
+ end
76
+ end
77
+
78
+ class ScopeNodeAdded < NodeAdded
79
+ extend T::Sig
80
+
81
+ sig { returns(RBI::Scope) }
82
+ attr_reader :node
83
+
84
+ sig { params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never) }
85
+ def initialize(symbol, constant, node)
86
+ super(symbol, constant)
87
+ @node = node
88
+ end
89
+ end
90
+
91
+ class MethodNodeAdded < NodeAdded
92
+ extend T::Sig
93
+
94
+ sig { returns(RBI::Method) }
95
+ attr_reader :node
96
+
97
+ sig { returns(T.untyped) }
98
+ attr_reader :signature
99
+
100
+ sig { returns(T::Array[[Symbol, String]]) }
101
+ attr_reader :parameters
102
+
103
+ sig do
104
+ params(
105
+ symbol: String,
106
+ constant: Module,
107
+ node: RBI::Method,
108
+ signature: T.untyped,
109
+ parameters: T::Array[[Symbol, String]]
110
+ ).void.checked(:never)
111
+ end
112
+ def initialize(symbol, constant, node, signature, parameters)
113
+ super(symbol, constant)
114
+ @node = node
115
+ @signature = signature
116
+ @parameters = parameters
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Base
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ abstract!
12
+
13
+ sig { params(pipeline: Pipeline).void }
14
+ def initialize(pipeline)
15
+ @pipeline = pipeline
16
+ end
17
+
18
+ sig { params(event: NodeAdded).void }
19
+ def dispatch(event)
20
+ case event
21
+ when ConstNodeAdded
22
+ on_const(event)
23
+ when ScopeNodeAdded
24
+ on_scope(event)
25
+ when MethodNodeAdded
26
+ on_method(event)
27
+ else
28
+ raise "Unsupported event #{event.class}"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ sig { params(event: ConstNodeAdded).void }
35
+ def on_const(event)
36
+ end
37
+
38
+ sig { params(event: ScopeNodeAdded).void }
39
+ def on_scope(event)
40
+ end
41
+
42
+ sig { params(event: MethodNodeAdded).void }
43
+ def on_method(event)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class DynamicMixins < 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
+ return if constant.is_a?(Class)
18
+
19
+ node = event.node
20
+ mixin_compiler = Runtime::DynamicMixinCompiler.new(constant)
21
+ mixin_compiler.compile_class_attributes(node)
22
+ dynamic_extends, dynamic_includes = mixin_compiler.compile_mixes_in_class_methods(node)
23
+
24
+ (dynamic_includes + dynamic_extends).each do |mod|
25
+ name = @pipeline.name_of(mod)
26
+ @pipeline.push_symbol(name) if name
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,183 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Methods < Base
8
+ extend T::Sig
9
+
10
+ include Runtime::Reflection
11
+
12
+ SPECIAL_METHOD_NAMES = T.let([
13
+ "!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^",
14
+ "<", "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`",
15
+ ], T::Array[String])
16
+
17
+ private
18
+
19
+ sig { override.params(event: ScopeNodeAdded).void }
20
+ def on_scope(event)
21
+ symbol = event.symbol
22
+ constant = event.constant
23
+ node = event.node
24
+
25
+ compile_method(node, symbol, constant, initialize_method_for(constant))
26
+ compile_directly_owned_methods(node, symbol, constant)
27
+ compile_directly_owned_methods(node, symbol, singleton_class_of(constant))
28
+ end
29
+
30
+ sig do
31
+ params(
32
+ tree: RBI::Tree,
33
+ module_name: String,
34
+ mod: Module,
35
+ for_visibility: T::Array[Symbol]
36
+ ).void
37
+ end
38
+ def compile_directly_owned_methods(tree, module_name, mod, for_visibility = [:public, :protected, :private])
39
+ method_names_by_visibility(mod)
40
+ .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
41
+ .each do |visibility, method_list|
42
+ method_list.sort!.map do |name|
43
+ next if name == :initialize
44
+ vis = case visibility
45
+ when :protected
46
+ RBI::Protected.new
47
+ when :private
48
+ RBI::Private.new
49
+ else
50
+ RBI::Public.new
51
+ end
52
+ compile_method(tree, module_name, mod, mod.instance_method(name), vis)
53
+ end
54
+ end
55
+ end
56
+
57
+ sig do
58
+ params(
59
+ tree: RBI::Tree,
60
+ symbol_name: String,
61
+ constant: Module,
62
+ method: T.nilable(UnboundMethod),
63
+ visibility: RBI::Visibility
64
+ ).void
65
+ end
66
+ def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
67
+ return unless method
68
+ return unless method.owner == constant
69
+ return if @pipeline.symbol_in_payload?(symbol_name) && !@pipeline.method_in_gem?(method)
70
+
71
+ signature = signature_of(method)
72
+ method = T.let(signature.method, UnboundMethod) if signature
73
+
74
+ method_name = method.name.to_s
75
+ return unless valid_method_name?(method_name)
76
+ return if struct_method?(constant, method_name)
77
+ return if method_name.start_with?("__t_props_generated_")
78
+
79
+ parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
80
+
81
+ sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
82
+ fallback_arg_name = "_arg#{index}"
83
+
84
+ name = if name
85
+ name.to_s
86
+ else
87
+ # For attr_writer methods, Sorbet signatures have the name
88
+ # of the method (without the trailing = sign) as the name of
89
+ # the only parameter. So, if the parameter does not have a name
90
+ # then the replacement name should be the name of the method
91
+ # (minus trailing =) if and only if there is a signature for the
92
+ # method and the parameter is required and there is a single
93
+ # parameter and the signature also defines a single parameter and
94
+ # the name of the method ends with a = character.
95
+ writer_method_with_sig = (
96
+ signature && type == :req &&
97
+ parameters.size == 1 &&
98
+ signature.arg_types.size == 1 &&
99
+ method_name[-1] == "="
100
+ )
101
+
102
+ if writer_method_with_sig
103
+ method_name.delete_suffix("=")
104
+ else
105
+ fallback_arg_name
106
+ end
107
+ end
108
+
109
+ # Sanitize param names
110
+ name = fallback_arg_name unless valid_parameter_name?(name)
111
+
112
+ [type, name]
113
+ end
114
+
115
+ rbi_method = RBI::Method.new(
116
+ method_name,
117
+ is_singleton: constant.singleton_class?,
118
+ visibility: visibility
119
+ )
120
+
121
+ sanitized_parameters.each do |type, name|
122
+ case type
123
+ when :req
124
+ rbi_method << RBI::Param.new(name)
125
+ when :opt
126
+ rbi_method << RBI::OptParam.new(name, "T.unsafe(nil)")
127
+ when :rest
128
+ rbi_method << RBI::RestParam.new(name)
129
+ when :keyreq
130
+ rbi_method << RBI::KwParam.new(name)
131
+ when :key
132
+ rbi_method << RBI::KwOptParam.new(name, "T.unsafe(nil)")
133
+ when :keyrest
134
+ rbi_method << RBI::KwRestParam.new(name)
135
+ when :block
136
+ rbi_method << RBI::BlockParam.new(name)
137
+ end
138
+ end
139
+
140
+ @pipeline.push_method(symbol_name, constant, rbi_method, signature, sanitized_parameters)
141
+ tree << rbi_method
142
+ end
143
+
144
+ sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
145
+ def method_names_by_visibility(mod)
146
+ {
147
+ public: public_instance_methods_of(mod),
148
+ protected: protected_instance_methods_of(mod),
149
+ private: private_instance_methods_of(mod),
150
+ }
151
+ end
152
+
153
+ sig { params(constant: Module, method_name: String).returns(T::Boolean) }
154
+ def struct_method?(constant, method_name)
155
+ return false unless T::Props::ClassMethods === constant
156
+
157
+ constant
158
+ .props
159
+ .keys
160
+ .include?(method_name.gsub(/=$/, "").to_sym)
161
+ end
162
+
163
+ sig { params(name: String).returns(T::Boolean) }
164
+ def valid_method_name?(name)
165
+ return true if SPECIAL_METHOD_NAMES.include?(name)
166
+ !!name.match(/^[[:word:]]+[?!=]?$/)
167
+ end
168
+
169
+ sig { params(name: String).returns(T::Boolean) }
170
+ def valid_parameter_name?(name)
171
+ name.match?(/^[[[:alnum:]]_]+$/)
172
+ end
173
+
174
+ sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
175
+ def initialize_method_for(constant)
176
+ constant.instance_method(:initialize)
177
+ rescue
178
+ nil
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -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