rdoc 7.2.0 → 8.0.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +3 -4
  3. data/LICENSE.rdoc +4 -0
  4. data/README.md +43 -2
  5. data/doc/markup_reference/markdown.md +104 -3
  6. data/lib/rdoc/code_object/alias.rb +2 -8
  7. data/lib/rdoc/code_object/any_method.rb +11 -6
  8. data/lib/rdoc/code_object/attr.rb +11 -6
  9. data/lib/rdoc/code_object/class_module.rb +62 -32
  10. data/lib/rdoc/code_object/constant.rb +29 -3
  11. data/lib/rdoc/code_object/context/section.rb +4 -35
  12. data/lib/rdoc/code_object/context.rb +39 -34
  13. data/lib/rdoc/code_object/method_attr.rb +9 -15
  14. data/lib/rdoc/code_object/mixin.rb +2 -2
  15. data/lib/rdoc/code_object/top_level.rb +9 -3
  16. data/lib/rdoc/code_object.rb +2 -4
  17. data/lib/rdoc/comment.rb +0 -65
  18. data/lib/rdoc/cross_reference.rb +7 -27
  19. data/lib/rdoc/encoding.rb +3 -3
  20. data/lib/rdoc/generator/aliki.rb +17 -0
  21. data/lib/rdoc/generator/darkfish.rb +12 -6
  22. data/lib/rdoc/generator/json_index.rb +2 -2
  23. data/lib/rdoc/generator/markup.rb +56 -31
  24. data/lib/rdoc/generator/template/aliki/DESIGN.md +536 -0
  25. data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +1 -1
  26. data/lib/rdoc/generator/template/aliki/_head.rhtml +1 -1
  27. data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +8 -6
  28. data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +8 -6
  29. data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +1 -1
  30. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +2 -2
  31. data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +1 -1
  32. data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +1 -1
  33. data/lib/rdoc/generator/template/aliki/class.rhtml +56 -46
  34. data/lib/rdoc/generator/template/aliki/css/rdoc.css +337 -111
  35. data/lib/rdoc/generator/template/aliki/index.rhtml +1 -1
  36. data/lib/rdoc/generator/template/aliki/js/aliki.js +20 -18
  37. data/lib/rdoc/generator/template/aliki/page.rhtml +1 -1
  38. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
  39. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +2 -2
  40. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +8 -6
  41. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +8 -6
  42. data/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +1 -1
  43. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +1 -1
  44. data/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +1 -1
  45. data/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +5 -5
  46. data/lib/rdoc/generator/template/darkfish/class.rhtml +18 -21
  47. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +0 -1
  48. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +3 -3
  49. data/lib/rdoc/i18n/text.rb +3 -3
  50. data/lib/rdoc/markdown.kpeg +15 -10
  51. data/lib/rdoc/markdown.rb +289 -104
  52. data/lib/rdoc/markup/document.rb +2 -2
  53. data/lib/rdoc/markup/formatter.rb +24 -34
  54. data/lib/rdoc/markup/heading.rb +1 -4
  55. data/lib/rdoc/markup/indented_paragraph.rb +1 -1
  56. data/lib/rdoc/markup/list.rb +2 -2
  57. data/lib/rdoc/markup/list_item.rb +2 -2
  58. data/lib/rdoc/markup/pre_process.rb +0 -25
  59. data/lib/rdoc/markup/to_ansi.rb +1 -1
  60. data/lib/rdoc/markup/to_bs.rb +1 -1
  61. data/lib/rdoc/markup/to_html.rb +131 -53
  62. data/lib/rdoc/markup/to_html_crossref.rb +97 -71
  63. data/lib/rdoc/markup/to_html_snippet.rb +5 -5
  64. data/lib/rdoc/markup/to_joined_paragraph.rb +0 -5
  65. data/lib/rdoc/markup/to_label.rb +2 -2
  66. data/lib/rdoc/markup/to_markdown.rb +1 -1
  67. data/lib/rdoc/markup/to_rdoc.rb +2 -2
  68. data/lib/rdoc/markup/to_table_of_contents.rb +1 -1
  69. data/lib/rdoc/markup/to_tt_only.rb +0 -7
  70. data/lib/rdoc/markup/verbatim.rb +1 -1
  71. data/lib/rdoc/options.rb +36 -51
  72. data/lib/rdoc/parser/c.rb +7 -6
  73. data/lib/rdoc/parser/rbs.rb +275 -0
  74. data/lib/rdoc/parser/ruby.rb +954 -2066
  75. data/lib/rdoc/parser/ruby_colorizer.rb +253 -0
  76. data/lib/rdoc/parser.rb +3 -2
  77. data/lib/rdoc/rbs_helper.rb +186 -0
  78. data/lib/rdoc/rdoc.rb +196 -24
  79. data/lib/rdoc/ri/driver.rb +8 -2
  80. data/lib/rdoc/ri/paths.rb +1 -1
  81. data/lib/rdoc/{servlet.rb → ri/servlet.rb} +5 -5
  82. data/lib/rdoc/ri.rb +4 -3
  83. data/lib/rdoc/rubygems_hook.rb +11 -11
  84. data/lib/rdoc/server.rb +460 -0
  85. data/lib/rdoc/stats.rb +147 -124
  86. data/lib/rdoc/store.rb +212 -4
  87. data/lib/rdoc/task.rb +16 -15
  88. data/lib/rdoc/text.rb +1 -118
  89. data/lib/rdoc/token_stream.rb +11 -33
  90. data/lib/rdoc/version.rb +1 -1
  91. data/lib/rdoc.rb +35 -7
  92. data/lib/rubygems_plugin.rb +2 -11
  93. data/rdoc-logo.svg +43 -0
  94. data/rdoc.gemspec +6 -4
  95. metadata +35 -18
  96. data/lib/rdoc/code_object/anon_class.rb +0 -10
  97. data/lib/rdoc/code_object/ghost_method.rb +0 -6
  98. data/lib/rdoc/code_object/meta_method.rb +0 -6
  99. data/lib/rdoc/parser/prism_ruby.rb +0 -1112
  100. data/lib/rdoc/parser/ripper_state_lex.rb +0 -302
  101. data/lib/rdoc/parser/ruby_tools.rb +0 -163
@@ -1,31 +1,14 @@
1
1
  # frozen_string_literal: true
2
- ##
3
- # This file contains stuff stolen outright from:
4
- #
5
- # rtags.rb -
6
- # ruby-lex.rb - ruby lexcal analyzer
7
- # ruby-token.rb - ruby tokens
8
- # by Keiju ISHITSUKA (Nippon Rational Inc.)
9
- #
10
2
 
11
- if ENV['RDOC_USE_PRISM_PARSER']
12
- require 'rdoc/parser/prism_ruby'
13
- RDoc::Parser.const_set(:Ruby, RDoc::Parser::PrismRuby)
14
- puts "========================================================================="
15
- puts "RDoc is using the experimental Prism parser to generate the documentation"
16
- puts "========================================================================="
17
- return
18
- end
3
+ require 'prism'
4
+ require_relative '../rbs_helper'
19
5
 
20
- require 'ripper'
21
- require_relative 'ripper_state_lex'
6
+ # Parse and collect document from Ruby source code.
22
7
 
23
8
  ##
24
9
  # Extracts code elements from a source file returning a TopLevel object
25
10
  # containing the constituent file elements.
26
11
  #
27
- # This file is based on rtags
28
- #
29
12
  # RubyParser understands how to document:
30
13
  # * classes
31
14
  # * modules
@@ -44,10 +27,6 @@ require_relative 'ripper_state_lex'
44
27
  #
45
28
  # == Method Arguments
46
29
  #
47
- #--
48
- # NOTE: I don't think this works, needs tests, remove the paragraph following
49
- # this block when known to work
50
- #
51
30
  # The parser extracts the arguments from the method definition. You can
52
31
  # override this with a custom argument definition using the :args: directive:
53
32
  #
@@ -61,11 +40,6 @@ require_relative 'ripper_state_lex'
61
40
  #
62
41
  # If you have a more-complex set of overrides you can use the :call-seq:
63
42
  # directive:
64
- #++
65
- #
66
- # The parser extracts the arguments from the method definition. You can
67
- # override this with a custom argument definition using the :call-seq:
68
- # directive:
69
43
  #
70
44
  # ##
71
45
  # # This method can be called with a range or an offset and length
@@ -92,14 +66,14 @@ require_relative 'ripper_state_lex'
92
66
  # == Metaprogrammed Methods
93
67
  #
94
68
  # To pick up a metaprogrammed method, the parser looks for a comment starting
95
- # with '##' before an identifier:
69
+ # with '##' before a metaprogramming method call:
96
70
  #
97
71
  # ##
98
72
  # # This is a meta-programmed method!
99
73
  #
100
74
  # add_my_method :meta_method, :arg1, :arg2
101
75
  #
102
- # The parser looks at the token after the identifier to determine the name, in
76
+ # The parser looks at the first argument to determine the name, in
103
77
  # this example, :meta_method. If a name cannot be found, a warning is printed
104
78
  # and 'unknown' is used.
105
79
  #
@@ -154,21 +128,11 @@ class RDoc::Parser::Ruby < RDoc::Parser
154
128
 
155
129
  parse_files_matching(/\.rbw?$/)
156
130
 
157
- include RDoc::TokenStream
158
- include RDoc::Parser::RubyTools
159
-
160
- ##
161
- # RDoc::NormalClass type
162
-
163
- NORMAL = "::"
164
-
165
- ##
166
- # RDoc::SingleClass type
131
+ # Matches an RBS inline type annotation line: #: followed by whitespace
132
+ RBS_SIG_LINE = /\A#:\s/ # :nodoc:
167
133
 
168
- SINGLE = "<<"
169
-
170
- ##
171
- # Creates a new Ruby parser.
134
+ attr_accessor :visibility
135
+ attr_reader :container, :singleton, :in_proc_block
172
136
 
173
137
  def initialize(top_level, content, options, stats)
174
138
  super
@@ -178,2201 +142,1125 @@ class RDoc::Parser::Ruby < RDoc::Parser
178
142
  @size = 0
179
143
  @token_listeners = nil
180
144
  content = RDoc::Encoding.remove_magic_comment content
181
- @scanner = RDoc::Parser::RipperStateLex.parse(content)
182
145
  @content = content
183
- @scanner_point = 0
184
- @prev_seek = nil
185
146
  @markup = @options.markup
186
147
  @track_visibility = :nodoc != @options.visibility
187
148
  @encoding = @options.encoding
188
149
 
189
- reset
190
- end
191
-
192
- ##
193
- # Return +true+ if +tk+ is a newline.
194
-
195
- def tk_nl?(tk)
196
- :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]
150
+ @module_nesting = [[top_level, false]]
151
+ @container = top_level
152
+ @visibility = :public
153
+ @singleton = false
154
+ @in_proc_block = false
155
+ end
156
+
157
+ # Suppress `extend` and `include` within block
158
+ # because they might be a metaprogramming block
159
+ # example: `Module.new { include M }` `M.module_eval { include N }`
160
+
161
+ def with_in_proc_block
162
+ in_proc_block = @in_proc_block
163
+ @in_proc_block = true
164
+ yield
165
+ @in_proc_block = in_proc_block
166
+ end
167
+
168
+ # Dive into another container
169
+
170
+ def with_container(container, singleton: false)
171
+ old_container = @container
172
+ old_visibility = @visibility
173
+ old_singleton = @singleton
174
+ old_in_proc_block = @in_proc_block
175
+ @visibility = :public
176
+ @container = container
177
+ @singleton = singleton
178
+ @in_proc_block = false
179
+ @module_nesting.push([container, singleton])
180
+ yield container
181
+ ensure
182
+ @container = old_container
183
+ @visibility = old_visibility
184
+ @singleton = old_singleton
185
+ @in_proc_block = old_in_proc_block
186
+ @module_nesting.pop
197
187
  end
198
188
 
199
- ##
200
- # Retrieves the read token stream and replaces +pattern+ with +replacement+
201
- # using gsub. If the result is only a ";" returns an empty string.
202
-
203
- def get_tkread_clean(pattern, replacement) # :nodoc:
204
- read = get_tkread.gsub(pattern, replacement).strip
205
- return '' if read == ';'
206
- read
207
- end
189
+ # Records the location of this +container+ in the file for this parser and
190
+ # adds it to the list of classes and modules in the file.
208
191
 
209
- ##
210
- # Extracts the visibility information for the visibility token +tk+
211
- # and +single+ class type identifier.
212
- #
213
- # Returns the visibility type (a string), the visibility (a symbol) and
214
- # +singleton+ if the methods following should be converted to singleton
215
- # methods.
216
-
217
- def get_visibility_information(tk, single) # :nodoc:
218
- vis_type = tk[:text]
219
- singleton = single == SINGLE
220
-
221
- vis =
222
- case vis_type
223
- when 'private' then :private
224
- when 'protected' then :protected
225
- when 'public' then :public
226
- when 'private_class_method' then
227
- singleton = true
228
- :private
229
- when 'public_class_method' then
230
- singleton = true
231
- :public
232
- when 'module_function' then
233
- singleton = true
234
- :public
235
- else
236
- raise RDoc::Error, "Invalid visibility: #{tk.name}"
237
- end
192
+ def record_location(container) # :nodoc:
193
+ case container
194
+ when RDoc::ClassModule then
195
+ @top_level.add_to_classes_or_modules container
196
+ end
238
197
 
239
- return vis_type, vis, singleton
198
+ container.record_location @top_level
240
199
  end
241
200
 
242
- ##
243
- # Look for the first comment in a file that isn't a shebang line.
244
-
245
- def collect_first_comment
246
- skip_tkspace
247
- comment = ''.dup
248
- comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
249
- first_line = true
250
- first_comment_tk_kind = nil
251
- line_no = nil
252
-
253
- tk = get_tk
254
-
255
- while tk && (:on_comment == tk[:kind] or :on_embdoc == tk[:kind])
256
- comment_body = retrieve_comment_body(tk)
257
- if first_line and comment_body =~ /\A#!/ then
258
- skip_tkspace
259
- tk = get_tk
260
- elsif first_line and comment_body =~ /\A#\s*-\*-/ then
261
- first_line = false
262
- skip_tkspace
263
- tk = get_tk
264
- else
265
- break if first_comment_tk_kind and not first_comment_tk_kind === tk[:kind]
266
- first_comment_tk_kind = tk[:kind]
201
+ # Scans this Ruby file for Ruby constructs
267
202
 
268
- line_no = tk[:line_no] if first_line
269
- first_line = false
270
- comment << comment_body
271
- tk = get_tk
203
+ def scan
204
+ @lines = @content.lines
205
+ result = Prism.parse_lex(@content)
206
+ @program_node, unordered_tokens = result.value
207
+ # Heredoc tokens are not in start_offset order.
208
+ # Need to sort them to use bsearch for finding tokens from location.
209
+ @prism_tokens = unordered_tokens.map(&:first).sort_by { |t| t.location.start_offset }
210
+ @line_nodes = {}
211
+ prepare_line_nodes(@program_node)
212
+ prepare_comments(result.comments)
213
+ return if @top_level.done_documenting
272
214
 
