docrb-parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +75 -0
  4. data/Rakefile +12 -0
  5. data/docrb-parser.gemspec +38 -0
  6. data/lib/docrb/core_extensions.rb +60 -0
  7. data/lib/docrb/parser/attribute.rb +25 -0
  8. data/lib/docrb/parser/call.rb +27 -0
  9. data/lib/docrb/parser/class.rb +94 -0
  10. data/lib/docrb/parser/comment.rb +40 -0
  11. data/lib/docrb/parser/comment_parser.rb +290 -0
  12. data/lib/docrb/parser/computations.rb +471 -0
  13. data/lib/docrb/parser/constant.rb +19 -0
  14. data/lib/docrb/parser/container.rb +305 -0
  15. data/lib/docrb/parser/deferred_singleton_class.rb +17 -0
  16. data/lib/docrb/parser/location.rb +43 -0
  17. data/lib/docrb/parser/method.rb +62 -0
  18. data/lib/docrb/parser/method_parameters.rb +85 -0
  19. data/lib/docrb/parser/module.rb +50 -0
  20. data/lib/docrb/parser/node_array.rb +24 -0
  21. data/lib/docrb/parser/reference.rb +25 -0
  22. data/lib/docrb/parser/reloader.rb +19 -0
  23. data/lib/docrb/parser/resolved_reference.rb +26 -0
  24. data/lib/docrb/parser/version.rb +7 -0
  25. data/lib/docrb/parser/virtual_container.rb +21 -0
  26. data/lib/docrb/parser/virtual_location.rb +9 -0
  27. data/lib/docrb/parser/virtual_method.rb +19 -0
  28. data/lib/docrb/parser.rb +139 -0
  29. data/lib/docrb-parser.rb +3 -0
  30. data/sig/docrb/core_extensions.rbs +24 -0
  31. data/sig/docrb/parser/attribute.rbs +18 -0
  32. data/sig/docrb/parser/call.rbs +17 -0
  33. data/sig/docrb/parser/class.rbs +34 -0
  34. data/sig/docrb/parser/comment.rbs +14 -0
  35. data/sig/docrb/parser/comment_parser.rbs +79 -0
  36. data/sig/docrb/parser/constant.rbs +15 -0
  37. data/sig/docrb/parser/container.rbs +91 -0
  38. data/sig/docrb/parser/deferred_singleton_class.rbs +12 -0
  39. data/sig/docrb/parser/location.rbs +24 -0
  40. data/sig/docrb/parser/method.rbs +34 -0
  41. data/sig/docrb/parser/method_parameters.rbs +34 -0
  42. data/sig/docrb/parser/module.rbs +14 -0
  43. data/sig/docrb/parser/node_array.rbs +12 -0
  44. data/sig/docrb/parser/reference.rbs +19 -0
  45. data/sig/docrb/parser/reloader.rbs +7 -0
  46. data/sig/docrb/parser/resolved_reference.rbs +22 -0
  47. data/sig/docrb/parser/virtual_method.rbs +17 -0
  48. data/sig/docrb/parser.rbs +5 -0
  49. 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