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,780 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "pathname"
5
-
6
- module Tapioca
7
- module Compilers
8
- module SymbolTable
9
- class SymbolGenerator
10
- extend(T::Sig)
11
- include(Reflection)
12
-
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)
33
- @gem = gem
34
- @indent = indent
35
- @seen = T.let(Set.new, T::Set[String])
36
- @alias_namespace = T.let(Set.new, T::Set[String])
37
- @symbol_queue = T.let(symbols.sort.dup, T::Array[String])
38
- @symbols = T.let(nil, T.nilable(T::Set[String]))
39
- @include_doc = include_doc
40
-
41
- gem.parse_yard_docs if include_doc
42
- end
43
-
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?
47
- end
48
-
49
- private
50
-
51
- sig { params(name: T.nilable(String)).void }
52
- def add_to_symbol_queue(name)
53
- @symbol_queue << name unless name.nil? || symbols.include?(name) || symbol_ignored?(name)
54
- end
55
-
56
- sig { returns(T::Set[String]) }
57
- def symbols
58
- @symbols ||= begin
59
- symbols = Tapioca::Compilers::SymbolTable::SymbolLoader.list_from_paths(gem.files)
60
- symbols.union(engine_symbols(symbols))
61
- end
62
- end
63
-
64
- sig { params(symbols: T::Set[String]).returns(T::Set[String]) }
65
- def engine_symbols(symbols)
66
- return Set.new unless Object.const_defined?("Rails::Engine")
67
-
68
- engine = descendants_of(Object.const_get("Rails::Engine"))
69
- .reject(&:abstract_railtie?)
70
- .find do |klass|
71
- name = name_of(klass)
72
- !name.nil? && symbols.include?(name)
73
- end
74
-
75
- return Set.new unless engine
76
-
77
- paths = engine.config.eager_load_paths.flat_map do |load_path|
78
- Pathname.glob("#{load_path}/**/*.rb")
79
- end
80
-
81
- Tapioca::Compilers::SymbolTable::SymbolLoader.list_from_paths(paths)
82
- rescue
83
- Set.new
84
- end
85
-
86
- sig { params(tree: RBI::Tree, symbol: String).void }
87
- def generate_from_symbol(tree, symbol)
88
- constant = constantize(symbol)
89
-
90
- return unless constant
91
-
92
- compile(tree, symbol, constant)
93
- end
94
-
95
- sig { params(tree: RBI::Tree, name: T.nilable(String), constant: BasicObject).void.checked(:never) }
96
- def compile(tree, name, constant)
97
- return unless constant
98
- return unless name
99
- return if name.strip.empty?
100
- return if name.start_with?("#<")
101
- return if name.downcase == name
102
- return if alias_namespaced?(name)
103
- return if seen?(name)
104
- return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
105
-
106
- mark_seen(name)
107
- compile_constant(tree, name, constant)
108
- end
109
-
110
- sig { params(tree: RBI::Tree, name: String, constant: BasicObject).void.checked(:never) }
111
- def compile_constant(tree, name, constant)
112
- case constant
113
- when Module
114
- if name_of(constant) != name
115
- compile_alias(tree, name, constant)
116
- else
117
- compile_module(tree, name, constant)
118
- end
119
- else
120
- compile_object(tree, name, constant)
121
- end
122
- end
123
-
124
- sig { params(tree: RBI::Tree, name: String, constant: Module).void }
125
- def compile_alias(tree, name, constant)
126
- return if symbol_ignored?(name)
127
-
128
- target = name_of(constant)
129
- # If target has no name, let's make it an anonymous class or module with `Class.new` or `Module.new`
130
- target = "#{constant.class}.new" unless target
131
-
132
- add_to_alias_namespace(name)
133
-
134
- return if IGNORED_SYMBOLS.include?(name)
135
-
136
- tree << RBI::Const.new(name, target)
137
- end
138
-
139
- sig { params(tree: RBI::Tree, name: String, value: BasicObject).void.checked(:never) }
140
- def compile_object(tree, name, value)
141
- return if symbol_ignored?(name)
142
-
143
- klass = class_of(value)
144
-
145
- klass_name = if klass == ObjectSpace::WeakMap
146
- # WeakMap is an implicit generic with one type variable
147
- "ObjectSpace::WeakMap[T.untyped]"
148
- elsif T::Generic === klass
149
- generic_name_of(klass)
150
- else
151
- name_of(klass)
152
- end
153
-
154
- comments = documentation_comments(name)
155
-
156
- if klass_name == "T::Private::Types::TypeAlias"
157
- type_alias = sanitize_signature_types(T.unsafe(value).aliased_type.to_s)
158
- constant = RBI::Const.new(name, "T.type_alias { #{type_alias} }", comments: comments)
159
- tree << constant
160
- return
161
- end
162
-
163
- return if klass_name&.start_with?("T::Types::", "T::Private::")
164
-
165
- type_name = klass_name || "T.untyped"
166
- constant = RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})", comments: comments)
167
-
168
- tree << constant
169
- end
170
-
171
- sig { params(tree: RBI::Tree, name: String, constant: Module).void }
172
- def compile_module(tree, name, constant)
173
- return unless defined_in_gem?(constant, strict: false)
174
- return if Tapioca::TypeVariableModule === constant
175
-
176
- comments = documentation_comments(name)
177
- scope =
178
- if constant.is_a?(Class)
179
- superclass = compile_superclass(constant)
180
- RBI::Class.new(name, superclass_name: superclass, comments: comments)
181
- else
182
- RBI::Module.new(name, comments: comments)
183
- end
184
-
185
- compile_body(scope, name, constant)
186
-
187
- return if symbol_ignored?(name) && scope.empty?
188
-
189
- tree << scope
190
- compile_subconstants(tree, name, constant)
191
- end
192
-
193
- sig { params(tree: RBI::Tree, name: String, constant: Module).void }
194
- def compile_body(tree, name, constant)
195
- # Compiling type variables must happen first to populate generic names
196
- compile_type_variables(tree, constant)
197
- compile_methods(tree, name, constant)
198
- compile_module_helpers(tree, constant)
199
- compile_mixins(tree, constant)
200
- compile_props(tree, constant)
201
- compile_enums(tree, constant)
202
- compile_dynamic_mixins(tree, constant)
203
- end
204
-
205
- sig { params(tree: RBI::Tree, constant: Module).void }
206
- def compile_dynamic_mixins(tree, constant)
207
- return if constant.is_a?(Class)
208
-
209
- mixin_compiler = DynamicMixinCompiler.new(constant)
210
- mixin_compiler.compile_class_attributes(tree)
211
- dynamic_extends, dynamic_includes = mixin_compiler.compile_mixes_in_class_methods(tree)
212
-
213
- (dynamic_includes + dynamic_extends).each do |mod|
214
- add_to_symbol_queue(name_of(mod))
215
- end
216
- end
217
-
218
- sig { params(tree: RBI::Tree, constant: Module).void }
219
- def compile_module_helpers(tree, constant)
220
- abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type) ||
221
- T::Private::Abstract::Data.get(singleton_class_of(constant), :abstract_type)
222
-
223
- tree << RBI::Helper.new(abstract_type.to_s) if abstract_type
224
- tree << RBI::Helper.new("final") if T::Private::Final.final_module?(constant)
225
- tree << RBI::Helper.new("sealed") if T::Private::Sealed.sealed_module?(constant)
226
- end
227
-
228
- sig { params(tree: RBI::Tree, constant: Module).void }
229
- def compile_props(tree, constant)
230
- return unless T::Props::ClassMethods === constant
231
-
232
- constant.props.map do |name, prop|
233
- type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
234
-
235
- default = prop.key?(:default) ? "T.unsafe(nil)" : nil
236
- tree << if prop.fetch(:immutable, false)
237
- RBI::TStructConst.new(name.to_s, type, default: default)
238
- else
239
- RBI::TStructProp.new(name.to_s, type, default: default)
240
- end
241
- end
242
- end
243
-
244
- sig { params(tree: RBI::Tree, constant: Module).void }
245
- def compile_enums(tree, constant)
246
- return unless T::Enum > constant
247
-
248
- enums = T.unsafe(constant).values.map do |enum_type|
249
- enum_type.instance_variable_get(:@const_name).to_s
250
- end
251
-
252
- tree << RBI::TEnumBlock.new(enums)
253
- end
254
-
255
- sig { params(tree: RBI::Tree, name: String, constant: Module).void }
256
- def compile_subconstants(tree, name, constant)
257
- constants_of(constant).sort.uniq.map do |constant_name|
258
- symbol = (name == "Object" ? "" : name) + "::#{constant_name}"
259
- subconstant = constantize(symbol)
260
-
261
- # Don't compile modules of Object because Object::Foo == Foo
262
- # Don't compile modules of BasicObject because BasicObject::BasicObject == BasicObject
263
- next if (Object == constant || BasicObject == constant) && Module === subconstant
264
- next unless subconstant
265
-
266
- compile(tree, symbol, subconstant)
267
- end
268
- end
269
-
270
- sig { params(tree: RBI::Tree, constant: Module).void }
271
- def compile_type_variables(tree, constant)
272
- compile_type_variable_declarations(tree, constant)
273
-
274
- sclass = RBI::SingletonClass.new
275
- compile_type_variable_declarations(sclass, singleton_class_of(constant))
276
- tree << sclass if sclass.nodes.length > 1
277
- end
278
-
279
- sig { params(tree: RBI::Tree, constant: Module).void }
280
- def compile_type_variable_declarations(tree, constant)
281
- # Try to find the type variables defined on this constant, bail if we can't
282
- type_variables = GenericTypeRegistry.lookup_type_variables(constant)
283
- return unless type_variables
284
-
285
- # Map each type variable to its string representation.
286
- #
287
- # Each entry of `type_variables` maps a Module to a String,
288
- # and the order they are inserted into the hash is the order they should be
289
- # defined in the source code.
290
- type_variable_declarations = type_variables.map do |type_variable|
291
- type_variable_name = type_variable.name
292
- next unless type_variable_name
293
-
294
- tree << RBI::TypeMember.new(type_variable_name, type_variable.serialize)
295
- end
296
-
297
- return if type_variable_declarations.empty?
298
-
299
- tree << RBI::Extend.new("T::Generic")
300
- end
301
-
302
- sig { params(constant: Class).returns(T.nilable(String)) }
303
- def compile_superclass(constant)
304
- superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
305
-
306
- while (superclass = superclass_of(constant))
307
- constant_name = name_of(constant)
308
- constant = superclass
309
-
310
- # Some types have "themselves" as their superclass
311
- # which can happen via:
312
- #
313
- # class A < Numeric; end
314
- # A = Class.new(A)
315
- # A.superclass #=> A
316
- #
317
- # We compare names here to make sure we skip those
318
- # superclass instances and walk up the chain.
319
- #
320
- # The name comparison is against the name of the constant
321
- # resolved from the name of the superclass, since
322
- # this is also possible:
323
- #
324
- # B = Class.new
325
- # class A < B; end
326
- # B = A
327
- # A.superclass.name #=> "B"
328
- # B #=> A
329
- superclass_name = name_of(superclass)
330
- next unless superclass_name
331
-
332
- resolved_superclass = constantize(superclass_name)
333
- next unless Module === resolved_superclass
334
- next if name_of(resolved_superclass) == constant_name
335
-
336
- # We found a suitable superclass
337
- break
338
- end
339
-
340
- return if superclass == ::Object || superclass == ::Delegator
341
- return if superclass.nil?
342
-
343
- name = name_of(superclass)
344
- return if name.nil? || name.empty?
345
-
346
- add_to_symbol_queue(name)
347
-
348
- "::#{name}"
349
- end
350
-
351
- sig { params(tree: RBI::Tree, constant: Module).void }
352
- def compile_mixins(tree, constant)
353
- singleton_class = singleton_class_of(constant)
354
-
355
- interesting_ancestors = interesting_ancestors_of(constant)
356
- interesting_singleton_class_ancestors = interesting_ancestors_of(singleton_class)
357
-
358
- prepends = interesting_ancestors.take_while { |c| !are_equal?(constant, c) }
359
- includes = interesting_ancestors.drop(prepends.size + 1)
360
- extends = interesting_singleton_class_ancestors.reject do |mod|
361
- Module != class_of(mod) || are_equal?(mod, singleton_class)
362
- end
363
-
364
- add_mixins(tree, prepends.reverse, Trackers::Mixin::Type::Prepend)
365
- add_mixins(tree, includes.reverse, Trackers::Mixin::Type::Include)
366
- add_mixins(tree, extends.reverse, Trackers::Mixin::Type::Extend)
367
- end
368
-
369
- sig do
370
- params(
371
- tree: RBI::Tree,
372
- mods: T::Array[Module],
373
- mixin_type: Trackers::Mixin::Type
374
- ).void
375
- end
376
- def add_mixins(tree, mods, mixin_type)
377
- mods
378
- .select do |mod|
379
- name = name_of(mod)
380
-
381
- name && !filtered_mixin?(name)
382
- end
383
- .map do |mod|
384
- add_to_symbol_queue(name_of(mod))
385
-
386
- qname = qualified_name_of(mod)
387
- case mixin_type
388
- # TODO: Sorbet currently does not handle prepend
389
- # properly for method resolution, so we generate an
390
- # include statement instead
391
- when Trackers::Mixin::Type::Include, Trackers::Mixin::Type::Prepend
392
- tree << RBI::Include.new(T.must(qname))
393
- when Trackers::Mixin::Type::Extend
394
- tree << RBI::Extend.new(T.must(qname))
395
- end
396
- end
397
- end
398
-
399
- sig { params(tree: RBI::Tree, name: String, constant: Module).void }
400
- def compile_methods(tree, name, constant)
401
- compile_method(
402
- tree,
403
- name,
404
- constant,
405
- initialize_method_for(constant)
406
- )
407
-
408
- compile_directly_owned_methods(tree, name, constant)
409
- compile_directly_owned_methods(tree, name, singleton_class_of(constant))
410
- end
411
-
412
- sig do
413
- params(
414
- tree: RBI::Tree,
415
- module_name: String,
416
- mod: Module,
417
- for_visibility: T::Array[Symbol]
418
- ).void
419
- end
420
- def compile_directly_owned_methods(tree, module_name, mod, for_visibility = [:public, :protected, :private])
421
- method_names_by_visibility(mod)
422
- .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
423
- .each do |visibility, method_list|
424
- method_list.sort!.map do |name|
425
- next if name == :initialize
426
- vis = case visibility
427
- when :protected
428
- RBI::Protected.new
429
- when :private
430
- RBI::Private.new
431
- else
432
- RBI::Public.new
433
- end
434
- compile_method(tree, module_name, mod, mod.instance_method(name), vis)
435
- end
436
- end
437
- end
438
-
439
- sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
440
- def method_names_by_visibility(mod)
441
- {
442
- public: public_instance_methods_of(mod),
443
- protected: protected_instance_methods_of(mod),
444
- private: private_instance_methods_of(mod),
445
- }
446
- end
447
-
448
- sig { params(constant: Module, method_name: String).returns(T::Boolean) }
449
- def struct_method?(constant, method_name)
450
- return false unless T::Props::ClassMethods === constant
451
-
452
- constant
453
- .props
454
- .keys
455
- .include?(method_name.gsub(/=$/, "").to_sym)
456
- end
457
-
458
- sig do
459
- params(
460
- tree: RBI::Tree,
461
- symbol_name: String,
462
- constant: Module,
463
- method: T.nilable(UnboundMethod),
464
- visibility: RBI::Visibility
465
- ).void
466
- end
467
- def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
468
- return unless method
469
- return unless method.owner == constant
470
- return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
471
-
472
- signature = signature_of(method)
473
- method = T.let(signature.method, UnboundMethod) if signature
474
-
475
- method_name = method.name.to_s
476
- return unless valid_method_name?(method_name)
477
- return if struct_method?(constant, method_name)
478
- return if method_name.start_with?("__t_props_generated_")
479
-
480
- parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
481
-
482
- sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
483
- fallback_arg_name = "_arg#{index}"
484
-
485
- name = if name
486
- name.to_s
487
- else
488
- # For attr_writer methods, Sorbet signatures have the name
489
- # of the method (without the trailing = sign) as the name of
490
- # the only parameter. So, if the parameter does not have a name
491
- # then the replacement name should be the name of the method
492
- # (minus trailing =) if and only if there is a signature for the
493
- # method and the parameter is required and there is a single
494
- # parameter and the signature also defines a single parameter and
495
- # the name of the method ends with a = character.
496
- writer_method_with_sig = (
497
- signature && type == :req &&
498
- parameters.size == 1 &&
499
- signature.arg_types.size == 1 &&
500
- method_name[-1] == "="
501
- )
502
-
503
- if writer_method_with_sig
504
- method_name.delete_suffix("=")
505
- else
506
- fallback_arg_name
507
- end
508
- end
509
-
510
- # Sanitize param names
511
- name = fallback_arg_name unless valid_parameter_name?(name)
512
-
513
- [type, name]
514
- end
515
-
516
- separator = constant.singleton_class? ? "." : "#"
517
- comments = documentation_comments("#{symbol_name}#{separator}#{method_name}")
518
- rbi_method = RBI::Method.new(
519
- method_name,
520
- is_singleton: constant.singleton_class?,
521
- visibility: visibility,
522
- comments: comments
523
- )
524
-
525
- rbi_method.sigs << compile_signature(signature, sanitized_parameters) if signature
526
-
527
- sanitized_parameters.each do |type, name|
528
- case type
529
- when :req
530
- rbi_method << RBI::Param.new(name)
531
- when :opt
532
- rbi_method << RBI::OptParam.new(name, "T.unsafe(nil)")
533
- when :rest
534
- rbi_method << RBI::RestParam.new(name)
535
- when :keyreq
536
- rbi_method << RBI::KwParam.new(name)
537
- when :key
538
- rbi_method << RBI::KwOptParam.new(name, "T.unsafe(nil)")
539
- when :keyrest
540
- rbi_method << RBI::KwRestParam.new(name)
541
- when :block
542
- rbi_method << RBI::BlockParam.new(name)
543
- end
544
- end
545
-
546
- tree << rbi_method
547
- end
548
-
549
- TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
550
-
551
- sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(RBI::Sig) }
552
- def compile_signature(signature, parameters)
553
- parameter_types = T.let(signature.arg_types.to_h, T::Hash[Symbol, T::Types::Base])
554
- parameter_types.merge!(signature.kwarg_types)
555
- parameter_types[signature.rest_name] = signature.rest_type if signature.has_rest
556
- parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
557
- parameter_types[signature.block_name] = signature.block_type if signature.block_name
558
-
559
- sig = RBI::Sig.new
560
-
561
- parameters.each do |_, name|
562
- type = sanitize_signature_types(parameter_types[name.to_sym].to_s)
563
- add_to_symbol_queue(type)
564
- sig << RBI::SigParam.new(name, type)
565
- end
566
-
567
- return_type = name_of_type(signature.return_type)
568
- sig.return_type = sanitize_signature_types(return_type)
569
- add_to_symbol_queue(sig.return_type)
570
-
571
- parameter_types.values.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq.each do |k, _|
572
- sig.type_params << k
573
- end
574
-
575
- case signature.mode
576
- when "abstract"
577
- sig.is_abstract = true
578
- when "override"
579
- sig.is_override = true
580
- when "overridable_override"
581
- sig.is_overridable = true
582
- sig.is_override = true
583
- when "overridable"
584
- sig.is_overridable = true
585
- end
586
-
587
- sig
588
- end
589
-
590
- sig { params(sig_string: String).returns(String) }
591
- def sanitize_signature_types(sig_string)
592
- sig_string
593
- .gsub(".returns(<VOID>)", ".void")
594
- .gsub("<VOID>", "void")
595
- .gsub("<NOT-TYPED>", "T.untyped")
596
- .gsub(".params()", "")
597
- end
598
-
599
- sig { params(symbol_name: String).returns(T::Boolean) }
600
- def symbol_ignored?(symbol_name)
601
- SymbolLoader.ignore_symbol?(symbol_name)
602
- end
603
-
604
- sig { params(mixin_name: String).returns(T::Boolean) }
605
- def filtered_mixin?(mixin_name)
606
- # filter T:: namespace mixins that aren't T::Props
607
- # T::Props and subconstants have semantic value
608
- mixin_name.start_with?("T::") && !mixin_name.start_with?("T::Props")
609
- end
610
-
611
- SPECIAL_METHOD_NAMES = T.let([
612
- "!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^",
613
- "<", "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"
614
- ], T::Array[String])
615
-
616
- sig { params(name: String).returns(T::Boolean) }
617
- def valid_method_name?(name)
618
- return true if SPECIAL_METHOD_NAMES.include?(name)
619
- !!name.match(/^[[:word:]]+[?!=]?$/)
620
- end
621
-
622
- sig { params(name: String).returns(T::Boolean) }
623
- def valid_parameter_name?(name)
624
- name.match?(/^[[[:alnum:]]_]+$/)
625
- end
626
-
627
- sig { params(method: UnboundMethod).returns(T::Boolean) }
628
- def method_in_gem?(method)
629
- source_location = method.source_location&.first
630
- return false if source_location.nil?
631
-
632
- gem.contains_path?(source_location)
633
- end
634
-
635
- sig { params(constant: Module, strict: T::Boolean).returns(T::Boolean) }
636
- def defined_in_gem?(constant, strict: true)
637
- files = Set.new(get_file_candidates(constant))
638
- .merge(Tapioca::Trackers::ConstantDefinition.files_for(constant))
639
-
640
- return !strict if files.empty?
641
-
642
- files.any? do |file|
643
- gem.contains_path?(file)
644
- end
645
- end
646
-
647
- sig do
648
- params(
649
- mod: Module,
650
- mixin_type: Trackers::Mixin::Type,
651
- mixin_locations: T::Hash[Trackers::Mixin::Type, T::Hash[Module, T::Array[String]]]
652
- ).returns(T::Boolean)
653
- end
654
- def mixed_in_by_gem?(mod, mixin_type, mixin_locations)
655
- locations = mixin_locations.dig(mixin_type, mod)
656
- return true unless locations
657
- locations.any? { |location| gem.contains_path?(location) }
658
- end
659
-
660
- sig { params(constant: Module).returns(T::Array[String]) }
661
- def get_file_candidates(constant)
662
- wrapped_module = Pry::WrappedModule.new(constant)
663
-
664
- wrapped_module.candidates.map(&:file).to_a.compact
665
- rescue ArgumentError, NameError
666
- []
667
- end
668
-
669
- sig { params(name: String).void }
670
- def add_to_alias_namespace(name)
671
- @alias_namespace.add("#{name}::")
672
- end
673
-
674
- sig { params(name: String).returns(T::Boolean) }
675
- def alias_namespaced?(name)
676
- @alias_namespace.any? do |namespace|
677
- name.start_with?(namespace)
678
- end
679
- end
680
-
681
- sig { params(name: String).void }
682
- def mark_seen(name)
683
- @seen.add(name)
684
- end
685
-
686
- sig { params(name: String).returns(T::Boolean) }
687
- def seen?(name)
688
- @seen.include?(name)
689
- end
690
-
691
- sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
692
- def initialize_method_for(constant)
693
- constant.instance_method(:initialize)
694
- rescue
695
- nil
696
- end
697
-
698
- sig { params(constant: Module).returns(T::Array[Module]) }
699
- def interesting_ancestors_of(constant)
700
- inherited_ancestors_ids = Set.new(
701
- inherited_ancestors_of(constant).map { |mod| object_id_of(mod) }
702
- )
703
- # TODO: There is actually a bug here where this will drop modules that
704
- # may be included twice. For example:
705
- #
706
- # ```ruby
707
- # class Foo
708
- # prepend Kernel
709
- # end
710
- # ````
711
- # would give:
712
- # ```ruby
713
- # Foo.ancestors #=> [Kernel, Foo, Object, Kernel, BasicObject]
714
- # ````
715
- # but since we drop `Kernel` whenever we match it, we would miss
716
- # the `prepend Kernel` in the output.
717
- #
718
- # Instead, we should only drop the tail matches of the ancestors and
719
- # inherited ancestors, past the location of the constant itself.
720
- constant.ancestors.reject do |mod|
721
- inherited_ancestors_ids.include?(object_id_of(mod))
722
- end
723
- end
724
-
725
- sig { params(constant: Module).returns(T.nilable(String)) }
726
- def name_of(constant)
727
- name = name_of_proxy_target(constant, super(class_of(constant)))
728
- return name if name
729
- name = super(constant)
730
- return if name.nil?
731
- return unless are_equal?(constant, constantize(name, inherit: true))
732
- name = "Struct" if name =~ /^(::)?Struct::[^:]+$/
733
- name
734
- end
735
-
736
- sig { params(constant: T.all(Module, T::Generic)).returns(String) }
737
- def generic_name_of(constant)
738
- type_name = T.must(constant.name)
739
- return type_name if type_name =~ /\[.*\]$/
740
-
741
- type_variables = Tapioca::GenericTypeRegistry.lookup_type_variables(constant)
742
- return type_name unless type_variables
743
-
744
- type_variable_names = type_variables.map { "T.untyped" }.join(", ")
745
-
746
- "#{type_name}[#{type_variable_names}]"
747
- end
748
-
749
- sig { params(constant: Module, class_name: T.nilable(String)).returns(T.nilable(String)) }
750
- def name_of_proxy_target(constant, class_name)
751
- return unless class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy"
752
- # We are dealing with a ActiveSupport::Deprecation::DeprecatedConstantProxy
753
- # so try to get the name of the target class
754
- begin
755
- target = constant.__send__(:target)
756
- rescue NoMethodError
757
- return
758
- end
759
-
760
- name_of(target)
761
- end
762
-
763
- sig { params(name: String).returns(T::Array[RBI::Comment]) }
764
- def documentation_comments(name)
765
- return [] unless @include_doc
766
-
767
- yard_docs = YARD::Registry.at(name)
768
- return [] unless yard_docs
769
-
770
- docstring = yard_docs.docstring
771
- return [] if /(copyright|license)/i.match?(docstring)
772
-
773
- docstring.lines
774
- .reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } }
775
- .map! { |line| RBI::Comment.new(line) }
776
- end
777
- end
778
- end
779
- end
780
- end