273
- if :on_nl === tk then
274
- skip_tkspace_without_nl
275
- tk = get_tk
276
- end
277
- end
215
+ @first_non_meta_comment_start_line = nil
216
+ if (_line_no, start_line = @unprocessed_comments.first)
217
+ @first_non_meta_comment_start_line = start_line if start_line < @program_node.location.start_line
278
218
  end
279
219
 
280
- unget_tk tk
281
-
282
- new_comment comment, line_no
220
+ @program_node.accept(RDocVisitor.new(self, @top_level, @store))
221
+ process_comments_until(@lines.size + 1)
283
222
  end
284
223
 
285
- ##
286
- # Consumes trailing whitespace from the token stream
287
-
288
- def consume_trailing_spaces # :nodoc:
289
- skip_tkspace_without_nl
224
+ def should_document?(code_object) # :nodoc:
225
+ return true unless @track_visibility
226
+ return false if code_object.parent&.document_children == false
227
+ code_object.document_self
290
228
  end
291
229
 
292
- ##
293
- # Creates a new attribute in +container+ with +name+.
230
+ # Assign AST node to a line.
231
+ # This is used to show meta-method source code in the documentation.
294
232
 
295
- def create_attr(container, single, name, rw, comment) # :nodoc:
296
- att = RDoc::Attr.new get_tkread, name, rw, comment, singleton: single == SINGLE
297
- record_location att
233
+ def prepare_line_nodes(node) # :nodoc:
234
+ case node
235
+ when Prism::CallNode, Prism::DefNode
236
+ @line_nodes[node.location.start_line] ||= node
237
+ end
238
+ node.compact_child_nodes.each do |child|
239
+ prepare_line_nodes(child)
240
+ end
241
+ end
298
242
 
299
- container.add_attribute att
300
- @stats.add_attribute att
243
+ # Prepares comments for processing. Comments are grouped into consecutive.
244
+ # Consecutive comment is linked to the next non-blank line.
245
+ #
246
+ # Example:
247
+ # 01| class A # modifier comment 1
248
+ # 02| def foo; end # modifier comment 2
249
+ # 03|
250
+ # 04| # consecutive comment 1 start_line: 4
251
+ # 05| # consecutive comment 1 linked to line: 7
252
+ # 06|
253
+ # 07| # consecutive comment 2 start_line: 7
254
+ # 08| # consecutive comment 2 linked to line: 10
255
+ # 09|
256
+ # 10| def bar; end # consecutive comment 2 linked to this line
257
+ # 11| end
258
+
259
+ def prepare_comments(comments)
260
+ current = []
261
+ consecutive_comments = [current]
262
+ @modifier_comments = {}
263
+ comments.each do |comment|
264
+ if comment.is_a? Prism::EmbDocComment
265
+ consecutive_comments << [comment] << (current = [])
266
+ elsif comment.location.start_line_slice.match?(/\S/)
267
+ text = comment.slice
268
+ text = RDoc::Encoding.change_encoding(text, @encoding) if @encoding
269
+ @modifier_comments[comment.location.start_line] = text
270
+ elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line
271
+ current << comment
272
+ else
273
+ consecutive_comments << (current = [comment])
274
+ end
275
+ end
276
+ consecutive_comments.reject!(&:empty?)
277
+
278
+ # Example: line_no = 5, start_line = 2, comment_text = "# comment_start_line\n# comment\n"
279
+ # 1| class A
280
+ # 2| # comment_start_line
281
+ # 3| # comment
282
+ # 4|
283
+ # 5| def f; end # comment linked to this line
284
+ # 6| end
285
+ @unprocessed_comments = consecutive_comments.map! do |comments|
286
+ start_line = comments.first.location.start_line
287
+ line_no = comments.last.location.end_line + (comments.last.location.end_column == 0 ? 0 : 1)
288
+ texts = comments.map do |c|
289
+ c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice
290
+ end
291
+ text = texts.join("\n")
292
+ text = RDoc::Encoding.change_encoding(text, @encoding) if @encoding
293
+ line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/)
294
+ [line_no, start_line, text]
295
+ end
301
296
 
302
- att
297
+ # The first comment is special. It defines markup for the rest of the comments.
298
+ _, first_comment_start_line, first_comment_text = @unprocessed_comments.first
299
+ if first_comment_text && @lines[0...first_comment_start_line - 1].all? { |l| l.match?(/\A\s*$/) }
300
+ _text, directives = @preprocess.parse_comment(first_comment_text, first_comment_start_line, :ruby)
301
+ markup, = directives['markup']
302
+ @markup = markup.downcase if markup
303
+ end
303
304
  end
304
305
 
305
- ##
306
- # Creates a module alias in +container+ at +rhs_name+ (or at the top-level
307
- # for "::") with the name from +constant+.
306
+ # Creates an RDoc::Method on +container+ from +comment+ if there is a
307
+ # Signature section in the comment
308
308
 
309
- def create_module_alias(container, constant, rhs_name) # :nodoc:
310
- mod = if rhs_name =~ /^::/ then
311
- @store.find_class_or_module rhs_name
312
- else
313
- container.find_module_named rhs_name
314
- end
309
+ def parse_comment_tomdoc(container, comment, line_no, start_line)
310
+ return unless signature = RDoc::TomDoc.signature(comment)
315
311
 
