rdoc 6.3.4.1 → 8.0.0

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