docrb-parser 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/.rspec +3 -0
- data/.rubocop.yml +75 -0
- data/Rakefile +12 -0
- data/docrb-parser.gemspec +38 -0
- data/lib/docrb/core_extensions.rb +60 -0
- data/lib/docrb/parser/attribute.rb +25 -0
- data/lib/docrb/parser/call.rb +27 -0
- data/lib/docrb/parser/class.rb +94 -0
- data/lib/docrb/parser/comment.rb +40 -0
- data/lib/docrb/parser/comment_parser.rb +290 -0
- data/lib/docrb/parser/computations.rb +471 -0
- data/lib/docrb/parser/constant.rb +19 -0
- data/lib/docrb/parser/container.rb +305 -0
- data/lib/docrb/parser/deferred_singleton_class.rb +17 -0
- data/lib/docrb/parser/location.rb +43 -0
- data/lib/docrb/parser/method.rb +62 -0
- data/lib/docrb/parser/method_parameters.rb +85 -0
- data/lib/docrb/parser/module.rb +50 -0
- data/lib/docrb/parser/node_array.rb +24 -0
- data/lib/docrb/parser/reference.rb +25 -0
- data/lib/docrb/parser/reloader.rb +19 -0
- data/lib/docrb/parser/resolved_reference.rb +26 -0
- data/lib/docrb/parser/version.rb +7 -0
- data/lib/docrb/parser/virtual_container.rb +21 -0
- data/lib/docrb/parser/virtual_location.rb +9 -0
- data/lib/docrb/parser/virtual_method.rb +19 -0
- data/lib/docrb/parser.rb +139 -0
- data/lib/docrb-parser.rb +3 -0
- data/sig/docrb/core_extensions.rbs +24 -0
- data/sig/docrb/parser/attribute.rbs +18 -0
- data/sig/docrb/parser/call.rbs +17 -0
- data/sig/docrb/parser/class.rbs +34 -0
- data/sig/docrb/parser/comment.rbs +14 -0
- data/sig/docrb/parser/comment_parser.rbs +79 -0
- data/sig/docrb/parser/constant.rbs +15 -0
- data/sig/docrb/parser/container.rbs +91 -0
- data/sig/docrb/parser/deferred_singleton_class.rbs +12 -0
- data/sig/docrb/parser/location.rbs +24 -0
- data/sig/docrb/parser/method.rbs +34 -0
- data/sig/docrb/parser/method_parameters.rbs +34 -0
- data/sig/docrb/parser/module.rbs +14 -0
- data/sig/docrb/parser/node_array.rbs +12 -0
- data/sig/docrb/parser/reference.rbs +19 -0
- data/sig/docrb/parser/reloader.rbs +7 -0
- data/sig/docrb/parser/resolved_reference.rbs +22 -0
- data/sig/docrb/parser/virtual_method.rbs +17 -0
- data/sig/docrb/parser.rbs +5 -0
- metadata +109 -0
@@ -0,0 +1,471 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
class Parser
|
5
|
+
class Computations
|
6
|
+
attr_reader :parser
|
7
|
+
|
8
|
+
def initialize(parser)
|
9
|
+
@parser = parser
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
unfurl_locations
|
14
|
+
merge_containers(parser.nodes)
|
15
|
+
2.times { resolve_references }
|
16
|
+
unfurl_flat_paths
|
17
|
+
resolve_overrides
|
18
|
+
resolve_deferred_singletons
|
19
|
+
attach_documentation
|
20
|
+
resolve_documentation_references
|
21
|
+
merge_inline_documentation_blocks
|
22
|
+
associate_container_sources
|
23
|
+
end
|
24
|
+
|
25
|
+
def unfurl_locations = parser.locations.each(&:load_source!)
|
26
|
+
|
27
|
+
def unfurl_flat_paths
|
28
|
+
parser.all_modules
|
29
|
+
.reject { _1.path_segments.nil? || _1.path_segments.empty? }
|
30
|
+
.each { unfurl_flat_path(_1) }
|
31
|
+
|
32
|
+
parser.all_classes
|
33
|
+
.reject { _1.path_segments.nil? || _1.path_segments.empty? }
|
34
|
+
.each { unfurl_flat_path(_1) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def unfurl_flat_path(node)
|
38
|
+
resolve = resolve_path_partial(node.path_segments, node.parent || parser, container_only: true)
|
39
|
+
last_found_node = resolve[:last_found_node] || parser
|
40
|
+
if resolve[:missing_segments].empty?
|
41
|
+
remove_from_parent!(node, parent: node.parent)
|
42
|
+
node.parent = resolve[:last_found_node]
|
43
|
+
if node.is_a? Class
|
44
|
+
last_found_node.classes << node
|
45
|
+
elsif node.is_a? Module
|
46
|
+
last_found_node.modules << node
|
47
|
+
end
|
48
|
+
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
# Synthesize missing segments as a Module, inject node into it.
|
53
|
+
root = VirtualContainer.new(:module_node, resolve[:missing_segments].shift)
|
54
|
+
parent = root
|
55
|
+
until resolve[:missing_segments].empty?
|
56
|
+
obj = VirtualContainer.new(:module_node, resolve[:missing_segments].shift)
|
57
|
+
parent.body.body = [obj]
|
58
|
+
parent = obj
|
59
|
+
end
|
60
|
+
|
61
|
+
if last_found_node.is_a? Parser
|
62
|
+
obj = last_found_node.handle_node(root)
|
63
|
+
last_found_node.nodes << obj
|
64
|
+
else
|
65
|
+
last_found_node.handle_node(root)
|
66
|
+
end
|
67
|
+
|
68
|
+
unfurl_flat_path(node)
|
69
|
+
end
|
70
|
+
|
71
|
+
def resolve_documentation_references
|
72
|
+
@parser.all_objects
|
73
|
+
.filter { _1.respond_to? :doc }
|
74
|
+
.reject { _1.doc.nil? }
|
75
|
+
.each do |node|
|
76
|
+
node.doc[:value].each { resolve_documentation_reference(node, _1) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def associate_container_sources
|
81
|
+
@parser.all_objects
|
82
|
+
.filter { _1.is_a? Container }
|
83
|
+
.filter { _1.defined_by.empty? }
|
84
|
+
.each { _1.defined_by << _1.location }
|
85
|
+
end
|
86
|
+
|
87
|
+
def resolve_documentation_reference(node, obj)
|
88
|
+
case obj[:type]
|
89
|
+
when :block
|
90
|
+
obj[:value].map! { resolve_documentation_reference node, _1 }
|
91
|
+
obj
|
92
|
+
when :fields
|
93
|
+
obj[:value].transform_values! { resolve_documentation_reference(node, _1) }
|
94
|
+
obj
|
95
|
+
when :method_ref
|
96
|
+
resolve_documentation_pure_reference(node, obj)
|
97
|
+
when :span, :symbol
|
98
|
+
obj
|
99
|
+
else
|
100
|
+
puts "Unhandled documentation node #{obj[:type]}"
|
101
|
+
obj
|
102
|
+
end
|
103
|
+
|
104
|
+
# obj[:value].each do |v|
|
105
|
+
# case v[:type]
|
106
|
+
# when :identifier
|
107
|
+
# if v[:value].to_sym == node.try(:name)&.to_sym
|
108
|
+
# v[:type] = :neutral_identifier
|
109
|
+
# else
|
110
|
+
# resolved = resolve_path(node, [v[:value].to_sym])
|
111
|
+
# if resolved.respond_to?(:id)
|
112
|
+
# v[:type] = :reference
|
113
|
+
# v[:id] = resolved.id
|
114
|
+
# v[:path] = path_of(resolved)
|
115
|
+
# else
|
116
|
+
# v[:type] = :unresolved_identifier
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# when :reference
|
120
|
+
# resolve_documentation_pure_reference(v, node)
|
121
|
+
# when :block
|
122
|
+
# v[:value]
|
123
|
+
# .reject { [:span, :symbol].include? _1[:type] }
|
124
|
+
# .each { resolve_documentation_reference(node, _1) }
|
125
|
+
# when :fields
|
126
|
+
# val = v[:value].values.flatten
|
127
|
+
# resolve_documentation_reference(node, { value: val })
|
128
|
+
# else
|
129
|
+
# puts "Unhandled documentation node #{v[:type]}"
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
end
|
133
|
+
|
134
|
+
def resolve_documentation_pure_reference(node, ref)
|
135
|
+
return ref if ref.key? :object
|
136
|
+
|
137
|
+
node = find_next_container(node) unless node.is_a? Container
|
138
|
+
name = ref[:name].to_sym
|
139
|
+
obj = if ref[:class_path]
|
140
|
+
resolve_path(ref[:class_path].split("::").map(&:to_sym), node)
|
141
|
+
else
|
142
|
+
node
|
143
|
+
end
|
144
|
+
result = if ref[:type] == :method
|
145
|
+
obj.all_instance_methods.named(name).first || obj.all_class_methods.named(name).first
|
146
|
+
else
|
147
|
+
obj.all_objects.named(name).first
|
148
|
+
end
|
149
|
+
|
150
|
+
if result.nil?
|
151
|
+
ref[:type] = :unresolved_identifier
|
152
|
+
else
|
153
|
+
ref[:object] = result
|
154
|
+
end
|
155
|
+
ref
|
156
|
+
end
|
157
|
+
|
158
|
+
def merge_inline_documentation_blocks
|
159
|
+
@parser.all_objects
|
160
|
+
.filter { _1.respond_to? :doc }
|
161
|
+
.reject { _1.doc.nil? }
|
162
|
+
.each { merge_inline_documentation_block(_1.doc) }
|
163
|
+
end
|
164
|
+
|
165
|
+
def merge_inline_documentation_block(block)
|
166
|
+
return if !block.key?(:value) || !block[:value].is_a?(Array)
|
167
|
+
|
168
|
+
continue = true
|
169
|
+
while continue
|
170
|
+
continue = false
|
171
|
+
block[:value].each.with_index do |current, i|
|
172
|
+
next if i.zero?
|
173
|
+
|
174
|
+
last_i = i - 1
|
175
|
+
last = block[:value][i - 1]
|
176
|
+
mergeable_types = %i[netural_identifier unresolved_identifier reference]
|
177
|
+
if mergeable_types.include?(last[:type]) && current[:type] == :text_block
|
178
|
+
case current[:value]
|
179
|
+
when String
|
180
|
+
current[:value] = [last, { type: :span, value: current[:value] }]
|
181
|
+
block[:value].delete_at(last_i)
|
182
|
+
continue = true
|
183
|
+
break
|
184
|
+
when Array
|
185
|
+
current[:value].unshift(last)
|
186
|
+
block[:value].delete_at(last_i)
|
187
|
+
continue = true
|
188
|
+
break
|
189
|
+
end
|
190
|
+
elsif mergeable_types.include?(current[:type]) && last[:type] == :text_block
|
191
|
+
case last[:value]
|
192
|
+
when String
|
193
|
+
last[:value] = [{ type: :span, value: last[:value] }, current]
|
194
|
+
block[:value].delete_at(i)
|
195
|
+
continue = true
|
196
|
+
break
|
197
|
+
when Array
|
198
|
+
last[:value].append(current)
|
199
|
+
block[:value].delete_at(i)
|
200
|
+
continue = true
|
201
|
+
break
|
202
|
+
end
|
203
|
+
elsif current[:type] == :text_block && last[:type] == :text_block && !current[:paragraph]
|
204
|
+
last[:value] = [{ type: :span, value: last[:value] }] if last[:value].is_a? String
|
205
|
+
current[:value] = [{ type: :span, value: current[:value] }] if current[:value].is_a? String
|
206
|
+
last[:value].append(*current[:value])
|
207
|
+
block[:value].delete_at(i)
|
208
|
+
continue = true
|
209
|
+
break
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def path_of(obj)
|
216
|
+
return [] if obj.nil?
|
217
|
+
|
218
|
+
[obj.name] + path_of(obj.parent)
|
219
|
+
end
|
220
|
+
|
221
|
+
def resolve_overrides
|
222
|
+
parser.all_modules.each do |mod|
|
223
|
+
unowned_class_methods = mod.unowned_class_methods
|
224
|
+
mod.class_methods.each do |meth|
|
225
|
+
next unless (other = unowned_class_methods.named(meth.name).first)
|
226
|
+
|
227
|
+
meth.overriding = other.id
|
228
|
+
other.overridden_by << meth.id
|
229
|
+
end
|
230
|
+
|
231
|
+
unowned_instance_methods = mod.unowned_instance_methods
|
232
|
+
mod.instance_methods.each do |meth|
|
233
|
+
next unless (other = unowned_instance_methods.named(meth.name).first)
|
234
|
+
|
235
|
+
meth.overriding = other.id
|
236
|
+
other.overridden_by << meth.id
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
parser.all_classes.each do |cls|
|
241
|
+
unowned_class_methods = cls.unowned_class_methods
|
242
|
+
cls.class_methods.each do |meth|
|
243
|
+
next unless (other = unowned_class_methods.named(meth.name).first)
|
244
|
+
|
245
|
+
meth.overriding = other.id
|
246
|
+
other.overridden_by << meth.id
|
247
|
+
end
|
248
|
+
|
249
|
+
unowned_instance_methods = cls.unowned_instance_methods
|
250
|
+
cls.instance_methods.each do |meth|
|
251
|
+
next unless (other = unowned_instance_methods.named(meth.name).first)
|
252
|
+
|
253
|
+
meth.overriding = other.id
|
254
|
+
other.overridden_by << meth.id
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
CONTAINER_APPENDABLE = %i[
|
260
|
+
constants classes modules includes extends instance_methods class_methods
|
261
|
+
instance_attributes class_attributes
|
262
|
+
].freeze
|
263
|
+
|
264
|
+
def merge_containers(from)
|
265
|
+
from.typed(Container).map(&:name).uniq.each do |n|
|
266
|
+
list = from.named(n)
|
267
|
+
next if list.length <= 1
|
268
|
+
|
269
|
+
final = merge_container(list)
|
270
|
+
from.delete_if { _1.name == n && _1 != final }
|
271
|
+
end
|
272
|
+
|
273
|
+
from.typed(Container).each do |container|
|
274
|
+
CONTAINER_APPENDABLE.each do |attr|
|
275
|
+
merge_containers(container.send(attr))
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
true
|
280
|
+
end
|
281
|
+
|
282
|
+
def merge_container(list)
|
283
|
+
list.shift.tap do |first|
|
284
|
+
list.each do |item|
|
285
|
+
first.defined_by << item.location if item.all_objects.any? { !_1.is_a?(Container) }
|
286
|
+
CONTAINER_APPENDABLE.each do |attr|
|
287
|
+
items = item.send(attr).each { _1.parent = first }
|
288
|
+
first.send(attr).append(*items)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
first.defined_by << first.location if first.defined_by.empty?
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def remove_from_parent!(item, parent:)
|
296
|
+
return parser.nodes.delete(item) if parent.nil?
|
297
|
+
|
298
|
+
CONTAINER_APPENDABLE.each { parent.send(_1).delete(item) }
|
299
|
+
end
|
300
|
+
|
301
|
+
def resolve_references
|
302
|
+
parser.references.reject(&:fulfilled?).each do |ref|
|
303
|
+
target = resolve_path(ref.parent, ref.path)
|
304
|
+
ref.resolved = if target.nil?
|
305
|
+
ResolvedReference.new(parser, :broken, nil)
|
306
|
+
else
|
307
|
+
ResolvedReference.new(parser, :valid, target.id)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def attach_documentation
|
313
|
+
parser.all_objects.each do |node|
|
314
|
+
comment = case node.try(:kind)
|
315
|
+
when :module, :class
|
316
|
+
([node.defined_by] + [node.location])
|
317
|
+
.flatten
|
318
|
+
.compact
|
319
|
+
.uniq
|
320
|
+
.lazy
|
321
|
+
.map { Comment.new(parser, _1) }
|
322
|
+
.reject { _1.comments.nil? }
|
323
|
+
.first
|
324
|
+
when :method
|
325
|
+
Comment.new(parser, node.location)
|
326
|
+
end
|
327
|
+
|
328
|
+
next if comment.nil? || comment.comments.nil?
|
329
|
+
|
330
|
+
node.doc = CommentParser.parse(comment.comments.join("\n"))
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def resolve_path(obj, path)
|
335
|
+
p = path.dup
|
336
|
+
obj = find_object(obj, p.shift) until p.empty? || obj.nil?
|
337
|
+
obj
|
338
|
+
end
|
339
|
+
|
340
|
+
def resolve_path_partial(path, parent, container_only: false)
|
341
|
+
path = path.dup
|
342
|
+
if path.first == :root!
|
343
|
+
parent = parser
|
344
|
+
path.shift
|
345
|
+
end
|
346
|
+
|
347
|
+
c_filter = -> { container_only ? _1.is_a?(Container) : true }
|
348
|
+
|
349
|
+
found_segments = []
|
350
|
+
until path.empty?
|
351
|
+
last_parent = parent
|
352
|
+
if parent.is_a? Parser
|
353
|
+
parent = parent.nodes.named(path.first).filter(&c_filter).first
|
354
|
+
if parent.nil?
|
355
|
+
parent = last_parent
|
356
|
+
break
|
357
|
+
end
|
358
|
+
|
359
|
+
found_segments << path.shift
|
360
|
+
next
|
361
|
+
end
|
362
|
+
|
363
|
+
parent = find_object(parent, path.first, container_only:)
|
364
|
+
if parent.nil?
|
365
|
+
parent = last_parent
|
366
|
+
break
|
367
|
+
end
|
368
|
+
|
369
|
+
found_segments << path.shift
|
370
|
+
end
|
371
|
+
|
372
|
+
{
|
373
|
+
last_found_node: parent,
|
374
|
+
found_segments:,
|
375
|
+
missing_segments: path
|
376
|
+
}
|
377
|
+
end
|
378
|
+
|
379
|
+
def resolve_deferred_singletons
|
380
|
+
parser.nodes.typed(DeferredSingletonClass).each do |sing|
|
381
|
+
resolve_deferred_singleton(sing)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def resolve_deferred_singleton(sing)
|
386
|
+
object = find_object(sing.parent, sing.target, container_only: true)
|
387
|
+
object ||= sing.parser.nodes
|
388
|
+
.named(sing.target)
|
389
|
+
.typed(Container)
|
390
|
+
.reject { _1.is_a?(DeferredSingletonClass) }
|
391
|
+
.first
|
392
|
+
return if !object.nil? && object.kind != :class
|
393
|
+
|
394
|
+
if object&.kind == :class
|
395
|
+
object.merge_singleton_class(sing)
|
396
|
+
remove_from_parent!(sing, parent: sing.parent)
|
397
|
+
return
|
398
|
+
end
|
399
|
+
|
400
|
+
resolve = resolve_path_partial(sing.target, sing.parent, container_only: true)
|
401
|
+
last_found_node = resolve[:last_found_node]
|
402
|
+
missing_segments = resolve[:missing_segments]
|
403
|
+
|
404
|
+
# At this point we will have to synthesize the class. Prepare its path as modules.
|
405
|
+
class_name = missing_segments.pop
|
406
|
+
root = if missing_segments.empty?
|
407
|
+
parser
|
408
|
+
else
|
409
|
+
VirtualContainer.new(:module_node, missing_segments.shift).tap do |root|
|
410
|
+
parent = root
|
411
|
+
until missing_segments.empty?
|
412
|
+
obj = VirtualContainer.new(:module_node, missing_segments.shift)
|
413
|
+
parent.body.body = [obj]
|
414
|
+
parent = obj
|
415
|
+
end
|
416
|
+
|
417
|
+
if last_found_node.is_a? Parser
|
418
|
+
obj = last_found_node.handle_node(root)
|
419
|
+
last_found_node.nodes << obj
|
420
|
+
else
|
421
|
+
last_found_node.handle_node(root)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
cls = VirtualContainer.new(:class_node, class_name)
|
427
|
+
cls.location = sing.location
|
428
|
+
if root.is_a? Parser
|
429
|
+
obj = root.handle_node(cls)
|
430
|
+
root.nodes << obj
|
431
|
+
else
|
432
|
+
root.handle_node(cls)
|
433
|
+
end
|
434
|
+
|
435
|
+
resolve_deferred_singleton(sing)
|
436
|
+
end
|
437
|
+
|
438
|
+
def find_object(container, name, container_only: false)
|
439
|
+
if container_only
|
440
|
+
return container&.all_modules&.named(name)&.first ||
|
441
|
+
container&.all_classes&.named(name)&.first ||
|
442
|
+
(find_object(container.parent, name, container_only:) if container&.parent) ||
|
443
|
+
parser.nodes.lazy
|
444
|
+
.filter { _1.is_a?(Container) && !_1.is_a?(DeferredSingletonClass) && _1.name == name }
|
445
|
+
.first ||
|
446
|
+
parser.nodes.lazy
|
447
|
+
.filter { _1.is_a?(Container) && !_1.is_a?(DeferredSingletonClass) }
|
448
|
+
.first { find_object(_1, name, container_only:) }
|
449
|
+
end
|
450
|
+
|
451
|
+
container&.all_modules&.named(name)&.first ||
|
452
|
+
container&.all_classes&.named(name)&.first ||
|
453
|
+
container&.all_instance_methods&.named(name)&.first ||
|
454
|
+
container&.all_instance_attributes&.named(name)&.first ||
|
455
|
+
container&.all_class_methods&.named(name)&.first ||
|
456
|
+
container&.all_class_attributes&.named(name)&.first ||
|
457
|
+
(find_object(container.parent, name, container_only:) if container&.parent) ||
|
458
|
+
parser.nodes.lazy.filter { _1.is_a?(Container) && _1.name == name }.first ||
|
459
|
+
parser.nodes.lazy.filter { _1.is_a? Container }.first { find_object(_1, name) }
|
460
|
+
end
|
461
|
+
|
462
|
+
def find_next_container(obj)
|
463
|
+
return obj.parent if obj.parent.is_a? Container
|
464
|
+
|
465
|
+
parent = obj.parent
|
466
|
+
parent = parent.parent while parent && !parent.is_a?(Container)
|
467
|
+
parent
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
class Parser
|
5
|
+
class Constant
|
6
|
+
visible_attr_reader :name, :location
|
7
|
+
attr_accessor :parent, :doc
|
8
|
+
|
9
|
+
def initialize(parser, parent, node)
|
10
|
+
@object_id = parser.make_id(self)
|
11
|
+
@parent = parent
|
12
|
+
@name = node.name
|
13
|
+
@location = parser.location(node.location)
|
14
|
+
end
|
15
|
+
|
16
|
+
def id = @object_id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|