316
- container.add_module_alias mod, rhs_name, constant, @top_level
317
- end
312
+ name, = signature.split %r%[ \(]%, 2
318
313
 
319
- ##
320
- # Aborts with +msg+
314
+ meth = RDoc::AnyMethod.new name
315
+ record_location(meth)
316
+ meth.line = start_line
317
+ meth.call_seq = signature
318
+ return unless meth.name
321
319
 
322
- def error(msg)
323
- msg = make_message msg
320
+ meth.start_collecting_tokens(:ruby)
321
+ node = @line_nodes[line_no]
322
+ tokens = node ? syntax_highlighted_tokens(node) : []
323
+ tokens.each { |token| meth.token_stream << token }
324
324
 
325
- abort msg
325
+ container.add_method meth
326
+ meth.comment = comment
327
+ @stats.add_method meth
326
328
  end
327
329
 
328
- ##
329
- # Looks for a true or false token.
330
-
331
- def get_bool
332
- skip_tkspace
333
- tk = get_tk
334
- if :on_kw == tk[:kind] && 'true' == tk[:text]
335
- true
336
- elsif :on_kw == tk[:kind] && ('false' == tk[:text] || 'nil' == tk[:text])
337
- false
338
- else
339
- unget_tk tk
340
- true
341
- end
330
+ def has_modifier_nodoc?(line_no) # :nodoc:
331
+ @modifier_comments[line_no]&.match?(/\A#\s*:nodoc:/)
342
332
  end
343
333
 
344
- ##
345
- # Look for the name of a class of module (optionally with a leading :: or
346
- # with :: separated named) and return the ultimate name, the associated
347
- # container, and the given name (with the ::).
348
-
349
- def get_class_or_module(container, ignore_constants = false)
350
- skip_tkspace
351
- name_t = get_tk
352
- given_name = ''.dup
353
-
354
- # class ::A -> A is in the top level
355
- if :on_op == name_t[:kind] and '::' == name_t[:text] then # bug
356
- name_t = get_tk
357
- container = @top_level
358
- given_name << '::'
334
+ def handle_modifier_directive(code_object, line_no) # :nodoc:
335
+ if (comment_text = @modifier_comments[line_no])
336
+ _text, directives = @preprocess.parse_comment(comment_text, line_no, :ruby)
337
+ handle_code_object_directives(code_object, directives)
359
338
  end
339
+ end
360
340
 
361
- skip_tkspace_without_nl
362
- given_name << name_t[:text]
363
-
364
- is_self = name_t[:kind] == :on_op && name_t[:text] == '<<'
365
- new_modules = []
366
- while !is_self && (tk = peek_tk) and :on_op == tk[:kind] and '::' == tk[:text] do
367
- prev_container = container
368
- container = container.find_module_named name_t[:text]
369
- container ||=
370
- if ignore_constants then
371
- c = RDoc::NormalModule.new name_t[:text]
372
- c.store = @store
373
- new_modules << [prev_container, c]
374
- c
375
- else
376
- c = prev_container.add_module RDoc::NormalModule, name_t[:text]
377
- c.ignore unless prev_container.document_children
378
- @top_level.add_to_classes_or_modules c
379
- c
380
- end
341
+ def call_node_name_arguments(call_node) # :nodoc:
342
+ return [] unless call_node.arguments
343
+ call_node.arguments.arguments.map do |arg|
344
+ case arg
345
+ when Prism::SymbolNode
346
+ arg.value
347
+ when Prism::StringNode
348
+ arg.unescaped
349
+ end
350
+ end || []
351
+ end
381
352
 
382
- record_location container
353
+ # Handles meta method comments
383
354
 
384
- get_tk
385
- skip_tkspace
386
- if :on_lparen == peek_tk[:kind] # ProcObjectInConstant::()
387
- parse_method_or_yield_parameters
388
- break
355
+ def handle_meta_method_comment(comment, directives, node)
356
+ handle_code_object_directives(@container, directives)
357
+ is_call_node = node.is_a?(Prism::CallNode)
358
+ singleton_method = false
359
+ visibility = @visibility
360
+ attributes = rw = line_no = method_name = nil
361
+ directives.each do |directive, (param, line)|
362
+ case directive
363
+ when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor'
364
+ attributes = [param] if param
365
+ attributes ||= call_node_name_arguments(node) if is_call_node
366
+ rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R'
367
+ when 'method'
368
+ method_name = param if param
369
+ line_no = line
370
+ when 'singleton-method'
371
+ method_name = param if param
372
+ line_no = line
373
+ singleton_method = true
374
+ visibility = :public
389
375
  end
390
- name_t = get_tk
391
- unless :on_const == name_t[:kind] || :on_ident == name_t[:kind]
392
- raise RDoc::Error, "Invalid class or module definition: #{given_name}"
376
+ end
377
+
378
+ if attributes
379
+ attributes.each do |attr|
380
+ a = RDoc::Attr.new(attr, rw, comment, singleton: @singleton)
381
+ a.store = @store
382
+ a.line = line_no
383
+ record_location(a)
384
+ @container.add_attribute(a)
385
+ a.visibility = visibility
393
386
  end
394
- if prev_container == container and !ignore_constants
395
- given_name = name_t[:text]
387
+ elsif line_no || node
388
+ method_name ||= call_node_name_arguments(node).first if is_call_node
389
+ if node
390
+ tokens = syntax_highlighted_tokens(node)
391
+ line_no = node.location.start_line
396
392
  else
397
- given_name << '::' + name_t[:text]
393
+ tokens = []
398
394
  end
395
+ internal_add_method(
396
+ method_name,
397
+ @container,
398
+ comment: comment,
399
+ directives: directives,
400
+ dont_rename_initialize: false,
401
+ line_no: line_no,
402
+ visibility: visibility,
403
+ singleton: @singleton || singleton_method,
404
+ params: nil,
405
+ calls_super: false,
406
+ block_params: nil,
407
+ tokens: tokens,
408
+ )
409
+ end
410
+ end
411
+
412
+ INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST = %w[
413
+ method singleton-method attr attr_reader attr_writer attr_accessor
414
+ ].freeze
415
+ private_constant :INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST
416
+
417
+ def normal_comment_treat_as_ghost_method_for_now?(directives, line_no) # :nodoc:
418
+ # Meta method comment should start with `##` but some comments does not follow this rule.
419
+ # For now, RDoc accepts them as a meta method comment if there is no node linked to it.
420
+ !@line_nodes[line_no] && INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST.any? { |directive| directives.has_key?(directive) }
421
+ end
422
+
423
+ def handle_standalone_consecutive_comment_directive(comment, directives, start_with_sharp_sharp, line_no, start_line) # :nodoc:
424
+ if start_with_sharp_sharp && start_line != @first_non_meta_comment_start_line
425
+ node = @line_nodes[line_no]
426
+ handle_meta_method_comment(comment, directives, node)
427
+ elsif normal_comment_treat_as_ghost_method_for_now?(directives, line_no) && start_line != @first_non_meta_comment_start_line
428
+ handle_meta_method_comment(comment, directives, nil)
429
+ else
430
+ handle_code_object_directives(@container, directives)
399
431
  end
400
-
401
- skip_tkspace_without_nl
402
-
403
- return [container, name_t, given_name, new_modules]
404
432
  end
405
433
 
406
- ##
407
- # Skip opening parentheses and yield the block.
408
- # Skip closing parentheses too when exists.
434
+ # Processes consecutive comments that were not linked to any documentable code until the given line number
409
435
 
410
- def skip_parentheses(&block)
411
- left_tk = peek_tk
412
-
413
- if :on_lparen == left_tk[:kind]
414
- get_tk
415
-
416
- ret = skip_parentheses(&block)
417
-
418
- right_tk = peek_tk
419
- if :on_rparen == right_tk[:kind]
420
- get_tk
436
+ def process_comments_until(line_no_until)
437
+ while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until
438
+ line_no, start_line, text = @unprocessed_comments.shift
439
+ if @markup == 'tomdoc'
440
+ comment = RDoc::Comment.new(text, @top_level, :ruby)
441
+ comment.format = 'tomdoc'
442
+ parse_comment_tomdoc(@container, comment, line_no, start_line)
443
+ @preprocess.run_post_processes(comment, @container)
444
+ elsif (comment_text, directives = parse_comment_text_to_directives(text, start_line))
445
+ handle_standalone_consecutive_comment_directive(comment_text, directives, text.start_with?(/#\#$/), line_no, start_line)
421
446
  end
422
-
423
- ret
424
- else
425
- yield
426
447
  end
427
448
  end
428
449
 
429
- ##
430
- # Return a superclass, which can be either a constant of an expression
431
-
432
- def get_class_specification
433
- tk = peek_tk
434
- if tk.nil?
435
- return ''
436
- elsif :on_kw == tk[:kind] && 'self' == tk[:text]
437
- return 'self'
438
- elsif :on_gvar == tk[:kind]
439
- return ''
440
- end
441
-
442
- res = get_constant
443
-
444
- skip_tkspace_without_nl
450
+ # Skips all undocumentable consecutive comments until the given line number.
451
+ # Undocumentable comments are comments written inside `def` or inside undocumentable class/module
445
452
 
446
- get_tkread # empty out read buffer
447
-
448
- tk = get_tk
449
- return res unless tk
450
-
451
- case tk[:kind]
452
- when :on_nl, :on_comment, :on_embdoc, :on_semicolon then
453
- unget_tk(tk)
454
- return res
453
+ def skip_comments_until(line_no_until)
454
+ while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until
455
+ @unprocessed_comments.shift
455
456
  end
456
-
457
- res += parse_call_parameters(tk)
458
- res
459
457
  end
460
458
 
461
- ##
462
- # Parse a constant, which might be qualified by one or more class or module
463
- # names
464
-
465
- def get_constant
466
- res = ""
467
- skip_tkspace_without_nl
468
- tk = get_tk
459
+ # Returns consecutive comment linked to the given line number
469
460
 
470
- while tk && ((:on_op == tk[:kind] && '::' == tk[:text]) || :on_const == tk[:kind]) do
471
- res += tk[:text]
472
- tk = get_tk
473
- end
474
-
475
- unget_tk(tk)
476
- res
461
+ def consecutive_comment(line_no)
462
+ return unless @unprocessed_comments.first&.first == line_no
463
+ _line_no, start_line, text = @unprocessed_comments.shift
464
+ parse_comment_text_to_directives(text, start_line)
477
465
  end
478
466
 
479
- ##
480
- # Get an included module that may be surrounded by parens
481
-
482
- def get_included_module_with_optional_parens
483
- skip_tkspace_without_nl
484
- get_tkread
485
- tk = get_tk
486
- end_token = get_end_token tk
487
- return '' unless end_token
488
-
489
- nest = 0
490
- continue = false
491
- only_constant = true
492
-
493
- while tk != nil do
494
- is_element_of_constant = false
495
- case tk[:kind]
496
- when :on_semicolon then
497
- break if nest == 0
498
- when :on_lbracket then
499
- nest += 1
500
- when :on_rbracket then
501
- nest -= 1
502
- when :on_lbrace then
503
- nest += 1
504
- when :on_rbrace then
505
- nest -= 1
506
- if nest <= 0
507
- # we might have a.each { |i| yield i }
508
- unget_tk(tk) if nest < 0
509
- break
510
- end
511
- when :on_lparen then
512
- nest += 1
513
- when end_token[:kind] then
514
- if end_token[:kind] == :on_rparen
515
- nest -= 1
516
- break if nest <= 0
517
- else
518
- break if nest <= 0
519
- end
520
- when :on_rparen then
521
- nest -= 1
522
- when :on_comment, :on_embdoc then
523
- @read.pop
524
- if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and
525
- (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then
526
- break if !continue and nest <= 0
527
- end
528
- when :on_comma then
529
- continue = true
530
- when :on_ident then
531
- continue = false if continue
532
- when :on_kw then
533
- case tk[:text]
534
- when 'def', 'do', 'case', 'for', 'begin', 'class', 'module'
535
- nest += 1
536
- when 'if', 'unless', 'while', 'until', 'rescue'
537
- # postfix if/unless/while/until/rescue must be EXPR_LABEL
538
- nest += 1 unless (tk[:state] & Ripper::EXPR_LABEL) != 0
539
- when 'end'
540
- nest -= 1
541
- break if nest == 0
542
- end
543
- when :on_const then
544
- is_element_of_constant = true
545
- when :on_op then
546
- is_element_of_constant = true if '::' == tk[:text]
547
- end
548
- only_constant = false unless is_element_of_constant
549
- tk = get_tk
550
- end
467
+ # Parses comment text and returns +[RDoc::Comment, directives, type_signature_lines]+,
468
+ # or +nil+ if the comment is a section header (which has no associated code
469
+ # object).
551
470
 
552
- if only_constant
553
- get_tkread_clean(/\s+/, ' ')
554
- else
555
- ''
471
+ def parse_comment_text_to_directives(comment_text, start_line) # :nodoc:
472
+ type_signature_lines = extract_type_signature!(comment_text, start_line)
473
+ comment_text, directives = @preprocess.parse_comment(comment_text, start_line, :ruby)
474
+ comment = RDoc::Comment.new(comment_text, @top_level, :ruby)
475
+ comment.normalized = true
476
+ comment.line = start_line
477
+ markup, = directives['markup']
478
+ comment.format = markup&.downcase || @markup
479
+ if (section, directive_line = directives['section'])
480
+ # If comment has :section:, it is not a documentable comment for a code object
481
+ comment.text = extract_section_comment(comment_text, directive_line - start_line)
482
+ @container.set_current_section(section, comment)
483
+ return
556
484
  end
485
+ @preprocess.run_post_processes(comment, @container)
486
+ [comment, directives, type_signature_lines]
557
487
  end
558
488
 
559
- ##
560
- # Little hack going on here. In the statement:
561
- #
562
- # f = 2*(1+yield)
563
- #
564
- # We see the RPAREN as the next token, so we need to exit early. This still
565
- # won't catch all cases (such as "a = yield + 1"
566
-
567
- def get_end_token(tk) # :nodoc:
568
- case tk[:kind]
569
- when :on_lparen
570
- token = RDoc::Parser::RipperStateLex::Token.new
571
- token[:kind] = :on_rparen
572
- token[:text] = ')'
573
- token
574
- when :on_rparen
575
- nil
576
- else
577
- token = RDoc::Parser::RipperStateLex::Token.new
578
- token[:kind] = :on_nl
579
- token[:text] = "\n"
580
- token
581
- end
489
+ # Extracts the comment for this section from the normalized comment block.
490
+ # Removes all lines before the line that contains :section:
491
+ # If the comment also ends with the same content, remove it as well
492
+
493
+ def extract_section_comment(comment_text, prefix_line_count) # :nodoc:
494
+ prefix = comment_text.lines[0...prefix_line_count].join
495
+ comment_text.delete_prefix!(prefix)
496
+ # Comment is already normalized and doesn't end with a newline
497
+ comment_text.delete_suffix!(prefix.chomp)
498
+ comment_text
582
499
  end
583
500
 
584
- ##
585
- # Retrieves the method container for a singleton method.
501
+ # Returns syntax highlighted tokens of the given node
586
502
 
587
- def get_method_container(container, name_t) # :nodoc:
588
- prev_container = container
589
- container = container.find_module_named(name_t[:text])
503
+ def syntax_highlighted_tokens(node)
504
+ RDoc::Parser::RubyColorizer.partial_colorize(@content, node, @prism_tokens)
505
+ end
590
506
 
591
- unless container then
592
- constant = prev_container.constants.find do |const|
593
- const.name == name_t[:text]
594
- end
507
+ # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar`
595
508
 
596
- if constant then
597
- parse_method_dummy prev_container
598
- return
509
+ def change_method_visibility(names, visibility, singleton: @singleton)
510
+ new_methods = []
511
+ @container.methods_matching(names, singleton) do |m|
512
+ if m.parent != @container
513
+ m = m.dup
514
+ record_location(m)
515
+ new_methods << m
516
+ else
517
+ m.visibility = visibility
599
518
  end
600
519
  end
601
-
602
- unless container then
603
- # TODO seems broken, should starting at Object in @store
604
- obj = name_t[:text].split("::").inject(Object) do |state, item|
605
- state.const_get(item)
606
- end rescue nil
607
-
608
- type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule
609
-
610
- unless [Class, Module].include?(obj.class) then
611
- warn("Couldn't find #{name_t[:text]}. Assuming it's a module")
612
- end
613
-
614
- if type == RDoc::NormalClass then
615
- sclass = obj.superclass ? obj.superclass.name : nil
616
- container = prev_container.add_class type, name_t[:text], sclass
617
- else
618
- container = prev_container.add_module type, name_t[:text]
520
+ new_methods.each do |method|
521
+ case method
522
+ when RDoc::AnyMethod then
523
+ @container.add_method(method)
524
+ when RDoc::Attr then
525
+ @container.add_attribute(method)
619
526
  end
620
-
621
- record_location container
527
+ method.visibility = visibility
622
528
  end
623
-
624
- container
625
529
  end
626
530
 
627
- ##
628
- # Extracts a name or symbol from the token stream.
629
-
630
- def get_symbol_or_name
631
- tk = get_tk
632
- case tk[:kind]
633
- when :on_symbol then
634
- text = tk[:text].sub(/^:/, '')
531
+ # Handles `module_function :foo, :bar`
635
532
 
636
- next_tk = peek_tk
637
- if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then
638
- get_tk
639
- text << '='
533
+ def change_method_to_module_function(names)
534
+ @container.set_visibility_for(names, :private, false)
535
+ new_methods = []
536
+ @container.methods_matching(names) do |m|
537
+ s_m = m.dup
538
+ record_location(s_m)
539
+ s_m.singleton = true
540
+ new_methods << s_m
541
+ end
542
+ new_methods.each do |method|
543
+ case method
544
+ when RDoc::AnyMethod then
545
+ @container.add_method(method)
546
+ when RDoc::Attr then
547
+ @container.add_attribute(method)
640
548
  end
549
+ method.visibility = :public
550
+ end
551
+ end
641
552
 
642
- text
643
- when :on_ident, :on_const, :on_gvar, :on_cvar, :on_ivar, :on_op, :on_kw then
644
- tk[:text]
645
- when :on_tstring, :on_dstring then
646
- tk[:text][1..-2]
647
- else
648
- raise RDoc::Error, "Name or symbol expected (got #{tk})"
553
+ def handle_code_object_directives(code_object, directives) # :nodoc:
554
+ directives.each do |directive, (param)|
555
+ @preprocess.handle_directive('', directive, param, code_object)
649
556
  end
650
557
  end
651
558
 
652
- ##
653
- # Marks containers between +container+ and +ancestor+ as ignored
559
+ # Handles `alias foo bar` and `alias_method :foo, :bar`
654
560
 
655
- def suppress_parents(container, ancestor) # :nodoc:
656
- while container and container != ancestor do
657
- container.suppress unless container.documented?
658
- container = container.parent
561
+ def add_alias_method(old_name, new_name, line_no)
562
+ comment, directives = consecutive_comment(line_no)
563
+ handle_code_object_directives(@container, directives) if directives
564
+ visibility = @container.find_method(old_name, @singleton)&.visibility || :public
565
+ a = RDoc::Alias.new(old_name, new_name, comment, singleton: @singleton)
566
+ handle_modifier_directive(a, line_no)
567
+ a.store = @store
568
+ a.line = line_no
569
+ record_location(a)
570
+ if should_document?(a)
571
+ @container.add_alias(a)
572
+ @container.find_method(new_name, @singleton)&.visibility = visibility
659
573
  end
660
574
  end
661
575
 
662
- ##
663
- # Look for directives in a normal comment block:
664
- #
665
- # # :stopdoc:
666
- # # Don't display comment from this point forward
667
- #
668
- # This routine modifies its +comment+ parameter.
576
+ # Handles `attr :a, :b`, `attr_reader :a, :b`, `attr_writer :a, :b` and `attr_accessor :a, :b`
669
577
 
670
- def look_for_directives_in(container, comment)
671
- @preprocess.handle comment, container do |directive, param|
672
- case directive
673
- when 'method', 'singleton-method',
674
- 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then
675
- false # handled elsewhere
676
- when 'section' then
677
- break unless container.kind_of?(RDoc::Context)
678
- container.set_current_section param, comment.dup
679
- comment.text = ''
680
- break
681
- end
682
- end
578
+ def add_attributes(names, rw, line_no)
579
+ comment, directives, type_signature_lines = consecutive_comment(line_no)
580
+ handle_code_object_directives(@container, directives) if directives
581
+ return unless @container.document_children
683
582
 
684
- comment.remove_private
583
+ names.each do |symbol|
584
+ a = RDoc::Attr.new(symbol.to_s, rw, comment, singleton: @singleton)
585
+ a.store = @store
586
+ a.line = line_no
587
+ a.type_signature_lines = type_signature_lines
588
+ record_location(a)
589
+ handle_modifier_directive(a, line_no)
590
+ @container.add_attribute(a) if should_document?(a)
591
+ a.visibility = visibility # should set after adding to container
592
+ end
685
593
  end
686
594
 
687
- ##
688
- # Adds useful info about the parser to +message+
595
+ # Adds includes/extends. Module name is resolved to full before adding.
689
596
 
690
- def make_message(message)
691
- prefix = "#{@file_name}:".dup
597
+ def add_includes_extends(names, rdoc_class, line_no) # :nodoc:
598
+ comment, directives = consecutive_comment(line_no)
599
+ handle_code_object_directives(@container, directives) if directives
600
+ names.each do |name|
601
+ resolved_name = resolve_constant_path(name)
602
+ ie = @container.add(rdoc_class, resolved_name || name, '')
603
+ ie.store = @store
604
+ ie.line = line_no
605
+ ie.comment = comment
606
+ record_location(ie)
607
+ end
608
+ end
692
609
 
693
- tk = peek_tk
694
- prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk
610
+ # Handle `include Foo, Bar`
695
611
 
696
- "#{prefix} #{message}"
612
+ def add_includes(names, line_no) # :nodoc:
613
+ add_includes_extends(names, RDoc::Include, line_no)
697
614
  end
698
615
 
699
- ##
700
- # Creates a comment with the correct format
616
+ # Handle `extend Foo, Bar`
701
617
 
702
- def new_comment(comment, line_no = nil)
703
- c = RDoc::Comment.new comment, @top_level, :ruby
704
- c.line = line_no
705
- c.format = @markup
706
- c
618
+ def add_extends(names, line_no) # :nodoc:
619
+ add_includes_extends(names, RDoc::Extend, line_no)
707
620
  end
708
621
 
709
- ##
710
- # Creates an RDoc::Attr for the name following +tk+, setting the comment to
711
- # +comment+.
622
+ # Adds a method defined by `def` syntax
623
+
624
+ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:)
625
+ receiver = receiver_name ? find_or_create_lexical_module_path(receiver_name, receiver_fallback_type) : @container
626
+ comment, directives, type_signature_lines = consecutive_comment(start_line)
627
+ handle_code_object_directives(@container, directives) if directives
712
628
 
713
- def parse_attr(context, single, tk, comment)
714
- line_no = tk[:line_no]
629
+ internal_add_method(
630
+ method_name,
631
+ receiver,
632
+ comment: comment,
633
+ directives: directives,
634
+ modifier_comment_lines: [start_line, args_end_line, end_line].uniq,
635
+ line_no: start_line,
636
+ visibility: visibility,
637
+ singleton: singleton,
638
+ params: params,
639
+ calls_super: calls_super,
640
+ block_params: block_params,
641
+ tokens: tokens,
642
+ type_signature_lines: type_signature_lines
643
+ )
644
+ end
715
645
 
716
- args = parse_symbol_arg 1
717
- if args.size > 0 then
718
- name = args[0]
719
- rw = "R"
720
- skip_tkspace_without_nl
721
- tk = get_tk
646
+ private def internal_add_method(method_name, container, comment:, dont_rename_initialize: false, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, type_signature_lines: nil) # :nodoc:
647
+ meth = RDoc::AnyMethod.new(method_name, singleton: singleton)
648
+ meth.comment = comment
649
+ handle_code_object_directives(meth, directives) if directives
650
+ modifier_comment_lines&.each do |line|
651
+ handle_modifier_directive(meth, line)
652
+ end
653
+ return unless should_document?(meth)
654
+
655
+ if directives && (call_seq, = directives['call-seq'])
656
+ meth.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") if call_seq
657
+ end
658
+ meth.name ||= meth.call_seq[/\A[^()\s]+/] if meth.call_seq
659
+ meth.name ||= 'unknown'
660
+ meth.store = @store
661
+ meth.line = line_no
662
+ container.add_method(meth) # should add after setting singleton and before setting visibility
663
+ meth.visibility = visibility
664
+ meth.params ||= params || '()'
665
+ meth.calls_super = calls_super
666
+ meth.block_params ||= block_params if block_params
667
+ meth.type_signature_lines = type_signature_lines
668
+ record_location(meth)
669
+ meth.start_collecting_tokens(:ruby)
670
+ tokens.each do |token|
671
+ meth.token_stream << token
672
+ end
722
673
 
723
- if :on_comma == tk[:kind] then
724
- rw = "RW" if get_bool
674
+ # Rename after add_method to register duplicated 'new' and 'initialize'
675
+ # defined in c and ruby.
676
+ if !dont_rename_initialize && method_name == 'initialize' && !singleton
677
+ if meth.dont_rename_initialize
678
+ meth.visibility = :protected
725
679
  else
726
- unget_tk tk
680
+ meth.name = 'new'
681
+ meth.singleton = true
682
+ meth.visibility = :public
727
683
  end
684
+ end
685
+ end
728
686
 
729
- att = create_attr context, single, name, rw, comment
730
- att.line = line_no
687
+ # Find or create module or class from a given module name using Ruby lexical
688
+ # nesting. If module or class does not exist, creates a module or a class
689
+ # according to `create_mode` argument.
731
690
 
732
- read_documentation_modifiers att, RDoc::ATTR_MODIFIERS
691
+ def find_or_create_lexical_module_path(module_name, create_mode)
692
+ root_name, *path, name = module_name.split('::')
693
+ add_module = ->(mod, name, mode) {
694
+ case mode
695
+ when :class
696
+ mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store }
697
+ when :module
698
+ mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store }
699
+ end
700
+ }
701
+ if root_name.empty?
702
+ mod = @top_level
733
703
  else
734
- warn "'attr' ignored - looks like a variable"
704
+ @module_nesting.reverse_each do |nesting, singleton|
705
+ next if singleton
706
+ mod = nesting.get_module_named(root_name)
707
+ break if mod
708
+ # If a constant is found and it is not a module or class, RDoc can't document about it.
709
+ # Return an anonymous module to avoid wrong document creation.
710
+ return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name)
711
+ end
712
+ last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton }
713
+ return mod || add_module.call(last_nesting, root_name, create_mode) unless name
714
+ mod ||= add_module.call(last_nesting, root_name, :module)
715
+ end
716
+ path.each do |name|
717
+ mod = mod.get_module_named(name) || add_module.call(mod, name, :module)
718
+ end
719
+ mod.get_module_named(name) || add_module.call(mod, name, create_mode)
720
+ end
721
+
722
+ # Resolves constant path to a full path by searching module nesting
723
+
724
+ def resolve_constant_path(constant_path)
725
+ owner_name, path = constant_path.split('::', 2)
726
+ return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar
727
+ mod = nil
728
+ @module_nesting.reverse_each do |nesting, singleton|
729
+ next if singleton
730
+ mod = nesting.get_module_named(owner_name)
731
+ break if mod
732
+ end
733
+ mod ||= @top_level.get_module_named(owner_name)
734
+ [mod.full_name, path].compact.join('::') if mod
735
+ end
736
+
737
+ # Returns a pair of owner module and constant name from a given constant path
738
+ # using Ruby lexical nesting. Creates owner module if it does not exist.
739
+
740
+ def find_or_create_lexical_constant_owner_name(constant_path)
741
+ const_path, colon, name = constant_path.rpartition('::')
742
+ if colon.empty? # class Foo
743
+ # Within `class C` or `module C`, owner is C(== current container)
744
+ # Within `class <<C`, owner is C.singleton_class
745
+ # but RDoc don't track constants of a singleton class of module
746
+ [(@singleton ? nil : @container), name]
747
+ elsif const_path.empty? # class ::Foo
748
+ [@top_level, name]
749
+ else # `class Foo::Bar` or `class ::Foo::Bar`
750
+ [find_or_create_lexical_module_path(const_path, :module), name]
751
+ end
752
+ end
753
+
754
+ # Adds a constant
755
+
756
+ def add_constant(constant_name, rhs_name, start_line, end_line, alias_path: nil)
757
+ comment, directives = consecutive_comment(start_line)
758
+ handle_code_object_directives(@container, directives) if directives
759
+ owner, name = find_or_create_lexical_constant_owner_name(constant_name)
760
+ return unless owner
761
+
762
+ constant = RDoc::Constant.new(name, rhs_name, comment)
763
+ constant.store = @store
764
+ constant.line = start_line
765
+ constant.is_alias_for_path = alias_path
766
+ record_location(constant)
767
+ handle_modifier_directive(constant, start_line)
768
+ handle_modifier_directive(constant, end_line)
769
+ owner.add_constant(constant)
770
+ return unless alias_path
771
+ mod =
772
+ if alias_path.start_with?('::')
773
+ @store.find_class_or_module(alias_path)
774
+ else
775
+ full_name = resolve_constant_path(alias_path)
776
+ @store.find_class_or_module(full_name)
777
+ end
778
+ if mod && constant.document_self
779
+ a = owner.add_module_alias(mod, alias_path, constant, @top_level)
780
+ a.store = @store
781
+ a.line = start_line
782
+ record_location(a)
735
783
  end
