racer-rb 0.1.0
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 +7 -0
- data/.rubocop.yml +9 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +23 -0
- data/ext/racer/extconf.rb +14 -0
- data/ext/racer/racer.cc +999 -0
- data/ext/racer/racer.hh +37 -0
- data/ext/racer/tiny_queue.cc +103 -0
- data/ext/racer/tiny_queue.hh +39 -0
- data/ext/racer/traces.cc +70 -0
- data/ext/racer/traces.hh +95 -0
- data/ext/racer/worker.cc +188 -0
- data/ext/racer/worker.hh +15 -0
- data/lib/racer/agent.rb +250 -0
- data/lib/racer/collectors/rbs_collector.rb +645 -0
- data/lib/racer/minitest.rb +20 -0
- data/lib/racer/minitest_plugin.rb +35 -0
- data/lib/racer/rails/railtie.rb +38 -0
- data/lib/racer/rb.rb +2 -0
- data/lib/racer/rspec.rb +19 -0
- data/lib/racer/rspec_plugin.rb +28 -0
- data/lib/racer/trace.rb +121 -0
- data/lib/racer/version.rb +5 -0
- data/lib/racer.rb +40 -0
- data/sig/racer.rbs +4 -0
- metadata +87 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
require "rbs"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Racer::Collectors
|
|
6
|
+
class RBSCollector
|
|
7
|
+
module EnsureValidConstantName
|
|
8
|
+
def self.valid_name(name)
|
|
9
|
+
return unless name
|
|
10
|
+
|
|
11
|
+
name.split("::").map do |fragment|
|
|
12
|
+
fragment[0].upcase.concat(fragment[1..])
|
|
13
|
+
end.join("::")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ensure_valid_name!
|
|
17
|
+
return if defined?(@original_name)
|
|
18
|
+
@original_name = @name
|
|
19
|
+
@name = EnsureValidConstantName.valid_name(@name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
refine Racer::Trace::Constant do
|
|
23
|
+
import_methods EnsureValidConstantName
|
|
24
|
+
|
|
25
|
+
def ensure_valid_names!
|
|
26
|
+
ensure_valid_name!
|
|
27
|
+
|
|
28
|
+
@superclass = EnsureValidConstantName.valid_name(@superclass)
|
|
29
|
+
@included_modules = @included_modules.map { EnsureValidConstantName.valid_name(_1) }
|
|
30
|
+
@prepended_modules = @prepended_modules.map { EnsureValidConstantName.valid_name(_1) }
|
|
31
|
+
@extended_modules = @extended_modules.map { EnsureValidConstantName.valid_name(_1) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
refine Racer::Trace::ConstantInstance do
|
|
36
|
+
import_methods EnsureValidConstantName
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
using EnsureValidConstantName
|
|
40
|
+
|
|
41
|
+
module EnsureValidMethodName
|
|
42
|
+
refine Racer::Trace do
|
|
43
|
+
def valid_method_name?
|
|
44
|
+
method_name.match?(
|
|
45
|
+
/\A([a-zA-Z_][a-zA-Z0-9_]*[!?=]?|\+|\-|\*|\/|%|\*\*|==|!=|===|<=>|<=|>=|<|>|\<\<|\>\>|\&|\||\^|~|!|`|=~|!~|\[\]|\[\]=)\z/
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
using EnsureValidMethodName
|
|
51
|
+
|
|
52
|
+
module FilterMultiEntryMembers
|
|
53
|
+
refine RBS::Environment::MultiEntry do
|
|
54
|
+
def members(of:)
|
|
55
|
+
decls.flat_map do |decl|
|
|
56
|
+
decl.decl.members.select { _1.is_a?(of) }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
using FilterMultiEntryMembers
|
|
62
|
+
|
|
63
|
+
def initialize(libraries: [])
|
|
64
|
+
@results = {}
|
|
65
|
+
|
|
66
|
+
loader = RBS::EnvironmentLoader.new
|
|
67
|
+
libraries.each { loader.add(library: _1) }
|
|
68
|
+
|
|
69
|
+
rbs_collection_config = Pathname.new("rbs_collection.yaml")
|
|
70
|
+
if rbs_collection_config.exist?
|
|
71
|
+
lockfile_path = RBS::Collection::Config.to_lockfile_path(rbs_collection_config)
|
|
72
|
+
lockfile_content = YAML.load_file(lockfile_path)
|
|
73
|
+
lockfile = RBS::Collection::Config::Lockfile.from_lockfile(lockfile_path:, data: lockfile_content)
|
|
74
|
+
loader.add_collection(lockfile)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@environment = RBS::Environment.from_loader(loader).resolve_type_names
|
|
78
|
+
@definition_builder = RBS::DefinitionBuilder.new(env: @environment)
|
|
79
|
+
|
|
80
|
+
@existing_types = {}
|
|
81
|
+
# Modules that are included or prepended to the Object class
|
|
82
|
+
# need to have a self type that is not Object (for example BasicObject)
|
|
83
|
+
@modules_in_object_class = Set.new
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def collect(trace)
|
|
87
|
+
trace.constant_updates.each do |constant|
|
|
88
|
+
constant.ensure_valid_names!
|
|
89
|
+
|
|
90
|
+
push_constant_to_results(constant)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
unless trace.valid_method_name?
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
method_type_key =
|
|
98
|
+
case trace.method_kind
|
|
99
|
+
when :instance
|
|
100
|
+
:instance_methods
|
|
101
|
+
when :singleton
|
|
102
|
+
:singleton_methods
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
owner = trace.method_callee || trace.method_owner
|
|
106
|
+
|
|
107
|
+
@results[owner.name][method_type_key][trace.method_name] ||= []
|
|
108
|
+
|
|
109
|
+
@results[owner.name][method_type_key][trace.method_name].each do |traces|
|
|
110
|
+
if traces.params == trace.params && traces.return_type == trace.return_type && traces.block_param == trace.block_param
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
@results[owner.name][method_type_key][trace.method_name] << trace
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def stop(path: "sig/generated")
|
|
119
|
+
FileUtils.rm_r(path) if File.directory?(path)
|
|
120
|
+
@results.each do |owner_name, owner|
|
|
121
|
+
owner => { constant:, instance_methods:, singleton_methods: }
|
|
122
|
+
|
|
123
|
+
declarations =
|
|
124
|
+
[
|
|
125
|
+
case constant.type
|
|
126
|
+
when :class
|
|
127
|
+
to_class_declaration(constant, instance_methods, singleton_methods)
|
|
128
|
+
when :module
|
|
129
|
+
to_module_declaration(constant, instance_methods, singleton_methods)
|
|
130
|
+
else
|
|
131
|
+
puts "Unknown owner type #{type} (#{instance_methods}, #{singleton_methods})"
|
|
132
|
+
end
|
|
133
|
+
].compact
|
|
134
|
+
|
|
135
|
+
# Skip writing for invalid owners and if the owner has no members and already exists as RBS
|
|
136
|
+
if declarations.empty? || (declarations.first.members.empty? && @existing_types.key?(owner_name))
|
|
137
|
+
next
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
filename = "#{path}/#{owner_name.split("::").map { underscore(_1) }.join("/")}.rbs"
|
|
141
|
+
dirname = File.dirname(filename)
|
|
142
|
+
unless File.directory?(dirname)
|
|
143
|
+
FileUtils.mkdir_p(dirname)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
io = File.open(filename, "w")
|
|
147
|
+
writer = RBS::Writer.new(out: io)
|
|
148
|
+
writer.write(declarations)
|
|
149
|
+
io.close
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def underscore(camel_cased_word)
|
|
156
|
+
return camel_cased_word.to_s.dup unless /[A-Z-]|::/.match?(camel_cased_word)
|
|
157
|
+
word = camel_cased_word.to_s.gsub("::", "/")
|
|
158
|
+
word.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
|
|
159
|
+
word.tr!("-", "_")
|
|
160
|
+
word.downcase!
|
|
161
|
+
word
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def push_constant_to_results(constant)
|
|
165
|
+
return if @results.key?(constant.name)
|
|
166
|
+
return if @existing_types.key?(constant.name)
|
|
167
|
+
|
|
168
|
+
type_name = to_type_name(constant.name)
|
|
169
|
+
|
|
170
|
+
class_decl = @environment.class_decls[type_name]
|
|
171
|
+
if class_decl
|
|
172
|
+
@existing_types[constant.name] = { class_decl:, type_name: }
|
|
173
|
+
|
|
174
|
+
included_modules = Set.new(class_decl.members(of: RBS::AST::Members::Include).map(&:name))
|
|
175
|
+
constant.included_modules.reject! do |module_name|
|
|
176
|
+
included_modules.include?(to_type_name(module_name))
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
prepended_modules = Set.new(class_decl.members(of: RBS::AST::Members::Prepend).map(&:name))
|
|
180
|
+
constant.prepended_modules.reject! do |module_name|
|
|
181
|
+
prepended_modules.include?(to_type_name(module_name))
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
extended_modules = Set.new(class_decl.members(of: RBS::AST::Members::Extend).map(&:name))
|
|
185
|
+
constant.extended_modules.reject! do |module_name|
|
|
186
|
+
extended_modules.include?(to_type_name(module_name))
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if constant.name == "Object"
|
|
191
|
+
constant.included_modules.each do |module_name|
|
|
192
|
+
@modules_in_object_class.add(module_name)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
constant.prepended_modules.each do |module_name|
|
|
196
|
+
@modules_in_object_class.add(module_name)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
@results[constant.name] = { constant:, instance_methods: {}, singleton_methods: {} }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def find_self_types(owner)
|
|
204
|
+
self_types = []
|
|
205
|
+
if @modules_in_object_class.include?(owner.name)
|
|
206
|
+
self_types << RBS::AST::Declarations::Module::Self.new(name: to_type_name("BasicObject"), args: [], location: nil)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
owner.included_modules.each do |module_name|
|
|
210
|
+
constant = @results[module_name][:constant]
|
|
211
|
+
if (existing_type = @existing_types[module_name])
|
|
212
|
+
unless existing_type[:class_decl].self_types.empty?
|
|
213
|
+
self_types.concat(existing_types[:class_decl].self_types)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
self_types.concat(find_self_types(constant))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
self_types
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def to_module_declaration(owner, instance_methods, singleton_methods)
|
|
224
|
+
self_types = find_self_types(owner)
|
|
225
|
+
|
|
226
|
+
RBS::AST::Declarations::Module.new(
|
|
227
|
+
name: to_type_name(owner.name),
|
|
228
|
+
type_params: type_params_of_existing_class(owner.name),
|
|
229
|
+
members: to_module_members(owner, instance_methods, singleton_methods),
|
|
230
|
+
annotations: [],
|
|
231
|
+
self_types:,
|
|
232
|
+
location: nil,
|
|
233
|
+
comment: nil
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def to_class_declaration(owner, instance_methods, singleton_methods)
|
|
238
|
+
super_class =
|
|
239
|
+
if owner.superclass
|
|
240
|
+
existing_type = @existing_types[owner.name]
|
|
241
|
+
|
|
242
|
+
if existing_type
|
|
243
|
+
existing_type[:class_decl].primary.decl.super_class
|
|
244
|
+
else
|
|
245
|
+
RBS::AST::Declarations::Class::Super.new(
|
|
246
|
+
name: to_type_name(owner.superclass),
|
|
247
|
+
args: generic_arguments_of_class(owner.superclass),
|
|
248
|
+
location: nil
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
RBS::AST::Declarations::Class.new(
|
|
254
|
+
name: to_type_name(owner.name),
|
|
255
|
+
type_params: type_params_of_existing_class(owner.name),
|
|
256
|
+
super_class:,
|
|
257
|
+
members: to_module_members(owner, instance_methods, singleton_methods),
|
|
258
|
+
annotations: [],
|
|
259
|
+
location: nil,
|
|
260
|
+
comment: nil
|
|
261
|
+
)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def to_module_members(constant, instance_methods, singleton_methods)
|
|
265
|
+
[
|
|
266
|
+
*to_mixin_definitions(constant.extended_modules, :extend),
|
|
267
|
+
*to_mixin_definitions(constant.prepended_modules, :prepend),
|
|
268
|
+
*to_mixin_definitions(constant.included_modules, :include),
|
|
269
|
+
*to_method_definitions(instance_methods, singleton_methods),
|
|
270
|
+
*required_interface_definitions(constant, instance_methods, singleton_methods)
|
|
271
|
+
]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def required_interface_definitions(constant, instance_methods, singleton_methods)
|
|
275
|
+
required_interface_members = ->(modules, existing_methods, kind) do
|
|
276
|
+
needed_methods = {}
|
|
277
|
+
|
|
278
|
+
existing_type = @existing_types[constant.name]
|
|
279
|
+
|
|
280
|
+
instance =
|
|
281
|
+
if existing_type
|
|
282
|
+
case kind
|
|
283
|
+
when :instance
|
|
284
|
+
@definition_builder.build_instance(existing_type[:type_name])
|
|
285
|
+
when :singleton
|
|
286
|
+
@definition_builder.build_singleton(existing_type[:type_name])
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
modules.each do |module_name|
|
|
291
|
+
module_existing_type = @existing_types[module_name]
|
|
292
|
+
next unless module_existing_type
|
|
293
|
+
|
|
294
|
+
type_name = module_existing_type[:type_name]
|
|
295
|
+
|
|
296
|
+
ancestors = @definition_builder.ancestor_builder.one_instance_ancestors(type_name)
|
|
297
|
+
ancestors.each_self_type do |self_type|
|
|
298
|
+
next unless @environment.interface_name?(self_type.name)
|
|
299
|
+
|
|
300
|
+
interface_definition = @definition_builder.build_interface(self_type.name)
|
|
301
|
+
needed_methods.merge!(interface_definition.methods)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
needed_methods.filter_map do |name, definition|
|
|
306
|
+
next if existing_methods.key?(name.to_s)
|
|
307
|
+
next if instance && instance.methods[name]&.implemented_in
|
|
308
|
+
|
|
309
|
+
defined_member = definition.defs.first.member
|
|
310
|
+
overload = defined_member.overloads.first
|
|
311
|
+
method_type = overload.method_type.map_type { RBS::Types::Bases::Any.new(location: nil) }
|
|
312
|
+
|
|
313
|
+
defined_member.update(kind:, overloads: [overload.update(method_type:)])
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
[
|
|
318
|
+
*required_interface_members.([*constant.prepended_modules, *constant.included_modules], instance_methods, :instance),
|
|
319
|
+
*required_interface_members.(constant.extended_modules, singleton_methods, :singleton)
|
|
320
|
+
]
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def to_method_definitions(instance_methods, singleton_methods)
|
|
324
|
+
instance_methods.map do |name, overloads|
|
|
325
|
+
to_method_definition(name, :instance, overloads)
|
|
326
|
+
end.concat(
|
|
327
|
+
singleton_methods.map do |name, overloads|
|
|
328
|
+
to_method_definition(name, :singleton, overloads)
|
|
329
|
+
end
|
|
330
|
+
).compact
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def method_defined?(method_name, method_kind, owner_name)
|
|
334
|
+
existing_type = @existing_types[owner_name]
|
|
335
|
+
|
|
336
|
+
if existing_type
|
|
337
|
+
methods =
|
|
338
|
+
case method_kind
|
|
339
|
+
when :instance
|
|
340
|
+
existing_type[:instance] ||= @definition_builder.build_instance(existing_type[:type_name])
|
|
341
|
+
when :singleton
|
|
342
|
+
existing_type[:singleton] ||= @definition_builder.build_singleton(existing_type[:type_name])
|
|
343
|
+
end.methods
|
|
344
|
+
|
|
345
|
+
methods.key?(method_name.to_sym)
|
|
346
|
+
else
|
|
347
|
+
false
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def to_method_definition(name, kind, traces)
|
|
352
|
+
first_trace = traces.first
|
|
353
|
+
|
|
354
|
+
# This is not ideal as we miss redefined core methods (for example if redefining Integer#+).
|
|
355
|
+
# Howver in most cases this does not happend and we instead want to keep the original type definition to have more
|
|
356
|
+
# correct signatures (for example Object#tap returns self instead of a chain of union types).
|
|
357
|
+
return if method_defined?(name, kind, first_trace.method_owner.name)
|
|
358
|
+
|
|
359
|
+
# We add methods to the method callee if present. In this case we still check if the callee
|
|
360
|
+
# defines this method already. As the callee is not equal to the owner, the method signature might
|
|
361
|
+
# have changed from the implemented method on the callee, so we overload in this case.
|
|
362
|
+
overloading =
|
|
363
|
+
if first_trace.method_callee
|
|
364
|
+
method_defined?(name, kind, first_trace.method_callee.name)
|
|
365
|
+
else
|
|
366
|
+
false
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# It could totally be that a method was public when called the first time and private
|
|
370
|
+
# the next time. We cannot depict such a case using RBS.
|
|
371
|
+
visibility = traces.last.method_visibility
|
|
372
|
+
|
|
373
|
+
overloads = {}
|
|
374
|
+
|
|
375
|
+
traces.map do |trace|
|
|
376
|
+
key = [trace.params]
|
|
377
|
+
if trace.block_param
|
|
378
|
+
params = trace.block_param.traces.first&.params || []
|
|
379
|
+
key << params.map { [_1.name, _1.type] }
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
overloads[key] ||= []
|
|
383
|
+
overloads[key] << trace
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
return_type =
|
|
387
|
+
if name == "initialize"
|
|
388
|
+
RBS::Types::Bases::Void.new(location: nil)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
RBS::AST::Members::MethodDefinition.new(
|
|
392
|
+
name: name.to_sym,
|
|
393
|
+
kind:,
|
|
394
|
+
overloads: overloads.map do |(params, *), overload_traces|
|
|
395
|
+
block_params = overload_traces.filter_map(&:block_param)
|
|
396
|
+
|
|
397
|
+
unless block_params.empty?
|
|
398
|
+
block_traces = block_params.flat_map(&:traces)
|
|
399
|
+
param_sets = block_traces.map(&:params)
|
|
400
|
+
unless param_sets.empty?
|
|
401
|
+
size = param_sets.first.size
|
|
402
|
+
if param_sets.any? { _1.size != size }
|
|
403
|
+
warn "block params different for #{overload_traces}"
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
RBS::AST::Members::MethodDefinition::Overload.new(
|
|
409
|
+
method_type: RBS::MethodType.new(
|
|
410
|
+
type_params: [],
|
|
411
|
+
type: RBS::Types::Function.new(
|
|
412
|
+
**method_parameters(params),
|
|
413
|
+
return_type: return_type || to_rbs_type(*overload_traces.map(&:return_type))
|
|
414
|
+
),
|
|
415
|
+
block: block_params.empty? ? nil : to_block(block_params),
|
|
416
|
+
location: nil
|
|
417
|
+
),
|
|
418
|
+
annotations: []
|
|
419
|
+
)
|
|
420
|
+
end,
|
|
421
|
+
annotations: [],
|
|
422
|
+
overloading:,
|
|
423
|
+
location: nil,
|
|
424
|
+
comment: nil,
|
|
425
|
+
# We do not use visibility sections so declare all methods that are not private
|
|
426
|
+
# without visibility to mark them as "public".
|
|
427
|
+
# Protected methods are not supported by RBS yet.
|
|
428
|
+
visibility: visibility == :private ? :private : nil
|
|
429
|
+
)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def method_parameters(*param_sets)
|
|
433
|
+
{
|
|
434
|
+
required_positionals: [],
|
|
435
|
+
optional_positionals: [],
|
|
436
|
+
trailing_positionals: [],
|
|
437
|
+
rest_positionals: nil,
|
|
438
|
+
required_keywords: {},
|
|
439
|
+
optional_keywords: {},
|
|
440
|
+
rest_keywords: nil
|
|
441
|
+
}.tap do |parameters|
|
|
442
|
+
size = param_sets.first.size
|
|
443
|
+
if param_sets.any? { _1.size != size }
|
|
444
|
+
warn "Received param sets with different sizes #{param_sets}"
|
|
445
|
+
next
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
size.times do |n|
|
|
449
|
+
# TODO-Racer: Rethink the data structure here...
|
|
450
|
+
type = param_sets.first[n].type
|
|
451
|
+
name = param_sets.first[n].name
|
|
452
|
+
types = param_sets.map { _1[n].type_name }
|
|
453
|
+
generic_arguments = types.first.generic_arguments
|
|
454
|
+
|
|
455
|
+
case type
|
|
456
|
+
when :required, :optional
|
|
457
|
+
rbs_param =
|
|
458
|
+
RBS::Types::Function::Param.new(
|
|
459
|
+
type: to_rbs_type(*types),
|
|
460
|
+
name:
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
if type == :required
|
|
464
|
+
if parameters[:rest_positionals]
|
|
465
|
+
parameters[:trailing_positionals] << rbs_param
|
|
466
|
+
else
|
|
467
|
+
parameters[:required_positionals] << rbs_param
|
|
468
|
+
end
|
|
469
|
+
else
|
|
470
|
+
parameters[:optional_positionals] << rbs_param
|
|
471
|
+
end
|
|
472
|
+
when :rest
|
|
473
|
+
type =
|
|
474
|
+
if generic_arguments.size == 1
|
|
475
|
+
to_rbs_type(*generic_arguments[0])
|
|
476
|
+
else
|
|
477
|
+
RBS::Types::Bases::Any.new(location: nil)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
parameters[:rest_positionals] =
|
|
481
|
+
RBS::Types::Function::Param.new(
|
|
482
|
+
type:,
|
|
483
|
+
name: name == :* ? nil : name
|
|
484
|
+
)
|
|
485
|
+
when :keyword_required, :keyword_optional
|
|
486
|
+
rbs_param =
|
|
487
|
+
RBS::Types::Function::Param.new(
|
|
488
|
+
type: to_rbs_type(*types),
|
|
489
|
+
name: nil
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if type == :keyword_required
|
|
493
|
+
parameters[:required_keywords][name] = rbs_param
|
|
494
|
+
else
|
|
495
|
+
parameters[:optional_keywords][name] = rbs_param
|
|
496
|
+
end
|
|
497
|
+
when :keyword_rest
|
|
498
|
+
type =
|
|
499
|
+
if generic_arguments.size == 2
|
|
500
|
+
to_rbs_type(*generic_arguments[1])
|
|
501
|
+
else
|
|
502
|
+
RBS::Types::Bases::Any.new(location: nil)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
parameters[:rest_keywords] =
|
|
506
|
+
RBS::Types::Function::Param.new(
|
|
507
|
+
type:,
|
|
508
|
+
name: name == :** ? nil : name
|
|
509
|
+
)
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Converts block parameters of a method to a block type
|
|
516
|
+
# Note: RBS does not support blocks that get other blocks passed so we cannot document
|
|
517
|
+
# "nested" block params.
|
|
518
|
+
def to_block(block_params)
|
|
519
|
+
required = block_params.any? { !_1.traces.empty? }
|
|
520
|
+
|
|
521
|
+
traces = block_params.flat_map(&:traces)
|
|
522
|
+
|
|
523
|
+
function =
|
|
524
|
+
if traces.empty?
|
|
525
|
+
RBS::Types::UntypedFunction.new(return_type: RBS::Types::Bases::Any.new(location: nil))
|
|
526
|
+
else
|
|
527
|
+
RBS::Types::Function.new(
|
|
528
|
+
**method_parameters(*traces.map(&:params)),
|
|
529
|
+
return_type: to_rbs_type(*traces.map(&:return_type))
|
|
530
|
+
)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
self_types = traces.filter_map(&:self_type)
|
|
534
|
+
|
|
535
|
+
RBS::Types::Block.new(
|
|
536
|
+
type: function,
|
|
537
|
+
self_type: self_types.empty? ? nil : to_rbs_type(*self_types),
|
|
538
|
+
required:
|
|
539
|
+
)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def to_mixin_definitions(modules, type)
|
|
543
|
+
klass =
|
|
544
|
+
case type
|
|
545
|
+
when :extend
|
|
546
|
+
RBS::AST::Members::Extend
|
|
547
|
+
when :prepend
|
|
548
|
+
RBS::AST::Members::Prepend
|
|
549
|
+
when :include
|
|
550
|
+
RBS::AST::Members::Include
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
modules.map do |module_name|
|
|
554
|
+
klass.new(
|
|
555
|
+
name: to_type_name(module_name),
|
|
556
|
+
args: generic_arguments_of_class(module_name),
|
|
557
|
+
annotations: [],
|
|
558
|
+
location: nil,
|
|
559
|
+
comment: nil
|
|
560
|
+
)
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def to_type_name(type_name_str)
|
|
565
|
+
path = type_name_str.split("::").map(&:to_sym)
|
|
566
|
+
|
|
567
|
+
RBS::TypeName.new(
|
|
568
|
+
name: path.pop,
|
|
569
|
+
namespace: RBS::Namespace.new(path:, absolute: true)
|
|
570
|
+
)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def to_rbs_type(*constants)
|
|
574
|
+
constants.uniq!
|
|
575
|
+
|
|
576
|
+
has_boolean = false
|
|
577
|
+
constants.each do |constant|
|
|
578
|
+
if constant.name == "TrueClass" || constant.name == "FalseClass"
|
|
579
|
+
has_boolean = true
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
if has_boolean
|
|
584
|
+
constants.delete_if { _1.name == "TrueClass" || _1.name == "FalseClass" }
|
|
585
|
+
constants.push(Racer::Trace::ConstantInstance.new(name: "bool", singleton: false, generic_arguments: []))
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
if constants.size > 1
|
|
589
|
+
constants.each do |constant|
|
|
590
|
+
unless constant.generic_arguments.empty?
|
|
591
|
+
constants.delete_if { _1.name == constant.name && _1.generic_arguments.empty? }
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
return RBS::Types::Union.new(types: constants.map { |type| to_rbs_type(type) }, location: nil)
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
constant = constants.first
|
|
599
|
+
|
|
600
|
+
case constant.name
|
|
601
|
+
when "bool"
|
|
602
|
+
RBS::Types::Bases::Bool.new(location: nil)
|
|
603
|
+
when "NilClass"
|
|
604
|
+
RBS::Types::Bases::Nil.new(location: nil)
|
|
605
|
+
else
|
|
606
|
+
constant.ensure_valid_name!
|
|
607
|
+
type_name = to_type_name(constant.name)
|
|
608
|
+
|
|
609
|
+
if constant.singleton
|
|
610
|
+
RBS::Types::ClassSingleton.new(
|
|
611
|
+
name: type_name,
|
|
612
|
+
location: nil
|
|
613
|
+
)
|
|
614
|
+
else
|
|
615
|
+
RBS::Types::ClassInstance.new(
|
|
616
|
+
name: type_name,
|
|
617
|
+
args: generic_arguments_of_class(constant.name, constant.generic_arguments),
|
|
618
|
+
location: nil
|
|
619
|
+
)
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def generic_arguments_of_class(name, existing_generic_arguments = [])
|
|
625
|
+
return [] unless @existing_types.key?(name)
|
|
626
|
+
|
|
627
|
+
existing_type = @existing_types[name][:class_decl]
|
|
628
|
+
type_params = existing_type&.type_params || []
|
|
629
|
+
|
|
630
|
+
if existing_generic_arguments.size == type_params.size
|
|
631
|
+
existing_generic_arguments.map do |union_types|
|
|
632
|
+
to_rbs_type(*union_types)
|
|
633
|
+
end
|
|
634
|
+
else
|
|
635
|
+
type_params.map { |param| RBS::Types::Bases::Any.new(location: nil) }
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def type_params_of_existing_class(owner)
|
|
640
|
+
return [] unless @existing_types.key?(owner)
|
|
641
|
+
|
|
642
|
+
@existing_types[owner][:class_decl].type_params
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# This ensures that racer can inject it's railtie even if rails was not fully required yet
|
|
2
|
+
#
|
|
3
|
+
# This happens if running a test with a path argument because the test:prepare task is not being executed and rails did not fully initialize
|
|
4
|
+
# when requiring racer/minitest.
|
|
5
|
+
#
|
|
6
|
+
# If no path argument was passed the rails:prepare rake task is being executed. In this case racer is being required during the rails boot process
|
|
7
|
+
# and installs the railtie accordingly.
|
|
8
|
+
begin
|
|
9
|
+
require "rails"
|
|
10
|
+
rescue LoadError
|
|
11
|
+
# no-op rails not available
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require "racer"
|
|
15
|
+
require_relative "minitest_plugin"
|
|
16
|
+
|
|
17
|
+
Racer::MinitestPlugin.agent_pid = Racer.start_agent(stop_at_exit: false, collectors: [Racer::Collectors::RBSCollector.new(libraries: ["json", "minitest", "tempfile", "base64", "pathname", "logger", "uri", "erb", "date", "ipaddr", "securerandom"])])
|
|
18
|
+
puts "Agent started with pid #{Racer::MinitestPlugin.agent_pid}"
|
|
19
|
+
|
|
20
|
+
Minitest.register_plugin Racer::MinitestPlugin
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Racer
|
|
4
|
+
class MinitestPlugin
|
|
5
|
+
class << self
|
|
6
|
+
attr_accessor :agent_pid
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.minitest_plugin_init(options)
|
|
10
|
+
Minitest.reporter << reporter(**options)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.reporter(**options)
|
|
14
|
+
Reporter.new(pid: agent_pid, **options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Do not subclass Minitest::AbstractReporter to be compatible with minitest-reporters
|
|
18
|
+
# (they expect an #io= method)
|
|
19
|
+
class Reporter < Minitest::Reporter
|
|
20
|
+
def initialize(pid:, **)
|
|
21
|
+
@pid = pid
|
|
22
|
+
super()
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def report
|
|
26
|
+
Racer.flush
|
|
27
|
+
Racer.stop_agent(@pid)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Compatibility with minitest-reporters gem
|
|
31
|
+
def before_test(*); end
|
|
32
|
+
def after_test(*); end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|