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.
- checksums.yaml +4 -4
- data/Gemfile +8 -2
- data/README.md +27 -15
- data/Rakefile +10 -14
- data/lib/tapioca/cli.rb +65 -80
- data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
- data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
- data/lib/tapioca/{generators → commands}/gem.rb +93 -30
- data/lib/tapioca/{generators → commands}/init.rb +9 -13
- data/lib/tapioca/{generators → commands}/require.rb +8 -10
- data/lib/tapioca/commands/todo.rb +86 -0
- data/lib/tapioca/commands.rb +13 -0
- data/lib/tapioca/dsl/compiler.rb +185 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +12 -8
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +22 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
- data/lib/tapioca/dsl/compilers.rb +31 -0
- data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
- data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +6 -3
- data/lib/tapioca/dsl/pipeline.rb +169 -0
- data/lib/tapioca/gem/events.rb +120 -0
- data/lib/tapioca/gem/listeners/base.rb +48 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
- data/lib/tapioca/gem/listeners/methods.rb +183 -0
- data/lib/tapioca/gem/listeners/mixins.rb +101 -0
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
- data/lib/tapioca/gem/listeners.rb +16 -0
- data/lib/tapioca/gem/pipeline.rb +365 -0
- data/lib/tapioca/helpers/cli_helper.rb +7 -0
- data/lib/tapioca/helpers/config_helper.rb +5 -8
- data/lib/tapioca/helpers/shims_helper.rb +87 -0
- data/lib/tapioca/helpers/signatures_helper.rb +17 -0
- data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
- data/lib/tapioca/helpers/test/isolation.rb +1 -1
- data/lib/tapioca/helpers/test/template.rb +13 -2
- data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
- data/lib/tapioca/internal.rb +18 -10
- data/lib/tapioca/rbi_ext/model.rb +14 -50
- data/lib/tapioca/rbi_formatter.rb +37 -0
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
- data/lib/tapioca/runtime/generic_type_registry.rb +168 -0
- data/lib/tapioca/runtime/loader.rb +123 -0
- data/lib/tapioca/runtime/reflection.rb +157 -0
- data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
- data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
- data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
- data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +69 -34
- data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
- data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
- data/lib/tapioca/static/symbol_loader.rb +83 -0
- data/lib/tapioca/static/symbol_table_parser.rb +63 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -7
- metadata +83 -62
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
- data/lib/tapioca/compilers/dsl/base.rb +0 -195
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
- data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
- data/lib/tapioca/compilers/sorbet.rb +0 -59
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
- data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
- data/lib/tapioca/compilers/todos_compiler.rb +0 -32
- data/lib/tapioca/generators/todo.rb +0 -76
- data/lib/tapioca/generators.rb +0 -9
- data/lib/tapioca/generic_type_registry.rb +0 -164
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
- data/lib/tapioca/loader.rb +0 -119
- data/lib/tapioca/reflection.rb +0 -151
- data/lib/tapioca/trackers/autoload.rb +0 -70
- data/lib/tapioca/trackers/constant_definition.rb +0 -42
- data/lib/tapioca/trackers/mixin.rb +0 -78
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Tapioca
|
|
7
|
+
module Gem
|
|
8
|
+
class Pipeline
|
|
9
|
+
extend T::Sig
|
|
10
|
+
include Runtime::Reflection
|
|
11
|
+
include SignaturesHelper
|
|
12
|
+
|
|
13
|
+
IGNORED_SYMBOLS = T.let(["YAML", "MiniTest", "Mutex"], T::Array[String])
|
|
14
|
+
|
|
15
|
+
sig { returns(Gemfile::GemSpec) }
|
|
16
|
+
attr_reader :gem
|
|
17
|
+
|
|
18
|
+
sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean).void }
|
|
19
|
+
def initialize(gem, include_doc: false)
|
|
20
|
+
@root = T.let(RBI::Tree.new, RBI::Tree)
|
|
21
|
+
@gem = gem
|
|
22
|
+
@seen = T.let(Set.new, T::Set[String])
|
|
23
|
+
@alias_namespace = T.let(Set.new, T::Set[String])
|
|
24
|
+
|
|
25
|
+
@events = T.let([], T::Array[Gem::Event])
|
|
26
|
+
|
|
27
|
+
@payload_symbols = T.let(Static::SymbolLoader.payload_symbols, T::Set[String])
|
|
28
|
+
@bootstrap_symbols = T.let(Static::SymbolLoader.gem_symbols(@gem).union(Static::SymbolLoader.engine_symbols),
|
|
29
|
+
T::Set[String])
|
|
30
|
+
@bootstrap_symbols.each { |symbol| push_symbol(symbol) }
|
|
31
|
+
|
|
32
|
+
@node_listeners = T.let([], T::Array[Gem::Listeners::Base])
|
|
33
|
+
@node_listeners << Gem::Listeners::SorbetTypeVariables.new(self)
|
|
34
|
+
@node_listeners << Gem::Listeners::Mixins.new(self)
|
|
35
|
+
@node_listeners << Gem::Listeners::DynamicMixins.new(self)
|
|
36
|
+
@node_listeners << Gem::Listeners::Methods.new(self)
|
|
37
|
+
@node_listeners << Gem::Listeners::SorbetHelpers.new(self)
|
|
38
|
+
@node_listeners << Gem::Listeners::SorbetEnums.new(self)
|
|
39
|
+
@node_listeners << Gem::Listeners::SorbetProps.new(self)
|
|
40
|
+
@node_listeners << Gem::Listeners::SorbetRequiredAncestors.new(self)
|
|
41
|
+
@node_listeners << Gem::Listeners::SorbetSignatures.new(self)
|
|
42
|
+
@node_listeners << Gem::Listeners::Subconstants.new(self)
|
|
43
|
+
@node_listeners << Gem::Listeners::YardDoc.new(self) if include_doc
|
|
44
|
+
@node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { returns(RBI::Tree) }
|
|
48
|
+
def compile
|
|
49
|
+
dispatch(next_event) until @events.empty?
|
|
50
|
+
@root
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
sig { params(symbol: String).void }
|
|
54
|
+
def push_symbol(symbol)
|
|
55
|
+
@events << Gem::SymbolFound.new(symbol)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
|
|
59
|
+
def push_constant(symbol, constant)
|
|
60
|
+
@events << Gem::ConstantFound.new(symbol, constant)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
sig { params(symbol: String, constant: Module, node: RBI::Const).void.checked(:never) }
|
|
64
|
+
def push_const(symbol, constant, node)
|
|
65
|
+
@events << Gem::ConstNodeAdded.new(symbol, constant, node)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never) }
|
|
69
|
+
def push_scope(symbol, constant, node)
|
|
70
|
+
@events << Gem::ScopeNodeAdded.new(symbol, constant, node)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sig do
|
|
74
|
+
params(
|
|
75
|
+
symbol: String,
|
|
76
|
+
constant: Module,
|
|
77
|
+
node: RBI::Method,
|
|
78
|
+
signature: T.untyped,
|
|
79
|
+
parameters: T::Array[[Symbol, String]]
|
|
80
|
+
).void.checked(:never)
|
|
81
|
+
end
|
|
82
|
+
def push_method(symbol, constant, node, signature, parameters)
|
|
83
|
+
@events << Gem::MethodNodeAdded.new(symbol, constant, node, signature, parameters)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
sig { params(symbol_name: String).returns(T::Boolean) }
|
|
87
|
+
def symbol_in_payload?(symbol_name)
|
|
88
|
+
symbol_name = symbol_name[2..-1] if symbol_name.start_with?("::")
|
|
89
|
+
return false unless symbol_name
|
|
90
|
+
@payload_symbols.include?(symbol_name)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
sig { params(method: UnboundMethod).returns(T::Boolean) }
|
|
94
|
+
def method_in_gem?(method)
|
|
95
|
+
source_location = method.source_location&.first
|
|
96
|
+
return false if source_location.nil?
|
|
97
|
+
|
|
98
|
+
@gem.contains_path?(source_location)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
|
102
|
+
def name_of(constant)
|
|
103
|
+
name = name_of_proxy_target(constant, super(class_of(constant)))
|
|
104
|
+
return name if name
|
|
105
|
+
name = super(constant)
|
|
106
|
+
return if name.nil?
|
|
107
|
+
return unless are_equal?(constant, constantize(name, inherit: true))
|
|
108
|
+
name = "Struct" if name =~ /^(::)?Struct::[^:]+$/
|
|
109
|
+
name
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
sig { returns(Gem::Event) }
|
|
115
|
+
def next_event
|
|
116
|
+
T.must(@events.shift)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
sig { params(event: Gem::Event).void }
|
|
120
|
+
def dispatch(event)
|
|
121
|
+
case event
|
|
122
|
+
when Gem::SymbolFound
|
|
123
|
+
on_symbol(event)
|
|
124
|
+
when Gem::ConstantFound
|
|
125
|
+
on_constant(event)
|
|
126
|
+
when Gem::NodeAdded
|
|
127
|
+
on_node(event)
|
|
128
|
+
else
|
|
129
|
+
raise "Unsupported event #{event.class}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
sig { params(event: Gem::SymbolFound).void }
|
|
134
|
+
def on_symbol(event)
|
|
135
|
+
symbol = event.symbol.delete_prefix("::")
|
|
136
|
+
return if symbol_in_payload?(symbol) && !@bootstrap_symbols.include?(symbol)
|
|
137
|
+
|
|
138
|
+
constant = constantize(symbol)
|
|
139
|
+
push_constant(symbol, constant) if constant
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
sig { params(event: Gem::ConstantFound).void.checked(:never) }
|
|
143
|
+
def on_constant(event)
|
|
144
|
+
name = event.symbol
|
|
145
|
+
|
|
146
|
+
return if name.strip.empty?
|
|
147
|
+
return if name.start_with?("#<")
|
|
148
|
+
return if name.downcase == name
|
|
149
|
+
return if alias_namespaced?(name)
|
|
150
|
+
return if seen?(name)
|
|
151
|
+
|
|
152
|
+
constant = event.constant
|
|
153
|
+
return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
|
|
154
|
+
|
|
155
|
+
mark_seen(name)
|
|
156
|
+
compile_constant(name, constant)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
sig { params(event: Gem::NodeAdded).void }
|
|
160
|
+
def on_node(event)
|
|
161
|
+
@node_listeners.each { |listener| listener.dispatch(event) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Compile
|
|
165
|
+
|
|
166
|
+
sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
|
|
167
|
+
def compile_constant(symbol, constant)
|
|
168
|
+
case constant
|
|
169
|
+
when Module
|
|
170
|
+
if name_of(constant) != symbol
|
|
171
|
+
compile_alias(symbol, constant)
|
|
172
|
+
else
|
|
173
|
+
compile_module(symbol, constant)
|
|
174
|
+
end
|
|
175
|
+
else
|
|
176
|
+
compile_object(symbol, constant)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
sig { params(name: String, constant: Module).void }
|
|
181
|
+
def compile_alias(name, constant)
|
|
182
|
+
return if symbol_in_payload?(name)
|
|
183
|
+
|
|
184
|
+
target = name_of(constant)
|
|
185
|
+
# If target has no name, let's make it an anonymous class or module with `Class.new` or `Module.new`
|
|
186
|
+
target = "#{constant.class}.new" unless target
|
|
187
|
+
|
|
188
|
+
add_to_alias_namespace(name)
|
|
189
|
+
|
|
190
|
+
return if IGNORED_SYMBOLS.include?(name)
|
|
191
|
+
|
|
192
|
+
node = RBI::Const.new(name, target)
|
|
193
|
+
push_const(name, constant, node)
|
|
194
|
+
@root << node
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
sig { params(name: String, value: BasicObject).void.checked(:never) }
|
|
198
|
+
def compile_object(name, value)
|
|
199
|
+
return if symbol_in_payload?(name)
|
|
200
|
+
|
|
201
|
+
klass = class_of(value)
|
|
202
|
+
|
|
203
|
+
klass_name = if klass == ObjectSpace::WeakMap
|
|
204
|
+
# WeakMap is an implicit generic with one type variable
|
|
205
|
+
"ObjectSpace::WeakMap[T.untyped]"
|
|
206
|
+
elsif T::Generic === klass
|
|
207
|
+
generic_name_of(klass)
|
|
208
|
+
else
|
|
209
|
+
name_of(klass)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if klass_name == "T::Private::Types::TypeAlias"
|
|
213
|
+
type_alias = sanitize_signature_types(T.unsafe(value).aliased_type.to_s)
|
|
214
|
+
node = RBI::Const.new(name, "T.type_alias { #{type_alias} }")
|
|
215
|
+
push_const(name, klass, node)
|
|
216
|
+
@root << node
|
|
217
|
+
return
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
return if klass_name&.start_with?("T::Types::", "T::Private::")
|
|
221
|
+
|
|
222
|
+
type_name = klass_name || "T.untyped"
|
|
223
|
+
node = RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})")
|
|
224
|
+
push_const(name, klass, node)
|
|
225
|
+
@root << node
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
sig { params(name: String, constant: Module).void }
|
|
229
|
+
def compile_module(name, constant)
|
|
230
|
+
return unless defined_in_gem?(constant, strict: false)
|
|
231
|
+
return if Tapioca::TypeVariableModule === constant
|
|
232
|
+
|
|
233
|
+
scope =
|
|
234
|
+
if constant.is_a?(Class)
|
|
235
|
+
superclass = compile_superclass(constant)
|
|
236
|
+
RBI::Class.new(name, superclass_name: superclass)
|
|
237
|
+
else
|
|
238
|
+
RBI::Module.new(name)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
push_scope(name, constant, scope)
|
|
242
|
+
@root << scope
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
sig { params(constant: Class).returns(T.nilable(String)) }
|
|
246
|
+
def compile_superclass(constant)
|
|
247
|
+
superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
|
|
248
|
+
|
|
249
|
+
while (superclass = superclass_of(constant))
|
|
250
|
+
constant_name = name_of(constant)
|
|
251
|
+
constant = superclass
|
|
252
|
+
|
|
253
|
+
# Some types have "themselves" as their superclass
|
|
254
|
+
# which can happen via:
|
|
255
|
+
#
|
|
256
|
+
# class A < Numeric; end
|
|
257
|
+
# A = Class.new(A)
|
|
258
|
+
# A.superclass #=> A
|
|
259
|
+
#
|
|
260
|
+
# We compare names here to make sure we skip those
|
|
261
|
+
# superclass instances and walk up the chain.
|
|
262
|
+
#
|
|
263
|
+
# The name comparison is against the name of the constant
|
|
264
|
+
# resolved from the name of the superclass, since
|
|
265
|
+
# this is also possible:
|
|
266
|
+
#
|
|
267
|
+
# B = Class.new
|
|
268
|
+
# class A < B; end
|
|
269
|
+
# B = A
|
|
270
|
+
# A.superclass.name #=> "B"
|
|
271
|
+
# B #=> A
|
|
272
|
+
superclass_name = name_of(superclass)
|
|
273
|
+
next unless superclass_name
|
|
274
|
+
|
|
275
|
+
resolved_superclass = constantize(superclass_name)
|
|
276
|
+
next unless Module === resolved_superclass
|
|
277
|
+
next if name_of(resolved_superclass) == constant_name
|
|
278
|
+
|
|
279
|
+
# We found a suitable superclass
|
|
280
|
+
break
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
return if superclass == ::Object || superclass == ::Delegator
|
|
284
|
+
return if superclass.nil?
|
|
285
|
+
|
|
286
|
+
name = name_of(superclass)
|
|
287
|
+
return if name.nil? || name.empty?
|
|
288
|
+
|
|
289
|
+
push_symbol(name)
|
|
290
|
+
|
|
291
|
+
"::#{name}"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
sig { params(constant: Module, strict: T::Boolean).returns(T::Boolean) }
|
|
295
|
+
def defined_in_gem?(constant, strict: true)
|
|
296
|
+
files = Set.new(get_file_candidates(constant))
|
|
297
|
+
.merge(Runtime::Trackers::ConstantDefinition.files_for(constant))
|
|
298
|
+
|
|
299
|
+
return !strict if files.empty?
|
|
300
|
+
|
|
301
|
+
files.any? do |file|
|
|
302
|
+
@gem.contains_path?(file)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
sig { params(constant: Module).returns(T::Array[String]) }
|
|
307
|
+
def get_file_candidates(constant)
|
|
308
|
+
wrapped_module = Pry::WrappedModule.new(constant)
|
|
309
|
+
|
|
310
|
+
wrapped_module.candidates.map(&:file).to_a.compact
|
|
311
|
+
rescue ArgumentError, NameError
|
|
312
|
+
[]
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
sig { params(name: String).void }
|
|
316
|
+
def add_to_alias_namespace(name)
|
|
317
|
+
@alias_namespace.add("#{name}::")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
sig { params(name: String).returns(T::Boolean) }
|
|
321
|
+
def alias_namespaced?(name)
|
|
322
|
+
@alias_namespace.any? do |namespace|
|
|
323
|
+
name.start_with?(namespace)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
sig { params(name: String).void }
|
|
328
|
+
def mark_seen(name)
|
|
329
|
+
@seen.add(name)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
sig { params(name: String).returns(T::Boolean) }
|
|
333
|
+
def seen?(name)
|
|
334
|
+
@seen.include?(name)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
sig { params(constant: T.all(Module, T::Generic)).returns(String) }
|
|
338
|
+
def generic_name_of(constant)
|
|
339
|
+
type_name = T.must(constant.name)
|
|
340
|
+
return type_name if type_name =~ /\[.*\]$/
|
|
341
|
+
|
|
342
|
+
type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant)
|
|
343
|
+
return type_name unless type_variables
|
|
344
|
+
|
|
345
|
+
type_variable_names = type_variables.map { "T.untyped" }.join(", ")
|
|
346
|
+
|
|
347
|
+
"#{type_name}[#{type_variable_names}]"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
sig { params(constant: Module, class_name: T.nilable(String)).returns(T.nilable(String)) }
|
|
351
|
+
def name_of_proxy_target(constant, class_name)
|
|
352
|
+
return unless class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy"
|
|
353
|
+
# We are dealing with a ActiveSupport::Deprecation::DeprecatedConstantProxy
|
|
354
|
+
# so try to get the name of the target class
|
|
355
|
+
begin
|
|
356
|
+
target = constant.__send__(:target)
|
|
357
|
+
rescue NoMethodError
|
|
358
|
+
return
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
name_of(target)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
@@ -23,5 +23,12 @@ module Tapioca
|
|
|
23
23
|
super(message, color)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(RBIFormatter) }
|
|
28
|
+
def rbi_formatter(options)
|
|
29
|
+
rbi_formatter = DEFAULT_RBI_FORMATTER
|
|
30
|
+
rbi_formatter.max_line_length = options[:rbi_max_line_length]
|
|
31
|
+
rbi_formatter
|
|
32
|
+
end
|
|
26
33
|
end
|
|
27
34
|
end
|
|
@@ -104,10 +104,8 @@ module Tapioca
|
|
|
104
104
|
def validate_config_options(command_options, config_key, config_options)
|
|
105
105
|
config_options.map do |config_option_key, config_option_value|
|
|
106
106
|
command_option = command_options[config_option_key.to_sym]
|
|
107
|
-
|
|
108
|
-
unless command_option
|
|
109
|
-
next build_error("unknown option `#{config_option_key}` for key `#{config_key}`")
|
|
110
|
-
end
|
|
107
|
+
error_msg = "unknown option `#{config_option_key}` for key `#{config_key}`"
|
|
108
|
+
next build_error(error_msg) unless command_option
|
|
111
109
|
|
|
112
110
|
config_option_value_type = case config_option_value
|
|
113
111
|
when FalseClass, TrueClass
|
|
@@ -124,10 +122,9 @@ module Tapioca
|
|
|
124
122
|
:object
|
|
125
123
|
end
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
end
|
|
125
|
+
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
|
126
|
+
"`#{command_option.type.capitalize}` but found #{config_option_value_type.capitalize}"
|
|
127
|
+
next build_error(error_msg) unless config_option_value_type == command_option.type
|
|
131
128
|
end.compact
|
|
132
129
|
end
|
|
133
130
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
module ShimsHelper
|
|
6
|
+
extend T::Sig
|
|
7
|
+
extend T::Helpers
|
|
8
|
+
|
|
9
|
+
requires_ancestor { Thor::Shell }
|
|
10
|
+
|
|
11
|
+
sig { params(index: RBI::Index, kind: String, dir: String).void }
|
|
12
|
+
def index_rbis(index, kind, dir)
|
|
13
|
+
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
|
14
|
+
|
|
15
|
+
say("Loading #{kind} RBIs from #{dir}... ")
|
|
16
|
+
files = Dir.glob("#{dir}/**/*.rbi").sort
|
|
17
|
+
|
|
18
|
+
trees = files.map do |file|
|
|
19
|
+
RBI::Parser.parse_file(file)
|
|
20
|
+
rescue RBI::ParseError => e
|
|
21
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
|
22
|
+
end.compact
|
|
23
|
+
|
|
24
|
+
index.visit_all(trees)
|
|
25
|
+
say(" Done", :green)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { params(index: RBI::Index, shim_rbi_dir: String).returns(T::Hash[String, T::Array[RBI::Node]]) }
|
|
29
|
+
def duplicated_nodes_from_index(index, shim_rbi_dir)
|
|
30
|
+
duplicates = {}
|
|
31
|
+
say("Looking for duplicates... ")
|
|
32
|
+
index.keys.each do |key|
|
|
33
|
+
nodes = index[key]
|
|
34
|
+
next unless shims_have_duplicates?(nodes, shim_rbi_dir)
|
|
35
|
+
duplicates[key] = nodes
|
|
36
|
+
end
|
|
37
|
+
say(" Done", :green)
|
|
38
|
+
duplicates
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String).returns(T::Boolean) }
|
|
44
|
+
def shims_have_duplicates?(nodes, shim_rbi_dir)
|
|
45
|
+
return false if nodes.size == 1
|
|
46
|
+
|
|
47
|
+
shims = extract_shims(nodes, shim_rbi_dir)
|
|
48
|
+
return false if shims.empty?
|
|
49
|
+
|
|
50
|
+
props = extract_methods_and_attrs(shims)
|
|
51
|
+
return false if props.empty?
|
|
52
|
+
|
|
53
|
+
shims_with_sigs = extract_nodes_with_sigs(props)
|
|
54
|
+
shims_with_sigs.each do |shim|
|
|
55
|
+
shim_sigs = shim.sigs
|
|
56
|
+
|
|
57
|
+
extract_methods_and_attrs(nodes).each do |node|
|
|
58
|
+
next if node == shim
|
|
59
|
+
return true if shim_sigs.all? { |sig| node.sigs.include?(sig) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String).returns(T::Array[RBI::Node]) }
|
|
69
|
+
def extract_shims(nodes, shim_rbi_dir)
|
|
70
|
+
nodes.select do |node|
|
|
71
|
+
node.loc&.file&.start_with?(shim_rbi_dir)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
|
76
|
+
def extract_methods_and_attrs(nodes)
|
|
77
|
+
T.cast(nodes.select do |node|
|
|
78
|
+
node.is_a?(RBI::Method) || node.is_a?(RBI::Attr)
|
|
79
|
+
end, T::Array[T.any(RBI::Method, RBI::Attr)])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sig { params(nodes: T::Array[T.any(RBI::Method, RBI::Attr)]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
|
83
|
+
def extract_nodes_with_sigs(nodes)
|
|
84
|
+
nodes.reject { |node| node.sigs.empty? }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
module SignaturesHelper
|
|
6
|
+
extend T::Sig
|
|
7
|
+
|
|
8
|
+
sig { params(sig_string: String).returns(String) }
|
|
9
|
+
def sanitize_signature_types(sig_string)
|
|
10
|
+
sig_string
|
|
11
|
+
.gsub(".returns(<VOID>)", ".void")
|
|
12
|
+
.gsub("<VOID>", "void")
|
|
13
|
+
.gsub("<NOT-TYPED>", "T.untyped")
|
|
14
|
+
.gsub(".params()", "")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
|
|
7
|
+
module Tapioca
|
|
8
|
+
module SorbetHelper
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
SORBET_GEM_SPEC = T.let(
|
|
12
|
+
::Gem::Specification.find_by_name("sorbet-static"),
|
|
13
|
+
::Gem::Specification
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
SORBET_BIN = T.let(
|
|
17
|
+
Pathname.new(SORBET_GEM_SPEC.full_gem_path) / "libexec" / "sorbet",
|
|
18
|
+
Pathname
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
SORBET_EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
|
|
22
|
+
|
|
23
|
+
FEATURE_REQUIREMENTS = T.let({
|
|
24
|
+
to_ary_nil_support: ::Gem::Requirement.new(">= 0.5.9220"), # https://github.com/sorbet/sorbet/pull/4706
|
|
25
|
+
type_variable_block_syntax: ::Gem::Requirement.new(">= 0.5.9892"), # https://github.com/sorbet/sorbet/pull/5639
|
|
26
|
+
}.freeze, T::Hash[Symbol, ::Gem::Requirement])
|
|
27
|
+
|
|
28
|
+
class CmdResult < T::Struct
|
|
29
|
+
const :out, String
|
|
30
|
+
const :err, String
|
|
31
|
+
const :status, T::Boolean
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
sig { params(sorbet_args: String).returns(CmdResult) }
|
|
35
|
+
def sorbet(*sorbet_args)
|
|
36
|
+
out, err, status = Open3.capture3([sorbet_path, *sorbet_args].join(" "))
|
|
37
|
+
CmdResult.new(out: out, err: err, status: status.success? || false)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig { returns(String) }
|
|
41
|
+
def sorbet_path
|
|
42
|
+
sorbet_path = ENV.fetch(SORBET_EXE_PATH_ENV_VAR, SORBET_BIN)
|
|
43
|
+
sorbet_path = SORBET_BIN if sorbet_path.empty?
|
|
44
|
+
sorbet_path.to_s.shellescape
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { params(feature: Symbol, version: T.nilable(::Gem::Version)).returns(T::Boolean) }
|
|
48
|
+
def sorbet_supports?(feature, version: nil)
|
|
49
|
+
version = SORBET_GEM_SPEC.version unless version
|
|
50
|
+
requirement = FEATURE_REQUIREMENTS[feature]
|
|
51
|
+
|
|
52
|
+
Kernel.raise "Invalid Sorbet feature #{feature}" unless requirement
|
|
53
|
+
|
|
54
|
+
requirement.satisfied_by?(version)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "tapioca/helpers/test/content"
|
|
5
|
+
require "tapioca/helpers/test/isolation"
|
|
6
|
+
require "tapioca/helpers/test/template"
|
|
7
|
+
|
|
8
|
+
module Tapioca
|
|
9
|
+
module Helpers
|
|
10
|
+
module Test
|
|
11
|
+
module DslCompiler
|
|
12
|
+
extend T::Sig
|
|
13
|
+
extend T::Helpers
|
|
14
|
+
|
|
15
|
+
include Isolation
|
|
16
|
+
include Content
|
|
17
|
+
include Template
|
|
18
|
+
|
|
19
|
+
requires_ancestor { Kernel }
|
|
20
|
+
|
|
21
|
+
sig { params(compiler_class: T.class_of(Tapioca::Dsl::Compiler)).void }
|
|
22
|
+
def use_dsl_compiler(compiler_class)
|
|
23
|
+
@context = T.let(CompilerContext.new(compiler_class), T.nilable(CompilerContext))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
sig { params(compiler_classes: T.class_of(Tapioca::Dsl::Compiler)).void }
|
|
27
|
+
def activate_other_dsl_compilers(*compiler_classes)
|
|
28
|
+
context.activate_other_dsl_compilers(compiler_classes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { params(constant_name: T.any(Symbol, String)).returns(String) }
|
|
32
|
+
def rbi_for(constant_name)
|
|
33
|
+
context.rbi_for(constant_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { returns(T::Array[String]) }
|
|
37
|
+
def gathered_constants
|
|
38
|
+
context.gathered_constants
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
sig { returns(T::Array[String]) }
|
|
42
|
+
def generated_errors
|
|
43
|
+
context.errors
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sig { returns(CompilerContext) }
|
|
47
|
+
def context
|
|
48
|
+
raise "Please call `use_dsl_compiler` before" unless @context
|
|
49
|
+
@context
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class CompilerContext
|
|
53
|
+
extend T::Sig
|
|
54
|
+
|
|
55
|
+
sig { returns(T.class_of(Tapioca::Dsl::Compiler)) }
|
|
56
|
+
attr_reader :compiler_class
|
|
57
|
+
|
|
58
|
+
sig { returns(T::Array[T.class_of(Tapioca::Dsl::Compiler)]) }
|
|
59
|
+
attr_reader :other_compiler_classes
|
|
60
|
+
|
|
61
|
+
sig { params(compiler_class: T.class_of(Tapioca::Dsl::Compiler)).void }
|
|
62
|
+
def initialize(compiler_class)
|
|
63
|
+
@compiler_class = compiler_class
|
|
64
|
+
@other_compiler_classes = T.let([], T::Array[T.class_of(Tapioca::Dsl::Compiler)])
|
|
65
|
+
@pipeline = T.let(nil, T.nilable(Tapioca::Dsl::Pipeline))
|
|
66
|
+
@errors = T.let([], T::Array[String])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sig { params(compiler_classes: T::Array[T.class_of(Tapioca::Dsl::Compiler)]).void }
|
|
70
|
+
def activate_other_dsl_compilers(compiler_classes)
|
|
71
|
+
@other_compiler_classes = compiler_classes
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sig { returns(T::Array[T.class_of(Tapioca::Dsl::Compiler)]) }
|
|
75
|
+
def activated_compiler_classes
|
|
76
|
+
[compiler_class, *other_compiler_classes]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
sig { returns(T::Array[String]) }
|
|
80
|
+
def gathered_constants
|
|
81
|
+
compiler_class.processable_constants.map(&:name).compact.sort
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
sig { params(constant_name: T.any(Symbol, String)).returns(String) }
|
|
85
|
+
def rbi_for(constant_name)
|
|
86
|
+
# Make sure this is a constant that we can handle.
|
|
87
|
+
unless gathered_constants.include?(constant_name.to_s)
|
|
88
|
+
raise "`#{constant_name}` is not processable by the `#{compiler_class}` compiler."
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
file = RBI::File.new(strictness: "strong")
|
|
92
|
+
constant = Object.const_get(constant_name)
|
|
93
|
+
|
|
94
|
+
compiler = compiler_class.new(pipeline, file.root, constant)
|
|
95
|
+
compiler.decorate
|
|
96
|
+
|
|
97
|
+
Tapioca::DEFAULT_RBI_FORMATTER.print_file(file)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
sig { returns(T::Array[String]) }
|
|
101
|
+
def errors
|
|
102
|
+
pipeline.errors
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
sig { returns(Tapioca::Dsl::Pipeline) }
|
|
108
|
+
def pipeline
|
|
109
|
+
@pipeline ||= Tapioca::Dsl::Pipeline.new(
|
|
110
|
+
requested_constants: [],
|
|
111
|
+
requested_compilers: activated_compiler_classes
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -100,7 +100,7 @@ module Tapioca
|
|
|
100
100
|
load_path_args << File.expand_path(p)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
child = IO.popen([env, Gem.ruby, *load_path_args, $PROGRAM_NAME, *ORIG_ARGV, test_opts])
|
|
103
|
+
child = IO.popen([env, ::Gem.ruby, *load_path_args, $PROGRAM_NAME, *ORIG_ARGV, test_opts])
|
|
104
104
|
|
|
105
105
|
begin
|
|
106
106
|
Process.wait(child.pid)
|