736
784
  end
737
785
 
738
- ##
739
- # Creates an RDoc::Attr for each attribute listed after +tk+, setting the
740
- # comment for each to +comment+.
786
+ # Adds module or class
741
787
 
742
- def parse_attr_accessor(context, single, tk, comment)
743
- line_no = tk[:line_no]
788
+ def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil)
789
+ comment, directives = consecutive_comment(start_line)
790
+ handle_code_object_directives(@container, directives) if directives
791
+ return unless @container.document_children
744
792
 
745
- args = parse_symbol_arg
746
- rw = "?"
793
+ owner, name = find_or_create_lexical_constant_owner_name(module_name)
794
+ return unless owner
747
795
 
748
- tmp = RDoc::CodeObject.new
749
- read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS
750
- # TODO In most other places we let the context keep track of document_self
751
- # and add found items appropriately but here we do not. I'm not sure why.
752
- return if @track_visibility and not tmp.document_self
753
-
754
- case tk[:text]
755
- when "attr_reader" then rw = "R"
756
- when "attr_writer" then rw = "W"
757
- when "attr_accessor" then rw = "RW"
796
+ if is_class
797
+ # RDoc::NormalClass resolves superclass name despite of the lack of module nesting information.
798
+ # We need to fix it when RDoc::NormalClass resolved to a wrong constant name
799
+ if superclass_name
800
+ superclass_full_path = resolve_constant_path(superclass_name)
801
+ superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path
802
+ superclass_full_path ||= superclass_name
803
+ superclass_full_path = superclass_full_path.sub(/^::/, '')
804
+ end
805
+ # add_class should be done after resolving superclass
806
+ mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object')
807
+ if superclass_name
808
+ if superclass
809
+ mod.superclass = superclass
810
+ elsif (mod.superclass.is_a?(String) || mod.superclass.name == 'Object') && mod.superclass != superclass_full_path
811
+ mod.superclass = superclass_full_path
812
+ end
813
+ end
758
814
  else
759
- rw = '?'
815
+ mod = owner.modules_hash[name] || owner.add_module(RDoc::NormalModule, name)
760
816
  end
761
817
 
762
- for name in args
763
- att = create_attr context, single, name, rw, comment
764
- att.line = line_no
765
- end
818
+ mod.store = @store
819
+ mod.line = start_line
820
+ record_location(mod)
821
+ handle_modifier_directive(mod, start_line)
822
+ handle_modifier_directive(mod, end_line)
823
+ mod.add_comment(comment, @top_level) if comment
824
+ mod
766
825
  end
767
826
 
768
- ##
769
- # Parses an +alias+ in +context+ with +comment+
827
+ private
770
828
 
771
- def parse_alias(context, single, tk, comment)
772
- line_no = tk[:line_no]
829
+ # Extracts RBS type signature lines (#: ...) from raw comment text.
830
+ # Mutates the input text to remove the extracted lines.
831
+ # Returns an array of extracted type signature lines, or nil if none are
832
+ # found. The array may contain multiple lines for overloaded signatures.
773
833
 
774
- skip_tkspace
834
+ def extract_type_signature!(text, start_line)
835
+ return nil unless text.include?('#:')
775
836
 
776
- if :on_lparen === peek_tk[:kind] then
777
- get_tk
778
- skip_tkspace
779
- end
837
+ lines = text.lines
838
+ sig_lines, doc_lines = lines.partition { |l| l.match?(RBS_SIG_LINE) }
839
+ return nil if sig_lines.empty?
780
840
 
781
- new_name = get_symbol_or_name
841
+ first_sig_line = start_line + lines.index(sig_lines.first)
842
+ text.replace(doc_lines.join)
843
+ type_signature_lines = sig_lines.map { |l| l.sub(RBS_SIG_LINE, '').strip }.reject(&:empty?)
844
+ return nil if type_signature_lines.empty?
782
845
 
783
- skip_tkspace
784
- if :on_comma === peek_tk[:kind] then
785
- get_tk
786
- skip_tkspace
787
- end
846
+ warn_invalid_type_signature(type_signature_lines, first_sig_line)
847
+ type_signature_lines
848
+ end
788
849
 
789
- begin
790
- old_name = get_symbol_or_name
791
- rescue RDoc::Error
792
- return
850
+ def warn_invalid_type_signature(type_signature_lines, line_no)
851
+ type_signature_lines.each_with_index do |line, i|
852
+ next if RDoc::RbsHelper.valid_method_type?(line)
853
+ next if RDoc::RbsHelper.valid_type?(line)
854
+ @options.warn "#{@top_level.relative_name}:#{line_no + i}: invalid RBS type signature: #{line.inspect}"
793
855
  end
856
+ end
794
857
 
795
- al = RDoc::Alias.new(get_tkread, old_name, new_name, comment, singleton: single == SINGLE)
796
- record_location al
797
- al.line = line_no
798
-
799
- read_documentation_modifiers al, RDoc::ATTR_MODIFIERS
800
- if al.document_self or not @track_visibility
801
- context.add_alias al
802
- @stats.add_alias al
858
+ class RDocVisitor < Prism::Visitor # :nodoc:
859
+ def initialize(scanner, top_level, store)
860
+ @scanner = scanner
861
+ @top_level = top_level
862
+ @store = store
803
863
  end
804
864
 
805
- al
806
- end
807
-
808
- ##
809
- # Extracts call parameters from the token stream.
810
-
811
- def parse_call_parameters(tk)
812
- end_token = case tk[:kind]
813
- when :on_lparen
814
- :on_rparen
815
- when :on_rparen
816
- return ""
817
- else
818
- :on_nl
819
- end
820
- nest = 0
821
-
822
- loop do
823
- break if tk.nil?
824
- case tk[:kind]
825
- when :on_semicolon
826
- break
827
- when :on_lparen
828
- nest += 1
829
- when end_token
830
- if end_token == :on_rparen
831
- nest -= 1
832
- break if RDoc::Parser::RipperStateLex.end?(tk) and nest <= 0
865
+ def visit_if_node(node)
866
+ if node.end_keyword
867
+ super
868
+ else
869
+ # Visit with the order in text representation to handle this method comment
870
+ # # comment
871
+ # def f
872
+ # end if call_node
873
+ node.statements.accept(self)
874
+ node.predicate.accept(self)
875
+ end
876
+ end
877
+ alias visit_unless_node visit_if_node
878
+
879
+ def visit_call_node(node)
880
+ @scanner.process_comments_until(node.location.start_line - 1)
881
+ if node.receiver.nil?
882
+ case node.name
883
+ when :attr
884
+ _visit_call_attr_reader_writer_accessor(node, 'R')
885
+ when :attr_reader
886
+ _visit_call_attr_reader_writer_accessor(node, 'R')
887
+ when :attr_writer
888
+ _visit_call_attr_reader_writer_accessor(node, 'W')
889
+ when :attr_accessor
890
+ _visit_call_attr_reader_writer_accessor(node, 'RW')
891
+ when :include
892
+ _visit_call_include(node)
893
+ when :extend
894
+ _visit_call_extend(node)
895
+ when :public
896
+ super
897
+ _visit_call_public_private_protected(node, :public)
898
+ when :private
899
+ super
900
+ _visit_call_public_private_protected(node, :private)
901
+ when :protected
902
+ super
903
+ _visit_call_public_private_protected(node, :protected)
904
+ when :private_constant
905
+ _visit_call_private_constant(node)
906
+ when :public_constant
907
+ _visit_call_public_constant(node)
908
+ when :require
909
+ _visit_call_require(node)
910
+ when :alias_method
911
+ _visit_call_alias_method(node)
912
+ when :module_function
913
+ super
914
+ _visit_call_module_function(node)
915
+ when :public_class_method
916
+ super
917
+ _visit_call_public_private_class_method(node, :public)
918
+ when :private_class_method
919
+ super
920
+ _visit_call_public_private_class_method(node, :private)
833
921
  else
834
- break if RDoc::Parser::RipperStateLex.end?(tk)
835
- end
836
- when :on_comment, :on_embdoc
837
- unget_tk(tk)
838
- break
839
- when :on_op
840
- if tk[:text] =~ /^(.{1,2})?=$/
841
- unget_tk(tk)
842
- break
922
+ super
843
923
  end
924
+ else
925
+ super
844
926
  end
845
- tk = get_tk
846
927
  end
847
928
 
848
- get_tkread_clean "\n", " "
849
- end
850
-
851
- ##
852
- # Parses a class in +context+ with +comment+
853
-
854
- def parse_class(container, single, tk, comment)
855
- line_no = tk[:line_no]
856
-
857
- declaration_context = container
858
- container, name_t, given_name, = get_class_or_module container
859
-
860
- if name_t[:kind] == :on_const
861
- cls = parse_class_regular container, declaration_context, single,
862
- name_t, given_name, comment
863
- elsif name_t[:kind] == :on_op && name_t[:text] == '<<'
864
- case name = skip_parentheses { get_class_specification }
865
- when 'self', container.name
866
- read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS
867
- parse_statements container, SINGLE
868
- return # don't update line
869
- else
870
- cls = parse_class_singleton container, name, comment
929
+ def visit_block_node(node)
930
+ @scanner.with_in_proc_block do
931
+ # include, extend and method definition inside block are not documentable.
932
+ # visibility methods and attribute definition methods should be ignored inside block.
933
+ super
871
934
  end
