tapioca 0.5.0 → 0.5.4
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/lib/tapioca/cli.rb +55 -139
- data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
- data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
- data/lib/tapioca/compilers/dsl/active_support_concern.rb +0 -2
- data/lib/tapioca/compilers/dsl/base.rb +12 -0
- data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +4 -4
- data/lib/tapioca/compilers/dsl_compiler.rb +7 -6
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
- data/lib/tapioca/compilers/sorbet.rb +0 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +84 -153
- data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
- data/lib/tapioca/config.rb +1 -0
- data/lib/tapioca/config_builder.rb +1 -0
- data/lib/tapioca/gemfile.rb +11 -5
- data/lib/tapioca/generators/base.rb +61 -0
- data/lib/tapioca/generators/dsl.rb +362 -0
- data/lib/tapioca/generators/gem.rb +345 -0
- data/lib/tapioca/generators/init.rb +79 -0
- data/lib/tapioca/generators/require.rb +52 -0
- data/lib/tapioca/generators/todo.rb +76 -0
- data/lib/tapioca/generators.rb +9 -0
- data/lib/tapioca/internal.rb +1 -2
- data/lib/tapioca/loader.rb +2 -2
- data/lib/tapioca/rbi_ext/model.rb +44 -0
- data/lib/tapioca/reflection.rb +8 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -0
- metadata +16 -6
- data/lib/tapioca/generator.rb +0 -717
@@ -0,0 +1,198 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class DynamicMixinCompiler
|
5
|
+
extend T::Sig
|
6
|
+
include Tapioca::Reflection
|
7
|
+
|
8
|
+
sig { returns(T::Array[Module]) }
|
9
|
+
attr_reader :dynamic_extends, :dynamic_includes
|
10
|
+
|
11
|
+
sig { returns(T::Array[Symbol]) }
|
12
|
+
attr_reader :class_attribute_readers, :class_attribute_writers, :class_attribute_predicates
|
13
|
+
|
14
|
+
sig { returns(T::Array[Symbol]) }
|
15
|
+
attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
|
16
|
+
|
17
|
+
sig { params(constant: Module).void }
|
18
|
+
def initialize(constant)
|
19
|
+
@constant = constant
|
20
|
+
mixins_from_modules = {}.compare_by_identity
|
21
|
+
class_attribute_readers = T.let([], T::Array[Symbol])
|
22
|
+
class_attribute_writers = T.let([], T::Array[Symbol])
|
23
|
+
class_attribute_predicates = T.let([], T::Array[Symbol])
|
24
|
+
|
25
|
+
instance_attribute_readers = T.let([], T::Array[Symbol])
|
26
|
+
instance_attribute_writers = T.let([], T::Array[Symbol])
|
27
|
+
instance_attribute_predicates = T.let([], T::Array[Symbol])
|
28
|
+
|
29
|
+
Class.new do
|
30
|
+
# Override the `self.include` method
|
31
|
+
define_singleton_method(:include) do |mod|
|
32
|
+
# Take a snapshot of the list of singleton class ancestors
|
33
|
+
# before the actual include
|
34
|
+
before = singleton_class.ancestors
|
35
|
+
# Call the actual `include` method with the supplied module
|
36
|
+
super(mod).tap do
|
37
|
+
# Take a snapshot of the list of singleton class ancestors
|
38
|
+
# after the actual include
|
39
|
+
after = singleton_class.ancestors
|
40
|
+
# The difference is the modules that are added to the list
|
41
|
+
# of ancestors of the singleton class. Those are all the
|
42
|
+
# modules that were `extend`ed due to the `include` call.
|
43
|
+
#
|
44
|
+
# We record those modules on our lookup table keyed by
|
45
|
+
# the included module with the values being all the modules
|
46
|
+
# that that module pulls into the singleton class.
|
47
|
+
#
|
48
|
+
# We need to reverse the order, since the extend order should
|
49
|
+
# be the inverse of the ancestor order. That is, earlier
|
50
|
+
# extended modules would be later in the ancestor chain.
|
51
|
+
mixins_from_modules[mod] = (after - before).reverse!
|
52
|
+
end
|
53
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
54
|
+
# this is a best effort, bail if we can't perform this
|
55
|
+
end
|
56
|
+
|
57
|
+
define_singleton_method(:class_attribute) do |*attrs, **kwargs|
|
58
|
+
class_attribute_readers.concat(attrs)
|
59
|
+
class_attribute_writers.concat(attrs)
|
60
|
+
|
61
|
+
instance_predicate = kwargs.fetch(:instance_predicate, true)
|
62
|
+
instance_accessor = kwargs.fetch(:instance_accessor, true)
|
63
|
+
instance_reader = kwargs.fetch(:instance_reader, instance_accessor)
|
64
|
+
instance_writer = kwargs.fetch(:instance_writer, instance_accessor)
|
65
|
+
|
66
|
+
if instance_reader
|
67
|
+
instance_attribute_readers.concat(attrs)
|
68
|
+
end
|
69
|
+
|
70
|
+
if instance_writer
|
71
|
+
instance_attribute_writers.concat(attrs)
|
72
|
+
end
|
73
|
+
|
74
|
+
if instance_predicate
|
75
|
+
class_attribute_predicates.concat(attrs)
|
76
|
+
|
77
|
+
if instance_reader
|
78
|
+
instance_attribute_predicates.concat(attrs)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
super(*attrs, **kwargs) if defined?(super)
|
83
|
+
end
|
84
|
+
|
85
|
+
# rubocop:disable Style/MissingRespondToMissing
|
86
|
+
T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
|
87
|
+
def method_missing(symbol, *args)
|
88
|
+
# We need this here so that we can handle any random instance
|
89
|
+
# method calls on the fake including class that may be done by
|
90
|
+
# the included module during the `self.included` hook.
|
91
|
+
end
|
92
|
+
|
93
|
+
class << self
|
94
|
+
extend T::Sig
|
95
|
+
|
96
|
+
T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
|
97
|
+
def method_missing(symbol, *args)
|
98
|
+
# Similarly, we need this here so that we can handle any
|
99
|
+
# random class method calls on the fake including class
|
100
|
+
# that may be done by the included module during the
|
101
|
+
# `self.included` hook.
|
102
|
+
end
|
103
|
+
end
|
104
|
+
# rubocop:enable Style/MissingRespondToMissing
|
105
|
+
end.include(constant)
|
106
|
+
|
107
|
+
# The value that corresponds to the original included constant
|
108
|
+
# is the list of all dynamically extended modules because of that
|
109
|
+
# constant. We grab that value by deleting the key for the original
|
110
|
+
# constant.
|
111
|
+
@dynamic_extends = T.let(mixins_from_modules.delete(constant) || [], T::Array[Module])
|
112
|
+
|
113
|
+
# Since we deleted the original constant from the list of keys, all
|
114
|
+
# the keys that remain are the ones that are dynamically included modules
|
115
|
+
# during the include of the original constant.
|
116
|
+
@dynamic_includes = T.let(mixins_from_modules.keys, T::Array[Module])
|
117
|
+
|
118
|
+
@class_attribute_readers = T.let(class_attribute_readers, T::Array[Symbol])
|
119
|
+
@class_attribute_writers = T.let(class_attribute_writers, T::Array[Symbol])
|
120
|
+
@class_attribute_predicates = T.let(class_attribute_predicates, T::Array[Symbol])
|
121
|
+
|
122
|
+
@instance_attribute_readers = T.let(instance_attribute_readers, T::Array[Symbol])
|
123
|
+
@instance_attribute_writers = T.let(instance_attribute_writers, T::Array[Symbol])
|
124
|
+
@instance_attribute_predicates = T.let(instance_attribute_predicates, T::Array[Symbol])
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { returns(T::Boolean) }
|
128
|
+
def empty_attributes?
|
129
|
+
@class_attribute_readers.empty? && @class_attribute_writers.empty?
|
130
|
+
end
|
131
|
+
|
132
|
+
sig { params(tree: RBI::Tree).void }
|
133
|
+
def compile_class_attributes(tree)
|
134
|
+
return if empty_attributes?
|
135
|
+
|
136
|
+
# Create a synthetic module to hold the generated class methods
|
137
|
+
tree << RBI::Module.new("GeneratedClassMethods") do |mod|
|
138
|
+
class_attribute_readers.each do |attribute|
|
139
|
+
mod << RBI::Method.new(attribute.to_s)
|
140
|
+
end
|
141
|
+
|
142
|
+
class_attribute_writers.each do |attribute|
|
143
|
+
mod << RBI::Method.new("#{attribute}=") do |method|
|
144
|
+
method << RBI::Param.new("value")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class_attribute_predicates.each do |attribute|
|
149
|
+
mod << RBI::Method.new("#{attribute}?")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Create a synthetic module to hold the generated instance methods
|
154
|
+
tree << RBI::Module.new("GeneratedInstanceMethods") do |mod|
|
155
|
+
instance_attribute_readers.each do |attribute|
|
156
|
+
mod << RBI::Method.new(attribute.to_s)
|
157
|
+
end
|
158
|
+
|
159
|
+
instance_attribute_writers.each do |attribute|
|
160
|
+
mod << RBI::Method.new("#{attribute}=") do |method|
|
161
|
+
method << RBI::Param.new("value")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
instance_attribute_predicates.each do |attribute|
|
166
|
+
mod << RBI::Method.new("#{attribute}?")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Add a mixes_in_class_methods and include for the generated modules
|
171
|
+
tree << RBI::MixesInClassMethods.new("GeneratedClassMethods")
|
172
|
+
tree << RBI::Include.new("GeneratedInstanceMethods")
|
173
|
+
end
|
174
|
+
|
175
|
+
sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
|
176
|
+
def compile_mixes_in_class_methods(tree)
|
177
|
+
includes = dynamic_includes.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
178
|
+
includes.each do |mod|
|
179
|
+
qname = qualified_name_of(mod)
|
180
|
+
tree << RBI::Include.new(T.must(qname))
|
181
|
+
end
|
182
|
+
|
183
|
+
# If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the
|
184
|
+
# constant itself
|
185
|
+
mixed_in_class_methods = dynamic_extends.select { |mod| mod != @constant }
|
186
|
+
return [[], []] if mixed_in_class_methods.empty?
|
187
|
+
|
188
|
+
mixed_in_class_methods.each do |mod|
|
189
|
+
qualified_name = qualified_name_of(mod)
|
190
|
+
next if qualified_name.nil? || qualified_name.empty?
|
191
|
+
tree << RBI::MixesInClassMethods.new(qualified_name)
|
192
|
+
end
|
193
|
+
|
194
|
+
[mixed_in_class_methods, includes]
|
195
|
+
rescue
|
196
|
+
[[], []] # silence errors
|
197
|
+
end
|
198
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "pathname"
|
@@ -10,36 +10,47 @@ module Tapioca
|
|
10
10
|
extend(T::Sig)
|
11
11
|
include(Reflection)
|
12
12
|
|
13
|
-
IGNORED_SYMBOLS = ["YAML", "MiniTest", "Mutex"]
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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)
|
19
33
|
@gem = gem
|
20
34
|
@indent = indent
|
21
|
-
@seen = Set.new
|
22
|
-
@alias_namespace
|
35
|
+
@seen = T.let(Set.new, T::Set[String])
|
36
|
+
@alias_namespace = T.let(Set.new, T::Set[String])
|
23
37
|
@symbol_queue = T.let(symbols.sort.dup, T::Array[String])
|
24
|
-
|
25
|
-
|
26
|
-
sig { returns(String) }
|
27
|
-
def generate
|
28
|
-
rbi = RBI::Tree.new
|
38
|
+
@symbols = T.let(nil, T.nilable(T::Set[String]))
|
39
|
+
@include_doc = include_doc
|
29
40
|
|
30
|
-
|
41
|
+
gem.parse_yard_docs if include_doc
|
42
|
+
end
|
31
43
|
|
32
|
-
|
33
|
-
|
34
|
-
rbi.
|
35
|
-
rbi.sort_nodes!
|
36
|
-
rbi.string
|
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?
|
37
47
|
end
|
38
48
|
|
39
49
|
private
|
40
50
|
|
51
|
+
sig { params(name: T.nilable(String)).void }
|
41
52
|
def add_to_symbol_queue(name)
|
42
|
-
@symbol_queue << name unless symbols.include?(name) || symbol_ignored?(name)
|
53
|
+
@symbol_queue << name unless name.nil? || symbols.include?(name) || symbol_ignored?(name)
|
43
54
|
end
|
44
55
|
|
45
56
|
sig { returns(T::Set[String]) }
|
@@ -154,28 +165,33 @@ module Tapioca
|
|
154
165
|
name_of(klass)
|
155
166
|
end
|
156
167
|
|
168
|
+
comments = documentation_comments(name)
|
169
|
+
|
157
170
|
if klass_name == "T::Private::Types::TypeAlias"
|
158
|
-
|
171
|
+
constant = RBI::Const.new(name, "T.type_alias { #{T.unsafe(value).aliased_type} }", comments: comments)
|
172
|
+
tree << constant
|
159
173
|
return
|
160
174
|
end
|
161
175
|
|
162
176
|
return if klass_name&.start_with?("T::Types::", "T::Private::")
|
163
177
|
|
164
178
|
type_name = klass_name || "T.untyped"
|
179
|
+
constant = RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})", comments: comments)
|
165
180
|
|
166
|
-
tree <<
|
181
|
+
tree << constant
|
167
182
|
end
|
168
183
|
|
169
184
|
sig { params(tree: RBI::Tree, name: String, constant: Module).void }
|
170
185
|
def compile_module(tree, name, constant)
|
171
186
|
return unless defined_in_gem?(constant, strict: false)
|
172
187
|
|
188
|
+
comments = documentation_comments(name)
|
173
189
|
scope =
|
174
190
|
if constant.is_a?(Class)
|
175
191
|
superclass = compile_superclass(constant)
|
176
|
-
RBI::Class.new(name, superclass_name: superclass)
|
192
|
+
RBI::Class.new(name, superclass_name: superclass, comments: comments)
|
177
193
|
else
|
178
|
-
RBI::Module.new(name)
|
194
|
+
RBI::Module.new(name, comments: comments)
|
179
195
|
end
|
180
196
|
|
181
197
|
compile_body(scope, name, constant)
|
@@ -193,9 +209,22 @@ module Tapioca
|
|
193
209
|
compile_methods(tree, name, constant)
|
194
210
|
compile_module_helpers(tree, constant)
|
195
211
|
compile_mixins(tree, constant)
|
196
|
-
compile_mixes_in_class_methods(tree, constant)
|
197
212
|
compile_props(tree, constant)
|
198
213
|
compile_enums(tree, constant)
|
214
|
+
compile_dynamic_mixins(tree, constant)
|
215
|
+
end
|
216
|
+
|
217
|
+
sig { params(tree: RBI::Tree, constant: Module).void }
|
218
|
+
def compile_dynamic_mixins(tree, constant)
|
219
|
+
return if constant.is_a?(Class)
|
220
|
+
|
221
|
+
mixin_compiler = DynamicMixinCompiler.new(constant)
|
222
|
+
mixin_compiler.compile_class_attributes(tree)
|
223
|
+
dynamic_extends, dynamic_includes = mixin_compiler.compile_mixes_in_class_methods(tree)
|
224
|
+
|
225
|
+
(dynamic_includes + dynamic_extends).each do |mod|
|
226
|
+
add_to_symbol_queue(name_of(mod))
|
227
|
+
end
|
199
228
|
end
|
200
229
|
|
201
230
|
sig { params(tree: RBI::Tree, constant: Module).void }
|
@@ -391,130 +420,6 @@ module Tapioca
|
|
391
420
|
end
|
392
421
|
end
|
393
422
|
|
394
|
-
sig { params(constant: Module).returns([T::Array[Module], T::Array[Module]]) }
|
395
|
-
def collect_dynamic_mixins_of(constant)
|
396
|
-
mixins_from_modules = {}.compare_by_identity
|
397
|
-
|
398
|
-
Class.new do
|
399
|
-
# Override the `self.include` method
|
400
|
-
define_singleton_method(:include) do |mod|
|
401
|
-
# Take a snapshot of the list of singleton class ancestors
|
402
|
-
# before the actual include
|
403
|
-
before = singleton_class.ancestors
|
404
|
-
# Call the actual `include` method with the supplied module
|
405
|
-
include_result = super(mod)
|
406
|
-
# Take a snapshot of the list of singleton class ancestors
|
407
|
-
# after the actual include
|
408
|
-
after = singleton_class.ancestors
|
409
|
-
# The difference is the modules that are added to the list
|
410
|
-
# of ancestors of the singleton class. Those are all the
|
411
|
-
# modules that were `extend`ed due to the `include` call.
|
412
|
-
#
|
413
|
-
# We record those modules on our lookup table keyed by
|
414
|
-
# the included module with the values being all the modules
|
415
|
-
# that that module pulls into the singleton class.
|
416
|
-
#
|
417
|
-
# We need to reverse the order, since the extend order should
|
418
|
-
# be the inverse of the ancestor order. That is, earlier
|
419
|
-
# extended modules would be later in the ancestor chain.
|
420
|
-
mixins_from_modules[mod] = (after - before).reverse!
|
421
|
-
|
422
|
-
include_result
|
423
|
-
rescue Exception # rubocop:disable Lint/RescueException
|
424
|
-
# this is a best effort, bail if we can't perform this
|
425
|
-
end
|
426
|
-
|
427
|
-
# rubocop:disable Style/MissingRespondToMissing
|
428
|
-
def method_missing(symbol, *args)
|
429
|
-
# We need this here so that we can handle any random instance
|
430
|
-
# method calls on the fake including class that may be done by
|
431
|
-
# the included module during the `self.included` hook.
|
432
|
-
end
|
433
|
-
|
434
|
-
class << self
|
435
|
-
def method_missing(symbol, *args)
|
436
|
-
# Similarly, we need this here so that we can handle any
|
437
|
-
# random class method calls on the fake including class
|
438
|
-
# that may be done by the included module during the
|
439
|
-
# `self.included` hook.
|
440
|
-
end
|
441
|
-
end
|
442
|
-
# rubocop:enable Style/MissingRespondToMissing
|
443
|
-
end.include(constant)
|
444
|
-
|
445
|
-
[
|
446
|
-
# The value that corresponds to the original included constant
|
447
|
-
# is the list of all dynamically extended modules because of that
|
448
|
-
# constant. We grab that value by deleting the key for the original
|
449
|
-
# constant.
|
450
|
-
T.must(mixins_from_modules.delete(constant)),
|
451
|
-
# Since we deleted the original constant from the list of keys, all
|
452
|
-
# the keys that remain are the ones that are dynamically included modules
|
453
|
-
# during the include of the original constant.
|
454
|
-
mixins_from_modules.keys,
|
455
|
-
]
|
456
|
-
end
|
457
|
-
|
458
|
-
sig { params(constant: Module, dynamic_extends: T::Array[Module]).returns(T::Array[Module]) }
|
459
|
-
def collect_mixed_in_class_methods(constant, dynamic_extends)
|
460
|
-
if Tapioca::Compilers::Sorbet.supports?(:mixes_in_class_methods_multiple_args)
|
461
|
-
# If we can generate multiple mixes_in_class_methods, then
|
462
|
-
# we want to use all dynamic extends that are not the constant itself
|
463
|
-
return dynamic_extends.select { |mod| mod != constant }
|
464
|
-
end
|
465
|
-
|
466
|
-
# For older Sorbet version, we do an explicit check for an AS::Concern
|
467
|
-
# related ClassMethods module.
|
468
|
-
ancestors = singleton_class_of(constant).ancestors
|
469
|
-
extends_as_concern = ancestors.any? do |mod|
|
470
|
-
qualified_name_of(mod) == "::ActiveSupport::Concern"
|
471
|
-
end
|
472
|
-
class_methods_module = resolve_constant("#{name_of(constant)}::ClassMethods")
|
473
|
-
|
474
|
-
mixed_in_module = if extends_as_concern && Module === class_methods_module
|
475
|
-
# If this module is a concern and the ClassMethods module exists
|
476
|
-
# then, we prefer to generate a mixes_in_class_methods call for
|
477
|
-
# that module only, since we only have a single shot.
|
478
|
-
class_methods_module
|
479
|
-
else
|
480
|
-
# Otherwise, we use the first dynamic extend module that is not
|
481
|
-
# the constant itself. We don't have a better heuristic in the
|
482
|
-
# absence of being able to supply multiple arguments.
|
483
|
-
dynamic_extends.find { |mod| mod != constant }
|
484
|
-
end
|
485
|
-
|
486
|
-
Array(mixed_in_module)
|
487
|
-
end
|
488
|
-
|
489
|
-
sig { params(tree: RBI::Tree, constant: Module).void }
|
490
|
-
def compile_mixes_in_class_methods(tree, constant)
|
491
|
-
return if constant.is_a?(Class)
|
492
|
-
|
493
|
-
dynamic_extends, dynamic_includes = collect_dynamic_mixins_of(constant)
|
494
|
-
|
495
|
-
dynamic_includes
|
496
|
-
.select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
|
497
|
-
.map do |mod|
|
498
|
-
add_to_symbol_queue(name_of(mod))
|
499
|
-
|
500
|
-
qname = qualified_name_of(mod)
|
501
|
-
tree << RBI::Include.new(T.must(qname))
|
502
|
-
end
|
503
|
-
|
504
|
-
mixed_in_class_methods = collect_mixed_in_class_methods(constant, dynamic_extends)
|
505
|
-
return if mixed_in_class_methods.empty?
|
506
|
-
|
507
|
-
mixed_in_class_methods.each do |mod|
|
508
|
-
add_to_symbol_queue(name_of(mod))
|
509
|
-
|
510
|
-
qualified_name = qualified_name_of(mod)
|
511
|
-
next if qualified_name.nil? || qualified_name.empty?
|
512
|
-
tree << RBI::MixesInClassMethods.new(qualified_name)
|
513
|
-
end
|
514
|
-
rescue
|
515
|
-
nil # silence errors
|
516
|
-
end
|
517
|
-
|
518
423
|
sig { params(tree: RBI::Tree, name: String, constant: Module).void }
|
519
424
|
def compile_methods(tree, name, constant)
|
520
425
|
compile_method(
|
@@ -630,7 +535,15 @@ module Tapioca
|
|
630
535
|
[type, name]
|
631
536
|
end
|
632
537
|
|
633
|
-
|
538
|
+
separator = constant.singleton_class? ? "." : "#"
|
539
|
+
comments = documentation_comments("#{symbol_name}#{separator}#{method_name}")
|
540
|
+
rbi_method = RBI::Method.new(
|
541
|
+
method_name,
|
542
|
+
is_singleton: constant.singleton_class?,
|
543
|
+
visibility: visibility,
|
544
|
+
comments: comments
|
545
|
+
)
|
546
|
+
|
634
547
|
rbi_method.sigs << compile_signature(signature, sanitized_parameters) if signature
|
635
548
|
|
636
549
|
sanitized_parameters.each do |type, name|
|
@@ -710,8 +623,10 @@ module Tapioca
|
|
710
623
|
SymbolLoader.ignore_symbol?(symbol_name)
|
711
624
|
end
|
712
625
|
|
713
|
-
SPECIAL_METHOD_NAMES = [
|
714
|
-
|
626
|
+
SPECIAL_METHOD_NAMES = T.let([
|
627
|
+
"!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^",
|
628
|
+
"<", "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"
|
629
|
+
], T::Array[String])
|
715
630
|
|
716
631
|
sig { params(name: String).returns(T::Boolean) }
|
717
632
|
def valid_method_name?(name)
|
@@ -770,6 +685,7 @@ module Tapioca
|
|
770
685
|
@seen.include?(name)
|
771
686
|
end
|
772
687
|
|
688
|
+
sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
|
773
689
|
def initialize_method_for(constant)
|
774
690
|
constant.instance_method(:initialize)
|
775
691
|
rescue
|
@@ -840,6 +756,21 @@ module Tapioca
|
|
840
756
|
|
841
757
|
name_of(target)
|
842
758
|
end
|
759
|
+
|
760
|
+
sig { params(name: String).returns(T::Array[RBI::Comment]) }
|
761
|
+
def documentation_comments(name)
|
762
|
+
return [] unless @include_doc
|
763
|
+
|
764
|
+
yard_docs = YARD::Registry.at(name)
|
765
|
+
return [] unless yard_docs
|
766
|
+
|
767
|
+
docstring = yard_docs.docstring
|
768
|
+
return [] if /(copyright|license)/i.match?(docstring)
|
769
|
+
|
770
|
+
docstring.lines
|
771
|
+
.reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } }
|
772
|
+
.map! { |line| RBI::Comment.new(line) }
|
773
|
+
end
|
843
774
|
end
|
844
775
|
end
|
845
776
|
end
|
@@ -6,17 +6,11 @@ module Tapioca
|
|
6
6
|
class SymbolTableCompiler
|
7
7
|
extend(T::Sig)
|
8
8
|
|
9
|
-
sig
|
10
|
-
|
11
|
-
|
12
|
-
indent
|
13
|
-
|
14
|
-
end
|
15
|
-
def compile(
|
16
|
-
gem,
|
17
|
-
indent = 0
|
18
|
-
)
|
19
|
-
Tapioca::Compilers::SymbolTable::SymbolGenerator.new(gem, indent).generate
|
9
|
+
sig { params(gem: Gemfile::GemSpec, rbi: RBI::File, indent: Integer, include_docs: T::Boolean).void }
|
10
|
+
def compile(gem, rbi, indent = 0, include_docs = false)
|
11
|
+
Tapioca::Compilers::SymbolTable::SymbolGenerator
|
12
|
+
.new(gem, indent, include_docs)
|
13
|
+
.generate(rbi)
|
20
14
|
end
|
21
15
|
end
|
22
16
|
end
|
data/lib/tapioca/config.rb
CHANGED
data/lib/tapioca/gemfile.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler"
|
5
|
+
require "logger"
|
6
|
+
require "yard-sorbet"
|
5
7
|
|
6
8
|
module Tapioca
|
7
9
|
class Gemfile
|
@@ -9,10 +11,7 @@ module Tapioca
|
|
9
11
|
|
10
12
|
Spec = T.type_alias do
|
11
13
|
T.any(
|
12
|
-
|
13
|
-
::Bundler::StubSpecification,
|
14
|
-
::Bundler::RemoteSpecification
|
15
|
-
),
|
14
|
+
::Bundler::StubSpecification,
|
16
15
|
::Gem::Specification
|
17
16
|
)
|
18
17
|
end
|
@@ -116,7 +115,9 @@ module Tapioca
|
|
116
115
|
sig { returns(T::Array[Pathname]) }
|
117
116
|
def files
|
118
117
|
if default_gem?
|
119
|
-
|
118
|
+
# `Bundler::RemoteSpecification` delegates missing methods to
|
119
|
+
# `Gem::Specification`, so `files` actually always exists on spec.
|
120
|
+
T.unsafe(@spec).files.map do |file|
|
120
121
|
ruby_lib_dir.join(file)
|
121
122
|
end
|
122
123
|
else
|
@@ -145,6 +146,11 @@ module Tapioca
|
|
145
146
|
end
|
146
147
|
end
|
147
148
|
|
149
|
+
sig { void }
|
150
|
+
def parse_yard_docs
|
151
|
+
files.each { |path| YARD.parse(path.to_s, [], Logger::Severity::FATAL) }
|
152
|
+
end
|
153
|
+
|
148
154
|
private
|
149
155
|
|
150
156
|
sig { returns(T::Boolean) }
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# TODO: Remove me when logging logic has been abstracted.
|
5
|
+
require "thor"
|
6
|
+
|
7
|
+
module Tapioca
|
8
|
+
module Generators
|
9
|
+
class Base
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
class FileWriter < Thor
|
14
|
+
include Thor::Actions
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: Remove me when logging logic has been abstracted
|
18
|
+
include Thor::Base
|
19
|
+
|
20
|
+
abstract!
|
21
|
+
|
22
|
+
sig { params(default_command: String, file_writer: Thor::Actions).void }
|
23
|
+
def initialize(default_command:, file_writer: FileWriter.new)
|
24
|
+
@file_writer = file_writer
|
25
|
+
@default_command = default_command
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { abstract.void }
|
29
|
+
def generate; end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# TODO: Remove me when logging logic has been abstracted
|
34
|
+
sig { params(message: String, color: T.any(Symbol, T::Array[Symbol])).void }
|
35
|
+
def say_error(message = "", *color)
|
36
|
+
force_new_line = (message.to_s !~ /( |\t)\Z/)
|
37
|
+
# NOTE: This is a hack. We're no longer subclassing from Thor::Shell::Color
|
38
|
+
# so we no longer have access to the prepare_message call.
|
39
|
+
# We should update this to remove this.
|
40
|
+
buffer = shell.send(:prepare_message, *T.unsafe([message, *T.unsafe(color)]))
|
41
|
+
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
|
42
|
+
|
43
|
+
$stderr.print(buffer)
|
44
|
+
$stderr.flush
|
45
|
+
end
|
46
|
+
|
47
|
+
sig do
|
48
|
+
params(
|
49
|
+
path: T.any(String, Pathname),
|
50
|
+
content: String,
|
51
|
+
force: T::Boolean,
|
52
|
+
skip: T::Boolean,
|
53
|
+
verbose: T::Boolean
|
54
|
+
).void
|
55
|
+
end
|
56
|
+
def create_file(path, content, force: true, skip: false, verbose: true)
|
57
|
+
@file_writer.create_file(path, force: force, skip: skip, verbose: verbose) { content }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|