Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/rdoc/code_object/class_module.rb
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ def path
# module or class return the name of the latter.

def name_for_path
is_alias_for ? is_alias_for.full_name : full_name
is_alias_for ? is_alias_for.name_for_path : full_name
end

##
Expand Down Expand Up @@ -848,6 +848,13 @@ def type
def update_aliases
constants.each do |const|
next unless cm = const.is_alias_for

# Resolve chained aliases (A = B = C) to the real class/module.
cm = @store.find_class_or_module(cm.full_name) || cm
while (target = cm.is_alias_for)
cm = target
end

cm_alias = cm.dup
cm_alias.name = const.name

Expand Down
37 changes: 37 additions & 0 deletions test/rdoc/generator/darkfish_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,43 @@ def test_canonical_url_for_classes
assert_include(content, '<link rel="canonical" href="https://docs.ruby-lang.org/en/master/Klass/Inner.html">')
end

def test_generate_chained_alias_sidebar_links
# Reproduces ruby/rdoc#1664:
# class Original < Base
# DirectAlias = Original # alias of real class
# ChainedAlias = DirectAlias # alias of an alias
#
# ChainedAlias's sidebar link must point to Original.html, not DirectAlias.html
# (which is never generated because aliases don't get their own files).
parent = @top_level.add_module RDoc::NormalModule, 'Parent'
original = parent.add_class RDoc::NormalClass, 'Original'

direct_alias_const = RDoc::Constant.new 'DirectAlias', nil, ''
direct_alias_const.record_location @top_level
direct_alias_const.is_alias_for = original
parent.add_constant direct_alias_const
parent.update_aliases

direct_alias = @store.find_class_or_module 'Parent::DirectAlias'

chained_alias_const = RDoc::Constant.new 'ChainedAlias', nil, ''
chained_alias_const.record_location @top_level
chained_alias_const.is_alias_for = direct_alias
parent.add_constant chained_alias_const
parent.update_aliases

Comment on lines +624 to +637
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test setup doesn’t closely match the production code path that triggered #1664: here ChainedAlias is set to alias an already-processed store entry (direct_alias), so it primarily exercises the cm = cm.is_alias_for while ... part, but not the new @store.find_class_or_module(cm.full_name) lookup that’s intended to resolve a chain when const.is_alias_for still points at the pre-update_aliases placeholder object for the intermediate alias.

Consider restructuring to add both alias constants before a single parent.update_aliases (or using Context#add_module_alias to build DirectAlias/ChainedAlias the way the parser does) and ensure ChainedAlias initially aliases the intermediate non-alias object, so this test fails if the store-lookup chain resolution regresses.

Copilot uses AI. Check for mistakes.
@store.complete :private
@g.generate

assert_file 'Parent/Original.html'
refute File.exist?('Parent/DirectAlias.html'), 'alias should not get its own file'
refute File.exist?('Parent/ChainedAlias.html'), 'chained alias should not get its own file'

index_html = File.binread('index.html')
assert_match %r{href="\./Parent/Original\.html">DirectAlias<}, index_html
assert_match %r{href="\./Parent/Original\.html">ChainedAlias<}, index_html
end

def test_canonical_url_for_rdoc_files
@store.add_file("CONTRIBUTING.rdoc", parser: RDoc::Parser::Simple)

Expand Down