872
- else
873
- warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}"
874
- return
875
935
  end
876
936
 
877
- cls.line = line_no
878
-
879
- # after end modifiers
880
- read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS
881
-
882
- cls
883
- end
884
-
885
- ##
886
- # Parses and creates a regular class
887
-
888
- def parse_class_regular(container, declaration_context, single, # :nodoc:
889
- name_t, given_name, comment)
890
- superclass = '::Object'
891
-
892
- if given_name =~ /^::/ then
893
- declaration_context = @top_level
894
- given_name = $'
937
+ def visit_alias_method_node(node)
938
+ return if @scanner.in_proc_block
939
+ @scanner.process_comments_until(node.location.start_line - 1)
940
+ return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode)
941
+ @scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line)
895
942
  end
896
943
 
897
- tk = peek_tk
898
- if tk[:kind] == :on_op && tk[:text] == '<' then
899
- get_tk
900
- skip_tkspace
901
- superclass = get_class_specification
902
- superclass = '(unknown)' if superclass.empty?
944
+ def visit_module_node(node)
945
+ node.constant_path.accept(self)
946
+ @scanner.process_comments_until(node.location.start_line - 1)
947
+ module_name = constant_path_string(node.constant_path)
948
+ mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name
949
+ if mod
950
+ @scanner.with_container(mod) do
951
+ node.body&.accept(self)
952
+ @scanner.process_comments_until(node.location.end_line)
953
+ end
954
+ else
955
+ @scanner.skip_comments_until(node.location.end_line)
956
+ end
903
957
  end
904
958
 
905
- cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass
906
- cls = declaration_context.add_class cls_type, given_name, superclass
907
- cls.ignore unless container.document_children
908
-
909
- read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS
910
- record_location cls
911
-
912
- cls.add_comment comment, @top_level
913
-
914
- @top_level.add_to_classes_or_modules cls
915
- @stats.add_class cls
916
-
917
- suppress_parents container, declaration_context unless cls.document_self
918
-
919
- parse_statements cls
920
-
921
- cls
922
- end
923
-
924
- ##
925
- # Parses a singleton class in +container+ with the given +name+ and
926
- # +comment+.
959
+ def visit_class_node(node)
960
+ node.constant_path.accept(self)
961
+ node.superclass&.accept(self)
962
+ @scanner.process_comments_until(node.location.start_line - 1)
963
+ superclass_name = constant_path_string(node.superclass) if node.superclass
964
+ superclass_expr = node.superclass.slice if node.superclass && !superclass_name
965
+ class_name = constant_path_string(node.constant_path)
966
+ 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
967
+ if klass
968
+ @scanner.with_container(klass) do
969
+ node.body&.accept(self)
970
+ @scanner.process_comments_until(node.location.end_line)
971
+ end
972
+ else
973
+ @scanner.skip_comments_until(node.location.end_line)
974
+ end
975
+ end
927
976
 
928
- def parse_class_singleton(container, name, comment) # :nodoc:
929
- other = @store.find_class_named name
977
+ def visit_singleton_class_node(node)
978
+ @scanner.process_comments_until(node.location.start_line - 1)
930
979
 
931
- unless other then
932
- if name =~ /^::/ then
933
- name = $'
934
- container = @top_level
980
+ if @scanner.has_modifier_nodoc?(node.location.start_line)
981
+ # Skip visiting inside the singleton class. Also skips creation of node.expression as a module
982
+ @scanner.skip_comments_until(node.location.end_line)
983
+ return
935
984
  end
936
985
 
937
- other = container.add_module RDoc::NormalModule, name
938
- record_location other
986
+ expression = node.expression
987
+ expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1
988
+
989
+ case expression
990
+ when Prism::ConstantWriteNode
991
+ # Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module
992
+ mod = @scanner.container.add_module(RDoc::NormalModule, expression.name.to_s)
993
+ when Prism::ConstantPathNode, Prism::ConstantReadNode
994
+ expression_name = constant_path_string(expression)
995
+ # If a constant_path does not exist, RDoc creates a module
996
+ mod = @scanner.find_or_create_lexical_module_path(expression_name, :module) if expression_name
997
+ when Prism::SelfNode
998
+ mod = @scanner.container if @scanner.container != @top_level
999
+ end
1000
+ expression.accept(self)
1001
+ if mod
1002
+ @scanner.with_container(mod, singleton: true) do
1003
+ node.body&.accept(self)
1004
+ @scanner.process_comments_until(node.location.end_line)
1005
+ end
1006
+ else
1007
+ @scanner.skip_comments_until(node.location.end_line)
1008
+ end
1009
+ end
939
1010
 
940
- # class << $gvar
941
- other.ignore if name.empty?
1011
+ def visit_def_node(node)
1012
+ start_line = node.location.start_line
1013
+ args_end_line = node.parameters&.location&.end_line || start_line
1014
+ end_line = node.location.end_line
1015
+ @scanner.process_comments_until(start_line - 1)
1016
+
1017
+ return if @scanner.in_proc_block
1018
+
1019
+ case node.receiver
1020
+ when Prism::NilNode, Prism::TrueNode, Prism::FalseNode
1021
+ visibility = :public
1022
+ singleton = false
1023
+ receiver_name =
1024
+ case node.receiver
1025
+ when Prism::NilNode
1026
+ 'NilClass'
1027
+ when Prism::TrueNode
1028
+ 'TrueClass'
1029
+ when Prism::FalseNode
1030
+ 'FalseClass'
1031
+ end
1032
+ receiver_fallback_type = :class
1033
+ when Prism::SelfNode
1034
+ # singleton method of a singleton class is not documentable
1035
+ return if @scanner.singleton
1036
+ visibility = :public
1037
+ singleton = true
1038
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
1039
+ visibility = :public
1040
+ singleton = true
1041
+ receiver_name = constant_path_string(node.receiver)
1042
+ receiver_fallback_type = :module
1043
+ return unless receiver_name
1044
+ when nil
1045
+ visibility = @scanner.visibility
1046
+ singleton = @scanner.singleton
1047
+ else
1048
+ # `def (unknown expression).method_name` is not documentable
1049
+ return
1050
+ end
1051
+ name = node.name.to_s
1052
+ params, block_params, calls_super = MethodSignatureVisitor.scan_signature(node)
1053
+ tokens = @scanner.syntax_highlighted_tokens(node)
1054
+
1055
+ @scanner.add_method(
1056
+ name,
1057
+ receiver_name: receiver_name,
1058
+ receiver_fallback_type: receiver_fallback_type,
1059
+ visibility: visibility,
1060
+ singleton: singleton,
1061
+ params: params,
1062
+ block_params: block_params,
1063
+ calls_super: calls_super,
1064
+ tokens: tokens,
1065
+ start_line: start_line,
1066
+ args_end_line: args_end_line,
1067
+ end_line: end_line
1068
+ )
1069
+ ensure
1070
+ @scanner.skip_comments_until(end_line)
1071
+ end
1072
+
1073
+ def visit_constant_path_write_node(node)
1074
+ @scanner.process_comments_until(node.location.start_line - 1)
1075
+ path = constant_path_string(node.target)
1076
+ return unless path
1077
+
1078
+ alias_path = constant_path_string(node.value)
1079
+ @scanner.add_constant(
1080
+ path,
1081
+ alias_path || node.value.slice,
1082
+ node.location.start_line,
1083
+ node.location.end_line,
1084
+ alias_path: alias_path
1085
+ )
1086
+ @scanner.skip_comments_until(node.location.end_line)
1087
+ # Do not traverse rhs not to document `A::B = Struct.new{def undocumentable_method; end}`
1088
+ end
1089
+
1090
+ def visit_constant_write_node(node)
1091
+ @scanner.process_comments_until(node.location.start_line - 1)
1092
+ alias_path = constant_path_string(node.value)
1093
+ @scanner.add_constant(
1094
+ node.name.to_s,
1095
+ alias_path || node.value.slice,
1096
+ node.location.start_line,
1097
+ node.location.end_line,
1098
+ alias_path: alias_path
1099
+ )
1100
+ @scanner.skip_comments_until(node.location.end_line)
1101
+ # Do not traverse rhs not to document `A = Struct.new{def undocumentable_method; end}`
1102
+ end
1103
+
1104
+ private
1105
+
1106
+ def constant_arguments_names(call_node)
1107
+ return unless call_node.arguments
1108
+ names = call_node.arguments.arguments.map { |arg| constant_path_string(arg) }
1109
+ names.all? ? names : nil
1110
+ end
1111
+
1112
+ def symbol_arguments(call_node)
1113
+ arguments_node = call_node.arguments
1114
+ return unless arguments_node && arguments_node.arguments.all? { |arg| arg.is_a?(Prism::SymbolNode)}
1115
+ arguments_node.arguments.map { |arg| arg.value.to_sym }
1116
+ end
1117
+
1118
+ def visibility_method_arguments(call_node, singleton:)
1119
+ arguments_node = call_node.arguments
1120
+ return unless arguments_node
1121
+ symbols = symbol_arguments(call_node)
1122
+ if symbols
1123
+ # module_function :foo, :bar
1124
+ return symbols.map(&:to_s)
1125
+ else
1126
+ return unless arguments_node.arguments.size == 1
1127
+ arg = arguments_node.arguments.first
1128
+ return unless arg.is_a?(Prism::DefNode)
942
1129
 
943
- other.add_comment comment, @top_level
1130
+ if singleton
1131
+ # `private_class_method def foo; end` `private_class_method def not_self.foo; end` should be ignored
1132
+ return unless arg.receiver.is_a?(Prism::SelfNode)
1133
+ else
1134
+ # `module_function def something.foo` should be ignored
1135
+ return if arg.receiver
1136
+ end
1137
+ # `module_function def foo; end` or `private_class_method def self.foo; end`
1138
+ [arg.name.to_s]
1139
+ end
944
1140
  end
945
1141
 
946
- # notify :nodoc: all if not a constant-named class/module
947
- # (and remove any comment)
948
- unless name =~ /\A(::)?[A-Z]/ then
949
- other.document_self = nil
950
- other.document_children = false
951
- other.clear_comment
1142
+ def constant_path_string(node)
1143
+ case node
1144
+ when Prism::ConstantReadNode
1145
+ node.name.to_s
1146
+ when Prism::ConstantPathNode
1147
+ parent_name = node.parent ? constant_path_string(node.parent) : ''
1148
+ "#{parent_name}::#{node.name}" if parent_name
1149
+ end
952
1150
  end
953
1151
 
954
- @top_level.add_to_classes_or_modules other
955
- @stats.add_class other
956
-
957
- read_documentation_modifiers other, RDoc::CLASS_MODIFIERS
958
- parse_statements(other, SINGLE)
959
-
960
- other
961
- end
962
-
963
- ##
964
- # Parses a constant in +context+ with +comment+. If +ignore_constants+ is
965
- # true, no found constants will be added to RDoc.
966
-
967
- def parse_constant(container, tk, comment, ignore_constants = false)
968
- line_no = tk[:line_no]
969
-
970
- name = tk[:text]
971
- skip_tkspace_without_nl
972
-
973
- return unless name =~ /^\w+$/
974
-
975
- new_modules = []
976
- if :on_op == peek_tk[:kind] && '::' == peek_tk[:text] then
977
- unget_tk tk
978
-
979
- container, name_t, _, new_modules = get_class_or_module container, true
980
-
981
- name = name_t[:text]
1152
+ def _visit_call_require(call_node)
1153
+ return unless call_node.arguments&.arguments&.size == 1
1154
+ arg = call_node.arguments.arguments.first
1155
+ return unless arg.is_a?(Prism::StringNode)
1156
+ @scanner.container.add_require(RDoc::Require.new(arg.unescaped, nil))
982
1157
  end
983
1158
 
984
- is_array_or_hash = false
985
- if peek_tk && :on_lbracket == peek_tk[:kind]
986
- get_tk
987
- nest = 1
988
- while bracket_tk = get_tk
989
- case bracket_tk[:kind]
990
- when :on_lbracket
991
- nest += 1
992
- when :on_rbracket
993
- nest -= 1
994
- break if nest == 0
995
- end
996
- end
997
- skip_tkspace_without_nl
998
- is_array_or_hash = true
1159
+ def _visit_call_module_function(call_node)
1160
+ return if @scanner.in_proc_block || @scanner.singleton
1161
+ names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s)
1162
+ @scanner.change_method_to_module_function(names) if names
999
1163
  end
1000
1164
 
1001
- unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then
1002
- return false
1165
+ def _visit_call_public_private_class_method(call_node, visibility)
1166
+ return if @scanner.in_proc_block || @scanner.singleton
1167
+ names = visibility_method_arguments(call_node, singleton: true)
1168
+ @scanner.change_method_visibility(names, visibility, singleton: true) if names
1003
1169
  end
1004
- get_tk
1005
1170
 
1006
- unless ignore_constants
1007
- new_modules.each do |prev_c, new_module|
1008
- prev_c.add_module_by_normal_module new_module
1009
- new_module.ignore unless prev_c.document_children
1010
- @top_level.add_to_classes_or_modules new_module
1171
+ def _visit_call_public_private_protected(call_node, visibility)
1172
+ return if @scanner.in_proc_block
1173
+ arguments_node = call_node.arguments
1174
+ if arguments_node.nil? # `public` `private`
1175
+ @scanner.visibility = visibility
1176
+ else # `public :foo, :bar`, `private def foo; end`
1177
+ names = visibility_method_arguments(call_node, singleton: false)
1178
+ @scanner.change_method_visibility(names, visibility) if names
1011
1179
  end
1012
1180
  end
1013
1181
 
1014
- value = ''
1015
- con = RDoc::Constant.new name, value, comment
1016
-
1017
- body = parse_constant_body container, con, is_array_or_hash
1018
-
1019
- return unless body
1182
+ def _visit_call_alias_method(call_node)
1183
+ return if @scanner.in_proc_block
1020
1184
 
1021
- con.value = body
1022
- record_location con
1023
- con.line = line_no
1024
- read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS
1025
-
1026
- return if is_array_or_hash
1027
-
1028
- @stats.add_constant con
1029
- container.add_constant con
1185
+ new_name, old_name, *rest = symbol_arguments(call_node)
1186
+ return unless old_name && new_name && rest.empty?
1187
+ @scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line)
1188
+ end
1030
1189
 
1031
- true
1032
- end
1190
+ def _visit_call_include(call_node)
1191
+ return if @scanner.in_proc_block
1033
1192
 
