Skip to content
Open
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
34 changes: 7 additions & 27 deletions lib/rdoc/cross_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,19 +190,13 @@ def resolve_local_symbol(name)
##
# Returns a reference to +name+.
#
# If the reference is found and +name+ is not documented +text+ will be
# returned. If +name+ is escaped +name+ is returned. If +name+ is not
# found +text+ is returned.
# If the reference is found and +name+ is not documented +nil+ will be
# returned. If +name+ is not found +nil+ is returned.

def resolve(name, text)
def resolve(name)
return @seen[name] if @seen.include? name

ref = case name
when /^\\(#{CLASS_REGEXP_STR})$/o then
@context.find_symbol $1
else
@context.find_symbol name
end
ref = @context.find_symbol name

ref = resolve_local_symbol name unless ref

Expand All @@ -211,25 +205,11 @@ def resolve(name, text)

ref = nil if RDoc::Alias === ref # external alias, can't link to it

out = if name == '\\' then
name
elsif name =~ /^\\/ then
# we remove the \ only in front of what we know:
# other backslashes are treated later, only outside of <tt>
ref ? $' : name
elsif ref then
if ref.display? then
ref
else
text
end
else
text
end
ref = nil unless ref&.display?

@seen[name] = out
@seen[name] = ref

out
ref
end

end
62 changes: 34 additions & 28 deletions lib/rdoc/markup/to_html_crossref.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,23 @@ def init_link_notation_regexp_handlings
##
# Creates a link to the reference +name+ if the name exists. If +text+ is
# given it is used as the link text, otherwise +name+ is used.
# Returns +nil+ if the link target could not be resolved.

def cross_reference(name, text = nil, code = true, rdoc_ref: false)
# 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

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

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
label = convert_string(RDoc::Text.decode_legacy_label(match[2]))
text ||= "#{label} at <code>#{convert_string(context_name)}</code>" if context_name
text ||= label
code = false
else
text ||= display
text ||= convert_string(display)
end

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

##
Expand All @@ -98,8 +95,7 @@ def handle_regexp_CROSSREF(name)
# cross-references to "new" in text, for instance)
return name if name =~ /\A[a-z]*\z/
end

cross_reference name, rdoc_ref: false
cross_reference(name, rdoc_ref: false) || convert_string(name)
end

##
Expand All @@ -111,7 +107,8 @@ def handle_regexp_HYPERLINK(url)

case url
when /\Ardoc-ref:/
cross_reference $', rdoc_ref: true
ref = $'
cross_reference(ref, rdoc_ref: true) || convert_string(ref)
else
super
end
Expand All @@ -131,7 +128,8 @@ def handle_regexp_RDOCLINK(url)
if in_tidylink_label?
convert_string(url)
else
cross_reference $', rdoc_ref: true
ref = $'
cross_reference(ref, rdoc_ref: true) || convert_string(ref)
end
else
super
Expand All @@ -145,14 +143,15 @@ def handle_regexp_RDOCLINK(url)
def gen_url(url, text)
if url =~ /\Ardoc-ref:/
name = $'
cross_reference name, text, name == text, rdoc_ref: true
cross_reference(name, text, name == text, rdoc_ref: true) || text
else
super
end
end

##
# Creates an HTML link to +name+ with the given +text+.
# +text+ is an HTML string, already escaped and may contain HTML tags.
# Returns the link HTML string, or +nil+ if the reference could not be resolved.

def link(name, text, code = true, rdoc_ref: false)
Expand All @@ -161,30 +160,29 @@ def link(name, text, code = true, rdoc_ref: false)
label = $'
end

ref = @cross_reference.resolve name, text if name
ref = @cross_reference.resolve name if name

# 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
end

case ref
when String
if ref
path = ref.as_href(@from_path)

if code and RDoc::CodeObject === ref and !(RDoc::TopLevel === ref)
text = "<code>#{text}</code>"
Comment on lines +174 to +175
end
elsif name
if rdoc_ref && @warn_missing_rdoc_ref
puts "#{@from_path}: `rdoc-ref:#{name}` can't be resolved for `#{text}`"
end
return
when nil
else
# A bare label reference like @foo still produces a valid anchor link
return unless label
path = +""
else
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
Expand Down Expand Up @@ -223,29 +221,37 @@ def link(name, text, code = true, rdoc_ref: false)
end

def handle_TT(code)
emit_inline(tt_cross_reference(code) || "<code>#{CGI.escapeHTML code}</code>")
emit_inline(tt_cross_reference(code) || "<code>#{convert_string(code)}</code>")
end

# Applies additional special handling on top of the one defined in ToHtml.
# When a tidy link is <tt>{Foo}[rdoc-ref:Foo]</tt>, the label part is surrounded by <tt><code></code></tt>.
# TODO: reconsider this workaround.
def apply_tidylink_label_special_handling(label, url)
if url == "rdoc-ref:#{label}" && cross_reference(label).include?('<code>')
if url == "rdoc-ref:#{label}" && cross_reference(label)&.include?('<code>')
"<code>#{convert_string(label)}</code>"
else
super
end
end

# Handles cross-reference and suppressed-crossref inside tt tag.
# Returns nil if code is not an existing cross-reference nor a suppressed-crossref.
def tt_cross_reference(code)
return if in_tidylink_label?

crossref_regexp = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP
match = crossref_regexp.match(code)
# REGEXP sometimes matches to a string starts with backslash which is not a suppressed crossref. (e.g. `\+`)
# We need to check the backslash removed part matches to crossref_regexp
Comment on lines +244 to +245
match = crossref_regexp.match(code.delete_prefix('\\'))
return unless match && match.begin(1).zero?
return unless match.post_match.match?(/\A[[:punct:]\s]*\z/)

ref = cross_reference(code)
ref if ref != code
if code.start_with?('\\')
# Remove leading backslash if crossref exists
"<code>#{convert_string(code[1..])}</code>" if cross_reference(code[1..])&.include?('<code>')
Comment on lines +250 to +252
else
cross_reference(code)
end
end
end
22 changes: 17 additions & 5 deletions test/rdoc/markup/to_html_crossref_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ def test_convert_CROSSREF_backslash_in_tt
assert_equal para('<code>.bar.hello(\\)</code>'), result
end

def test_convert_suppressed_CROSSREF_in_tt
result = @to.convert '<tt>C1</tt> <tt>\\C1</tt>'
assert_equal para('<a href="C1.html"><code>C1</code></a> <code>C1</code>'), result

result = @to.convert '<tt>C1#m()</tt> <tt>\\C1#m()</tt>'
assert_equal para('<a href="C1.html#method-i-m"><code>C1#m()</code></a> <code>C1#m()</code>'), result

# Keep backshash if crossref doesn't exitst
result = @to.convert '<tt>C1#&</tt> <tt>\\n</tt> <tt>\\C1#&</tt>'
assert_equal para('<code>C1#&amp;</code> <code>\\n</code> <code>\\C1#&amp;</code>'), result
end

def test_convert_CROSSREF_ignored_excluded_words
@to = RDoc::Markup::ToHtmlCrossref.new 'index.html', @c1,
hyperlink_all: true, warn_missing_rdoc_ref: true,
Expand Down Expand Up @@ -118,7 +130,7 @@ def test_convert_CROSSREF_section_with_spaces

def test_convert_CROSSREF_legacy_label
result = @to.convert 'C1@What-27s+Here'
assert_equal para("<a href=\"C1.html#class-c1-whats-here\">What's Here at <code>C1</code></a>"), result
assert_equal para("<a href=\"C1.html#class-c1-whats-here\">What&#39;s Here at <code>C1</code></a>"), result
end

def test_convert_CROSSREF_legacy_label_colon
Expand All @@ -130,7 +142,7 @@ def test_convert_CROSSREF_legacy_section
@c1.add_section "What's Here"

result = @to.convert "C1@What-27s+Here"
assert_equal para("<a href=\"C1.html#whats-here\">What's Here at <code>C1</code></a>"), result
assert_equal para("<a href=\"C1.html#whats-here\">What&#39;s Here at <code>C1</code></a>"), result
end

def test_convert_CROSSREF_constant
Expand Down Expand Up @@ -430,9 +442,9 @@ def test_handle_regexp_CROSSREF_hash_preserved_for_unresolved
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")
def test_cross_reference_returns_nil_for_unresolved
# Fallback of unresolved refs depends on the context, so cross_reference should return nil for unresolved refs
assert_nil @to.cross_reference("Missing", "Foo")
end

private
Expand Down
27 changes: 13 additions & 14 deletions test/rdoc/rdoc_cross_reference_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ def setup
end

def assert_ref(expected, name)
assert_equal expected, @xref.resolve(name, 'fail')
assert_equal expected, @xref.resolve(name)
end

def refute_ref(name)
assert_equal name, @xref.resolve(name, name)
assert_nil @xref.resolve(name)
end

def test_METHOD_REGEXP_STR
Expand Down Expand Up @@ -202,22 +202,21 @@ def assert_resolve_method(x)
end

def test_resolve_no_ref
assert_equal '', @xref.resolve('', '')
refute_ref('')

assert_equal "bogus", @xref.resolve("bogus", "bogus")
assert_equal "\\bogus", @xref.resolve("\\bogus", "\\bogus")
assert_equal "\\\\bogus", @xref.resolve("\\\\bogus", "\\\\bogus")
refute_ref("bogus")
refute_ref("\\bogus")

assert_equal "\\#n", @xref.resolve("\\#n", "fail")
assert_equal "\\#n()", @xref.resolve("\\#n()", "fail")
assert_equal "\\#n(*)", @xref.resolve("\\#n(*)", "fail")
refute_ref("\\#n")
refute_ref("\\#n()")
refute_ref("\\#n(*)")

assert_equal "C1", @xref.resolve("\\C1", "fail")
assert_equal "::C3", @xref.resolve("\\::C3", "fail")
refute_ref("\\C1")
refute_ref("\\::C3")

assert_equal "succeed", @xref.resolve("::C3::H1#n", "succeed")
assert_equal "succeed", @xref.resolve("::C3::H1#n(*)", "succeed")
assert_equal "\\::C3::H1#n", @xref.resolve("\\::C3::H1#n", "fail")
refute_ref("::C3::H1#n")
refute_ref("::C3::H1#n(*)")
refute_ref("\\::C3::H1#n")
end

end
Loading