tapioca 0.6.4 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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