1034
- def parse_constant_body(container, constant, is_array_or_hash) # :nodoc:
1035
- nest = 0
1036
- rhs_name = ''.dup
1037
-
1038
- get_tkread
1039
-
1040
- tk = get_tk
1041
-
1042
- body = nil
1043
- loop do
1044
- break if tk.nil?
1045
- if :on_semicolon == tk[:kind] then
1046
- break if nest <= 0
1047
- elsif [:on_tlambeg, :on_lparen, :on_lbrace, :on_lbracket].include?(tk[:kind]) then
1048
- nest += 1
1049
- elsif (:on_kw == tk[:kind] && 'def' == tk[:text]) then
1050
- nest += 1
1051
- elsif (:on_kw == tk[:kind] && %w{do if unless case begin}.include?(tk[:text])) then
1052
- if (tk[:state] & Ripper::EXPR_LABEL) == 0
1053
- nest += 1
1054
- end
1055
- elsif [:on_rparen, :on_rbrace, :on_rbracket].include?(tk[:kind]) ||
1056
- (:on_kw == tk[:kind] && 'end' == tk[:text]) then
1057
- nest -= 1
1058
- elsif (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then
1059
- unget_tk tk
1060
- if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then
1061
- body = get_tkread_clean(/^[ \t]+/, '')
1062
- read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS
1063
- break
1064
- else
1065
- read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS
1066
- end
1067
- elsif :on_const == tk[:kind] then
1068
- rhs_name << tk[:text]
1193
+ names = constant_arguments_names(call_node)
1194
+ line_no = call_node.location.start_line
1195
+ return unless names
1069
1196
 
1070
- next_tk = peek_tk
1071
- if nest <= 0 and (next_tk.nil? || :on_nl == next_tk[:kind]) then
1072
- create_module_alias container, constant, rhs_name unless is_array_or_hash
1073
- break
1074
- end
1075
- elsif :on_nl == tk[:kind] then
1076
- if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then
1077
- unget_tk tk
1078
- break
1079
- end
1080
- elsif :on_op == tk[:kind] && '::' == tk[:text]
1081
- rhs_name << '::'
1197
+ if @scanner.singleton
1198
+ @scanner.add_extends(names, line_no)
1199
+ else
1200
+ @scanner.add_includes(names, line_no)
1082
1201
  end
1083
- tk = get_tk
1084
1202
  end
1085
1203
 
1086
- body ? body : get_tkread_clean(/^[ \t]+/, '')
1087
- end
1088
-
1089
- ##
1090
- # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for
1091
- # \:method: or :attr: directives in +comment+.
1204
+ def _visit_call_extend(call_node)
1205
+ return if @scanner.in_proc_block
1092
1206
 
1093
- def parse_comment(container, tk, comment)
1094
- return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc'
1095
- column = tk[:char_no]
1096
- line_no = comment.line.nil? ? tk[:line_no] : comment.line
1097
-
1098
- comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3')
1099
- singleton = !!$~
1100
-
1101
- co =
1102
- if (comment.text = comment.text.sub(/^# +:?method: *(\S*).*?\n/i, '')) && !!$~ then
1103
- line_no += $`.count("\n")
1104
- parse_comment_ghost container, comment.text, $1, column, line_no, comment
1105
- elsif (comment.text = comment.text.sub(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '')) && !!$~ then
1106
- parse_comment_attr container, $1, $3, comment
1107
- end
1108
-
1109
- if co then
1110
- co.singleton = singleton
1111
- co.line = line_no
1207
+ names = constant_arguments_names(call_node)
1208
+ @scanner.add_extends(names, call_node.location.start_line) if names && !@scanner.singleton
1112
1209
  end
1113
1210
 
1114
- true
1115
- end
1211
+ def _visit_call_public_constant(call_node)
1212
+ return if @scanner.in_proc_block || @scanner.singleton
1213
+ names = symbol_arguments(call_node)
1214
+ @scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names
1215
+ end
1116
1216
 
1117
- ##
1118
- # Parse a comment that is describing an attribute in +container+ with the
1119
- # given +name+ and +comment+.
1217
+ def _visit_call_private_constant(call_node)
1218
+ return if @scanner.in_proc_block || @scanner.singleton
1219
+ names = symbol_arguments(call_node)
1220
+ @scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names
1221
+ end
1120
1222
 
1121
- def parse_comment_attr(container, type, name, comment) # :nodoc:
1122
- return if name.empty?
1223
+ def _visit_call_attr_reader_writer_accessor(call_node, rw)
1224
+ return if @scanner.in_proc_block
1225
+ names = symbol_arguments(call_node)
1226
+ @scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names
1227
+ end
1123
1228
 
1124
- rw = case type
1125
- when 'attr_reader' then 'R'
1126
- when 'attr_writer' then 'W'
1127
- else 'RW'
1128
- end
1229
+ class MethodSignatureVisitor < Prism::Visitor # :nodoc:
1230
+ class << self
1231
+ def scan_signature(def_node)
1232
+ visitor = new
1233
+ def_node.body&.accept(visitor)
1234
+ params = "(#{def_node.parameters&.slice})"
1235
+ block_params = visitor.yields.first
1236
+ [params, block_params, visitor.calls_super]
1237
+ end
1238
+ end
1129
1239
 
1130
- create_attr container, NORMAL, name, rw, comment
1131
- end
1240
+ attr_reader :params, :yields, :calls_super
1132
1241
 
1133
- def parse_comment_ghost(container, text, name, column, line_no, # :nodoc:
1134
- comment)
1135
- name = nil if name.empty?
1242
+ def initialize
1243
+ @params = nil
1244
+ @calls_super = false
1245
+ @yields = []
1246
+ end
1136
1247
 
1137
- meth = RDoc::GhostMethod.new get_tkread, name
1138
- record_location meth
1248
+ def visit_def_node(node)
1249
+ # stop traverse inside nested def
1250
+ end
1139
1251
 
1140
- meth.start_collecting_tokens(:ruby)
1141
- indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
1142
- position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
1143
- position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
1144
- newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
1145
- meth.add_tokens [position_comment, newline, indent]
1146
-
1147
- meth.params =
1148
- if text.sub!(/^#\s+:?args?:\s*(.*?)\s*$/i, '') then
1149
- $1
1150
- else
1151
- ''
1252
+ def visit_yield_node(node)
1253
+ @yields << (node.arguments&.slice || '')
1152
1254
  end
1153
1255
 
1154
- comment.normalize
1155
- meth.call_seq = comment.extract_call_seq
1256
+ def visit_super_node(node)
1257
+ @calls_super = true
1258
+ super
1259
+ end
1156
1260
 
1157
- return unless meth.name
1158
-
1159
- container.add_method meth
1160
-
1161
- meth.comment = comment
1162
-
1163
- @stats.add_method meth
1164
-
1165
- meth
1166
- end
1167
-
1168
- ##
1169
- # Creates an RDoc::Method on +container+ from +comment+ if there is a
1170
- # Signature section in the comment
1171
-
1172
- def parse_comment_tomdoc(container, tk, comment)
1173
- return unless signature = RDoc::TomDoc.signature(comment)
1174
- column = tk[:char_no]
1175
- line_no = tk[:line_no]
1176
-
1177
- name, = signature.split %r%[ \(]%, 2
1178
-
1179
- meth = RDoc::GhostMethod.new get_tkread, name
1180
- record_location meth
1181
- meth.line = line_no
1182
-
1183
- meth.start_collecting_tokens(:ruby)
1184
- indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
1185
- position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
1186
- position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
1187
- newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
1188
- meth.add_tokens [position_comment, newline, indent]
1189
-
1190
- meth.call_seq = signature
1191
-
1192
- comment.normalize
1193
-
1194
- return unless meth.name
1195
-
1196
- container.add_method meth
1197
-
1198
- meth.comment = comment
1199
-
1200
- @stats.add_method meth
1201
- end
1202
-
1203
- ##
1204
- # Parses an +include+ or +extend+, indicated by the +klass+ and adds it to
1205
- # +container+ # with +comment+
1206
-
1207
- def parse_extend_or_include(klass, container, comment) # :nodoc:
1208
- loop do
1209
- skip_tkspace_comment
1210
-
1211
- name = get_included_module_with_optional_parens
1212
-
1213
- unless name.empty? then
1214
- obj = container.add klass, name, comment
1215
- record_location obj
1216
- end
1217
-
1218
- return if peek_tk.nil? || :on_comma != peek_tk[:kind]
1219
-
1220
- get_tk
1221
- end
1222
- end
1223
-
1224
- ##
1225
- # Parses an +included+ with a block feature of ActiveSupport::Concern.
1226
-
1227
- def parse_included_with_activesupport_concern(container, comment) # :nodoc:
1228
- skip_tkspace_without_nl
1229
- tk = get_tk
1230
- unless tk[:kind] == :on_lbracket || (tk[:kind] == :on_kw && tk[:text] == 'do')
1231
- unget_tk tk
1232
- return nil # should be a block
1233
- end
1234
-
1235
- parse_statements container
1236
-
1237
- container
1238
- end
1239
-
1240
- ##
1241
- # Parses identifiers that can create new methods or change visibility.
1242
- #
1243
- # Returns true if the comment was not consumed.
1244
-
1245
- def parse_identifier(container, single, tk, comment) # :nodoc:
1246
- case tk[:text]
1247
- when 'private', 'protected', 'public', 'private_class_method',
1248
- 'public_class_method', 'module_function' then
1249
- parse_visibility container, single, tk
1250
- return true
1251
- when 'private_constant', 'public_constant'
1252
- parse_constant_visibility container, single, tk
1253
- return true
1254
- when 'attr' then
1255
- parse_attr container, single, tk, comment
1256
- when /^attr_(reader|writer|accessor)$/ then
1257
- parse_attr_accessor container, single, tk, comment
1258
- when 'alias_method' then
1259
- parse_alias container, single, tk, comment
1260
- when 'require', 'include' then
1261
- # ignore
1262
- else
1263
- if comment.text =~ /\A#\#$/ then
1264
- case comment.text
1265
- when /^# +:?attr(_reader|_writer|_accessor)?:/ then
1266
- parse_meta_attr container, single, tk, comment
1267
- else
1268
- method = parse_meta_method container, single, tk, comment
1269
- method.params = container.params if
1270
- container.params
1271
- method.block_params = container.block_params if
1272
- container.block_params
1273
- end
1274
- end
1275
- end
1276
-
1277
- false
1278
- end
1279
-
1280
- ##
1281
- # Parses a meta-programmed attribute and creates an RDoc::Attr.
1282
- #
1283
- # To create foo and bar attributes on class C with comment "My attributes":
1284
- #
1285
- # class C
1286
- #
1287
- # ##
1288
- # # :attr:
1289
- # #
1290
- # # My attributes
1291
- #
1292
- # my_attr :foo, :bar
1293
- #
1294
- # end
1295
- #
1296
- # To create a foo attribute on class C with comment "My attribute":
1297
- #
1298
- # class C
1299
- #
1300
- # ##
1301
- # # :attr: foo
1302
- # #
1303
- # # My attribute
1304
- #
1305
- # my_attr :foo, :bar
1306
- #
1307
- # end
1308
-
1309
- def parse_meta_attr(context, single, tk, comment)
1310
- args = parse_symbol_arg
1311
- rw = "?"
1312
-
1313
- # If nodoc is given, don't document any of them
1314
-
1315
- tmp = RDoc::CodeObject.new
1316
- read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS
1317
-
1318
- regexp = /^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i
1319
- if regexp =~ comment.text then
1320
- comment.text = comment.text.sub(regexp, '')
1321
- rw = case $1
1322
- when 'attr_reader' then 'R'
1323
- when 'attr_writer' then 'W'
1324
- else 'RW'
1325
- end
1326
- name = $3 unless $3.empty?
1327
- end
1328
-
1329
- if name then
1330
- att = create_attr context, single, name, rw, comment
1331
- else
1332
- args.each do |attr_name|
1333
- att = create_attr context, single, attr_name, rw, comment
1334
- end
1335
- end
1336
-
1337
- att
1338
- end
1339
-
1340
- ##
1341
- # Parses a meta-programmed method
1342
-
1343
- def parse_meta_method(container, single, tk, comment)
1344
- column = tk[:char_no]
1345
- line_no = tk[:line_no]
1346
-
1347
- start_collecting_tokens(:ruby)
1348
- add_token tk
1349
- add_token_listener self
1350
-
1351
- skip_tkspace_without_nl
1352
-
1353
- comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3')
1354
- singleton = !!$~
1355
-
1356
- name = parse_meta_method_name comment, tk
1357
-
1358
- return unless name
1359
-
1360
- meth = RDoc::MetaMethod.new get_tkread, name, singleton: singleton
1361
- record_location meth
1362
- meth.line = line_no
1363
-
1364
- remove_token_listener self
1365
-
1366
- meth.start_collecting_tokens(:ruby)
1367
- indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
1368
- position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
1369
- position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
1370
- newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
1371
- meth.add_tokens [position_comment, newline, indent]
1372
- meth.add_tokens @token_stream
1373
-
1374
- parse_meta_method_params container, single, meth, tk, comment
1375
-
1376
- meth.comment = comment
1377
-
1378
- @stats.add_method meth
1379
-
1380
- meth
1381
- end
1382
-
1383
- ##
1384
- # Parses the name of a metaprogrammed method. +comment+ is used to
1385
- # determine the name while +tk+ is used in an error message if the name
1386
- # cannot be determined.
1387
-
1388
- def parse_meta_method_name(comment, tk) # :nodoc:
1389
- if comment.text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then
1390
- return $1 unless $1.empty?
1391
- end
1392
-
1393
- name_t = get_tk
1394
-
1395
- if :on_symbol == name_t[:kind] then
1396
- name_t[:text][1..-1]
1397
- elsif :on_tstring == name_t[:kind] then
1398
- name_t[:text][1..-2]
1399
- elsif :on_op == name_t[:kind] && '=' == name_t[:text] then # ignore
1400
- remove_token_listener self
1401
-
1402
- nil
1403
- else
1404
- warn "unknown name token #{name_t.inspect} for meta-method '#{tk[:text]}'"
1405
- 'unknown'
1406
- end
1407
- end
1408
-
1409
- ##
1410
- # Parses the parameters and block for a meta-programmed method.
1411
-
1412
- def parse_meta_method_params(container, single, meth, tk, comment) # :nodoc:
1413
- token_listener meth do
1414
- meth.params = ''
1415
-
1416
- look_for_directives_in meth, comment
1417
- comment.normalize
1418
- meth.call_seq = comment.extract_call_seq
1419
-
1420
- container.add_method meth
1421
-
1422
- last_tk = tk
1423
-
1424
- while tk = get_tk do
1425
- if :on_semicolon == tk[:kind] then
1426
- break
1427
- elsif :on_nl == tk[:kind] then
1428
- break unless last_tk and :on_comma == last_tk[:kind]
1429
- elsif :on_sp == tk[:kind] then
1430
- # expression continues
1431
- elsif :on_kw == tk[:kind] && 'do' == tk[:text] then
1432
- parse_statements container, single, meth
1433
- break
1434
- else
1435
- last_tk = tk
1436
- end
1437
- end
1438
- end
1439
- end
1440
-
1441
- ##
1442
- # Parses a normal method defined by +def+
1443
-
1444
- def parse_method(container, single, tk, comment)
1445
- singleton = nil
1446
- added_container = false
1447
- name = nil
1448
- column = tk[:char_no]
1449
- line_no = tk[:line_no]
1450
-
1451
- start_collecting_tokens(:ruby)
1452
- add_token tk
1453
-
1454
- token_listener self do
1455
- prev_container = container
1456
- name, container, singleton = parse_method_name container
1457
- added_container = container != prev_container
1458
- end
1459
-
1460
- return unless name
1461
-
1462
- meth = RDoc::AnyMethod.new get_tkread, name, singleton: single == SINGLE ? true : singleton
1463
- look_for_directives_in meth, comment
1464
- if singleton
1465
- # `current_line_visibility' is useless because it works against
1466
- # the normal method named as same as the singleton method, after
1467
- # the latter was defined. Of course these are different things.
1468
- container.current_line_visibility = :public
1469
- end
1470
-
1471
- record_location meth
1472
- meth.line = line_no
1473
-
1474
- meth.start_collecting_tokens(:ruby)
1475
- indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
1476
- token = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
1477
- token[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
1478
- newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
1479
- meth.add_tokens [token, newline, indent]
1480
- meth.add_tokens @token_stream
1481
-
1482
- parse_method_params_and_body container, single, meth, added_container
1483
-
1484
- comment.normalize
1485
- meth.call_seq = comment.extract_call_seq
1486
-
1487
- meth.comment = comment
1488
-
1489
- # after end modifiers
1490
- read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS
1491
-
1492
- @stats.add_method meth
1493
- end
1494
-
1495
- ##
1496
- # Parses the parameters and body of +meth+
1497
-
1498
- def parse_method_params_and_body(container, single, meth, added_container)
1499
- token_listener meth do
1500
- parse_method_parameters meth
1501
-
1502
- if meth.document_self or not @track_visibility then
1503
- container.add_method meth
1504
- elsif added_container then
1505
- container.document_self = false
1506
- end
1507
-
1508
- # Having now read the method parameters and documentation modifiers, we
1509
- # now know whether we have to rename #initialize to ::new
1510
-
1511
- if meth.name == "initialize" && !meth.singleton then
1512
- if meth.dont_rename_initialize then
1513
- meth.visibility = :protected
1514
- else
1515
- meth.singleton = true
1516
- meth.name = "new"
1517
- meth.visibility = :public
1518
- end
1519
- end
1520
-
1521
- parse_statements container, single, meth
1522
- end
1523
- end
1524
-
1525
- ##
1526
- # Parses a method that needs to be ignored.
1527
-
1528
- def parse_method_dummy(container)
1529
- dummy = RDoc::Context.new
1530
- dummy.parent = container
1531
- dummy.store = container.store
1532
- skip_method dummy
1533
- end
1534
-
1535
- ##
1536
- # Parses the name of a method in +container+.
1537
- #
1538
- # Returns the method name, the container it is in (for def Foo.name) and if
1539
- # it is a singleton or regular method.
1540
-
1541
- def parse_method_name(container) # :nodoc:
1542
- skip_tkspace
1543
- name_t = get_tk
1544
- back_tk = skip_tkspace_without_nl
1545
- singleton = false
1546
-
1547
- dot = get_tk
1548
- if dot[:kind] == :on_period || (dot[:kind] == :on_op && dot[:text] == '::') then
1549
- singleton = true
1550
-
1551
- name, container = parse_method_name_singleton container, name_t
1552
- else
1553
- unget_tk dot
1554
- back_tk.reverse_each do |token|
1555
- unget_tk token
1556
- end
1557
-
1558
- name = parse_method_name_regular container, name_t
1559
- end
1560
-
1561
- return name, container, singleton
1562
- end
1563
-
1564
- ##
1565
- # For the given +container+ and initial name token +name_t+ the method name
1566
- # is parsed from the token stream for a regular method.
1567
-
1568
- def parse_method_name_regular(container, name_t) # :nodoc:
1569
- if :on_op == name_t[:kind] && (%w{* & [] []= <<}.include?(name_t[:text])) then
1570
- name_t[:text]
1571
- else
1572
- unless [:on_kw, :on_const, :on_ident].include?(name_t[:kind]) then
1573
- warn "expected method name token, . or ::, got #{name_t.inspect}"
1574
- skip_method container
1575
- return
1576
- end
1577
- name_t[:text]
1578
- end
1579
- end
1580
-
1581
- ##
1582
- # For the given +container+ and initial name token +name_t+ the method name
1583
- # and the new +container+ (if necessary) are parsed from the token stream
1584
- # for a singleton method.
1585
-
1586
- def parse_method_name_singleton(container, name_t) # :nodoc:
1587
- skip_tkspace
1588
- name_t2 = get_tk
1589
-
1590
- if (:on_kw == name_t[:kind] && 'self' == name_t[:text]) || (:on_op == name_t[:kind] && '%' == name_t[:text]) then
1591
- # NOTE: work around '[' being consumed early
1592
- if :on_lbracket == name_t2[:kind]
1593
- get_tk
1594
- name = '[]'
1595
- else
1596
- name = name_t2[:text]
1597
- end
1598
- elsif :on_const == name_t[:kind] then
1599
- name = name_t2[:text]
1600
-
1601
- container = get_method_container container, name_t
1602
-
1603
- return unless container
1604
-
1605
- name
1606
- elsif :on_ident == name_t[:kind] || :on_ivar == name_t[:kind] || :on_gvar == name_t[:kind] then
1607
- parse_method_dummy container
1608
-
1609
- name = nil
1610
- elsif (:on_kw == name_t[:kind]) && ('true' == name_t[:text] || 'false' == name_t[:text] || 'nil' == name_t[:text]) then
1611
- klass_name = "#{name_t[:text].capitalize}Class"
1612
- container = @store.find_class_named klass_name
1613
- container ||= @top_level.add_class RDoc::NormalClass, klass_name
1614
-
1615
- name = name_t2[:text]
1616
- else
1617
- warn "unexpected method name token #{name_t.inspect}"
1618
- # break
1619
- skip_method container
1620
-
1621
- name = nil
1622
- end
1623
-
1624
- return name, container
1625
- end
1626
-
1627
- ##
1628
- # Extracts +yield+ parameters from +method+
1629
-
1630
- def parse_method_or_yield_parameters(method = nil,
1631
- modifiers = RDoc::METHOD_MODIFIERS)
1632
- skip_tkspace_without_nl
1633
- tk = get_tk
1634
- end_token = get_end_token tk
1635
- return '' unless end_token
1636
-
1637
- nest = 0
1638
- continue = false
1639
-
1640
- while tk != nil do
1641
- case tk[:kind]
1642
- when :on_semicolon then
1643
- break if nest == 0
1644
- when :on_lbracket then
1645
- nest += 1
1646
- when :on_rbracket then
1647
- nest -= 1
1648
- when :on_lbrace then
1649
- nest += 1
1650
- when :on_rbrace then
1651
- nest -= 1
1652
- if nest <= 0
1653
- # we might have a.each { |i| yield i }
1654
- unget_tk(tk) if nest < 0
1655
- break
1656
- end
1657
- when :on_lparen then
1658
- nest += 1
1659
- when end_token[:kind] then
1660
- if end_token[:kind] == :on_rparen
1661
- nest -= 1
1662
- break if nest <= 0
1663
- else
1664
- break
1665
- end
1666
- when :on_rparen then
1667
- nest -= 1
1668
- when :on_comment, :on_embdoc then
1669
- @read.pop
1670
- if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and
1671
- (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then
1672
- if method && method.block_params.nil? then
1673
- unget_tk tk
1674
- read_documentation_modifiers method, modifiers
1675
- end
1676
- break if !continue and nest <= 0
1677
- end
1678
- when :on_comma then
1679
- continue = true
1680
- when :on_ident then
1681
- continue = false if continue
1261
+ def visit_forwarding_super_node(node)
1262
+ @calls_super = true
1682
1263
  end
1683
- tk = get_tk
1684
1264
  end
1685
-
1686
- get_tkread_clean(/\s+/, ' ')
1687
- end
1688
-
1689
- ##
1690
- # Capture the method's parameters. Along the way, look for a comment
1691
- # containing:
1692
- #
1693
- # # yields: ....
1694
- #
1695
- # and add this as the block_params for the method
1696
-
1697
- def parse_method_parameters(method)
1698
- res = parse_method_or_yield_parameters method
1699
-
1700
- res = "(#{res})" unless res =~ /\A\(/
1701
- method.params = res unless method.params
1702
-
1703
- return if method.block_params
1704
-
1705
- skip_tkspace_without_nl
1706
- read_documentation_modifiers method, RDoc::METHOD_MODIFIERS
1707
1265
  end
1708
-
1709
- ##
1710
- # Parses an RDoc::NormalModule in +container+ with +comment+
1711
-
1712
- def parse_module(container, single, tk, comment)
1713
- container, name_t, = get_class_or_module container
1714
-
1715
- name = name_t[:text]
1716
-
1717
- mod = container.add_module RDoc::NormalModule, name
1718
- mod.ignore unless container.document_children
1719
- record_location mod
1720
-
1721
- read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
1722
- mod.add_comment comment, @top_level
1723
- parse_statements mod
1724
-
1725
- # after end modifiers
1726
- read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
1727
-
1728
- @stats.add_module mod
1729
- end
1730
-
1731
- ##
1732
- # Parses an RDoc::Require in +context+ containing +comment+
1733
-
1734
- def parse_require(context, comment)
1735
- skip_tkspace_comment
1736
- tk = get_tk
1737
-
1738
- if :on_lparen == tk[:kind] then
1739
- skip_tkspace_comment
1740
- tk = get_tk
1741
- end
1742
-
1743
- name = tk[:text][1..-2] if :on_tstring == tk[:kind]
1744
-
1745
- if name then
1746
- @top_level.add_require RDoc::Require.new(name, comment)
1747
- else
1748
- unget_tk tk
1749
- end
1750
- end
1751
-
1752
- ##
1753
- # Parses a rescue
1754
-
1755
- def parse_rescue
1756
- skip_tkspace_without_nl
1757
-
1758
- while tk = get_tk
1759
- case tk[:kind]
1760
- when :on_nl, :on_semicolon, :on_comment then
1761
- break
1762
- when :on_comma then
1763
- skip_tkspace_without_nl
1764
-
1765
- get_tk if :on_nl == peek_tk[:kind]
1766
- end
1767
-
1768
- skip_tkspace_without_nl
1769
- end
1770
- end
1771
-
1772
- ##
1773
- # Retrieve comment body without =begin/=end
1774
-
1775
- def retrieve_comment_body(tk)
1776
- if :on_embdoc == tk[:kind]
1777
- tk[:text].gsub(/\A=begin.*\n/, '').gsub(/=end\n?\z/, '')
1778
- else
1779
- tk[:text]
1780
- end
1781
- end
1782
-
1783
- ##
1784
- # The core of the Ruby parser.
1785
-
1786
- def parse_statements(container, single = NORMAL, current_method = nil,
1787
- comment = new_comment(''))
1788
- raise 'no' unless RDoc::Comment === comment
1789
- comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
1790
-
1791
- nest = 1
1792
- save_visibility = container.visibility
1793
- container.visibility = :public unless current_method
1794
-
1795
- non_comment_seen = true
1796
-
1797
- while tk = get_tk do
1798
- keep_comment = false
1799
- try_parse_comment = false
1800
-
1801
- non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind])
1802
-
1803
- case tk[:kind]
1804
- when :on_nl, :on_ignored_nl, :on_comment, :on_embdoc then
1805
- if :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]
1806
- skip_tkspace
1807
- tk = get_tk
1808
- else
1809
- past_tokens = @read.size > 1 ? @read[0..-2] : []
1810
- nl_position = 0
1811
- past_tokens.reverse.each_with_index do |read_tk, i|
1812
- if read_tk =~ /^\n$/ then
1813
- nl_position = (past_tokens.size - 1) - i
1814
- break
1815
- elsif read_tk =~ /^#.*\n$/ then
1816
- nl_position = ((past_tokens.size - 1) - i) + 1
1817
- break
1818
- end
1819
- end
1820
- comment_only_line = past_tokens[nl_position..-1].all?{ |c| c =~ /^\s+$/ }
1821
- unless comment_only_line then
1822
- tk = get_tk
1823
- end
1824
- end
1825
-
1826
- if tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then
1827
- if non_comment_seen then
1828
- # Look for RDoc in a comment about to be thrown away
1829
- non_comment_seen = parse_comment container, tk, comment unless
1830
- comment.empty?
1831
-
1832
- comment = ''
1833
- comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
1834
- end
1835
-
1836
- line_no = nil
1837
- while tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) do
1838
- comment_body = retrieve_comment_body(tk)
1839
- line_no = tk[:line_no] if comment.empty?
1840
- comment += comment_body
1841
- comment << "\n" unless comment_body =~ /\n\z/
1842
-
1843
- if comment_body.size > 1 && comment_body =~ /\n\z/ then
1844
- skip_tkspace_without_nl # leading spaces
1845
- end
1846
- tk = get_tk
1847
- end
1848
-
1849
- comment = new_comment comment, line_no
1850
-
1851
- unless comment.empty? then
1852
- look_for_directives_in container, comment
1853
-
1854
- if container.done_documenting then
1855
- throw :eof if RDoc::TopLevel === container
1856
- container.ongoing_visibility = save_visibility
1857
- end
1858
- end
1859
-
1860
- keep_comment = true
1861
- else
1862
- non_comment_seen = true
1863
- end
1864
-
1865
- unget_tk tk
1866
- keep_comment = true
1867
- container.current_line_visibility = nil
1868
-
1869
- when :on_kw then
1870
- case tk[:text]
1871
- when 'class' then
1872
- parse_class container, single, tk, comment
1873
-
1874
- when 'module' then
1875
- parse_module container, single, tk, comment
1876
-
1877
- when 'def' then
1878
- parse_method container, single, tk, comment
1879
-
1880
- when 'alias' then
1881
- parse_alias container, single, tk, comment unless current_method
1882
-
1883
- when 'yield' then
1884
- if current_method.nil? then
1885
- warn "Warning: yield outside of method" if container.document_self
1886
- else
1887
- parse_yield container, single, tk, current_method
1888
- end
1889
-
1890
- when 'until', 'while' then
1891
- if (tk[:state] & Ripper::EXPR_LABEL) == 0
1892
- nest += 1
1893
- skip_optional_do_after_expression
1894
- end
1895
-
1896
- # Until and While can have a 'do', which shouldn't increase the nesting.
1897
- # We can't solve the general case, but we can handle most occurrences by
1898
- # ignoring a do at the end of a line.
1899
-
1900
- # 'for' is trickier
1901
- when 'for' then
1902
- nest += 1
1903
- skip_for_variable
1904
- skip_optional_do_after_expression
1905
-
1906
- when 'case', 'do', 'if', 'unless', 'begin' then
1907
- if (tk[:state] & Ripper::EXPR_LABEL) == 0
1908
- nest += 1
1909
- end
1910
-
1911
- when 'super' then
1912
- current_method.calls_super = true if current_method
1913
-
1914
- when 'rescue' then
1915
- parse_rescue
1916
-
1917
- when 'end' then
1918
- nest -= 1
1919
- if nest == 0 then
1920
- container.ongoing_visibility = save_visibility
1921
-
1922
- parse_comment container, tk, comment unless comment.empty?
1923
-
1924
- return
1925
- end
1926
- end
1927
-
1928
- when :on_const then
1929
- unless parse_constant container, tk, comment, current_method then
1930
- try_parse_comment = true
1931
- end
1932
-
1933
- when :on_ident then
1934
- if nest == 1 and current_method.nil? then
1935
- keep_comment = parse_identifier container, single, tk, comment
1936
- end
1937
-
1938
- case tk[:text]
1939
- when "require" then
1940
- parse_require container, comment
1941
- when "include" then
1942
- parse_extend_or_include RDoc::Include, container, comment
1943
- when "extend" then
1944
- parse_extend_or_include RDoc::Extend, container, comment
1945
- when "included" then
1946
- parse_included_with_activesupport_concern container, comment
1947
- end
1948
-
1949
- else
1950
- try_parse_comment = nest == 1
1951
- end
1952
-
1953
- if try_parse_comment then
1954
- non_comment_seen = parse_comment container, tk, comment unless
1955
- comment.empty?
1956
-
1957
- keep_comment = false
1958
- end
1959
-
1960
- unless keep_comment then
1961
- comment = new_comment ''
1962
- comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
1963
- container.params = nil
1964
- container.block_params = nil
1965
- end
1966
-
1967
- consume_trailing_spaces
1968
- end
1969
-
1970
- container.params = nil
1971
- container.block_params = nil
1972
- end
1973
-
1974
- ##
1975
- # Parse up to +no+ symbol arguments
1976
-
1977
- def parse_symbol_arg(no = nil)
1978
- skip_tkspace_comment
1979
-
1980
- tk = get_tk
1981
- if tk[:kind] == :on_lparen
1982
- parse_symbol_arg_paren no
1983
- else
1984
- parse_symbol_arg_space no, tk
1985
- end
1986
- end
1987
-
1988
- ##
1989
- # Parses up to +no+ symbol arguments surrounded by () and places them in
1990
- # +args+.
1991
-
1992
- def parse_symbol_arg_paren(no) # :nodoc:
1993
- args = []
1994
-
1995
- loop do
1996
- skip_tkspace_comment
1997
- if tk1 = parse_symbol_in_arg
1998
- args.push tk1
1999
- break if no and args.size >= no
2000
- end
2001
-
2002
- skip_tkspace_comment
2003
- case (tk2 = get_tk)[:kind]
2004
- when :on_rparen
2005
- break
2006
- when :on_comma
2007
- else
2008
- warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC
2009
- break
2010
- end
2011
- end
2012
-
2013
- args
2014
- end
2015
-
2016
- ##
2017
- # Parses up to +no+ symbol arguments separated by spaces and places them in
2018
- # +args+.
2019
-
2020
- def parse_symbol_arg_space(no, tk) # :nodoc:
2021
- args = []
2022
-
2023
- unget_tk tk
2024
- if tk = parse_symbol_in_arg
2025
- args.push tk
2026
- return args if no and args.size >= no
2027
- end
2028
-
2029
- loop do
2030
- skip_tkspace_without_nl
2031
-
2032
- tk1 = get_tk
2033
- if tk1.nil? || :on_comma != tk1[:kind] then
2034
- unget_tk tk1
2035
- break
2036
- end
2037
-
2038
- skip_tkspace_comment
2039
- if tk = parse_symbol_in_arg
2040
- args.push tk
2041
- break if no and args.size >= no
2042
- end
2043
- end
2044
-
2045
- args
2046
- end
2047
-
2048
- ##
2049
- # Returns symbol text from the next token
2050
-
2051
- def parse_symbol_in_arg
2052
- tk = get_tk
2053
- if :on_symbol == tk[:kind] then
2054
- tk[:text].sub(/^:/, '')
2055
- elsif :on_tstring == tk[:kind] then
2056
- tk[:text][1..-2]
2057
- elsif :on_dstring == tk[:kind] or :on_ident == tk[:kind] then
2058
- nil # ignore
2059
- else
2060
- warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC
2061
- nil
2062
- end
2063
- end
2064
-
2065
- ##
2066
- # Parses statements in the top-level +container+
2067
-
2068
- def parse_top_level_statements(container)
2069
- comment = collect_first_comment
2070
-
2071
- look_for_directives_in container, comment
2072
-
2073
- throw :eof if container.done_documenting
2074
-
2075
- @markup = comment.format
2076
-
2077
- # HACK move if to RDoc::Context#comment=
2078
- container.comment = comment if container.document_self unless comment.empty?
2079
-
2080
- parse_statements container, NORMAL, nil, comment
2081
- end
2082
-
2083
- ##
2084
- # Determines the visibility in +container+ from +tk+
2085
-
2086
- def parse_visibility(container, single, tk)
2087
- vis_type, vis, singleton = get_visibility_information tk, single
2088
-
2089
- skip_tkspace_comment false
2090
-
2091
- ptk = peek_tk
2092
- # Ryan Davis suggested the extension to ignore modifiers, because he
2093
- # often writes
2094
- #
2095
- # protected unless $TESTING
2096
- #
2097
- if [:on_nl, :on_semicolon].include?(ptk[:kind]) || (:on_kw == ptk[:kind] && (['if', 'unless'].include?(ptk[:text]))) then
2098
- container.ongoing_visibility = vis
2099
- elsif :on_kw == ptk[:kind] && 'def' == ptk[:text]
2100
- container.current_line_visibility = vis
2101
- else
2102
- update_visibility container, vis_type, vis, singleton
2103
- end
2104
- end
2105
-
2106
- ##
2107
- # Parses a Module#private_constant or Module#public_constant call from +tk+.
2108
-
2109
- def parse_constant_visibility(container, single, tk)
2110
- args = parse_symbol_arg
2111
- case tk[:text]
2112
- when 'private_constant'
2113
- vis = :private
2114
- when 'public_constant'
2115
- vis = :public
2116
- else
2117
- raise RDoc::Error, 'Unreachable'
2118
- end
2119
- container.set_constant_visibility_for args, vis
2120
- end
2121
-
2122
- ##
2123
- # Determines the block parameter for +context+
2124
-
2125
- def parse_yield(context, single, tk, method)
2126
- return if method.block_params
2127
-
2128
- get_tkread
2129
- method.block_params = parse_method_or_yield_parameters
2130
- end
2131
-
2132
- ##
2133
- # Directives are modifier comments that can appear after class, module, or
2134
- # method names. For example:
2135
- #
2136
- # def fred # :yields: a, b
2137
- #
2138
- # or:
2139
- #
2140
- # class MyClass # :nodoc:
2141
- #
2142
- # We return the directive name and any parameters as a two element array if
2143
- # the name is in +allowed+. A directive can be found anywhere up to the end
2144
- # of the current line.
2145
-
2146
- def read_directive(allowed)
2147
- tokens = []
2148
-
2149
- while tk = get_tk do
2150
- tokens << tk
2151
-
2152
- if :on_nl == tk[:kind] or (:on_kw == tk[:kind] && 'def' == tk[:text]) then
2153
- return
2154
- elsif :on_comment == tk[:kind] or :on_embdoc == tk[:kind] then
2155
- return unless tk[:text] =~ /:?\b([\w-]+):\s*(.*)/
2156
-
2157
- directive = $1.downcase
2158
-
2159
- return [directive, $2] if allowed.include? directive
2160
-
2161
- return
2162
- end
2163
- end
2164
- ensure
2165
- unless tokens.length == 1 and (:on_comment == tokens.first[:kind] or :on_embdoc == tokens.first[:kind]) then
2166
- tokens.reverse_each do |token|
2167
- unget_tk token
2168
- end
2169
- end
2170
- end
2171
-
2172
- ##
2173
- # Handles directives following the definition for +context+ (any
2174
- # RDoc::CodeObject) if the directives are +allowed+ at this point.
2175
- #
2176
- # See also RDoc::Markup::PreProcess#handle_directive
2177
-
2178
- def read_documentation_modifiers(context, allowed)
2179
- skip_tkspace_without_nl
2180
- directive, value = read_directive allowed
2181
-
2182
- return unless directive
2183
-
2184
- @preprocess.handle_directive '', directive, value, context do |dir, param|
2185
- if %w[notnew not_new not-new].include? dir then
2186
- context.dont_rename_initialize = true
2187
-
2188
- true
2189
- end
2190
- end
2191
- end
2192
-
2193
- ##
2194
- # Records the location of this +container+ in the file for this parser and
2195
- # adds it to the list of classes and modules in the file.
2196
-
2197
- def record_location(container) # :nodoc:
2198
- case container
2199
- when RDoc::ClassModule then
2200
- @top_level.add_to_classes_or_modules container
2201
- end
2202
-
2203
- container.record_location @top_level
2204
- end
2205
-
2206
- ##
2207
- # Scans this Ruby file for Ruby constructs
2208
-
2209
- def scan
2210
- reset
2211
-
2212
- catch :eof do
2213
- begin
2214
- parse_top_level_statements @top_level
2215
-
2216
- rescue StandardError => e
2217
- if @content.include?('<%') and @content.include?('%>') then
2218
- # Maybe, this is ERB.
2219
- $stderr.puts "\033[2KRDoc detects ERB file. Skips it for compatibility:"
2220
- $stderr.puts @file_name
2221
- return
2222
- end
2223
-
2224
- if @scanner_point >= @scanner.size
2225
- now_line_no = @scanner[@scanner.size - 1][:line_no]
2226
- else
2227
- now_line_no = peek_tk[:line_no]
2228
- end
2229
- first_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no }
2230
- last_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no + 1 }
2231
- last_tk_index = last_tk_index ? last_tk_index - 1 : @scanner.size - 1
2232
- code = @scanner[first_tk_index..last_tk_index].map{ |t| t[:text] }.join
2233
-
2234
- $stderr.puts <<-EOF
2235
-
2236
- #{self.class} failure around line #{now_line_no} of
2237
- #{@file_name}
2238
-
2239
- EOF
2240
-
2241
- unless code.empty? then
2242
- $stderr.puts code
2243
- $stderr.puts
2244
- end
2245
-
2246
- raise e
2247
- end
2248
- end
2249
-
2250
- @top_level
2251
- end
2252
-
2253
- ##
2254
- # while, until, and for have an optional do
2255
-
2256
- def skip_optional_do_after_expression
2257
- skip_tkspace_without_nl
2258
- tk = get_tk
2259
-
2260
- b_nest = 0
2261
- nest = 0
2262
-
2263
- loop do
2264
- break unless tk
2265
- case tk[:kind]
2266
- when :on_semicolon, :on_nl, :on_ignored_nl then
2267
- break if b_nest.zero?
2268
- when :on_lparen then
2269
- nest += 1
2270
- when :on_rparen then
2271
- nest -= 1
2272
- when :on_kw then
2273
- case tk[:text]
2274
- when 'begin'
2275
- b_nest += 1
2276
- when 'end'
2277
- b_nest -= 1
2278
- when 'do'
2279
- break if nest.zero?
2280
- end
2281
- when :on_comment, :on_embdoc then
2282
- if b_nest.zero? and "\n" == tk[:text][-1] then
2283
- break
2284
- end
2285
- end
2286
- tk = get_tk
2287
- end
2288
-
2289
- skip_tkspace_without_nl
2290
-
2291
- get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text]
2292
- end
2293
-
2294
- ##
2295
- # skip the var [in] part of a 'for' statement
2296
-
2297
- def skip_for_variable
2298
- skip_tkspace_without_nl
2299
- get_tk
2300
- skip_tkspace_without_nl
2301
- tk = get_tk
2302
- unget_tk(tk) unless :on_kw == tk[:kind] and 'in' == tk[:text]
2303
- end
2304
-
2305
- ##
2306
- # Skips the next method in +container+
2307
-
2308
- def skip_method(container)
2309
- meth = RDoc::AnyMethod.new "", "anon"
2310
- parse_method_parameters meth
2311
- parse_statements container, false, meth
2312
- end
2313
-
2314
- ##
2315
- # Skip spaces until a comment is found
2316
-
2317
- def skip_tkspace_comment(skip_nl = true)
2318
- loop do
2319
- skip_nl ? skip_tkspace : skip_tkspace_without_nl
2320
- next_tk = peek_tk
2321
- return if next_tk.nil? || (:on_comment != next_tk[:kind] and :on_embdoc != next_tk[:kind])
2322
- get_tk
2323
- end
2324
- end
2325
-
2326
- ##
2327
- # Updates visibility in +container+ from +vis_type+ and +vis+.
2328
-
2329
- def update_visibility(container, vis_type, vis, singleton) # :nodoc:
2330
- new_methods = []
2331
-
2332
- case vis_type
2333
- when 'module_function' then
2334
- args = parse_symbol_arg
2335
- container.set_visibility_for args, :private, false
2336
-
2337
- container.methods_matching args do |m|
2338
- s_m = m.dup
2339
- record_location s_m
2340
- s_m.singleton = true
2341
- new_methods << s_m
2342
- end
2343
- when 'public_class_method', 'private_class_method' then
2344
- args = parse_symbol_arg
2345
-
2346
- container.methods_matching args, true do |m|
2347
- if m.parent != container then
2348
- m = m.dup
2349
- record_location m
2350
- new_methods << m
2351
- end
2352
-
2353
- m.visibility = vis
2354
- end
2355
- else
2356
- args = parse_symbol_arg
2357
- container.set_visibility_for args, vis, singleton
2358
- end
2359
-
2360
- new_methods.each do |method|
2361
- case method
2362
- when RDoc::AnyMethod then
2363
- container.add_method method
2364
- when RDoc::Attr then
2365
- container.add_attribute method
2366
- end
2367
- method.visibility = vis
2368
- end
2369
- end
2370
-
2371
- ##
2372
- # Prints +message+ to +$stderr+ unless we're being quiet
2373
-
2374
- def warn(message)
2375
- @options.warn make_message message
2376
- end
2377
-
2378
1266
  end