rdoc 6.7.0 → 6.13.1

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/ExampleMarkdown.md +2 -0
  3. data/ExampleRDoc.rdoc +2 -0
  4. data/History.rdoc +64 -62
  5. data/LICENSE.rdoc +2 -0
  6. data/README.rdoc +13 -0
  7. data/RI.md +842 -0
  8. data/TODO.rdoc +8 -7
  9. data/lib/rdoc/{alias.rb → code_object/alias.rb} +3 -10
  10. data/lib/rdoc/{any_method.rb → code_object/any_method.rb} +7 -12
  11. data/lib/rdoc/{attr.rb → code_object/attr.rb} +4 -7
  12. data/lib/rdoc/{class_module.rb → code_object/class_module.rb} +107 -20
  13. data/lib/rdoc/{constant.rb → code_object/constant.rb} +1 -1
  14. data/lib/rdoc/{context → code_object/context}/section.rb +10 -68
  15. data/lib/rdoc/{context.rb → code_object/context.rb} +4 -46
  16. data/lib/rdoc/{method_attr.rb → code_object/method_attr.rb} +19 -27
  17. data/lib/rdoc/{require.rb → code_object/require.rb} +1 -1
  18. data/lib/rdoc/{top_level.rb → code_object/top_level.rb} +10 -28
  19. data/lib/rdoc/code_object.rb +6 -32
  20. data/lib/rdoc/comment.rb +12 -5
  21. data/lib/rdoc/generator/darkfish.rb +121 -95
  22. data/lib/rdoc/generator/json_index.rb +1 -17
  23. data/lib/rdoc/generator/markup.rb +2 -2
  24. data/lib/rdoc/generator/pot/message_extractor.rb +3 -3
  25. data/lib/rdoc/generator/pot/po_entry.rb +1 -1
  26. data/lib/rdoc/generator/pot.rb +0 -5
  27. data/lib/rdoc/generator/template/darkfish/_head.rhtml +23 -0
  28. data/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml +1 -29
  29. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +1 -1
  30. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +1 -1
  31. data/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml +20 -11
  32. data/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml +3 -8
  33. data/lib/rdoc/generator/template/darkfish/_sidebar_toggle.rhtml +3 -0
  34. data/lib/rdoc/generator/template/darkfish/class.rhtml +84 -43
  35. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +391 -397
  36. data/lib/rdoc/generator/template/darkfish/index.rhtml +11 -10
  37. data/lib/rdoc/generator/template/darkfish/js/darkfish.js +24 -1
  38. data/lib/rdoc/generator/template/darkfish/page.rhtml +5 -5
  39. data/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml +10 -8
  40. data/lib/rdoc/generator/template/darkfish/servlet_root.rhtml +5 -2
  41. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +11 -0
  42. data/lib/rdoc/markdown.kpeg +8 -6
  43. data/lib/rdoc/markdown.rb +55 -32
  44. data/lib/rdoc/markup/attribute_manager.rb +2 -2
  45. data/lib/rdoc/markup/formatter.rb +19 -12
  46. data/lib/rdoc/markup/heading.rb +7 -1
  47. data/lib/rdoc/markup/pre_process.rb +26 -6
  48. data/lib/rdoc/markup/to_bs.rb +1 -1
  49. data/lib/rdoc/markup/to_html.rb +7 -2
  50. data/lib/rdoc/markup/to_html_crossref.rb +63 -12
  51. data/lib/rdoc/markup/to_rdoc.rb +5 -5
  52. data/lib/rdoc/markup.rb +18 -13
  53. data/lib/rdoc/options.rb +90 -12
  54. data/lib/rdoc/parser/c.rb +30 -9
  55. data/lib/rdoc/parser/changelog.rb +5 -4
  56. data/lib/rdoc/parser/prism_ruby.rb +1092 -0
  57. data/lib/rdoc/parser/ripper_state_lex.rb +7 -305
  58. data/lib/rdoc/parser/ruby.rb +23 -17
  59. data/lib/rdoc/parser/simple.rb +1 -1
  60. data/lib/rdoc/parser.rb +5 -4
  61. data/lib/rdoc/rd/block_parser.rb +3 -3
  62. data/lib/rdoc/rd/inline_parser.rb +3 -3
  63. data/lib/rdoc/rdoc.rb +9 -21
  64. data/lib/rdoc/ri/driver.rb +85 -32
  65. data/lib/rdoc/rubygems_hook.rb +91 -15
  66. data/lib/rdoc/servlet.rb +8 -7
  67. data/lib/rdoc/stats.rb +2 -2
  68. data/lib/rdoc/store.rb +21 -13
  69. data/lib/rdoc/task.rb +2 -3
  70. data/lib/rdoc/text.rb +2 -2
  71. data/lib/rdoc/tom_doc.rb +1 -7
  72. data/lib/rdoc/version.rb +1 -1
  73. data/lib/rdoc.rb +22 -24
  74. data/lib/rubygems_plugin.rb +23 -0
  75. metadata +27 -28
  76. data/RI.rdoc +0 -57
  77. data/lib/rdoc/generator/template/darkfish/.document +0 -0
  78. data/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml +0 -19
  79. data/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml +0 -9
  80. data/lib/rdoc/generator/template/json_index/.document +0 -1
  81. /data/lib/rdoc/{anon_class.rb → code_object/anon_class.rb} +0 -0
  82. /data/lib/rdoc/{extend.rb → code_object/extend.rb} +0 -0
  83. /data/lib/rdoc/{ghost_method.rb → code_object/ghost_method.rb} +0 -0
  84. /data/lib/rdoc/{include.rb → code_object/include.rb} +0 -0
  85. /data/lib/rdoc/{meta_method.rb → code_object/meta_method.rb} +0 -0
  86. /data/lib/rdoc/{mixin.rb → code_object/mixin.rb} +0 -0
  87. /data/lib/rdoc/{normal_class.rb → code_object/normal_class.rb} +0 -0
  88. /data/lib/rdoc/{normal_module.rb → code_object/normal_module.rb} +0 -0
  89. /data/lib/rdoc/{single_class.rb → code_object/single_class.rb} +0 -0
