rdoc 7.2.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +4 -7
  3. data/LICENSE.rdoc +4 -0
  4. data/README.md +43 -2
  5. data/RI.md +75 -75
  6. data/doc/markup_reference/markdown.md +104 -3
  7. data/exe/rdoc +2 -2
  8. data/lib/rdoc/code_object/alias.rb +70 -74
  9. data/lib/rdoc/code_object/any_method.rb +305 -298
  10. data/lib/rdoc/code_object/attr.rb +150 -143
  11. data/lib/rdoc/code_object/class_module.rb +801 -765
  12. data/lib/rdoc/code_object/constant.rb +178 -150
  13. data/lib/rdoc/code_object/context/section.rb +133 -160
  14. data/lib/rdoc/code_object/context.rb +925 -952
  15. data/lib/rdoc/code_object/extend.rb +7 -5
  16. data/lib/rdoc/code_object/include.rb +7 -5
  17. data/lib/rdoc/code_object/method_attr.rb +325 -324
  18. data/lib/rdoc/code_object/mixin.rb +97 -95
  19. data/lib/rdoc/code_object/normal_class.rb +77 -78
  20. data/lib/rdoc/code_object/normal_module.rb +61 -59
  21. data/lib/rdoc/code_object/require.rb +23 -39
  22. data/lib/rdoc/code_object/single_class.rb +21 -19
  23. data/lib/rdoc/code_object/top_level.rb +212 -213
  24. data/lib/rdoc/code_object.rb +305 -305
  25. data/lib/rdoc/comment.rb +274 -337
  26. data/lib/rdoc/cross_reference.rb +194 -212
  27. data/lib/rdoc/encoding.rb +105 -103
  28. data/lib/rdoc/erb_partial.rb +13 -11
  29. data/lib/rdoc/erbio.rb +29 -27
  30. data/lib/rdoc/generator/aliki.rb +165 -140
  31. data/lib/rdoc/generator/darkfish.rb +647 -631
  32. data/lib/rdoc/generator/json_index.rb +233 -229
  33. data/lib/rdoc/generator/markup.rb +165 -122
  34. data/lib/rdoc/generator/pot/message_extractor.rb +57 -51
  35. data/lib/rdoc/generator/pot/po.rb +52 -51
  36. data/lib/rdoc/generator/pot/po_entry.rb +138 -132
  37. data/lib/rdoc/generator/pot.rb +85 -81
  38. data/lib/rdoc/generator/ri.rb +23 -19
  39. data/lib/rdoc/generator/template/aliki/DESIGN.md +538 -0
  40. data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +1 -1
  41. data/lib/rdoc/generator/template/aliki/_footer.rhtml +1 -1
  42. data/lib/rdoc/generator/template/aliki/_head.rhtml +11 -11
  43. data/lib/rdoc/generator/template/aliki/_header.rhtml +29 -44
  44. data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +8 -6
  45. data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +8 -6
  46. data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +1 -1
  47. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +2 -2
  48. data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +4 -4
  49. data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +1 -1
  50. data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +1 -1
  51. data/lib/rdoc/generator/template/aliki/class.rhtml +56 -46
  52. data/lib/rdoc/generator/template/aliki/css/rdoc.css +538 -283
  53. data/lib/rdoc/generator/template/aliki/index.rhtml +1 -1
  54. data/lib/rdoc/generator/template/aliki/js/aliki.js +80 -102
  55. data/lib/rdoc/generator/template/aliki/page.rhtml +1 -1
  56. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
  57. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +2 -2
  58. data/lib/rdoc/generator/template/darkfish/_footer.rhtml +1 -1
  59. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +8 -6
  60. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +8 -6
  61. data/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +1 -1
  62. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +1 -1
  63. data/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +1 -1
  64. data/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +5 -5
  65. data/lib/rdoc/generator/template/darkfish/class.rhtml +18 -21
  66. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +0 -1
  67. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +3 -3
  68. data/lib/rdoc/generator.rb +48 -46
  69. data/lib/rdoc/i18n/locale.rb +99 -95
  70. data/lib/rdoc/i18n/text.rb +109 -105
  71. data/lib/rdoc/i18n.rb +7 -5
  72. data/lib/rdoc/markdown/byte_runtime.rb +80 -0
  73. data/lib/rdoc/markdown.kpeg +30 -21
  74. data/lib/rdoc/markdown.rb +329 -151
  75. data/lib/rdoc/markup/block_quote.rb +12 -8
  76. data/lib/rdoc/markup/document.rb +127 -123
  77. data/lib/rdoc/markup/formatter.rb +215 -221
  78. data/lib/rdoc/markup/heading.rb +1 -4
  79. data/lib/rdoc/markup/include.rb +33 -29
  80. data/lib/rdoc/markup/indented_paragraph.rb +37 -33
  81. data/lib/rdoc/markup/inline_parser.rb +281 -277
  82. data/lib/rdoc/markup/list.rb +80 -88
  83. data/lib/rdoc/markup/list_item.rb +73 -85
  84. data/lib/rdoc/markup/paragraph.rb +23 -19
  85. data/lib/rdoc/markup/parser.rb +501 -497
  86. data/lib/rdoc/markup/pre_process.rb +284 -305
  87. data/lib/rdoc/markup/raw.rb +2 -2
  88. data/lib/rdoc/markup/rule.rb +16 -12
  89. data/lib/rdoc/markup/to_ansi.rb +143 -139
  90. data/lib/rdoc/markup/to_bs.rb +72 -68
  91. data/lib/rdoc/markup/to_html.rb +600 -493
  92. data/lib/rdoc/markup/to_html_crossref.rb +221 -191
  93. data/lib/rdoc/markup/to_html_snippet.rb +232 -227
  94. data/lib/rdoc/markup/to_joined_paragraph.rb +40 -41
  95. data/lib/rdoc/markup/to_label.rb +63 -59
  96. data/lib/rdoc/markup/to_markdown.rb +212 -208
  97. data/lib/rdoc/markup/to_rdoc.rb +336 -332
  98. data/lib/rdoc/markup/to_table_of_contents.rb +66 -62
  99. data/lib/rdoc/markup/to_test.rb +60 -56
  100. data/lib/rdoc/markup/to_tt_only.rb +83 -86
  101. data/lib/rdoc/markup/verbatim.rb +62 -58
  102. data/lib/rdoc/markup.rb +198 -196
  103. data/lib/rdoc/options.rb +1063 -1076
  104. data/lib/rdoc/parser/c.rb +1039 -1036
  105. data/lib/rdoc/parser/changelog.rb +319 -315
  106. data/lib/rdoc/parser/markdown.rb +17 -13
  107. data/lib/rdoc/parser/rbs.rb +279 -0
  108. data/lib/rdoc/parser/rd.rb +17 -13
  109. data/lib/rdoc/parser/ruby.rb +1231 -2222
  110. data/lib/rdoc/parser/ruby_colorizer.rb +303 -0
  111. data/lib/rdoc/parser/simple.rb +31 -27
  112. data/lib/rdoc/parser/text.rb +12 -8
  113. data/lib/rdoc/parser.rb +230 -221
  114. data/lib/rdoc/rbs_helper.rb +186 -0
  115. data/lib/rdoc/rd/inline.rb +57 -53
  116. data/lib/rdoc/rd.rb +90 -88
  117. data/lib/rdoc/rdoc.rb +547 -366
  118. data/lib/rdoc/ri/driver.rb +1141 -1130
  119. data/lib/rdoc/ri/formatter.rb +7 -3
  120. data/lib/rdoc/ri/paths.rb +140 -136
  121. data/lib/rdoc/ri/servlet.rb +456 -0
  122. data/lib/rdoc/ri/store.rb +4 -2
  123. data/lib/rdoc/ri/task.rb +55 -51
  124. data/lib/rdoc/ri.rb +14 -11
  125. data/lib/rdoc/rubygems_hook.rb +194 -192
  126. data/lib/rdoc/server.rb +462 -0
  127. data/lib/rdoc/stats/normal.rb +46 -42
  128. data/lib/rdoc/stats/quiet.rb +39 -35
  129. data/lib/rdoc/stats/verbose.rb +35 -31
  130. data/lib/rdoc/stats.rb +363 -338
  131. data/lib/rdoc/store.rb +919 -725
  132. data/lib/rdoc/task.rb +260 -255
  133. data/lib/rdoc/text.rb +130 -245
  134. data/lib/rdoc/token_stream.rb +101 -115
  135. data/lib/rdoc/tom_doc.rb +203 -201
  136. data/lib/rdoc/version.rb +1 -1
  137. data/lib/rdoc.rb +35 -7
  138. data/lib/rubygems_plugin.rb +2 -11
  139. data/rdoc-logo.svg +43 -0
  140. data/rdoc.gemspec +6 -4
  141. metadata +36 -20
  142. data/lib/rdoc/code_object/anon_class.rb +0 -10
  143. data/lib/rdoc/code_object/ghost_method.rb +0 -6
  144. data/lib/rdoc/code_object/meta_method.rb +0 -6
  145. data/lib/rdoc/markdown/literals.kpeg +0 -21
  146. data/lib/rdoc/markdown/literals.rb +0 -454
  147. data/lib/rdoc/parser/prism_ruby.rb +0 -1112
  148. data/lib/rdoc/parser/ripper_state_lex.rb +0 -302
  149. data/lib/rdoc/parser/ruby_tools.rb +0 -163
  150. data/lib/rdoc/servlet.rb +0 -452
