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.
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,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class Container
6
+ visible_attr_accessor :classes, :modules, :defined_by, :instance_methods,
7
+ :class_methods, :extends, :includes, :name, :path_segments,
8
+ :instance_attributes, :class_attributes, :current_visibility_modifier,
9
+ :constants, :location
10
+ attr_accessor :parent, :parser, :doc
11
+
12
+ SINGLETON_CLASS_TYPES = %i[
13
+ singleton_class_node
14
+ deferred_singleton_class_node
15
+ ].freeze
16
+
17
+ def initialize(parser, parent, node)
18
+ @object_id = parser.make_id(self)
19
+ @node = node
20
+ @parser = parser
21
+ @explicit_instance_visibility = {}
22
+ @explicit_class_visibility = {}
23
+ @constants = NodeArray.new
24
+ @classes = NodeArray.new
25
+ @modules = NodeArray.new
26
+ @includes = NodeArray.new
27
+ @extends = NodeArray.new
28
+ @defined_by = []
29
+ @instance_methods = NodeArray.new
30
+ @class_methods = NodeArray.new
31
+ @class_attributes = NodeArray.new
32
+ @instance_attributes = NodeArray.new
33
+ @inside_module_function = false
34
+ @location = parser.location(node.location)
35
+ @parent = parent
36
+
37
+ @name = if SINGLETON_CLASS_TYPES.include? node&.type
38
+ nil
39
+ elsif node
40
+ name = parser.unfurl_constant_path(node.constant_path)
41
+ @path_segments = name
42
+ name.pop
43
+ end
44
+
45
+ node&.body&.body&.each { handle_node(_1) }
46
+ end
47
+
48
+ def full_path(relative_to: nil)
49
+ path = [self] + (parent&.full_path || [])
50
+ return path unless relative_to
51
+
52
+ rel_path = relative_to.full_path
53
+ while rel_path.last == path.last
54
+ rel_path.pop
55
+ path.pop
56
+ end
57
+
58
+ path
59
+ end
60
+
61
+ def extract_references(from, attr)
62
+ NodeArray.new(from
63
+ .filter(&:fulfilled?)
64
+ .map { _1.resolved.id }
65
+ .map { parser.object_by_id(_1) }
66
+ .map { _1.send(attr) }
67
+ .flatten)
68
+ end
69
+
70
+ def unowned_classes = extract_references(@includes, :all_classes)
71
+
72
+ def all_classes = NodeArray.new(@classes).tap { _1.merge_unowned(*unowned_classes) }
73
+
74
+ def unowned_modules = extract_references(@includes, :all_modules)
75
+
76
+ def all_modules = NodeArray.new(@modules).tap { _1.merge_unowned(*unowned_modules) }
77
+
78
+ def unowned_instance_methods = extract_references(@includes, :all_instance_methods)
79
+
80
+ def all_instance_methods = NodeArray.new(@instance_methods).tap { _1.merge_unowned(*unowned_instance_methods) }
81
+
82
+ def unowned_class_methods = extract_references(@extends, :all_instance_methods)
83
+
84
+ def all_class_methods = NodeArray.new(@class_methods).tap { _1.merge_unowned(*unowned_class_methods) }
85
+
86
+ def unowned_class_attributes = extract_references(@extends, :all_instance_attributes)
87
+
88
+ def all_class_attributes = NodeArray.new(@class_attributes).tap { _1.merge_unowned(*unowned_class_attributes) }
89
+
90
+ def unowned_instance_attributes = extract_references(@includes, :all_instance_attributes)
91
+
92
+ def all_instance_attributes
93
+ NodeArray.new(@instance_attributes)
94
+ .tap { _1.merge_unowned(*unowned_instance_attributes) }
95
+ end
96
+
97
+ def all_objects
98
+ NodeArray.new(
99
+ all_classes + all_modules + all_instance_methods + all_classes +
100
+ all_class_attributes + all_instance_attributes
101
+ )
102
+ end
103
+
104
+ def id = @object_id
105
+
106
+ def source_of(obj)
107
+ parent = obj.parent.id
108
+ case
109
+ when parent == id
110
+ :self
111
+ when @includes.filter(&:fulfilled?).any? { _1.resolved.id == id }
112
+ :included
113
+ when @extends.filter(&:fulfilled?).any? { _1.resolved.id == id }
114
+ :extended
115
+ else
116
+ :unknown
117
+ end
118
+ end
119
+
120
+ def handle_parsed_node(_parser, _node) = nil
121
+
122
+ def instance_method_added(_parser, _node, _method) = nil
123
+
124
+ def class_method_added(_parser, _node, _method) = nil
125
+
126
+ def class_added(_parser, _node, _method) = nil
127
+
128
+ def module_added(_parser, _node, _method) = nil
129
+
130
+ def handle_call(call)
131
+ case call.name
132
+ when :extend
133
+ extends.append(*call.arguments.map { reference(_1) })
134
+ true
135
+ when :include
136
+ includes.append(*call.arguments.map { reference(_1) })
137
+ true
138
+ when :attr, :attr_reader
139
+ add_attribute(parser, call, :reader)
140
+ true
141
+ when :attr_accessor
142
+ add_attribute(parser, call, :accessor)
143
+ true
144
+ when :attr_writer
145
+ add_attribute(parser, call, :writer)
146
+ true
147
+ when :private, :public, :protected
148
+ handle_visibility_modifier(parser, call)
149
+ true
150
+ when :private_class_method, :public_class_method
151
+ handle_singleton_visibility_modifier(parser, call)
152
+ true
153
+ else
154
+ false
155
+ end
156
+ end
157
+
158
+ def handle_node(node)
159
+ case v = parse_node(node)
160
+ when nil
161
+ # Pass
162
+ when Method
163
+ if v.instance?
164
+ @instance_methods << v
165
+ instance_method_added(parser, node, v)
166
+ else
167
+ @class_methods << v
168
+ class_method_added(parser, node, v)
169
+ end
170
+ when Class
171
+ if v.singleton? && kind == :class
172
+ merge_singleton_class(v)
173
+ else
174
+ @classes << v
175
+ class_added(parser, node, v)
176
+ end
177
+ when Module
178
+ @modules << v
179
+ module_added(parser, node, v)
180
+ when Call
181
+ handle_parsed_node(parser, v) unless handle_call(v)
182
+ when Constant
183
+ @constants << v
184
+ else
185
+ handle_parsed_node(parser, v)
186
+ end
187
+ end
188
+
189
+ def parse_node(node) = parser.handle_node(node, self)
190
+
191
+ def reference(path) = parser.reference(self, path)
192
+
193
+ def add_attribute(parser, node, type)
194
+ node.arguments.each do |arg|
195
+ next unless arg.try(:type) == :symbol_node # TODO: Support extra types?
196
+
197
+ @instance_attributes << Attribute.new(parser, self, node, arg.value.to_sym, type)
198
+ end
199
+ end
200
+
201
+ def adjust_split_attributes!(scope)
202
+ attrs = instance_variable_get("@#{scope}_attributes")
203
+ methods = instance_variable_get("@#{scope}_methods")
204
+ exp = instance_variable_get("@explicit_#{scope}_visibility")
205
+
206
+ loop do
207
+ changed = false
208
+ attrs.each do |attr|
209
+ setter = "#{attr.name}=".to_sym
210
+
211
+ # Do we have a writer defined explicitly?
212
+ if (method = methods.named(setter).first)
213
+ changed = true
214
+ methods.delete(method)
215
+ attr.writer_visibility = exp.fetch(setter, method.visibility)
216
+
217
+ # Promote attribute to accessor in case it was only a reader.
218
+ attr.type = :accessor if attr.type == :reader
219
+ end
220
+
221
+ next unless (method = methods.named(attr.name).first)
222
+
223
+ changed = true
224
+ methods.delete(method)
225
+ attr.reader_visibility = exp.fetch(setter, method.visibility)
226
+
227
+ # Promote attribute to accessor in case it was only a writer.
228
+ attr.type = :accessor if attr.type == :writer
229
+ end
230
+
231
+ # Finally, move all methods ending in `=` that are left to write-only
232
+ # attributes, and re-run this loop if required.
233
+ methods.each do |met|
234
+ name = met.name.to_s
235
+ next unless name.end_with? "="
236
+
237
+ changed = true
238
+ attr_name = name.gsub("=", "").to_sym
239
+ attrs << Attribute.new(@parser, self, met.node, attr_name, :writer).tap do |attr|
240
+ attr.writer_visibility = met.visibility
241
+ end
242
+ methods.delete(met)
243
+ end
244
+
245
+ break unless changed
246
+ end
247
+ end
248
+
249
+ def handle_visibility_modifier(_parser, node)
250
+ @inside_module_function = false
251
+ old_visibility = @current_visibility_modifier
252
+ @current_visibility_modifier = node.name
253
+ node.arguments&.each do |arg|
254
+ case arg.type
255
+ when :symbol_node
256
+ @explicit_instance_visibility[arg.value.to_sym] = @current_visibility_modifier
257
+ if (method = @instance_methods.named(arg.value.to_sym).first)
258
+ method.visibility = @current_visibility_modifier
259
+ next
260
+ end
261
+
262
+ if (attr = @instance_attributes.named(arg.value.gsub("=", "").to_sym).first)
263
+ if arg.value.end_with? "="
264
+ attr.writer_visibility = @current_visibility_modifier
265
+ else
266
+ attr.reader_visibility = @current_visibility_modifier
267
+ end
268
+ end
269
+
270
+ when :def_node, :call_node
271
+ @explicit_instance_visibility[arg.name.to_sym] = @current_visibility_modifier
272
+ known_methods = @instance_methods.map(&:name)
273
+ handle_node(arg)
274
+ (@instance_methods.map(&:name) - known_methods).each do |n|
275
+ @explicit_instance_visibility[n] = @current_visibility_modifier
276
+ end
277
+ else
278
+ parser.unhandled_node! node
279
+ end
280
+ end
281
+ ensure
282
+ @current_visibility_modifier = old_visibility unless node.arguments && node.arguments.empty?
283
+ end
284
+
285
+ def handle_singleton_visibility_modifier(_parser, node)
286
+ visibility = node.name.to_s.split("_", 2).first.to_sym
287
+ node.arguments.each do |arg|
288
+ if arg.value == "new"
289
+ @default_constructor_visibility = visibility
290
+ next
291
+ end
292
+ @class_methods.named(arg.value.to_sym).first&.visibility = visibility
293
+ end
294
+ end
295
+
296
+ def update_constructor_visibility!
297
+ return if @default_constructor_visibility == :public
298
+
299
+ # Create a new "new" singleton
300
+ handle_node(VirtualMethod.new(:new, :self_node))
301
+ @class_methods.named(:new).first!.visibility = @default_constructor_visibility
302
+ end
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class DeferredSingletonClass < Class
6
+ visible_attr_reader :target
7
+
8
+ def kind = :deferred_singleton_class
9
+
10
+ def initialize(parser, parent, node)
11
+ super
12
+ @target = parser.unfurl_constant_path(node.expression)
13
+ singleton!
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class Location
6
+ visible_attr_reader :line_start, :line_end, :offset_start, :offset_end
7
+ attr_reader :parser, :file_path, :ast, :source
8
+
9
+ def initialize(parser, ast, loc)
10
+ @parser = parser
11
+ @ast = ast
12
+ if loc.try(:virtual?)
13
+ @virtual = true
14
+ else
15
+ @line_start = loc.start_line
16
+ @line_end = loc.end_line
17
+ @offset_start = loc.start_offset
18
+ @offset_end = loc.end_offset
19
+ @file_path = parser.current_file
20
+ end
21
+
22
+ @source = nil
23
+ end
24
+
25
+ def virtual? = @virtual || false
26
+
27
+ def load_source!
28
+ return if @virtual
29
+
30
+ @source = @ast.source.source[@offset_start...@offset_end]
31
+ .then do |src|
32
+ lines = src.lines
33
+ next src unless lines.length > 1
34
+
35
+ match = /^(\s+)/.match(lines.last)
36
+ next src unless match
37
+
38
+ src.gsub(/^\s{#{match[1].length}}/, "")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class Method
6
+ visible_attr_accessor :name, :parameters, :visibility, :overriding,
7
+ :overridden_by, :type, :external_receiver, :location
8
+ attr_accessor :parent, :node, :doc
9
+
10
+ def kind = :method
11
+
12
+ def initialize(parser, parent, node)
13
+ @object_id = parser.make_id(self)
14
+ @parser = parser
15
+ @parent = parent
16
+ @node = node
17
+ @name = node.name
18
+ @visibility = parent.try(:current_visibility_modifier) || :public
19
+ @location = parser.location(node.location) unless node.is_a? VirtualMethod
20
+ @parameters = MethodParameters.new(parser, self, node.parameters)
21
+ @overridden_by = []
22
+ case node.receiver&.type
23
+ when nil
24
+ @type = :instance
25
+ when :self_node
26
+ @type = :class
27
+ else
28
+ @type = :class
29
+ @external_receiver = parser.reference(self, parser.unfurl_constant_path(node.receiver))
30
+ end
31
+ end
32
+
33
+ def id = @object_id
34
+
35
+ def class? = @type == :class
36
+
37
+ def instance? = @type == :instance
38
+
39
+ def module_function? = @visibility == :module_function
40
+
41
+ def external? = !@external_receiver.nil?
42
+
43
+ def override_target = (@parser.object_by_id(@overriding) if @overriding)
44
+ def overriders = @overridden_by.map { @parser.object_by_id(_1) }.map(&:parent).compact
45
+
46
+ def full_path(relative_to: nil)
47
+ path = (parent&.full_path || []).reverse + [self]
48
+ return path unless relative_to
49
+
50
+ full_rel = relative_to.full_path.reverse
51
+ while full_rel.first == path.first
52
+ full_rel.shift
53
+ path.shift
54
+ end
55
+
56
+ path
57
+ end
58
+
59
+ docrb_inspect { "name=#{name}" }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class MethodParameters < Array
6
+ def initialize(parser, parent, node)
7
+ @object_id = parser.make_id(self)
8
+ super()
9
+ @parent = parent
10
+ @node = node
11
+ node&.compact_child_nodes&.sort_by { _1.location.start_offset }&.each do |n|
12
+ kind = case n
13
+ when Prism::RequiredParameterNode then :arg
14
+ when Prism::OptionalParameterNode then :optarg
15
+ when Prism::RestParameterNode then :rest
16
+ when Prism::KeywordParameterNode then n.value ? :optkw : :kw
17
+ when Prism::KeywordRestParameterNode then :kwrest
18
+ when Prism::BlockParameterNode then :block
19
+ else raise NotImplementedError, "Unsupported parameter kind #{n.class}"
20
+ end
21
+ append Parameter.new(parser, kind, n.name, n.try(:value))
22
+ end
23
+ end
24
+
25
+ def id = @object_id
26
+
27
+ docrb_inspect { to_a }
28
+
29
+ class Parameter
30
+ visible_attr_reader :kind, :name, :value, :value_type
31
+ attr_reader :parser
32
+
33
+ def initialize(parser, kind, name, value = nil)
34
+ @object_id = parser.make_id(self)
35
+ @kind = kind
36
+ @name = name || name_by_type
37
+ @value_type = type_for_value(value)
38
+ @value = value.then! { parser.location(_1.location) }
39
+ end
40
+
41
+ def id = @object_id
42
+
43
+ def has_value? = !value.nil?
44
+
45
+ def optional? = kind == :optarg || kind == :optkw
46
+
47
+ def positional? = kind == :optarg || kind == :arg || kind == :rest
48
+
49
+ def keyword? = kind == :kw || kind == :optkw || kind == :kwrest
50
+
51
+ def block? = kind == :block
52
+
53
+ def rest? = kind == :kwrest || kind == :rest
54
+
55
+ private
56
+
57
+ def name_by_type
58
+ case kind
59
+ when :rest then :*
60
+ when :kwrest then :**
61
+ when :block then :&
62
+ else
63
+ raise "Invalid call to name_by_type for a non-anonymous parameter"
64
+ end
65
+ end
66
+
67
+ def type_for_value(value)
68
+ return unless value
69
+
70
+ case value
71
+ when Prism::SymbolNode then :symbol
72
+ when Prism::NilNode then :nil
73
+ when Prism::FalseNode, Prism::TrueNode then :bool
74
+ when Prism::CallNode then :call
75
+ when Prism::StringNode then :string
76
+ when Prism::IntegerNode, Prism::FloatNode then :number
77
+ when Prism::ConstantReadNode, Prism::ConstantPathNode then :const
78
+ else
79
+ puts "Unhandled parameter value type #{value.class.name}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class Module < Container
6
+ def kind = :module
7
+
8
+ def initialize(parser, parent, node)
9
+ super
10
+ adjust_split_attributes! :class
11
+ adjust_split_attributes! :instance
12
+ end
13
+
14
+ def instance_method_added(_parser, _node, method)
15
+ return unless @inside_module_function
16
+ return unless method.instance?
17
+
18
+ method.type = :module_function
19
+ @class_methods.append(method)
20
+ @instance_methods.delete(method)
21
+ end
22
+
23
+ def handle_parsed_node(parser, node)
24
+ return unless node.is_a? Call
25
+ return unless node.name == :module_function
26
+
27
+ old_module_function = @inside_module_function
28
+ @inside_module_function = true
29
+
30
+ node.arguments&.each do |arg|
31
+ case arg.type
32
+ when :symbol_node
33
+ if (method = @instance_methods.named(arg.value.to_sym).first)
34
+ method.type = :module_function
35
+ @class_methods.append(method)
36
+ @instance_methods.delete(method)
37
+ end
38
+
39
+ when :def_node, :call_node
40
+ handle_node(arg)
41
+ else
42
+ parser.unhandled_node! node
43
+ end
44
+ ensure
45
+ @inside_module_function = old_module_function unless node.arguments && node.arguments.empty?
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class NodeArray < Array
6
+ def by_kind(*kind) = filter { _1.respond_to?(:kind) && kind.include?(_1.kind) }
7
+
8
+ def named(name)
9
+ name = name.to_s
10
+ NodeArray.new(filter { _1.respond_to?(:name) && _1.name.to_s == name })
11
+ end
12
+
13
+ def named!(name) = named(name).first!
14
+
15
+ def typed(*classes) = filter { |node| classes.any? { node.is_a? _1 } }
16
+
17
+ def merge_unowned(*other)
18
+ other
19
+ .reject { |v| find { _1.name == v.name } }
20
+ .then { append(*_1) unless _1.empty? }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class Reference
6
+ visible_attr_accessor :path, :resolved
7
+ attr_accessor :parent
8
+
9
+ def initialize(parser, parent, path)
10
+ @parent = parent
11
+ @path = path
12
+ @parser = parser
13
+ end
14
+
15
+ def resolved? = !resolved.nil?
16
+ def fulfilled? = resolved? && resolved.valid?
17
+
18
+ def dereference!
19
+ raise "Dereference of unfulfilled reference" unless fulfilled?
20
+
21
+ resolved.dereference!
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class Reloader
6
+ def self.reload!
7
+ Object.send(:remove_const, :Docrb) if defined? Docrb
8
+
9
+ root_dir = File.expand_path("../..", __dir__)
10
+ $LOADED_FEATURES
11
+ .select { _1.start_with? root_dir }
12
+ .each { $LOADED_FEATURES.delete _1 }
13
+
14
+ require "docrb/parser"
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class ResolvedReference
6
+ visible_attr_accessor :status
7
+
8
+ def initialize(parser, status, id)
9
+ @status = status
10
+ @object_id = id
11
+ @parser = parser
12
+ end
13
+
14
+ def id = @object_id
15
+
16
+ def broken? = status == :broken
17
+ def valid? = status == :valid
18
+
19
+ def dereference!
20
+ raise "Cannot dereference broken reference" if broken?
21
+
22
+ @parser.object_by_id(id)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class VirtualContainer
6
+ Body = Struct.new(:body)
7
+ ConstantName = Struct.new(:name) do
8
+ def type = :constant_read_node
9
+ end
10
+
11
+ attr_accessor :location, :body, :constant_path, :type, :superclass
12
+
13
+ def initialize(type, name)
14
+ @type = type
15
+ @constant_path = ConstantName.new(name:)
16
+ @location = VirtualLocation.new
17
+ @body = Body.new(body: [])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class Parser
5
+ class VirtualLocation
6
+ def virtual? = true
7
+ end
8
+ end
9
+ end