@@ -0,0 +1,1092 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+ require_relative 'ripper_state_lex'
5
+
6
+ # Unlike lib/rdoc/parser/ruby.rb, this file is not based on rtags and does not contain code from
7
+ # rtags.rb -
8
+ # ruby-lex.rb - ruby lexcal analyzer
9
+ # ruby-token.rb - ruby tokens
10
+
11
+ # Parse and collect document from Ruby source code.
12
+ # RDoc::Parser::PrismRuby is compatible with RDoc::Parser::Ruby and aims to replace it.
13
+
14
+ class RDoc::Parser::PrismRuby < RDoc::Parser
15
+
16
+ parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER']
17
+
18
+ attr_accessor :visibility
19
+ attr_reader :container, :singleton
20
+
21
+ def initialize(top_level, content, options, stats)
22
+ super
23
+
24
+ content = handle_tab_width(content)
25
+
26
+ @size = 0
27
+ @token_listeners = nil
28
+ content = RDoc::Encoding.remove_magic_comment content
29
+ @content = content
30
+ @markup = @options.markup
31
+ @track_visibility = :nodoc != @options.visibility
32
+ @encoding = @options.encoding
33
+
34
+ @module_nesting = [[top_level, false]]
35
+ @container = top_level
36
+ @visibility = :public
37
+ @singleton = false
38
+ @in_proc_block = false
39
+ end
40
+
41
+ # Suppress `extend` and `include` within block
42
+ # because they might be a metaprogramming block
43
+ # example: `Module.new { include M }` `M.module_eval { include N }`
44
+
45
+ def with_in_proc_block
46
+ @in_proc_block = true
47
+ yield
48
+ @in_proc_block = false
49
+ end
50
+
51
+ # Dive into another container
52
+
53
+ def with_container(container, singleton: false)
54
+ old_container = @container
55
+ old_visibility = @visibility
56
+ old_singleton = @singleton
57
+ old_in_proc_block = @in_proc_block
58
+ @visibility = :public
59
+ @container = container
60
+ @singleton = singleton
61
+ @in_proc_block = false
62
+ unless singleton
63
+ # Need to update module parent chain to emulate Module.nesting.
64
+ # This mechanism is inaccurate and needs to be fixed.
65
+ container.parent = old_container
66
+ end
67
+ @module_nesting.push([container, singleton])
68
+ yield container
69
+ ensure
70
+ @container = old_container
71
+ @visibility = old_visibility
72
+ @singleton = old_singleton
73
+ @in_proc_block = old_in_proc_block
74
+ @module_nesting.pop
75
+ end
76
+
77
+ # Records the location of this +container+ in the file for this parser and
78
+ # adds it to the list of classes and modules in the file.
79
+
80
+ def record_location container # :nodoc:
81
+ case container
82
+ when RDoc::ClassModule then
83
+ @top_level.add_to_classes_or_modules container
84
+ end
85
+
86
+ container.record_location @top_level
87
+ end
88
+
89
+ # Scans this Ruby file for Ruby constructs
90
+
91
+ def scan
92
+ @tokens = RDoc::Parser::RipperStateLex.parse(@content)
93
+ @lines = @content.lines
94
+ result = Prism.parse(@content)
95
+ @program_node = result.value
96
+ @line_nodes = {}
97
+ prepare_line_nodes(@program_node)
98
+ prepare_comments(result.comments)
99
+ return if @top_level.done_documenting
100
+
101
+ @first_non_meta_comment = nil
102
+ if (_line_no, start_line, rdoc_comment = @unprocessed_comments.first)
103
+ @first_non_meta_comment = rdoc_comment if start_line < @program_node.location.start_line
104
+ end
105
+
106
+ @program_node.accept(RDocVisitor.new(self, @top_level, @store))
107
+ process_comments_until(@lines.size + 1)
108
+ end
109
+
110
+ def should_document?(code_object) # :nodoc:
111
+ return true unless @track_visibility
112
+ return false if code_object.parent&.document_children == false
113
+ code_object.document_self
114
+ end
115
+
116
+ # Assign AST node to a line.
117
+ # This is used to show meta-method source code in the documentation.
118
+
119
+ def prepare_line_nodes(node) # :nodoc:
120
+ case node
121
+ when Prism::CallNode, Prism::DefNode
122
+ @line_nodes[node.location.start_line] ||= node
123
+ end
124
+ node.compact_child_nodes.each do |child|
125
+ prepare_line_nodes(child)
126
+ end
127
+ end
128
+
129
+ # Prepares comments for processing. Comments are grouped into consecutive.
130
+ # Consecutive comment is linked to the next non-blank line.
131
+ #
132
+ # Example:
133
+ # 01| class A # modifier comment 1
134
+ # 02| def foo; end # modifier comment 2
135
+ # 03|
136
+ # 04| # consecutive comment 1 start_line: 4
137
+ # 05| # consecutive comment 1 linked to line: 7
138
+ # 06|
139
+ # 07| # consecutive comment 2 start_line: 7
140
+ # 08| # consecutive comment 2 linked to line: 10
141
+ # 09|
142
+ # 10| def bar; end # consecutive comment 2 linked to this line
143
+ # 11| end
144
+
145
+ def prepare_comments(comments)
146
+ current = []
147
+ consecutive_comments = [current]
148
+ @modifier_comments = {}
149
+ comments.each do |comment|
150
+ if comment.is_a? Prism::EmbDocComment
151
+ consecutive_comments << [comment] << (current = [])
152
+ elsif comment.location.start_line_slice.match?(/\S/)
153
+ @modifier_comments[comment.location.start_line] = RDoc::Comment.new(comment.slice, @top_level, :ruby)
154
+ elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line
155
+ current << comment
156
+ else
157
+ consecutive_comments << (current = [comment])
158
+ end
159
+ end
160
+ consecutive_comments.reject!(&:empty?)
161
+
162
+ # Example: line_no = 5, start_line = 2, comment_text = "# comment_start_line\n# comment\n"
163
+ # 1| class A
164
+ # 2| # comment_start_line
165
+ # 3| # comment
166
+ # 4|
167
+ # 5| def f; end # comment linked to this line
168
+ # 6| end
169
+ @unprocessed_comments = consecutive_comments.map! do |comments|
170
+ start_line = comments.first.location.start_line
171
+ line_no = comments.last.location.end_line + (comments.last.location.end_column == 0 ? 0 : 1)
172
+ texts = comments.map do |c|
173
+ c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice
174
+ end
175
+ text = RDoc::Encoding.change_encoding(texts.join("\n"), @encoding) if @encoding
176
+ line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/)
177
+ comment = RDoc::Comment.new(text, @top_level, :ruby)
178
+ comment.line = start_line
179
+ [line_no, start_line, comment]
180
+ end
181
+
182
+ # The first comment is special. It defines markup for the rest of the comments.
183
+ _, first_comment_start_line, first_comment_text = @unprocessed_comments.first
184
+ if first_comment_text && @lines[0...first_comment_start_line - 1].all? { |l| l.match?(/\A\s*$/) }
185
+ comment = RDoc::Comment.new(first_comment_text.text, @top_level, :ruby)
186
+ handle_consecutive_comment_directive(@container, comment)
187
+ @markup = comment.format
188
+ end
189
+ @unprocessed_comments.each do |_, _, comment|
190
+ comment.format = @markup
191
+ end
192
+ end
193
+
194
+ # Creates an RDoc::Method on +container+ from +comment+ if there is a
195
+ # Signature section in the comment
196
+
197
+ def parse_comment_tomdoc(container, comment, line_no, start_line)
198
+ return unless signature = RDoc::TomDoc.signature(comment)
199
+
200
+ name, = signature.split %r%[ \(]%, 2
201
+
202
+ meth = RDoc::GhostMethod.new comment.text, name
203
+ record_location(meth)
204
+ meth.line = start_line
205
+ meth.call_seq = signature
206
+ return unless meth.name
207
+
208
+ meth.start_collecting_tokens
209
+ node = @line_nodes[line_no]
210
+ tokens = node ? visible_tokens_from_location(node.location) : [file_line_comment_token(start_line)]
211
+ tokens.each { |token| meth.token_stream << token }
212
+
213
+ container.add_method meth
214
+ comment.remove_private
215
+ comment.normalize
216
+ meth.comment = comment
217
+ @stats.add_method meth
218
+ end
219
+
220
+ def has_modifier_nodoc?(line_no) # :nodoc:
221
+ @modifier_comments[line_no]&.text&.match?(/\A#\s*:nodoc:/)
222
+ end
223
+
224
+ def handle_modifier_directive(code_object, line_no) # :nodoc:
225
+ comment = @modifier_comments[line_no]
226
+ @preprocess.handle(comment.text, code_object) if comment
227
+ end
228
+
229
+ def handle_consecutive_comment_directive(code_object, comment) # :nodoc:
230
+ return unless comment
231
+ @preprocess.handle(comment, code_object) do |directive, param|
232
+ case directive
233
+ when 'method', 'singleton-method',
234
+ 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then
235
+ # handled elsewhere
236
+ ''
237
+ when 'section' then
238
+ @container.set_current_section(param, comment.dup)
239
+ comment.text = ''
240
+ break
241
+ end
242
+ end
243
+ comment.remove_private
244
+ end
245
+
246
+ def call_node_name_arguments(call_node) # :nodoc:
247
+ return [] unless call_node.arguments
248
+ call_node.arguments.arguments.map do |arg|
249
+ case arg
250
+ when Prism::SymbolNode
251
+ arg.value
252
+ when Prism::StringNode
253
+ arg.unescaped
254
+ end
255
+ end || []
256
+ end
257
+
258
+ # Handles meta method comments
259
+
260
+ def handle_meta_method_comment(comment, node)
261
+ is_call_node = node.is_a?(Prism::CallNode)
262
+ singleton_method = false
263
+ visibility = @visibility
264
+ attributes = rw = line_no = method_name = nil
265
+
266
+ processed_comment = comment.dup
267
+ @preprocess.handle(processed_comment, @container) do |directive, param, line|
268
+ case directive
269
+ when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor'
270
+ attributes = [param] if param
271
+ attributes ||= call_node_name_arguments(node) if is_call_node
272
+ rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R'
273
+ ''
274
+ when 'method'
275
+ method_name = param
276
+ line_no = line
277
+ ''
278
+ when 'singleton-method'
279
+ method_name = param
280
+ line_no = line
281
+ singleton_method = true
282
+ visibility = :public
283
+ ''
284
+ when 'section' then
285
+ @container.set_current_section(param, comment.dup)
286
+ return # If the comment contains :section:, it is not a meta method comment
287
+ end
288
+ end
289
+
290
+ if attributes
291
+ attributes.each do |attr|
292
+ a = RDoc::Attr.new(@container, attr, rw, processed_comment, singleton: @singleton)
293
+ a.store = @store
294
+ a.line = line_no
295
+ record_location(a)
296
+ @container.add_attribute(a)
297
+ a.visibility = visibility
298
+ end
299
+ elsif line_no || node
300
+ method_name ||= call_node_name_arguments(node).first if is_call_node
301
+ meth = RDoc::AnyMethod.new(@container, method_name, singleton: @singleton || singleton_method)
302
+ handle_consecutive_comment_directive(meth, comment)
303
+ comment.normalize
304
+ meth.call_seq = comment.extract_call_seq
305
+ meth.comment = comment
306
+ if node
307
+ tokens = visible_tokens_from_location(node.location)
308
+ line_no = node.location.start_line
309
+ else
310
+ tokens = [file_line_comment_token(line_no)]
311
+ end
312
+ internal_add_method(
313
+ @container,
314
+ meth,
315
+ line_no: line_no,
316
+ visibility: visibility,
317
+ params: '()',
318
+ calls_super: false,
319
+ block_params: nil,
320
+ tokens: tokens
321
+ )
322
+ end
323
+ end
324
+
325
+ def normal_comment_treat_as_ghost_method_for_now?(comment_text, line_no) # :nodoc:
326
+ # Meta method comment should start with `##` but some comments does not follow this rule.
327
+ # For now, RDoc accepts them as a meta method comment if there is no node linked to it.
328
+ !@line_nodes[line_no] && comment_text.match?(/^#\s+:(method|singleton-method|attr|attr_reader|attr_writer|attr_accessor):/)
329
+ end
330
+
331
+ def handle_standalone_consecutive_comment_directive(comment, line_no, start_line) # :nodoc:
332
+ if @markup == 'tomdoc'
333
+ parse_comment_tomdoc(@container, comment, line_no, start_line)
334
+ return
335
+ end
336
+
337
+ if comment.text =~ /\A#\#$/ && comment != @first_non_meta_comment
338
+ node = @line_nodes[line_no]
339
+ handle_meta_method_comment(comment, node)
340
+ elsif normal_comment_treat_as_ghost_method_for_now?(comment.text, line_no) && comment != @first_non_meta_comment
341
+ handle_meta_method_comment(comment, nil)
342
+ else
343
+ handle_consecutive_comment_directive(@container, comment)
344
+ end
345
+ end
346
+
347
+ # Processes consecutive comments that were not linked to any documentable code until the given line number
348
+
349
+ def process_comments_until(line_no_until)
350
+ while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until
351
+ line_no, start_line, rdoc_comment = @unprocessed_comments.shift
352
+ handle_standalone_consecutive_comment_directive(rdoc_comment, line_no, start_line)
353
+ end
354
+ end
355
+
356
+ # Skips all undocumentable consecutive comments until the given line number.
357
+ # Undocumentable comments are comments written inside `def` or inside undocumentable class/module
358
+
359
+ def skip_comments_until(line_no_until)
360
+ while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until
361
+ @unprocessed_comments.shift
362
+ end
363
+ end
364
+
365
+ # Returns consecutive comment linked to the given line number
366
+
367
+ def consecutive_comment(line_no)
368
+ if @unprocessed_comments.first&.first == line_no
369
+ @unprocessed_comments.shift.last
370
+ end
371
+ end
372
+
373
+ def slice_tokens(start_pos, end_pos) # :nodoc:
374
+ start_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> start_pos) >= 0 }
375
+ end_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> end_pos) >= 0 }
376
+ tokens = @tokens[start_index...end_index]
377
+ tokens.pop if tokens.last&.kind == :on_nl
378
+ tokens
379
+ end
380
+
381
+ def file_line_comment_token(line_no) # :nodoc:
382
+ position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no - 1, 0, :on_comment)
383
+ position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
384
+ position_comment
385
+ end
386
+
387
+ # Returns tokens from the given location
388
+
389
+ def visible_tokens_from_location(location)
390
+ position_comment = file_line_comment_token(location.start_line)
391
+ newline_token = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
392
+ indent_token = RDoc::Parser::RipperStateLex::Token.new(location.start_line, 0, :on_sp, ' ' * location.start_character_column)
393
+ tokens = slice_tokens(
394
+ [location.start_line, location.start_character_column],
395
+ [location.end_line, location.end_character_column]
396
+ )
397
+ [position_comment, newline_token, indent_token, *tokens]
398
+ end
399
+
400
+ # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar`
401
+
402
+ def change_method_visibility(names, visibility, singleton: @singleton)
403
+ new_methods = []
404
+ @container.methods_matching(names, singleton) do |m|
405
+ if m.parent != @container
406
+ m = m.dup
407
+ record_location(m)
408
+ new_methods << m
409
+ else
410
+ m.visibility = visibility
411
+ end
412
+ end
413
+ new_methods.each do |method|
414
+ case method
415
+ when RDoc::AnyMethod then
416
+ @container.add_method(method)
417
+ when RDoc::Attr then
418
+ @container.add_attribute(method)
419
+ end
420
+ method.visibility = visibility
421
+ end
422
+ end
423
+
424
+ # Handles `module_function :foo, :bar`
425
+
426
+ def change_method_to_module_function(names)
427
+ @container.set_visibility_for(names, :private, false)
428
+ new_methods = []
429
+ @container.methods_matching(names) do |m|
430
+ s_m = m.dup
431
+ record_location(s_m)
432
+ s_m.singleton = true
433
+ new_methods << s_m
434
+ end
435
+ new_methods.each do |method|
436
+ case method
437
+ when RDoc::AnyMethod then
438
+ @container.add_method(method)
439
+ when RDoc::Attr then
440
+ @container.add_attribute(method)
441
+ end
442
+ method.visibility = :public
443
+ end
444
+ end
445
+
446
+ # Handles `alias foo bar` and `alias_method :foo, :bar`
447
+
448
+ def add_alias_method(old_name, new_name, line_no)
449
+ comment = consecutive_comment(line_no)
450
+ handle_consecutive_comment_directive(@container, comment)
451
+ visibility = @container.find_method(old_name, @singleton)&.visibility || :public
452
+ a = RDoc::Alias.new(nil, old_name, new_name, comment, singleton: @singleton)
453
+ handle_modifier_directive(a, line_no)
454
+ a.store = @store
455
+ a.line = line_no
456
+ record_location(a)
457
+ if should_document?(a)
458
+ @container.add_alias(a)
459
+ @container.find_method(new_name, @singleton)&.visibility = visibility
460
+ end
461
+ end
462
+
463
+ # Handles `attr :a, :b`, `attr_reader :a, :b`, `attr_writer :a, :b` and `attr_accessor :a, :b`
464
+
465
+ def add_attributes(names, rw, line_no)
466
+ comment = consecutive_comment(line_no)
467
+ handle_consecutive_comment_directive(@container, comment)
468
+ return unless @container.document_children
469
+
470
+ names.each do |symbol|
471
+ a = RDoc::Attr.new(nil, symbol.to_s, rw, comment, singleton: @singleton)
472
+ a.store = @store
473
+ a.line = line_no
474
+ record_location(a)
475
+ handle_modifier_directive(a, line_no)
476
+ @container.add_attribute(a) if should_document?(a)
477
+ a.visibility = visibility # should set after adding to container
478
+ end
479
+ end
480
+
481
+ def add_includes_extends(names, rdoc_class, line_no) # :nodoc:
482
+ return if @in_proc_block
483
+ comment = consecutive_comment(line_no)
484
+ handle_consecutive_comment_directive(@container, comment)
485
+ names.each do |name|
486
+ ie = @container.add(rdoc_class, name, '')
487
+ ie.store = @store
488
+ ie.line = line_no
489
+ ie.comment = comment
490
+ record_location(ie)
491
+ end
492
+ end
493
+
494
+ # Handle `include Foo, Bar`
495
+
496
+ def add_includes(names, line_no) # :nodoc:
497
+ add_includes_extends(names, RDoc::Include, line_no)
498
+ end
499
+
500
+ # Handle `extend Foo, Bar`
501
+
502
+ def add_extends(names, line_no) # :nodoc:
503
+ add_includes_extends(names, RDoc::Extend, line_no)
504
+ end
505
+
506
+ # Adds a method defined by `def` syntax
507
+
508
+ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:)
509
+ return if @in_proc_block
510
+
511
+ receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container
512
+ meth = RDoc::AnyMethod.new(nil, name, singleton: singleton)
513
+ if (comment = consecutive_comment(start_line))
514
+ handle_consecutive_comment_directive(@container, comment)
515
+ handle_consecutive_comment_directive(meth, comment)
516
+
517
+ comment.normalize
518
+ meth.call_seq = comment.extract_call_seq
519
+ meth.comment = comment
520
+ end
521
+ handle_modifier_directive(meth, start_line)
522
+ handle_modifier_directive(meth, args_end_line)
523
+ handle_modifier_directive(meth, end_line)
524
+ return unless should_document?(meth)
525
+
526
+ internal_add_method(
527
+ receiver,
528
+ meth,
529
+ line_no: start_line,
530
+ visibility: visibility,
531
+ params: params,
532
+ calls_super: calls_super,
533
+ block_params: block_params,
534
+ tokens: tokens
535
+ )
536
+
537
+ # Rename after add_method to register duplicated 'new' and 'initialize'
538
+ # defined in c and ruby just like the old parser did.
539
+ if meth.name == 'initialize' && !singleton
540
+ if meth.dont_rename_initialize
541
+ meth.visibility = :protected
542
+ else
543
+ meth.name = 'new'
544
+ meth.singleton = true
545
+ meth.visibility = :public
546
+ end
547
+ end
548
+ end
549
+
550
+ private def internal_add_method(container, meth, line_no:, visibility:, params:, calls_super:, block_params:, tokens:) # :nodoc:
551
+ meth.name ||= meth.call_seq[/\A[^()\s]+/] if meth.call_seq
552
+ meth.name ||= 'unknown'
553
+ meth.store = @store
554
+ meth.line = line_no
555
+ container.add_method(meth) # should add after setting singleton and before setting visibility
556
+ meth.visibility = visibility
557
+ meth.params ||= params
558
+ meth.calls_super = calls_super
559
+ meth.block_params ||= block_params if block_params
560
+ record_location(meth)
561
+ meth.start_collecting_tokens
562
+ tokens.each do |token|
563
+ meth.token_stream << token
564
+ end
565
+ end
566
+
567
+ # Find or create module or class from a given module name.
568
+ # If module or class does not exist, creates a module or a class according to `create_mode` argument.
569
+
570
+ def find_or_create_module_path(module_name, create_mode)
571
+ root_name, *path, name = module_name.split('::')
572
+ add_module = ->(mod, name, mode) {
573
+ case mode
574
+ when :class
575
+ mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store }
576
+ when :module
577
+ mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store }
578
+ end
579
+ }
580
+ if root_name.empty?
581
+ mod = @top_level
582
+ else
583
+ @module_nesting.reverse_each do |nesting, singleton|
584
+ next if singleton
585
+ mod = nesting.find_module_named(root_name)
586
+ break if mod
587
+ # If a constant is found and it is not a module or class, RDoc can't document about it.
588
+ # Return an anonymous module to avoid wrong document creation.
589
+ return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name)
590
+ end
591
+ last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton }
592
+ return mod || add_module.call(last_nesting, root_name, create_mode) unless name
593
+ mod ||= add_module.call(last_nesting, root_name, :module)
594
+ end
595
+ path.each do |name|
596
+ mod = mod.find_module_named(name) || add_module.call(mod, name, :module)
597
+ end
598
+ mod.find_module_named(name) || add_module.call(mod, name, create_mode)
599
+ end
600
+
601
+ # Resolves constant path to a full path by searching module nesting
602
+
603
+ def resolve_constant_path(constant_path)
604
+ owner_name, path = constant_path.split('::', 2)
605
+ return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar
606
+ mod = nil
607
+ @module_nesting.reverse_each do |nesting, singleton|
608
+ next if singleton
609
+ mod = nesting.find_module_named(owner_name)
610
+ break if mod
611
+ end
612
+ mod ||= @top_level.find_module_named(owner_name)
613
+ [mod.full_name, path].compact.join('::') if mod
614
+ end
615
+
616
+ # Returns a pair of owner module and constant name from a given constant path.
617
+ # Creates owner module if it does not exist.
618
+
619
+ def find_or_create_constant_owner_name(constant_path)
620
+ const_path, colon, name = constant_path.rpartition('::')
621
+ if colon.empty? # class Foo
622
+ # Within `class C` or `module C`, owner is C(== current container)
623
+ # Within `class <<C`, owner is C.singleton_class
624
+ # but RDoc don't track constants of a singleton class of module
625
+ [(@singleton ? nil : @container), name]
626
+ elsif const_path.empty? # class ::Foo
627
+ [@top_level, name]
628
+ else # `class Foo::Bar` or `class ::Foo::Bar`
629
+ [find_or_create_module_path(const_path, :module), name]
630
+ end
631
+ end
632
+
633
+ # Adds a constant
634
+
635
+ def add_constant(constant_name, rhs_name, start_line, end_line)
636
+ comment = consecutive_comment(start_line)
637
+ handle_consecutive_comment_directive(@container, comment)
638
+ owner, name = find_or_create_constant_owner_name(constant_name)
639
+ return unless owner
640
+
641
+ constant = RDoc::Constant.new(name, rhs_name, comment)
642
+ constant.store = @store
643
+ constant.line = start_line
644
+ record_location(constant)
645
+ handle_modifier_directive(constant, start_line)
646
+ handle_modifier_directive(constant, end_line)
647
+ owner.add_constant(constant)
648
+ mod =
649
+ if rhs_name =~ /^::/
650
+ @store.find_class_or_module(rhs_name)
651
+ else
652
+ @container.find_module_named(rhs_name)
653
+ end
654
+ if mod && constant.document_self
655
+ a = @container.add_module_alias(mod, rhs_name, constant, @top_level)
656
+ a.store = @store
657
+ a.line = start_line
658
+ record_location(a)
659
+ end
660
+ end
661
+
662
+ # Adds module or class
663
+
664
+ def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil)
665
+ comment = consecutive_comment(start_line)
666
+ handle_consecutive_comment_directive(@container, comment)
667
+ return unless @container.document_children
668
+
669
+ owner, name = find_or_create_constant_owner_name(module_name)
670
+ return unless owner
671
+
672
+ if is_class
673
+ # RDoc::NormalClass resolves superclass name despite of the lack of module nesting information.
674
+ # We need to fix it when RDoc::NormalClass resolved to a wrong constant name
675
+ if superclass_name
676
+ superclass_full_path = resolve_constant_path(superclass_name)
677
+ superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path
678
+ superclass_full_path ||= superclass_name
679
+ superclass_full_path = superclass_full_path.sub(/^::/, '')
680
+ end
681
+ # add_class should be done after resolving superclass
682
+ mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object')
683
+ if superclass_name
684
+ if superclass
685
+ mod.superclass = superclass
686
+ elsif (mod.superclass.is_a?(String) || mod.superclass.name == 'Object') && mod.superclass != superclass_full_path
687
+ mod.superclass = superclass_full_path
688
+ end
689
+ end
690
+ else
691
+ mod = owner.modules_hash[name] || owner.add_module(RDoc::NormalModule, name)
692
+ end
693
+
694
+ mod.store = @store
695
+ mod.line = start_line
696
+ record_location(mod)
697
+ handle_modifier_directive(mod, start_line)
698
+ handle_modifier_directive(mod, end_line)
699
+ mod.add_comment(comment, @top_level) if comment
700
+ mod
701
+ end
702
+
703
+ class RDocVisitor < Prism::Visitor # :nodoc:
704
+ def initialize(scanner, top_level, store)
705
+ @scanner = scanner
706
+ @top_level = top_level
707
+ @store = store
708
+ end
709
+
710
+ def visit_if_node(node)
711
+ if node.end_keyword
712
+ super
713
+ else
714
+ # Visit with the order in text representation to handle this method comment
715
+ # # comment
716
+ # def f
717
+ # end if call_node
718
+ node.statements.accept(self)
719
+ node.predicate.accept(self)
720
+ end
721
+ end
722
+ alias visit_unless_node visit_if_node
723
+
724
+ def visit_call_node(node)
725
+ @scanner.process_comments_until(node.location.start_line - 1)
726
+ if node.receiver.nil?
727
+ case node.name
728
+ when :attr
729
+ _visit_call_attr_reader_writer_accessor(node, 'R')
730
+ when :attr_reader
731
+ _visit_call_attr_reader_writer_accessor(node, 'R')
732
+ when :attr_writer
733
+ _visit_call_attr_reader_writer_accessor(node, 'W')
734
+ when :attr_accessor
735
+ _visit_call_attr_reader_writer_accessor(node, 'RW')
736
+ when :include
737
+ _visit_call_include(node)
738
+ when :extend
739
+ _visit_call_extend(node)
740
+ when :public
741
+ _visit_call_public_private_protected(node, :public) { super }
742
+ when :private
743
+ _visit_call_public_private_protected(node, :private) { super }
744
+ when :protected
745
+ _visit_call_public_private_protected(node, :protected) { super }
746
+ when :private_constant
747
+ _visit_call_private_constant(node)
748
+ when :public_constant
749
+ _visit_call_public_constant(node)
750
+ when :require
751
+ _visit_call_require(node)
752
+ when :alias_method
753
+ _visit_call_alias_method(node)
754
+ when :module_function
755
+ _visit_call_module_function(node) { super }
756
+ when :public_class_method
757
+ _visit_call_public_private_class_method(node, :public) { super }
758
+ when :private_class_method
759
+ _visit_call_public_private_class_method(node, :private) { super }
760
+ else
761
+ node.arguments&.accept(self)
762
+ super
763
+ end
764
+ else
765
+ super
766
+ end
767
+ end
768
+
769
+ def visit_block_node(node)
770
+ @scanner.with_in_proc_block do
771
+ # include, extend and method definition inside block are not documentable
772
+ super
773
+ end
774
+ end
775
+
776
+ def visit_alias_method_node(node)
777
+ @scanner.process_comments_until(node.location.start_line - 1)
778
+ return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode)
779
+ @scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line)
780
+ end
781
+
782
+ def visit_module_node(node)
783
+ node.constant_path.accept(self)
784
+ @scanner.process_comments_until(node.location.start_line - 1)
785
+ module_name = constant_path_string(node.constant_path)
786
+ mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name
787
+ if mod
788
+ @scanner.with_container(mod) do
789
+ node.body&.accept(self)
790
+ @scanner.process_comments_until(node.location.end_line)
791
+ end
792
+ else
793
+ @scanner.skip_comments_until(node.location.end_line)
794
+ end
795
+ end
796
+
797
+ def visit_class_node(node)
798
+ node.constant_path.accept(self)
799
+ node.superclass&.accept(self)
800
+ @scanner.process_comments_until(node.location.start_line - 1)
801
+ superclass_name = constant_path_string(node.superclass) if node.superclass
802
+ superclass_expr = node.superclass.slice if node.superclass && !superclass_name
803
+ class_name = constant_path_string(node.constant_path)
804
+ klass = @scanner.add_module_or_class(class_name, node.location.start_line, node.location.end_line, is_class: true, superclass_name: superclass_name, superclass_expr: superclass_expr) if class_name
805
+ if klass
806
+ @scanner.with_container(klass) do
807
+ node.body&.accept(self)
808
+ @scanner.process_comments_until(node.location.end_line)
809
+ end
810
+ else
811
+ @scanner.skip_comments_until(node.location.end_line)
812
+ end
813
+ end
814
+
815
+ def visit_singleton_class_node(node)
816
+ @scanner.process_comments_until(node.location.start_line - 1)
817
+
818
+ if @scanner.has_modifier_nodoc?(node.location.start_line)
819
+ # Skip visiting inside the singleton class. Also skips creation of node.expression as a module
820
+ @scanner.skip_comments_until(node.location.end_line)
821
+ return
822
+ end
823
+
824
+ expression = node.expression
825
+ expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1
826
+
827
+ case expression
828
+ when Prism::ConstantWriteNode
829
+ # Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module
830
+ mod = @scanner.container.add_module(RDoc::NormalModule, expression.name.to_s)
831
+ when Prism::ConstantPathNode, Prism::ConstantReadNode
832
+ expression_name = constant_path_string(expression)
833
+ # If a constant_path does not exist, RDoc creates a module
834
+ mod = @scanner.find_or_create_module_path(expression_name, :module) if expression_name
835
+ when Prism::SelfNode
836
+ mod = @scanner.container if @scanner.container != @top_level
837
+ end
838
+ expression.accept(self)
839
+ if mod
840
+ @scanner.with_container(mod, singleton: true) do
841
+ node.body&.accept(self)
842
+ @scanner.process_comments_until(node.location.end_line)
843
+ end
844
+ else
845
+ @scanner.skip_comments_until(node.location.end_line)
846
+ end
847
+ end
848
+
849
+ def visit_def_node(node)
850
+ start_line = node.location.start_line
851
+ args_end_line = node.parameters&.location&.end_line || start_line
852
+ end_line = node.location.end_line
853
+ @scanner.process_comments_until(start_line - 1)
854
+
855
+ case node.receiver
856
+ when Prism::NilNode, Prism::TrueNode, Prism::FalseNode
857
+ visibility = :public
858
+ singleton = false
859
+ receiver_name =
860
+ case node.receiver
861
+ when Prism::NilNode
862
+ 'NilClass'
863
+ when Prism::TrueNode
864
+ 'TrueClass'
865
+ when Prism::FalseNode
866
+ 'FalseClass'
867
+ end
868
+ receiver_fallback_type = :class
869
+ when Prism::SelfNode
870
+ # singleton method of a singleton class is not documentable
871
+ return if @scanner.singleton
872
+ visibility = :public
873
+ singleton = true
874
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
875
+ visibility = :public
876
+ singleton = true
877
+ receiver_name = constant_path_string(node.receiver)
878
+ receiver_fallback_type = :module
879
+ return unless receiver_name
880
+ when nil
881
+ visibility = @scanner.visibility
882
+ singleton = @scanner.singleton
883
+ else
884
+ # `def (unknown expression).method_name` is not documentable
885
+ return
886
+ end
887
+ name = node.name.to_s
888
+ params, block_params, calls_super = MethodSignatureVisitor.scan_signature(node)
889
+ tokens = @scanner.visible_tokens_from_location(node.location)
890
+
891
+ @scanner.add_method(
892
+ name,
893
+ receiver_name: receiver_name,
894
+ receiver_fallback_type: receiver_fallback_type,
895
+ visibility: visibility,
896
+ singleton: singleton,
897
+ params: params,
898
+ block_params: block_params,
899
+ calls_super: calls_super,
900
+ tokens: tokens,
901
+ start_line: start_line,
902
+ args_end_line: args_end_line,
903
+ end_line: end_line
904
+ )
905
+ ensure
906
+ @scanner.skip_comments_until(end_line)
907
+ end
908
+
909
+ def visit_constant_path_write_node(node)
910
+ @scanner.process_comments_until(node.location.start_line - 1)
911
+ path = constant_path_string(node.target)
912
+ return unless path
913
+
914
+ @scanner.add_constant(
915
+ path,
916
+ constant_path_string(node.value) || node.value.slice,
917
+ node.location.start_line,
918
+ node.location.end_line
919
+ )
920
+ @scanner.skip_comments_until(node.location.end_line)
921
+ # Do not traverse rhs not to document `A::B = Struct.new{def undocumentable_method; end}`
922
+ end
923
+
924
+ def visit_constant_write_node(node)
925
+ @scanner.process_comments_until(node.location.start_line - 1)
926
+ @scanner.add_constant(
927
+ node.name.to_s,
928
+ constant_path_string(node.value) || node.value.slice,
929
+ node.location.start_line,
930
+ node.location.end_line
931
+ )
932
+ @scanner.skip_comments_until(node.location.end_line)
933
+ # Do not traverse rhs not to document `A = Struct.new{def undocumentable_method; end}`
934
+ end
935
+
936
+ private
937
+
938
+ def constant_arguments_names(call_node)
939
+ return unless call_node.arguments
940
+ names = call_node.arguments.arguments.map { |arg| constant_path_string(arg) }
941
+ names.all? ? names : nil
942
+ end
943
+
944
+ def symbol_arguments(call_node)
945
+ arguments_node = call_node.arguments
946
+ return unless arguments_node && arguments_node.arguments.all? { |arg| arg.is_a?(Prism::SymbolNode)}
947
+ arguments_node.arguments.map { |arg| arg.value.to_sym }
948
+ end
949
+
950
+ def visibility_method_arguments(call_node, singleton:)
951
+ arguments_node = call_node.arguments
952
+ return unless arguments_node
953
+ symbols = symbol_arguments(call_node)
954
+ if symbols
955
+ # module_function :foo, :bar
956
+ return symbols.map(&:to_s)
957
+ else
958
+ return unless arguments_node.arguments.size == 1
959
+ arg = arguments_node.arguments.first
960
+ return unless arg.is_a?(Prism::DefNode)
961
+
962
+ if singleton
963
+ # `private_class_method def foo; end` `private_class_method def not_self.foo; end` should be ignored
964
+ return unless arg.receiver.is_a?(Prism::SelfNode)
965
+ else
966
+ # `module_function def something.foo` should be ignored
967
+ return if arg.receiver
968
+ end
969
+ # `module_function def foo; end` or `private_class_method def self.foo; end`
970
+ [arg.name.to_s]
971
+ end
972
+ end
973
+
974
+ def constant_path_string(node)
975
+ case node
976
+ when Prism::ConstantReadNode
977
+ node.name.to_s
978
+ when Prism::ConstantPathNode
979
+ parent_name = node.parent ? constant_path_string(node.parent) : ''
980
+ "#{parent_name}::#{node.name}" if parent_name
981
+ end
982
+ end
983
+
984
+ def _visit_call_require(call_node)
985
+ return unless call_node.arguments&.arguments&.size == 1
986
+ arg = call_node.arguments.arguments.first
987
+ return unless arg.is_a?(Prism::StringNode)
988
+ @scanner.container.add_require(RDoc::Require.new(arg.unescaped, nil))
989
+ end
990
+
991
+ def _visit_call_module_function(call_node)
992
+ yield
993
+ return if @scanner.singleton
994
+ names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s)
995
+ @scanner.change_method_to_module_function(names) if names
996
+ end
997
+
998
+ def _visit_call_public_private_class_method(call_node, visibility)
999
+ yield
1000
+ return if @scanner.singleton
1001
+ names = visibility_method_arguments(call_node, singleton: true)
1002
+ @scanner.change_method_visibility(names, visibility, singleton: true) if names
1003
+ end
1004
+
1005
+ def _visit_call_public_private_protected(call_node, visibility)
1006
+ arguments_node = call_node.arguments
1007
+ if arguments_node.nil? # `public` `private`
1008
+ @scanner.visibility = visibility
1009
+ else # `public :foo, :bar`, `private def foo; end`
1010
+ yield
1011
+ names = visibility_method_arguments(call_node, singleton: false)
1012
+ @scanner.change_method_visibility(names, visibility) if names
1013
+ end
1014
+ end
1015
+
1016
+ def _visit_call_alias_method(call_node)
1017
+ new_name, old_name, *rest = symbol_arguments(call_node)
1018
+ return unless old_name && new_name && rest.empty?
1019
+ @scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line)
1020
+ end
1021
+
1022
+ def _visit_call_include(call_node)
1023
+ names = constant_arguments_names(call_node)
1024
+ line_no = call_node.location.start_line
1025
+ return unless names
1026
+
1027
+ if @scanner.singleton
1028
+ @scanner.add_extends(names, line_no)
1029
+ else
1030
+ @scanner.add_includes(names, line_no)
1031
+ end
1032
+ end
1033
+
1034
+ def _visit_call_extend(call_node)
1035
+ names = constant_arguments_names(call_node)
1036
+ @scanner.add_extends(names, call_node.location.start_line) if names && !@scanner.singleton
1037
+ end
1038
+
1039
+ def _visit_call_public_constant(call_node)
1040
+ return if @scanner.singleton
1041
+ names = symbol_arguments(call_node)
1042
+ @scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names
1043
+ end
1044
+
1045
+ def _visit_call_private_constant(call_node)
1046
+ return if @scanner.singleton
1047
+ names = symbol_arguments(call_node)
1048
+ @scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names
1049
+ end
1050
+
1051
+ def _visit_call_attr_reader_writer_accessor(call_node, rw)
1052
+ names = symbol_arguments(call_node)
1053
+ @scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names
1054
+ end
1055
+ class MethodSignatureVisitor < Prism::Visitor # :nodoc:
1056
+ class << self
1057
+ def scan_signature(def_node)
1058
+ visitor = new
1059
+ def_node.body&.accept(visitor)
1060
+ params = "(#{def_node.parameters&.slice})"
1061
+ block_params = visitor.yields.first
1062
+ [params, block_params, visitor.calls_super]
1063
+ end
1064
+ end
1065
+
1066
+ attr_reader :params, :yields, :calls_super
1067
+
1068
+ def initialize
1069
+ @params = nil
1070
+ @calls_super = false
1071
+ @yields = []
1072
+ end
1073
+
1074
+ def visit_def_node(node)
1075
+ # stop traverse inside nested def
1076
+ end
1077
+
1078
+ def visit_yield_node(node)
1079
+ @yields << (node.arguments&.slice || '')
1080
+ end
1081
+
1082
+ def visit_super_node(node)
1083
+ @calls_super = true
1084
+ super
1085
+ end
1086
+
1087
+ def visit_forwarding_super_node(node)
1088
+ @calls_super = true
1089
+ end
1090
+ end
1091
+ end
1092
+ end