@@ -1,2378 +1,1387 @@
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
-
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
19
-
20
- require 'ripper'
21
- require_relative 'ripper_state_lex'
22
-
23
- ##
24
- # Extracts code elements from a source file returning a TopLevel object
25
- # containing the constituent file elements.
26
- #
27
- # This file is based on rtags
28
- #
29
- # RubyParser understands how to document:
30
- # * classes
31
- # * modules
32
- # * methods
33
- # * constants
34
- # * aliases
35
- # * private, public, protected
36
- # * private_class_function, public_class_function
37
- # * private_constant, public_constant
38
- # * module_function
39
- # * attr, attr_reader, attr_writer, attr_accessor
40
- # * extra accessors given on the command line
41
- # * metaprogrammed methods
42
- # * require
43
- # * include
44
- #
45
- # == Method Arguments
46
- #
47
- #--
48
- # NOTE: I don't think this works, needs tests, remove the paragraph following
49
- # this block when known to work
50
- #
51
- # The parser extracts the arguments from the method definition. You can
52
- # override this with a custom argument definition using the :args: directive:
53
- #
54
- # ##
55
- # # This method tries over and over until it is tired
56
- #
57
- # def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
58
- # puts thing_to_try
59
- # go_go_go thing_to_try, tries - 1
60
- # end
61
- #
62
- # If you have a more-complex set of overrides you can use the :call-seq:
63
- # 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
- #
70
- # ##
71
- # # This method can be called with a range or an offset and length
72
- # #
73
- # # :call-seq:
74
- # # my_method(Range)
75
- # # my_method(offset, length)
76
- #
77
- # def my_method(*args)
78
- # end
79
- #
80
- # The parser extracts +yield+ expressions from method bodies to gather the
81
- # yielded argument names. If your method manually calls a block instead of
82
- # yielding or you want to override the discovered argument names use
83
- # the :yields: directive:
84
- #
85
- # ##
86
- # # My method is awesome
87
- #
88
- # def my_method(&block) # :yields: happy, times
89
- # block.call 1, 2
90
- # end
91
- #
92
- # == Metaprogrammed Methods
93
- #
94
- # To pick up a metaprogrammed method, the parser looks for a comment starting
95
- # with '##' before an identifier:
96
- #
97
- # ##
98
- # # This is a meta-programmed method!
99
- #
100
- # add_my_method :meta_method, :arg1, :arg2
101
- #
102
- # The parser looks at the token after the identifier to determine the name, in
103
- # this example, :meta_method. If a name cannot be found, a warning is printed
104
- # and 'unknown' is used.
105
- #
106
- # You can force the name of a method using the :method: directive:
107
- #
108
- # ##
109
- # # :method: some_method!
110
- #
111
- # By default, meta-methods are instance methods. To indicate that a method is
112
- # a singleton method instead use the :singleton-method: directive:
113
- #
114
- # ##
115
- # # :singleton-method:
116
- #
117
- # You can also use the :singleton-method: directive with a name:
118
- #
119
- # ##
120
- # # :singleton-method: some_method!
121
- #
122
- # You can define arguments for metaprogrammed methods via either the
123
- # \:call-seq:, :arg: or :args: directives.
124
- #
125
- # Additionally you can mark a method as an attribute by
126
- # using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like
127
- # for :method:, the name is optional.
128
- #
129
- # ##
130
- # # :attr_reader: my_attr_name
131
- #
132
- # == Hidden methods and attributes
133
- #
134
- # You can provide documentation for methods that don't appear using
135
- # the :method:, :singleton-method: and :attr: directives:
136
- #
137
- # ##
138
- # # :attr_writer: ghost_writer
139
- # # There is an attribute here, but you can't see it!
140
- #
141
- # ##
142
- # # :method: ghost_method
143
- # # There is a method here, but you can't see it!
144
- #
145
- # ##
146
- # # this is a comment for a regular method
147
- #
148
- # def regular_method() end
149
- #
150
- # Note that by default, the :method: directive will be ignored if there is a
151
- # standard rdocable item following it.
152
-
153
- class RDoc::Parser::Ruby < RDoc::Parser
154
-
155
- parse_files_matching(/\.rbw?$/)
156
-
157
- include RDoc::TokenStream
158
- include RDoc::Parser::RubyTools
159
-
160
- ##
161
- # RDoc::NormalClass type
162
-
163
- NORMAL = "::"
164
-
165
- ##
166
- # RDoc::SingleClass type
167
-
168
- SINGLE = "<<"
169
-
170
- ##
171
- # Creates a new Ruby parser.
172
-
173
- def initialize(top_level, content, options, stats)
174
- super
175
-
176
- content = handle_tab_width(content)
177
-
178
- @size = 0
179
- @token_listeners = nil
180
- content = RDoc::Encoding.remove_magic_comment content
181
- @scanner = RDoc::Parser::RipperStateLex.parse(content)
182
- @content = content
183
- @scanner_point = 0
184
- @prev_seek = nil
185
- @markup = @options.markup
186
- @track_visibility = :nodoc != @options.visibility
187
- @encoding = @options.encoding
188
-
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]
197
- end
198
-
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
208
-
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
238
-
239
- return vis_type, vis, singleton
240
- end
241
-
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]
267
-
268
- line_no = tk[:line_no] if first_line
269
- first_line = false
270
- comment << comment_body
271
- tk = get_tk
272
-
273
- if :on_nl === tk then
274
- skip_tkspace_without_nl
275
- tk = get_tk
276
- end
277
- end
278
- end
279
-
280
- unget_tk tk
281
-
282
- new_comment comment, line_no
283
- end
284
-
285
- ##
286
- # Consumes trailing whitespace from the token stream
287
-
288
- def consume_trailing_spaces # :nodoc:
289
- skip_tkspace_without_nl
290
- end
291
-
292
- ##
293
- # Creates a new attribute in +container+ with +name+.
294
-
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
298
-
299
- container.add_attribute att
300
- @stats.add_attribute att
301
-
302
- att
303
- end
304
-
305
- ##
306
- # Creates a module alias in +container+ at +rhs_name+ (or at the top-level
307
- # for "::") with the name from +constant+.
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
315
-
316
- container.add_module_alias mod, rhs_name, constant, @top_level
317
- end
318
-
319
- ##
320
- # Aborts with +msg+
321
-
322
- def error(msg)
323
- msg = make_message msg
324
-
325
- abort msg
326
- end
327
-
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
342
- end
343
-
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 << '::'
359
- end
360
-
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
381
-
382
- record_location container
383
-
384
- get_tk
385
- skip_tkspace
386
- if :on_lparen == peek_tk[:kind] # ProcObjectInConstant::()
387
- parse_method_or_yield_parameters
388
- break
389
- 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}"
393
- end
394
- if prev_container == container and !ignore_constants
395
- given_name = name_t[:text]
396
- else
397
- given_name << '::' + name_t[:text]
398
- end
399
- end
400
-
401
- skip_tkspace_without_nl
402
-
403
- return [container, name_t, given_name, new_modules]
404
- end
405
-
406
- ##
407
- # Skip opening parentheses and yield the block.
408
- # Skip closing parentheses too when exists.
409
-
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
421
- end
422
-
423
- ret
424
- else
425
- yield
426
- end
427
- end
428
-
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
445
-
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
455
- end
456
-
457
- res += parse_call_parameters(tk)
458
- res
459
- end
460
-
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
469
-
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
477
- end
478
-
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
551
-
552
- if only_constant
553
- get_tkread_clean(/\s+/, ' ')
554
- else
555
- ''
556
- end
557
- end
558
-
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
582
- end
583
-
584
- ##
585
- # Retrieves the method container for a singleton method.
586
-
587
- def get_method_container(container, name_t) # :nodoc:
588
- prev_container = container
589
- container = container.find_module_named(name_t[:text])
590
-
591
- unless container then
592
- constant = prev_container.constants.find do |const|
593
- const.name == name_t[:text]
594
- end
595
-
596
- if constant then
597
- parse_method_dummy prev_container
598
- return
599
- end
600
- 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
2
 
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]
619
- end
620
-
621
- record_location container
622
- end
623
-
624
- container
625
- end
626
-
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(/^:/, '')
635
-
636
- next_tk = peek_tk
637
- if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then
638
- get_tk
639
- text << '='
640
- end
641
-
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})"
649
- end
650
- end
651
-
652
- ##
653
- # Marks containers between +container+ and +ancestor+ as ignored
654
-
655
- def suppress_parents(container, ancestor) # :nodoc:
656
- while container and container != ancestor do
657
- container.suppress unless container.documented?
658
- container = container.parent
659
- end
660
- end
661
-
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.
669
-
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
683
-
684
- comment.remove_private
685
- end
686
-
687
- ##
688
- # Adds useful info about the parser to +message+
689
-
690
- def make_message(message)
691
- prefix = "#{@file_name}:".dup
692
-
693
- tk = peek_tk
694
- prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk
695
-
696
- "#{prefix} #{message}"
697
- end
698
-
699
- ##
700
- # Creates a comment with the correct format
701
-
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
707
- end
708
-
709
- ##
710
- # Creates an RDoc::Attr for the name following +tk+, setting the comment to
711
- # +comment+.
712
-
713
- def parse_attr(context, single, tk, comment)
714
- line_no = tk[:line_no]
715
-
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
722
-
723
- if :on_comma == tk[:kind] then
724
- rw = "RW" if get_bool
725
- else
726
- unget_tk tk
727
- end
728
-
729
- att = create_attr context, single, name, rw, comment
730
- att.line = line_no
731
-
732
- read_documentation_modifiers att, RDoc::ATTR_MODIFIERS
733
- else
734
- warn "'attr' ignored - looks like a variable"
735
- end
736
- end
737
-
738
- ##
739
- # Creates an RDoc::Attr for each attribute listed after +tk+, setting the
740
- # comment for each to +comment+.
741
-
742
- def parse_attr_accessor(context, single, tk, comment)
743
- line_no = tk[:line_no]
744
-
745
- args = parse_symbol_arg
746
- rw = "?"
747
-
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"
758
- else
759
- rw = '?'
760
- end
761
-
762
- for name in args
763
- att = create_attr context, single, name, rw, comment
764
- att.line = line_no
765
- end
766
- end
767
-
768
- ##
769
- # Parses an +alias+ in +context+ with +comment+
770
-
771
- def parse_alias(context, single, tk, comment)
772
- line_no = tk[:line_no]
773
-
774
- skip_tkspace
775
-
776
- if :on_lparen === peek_tk[:kind] then
777
- get_tk
778
- skip_tkspace
779
- end
780
-
781
- new_name = get_symbol_or_name
782
-
783
- skip_tkspace
784
- if :on_comma === peek_tk[:kind] then
785
- get_tk
786
- skip_tkspace
787
- end
788
-
789
- begin
790
- old_name = get_symbol_or_name
791
- rescue RDoc::Error
792
- return
793
- end
794
-
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
803
- end
804
-
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
833
- 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
843
- end
844
- end
845
- tk = get_tk
846
- end
847
-
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
871
- end
872
- else
873
- warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}"
874
- return
875
- end
876
-
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 = $'
895
- end
896
-
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?
903
- end
904
-
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+.
927
-
928
- def parse_class_singleton(container, name, comment) # :nodoc:
929
- other = @store.find_class_named name
930
-
931
- unless other then
932
- if name =~ /^::/ then
933
- name = $'
934
- container = @top_level
935
- end
936
-
937
- other = container.add_module RDoc::NormalModule, name
938
- record_location other
939
-
940
- # class << $gvar
941
- other.ignore if name.empty?
942
-
943
- other.add_comment comment, @top_level
944
- end
945
-
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
952
- end
3
+ require 'prism'
4
+ require_relative '../rbs_helper'
953
5
 
