tapioca 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) 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 +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 +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 +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 +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 +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 +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 +10 -8
  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 +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/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  66. data/lib/tapioca/helpers/shims_helper.rb +87 -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/internal.rb +17 -10
  72. data/lib/tapioca/rbi_ext/model.rb +2 -48
  73. data/lib/tapioca/rbi_formatter.rb +37 -0
  74. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  75. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  76. data/lib/tapioca/runtime/loader.rb +123 -0
  77. data/lib/tapioca/runtime/reflection.rb +153 -0
  78. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  79. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  80. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  81. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  82. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  83. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +33 -15
  84. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  85. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  86. data/lib/tapioca/static/symbol_loader.rb +83 -0
  87. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  88. data/lib/tapioca/version.rb +1 -1
  89. data/lib/tapioca.rb +2 -7
  90. metadata +80 -60
  91. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  92. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  93. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  94. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  95. data/lib/tapioca/compilers/sorbet.rb +0 -59
  96. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  97. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  98. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  99. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  100. data/lib/tapioca/generators/todo.rb +0 -76
  101. data/lib/tapioca/generators.rb +0 -9
  102. data/lib/tapioca/generic_type_registry.rb +0 -164
  103. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  104. data/lib/tapioca/loader.rb +0 -119
  105. data/lib/tapioca/reflection.rb +0 -151
  106. data/lib/tapioca/trackers/autoload.rb +0 -70
  107. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  108. 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