Skip to content

Commit 7b3331b

Browse files
committed
Reduce the difference between RDoc::Parser::PrismRuby and RDoc::Parser::Ruby
1 parent 80a146b commit 7b3331b

File tree

2 files changed

+85
-11
lines changed

2 files changed

+85
-11
lines changed

lib/rdoc/parser/prism_ruby.rb

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ def parse_comment_tomdoc(container, comment, line_no, start_line)
204204
@stats.add_method meth
205205
end
206206

207+
def has_modifier_nodoc?(line_no) # :nodoc:
208+
@modifier_comments[line_no]&.text&.match?(/\A#\s*:nodoc:/)
209+
end
210+
207211
def handle_modifier_directive(code_object, line_no) # :nodoc:
208212
comment = @modifier_comments[line_no]
209213
@preprocess.handle(comment.text, code_object) if comment
@@ -568,9 +572,12 @@ def find_or_create_module_path(module_name, create_mode)
568572
@module_nesting.reverse_each do |nesting|
569573
mod = nesting.find_module_named(root_name)
570574
break if mod
575+
# If a constant is found and it is not a module or class, RDoc can't document about it.
576+
# Return an anonymous module to avoid wrong document creation.
577+
return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name)
571578
end
572-
return mod || add_module.call(@top_level, root_name, create_mode) unless name
573-
mod ||= add_module.call(@top_level, root_name, :module)
579+
return mod || add_module.call(@module_nesting.last, root_name, create_mode) unless name
580+
mod ||= add_module.call(@module_nesting.last, root_name, :module)
574581
end
575582
path.each do |name|
576583
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)
635642

636643
# Adds module or class
637644

638-
def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil)
645+
def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil)
639646
comment = consecutive_comment(start_line)
640647
handle_consecutive_comment_directive(@container, comment)
641648
return unless @container.document_children
@@ -650,7 +657,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe
650657
superclass_full_path ||= superclass_name
651658
end
652659
# add_class should be done after resolving superclass
653-
mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || '::Object')
660+
mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object')
654661
if superclass_name
655662
if superclass
656663
mod.superclass = superclass
@@ -678,6 +685,20 @@ def initialize(scanner, top_level, store)
678685
@store = store
679686
end
680687

688+
def visit_if_node(node)
689+
if node.end_keyword
690+
super
691+
else
692+
# Visit with the order in text representation to handle this method comment
693+
# # comment
694+
# def f
695+
# end if call_node
696+
node.statements.accept(self)
697+
node.predicate.accept(self)
698+
end
699+
end
700+
alias visit_unless_node visit_if_node
701+
681702
def visit_call_node(node)
682703
@scanner.process_comments_until(node.location.start_line - 1)
683704
if node.receiver.nil?
@@ -745,8 +766,9 @@ def visit_module_node(node)
745766
def visit_class_node(node)
746767
@scanner.process_comments_until(node.location.start_line - 1)
747768
superclass_name = constant_path_string(node.superclass) if node.superclass
769+
superclass_expr = node.superclass.slice if node.superclass && !superclass_name
748770
class_name = constant_path_string(node.constant_path)
749-
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
771+
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
750772
if klass
751773
@scanner.with_container(klass) do
752774
super
@@ -760,6 +782,12 @@ def visit_class_node(node)
760782
def visit_singleton_class_node(node)
761783
@scanner.process_comments_until(node.location.start_line - 1)
762784

785+
if @scanner.has_modifier_nodoc?(node.location.start_line)
786+
# Skip visiting inside the singleton class. Also skips creation of node.expression as a module
787+
@scanner.skip_comments_until(node.location.end_line)
788+
return
789+
end
790+
763791
expression = node.expression
764792
expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1
765793

@@ -944,7 +972,7 @@ def _visit_call_public_private_protected(call_node, visibility)
944972
@scanner.visibility = visibility
945973
else # `public :foo, :bar`, `private def foo; end`
946974
yield
947-
names = visibility_method_arguments(call_node, singleton: @scanner.singleton)
975+
names = visibility_method_arguments(call_node, singleton: false)
948976
@scanner.change_method_visibility(names, visibility) if names
949977
end
950978
end

test/rdoc/test_rdoc_parser_prism_ruby.rb

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ class Baz < (any expression)
281281
assert_equal ['Foo', 'Bar', 'Baz'], @top_level.classes.map(&:full_name)
282282
foo, bar, baz = @top_level.classes
283283
assert_equal foo, bar.superclass
284-
assert_equal 'Object', baz.superclass unless accept_legacy_bug?
284+
assert_equal '(any expression)', baz.superclass
285285
end
286286

287287
def test_class_new_notnew
@@ -534,6 +534,27 @@ def three x; end
534534
assert_equal @top_level, three.file
535535
end
536536

537+
def test_method_with_modifier_if_unless
538+
util_parser <<~RUBY
539+
class Foo
540+
# my method one
541+
def one
542+
end if foo
543+
544+
# my method two
545+
def two
546+
end unless foo
547+
end
548+
RUBY
549+
550+
klass = @store.find_class_named 'Foo'
551+
one, two = klass.method_list
552+
assert_equal 'Foo#one', one.full_name
553+
assert_equal 'my method one', one.comment.text.strip
554+
assert_equal 'Foo#two', two.full_name
555+
assert_equal 'my method two', two.comment.text.strip
556+
end
557+
537558
def test_method_toplevel
538559
util_parser <<~RUBY
539560
# comment
@@ -769,14 +790,22 @@ def baz; end
769790

770791
def test_undefined_singleton_class_defines_module
771792
util_parser <<~RUBY
772-
class << Foo
773-
end
774-
class << ::Bar
793+
module A
794+
class << Foo
795+
end
796+
class << ::Bar
797+
end
798+
Baz1 = ''
799+
class << Baz1
800+
end
801+
class << Baz2 # :nodoc:
802+
end
775803
end
776804
RUBY
777805

778806
modules = @store.all_modules
779-
assert_equal ['Foo', 'Bar'], modules.map(&:name)
807+
modules = modules.take(3) if accept_legacy_bug?
808+
assert_equal ['A', 'A::Foo', 'Bar'], modules.map(&:full_name)
780809
end
781810

782811
def test_singleton_class
@@ -1039,6 +1068,23 @@ def self.m2; end
10391068
assert_equal [:public] * 4, klass.method_list.map(&:visibility)
10401069
end
10411070

1071+
def test_singleton_class_def_with_visibility
1072+
util_parser <<~RUBY
1073+
class A
1074+
class <<self
1075+
private def m1; end
1076+
end
1077+
private def self.m2; end
1078+
end
1079+
class <<A
1080+
private def m3; end
1081+
end
1082+
RUBY
1083+
klass = @store.find_class_named 'A'
1084+
assert_equal [true, true, true], klass.method_list.map(&:singleton)
1085+
assert_equal [:private, :public, :private], klass.method_list.map(&:visibility)
1086+
end
1087+
10421088
def test_method_visibility_change_in_subclass
10431089
pend 'not implemented' if accept_legacy_bug?
10441090
util_parser <<~RUBY

0 commit comments

Comments
 (0)