954
- @top_level.add_to_classes_or_modules other
955
- @stats.add_class other
6
+ module RDoc
7
+ class Parser
8
+ # Parse and collect document from Ruby source code.
956
9
 
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]
982
- end
983
-
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
999
- end
1000
-
1001
- unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then
1002
- return false
1003
- end
1004
- get_tk
1005
-
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
1011
- end
1012
- end
1013
-
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
1020
-
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
1030
-
1031
- true
1032
- end
1033
-
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]
1069
-
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 << '::'
1082
- end
1083
- tk = get_tk
1084
- end
1085
-
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+.
1092
-
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
1112
- end
1113
-
1114
- true
1115
- end
1116
-
1117
- ##
1118
- # Parse a comment that is describing an attribute in +container+ with the
1119
- # given +name+ and +comment+.
1120
-
1121
- def parse_comment_attr(container, type, name, comment) # :nodoc:
1122
- return if name.empty?
1123
-
1124
- rw = case type
1125
- when 'attr_reader' then 'R'
1126
- when 'attr_writer' then 'W'
1127
- else 'RW'
1128
- end
1129
-
1130
- create_attr container, NORMAL, name, rw, comment
1131
- end
1132
-
1133
- def parse_comment_ghost(container, text, name, column, line_no, # :nodoc:
1134
- comment)
1135
- name = nil if name.empty?
1136
-
1137
- meth = RDoc::GhostMethod.new get_tkread, name
1138
- record_location meth
1139
-
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
- ''
1152
- end
1153
-
1154
- comment.normalize
1155
- meth.call_seq = comment.extract_call_seq
1156
-
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
10
+ ##
11
+ # Extracts code elements from a source file returning a TopLevel object
12
+ # containing the constituent file elements.
13
+ #
14
+ # RubyParser understands how to document:
15
+ # * classes
16
+ # * modules
17
+ # * methods
18
+ # * constants
19
+ # * aliases
20
+ # * private, public, protected
21
+ # * private_class_method, public_class_method
22
+ # * private_constant, public_constant
23
+ # * module_function
24
+ # * attr, attr_reader, attr_writer, attr_accessor
25
+ # * extra accessors given on the command line
26
+ # * metaprogrammed methods
27
+ # * require
28
+ # * include
29
+ #
30
+ # == Method Arguments
31
+ #
32
+ # The parser extracts the arguments from the method definition. You can
33
+ # override this with a custom argument definition using the :args: directive:
34
+ #
35
+ # ##
36
+ # # This method tries over and over until it is tired
37
+ #
38
+ # def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
39
+ # puts thing_to_try
40
+ # go_go_go thing_to_try, tries - 1
41
+ # end
42
+ #
43
+ # If you have a more-complex set of overrides you can use the :call-seq:
44
+ # directive:
45
+ #
46
+ # ##
47
+ # # This method can be called with a range or an offset and length
48
+ # #
49
+ # # :call-seq:
50
+ # # my_method(Range)
51
+ # # my_method(offset, length)
52
+ #
53
+ # def my_method(*args)
54
+ # end
55
+ #
56
+ # The parser extracts +yield+ expressions from method bodies to gather the
57
+ # yielded argument names. If your method manually calls a block instead of
58
+ # yielding or you want to override the discovered argument names use
59
+ # the :yields: directive:
60
+ #
61
+ # ##
62
+ # # My method is awesome
63
+ #
64
+ # def my_method(&block) # :yields: happy, times
65
+ # block.call 1, 2
66
+ # end
67
+ #
68
+ # == Metaprogrammed Methods
69
+ #
70
+ # To pick up a metaprogrammed method, the parser looks for a comment starting
71
+ # with '##' before a metaprogramming method call:
72
+ #
73
+ # ##
74
+ # # This is a meta-programmed method!
75
+ #
76
+ # add_my_method :meta_method, :arg1, :arg2
77
+ #
78
+ # The parser looks at the first argument to determine the name, in
79
+ # this example, :meta_method. If a name cannot be found, a warning is printed
80
+ # and 'unknown' is used.
81
+ #
82
+ # You can force the name of a method using the :method: directive:
83
+ #
84
+ # ##
85
+ # # :method: some_method!
86
+ #
87
+ # By default, meta-methods are instance methods. To indicate that a method is
88
+ # a singleton method instead use the :singleton-method: directive:
89
+ #
90
+ # ##
91
+ # # :singleton-method:
92
+ #
93
+ # You can also use the :singleton-method: directive with a name:
94
+ #
95
+ # ##
96
+ # # :singleton-method: some_method!
97
+ #
98
+ # You can define arguments for metaprogrammed methods via either the
99
+ # \:call-seq:, :arg: or :args: directives.
100
+ #
101
+ # Additionally you can mark a method as an attribute by
102
+ # using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like
103
+ # for :method:, the name is optional.
104
+ #
105
+ # ##
106
+ # # :attr_reader: my_attr_name
107
+ #
108
+ # == Hidden methods and attributes
109
+ #
110
+ # You can provide documentation for methods that don't appear using
111
+ # the :method:, :singleton-method: and :attr: directives:
112
+ #
113
+ # ##
114
+ # # :attr_writer: ghost_writer
115
+ # # There is an attribute here, but you can't see it!
116
+ #
117
+ # ##
118
+ # # :method: ghost_method
119
+ # # There is a method here, but you can't see it!
120
+ #
121
+ # ##
122
+ # # this is a comment for a regular method
123
+ #
124
+ # def regular_method() end
125
+ #
126
+ # Note that by default, the :method: directive will be ignored if there is a
127
+ # standard rdocable item following it.
1580
128
 
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]
129
+ class Ruby < Parser
1600
130
 
1601
- container = get_method_container container, name_t
131
+ parse_files_matching(/\.rbw?$/)
1602
132
 
1603
- return unless container
133
+ # Matches an RBS inline type annotation line: #: followed by whitespace
134
+ RBS_SIG_LINE = /\A#:\s/ # :nodoc:
1604
135
 
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
136
+ attr_accessor :visibility
137
+ attr_reader :container, :singleton, :in_proc_block
1608
138
 
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
139
+ def initialize(top_level, content, options, stats)
140
+ super
1614
141
 
1615
- name = name_t2[:text]
1616
- else
1617
- warn "unexpected method name token #{name_t.inspect}"
1618
- # break
1619
- skip_method container
142
+ content = handle_tab_width(content)
1620
143
 
1621
- name = nil
1622
- end
144
+ @size = 0
145
+ @token_listeners = nil
146
+ content = Encoding.remove_magic_comment content
147
+ @content = content
148
+ @colorizer_context = Parser::RubyColorizer::DeferredContext.new(content)
149
+ @markup = @options.markup
150
+ @track_visibility = :nodoc != @options.visibility
151
+ @encoding = @options.encoding
1623
152
 
1624
- return name, container
1625
- end
153
+ @module_nesting = [[top_level, false]]
154
+ @container = top_level
155
+ @visibility = :public
156
+ @singleton = false
157
+ @in_proc_block = false
158
+ @doc_state = :startdoc
159
+ end
1626
160
 
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
161
+ # Applies document control directives (:startdoc:, :stopdoc: and :enddoc:)
162
+ # to the current lexical scope. The state is restored when the enclosing
163
+ # class/module scope is closed.
164
+
165
+ def apply_document_control_directive(directives)
166
+ directives.each do |directive, (_param, line)|
167
+ case directive
168
+ when 'startdoc', 'stopdoc'
169
+ # :enddoc: cannot be cancelled within the scope, even by :startdoc:
170
+ if @doc_state == :enddoc
171
+ @options.warn "#{@top_level.relative_name}:#{line}: :startdoc: is ignored after :enddoc:" if directive == 'startdoc'
172
+ next
173
+ end
174
+ @doc_state = directive.to_sym
175
+ if directive == 'startdoc' && !@container.ignored?
176
+ # Compatibility: `module Net #:nodoc:` followed by :stopdoc:/:startdoc:
177
+ # regions is a common pattern that expects :startdoc: to make the
178
+ # container documentable again. Containers ignored here were created
179
+ # in a suppressed region and need documentable contents to revive.
180
+ @container.start_doc
181
+ @container.force_documentation = true
182
+ end
183
+ when 'enddoc'
184
+ @doc_state = :enddoc
1675
185
  end
1676
- break if !continue and nest <= 0
1677
186
  end
1678
- when :on_comma then
1679
- continue = true
1680
- when :on_ident then
1681
- continue = false if continue
1682
187
  end
1683
- tk = get_tk
1684
- end
1685
-
1686
- get_tkread_clean(/\s+/, ' ')
1687
- end
1688
188
 
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
189
+ # Returns true if code objects at the current position should not be
190
+ # documented, that is, inside a :stopdoc: or :enddoc: region.
1696
191
 
1697
- def parse_method_parameters(method)
1698
- res = parse_method_or_yield_parameters method
192
+ def document_suppressed?
193
+ @track_visibility && @doc_state != :startdoc
194
+ end
1699
195
 
