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
90 changes: 49 additions & 41 deletions lib/rdoc/markup/to_html_crossref.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,24 @@ def init_link_notation_regexp_handlings
# given it is used as the link text, otherwise +name+ is used.

def cross_reference(name, text = nil, code = true, rdoc_ref: false)
lookup = name
# What to show when the reference doesn't resolve to a link:
# caller-provided text if any, otherwise the original name (preserving '#').
fallback = text || name

name = name[1..-1] unless @show_hash if name[0, 1] == '#'
# Strip '#' for link display text (e.g. #method shows as "method" in links)
display = !@show_hash && name.start_with?('#') ? name[1..] : name

if !name.end_with?('+@', '-@') && match = name.match(/(.*[^#:])?@(.*)/)
if !display.end_with?('+@', '-@') && match = display.match(/(.*[^#:])?@(.*)/)
context_name = match[1]
label = RDoc::Text.decode_legacy_label(match[2])
text ||= "#{label} at <code>#{context_name}</code>" if context_name
text ||= label
code = false
else
text ||= name
text ||= display
end

link lookup, text, code, rdoc_ref: rdoc_ref
link(name, text, code, rdoc_ref: rdoc_ref) || fallback
end

##
Expand Down Expand Up @@ -150,6 +153,7 @@ def gen_url(url, text)

##
# Creates an HTML link to +name+ with the given +text+.
# Returns the link HTML string, or +nil+ if the reference could not be resolved.

def link(name, text, code = true, rdoc_ref: false)
if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/
Expand All @@ -162,56 +166,60 @@ def link(name, text, code = true, rdoc_ref: false)
# Non-text source files (C, Ruby, etc.) don't get HTML pages generated,
# so don't auto-link to them. Explicit rdoc-ref: links are still allowed.
if !rdoc_ref && RDoc::TopLevel === ref && !ref.text?
return text
return
end

case ref
when String then
when String
if rdoc_ref && @warn_missing_rdoc_ref
puts "#{@from_path}: `rdoc-ref:#{name}` can't be resolved for `#{text}`"
end
ref
return
when nil
# A bare label reference like @foo still produces a valid anchor link
return unless label
path = +""
else
path = ref ? ref.as_href(@from_path) : +""
path = ref.as_href(@from_path)

if code and RDoc::CodeObject === ref and !(RDoc::TopLevel === ref)
text = "<code>#{CGI.escapeHTML text}</code>"
end
end

if label
# Decode legacy labels (e.g., "What-27s+Here" -> "What's Here")
# then convert to GitHub-style anchor format
decoded_label = RDoc::Text.decode_legacy_label(label)
formatted_label = RDoc::Text.to_anchor(decoded_label)

# Case 1: Path already has an anchor (e.g., method link)
# Input: C1#method@label -> path="C1.html#method-i-m"
# Output: C1.html#method-i-m-label
if path =~ /#/
path << "-#{formatted_label}"

# Case 2: Label matches a section title
# Input: C1@Section -> path="C1.html", section "Section" exists
# Output: C1.html#section (uses section.aref for GitHub-style)
elsif (section = ref&.sections&.find { |s| decoded_label == s.title })
path << "##{section.aref}"

# Case 3: Ref has an aref (class/module context)
# Input: C1@heading -> path="C1.html", ref=C1 class
# Output: C1.html#class-c1-heading
elsif ref.respond_to?(:aref)
path << "##{ref.aref}-#{formatted_label}"

# Case 4: No context, just the label (e.g., TopLevel/file)
# Input: README@section -> path="README_md.html"
# Output: README_md.html#section
else
path << "##{formatted_label}"
end
if label
# Decode legacy labels (e.g., "What-27s+Here" -> "What's Here")
# then convert to GitHub-style anchor format
decoded_label = RDoc::Text.decode_legacy_label(label)
formatted_label = RDoc::Text.to_anchor(decoded_label)

# Case 1: Path already has an anchor (e.g., method link)
# Input: C1#method@label -> path="C1.html#method-i-m"
# Output: C1.html#method-i-m-label
if path =~ /#/
path << "-#{formatted_label}"

# Case 2: Label matches a section title
# Input: C1@Section -> path="C1.html", section "Section" exists
# Output: C1.html#section (uses section.aref for GitHub-style)
elsif (section = ref&.sections&.find { |s| decoded_label == s.title })
path << "##{section.aref}"

# Case 3: Ref has an aref (class/module context)
# Input: C1@heading -> path="C1.html", ref=C1 class
# Output: C1.html#class-c1-heading
elsif ref.respond_to?(:aref)
path << "##{ref.aref}-#{formatted_label}"

# Case 4: No context, just the label (e.g., TopLevel/file)
# Input: README@section -> path="README_md.html"
# Output: README_md.html#section
else
path << "##{formatted_label}"
end

"<a href=\"#{path}\">#{text}</a>"
end

"<a href=\"#{path}\">#{text}</a>"
end

def handle_TT(code)
Expand Down
16 changes: 15 additions & 1 deletion test/rdoc/markup/to_html_crossref_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def test_convert_RDOCLINK_rdoc_ref_c_file_linked
end

def test_link
assert_equal 'n', @to.link('n', 'n')
assert_nil @to.link('n', 'n')

assert_equal '<a href="C1.html#method-c-m"><code>m</code></a>', @to.link('m', 'm')
end
Expand All @@ -423,6 +423,20 @@ def test_link_class_method_full
@to.link('Parent::m', 'Parent::m')
end

def test_handle_regexp_CROSSREF_hash_preserved_for_unresolved
@to.show_hash = false

# #no should not lose its '#' when it doesn't resolve to a method
assert_equal "#no", REGEXP_HANDLING('#no')
end

def test_cross_reference_preserves_explicit_text_for_unresolved
# When explicit text is provided, it should be preserved on unresolved refs
assert_equal "Foo", @to.cross_reference("Missing", "Foo")
end

private

def para(text)
"\n<p>#{text}</p>\n"
end
Expand Down