Skip to content

Commit ab7553e

Browse files
committed
Fix constant defined in singleton class, class constant_path, module constant_path
1 parent 2420152 commit ab7553e

File tree

2 files changed

+56
-13
lines changed

2 files changed

+56
-13
lines changed

lib/rdoc/parser/prism_ruby.rb

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def initialize(top_level, content, options, stats)
3131
@track_visibility = :nodoc != @options.visibility
3232
@encoding = @options.encoding
3333

34-
@module_nesting = [top_level]
34+
@module_nesting = [[top_level, false]]
3535
@container = top_level
3636
@visibility = :public
3737
@singleton = false
@@ -60,19 +60,18 @@ def with_container(container, singleton: false)
6060
@singleton = singleton
6161
@include_extend_suppressed = false
6262
unless singleton
63-
@module_nesting.push container
64-
6563
# Need to update module parent chain to emulate Module.nesting.
6664
# This mechanism is inaccurate and needs to be fixed.
6765
container.parent = old_container
6866
end
67+
@module_nesting.push([container, singleton])
6968
yield container
7069
ensure
7170
@container = old_container
7271
@visibility = old_visibility
7372
@singleton = old_singleton
7473
@include_extend_suppressed = old_include_extend_suppressed
75-
@module_nesting.pop unless singleton
74+
@module_nesting.pop
7675
end
7776

7877
# 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)
584583
if root_name.empty?
585584
mod = @top_level
586585
else
587-
@module_nesting.reverse_each do |nesting|
586+
@module_nesting.reverse_each do |nesting, singleton|
587+
next if singleton
588588
mod = nesting.find_module_named(root_name)
589589
break if mod
590590
# If a constant is found and it is not a module or class, RDoc can't document about it.
591591
# Return an anonymous module to avoid wrong document creation.
592592
return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name)
593593
end
594-
return mod || add_module.call(@module_nesting.last, root_name, create_mode) unless name
595-
mod ||= add_module.call(@module_nesting.last, root_name, :module)
594+
last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton }
595+
return mod || add_module.call(last_nesting, root_name, create_mode) unless name
596+
mod ||= add_module.call(last_nesting, root_name, :module)
596597
end
597598
path.each do |name|
598599
mod = mod.find_module_named(name) || add_module.call(mod, name, :module)
@@ -606,7 +607,8 @@ def resolve_constant_path(constant_path)
606607
owner_name, path = constant_path.split('::', 2)
607608
return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar
608609
mod = nil
609-
@module_nesting.reverse_each do |nesting|
610+
@module_nesting.reverse_each do |nesting, singleton|
611+
next if singleton
610612
mod = nesting.find_module_named(owner_name)
611613
break if mod
612614
end
@@ -620,7 +622,10 @@ def resolve_constant_path(constant_path)
620622
def find_or_create_constant_owner_name(constant_path)
621623
const_path, colon, name = constant_path.rpartition('::')
622624
if colon.empty? # class Foo
623-
[@container, name]
625+
# Within `class C` or `module C`, owner is C(== current container)
626+
# Within `class <<C`, owner is C.singleton_class
627+
# but RDoc don't track constants of a singleton class of module
628+
[(@singleton ? nil : @container), name]
624629
elsif const_path.empty? # class ::Foo
625630
[@top_level, name]
626631
else # `class Foo::Bar` or `class ::Foo::Bar`
@@ -634,6 +639,8 @@ def add_constant(constant_name, rhs_name, start_line, end_line)
634639
comment = consecutive_comment(start_line)
635640
handle_consecutive_comment_directive(@container, comment)
636641
owner, name = find_or_create_constant_owner_name(constant_name)
642+
return unless owner
643+
637644
constant = RDoc::Constant.new(name, rhs_name, comment)
638645
constant.store = @store
639646
constant.line = start_line
@@ -663,6 +670,8 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe
663670
return unless @container.document_children
664671

665672
owner, name = find_or_create_constant_owner_name(module_name)
673+
return unless owner
674+
666675
if is_class
667676
# RDoc::NormalClass resolves superclass name despite of the lack of module nesting information.
668677
# We need to fix it when RDoc::NormalClass resolved to a wrong constant name
@@ -774,12 +783,13 @@ def visit_alias_method_node(node)
774783
end
775784

776785
def visit_module_node(node)
786+
node.constant_path.accept(self)
777787
@scanner.process_comments_until(node.location.start_line - 1)
778788
module_name = constant_path_string(node.constant_path)
779789
mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name
780790
if mod
781791
@scanner.with_container(mod) do
782-
super
792+
node.body&.accept(self)
783793
@scanner.process_comments_until(node.location.end_line)
784794
end
785795
else
@@ -788,14 +798,16 @@ def visit_module_node(node)
788798
end
789799

790800
def visit_class_node(node)
801+
node.constant_path.accept(self)
802+
node.superclass&.accept(self)
791803
@scanner.process_comments_until(node.location.start_line - 1)
792804
superclass_name = constant_path_string(node.superclass) if node.superclass
793805
superclass_expr = node.superclass.slice if node.superclass && !superclass_name
794806
class_name = constant_path_string(node.constant_path)
795807
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
796808
if klass
797809
@scanner.with_container(klass) do
798-
super
810+
node.body&.accept(self)
799811
@scanner.process_comments_until(node.location.end_line)
800812
end
801813
else
@@ -826,9 +838,10 @@ def visit_singleton_class_node(node)
826838
when Prism::SelfNode
827839
mod = @scanner.container if @scanner.container != @top_level
828840
end
841+
expression.accept(self)
829842
if mod
830843
@scanner.with_container(mod, singleton: true) do
831-
super
844+
node.body&.accept(self)
832845
@scanner.process_comments_until(node.location.end_line)
833846
end
834847
else

test/rdoc/test_rdoc_parser_prism_ruby.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,6 @@ def new; end
358358
assert_equal ['DidYouMean::NameErrorCheckers::new'], mod.method_list.map(&:full_name)
359359
end
360360

361-
362361
def test_ghost_method
363362
util_parser <<~RUBY
364363
class Foo
@@ -1595,6 +1594,37 @@ class Bar; end
15951594
assert_equal 'Foo::C', klass.find_module_named('C').full_name
15961595
end
15971596

1597+
def test_constant_with_singleton_class
1598+
pend if accept_legacy_bug?
1599+
util_parser <<~RUBY
1600+
class Foo
1601+
class Bar; end
1602+
A = 1
1603+
class <<Bar
1604+
B = 1
1605+
Foo::Bar::B2 = 1
1606+
end
1607+
class Bar
1608+
C = 1
1609+
end
1610+
class <<(p(D = 1))
1611+
E = 1
1612+
end
1613+
class (F = 1)::Baz
1614+
G = 1
1615+
end
1616+
module (H = 1)::Baz
1617+
I = 1
1618+
end
1619+
end
1620+
J = 1
1621+
RUBY
1622+
foo, bar = @store.all_classes
1623+
assert_equal ['J'], @store.find_class_named('Object').constants.map(&:name)
1624+
assert_equal ['A', 'D', 'F', 'H'], foo.constants.map(&:name)
1625+
assert_equal ['B2', 'C'], bar.constants.map(&:name)
1626+
end
1627+
15981628
def test_constant_method
15991629
util_parser <<~RUBY
16001630
def Object.foo; end

0 commit comments

Comments
 (0)