1700
- res = "(#{res})" unless res =~ /\A\(/
1701
- method.params = res unless method.params
196
+ # Makes a container that was created inside a :stopdoc:/:enddoc: region
197
+ # (thus ignored) documentable again when it receives documentable contents
198
+ # outside the region, possibly from another file.
1702
199
 
1703
- return if method.block_params
200
+ def mark_container_documentable(container)
201
+ return if container.received_nodoc || !container.ignored?
202
+ record_location(container)
203
+ container.start_doc
204
+ mark_container_documentable(container.parent) if container.parent.is_a?(ClassModule)
205
+ end
1704
206
 
1705
- skip_tkspace_without_nl
1706
- read_documentation_modifiers method, RDoc::METHOD_MODIFIERS
1707
- end
207
+ # Suppress `extend` and `include` within block
208
+ # because they might be a metaprogramming block
209
+ # example: `Module.new { include M }` `M.module_eval { include N }`
1708
210
 
1709
- ##
1710
- # Parses an RDoc::NormalModule in +container+ with +comment+
211
+ def with_in_proc_block
212
+ in_proc_block = @in_proc_block
213
+ @in_proc_block = true
214
+ yield
215
+ @in_proc_block = in_proc_block
216
+ end
1711
217
 
1712
- def parse_module(container, single, tk, comment)
1713
- container, name_t, = get_class_or_module container
218
+ # Dive into another container
219
+
220
+ def with_container(container, singleton: false)
221
+ old_container = @container
222
+ old_visibility = @visibility
223
+ old_singleton = @singleton
224
+ old_in_proc_block = @in_proc_block
225
+ old_doc_state = @doc_state
226
+ @visibility = :public
227
+ @container = container
228
+ @singleton = singleton
229
+ @in_proc_block = false
230
+ @module_nesting.push([container, singleton])
231
+ yield container
232
+ ensure
233
+ @container = old_container
234
+ @visibility = old_visibility
235
+ @singleton = old_singleton
236
+ @in_proc_block = old_in_proc_block
237
+ @doc_state = old_doc_state
238
+ @module_nesting.pop
239
+ end
1714
240
 
1715
- name = name_t[:text]
241
+ # Records the location of this +container+ in the file for this parser and
242
+ # adds it to the list of classes and modules in the file.
1716
243
 
1717
- mod = container.add_module RDoc::NormalModule, name
1718
- mod.ignore unless container.document_children
1719
- record_location mod
244
+ def record_location(container) # :nodoc:
245
+ case container
246
+ when ClassModule
247
+ @top_level.add_to_classes_or_modules container
248
+ end
1720
249
 
1721
- read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
1722
- mod.add_comment comment, @top_level
1723
- parse_statements mod
250
+ container.record_location @top_level
251
+ end
1724
252
 
1725
- # after end modifiers
1726
- read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
253
+ # Scans this Ruby file for Ruby constructs
1727
254
 
1728
- @stats.add_module mod
1729
- end
255
+ def scan
256
+ @lines = @content.lines
257
+ result = Prism.parse(@content)
258
+ @program_node = result.value
259
+ @line_nodes = {}
260
+ prepare_line_nodes(@program_node)
261
+ prepare_comments(result.comments)
262
+ return if @top_level.done_documenting
1730
263
 
1731
- ##
1732
- # Parses an RDoc::Require in +context+ containing +comment+
264
+ @first_non_meta_comment_start_line = nil
265
+ if (_line_no, start_line = @unprocessed_comments.first)
266
+ @first_non_meta_comment_start_line = start_line if start_line < @program_node.location.start_line
267
+ end
1733
268
 
1734
- def parse_require(context, comment)
1735
- skip_tkspace_comment
1736
- tk = get_tk
269
+ @program_node.accept(RDocVisitor.new(self, @top_level, @store))
270
+ process_comments_until(@lines.size + 1)
271
+ end
1737
272
 
1738
- if :on_lparen == tk[:kind] then
1739
- skip_tkspace_comment
1740
- tk = get_tk
1741
- end
273
+ def should_document?(code_object) # :nodoc:
274
+ return true unless @track_visibility
275
+ return false if code_object.parent&.document_children == false
276
+ code_object.document_self
277
+ end
1742
278
 
1743
- name = tk[:text][1..-2] if :on_tstring == tk[:kind]
279
+ # Assign AST node to a line.
280
+ # This is used to show meta-method source code in the documentation.
1744
281
 
1745
- if name then
1746
- @top_level.add_require RDoc::Require.new(name, comment)
1747
- else
1748
- unget_tk tk
1749
- end
1750
- end
282
+ def prepare_line_nodes(node) # :nodoc:
283
+ case node
284
+ when Prism::CallNode, Prism::DefNode
285
+ @line_nodes[node.location.start_line] ||= node
286
+ end
287
+ node.compact_child_nodes.each do |child|
288
+ prepare_line_nodes(child)
289
+ end
290
+ end
1751
291
 
1752
- ##
1753
- # Parses a rescue
292
+ # Prepares comments for processing. Comments are grouped into consecutive.
293
+ # Consecutive comment is linked to the next non-blank line.
294
+ #
295
+ # Example:
296
+ # 01| class A # modifier comment 1
297
+ # 02| def foo; end # modifier comment 2
298
+ # 03|
299
+ # 04| # consecutive comment 1 start_line: 4
300
+ # 05| # consecutive comment 1 linked to line: 7
301
+ # 06|
302
+ # 07| # consecutive comment 2 start_line: 7
303
+ # 08| # consecutive comment 2 linked to line: 10
304
+ # 09|
305
+ # 10| def bar; end # consecutive comment 2 linked to this line
306
+ # 11| end
307
+
308
+ def prepare_comments(comments)
309
+ current = []
310
+ consecutive_comments = [current]
311
+ @modifier_comments = {}
312
+ comments.each do |comment|
313
+ if comment.is_a? Prism::EmbDocComment
314
+ consecutive_comments << [comment] << (current = [])
315
+ elsif comment.location.start_line_slice.match?(/\S/)
316
+ text = comment.slice
317
+ text = Encoding.change_encoding(text, @encoding) if @encoding
318
+ @modifier_comments[comment.location.start_line] = text
319
+ elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line
320
+ current << comment
321
+ else
322
+ consecutive_comments << (current = [comment])
323
+ end
324
+ end
325
+ consecutive_comments.reject!(&:empty?)
326
+
327
+ # Example: line_no = 5, start_line = 2, comment_text = "# comment_start_line\n# comment\n"
328
+ # 1| class A
329
+ # 2| # comment_start_line
330
+ # 3| # comment
331
+ # 4|
332
+ # 5| def f; end # comment linked to this line
333
+ # 6| end
334
+ @unprocessed_comments = consecutive_comments.map! do |comments|
335
+ start_line = comments.first.location.start_line
336
+ line_no = comments.last.location.end_line + (comments.last.location.end_column == 0 ? 0 : 1)
337
+ texts = comments.map do |c|
338
+ c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice
339
+ end
340
+ text = texts.join("\n")
341
+ text = Encoding.change_encoding(text, @encoding) if @encoding
342
+ line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/)
343
+ [line_no, start_line, text]
344
+ end
1754
345
 
1755
- def parse_rescue
1756
- skip_tkspace_without_nl
346
+ # The first comment is special. It defines markup for the rest of the comments.
347
+ _, first_comment_start_line, first_comment_text = @unprocessed_comments.first
348
+ if first_comment_text && @lines[0...first_comment_start_line - 1].all? { |l| l.match?(/\A\s*$/) }
349
+ _text, directives = @preprocess.parse_comment(first_comment_text, first_comment_start_line, :ruby)
350
+ markup, = directives['markup']
351
+ @markup = markup.downcase if markup
352
+ end
353
+ end
1757
354
 
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
355
+ # Creates an RDoc::Method on +container+ from +comment+ if there is a
356
+ # Signature section in the comment
1764
357
 
1765
- get_tk if :on_nl == peek_tk[:kind]
1766
- end
358
+ def parse_comment_tomdoc(container, comment, line_no, start_line)
359
+ return if document_suppressed?
360
+ return unless signature = TomDoc.signature(comment)
1767
361
 
1768
- skip_tkspace_without_nl
1769
- end
1770
- end
362
+ name, = signature.split %r%[ \(]%, 2
1771
363
 
1772
- ##
1773
- # Retrieve comment body without =begin/=end
364
+ meth = AnyMethod.new name
365
+ record_location(meth)
366
+ meth.line = start_line
367
+ meth.call_seq = signature
368
+ return unless meth.name
1774
369
 
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
370
+ node = @line_nodes[line_no]
371
+ token_stream_loader = @colorizer_context.token_stream_loader(node.node_id) if node
372
+ meth.start_collecting_tokens(:ruby, loader: token_stream_loader)
1782
373
 
1783
- ##
1784
- # The core of the Ruby parser.
374
+ container.add_method meth
375
+ meth.comment = comment
376
+ @stats.add_method meth
377
+ end
1785
378
 
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
379
+ def has_modifier_nodoc?(line_no) # :nodoc:
380
+ @modifier_comments[line_no]&.match?(/\A#\s*:nodoc:/)
381
+ end
1790
382
 
1791
- nest = 1
1792
- save_visibility = container.visibility
1793
- container.visibility = :public unless current_method
383
+ def handle_modifier_directive(code_object, line_no) # :nodoc:
384
+ if (comment_text = @modifier_comments[line_no])
385
+ _text, directives = @preprocess.parse_comment(comment_text, line_no, :ruby)
386
+ handle_code_object_directives(code_object, directives)
387
+ end
388
+ end
1794
389
 
1795
- non_comment_seen = true
390
+ def call_node_name_arguments(call_node) # :nodoc:
391
+ return unless arguments_node = call_node.arguments
392
+ names = arguments_node.arguments.filter_map { |arg| argument_name(arg) }
393
+ names unless names.empty?
394
+ end
1796
395
 
1797
- while tk = get_tk do
1798
- keep_comment = false
1799
- try_parse_comment = false
396
+ def call_node_name_argument(call_node) # :nodoc:
397
+ return unless call_node.arguments
398
+ argument_name(call_node.arguments.arguments.first)
399
+ end
1800
400
 
1801
- non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind])
401
+ def argument_name(argument_node) # :nodoc:
402
+ case argument_node
403
+ when Prism::SymbolNode
404
+ argument_node.value
405
+ when Prism::StringNode
406
+ argument_node.unescaped
407
+ end
408
+ end
1802
409
 
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
410
+ # Handles meta method comments
411
+
412
+ def handle_meta_method_comment(comment, directives, node)
413
+ apply_document_control_directive(directives)
414
+ handle_code_object_directives(@container, directives)
415
+ is_call_node = node.is_a?(Prism::CallNode)
416
+ singleton_method = false
417
+ visibility = @visibility
418
+ attributes = rw = line_no = method_name = nil
419
+ directives.each do |directive, (param, line)|
420
+ case directive
421
+ when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor'
422
+ attributes = [param] if param
423
+ attributes ||= call_node_name_arguments(node) || [] if is_call_node
424
+ rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R'
425
+ when 'method'
426
+ method_name = param if param
427
+ line_no = line
428
+ when 'singleton-method'
429
+ method_name = param if param
430
+ line_no = line
431
+ singleton_method = true
432
+ visibility = :public
1823
433
  end
1824
434
  end
1825
435
 
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
436
+ return if document_suppressed?
437
+
438
+ if attributes
439
+ attributes.each do |attr|
440
+ a = Attr.new(attr, rw, comment, singleton: @singleton)
441
+ a.store = @store
442
+ a.line = line_no
443
+ a.visibility = visibility
444
+ record_location(a)
445
+ @container.add_attribute(a)
446
+ mark_container_documentable(@container)
1834
447
  end
448
+ elsif line_no || node
449
+ method_name ||= call_node_name_argument(node) if is_call_node
450
+ line_no = node.location.start_line if node
451
+ internal_add_method(
452
+ method_name,
453
+ @container,
454
+ comment: comment,
455
+ directives: directives,
456
+ line_no: line_no,
457
+ visibility: visibility,
458
+ singleton: @singleton || singleton_method,
459
+ params: nil,
460
+ calls_super: false,
461
+ block_params: nil,
462
+ node_id: node&.node_id,
463
+ )
464
+ end
465
+ end
1835
466
 
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
467
+ INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST = %w[
468
+ method singleton-method attr attr_reader attr_writer attr_accessor
469
+ ].freeze
470
+ private_constant :INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST
1848
471
 
