From de003237d91da6982d15d82df5022cabdfadc6cd Mon Sep 17 00:00:00 2001 From: tompng Date: Thu, 5 Feb 2026 03:47:37 +0900 Subject: [PATCH] Ignore visibility method, attr definition, module_function within block We need to ignore these within `Module.new do end` and any other block because it might be a metaprogramming block. --- lib/rdoc/parser/prism_ruby.rb | 27 ++++++--- test/rdoc/parser/prism_ruby_test.rb | 87 +++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 877b6045b2..f9a48c7086 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -16,7 +16,7 @@ class RDoc::Parser::PrismRuby < RDoc::Parser parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER'] attr_accessor :visibility - attr_reader :container, :singleton + attr_reader :container, :singleton, :in_proc_block def initialize(top_level, content, options, stats) super @@ -480,7 +480,6 @@ def add_attributes(names, rw, line_no) # Adds includes/extends. Module name is resolved to full before adding. def add_includes_extends(names, rdoc_class, line_no) # :nodoc: - return if @in_proc_block comment, directives = consecutive_comment(line_no) handle_code_object_directives(@container, directives) if directives names.each do |name| @@ -508,8 +507,6 @@ def add_extends(names, line_no) # :nodoc: # Adds a method defined by `def` syntax 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:) - return if @in_proc_block - receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container comment, directives = consecutive_comment(start_line) handle_code_object_directives(@container, directives) if directives @@ -775,7 +772,8 @@ def visit_call_node(node) def visit_block_node(node) @scanner.with_in_proc_block do - # include, extend and method definition inside block are not documentable + # include, extend and method definition inside block are not documentable. + # visibility methods and attribute definition methods should be ignored inside block. super end end @@ -859,6 +857,8 @@ def visit_def_node(node) end_line = node.location.end_line @scanner.process_comments_until(start_line - 1) + return if @scanner.in_proc_block + case node.receiver when Prism::NilNode, Prism::TrueNode, Prism::FalseNode visibility = :public @@ -997,19 +997,20 @@ def _visit_call_require(call_node) def _visit_call_module_function(call_node) yield - return if @scanner.singleton + return if @scanner.in_proc_block || @scanner.singleton names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s) @scanner.change_method_to_module_function(names) if names end def _visit_call_public_private_class_method(call_node, visibility) yield - return if @scanner.singleton + return if @scanner.in_proc_block || @scanner.singleton names = visibility_method_arguments(call_node, singleton: true) @scanner.change_method_visibility(names, visibility, singleton: true) if names end def _visit_call_public_private_protected(call_node, visibility) + return if @scanner.in_proc_block arguments_node = call_node.arguments if arguments_node.nil? # `public` `private` @scanner.visibility = visibility @@ -1021,12 +1022,16 @@ def _visit_call_public_private_protected(call_node, visibility) end def _visit_call_alias_method(call_node) + return if @scanner.in_proc_block + new_name, old_name, *rest = symbol_arguments(call_node) return unless old_name && new_name && rest.empty? @scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line) end def _visit_call_include(call_node) + return if @scanner.in_proc_block + names = constant_arguments_names(call_node) line_no = call_node.location.start_line return unless names @@ -1039,26 +1044,30 @@ def _visit_call_include(call_node) end def _visit_call_extend(call_node) + return if @scanner.in_proc_block + names = constant_arguments_names(call_node) @scanner.add_extends(names, call_node.location.start_line) if names && !@scanner.singleton end def _visit_call_public_constant(call_node) - return if @scanner.singleton + return if @scanner.in_proc_block || @scanner.singleton names = symbol_arguments(call_node) @scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names end def _visit_call_private_constant(call_node) - return if @scanner.singleton + return if @scanner.in_proc_block || @scanner.singleton names = symbol_arguments(call_node) @scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names end def _visit_call_attr_reader_writer_accessor(call_node, rw) + return if @scanner.in_proc_block names = symbol_arguments(call_node) @scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names end + class MethodSignatureVisitor < Prism::Visitor # :nodoc: class << self def scan_signature(def_node) diff --git a/test/rdoc/parser/prism_ruby_test.rb b/test/rdoc/parser/prism_ruby_test.rb index 26be342ecb..e3710a75b0 100644 --- a/test/rdoc/parser/prism_ruby_test.rb +++ b/test/rdoc/parser/prism_ruby_test.rb @@ -2032,6 +2032,93 @@ class B assert_equal ['N'], b.extends.map(&:name) end + def test_visibility_methods_suppressed_within_block + util_parser <<~RUBY + class A + def pub1; end + X = 1 + Y = 1 + def self.s_pub; end + private_class_method def self.s_pri; end + private_constant :Y + Module.new do + private_method :pub1 + private_constant :X + public_constant :Y + private + private_class_method :s_pub + public_class_method :s_pri + end + def pub2; end + private + Module.new do + public + end + def pri; end + end + RUBY + klass = @store.find_class_named 'A' + + assert_equal :public, klass.find_constant_named('X').visibility + assert_equal :private, klass.find_constant_named('Y').visibility + assert_equal :public, klass.find_method_named('pub1').visibility + assert_equal :private, klass.find_method_named('pri').visibility + assert_equal :public, klass.find_method_named('pub2').visibility + assert_equal :public, klass.find_class_method_named('s_pub').visibility + assert_equal :private, klass.find_class_method_named('s_pri').visibility unless accept_legacy_bug? + end + + def test_alias_method_suppressed_within_block + omit if accept_legacy_bug? + + util_parser <<~RUBY + class A + def foo; end + Module.new do + def bar; end + alias_method :bar2, :bar + end + alias_method :foo3, :foo + end + RUBY + klass = @store.find_class_named 'A' + assert_equal ['foo', 'foo3'], klass.method_list.map(&:name) + end + + def test_attr_method_suppressed_within_block + util_parser <<~RUBY + class A + attr_reader :r + attr_writer :w + attr_accessor :rw + Module.new do + attr_reader :r2 + attr_writer :w2 + attr_accessor :rw2 + end + alias_method :foo3, :foo + end + RUBY + klass = @store.find_class_named 'A' + assert_equal ['r', 'w', 'rw'], klass.attributes.map(&:name) + end + + def test_module_function_suppressed_within_block + util_parser <<~RUBY + module M + def foo; end + Module.new do + def foo; end + module_function :foo + end + def bar; end + module_function :bar + end + RUBY + mod = @store.find_module_named 'M' + assert_equal ['bar'], mod.class_method_list.map(&:name) + end + def test_multibyte_method_name content = <<~RUBY class Foo