From de9f6ac8ad74657a11ebdd890164cee5ba947348 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 6 Apr 2026 21:43:02 +0100 Subject: [PATCH] Fix broken sidebar links for chained class aliases When Ruby defines chained aliases like `A = B = C` (e.g., `HTTPRequestURITooLarge = HTTPRequestURITooLong = HTTPURITooLong` in net/http), the sidebar link for the outermost alias pointed to the intermediate alias's HTML file, which is never generated. The root cause: `update_aliases` creates ClassModule objects whose `is_alias_for` references stale intermediate objects from `add_module_alias` that don't know they are aliases. Resolve through the store to follow already-processed aliases back to the real class. Also make `name_for_path` follow the alias chain recursively as a defensive measure. Fixes ruby/rdoc#1664. --- lib/rdoc/code_object/class_module.rb | 9 ++++++- test/rdoc/generator/darkfish_test.rb | 37 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb index b564059ae7..de7665a589 100644 --- a/lib/rdoc/code_object/class_module.rb +++ b/lib/rdoc/code_object/class_module.rb @@ -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 ## @@ -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 diff --git a/test/rdoc/generator/darkfish_test.rb b/test/rdoc/generator/darkfish_test.rb index bf4ec57058..dc9f7ae035 100644 --- a/test/rdoc/generator/darkfish_test.rb +++ b/test/rdoc/generator/darkfish_test.rb @@ -610,6 +610,43 @@ def test_canonical_url_for_classes assert_include(content, '') 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 + + @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)