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