1849
- comment = new_comment comment, line_no
472
+ def normal_comment_treat_as_ghost_method_for_now?(directives, line_no) # :nodoc:
473
+ # Meta method comment should start with `##` but some comments does not follow this rule.
474
+ # For now, RDoc accepts them as a meta method comment if there is no node linked to it.
475
+ !@line_nodes[line_no] && INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST.any? { |directive| directives.has_key?(directive) }
476
+ end
1850
477
 
1851
- unless comment.empty? then
1852
- look_for_directives_in container, comment
478
+ def handle_standalone_consecutive_comment_directive(comment, directives, start_with_sharp_sharp, line_no, start_line) # :nodoc:
479
+ if start_with_sharp_sharp && start_line != @first_non_meta_comment_start_line
480
+ node = @line_nodes[line_no]
481
+ handle_meta_method_comment(comment, directives, node)
482
+ elsif normal_comment_treat_as_ghost_method_for_now?(directives, line_no) && start_line != @first_non_meta_comment_start_line
483
+ handle_meta_method_comment(comment, directives, nil)
484
+ else
485
+ apply_document_control_directive(directives)
486
+ handle_code_object_directives(@container, directives)
487
+ end
488
+ end
1853
489
 
1854
- if container.done_documenting then
1855
- throw :eof if RDoc::TopLevel === container
1856
- container.ongoing_visibility = save_visibility
1857
- end
490
+ # Processes consecutive comments that were not linked to any documentable code until the given line number
491
+
492
+ def process_comments_until(line_no_until)
493
+ while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until
494
+ line_no, start_line, text = @unprocessed_comments.shift
495
+ if @markup == 'tomdoc'
496
+ comment = Comment.new(text, @top_level, :ruby)
497
+ comment.format = 'tomdoc'
498
+ parse_comment_tomdoc(@container, comment, line_no, start_line)
499
+ @preprocess.run_post_processes(comment, @container)
500
+ elsif (comment_text, directives = parse_comment_text_to_directives(text, start_line))
501
+ handle_standalone_consecutive_comment_directive(comment_text, directives, text.start_with?(/#\#$/), line_no, start_line)
1858
502
  end
503
+ end
504
+ end
1859
505
 
1860
- keep_comment = true
1861
- else
1862
- non_comment_seen = true
506
+ # Skips all undocumentable consecutive comments until the given line number.
507
+ # Undocumentable comments are comments written inside `def` or inside undocumentable class/module
508
+
509
+ def skip_comments_until(line_no_until)
510
+ while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until
511
+ @unprocessed_comments.shift
1863
512
  end
513
+ end
1864
514
 
1865
- unget_tk tk
1866
- keep_comment = true
1867
- container.current_line_visibility = nil
515
+ # Returns consecutive comment linked to the given line number
1868
516
 
1869
- when :on_kw then
1870
- case tk[:text]
1871
- when 'class' then
1872
- parse_class container, single, tk, comment
517
+ def consecutive_comment(line_no)
518
+ return unless @unprocessed_comments.first&.first == line_no
519
+ _line_no, start_line, text = @unprocessed_comments.shift
520
+ parse_comment_text_to_directives(text, start_line)
521
+ end
1873
522
 
1874
- when 'module' then
1875
- parse_module container, single, tk, comment
523
+ # Parses comment text and returns +[RDoc::Comment, directives, type_signature_lines]+,
524
+ # or +nil+ if the comment is a section header (which has no associated code
525
+ # object).
526
+
527
+ def parse_comment_text_to_directives(comment_text, start_line) # :nodoc:
528
+ type_signature_lines = extract_type_signature!(comment_text, start_line)
529
+ comment_text, directives = @preprocess.parse_comment(comment_text, start_line, :ruby)
530
+ comment = Comment.new(comment_text, @top_level, :ruby)
531
+ comment.normalized = true
532
+ comment.line = start_line
533
+ markup, = directives['markup']
534
+ comment.format = markup&.downcase || @markup
535
+ if (section, directive_line = directives['section'])
536
+ # If comment has :section:, it is not a documentable comment for a code object
537
+ comment.text = extract_section_comment(comment_text, directive_line - start_line)
538
+ @container.set_current_section(section, comment)
539
+ return
540
+ end
541
+ @preprocess.run_post_processes(comment, @container)
542
+ [comment, directives, type_signature_lines]
543
+ end
1876
544
 
1877
- when 'def' then
1878
- parse_method container, single, tk, comment
545
+ # Extracts the comment for this section from the normalized comment block.
546
+ # Removes all lines before the line that contains :section:
547
+ # If the comment also ends with the same content, remove it as well
1879
548
 
1880
- when 'alias' then
1881
- parse_alias container, single, tk, comment unless current_method
549
+ def extract_section_comment(comment_text, prefix_line_count) # :nodoc:
550
+ prefix = comment_text.lines[0...prefix_line_count].join
551
+ comment_text.delete_prefix!(prefix)
552
+ # Comment is already normalized and doesn't end with a newline
553
+ comment_text.delete_suffix!(prefix.chomp)
554
+ comment_text
555
+ end
1882
556
 
1883
- when 'yield' then
1884
- if current_method.nil? then
1885
- warn "Warning: yield outside of method" if container.document_self
557
+ # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar`
558
+
559
+ def change_method_visibility(names, visibility, singleton: @singleton)
560
+ new_methods = []
561
+ @container.methods_matching(names, singleton) do |m|
562
+ if m.parent != @container
563
+ # A copy of an ancestor's method must not be documented
564
+ # in a :stopdoc:/:enddoc: region
565
+ next if document_suppressed?
566
+ m = m.dup
567
+ record_location(m)
568
+ new_methods << m
1886
569
  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
570
+ m.visibility = visibility
1894
571
  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
572
+ end
573
+ new_methods.each do |method|
574
+ method.visibility = visibility
575
+ case method
576
+ when AnyMethod
577
+ @container.add_method(method)
578
+ when Attr
579
+ @container.add_attribute(method)
1909
580
  end
581
+ end
582
+ end
1910
583
 
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
584
+ # Handles `module_function :foo, :bar`
1921
585
 
1922
- parse_comment container, tk, comment unless comment.empty?
586
+ def change_method_to_module_function(names)
587
+ @container.set_visibility_for(names, :private, false)
588
+ # In a :stopdoc:/:enddoc: region, the visibility of instance methods still
589
+ # changes but the singleton method copies must not be documented
590
+ return if document_suppressed?
1923
591
 
1924
- return
1925
- end
592
+ new_methods = []
593
+ @container.methods_matching(names) do |m|
594
+ s_m = m.dup
595
+ record_location(s_m)
596
+ s_m.singleton = true
597
+ new_methods << s_m
1926
598
  end
1927
-
1928
- when :on_const then
1929
- unless parse_constant container, tk, comment, current_method then
1930
- try_parse_comment = true
599
+ new_methods.each do |method|
600
+ method.visibility = :public
601
+ case method
602
+ when AnyMethod
603
+ @container.add_method(method)
604
+ when Attr
605
+ @container.add_attribute(method)
606
+ end
1931
607
  end
608
+ end
1932
609
 
1933
- when :on_ident then
1934
- if nest == 1 and current_method.nil? then
1935
- keep_comment = parse_identifier container, single, tk, comment
610
+ def handle_code_object_directives(code_object, directives) # :nodoc:
611
+ directives.each do |directive, (param)|
612
+ # startdoc/stopdoc/enddoc are handled by apply_document_control_directive.
613
+ # They control the lexical scope of the parser, not the code object.
614
+ next if directive == 'startdoc' || directive == 'stopdoc' || directive == 'enddoc'
615
+ @preprocess.handle_directive('', directive, param, code_object)
1936
616
  end
617
+ end
1937
618
 
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
619
+ # Handles `alias foo bar` and `alias_method :foo, :bar`
620
+
621
+ def add_alias_method(old_name, new_name, line_no)
622
+ comment, directives = consecutive_comment(line_no)
623
+ apply_document_control_directive(directives) if directives
624
+ handle_code_object_directives(@container, directives) if directives
625
+ return if document_suppressed?
626
+
627
+ a = Alias.new(old_name, new_name, comment, singleton: @singleton)
628
+ handle_modifier_directive(a, line_no)
629
+ a.store = @store
630
+ a.line = line_no
631
+ record_location(a)
632
+ if should_document?(a)
633
+ mark_container_documentable(@container)
634
+ @container.add_alias(a)
1947
635
  end
1948
-
1949
- else
1950
- try_parse_comment = nest == 1
1951
636
  end
1952
637
 
1953
- if try_parse_comment then
1954
- non_comment_seen = parse_comment container, tk, comment unless
1955
- comment.empty?
1956
-
1957
- keep_comment = false
638
+ # Handles `attr :a, :b`, `attr_reader :a, :b`, `attr_writer :a, :b` and `attr_accessor :a, :b`
639
+
640
+ def add_attributes(names, rw, line_no)
641
+ comment, directives, type_signature_lines = consecutive_comment(line_no)
642
+ apply_document_control_directive(directives) if directives
643
+ handle_code_object_directives(@container, directives) if directives
644
+ return if document_suppressed?
645
+ return unless @container.document_children
646
+
647
+ names.each do |symbol|
648
+ a = Attr.new(symbol.to_s, rw, comment, singleton: @singleton)
649
+ a.store = @store
650
+ a.line = line_no
651
+ a.type_signature_lines = type_signature_lines
652
+ a.visibility = visibility
653
+ record_location(a)
654
+ handle_modifier_directive(a, line_no)
655
+ if should_document?(a)
656
+ @container.add_attribute(a)
657
+ mark_container_documentable(@container)
658
+ end
659
+ end
1958
660
  end
1959
661
 
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
662
+ # Adds includes/extends. Module name is resolved to full before adding.
663
+
664
+ def add_includes_extends(names, rdoc_class, line_no) # :nodoc:
665
+ comment, directives = consecutive_comment(line_no)
666
+ apply_document_control_directive(directives) if directives
667
+ handle_code_object_directives(@container, directives) if directives
668
+ return if document_suppressed?
669
+
670
+ mark_container_documentable(@container)
671
+ names.each do |name|
672
+ resolved_name = resolve_constant_path(name)
673
+ ie = @container.add(rdoc_class, resolved_name || name, '')
674
+ ie.store = @store
675
+ ie.line = line_no
676
+ ie.comment = comment
677
+ record_location(ie)
678
+ end
1965
679
  end
1966
680
 
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
681
+ # Handle `include Foo, Bar`
1979
682
 
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+.
683
+ def add_includes(names, line_no) # :nodoc:
684
+ add_includes_extends(names, Include, line_no)
685
+ end
1991
686
 
1992
- def parse_symbol_arg_paren(no) # :nodoc:
1993
- args = []
687
+ # Handle `extend Foo, Bar`
1994
688
 
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
689
+ def add_extends(names, line_no) # :nodoc:
690
+ add_includes_extends(names, Extend, line_no)
2000
691
  end
2001
692
 
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
693
+ # Adds a method defined by `def` syntax
694
+
695
+ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, node_id:, start_line:, args_end_line:, end_line:)
696
+ comment, directives, type_signature_lines = consecutive_comment(start_line)
697
+ apply_document_control_directive(directives) if directives
698
+ handle_code_object_directives(@container, directives) if directives
699
+ # Resolve receiver after applying directives so that a namespace created
700
+ # here is marked as ignored when the comment starts a :stopdoc: region
701
+ receiver = receiver_name ? find_or_create_lexical_module_path(receiver_name, receiver_fallback_type) : @container
702
+
703
+ internal_add_method(
704
+ method_name,
705
+ receiver,
706
+ comment: comment,
707
+ directives: directives,
708
+ modifier_comment_lines: [start_line, args_end_line, end_line].uniq,
709
+ line_no: start_line,
710
+ visibility: visibility,
711
+ singleton: singleton,
712
+ params: params,
713
+ calls_super: calls_super,
714
+ block_params: block_params,
715
+ node_id: node_id,
716
+ type_signature_lines: type_signature_lines
717
+ )
2010
718
  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
719
 
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
720
+ private def internal_add_method(method_name, container, comment:, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, node_id:, type_signature_lines: nil) # :nodoc:
721
+ meth = AnyMethod.new(method_name, singleton: singleton)
722
+ meth.comment = comment
723
+ handle_code_object_directives(meth, directives) if directives
724
+ modifier_comment_lines&.each do |line|
725
+ handle_modifier_directive(meth, line)
726
+ end
727
+ return if document_suppressed?
728
+ return unless should_document?(meth)
2028
729
 
2029
- loop do
2030
- skip_tkspace_without_nl
730
+ mark_container_documentable(container)
2031
731
 
2032
- tk1 = get_tk
2033
- if tk1.nil? || :on_comma != tk1[:kind] then
2034
- unget_tk tk1
2035
- break
732
+ if directives && (call_seq, = directives['call-seq'])
733
+ meth.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") if call_seq
2036
734
  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
735
+ meth.name ||= meth.call_seq[/\A[^()\s]+/] if meth.call_seq
736
+ meth.name ||= 'unknown'
737
+ meth.store = @store
738
+ meth.line = line_no
739
+ meth.visibility = visibility
740
+ meth.params ||= params || '()'
741
+ meth.calls_super = calls_super
742
+ meth.block_params ||= block_params if block_params
743
+ meth.type_signature_lines = type_signature_lines
744
+ # An instance method `initialize` is documented as `::new` unless the
745
+ # :notnew: directive is given
746
+ if method_name == 'initialize' && !singleton
747
+ if meth.dont_rename_initialize
748
+ meth.visibility = :protected
749
+ else
750
+ meth.name = 'new'
751
+ meth.singleton = true
752
+ meth.visibility = :public
753
+ end
2042
754
  end
2043
- end
2044
-
2045
- args
2046
- end
2047
755
 
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+
756
+ record_location(meth)
757
+ container.add_method(meth)
758
+ token_stream_loader = @colorizer_context.token_stream_loader(node_id) if node_id
759
+ meth.start_collecting_tokens(:ruby, loader: token_stream_loader)
760
+ end
761
+
762
+ # Find or create module or class from a given module name using Ruby lexical
763
+ # nesting. If module or class does not exist, creates a module or a class
764
+ # according to `create_mode` argument.
765
+
766
+ def find_or_create_lexical_module_path(module_name, create_mode)
767
+ root_name, *path, name = module_name.split('::')
768
+ add_module = ->(mod, name, mode) {
769
+ created =
770
+ case mode
771
+ when :class
772
+ mod.add_class(NormalClass, name, 'Object').tap { |m| m.store = @store }
773
+ when :module
774
+ mod.add_module(NormalModule, name).tap { |m| m.store = @store }
775
+ end
776
+ # add_class/add_module may return an existing object created by another
777
+ # file (in_files is not empty then), which must not be ignored here.
778
+ # Documentable again when reopened or receiving contents outside the region.
779
+ created.ignore if document_suppressed? && created.in_files.empty?
780
+ created
781
+ }
782
+ if root_name.empty?
783
+ mod = @top_level
784
+ else
785
+ @module_nesting.reverse_each do |nesting, singleton|
786
+ next if singleton
787
+ mod = nesting.get_module_named(root_name)
788
+ break if mod
789
+ # If a constant is found and it is not a module or class, RDoc can't document about it.
790
+ # Return an anonymous module to avoid wrong document creation.
791
+ return NormalModule.new(nil) if nesting.find_constant_named(root_name)
792
+ end
793
+ last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton }
794
+ return mod || add_module.call(last_nesting, root_name, create_mode) unless name
795
+ mod ||= add_module.call(last_nesting, root_name, :module)
796
+ end
797
+ path.each do |name|
798
+ mod = mod.get_module_named(name) || add_module.call(mod, name, :module)
799
+ end
800
+ mod.get_module_named(name) || add_module.call(mod, name, create_mode)
801
+ end
2067
802
 
