diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 9ca1a56ec4..01614da032 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -31,10 +31,21 @@ def initialize(top_level, content, options, stats) @track_visibility = :nodoc != @options.visibility @encoding = @options.encoding - @module_nesting = [top_level] + @module_nesting = [[top_level, false]] @container = top_level @visibility = :public @singleton = false + @in_proc_block = false + end + + # Suppress `extend` and `include` within block + # because they might be a metaprogramming block + # example: `Module.new { include M }` `M.module_eval { include N }` + + def with_in_proc_block + @in_proc_block = true + yield + @in_proc_block = false end # Dive into another container @@ -43,22 +54,24 @@ def with_container(container, singleton: false) old_container = @container old_visibility = @visibility old_singleton = @singleton + old_in_proc_block = @in_proc_block @visibility = :public @container = container @singleton = singleton + @in_proc_block = false unless singleton - @module_nesting.push container - # Need to update module parent chain to emulate Module.nesting. # This mechanism is inaccurate and needs to be fixed. container.parent = old_container end + @module_nesting.push([container, singleton]) yield container ensure @container = old_container @visibility = old_visibility @singleton = old_singleton - @module_nesting.pop unless singleton + @in_proc_block = old_in_proc_block + @module_nesting.pop end # Records the location of this +container+ in the file for this parser and @@ -204,6 +217,10 @@ def parse_comment_tomdoc(container, comment, line_no, start_line) @stats.add_method meth end + def has_modifier_nodoc?(line_no) # :nodoc: + @modifier_comments[line_no]&.text&.match?(/\A#\s*:nodoc:/) + end + def handle_modifier_directive(code_object, line_no) # :nodoc: comment = @modifier_comments[line_no] @preprocess.handle(comment.text, code_object) if comment @@ -467,6 +484,7 @@ def add_attributes(names, rw, line_no) end def add_includes_extends(names, rdoc_class, line_no) # :nodoc: + return if @in_proc_block comment = consecutive_comment(line_no) handle_consecutive_comment_directive(@container, comment) names.each do |name| @@ -492,7 +510,9 @@ def add_extends(names, line_no) # :nodoc: # Adds a method defined by `def` syntax - def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, end_line:) + def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:) + return if @in_proc_block + receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container meth = RDoc::AnyMethod.new(nil, name) if (comment = consecutive_comment(start_line)) @@ -504,20 +524,10 @@ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singl meth.comment = comment end handle_modifier_directive(meth, start_line) + handle_modifier_directive(meth, args_end_line) handle_modifier_directive(meth, end_line) return unless should_document?(meth) - - if meth.name == 'initialize' && !singleton - if meth.dont_rename_initialize - visibility = :protected - else - meth.name = 'new' - singleton = true - visibility = :public - end - end - internal_add_method( receiver, meth, @@ -529,6 +539,18 @@ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singl block_params: block_params, tokens: tokens ) + + # Rename after add_method to register duplicated 'new' and 'initialize' + # defined in c and ruby just like the old parser did. + if meth.name == 'initialize' && !singleton + if meth.dont_rename_initialize + meth.visibility = :protected + else + meth.name = 'new' + meth.singleton = true + meth.visibility = :public + end + end end private def internal_add_method(container, meth, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:) # :nodoc: @@ -565,12 +587,17 @@ def find_or_create_module_path(module_name, create_mode) if root_name.empty? mod = @top_level else - @module_nesting.reverse_each do |nesting| + @module_nesting.reverse_each do |nesting, singleton| + next if singleton mod = nesting.find_module_named(root_name) break if mod + # If a constant is found and it is not a module or class, RDoc can't document about it. + # Return an anonymous module to avoid wrong document creation. + return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name) end - return mod || add_module.call(@top_level, root_name, create_mode) unless name - mod ||= add_module.call(@top_level, root_name, :module) + last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton } + return mod || add_module.call(last_nesting, root_name, create_mode) unless name + mod ||= add_module.call(last_nesting, root_name, :module) end path.each do |name| mod = mod.find_module_named(name) || add_module.call(mod, name, :module) @@ -584,7 +611,8 @@ def resolve_constant_path(constant_path) owner_name, path = constant_path.split('::', 2) return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar mod = nil - @module_nesting.reverse_each do |nesting| + @module_nesting.reverse_each do |nesting, singleton| + next if singleton mod = nesting.find_module_named(owner_name) break if mod end @@ -598,7 +626,10 @@ def resolve_constant_path(constant_path) def find_or_create_constant_owner_name(constant_path) const_path, colon, name = constant_path.rpartition('::') if colon.empty? # class Foo - [@container, name] + # Within `class C` or `module C`, owner is C(== current container) + # Within `class <