From 7b3331b097899d38b458985453599b583e438eb1 Mon Sep 17 00:00:00 2001 From: tompng Date: Fri, 31 Jan 2025 00:22:04 +0900 Subject: [PATCH 01/10] Reduce the difference between RDoc::Parser::PrismRuby and RDoc::Parser::Ruby --- lib/rdoc/parser/prism_ruby.rb | 40 ++++++++++++++--- test/rdoc/test_rdoc_parser_prism_ruby.rb | 56 +++++++++++++++++++++--- 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 9ca1a56ec4..baa7265898 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -204,6 +204,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 @@ -568,9 +572,12 @@ def find_or_create_module_path(module_name, create_mode) @module_nesting.reverse_each do |nesting| 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) + return mod || add_module.call(@module_nesting.last, root_name, create_mode) unless name + mod ||= add_module.call(@module_nesting.last, root_name, :module) end path.each do |name| mod = mod.find_module_named(name) || add_module.call(mod, name, :module) @@ -635,7 +642,7 @@ def add_constant(constant_name, rhs_name, start_line, end_line) # Adds module or class - def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil) + def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil) comment = consecutive_comment(start_line) handle_consecutive_comment_directive(@container, comment) return unless @container.document_children @@ -650,7 +657,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe superclass_full_path ||= superclass_name end # add_class should be done after resolving superclass - mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || '::Object') + mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object') if superclass_name if superclass mod.superclass = superclass @@ -678,6 +685,20 @@ def initialize(scanner, top_level, store) @store = store end + def visit_if_node(node) + if node.end_keyword + super + else + # Visit with the order in text representation to handle this method comment + # # comment + # def f + # end if call_node + node.statements.accept(self) + node.predicate.accept(self) + end + end + alias visit_unless_node visit_if_node + def visit_call_node(node) @scanner.process_comments_until(node.location.start_line - 1) if node.receiver.nil? @@ -745,8 +766,9 @@ def visit_module_node(node) def visit_class_node(node) @scanner.process_comments_until(node.location.start_line - 1) superclass_name = constant_path_string(node.superclass) if node.superclass + superclass_expr = node.superclass.slice if node.superclass && !superclass_name class_name = constant_path_string(node.constant_path) - klass = @scanner.add_module_or_class(class_name, node.location.start_line, node.location.end_line, is_class: true, superclass_name: superclass_name) if class_name + 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 if klass @scanner.with_container(klass) do super @@ -760,6 +782,12 @@ def visit_class_node(node) def visit_singleton_class_node(node) @scanner.process_comments_until(node.location.start_line - 1) + if @scanner.has_modifier_nodoc?(node.location.start_line) + # Skip visiting inside the singleton class. Also skips creation of node.expression as a module + @scanner.skip_comments_until(node.location.end_line) + return + end + expression = node.expression expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1 @@ -944,7 +972,7 @@ def _visit_call_public_private_protected(call_node, visibility) @scanner.visibility = visibility else # `public :foo, :bar`, `private def foo; end` yield - names = visibility_method_arguments(call_node, singleton: @scanner.singleton) + names = visibility_method_arguments(call_node, singleton: false) @scanner.change_method_visibility(names, visibility) if names end end diff --git a/test/rdoc/test_rdoc_parser_prism_ruby.rb b/test/rdoc/test_rdoc_parser_prism_ruby.rb index 8889b91cc5..1b06b61fe7 100644 --- a/test/rdoc/test_rdoc_parser_prism_ruby.rb +++ b/test/rdoc/test_rdoc_parser_prism_ruby.rb @@ -281,7 +281,7 @@ class Baz < (any expression) assert_equal ['Foo', 'Bar', 'Baz'], @top_level.classes.map(&:full_name) foo, bar, baz = @top_level.classes assert_equal foo, bar.superclass - assert_equal 'Object', baz.superclass unless accept_legacy_bug? + assert_equal '(any expression)', baz.superclass end def test_class_new_notnew @@ -534,6 +534,27 @@ def three x; end assert_equal @top_level, three.file end + def test_method_with_modifier_if_unless + util_parser <<~RUBY + class Foo + # my method one + def one + end if foo + + # my method two + def two + end unless foo + end + RUBY + + klass = @store.find_class_named 'Foo' + one, two = klass.method_list + assert_equal 'Foo#one', one.full_name + assert_equal 'my method one', one.comment.text.strip + assert_equal 'Foo#two', two.full_name + assert_equal 'my method two', two.comment.text.strip + end + def test_method_toplevel util_parser <<~RUBY # comment @@ -769,14 +790,22 @@ def baz; end def test_undefined_singleton_class_defines_module util_parser <<~RUBY - class << Foo - end - class << ::Bar + module A + class << Foo + end + class << ::Bar + end + Baz1 = '' + class << Baz1 + end + class << Baz2 # :nodoc: + end end RUBY modules = @store.all_modules - assert_equal ['Foo', 'Bar'], modules.map(&:name) + modules = modules.take(3) if accept_legacy_bug? + assert_equal ['A', 'A::Foo', 'Bar'], modules.map(&:full_name) end def test_singleton_class @@ -1039,6 +1068,23 @@ def self.m2; end assert_equal [:public] * 4, klass.method_list.map(&:visibility) end + def test_singleton_class_def_with_visibility + util_parser <<~RUBY + class A + class < Date: Fri, 31 Jan 2025 04:20:03 +0900 Subject: [PATCH 02/10] Suppress include and extend inside method block --- lib/rdoc/parser/prism_ruby.rb | 24 ++++++++++++++++++++ test/rdoc/test_rdoc_parser_prism_ruby.rb | 29 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index baa7265898..198d9b29b0 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -35,6 +35,17 @@ def initialize(top_level, content, options, stats) @container = top_level @visibility = :public @singleton = false + @include_extend_suppressed = 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 suppress_include_extend + @include_extend_suppressed = true + yield + @include_extend_suppressed = false end # Dive into another container @@ -43,9 +54,11 @@ def with_container(container, singleton: false) old_container = @container old_visibility = @visibility old_singleton = @singleton + old_include_extend_suppressed = @include_extend_suppressed @visibility = :public @container = container @singleton = singleton + @include_extend_suppressed = false unless singleton @module_nesting.push container @@ -58,6 +71,7 @@ def with_container(container, singleton: false) @container = old_container @visibility = old_visibility @singleton = old_singleton + @include_extend_suppressed = old_include_extend_suppressed @module_nesting.pop unless singleton end @@ -471,6 +485,7 @@ def add_attributes(names, rw, line_no) end def add_includes_extends(names, rdoc_class, line_no) # :nodoc: + return if @include_extend_suppressed comment = consecutive_comment(line_no) handle_consecutive_comment_directive(@container, comment) names.each do |name| @@ -736,6 +751,7 @@ def visit_call_node(node) when :private_class_method _visit_call_public_private_class_method(node, :private) { super } else + node.arguments&.accept(self) super end else @@ -743,6 +759,14 @@ def visit_call_node(node) end end + def visit_block_node(node) + @scanner.suppress_include_extend do + # include and extend inside block are not documentable + # method definition might also not work but document it for now. + super + end + end + def visit_alias_method_node(node) @scanner.process_comments_until(node.location.start_line - 1) return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode) diff --git a/test/rdoc/test_rdoc_parser_prism_ruby.rb b/test/rdoc/test_rdoc_parser_prism_ruby.rb index 1b06b61fe7..b7a776ad69 100644 --- a/test/rdoc/test_rdoc_parser_prism_ruby.rb +++ b/test/rdoc/test_rdoc_parser_prism_ruby.rb @@ -1927,6 +1927,35 @@ def d; end assert_equal ['a', 'f'], mod.method_list.map(&:name) end + def test_include_extend_suppressed_within_block + util_parser <<~RUBY + module M; end + module N; end + module O: end + class A + metaprogramming do + include M + extend N + class B + include M + extend N + end + include M + extend N + end + include O + extend O + end + RUBY + a, b = @store.all_classes + unless accept_legacy_bug? + assert_equal ['O'], a.includes.map(&:name) + assert_equal ['O'], a.extends.map(&:name) + end + assert_equal ['M'], b.includes.map(&:name) + assert_equal ['N'], b.extends.map(&:name) + end + def test_multibyte_method_name content = <<~RUBY class Foo From 01cbb99dbc777394d70c614b1049ca291168c412 Mon Sep 17 00:00:00 2001 From: tompng Date: Fri, 31 Jan 2025 21:21:43 +0900 Subject: [PATCH 03/10] Fix constant defined in singleton class, class constant_path, module constant_path --- lib/rdoc/parser/prism_ruby.rb | 37 ++++++++++++++++-------- test/rdoc/test_rdoc_parser_prism_ruby.rb | 32 +++++++++++++++++++- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 198d9b29b0..6871029236 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -31,7 +31,7 @@ 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 @@ -60,19 +60,18 @@ def with_container(container, singleton: false) @singleton = singleton @include_extend_suppressed = 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 @include_extend_suppressed = old_include_extend_suppressed - @module_nesting.pop unless singleton + @module_nesting.pop end # Records the location of this +container+ in the file for this parser and @@ -584,15 +583,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(@module_nesting.last, root_name, create_mode) unless name - mod ||= add_module.call(@module_nesting.last, 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) @@ -606,7 +607,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 @@ -620,7 +622,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 < Date: Sat, 1 Feb 2025 02:42:24 +0900 Subject: [PATCH 04/10] Strip prefix :: from superclass name --- lib/rdoc/parser/prism_ruby.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 6871029236..c544f08ad1 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -679,6 +679,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe superclass_full_path = resolve_constant_path(superclass_name) superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path superclass_full_path ||= superclass_name + superclass_full_path = superclass_full_path.sub(/^::/, '') end # add_class should be done after resolving superclass mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object') From 69bb46de6ecbfa66c6786527cc45797613049c1d Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 1 Feb 2025 05:10:30 +0900 Subject: [PATCH 05/10] Reject documenting `def` inside block --- lib/rdoc/parser/prism_ruby.rb | 30 ++++++++++++------------ test/rdoc/test_rdoc_parser_prism_ruby.rb | 19 +++++++-------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index c544f08ad1..25959fc942 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -35,17 +35,17 @@ def initialize(top_level, content, options, stats) @container = top_level @visibility = :public @singleton = false - @include_extend_suppressed = 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 suppress_include_extend - @include_extend_suppressed = true + def with_in_proc_block + @in_proc_block = true yield - @include_extend_suppressed = false + @in_proc_block = false end # Dive into another container @@ -54,11 +54,11 @@ def with_container(container, singleton: false) old_container = @container old_visibility = @visibility old_singleton = @singleton - old_include_extend_suppressed = @include_extend_suppressed + old_in_proc_block = @in_proc_block @visibility = :public @container = container @singleton = singleton - @include_extend_suppressed = false + @in_proc_block = false unless singleton # Need to update module parent chain to emulate Module.nesting. # This mechanism is inaccurate and needs to be fixed. @@ -70,7 +70,7 @@ def with_container(container, singleton: false) @container = old_container @visibility = old_visibility @singleton = old_singleton - @include_extend_suppressed = old_include_extend_suppressed + @in_proc_block = old_in_proc_block @module_nesting.pop end @@ -484,7 +484,7 @@ def add_attributes(names, rw, line_no) end def add_includes_extends(names, rdoc_class, line_no) # :nodoc: - return if @include_extend_suppressed + return if @in_proc_block comment = consecutive_comment(line_no) handle_consecutive_comment_directive(@container, comment) names.each do |name| @@ -511,6 +511,8 @@ 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:) + 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)) @@ -525,14 +527,13 @@ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singl handle_modifier_directive(meth, end_line) return unless should_document?(meth) - if meth.name == 'initialize' && !singleton if meth.dont_rename_initialize - visibility = :protected + meth.visibility = :protected else meth.name = 'new' - singleton = true - visibility = :public + meth.singleton = true + meth.visibility = :public end end @@ -770,9 +771,8 @@ def visit_call_node(node) end def visit_block_node(node) - @scanner.suppress_include_extend do - # include and extend inside block are not documentable - # method definition might also not work but document it for now. + @scanner.with_in_proc_block do + # include, extend and method definition inside block are not documentable super end end diff --git a/test/rdoc/test_rdoc_parser_prism_ruby.rb b/test/rdoc/test_rdoc_parser_prism_ruby.rb index c73782df93..0b2aac947a 100644 --- a/test/rdoc/test_rdoc_parser_prism_ruby.rb +++ b/test/rdoc/test_rdoc_parser_prism_ruby.rb @@ -663,25 +663,22 @@ module A add_my_method :bar end - tap do - # comment baz1 + metaprogramming do + # class that defines this method is unknown def baz1; end end - self.tap do - # comment baz2 - def baz2; end - end - - my_decorator def self.baz3; end + my_decorator def self.baz2; end - self.my_decorator def baz4; end + self.my_decorator def baz3; end end RUBY mod = @store.find_module_named 'A' methods = mod.method_list - assert_equal ['A::foo', 'A#bar', 'A#baz1', 'A#baz2', 'A::baz3', 'A#baz4'], methods.map(&:full_name) - assert_equal ['comment foo', 'comment bar', 'comment baz1', 'comment baz2'], methods.take(4).map { |m| m.comment.text.strip } + unless accept_legacy_bug? + assert_equal ['A::foo', 'A#bar', 'A::baz2', 'A#baz3'], methods.map(&:full_name) + end + assert_equal ['comment foo', 'comment bar'], methods.take(2).map { |m| m.comment.text.strip } end def test_method_yields_directive From c5d96a8186f4c320fa18269264f5ab4c1438a2dd Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 1 Feb 2025 05:10:59 +0900 Subject: [PATCH 06/10] Accept "new" and "initialize" method both documented --- lib/rdoc/parser/prism_ruby.rb | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 25959fc942..5d000f9524 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -527,16 +527,6 @@ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singl handle_modifier_directive(meth, end_line) return unless should_document?(meth) - 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 - internal_add_method( receiver, meth, @@ -548,6 +538,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: From 25f1df9ba4ff7096fc1f792f43efdf8923f1cb29 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 1 Feb 2025 18:52:17 +0900 Subject: [PATCH 07/10] Fix wrong test for class defined inside singleton class --- test/rdoc/test_rdoc_store.rb | 23 +++++++++++++++++++++-- test/rdoc/xref_data.rb | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/rdoc/test_rdoc_store.rb b/test/rdoc/test_rdoc_store.rb index 13485cd075..68892f5810 100644 --- a/test/rdoc/test_rdoc_store.rb +++ b/test/rdoc/test_rdoc_store.rb @@ -66,6 +66,10 @@ def setup @mod.record_location @top_level end + def using_prism_ruby_parser? + RDoc::Parser::Ruby.name == 'RDoc::Parser::PrismRuby' + end + def teardown super @@ -161,13 +165,17 @@ def test_add_file_relative def test_all_classes_and_modules expected = %w[ - C1 C10 C10::C11 C11 C2 C2::C3 C2::C3::H1 C3 C3::H1 C3::H2 C4 C4::C4 C5 C5::C1 C6 C7 C8 C8::S1 C9 C9::A C9::B + C1 C10 C10::C11 C11 C2 C2::C3 C2::C3::H1 C3 C3::H1 C3::H2 C4 C4::C4 C5 C5::C1 C6 C7 C8 C9 C9::A C9::B Child M1 M1::M2 Object Parent ] + # C8::S1 does not exist. It should not be in the list. + # class C8; class << something; class S1; end; end; end + expected = (expected + ['C8::S1']).sort unless using_prism_ruby_parser? + assert_equal expected, @store.all_classes_and_modules.map { |m| m.full_name }.sort end @@ -213,12 +221,16 @@ def test_class_path def test_classes expected = %w[ - C1 C10 C10::C11 C11 C2 C2::C3 C2::C3::H1 C3 C3::H1 C3::H2 C4 C4::C4 C5 C5::C1 C6 C7 C8 C8::S1 C9 C9::A C9::B + C1 C10 C10::C11 C11 C2 C2::C3 C2::C3::H1 C3 C3::H1 C3::H2 C4 C4::C4 C5 C5::C1 C6 C7 C8 C9 C9::A C9::B Child Object Parent ] + # C8::S1 does not exist. It should not be in the list. + # class C8; class << something; class S1; end; end; end + expected = (expected + ['C8::S1']).sort unless using_prism_ruby_parser? + assert_equal expected, @store.all_classes.map { |m| m.full_name }.sort end @@ -550,6 +562,13 @@ def test_load_class end def test_load_single_class + if using_prism_ruby_parser? + # Class defined inside singleton class is not documentable. + # @c8_s1 should be nil because C8::S1 does not exist. + assert_nil @c8_s1 + return + end + @s.save_class @c8_s1 @s.classes_hash.clear diff --git a/test/rdoc/xref_data.rb b/test/rdoc/xref_data.rb index 257b821f4f..acfc0ce3ff 100644 --- a/test/rdoc/xref_data.rb +++ b/test/rdoc/xref_data.rb @@ -110,6 +110,7 @@ class C7 class C8 class << self + # This is C8.singleton_class::S1. C8::S1 does not exist. class S1 end end From 0299ae14031e5e12f2db002285bce3af09867d1c Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 1 Feb 2025 22:08:10 +0900 Subject: [PATCH 08/10] Fix superclass override when class Object is documented --- lib/rdoc/parser/prism_ruby.rb | 2 +- test/rdoc/test_rdoc_parser_prism_ruby.rb | 44 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index 5d000f9524..dd879ca795 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -689,7 +689,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe if superclass_name if superclass mod.superclass = superclass - elsif mod.superclass.is_a?(String) && mod.superclass != superclass_full_path + elsif (mod.superclass.is_a?(String) || mod.superclass.name == 'Object') && mod.superclass != superclass_full_path mod.superclass = superclass_full_path end end diff --git a/test/rdoc/test_rdoc_parser_prism_ruby.rb b/test/rdoc/test_rdoc_parser_prism_ruby.rb index 0b2aac947a..50a4483b1b 100644 --- a/test/rdoc/test_rdoc_parser_prism_ruby.rb +++ b/test/rdoc/test_rdoc_parser_prism_ruby.rb @@ -173,6 +173,50 @@ def m2; end assert_equal ['m1', 'm2'], c.method_list.map(&:name) end + def test_open_class_with_superclass_specified_later + util_parser <<~RUBY + # file_2 + require 'file_1' + class A; end + class B; end + class C; end + RUBY + _a, b, c = @top_level.classes + assert_equal 'Object', b.superclass + assert_equal 'Object', c.superclass + + util_parser <<~RUBY + # file_1 + class B < A; end + class C < Unknown; end + RUBY + assert_equal 'A', b.superclass.full_name + assert_equal 'Unknown', c.superclass + end + + def test_open_class_with_superclass_specified_later_with_object_defined + util_parser <<~RUBY + # file_2 + require 'file_1' + class Object; end + class A; end + class B; end + class C; end + RUBY + _object, _a, b, c = @top_level.classes + # If Object exists, superclass will be a NormalClass(Object) instead of string "Object" + assert_equal 'Object', b.superclass.full_name + assert_equal 'Object', c.superclass.full_name + + util_parser <<~RUBY + # file_1 + class B < A; end + class C < Unknown; end + RUBY + assert_equal 'A', b.superclass.full_name + assert_equal 'Unknown', c.superclass + end + def test_confusing_superclass util_parser <<~RUBY module A From 2973d9de5dc1af62319910cb8f588bcdab53ae47 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 1 Feb 2025 22:11:57 +0900 Subject: [PATCH 09/10] Support :nodoc: on method definition parameter end line --- lib/rdoc/parser/prism_ruby.rb | 5 ++++- test/rdoc/test_rdoc_parser_prism_ruby.rb | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb index dd879ca795..01614da032 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/prism_ruby.rb @@ -510,7 +510,7 @@ 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 @@ -524,6 +524,7 @@ 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) @@ -854,6 +855,7 @@ def visit_singleton_class_node(node) def visit_def_node(node) start_line = node.location.start_line + args_end_line = node.parameters&.location&.end_line || start_line end_line = node.location.end_line @scanner.process_comments_until(start_line - 1) @@ -904,6 +906,7 @@ def visit_def_node(node) calls_super: calls_super, tokens: tokens, start_line: start_line, + args_end_line: args_end_line, end_line: end_line ) ensure diff --git a/test/rdoc/test_rdoc_parser_prism_ruby.rb b/test/rdoc/test_rdoc_parser_prism_ruby.rb index 50a4483b1b..7747be4670 100644 --- a/test/rdoc/test_rdoc_parser_prism_ruby.rb +++ b/test/rdoc/test_rdoc_parser_prism_ruby.rb @@ -1391,9 +1391,12 @@ def nodoc2 # :nodoc: def doc3; end def nodoc3 end # :nodoc: + def nodoc4(arg1, + arg2) # :nodoc: + end def doc4; end # :stopdoc: - def nodoc4; end + def nodoc5; end end RUBY klass = @store.find_class_named 'Foo' From de966075ec1db227f3f2395f497bdcbfbbdcec6e Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 2 Feb 2025 16:01:00 +0900 Subject: [PATCH 10/10] =?UTF-8?q?pend=E2=86=92omit=20test=20that=20RDoc::P?= =?UTF-8?q?arser::Ruby=20has=20a=20bug=20or=20does=20not=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/rdoc/test_rdoc_parser_prism_ruby.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/rdoc/test_rdoc_parser_prism_ruby.rb b/test/rdoc/test_rdoc_parser_prism_ruby.rb index 7747be4670..627ffd90ad 100644 --- a/test/rdoc/test_rdoc_parser_prism_ruby.rb +++ b/test/rdoc/test_rdoc_parser_prism_ruby.rb @@ -1091,7 +1091,7 @@ def m5; end end def test_undocumentable_change_visibility - pend if accept_legacy_bug? + omit if accept_legacy_bug? util_parser <<~RUBY class A def m1; end @@ -1126,7 +1126,7 @@ class <