2068
- def parse_top_level_statements(container)
2069
- comment = collect_first_comment
803
+ # Resolves constant path to a full path by searching module nesting
2070
804
 
2071
- look_for_directives_in container, comment
805
+ def resolve_constant_path(constant_path)
806
+ owner_name, path = constant_path.split('::', 2)
807
+ return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar
808
+ mod = nil
809
+ @module_nesting.reverse_each do |nesting, singleton|
810
+ next if singleton
811
+ mod = nesting.get_module_named(owner_name)
812
+ break if mod
813
+ end
814
+ mod ||= @top_level.get_module_named(owner_name)
815
+ [mod.full_name, path].compact.join('::') if mod
816
+ end
2072
817
 
2073
- throw :eof if container.done_documenting
818
+ # Returns a pair of owner module and constant name from a given constant path
819
+ # using Ruby lexical nesting. Creates owner module if it does not exist.
820
+
821
+ def find_or_create_lexical_constant_owner_name(constant_path)
822
+ const_path, colon, name = constant_path.rpartition('::')
823
+ if colon.empty? # class Foo
824
+ # Within `class C` or `module C`, owner is C(== current container)
825
+ # Within `class <<C`, owner is C.singleton_class
826
+ # but RDoc don't track constants of a singleton class of module
827
+ [(@singleton ? nil : @container), name]
828
+ elsif const_path.empty? # class ::Foo
829
+ [@top_level, name]
830
+ else # `class Foo::Bar` or `class ::Foo::Bar`
831
+ [find_or_create_lexical_module_path(const_path, :module), name]
832
+ end
833
+ end
2074
834
 
2075
- @markup = comment.format
835
+ # Adds a constant
836
+
837
+ def add_constant(constant_name, rhs_name, start_line, end_line, alias_path: nil)
838
+ comment, directives = consecutive_comment(start_line)
839
+ apply_document_control_directive(directives) if directives
840
+ handle_code_object_directives(@container, directives) if directives
841
+ return if document_suppressed?
842
+
843
+ owner, name = find_or_create_lexical_constant_owner_name(constant_name)
844
+ return unless owner
845
+
846
+ constant = Constant.new(name, rhs_name, comment)
847
+ constant.store = @store
848
+ constant.line = start_line
849
+ constant.is_alias_for_path = alias_path
850
+ handle_modifier_directive(constant, start_line)
851
+ handle_modifier_directive(constant, end_line)
852
+ # A constant marked :nodoc: must not make an ignored owner documentable
853
+ mark_container_documentable(owner) if constant.document_self && owner.is_a?(ClassModule)
854
+ record_location(constant)
855
+ owner.add_constant(constant)
856
+ return unless alias_path
857
+ mod =
858
+ if alias_path.start_with?('::')
859
+ @store.find_class_or_module(alias_path)
860
+ else
861
+ full_name = resolve_constant_path(alias_path)
862
+ @store.find_class_or_module(full_name)
863
+ end
864
+ if mod && constant.document_self
865
+ a = owner.add_module_alias(mod, constant, @top_level)
866
+ a.store = @store
867
+ a.line = start_line
868
+ record_location(a)
869
+ end
870
+ end
2076
871
 
2077
- # HACK move if to RDoc::Context#comment=
2078
- container.comment = comment if container.document_self unless comment.empty?
872
+ # Adds module or class
873
+
874
+ def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil)
875
+ comment, directives = consecutive_comment(start_line)
876
+ apply_document_control_directive(directives) if directives
877
+ handle_code_object_directives(@container, directives) if directives
878
+ return unless @container.document_children
879
+
880
+ owner, name = find_or_create_lexical_constant_owner_name(module_name)
881
+ return unless owner
882
+
883
+ if is_class
884
+ # RDoc::NormalClass resolves superclass name despite of the lack of module nesting information.
885
+ # We need to fix it when RDoc::NormalClass resolved to a wrong constant name
886
+ if superclass_name
887
+ superclass_full_path = resolve_constant_path(superclass_name)
888
+ superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path
889
+ superclass_full_path ||= superclass_name
890
+ superclass_full_path = superclass_full_path.sub(/^::/, '')
891
+ end
892
+ # add_class should be done after resolving superclass
893
+ mod = owner.classes_hash[name]
894
+ unless mod
895
+ # add_class may return an existing class created by another file
896
+ # (in_files is not empty then), which must not be ignored here
897
+ mod = owner.add_class(NormalClass, name, superclass_name || superclass_expr || '::Object')
898
+ mod.ignore if document_suppressed? && mod.in_files.empty?
899
+ end
2079
900
 
2080
- parse_statements container, NORMAL, nil, comment
2081
- end
901
+ # Superclass with the same full path and superclass for BasicObject are not allowed
902
+ if superclass_name && mod.full_name != superclass_full_path && mod.full_name != 'BasicObject'
903
+ if superclass
904
+ mod.superclass = superclass
905
+ elsif mod.superclass.nil? || (mod.superclass.is_a?(String) || mod.superclass.name == 'Object') && mod.superclass != superclass_full_path
906
+ mod.superclass = superclass_full_path
907
+ end
908
+ end
909
+ else
910
+ mod = owner.modules_hash[name]
911
+ unless mod
912
+ mod = owner.add_module(NormalModule, name)
913
+ mod.ignore if document_suppressed? && mod.in_files.empty?
914
+ end
915
+ end
2082
916
 
