rdoc 8.0.0 → 8.1.0

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