2083
- ##
2084
- # Determines the visibility in +container+ from +tk+
917
+ mod.store = @store
918
+ mod.line = start_line
919
+ handle_modifier_directive(mod, start_line)
920
+ handle_modifier_directive(mod, end_line)
921
+ unless document_suppressed?
922
+ # In a :stopdoc:/:enddoc: region, the container is still created as a
923
+ # namespace but is not recorded to this file nor documented.
924
+ # The body is also visited: an inner :startdoc: re-enables documentation
925
+ # in a :stopdoc: region (not in an :enddoc: region), and nested
926
+ # namespaces need to be created for later promotion from other files
927
+ if mod.ignored?
928
+ # Promotes the owner chain too, unless mod received :nodoc:
929
+ mark_container_documentable(mod)
930
+ else
931
+ # A class/module marked :nodoc: must not make an ignored owner documentable
932
+ mark_container_documentable(owner) if mod.document_self && owner.is_a?(ClassModule)
933
+ record_location(mod)
934
+ end
935
+ mod.add_comment(comment, @top_level) if comment
936
+ end
937
+ mod
938
+ end
2085
939
 
2086
- def parse_visibility(container, single, tk)
2087
- vis_type, vis, singleton = get_visibility_information tk, single
940
+ private
2088
941
 
2089
- skip_tkspace_comment false
942
+ # Extracts RBS type signature lines (#: ...) from raw comment text.
943
+ # Mutates the input text to remove the extracted lines.
944
+ # Returns an array of extracted type signature lines, or nil if none are
945
+ # found. The array may contain multiple lines for overloaded signatures.
2090
946
 
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
947
+ def extract_type_signature!(text, start_line)
948
+ return nil unless text.include?('#:')
2105
949
 
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
950
+ lines = text.lines
951
+ sig_lines, doc_lines = lines.partition { |l| l.match?(RBS_SIG_LINE) }
952
+ return nil if sig_lines.empty?
2121
953
 
2122
- ##
2123
- # Determines the block parameter for +context+
954
+ first_sig_line = start_line + lines.index(sig_lines.first)
955
+ text.replace(doc_lines.join)
956
+ type_signature_lines = sig_lines.map { |l| l.sub(RBS_SIG_LINE, '').strip }.reject(&:empty?)
957
+ return nil if type_signature_lines.empty?
2124
958
 
2125
- def parse_yield(context, single, tk, method)
2126
- return if method.block_params
959
+ warn_invalid_type_signature(type_signature_lines, first_sig_line)
960
+ type_signature_lines
961
+ end
2127
962
 
2128
- get_tkread
2129
- method.block_params = parse_method_or_yield_parameters
2130
- end
963
+ def warn_invalid_type_signature(type_signature_lines, line_no)
964
+ type_signature_lines.each_with_index do |line, i|
965
+ next if RbsHelper.valid_method_type?(line)
966
+ next if RbsHelper.valid_type?(line)
967
+ @options.warn "#{@top_level.relative_name}:#{line_no + i}: invalid RBS type signature: #{line.inspect}"
968
+ end
969
+ end
2131
970
 
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.
971
+ class RDocVisitor < Prism::Visitor # :nodoc:
972
+ def initialize(scanner, top_level, store)
973
+ @scanner = scanner
974
+ @top_level = top_level
975
+ @store = store
976
+ end
2145
977
 
2146
- def read_directive(allowed)
2147
- tokens = []
978
+ def visit_if_node(node)
979
+ if node.end_keyword
980
+ super
981
+ else
982
+ # Visit with the order in text representation to handle this method comment
983
+ # # comment
984
+ # def f
985
+ # end if call_node
986
+ node.statements.accept(self)
987
+ node.predicate.accept(self)
988
+ end
989
+ end
990
+ alias visit_unless_node visit_if_node
991
+
992
+ def visit_call_node(node)
993
+ @scanner.process_comments_until(node.location.start_line - 1)
994
+ if node.receiver.nil?
995
+ case node.name
996
+ when :attr
997
+ _visit_call_attr_reader_writer_accessor(node, 'R')
998
+ when :attr_reader
999
+ _visit_call_attr_reader_writer_accessor(node, 'R')
1000
+ when :attr_writer
1001
+ _visit_call_attr_reader_writer_accessor(node, 'W')
1002
+ when :attr_accessor
1003
+ _visit_call_attr_reader_writer_accessor(node, 'RW')
1004
+ when :include
1005
+ _visit_call_include(node)
1006
+ when :extend
1007
+ _visit_call_extend(node)
1008
+ when :public
1009
+ super
1010
+ _visit_call_public_private_protected(node, :public)
1011
+ when :private
1012
+ super
1013
+ _visit_call_public_private_protected(node, :private)
1014
+ when :protected
1015
+ super
1016
+ _visit_call_public_private_protected(node, :protected)
1017
+ when :private_constant
1018
+ _visit_call_private_constant(node)
1019
+ when :public_constant
1020
+ _visit_call_public_constant(node)
1021
+ when :require
1022
+ _visit_call_require(node)
1023
+ when :alias_method
1024
+ _visit_call_alias_method(node)
1025
+ when :module_function
1026
+ super
1027
+ _visit_call_module_function(node)
1028
+ when :public_class_method
1029
+ super
1030
+ _visit_call_public_private_class_method(node, :public)
1031
+ when :private_class_method
1032
+ super
1033
+ _visit_call_public_private_class_method(node, :private)
1034
+ else
1035
+ super
1036
+ end
1037
+ else
1038
+ super
1039
+ end
1040
+ end
2148
1041
 
2149
- while tk = get_tk do
2150
- tokens << tk
1042
+ def visit_block_node(node)
1043
+ @scanner.with_in_proc_block do
1044
+ # include, extend and method definition inside block are not documentable.
1045
+ # visibility methods and attribute definition methods should be ignored inside block.
1046
+ super
1047
+ end
1048
+ end
2151
1049
 
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*(.*)/
1050
+ def visit_alias_method_node(node)
1051
+ return if @scanner.in_proc_block
1052
+ @scanner.process_comments_until(node.location.start_line - 1)
1053
+ return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode)
1054
+ @scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line)
1055
+ end
2156
1056
 
2157
- directive = $1.downcase
1057
+ def visit_module_node(node)
1058
+ node.constant_path.accept(self)
1059
+ @scanner.process_comments_until(node.location.start_line - 1)
1060
+ module_name = constant_path_string(node.constant_path)
1061
+ mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name
1062
+ if mod
1063
+ @scanner.with_container(mod) do
1064
+ node.body&.accept(self)
1065
+ @scanner.process_comments_until(node.location.end_line)
1066
+ end
1067
+ else
1068
+ @scanner.skip_comments_until(node.location.end_line)
1069
+ end
1070
+ end
2158
1071
 
2159
- return [directive, $2] if allowed.include? directive
1072
+ def visit_class_node(node)
1073
+ node.constant_path.accept(self)
1074
+ node.superclass&.accept(self)
1075
+ @scanner.process_comments_until(node.location.start_line - 1)
1076
+ superclass_name = constant_path_string(node.superclass) if node.superclass
1077
+ superclass_expr = node.superclass.slice if node.superclass && !superclass_name
1078
+ class_name = constant_path_string(node.constant_path)
1079
+ 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
1080
+ if klass
1081
+ @scanner.with_container(klass) do
1082
+ node.body&.accept(self)
1083
+ @scanner.process_comments_until(node.location.end_line)
1084
+ end
1085
+ else
1086
+ @scanner.skip_comments_until(node.location.end_line)
1087
+ end
1088
+ end
2160
1089
 
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
1090
+ def visit_singleton_class_node(node)
1091
+ # A comment linked to the `class << ...` line (e.g. a document control
1092
+ # directive) belongs to the enclosing scope, not to the singleton scope
1093
+ @scanner.process_comments_until(node.location.start_line)
2171
1094
 
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
1095
+ if @scanner.has_modifier_nodoc?(node.location.start_line)
1096
+ # Skip visiting inside the singleton class. Also skips creation of node.expression as a module
1097
+ @scanner.skip_comments_until(node.location.end_line)
1098
+ return
1099
+ end
2177
1100
 
2178
- def read_documentation_modifiers(context, allowed)
2179
- skip_tkspace_without_nl
2180
- directive, value = read_directive allowed
1101
+ expression = node.expression
1102
+ expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1
1103
+
1104
+ case expression
1105
+ when Prism::ConstantWriteNode
1106
+ # Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module
1107
+ mod = @scanner.container.add_module(NormalModule, expression.name.to_s)
1108
+ mod.ignore if @scanner.document_suppressed? && mod.in_files.empty?
1109
+ when Prism::ConstantPathNode, Prism::ConstantReadNode
1110
+ expression_name = constant_path_string(expression)
1111
+ # If a constant_path does not exist, RDoc creates a module
1112
+ mod = @scanner.find_or_create_lexical_module_path(expression_name, :module) if expression_name
1113
+ when Prism::SelfNode
1114
+ mod = @scanner.container if @scanner.container != @top_level
1115
+ end
1116
+ expression.accept(self)
1117
+ if mod
1118
+ @scanner.with_container(mod, singleton: true) do
1119
+ node.body&.accept(self)
1120
+ @scanner.process_comments_until(node.location.end_line)
1121
+ end
1122
+ else
1123
+ @scanner.skip_comments_until(node.location.end_line)
1124
+ end
1125
+ end
2181
1126
 
2182
- return unless directive
1127
+ def visit_def_node(node)
1128
+ start_line = node.location.start_line
1129
+ args_end_line = node.parameters&.location&.end_line || start_line
1130
+ end_line = node.location.end_line
1131
+ @scanner.process_comments_until(start_line - 1)
1132
+
1133
+ return if @scanner.in_proc_block
1134
+
1135
+ case node.receiver
1136
+ when Prism::NilNode, Prism::TrueNode, Prism::FalseNode
1137
+ visibility = :public
1138
+ singleton = false
1139
+ receiver_name =
1140
+ case node.receiver
1141
+ when Prism::NilNode
1142
+ 'NilClass'
1143
+ when Prism::TrueNode
1144
+ 'TrueClass'
1145
+ when Prism::FalseNode
1146
+ 'FalseClass'
1147
+ end
1148
+ receiver_fallback_type = :class
1149
+ when Prism::SelfNode
1150
+ # singleton method of a singleton class is not documentable
1151
+ return if @scanner.singleton
1152
+ visibility = :public
1153
+ singleton = true
1154
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
1155
+ visibility = :public
1156
+ singleton = true
1157
+ receiver_name = constant_path_string(node.receiver)
1158
+ receiver_fallback_type = :module
1159
+ return unless receiver_name
1160
+ when nil
1161
+ visibility = @scanner.visibility
1162
+ singleton = @scanner.singleton
1163
+ else
1164
+ # `def (unknown expression).method_name` is not documentable
1165
+ return
1166
+ end
1167
+ name = node.name.to_s
1168
+ params, block_params, calls_super = MethodSignatureVisitor.scan_signature(node)
1169
+ @scanner.add_method(
1170
+ name,
1171
+ receiver_name: receiver_name,
1172
+ receiver_fallback_type: receiver_fallback_type,
1173
+ visibility: visibility,
1174
+ singleton: singleton,
1175
+ params: params,
1176
+ block_params: block_params,
1177
+ calls_super: calls_super,
1178
+ node_id: node.node_id,
1179
+ start_line: start_line,
1180
+ args_end_line: args_end_line,
1181
+ end_line: end_line
1182
+ )
1183
+ ensure
1184
+ @scanner.skip_comments_until(end_line)
1185
+ end
2183
1186
 
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
1187
+ def visit_constant_path_write_node(node)
1188
+ @scanner.process_comments_until(node.location.start_line - 1)
1189
+ path = constant_path_string(node.target)
1190
+ return unless path
1191
+
1192
+ alias_path = constant_path_string(node.value)
1193
+ @scanner.add_constant(
1194
+ path,
1195
+ alias_path || node.value.slice,
1196
+ node.location.start_line,
1197
+ node.location.end_line,
1198
+ alias_path: alias_path
1199
+ )
1200
+ @scanner.skip_comments_until(node.location.end_line)
1201
+ # Do not traverse rhs not to document `A::B = Struct.new{def undocumentable_method; end}`
1202
+ end
2187
1203
 
2188
- true
2189
- end
2190
- end
2191
- end
1204
+ def visit_constant_write_node(node)
1205
+ @scanner.process_comments_until(node.location.start_line - 1)
1206
+ alias_path = constant_path_string(node.value)
1207
+ @scanner.add_constant(
1208
+ node.name.to_s,
1209
+ alias_path || node.value.slice,
1210
+ node.location.start_line,
1211
+ node.location.end_line,
1212
+ alias_path: alias_path
1213
+ )
1214
+ @scanner.skip_comments_until(node.location.end_line)
1215
+ # Do not traverse rhs not to document `A = Struct.new{def undocumentable_method; end}`
1216
+ end
2192
1217
 
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.
1218
+ private
2196
1219
 
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
1220
+ def constant_arguments_names(call_node)
1221
+ return unless call_node.arguments
1222
+ names = call_node.arguments.arguments.map { |arg| constant_path_string(arg) }
1223
+ names.all? ? names : nil
1224
+ end
2202
1225
 
2203
- container.record_location @top_level
2204
- end
1226
+ def call_node_name_arguments(call_node)
1227
+ @scanner.call_node_name_arguments(call_node)
1228
+ end
2205
1229
 
2206
- ##
2207
- # Scans this Ruby file for Ruby constructs
1230
+ def symbol_arguments(call_node)
1231
+ arguments_node = call_node.arguments
1232
+ return unless arguments_node && arguments_node.arguments.all? { |arg| arg.is_a?(Prism::SymbolNode)}
1233
+ arguments_node.arguments.map { |arg| arg.value.to_sym }
1234
+ end
2208
1235
 
2209
- def scan
2210
- reset
1236
+ def visibility_method_arguments(call_node, singleton:)
1237
+ arguments_node = call_node.arguments
1238
+ return unless arguments_node
1239
+ names = call_node_name_arguments(call_node)
1240
+ if names
1241
+ # module_function :foo, "bar"
1242
+ return names
1243
+ else
1244
+ return unless arguments_node.arguments.size == 1
1245
+ arg = arguments_node.arguments.first
1246
+ return unless arg.is_a?(Prism::DefNode)
1247
+
1248
+ if singleton
1249
+ # `private_class_method def foo; end` `private_class_method def not_self.foo; end` should be ignored
1250
+ return unless arg.receiver.is_a?(Prism::SelfNode)
1251
+ else
1252
+ # `module_function def something.foo` should be ignored
1253
+ return if arg.receiver
1254
+ end
1255
+ # `module_function def foo; end` or `private_class_method def self.foo; end`
1256
+ [arg.name.to_s]
1257
+ end
1258
+ end
2211
1259
 
2212
- catch :eof do
2213
- begin
2214
- parse_top_level_statements @top_level
1260
+ def constant_path_string(node)
1261
+ case node
1262
+ when Prism::ConstantReadNode
1263
+ node.name.to_s
1264
+ when Prism::ConstantPathNode
1265
+ parent_name = node.parent ? constant_path_string(node.parent) : ''
1266
+ "#{parent_name}::#{node.name}" if parent_name
1267
+ end
1268
+ end
2215
1269
 
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
1270
+ def _visit_call_require(call_node)
1271
+ return if @scanner.document_suppressed?
1272
+ return unless call_node.arguments&.arguments&.size == 1
1273
+ arg = call_node.arguments.arguments.first
1274
+ return unless arg.is_a?(Prism::StringNode)
1275
+ @scanner.container.add_require(Require.new(arg.unescaped, nil))
2222
1276
  end
2223
1277
 
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]
1278
+ def _visit_call_module_function(call_node)
1279
+ return if @scanner.in_proc_block || @scanner.singleton
1280
+ names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s)
1281
+ @scanner.change_method_to_module_function(names) if names
2228
1282
  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
1283
 
2234
- $stderr.puts <<-EOF
1284
+ def _visit_call_public_private_class_method(call_node, visibility)
1285
+ return if @scanner.in_proc_block || @scanner.singleton
1286
+ names = visibility_method_arguments(call_node, singleton: true)
1287
+ @scanner.change_method_visibility(names, visibility, singleton: true) if names
1288
+ end
2235
1289
 
2236
- #{self.class} failure around line #{now_line_no} of
2237
- #{@file_name}
1290
+ def _visit_call_public_private_protected(call_node, visibility)
1291
+ return if @scanner.in_proc_block
1292
+ arguments_node = call_node.arguments
1293
+ if arguments_node.nil? # `public` `private`
1294
+ @scanner.visibility = visibility
1295
+ else # `public :foo, :bar`, `private def foo; end`
1296
+ names = visibility_method_arguments(call_node, singleton: false)
1297
+ @scanner.change_method_visibility(names, visibility) if names
1298
+ end
1299
+ end
2238
1300
 
2239
- EOF
1301
+ def _visit_call_alias_method(call_node)
1302
+ return if @scanner.in_proc_block
2240
1303
 
2241
- unless code.empty? then
2242
- $stderr.puts code
2243
- $stderr.puts
1304
+ new_name, old_name, *rest = symbol_arguments(call_node)
1305
+ return unless old_name && new_name && rest.empty?
1306
+ @scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line)
2244
1307
  end
2245
1308
 
2246
- raise e
2247
- end
2248
- end
1309
+ def _visit_call_include(call_node)
1310
+ return if @scanner.in_proc_block
2249
1311
 
2250
- @top_level
2251
- end
1312
+ names = constant_arguments_names(call_node)
1313
+ line_no = call_node.location.start_line
1314
+ return unless names
2252
1315
 
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
1316
+ if @scanner.singleton
1317
+ @scanner.add_extends(names, line_no)
1318
+ else
1319
+ @scanner.add_includes(names, line_no)
1320
+ end
2284
1321
  end
2285
- end
2286
- tk = get_tk
2287
- end
2288
1322
 
2289
- skip_tkspace_without_nl
1323
+ def _visit_call_extend(call_node)
1324
+ return if @scanner.in_proc_block
2290
1325
 
2291
- get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text]
2292
- end
1326
+ names = constant_arguments_names(call_node)
1327
+ @scanner.add_extends(names, call_node.location.start_line) if names && !@scanner.singleton
1328
+ end
2293
1329
 
2294
- ##
2295
- # skip the var [in] part of a 'for' statement
1330
+ def _visit_call_public_constant(call_node)
1331
+ return if @scanner.in_proc_block || @scanner.singleton
1332
+ names = call_node_name_arguments(call_node)
1333
+ @scanner.container.set_constant_visibility_for(names, :public) if names
1334
+ end
2296
1335
 
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
1336
+ def _visit_call_private_constant(call_node)
1337
+ return if @scanner.in_proc_block || @scanner.singleton
1338
+ names = call_node_name_arguments(call_node)
1339
+ @scanner.container.set_constant_visibility_for(names, :private) if names
1340
+ end
2304
1341
 
2305
- ##
2306
- # Skips the next method in +container+
1342
+ def _visit_call_attr_reader_writer_accessor(call_node, rw)
1343
+ return if @scanner.in_proc_block
1344
+ names = call_node_name_arguments(call_node)
1345
+ @scanner.add_attributes(names, rw, call_node.location.start_line) if names
1346
+ end
2307
1347
 
2308
- def skip_method(container)
2309
- meth = RDoc::AnyMethod.new "", "anon"
2310
- parse_method_parameters meth
2311
- parse_statements container, false, meth
2312
- end
1348
+ class MethodSignatureVisitor < Prism::Visitor # :nodoc:
1349
+ class << self
1350
+ def scan_signature(def_node)
1351
+ visitor = new
1352
+ def_node.body&.accept(visitor)
1353
+ params = "(#{def_node.parameters&.slice})"
1354
+ block_params = visitor.yields.first
1355
+ [params, block_params, visitor.calls_super]
1356
+ end
1357
+ end
2313
1358
 
2314
- ##
2315
- # Skip spaces until a comment is found
1359
+ attr_reader :params, :yields, :calls_super
2316
1360
 
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
1361
+ def initialize
1362
+ @params = nil
1363
+ @calls_super = false
1364
+ @yields = []
1365
+ end
2325
1366
 
2326
- ##
2327
- # Updates visibility in +container+ from +vis_type+ and +vis+.
1367
+ def visit_def_node(node)
1368
+ # stop traverse inside nested def
1369
+ end
2328
1370
 
2329
- def update_visibility(container, vis_type, vis, singleton) # :nodoc:
2330
- new_methods = []
1371
+ def visit_yield_node(node)
1372
+ @yields << (node.arguments&.slice || '')
1373
+ end
2331
1374
 
2332
- case vis_type
2333
- when 'module_function' then
2334
- args = parse_symbol_arg
2335
- container.set_visibility_for args, :private, false
1375
+ def visit_super_node(node)
1376
+ @calls_super = true
1377
+ super
1378
+ end
2336
1379
 
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
1380
+ def visit_forwarding_super_node(node)
1381
+ @calls_super = true
1382
+ end
2351
1383
  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
1384
  end
2367
- method.visibility = vis
2368
1385
  end
2369
1386
  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
1387
  end