rdoc 7.0.3 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -4
  3. data/doc/markup_reference/markdown.md +558 -0
  4. data/doc/markup_reference/rdoc.rdoc +1169 -0
  5. data/lib/rdoc/code_object/attr.rb +2 -1
  6. data/lib/rdoc/code_object/class_module.rb +24 -3
  7. data/lib/rdoc/code_object/context/section.rb +46 -9
  8. data/lib/rdoc/code_object/context.rb +15 -4
  9. data/lib/rdoc/code_object/mixin.rb +3 -0
  10. data/lib/rdoc/code_object/top_level.rb +2 -0
  11. data/lib/rdoc/comment.rb +1 -1
  12. data/lib/rdoc/cross_reference.rb +31 -24
  13. data/lib/rdoc/generator/template/aliki/_head.rhtml +5 -0
  14. data/lib/rdoc/generator/template/aliki/class.rhtml +8 -6
  15. data/lib/rdoc/generator/template/aliki/css/rdoc.css +48 -36
  16. data/lib/rdoc/generator/template/aliki/js/aliki.js +8 -2
  17. data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
  18. data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +1 -1
  19. data/lib/rdoc/generator/template/aliki/js/search_controller.js +1 -1
  20. data/lib/rdoc/generator/template/darkfish/class.rhtml +2 -0
  21. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -0
  22. data/lib/rdoc/markdown.kpeg +22 -12
  23. data/lib/rdoc/markdown.rb +36 -26
  24. data/lib/rdoc/markup/formatter.rb +129 -106
  25. data/lib/rdoc/markup/heading.rb +101 -29
  26. data/lib/rdoc/markup/inline_parser.rb +312 -0
  27. data/lib/rdoc/markup/parser.rb +1 -1
  28. data/lib/rdoc/markup/to_ansi.rb +51 -4
  29. data/lib/rdoc/markup/to_bs.rb +22 -42
  30. data/lib/rdoc/markup/to_html.rb +178 -183
  31. data/lib/rdoc/markup/to_html_crossref.rb +58 -79
  32. data/lib/rdoc/markup/to_html_snippet.rb +62 -62
  33. data/lib/rdoc/markup/to_label.rb +29 -20
  34. data/lib/rdoc/markup/to_markdown.rb +61 -37
  35. data/lib/rdoc/markup/to_rdoc.rb +86 -26
  36. data/lib/rdoc/markup/to_test.rb +9 -1
  37. data/lib/rdoc/markup/to_tt_only.rb +10 -16
  38. data/lib/rdoc/markup/verbatim.rb +1 -1
  39. data/lib/rdoc/markup.rb +10 -32
  40. data/lib/rdoc/parser/changelog.rb +29 -0
  41. data/lib/rdoc/parser/prism_ruby.rb +44 -32
  42. data/lib/rdoc/parser/ruby.rb +1 -1
  43. data/lib/rdoc/text.rb +44 -5
  44. data/lib/rdoc/token_stream.rb +4 -8
  45. data/lib/rdoc/version.rb +1 -1
  46. data/rdoc.gemspec +2 -2
  47. metadata +7 -12
  48. data/ExampleMarkdown.md +0 -39
  49. data/ExampleRDoc.rdoc +0 -210
  50. data/lib/rdoc/markup/attr_changer.rb +0 -22
  51. data/lib/rdoc/markup/attr_span.rb +0 -35
  52. data/lib/rdoc/markup/attribute_manager.rb +0 -405
  53. data/lib/rdoc/markup/attributes.rb +0 -70
  54. data/lib/rdoc/markup/regexp_handling.rb +0 -40
@@ -51,11 +51,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
51
51
  @in_list_entry = nil
52
52
  @list = nil
53
53
  @th = nil
54
+ @in_tidylink_label = false
54
55
  @hard_break = "<br>\n"
55
56
 
56
57
  init_regexp_handlings
57
-
58
- init_tags
59
58
  end
60
59
 
61
60
  # :section: Regexp Handling
@@ -72,6 +71,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
72
71
  # external links
73
72
  @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/,
74
73
  :HYPERLINK)
74
+
75
+ # suppress crossref: \#method \::method \ClassName \method_with_underscores
76
+ @markup.add_regexp_handling(/\\(?:[#:A-Z]|[a-z]+_[a-z0-9])/, :SUPPRESSED_CROSSREF)
77
+
75
78
  init_link_notation_regexp_handlings
76
79
  end
77
80
 
@@ -80,7 +83,6 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
80
83
 
81
84
  def init_link_notation_regexp_handlings
82
85
  add_regexp_handling_RDOCLINK
83
- add_regexp_handling_TIDYLINK
84
86
  end
85
87
 
86
88
  def handle_RDOCLINK(url) # :nodoc:
@@ -88,6 +90,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
88
90
  when /^rdoc-ref:/
89
91
  CGI.escapeHTML($')
90
92
  when /^rdoc-label:/
93
+ return CGI.escapeHTML(url) if in_tidylink_label?
91
94
  text = $'
92
95
 
93
96
  text = case text
@@ -113,11 +116,127 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
113
116
  end
114
117
  end
115
118
 
116
- ##
117
- # +target+ is a <code><br></code>
119
+ def handle_PLAIN_TEXT(text)
120
+ emit_inline(convert_string(text))
121
+ end
122
+
123
+ def handle_REGEXP_HANDLING_TEXT(text)
124
+ emit_inline(text)
125
+ end
126
+
127
+ def handle_BOLD(nodes)
128
+ emit_inline('<strong>')
129
+ super
130
+ emit_inline('</strong>')
131
+ end
132
+
133
+ def handle_EM(nodes)
134
+ emit_inline('<em>')
135
+ super
136
+ emit_inline('</em>')
137
+ end
138
+
139
+ def handle_BOLD_WORD(word)
140
+ emit_inline('<strong>')
141
+ super
142
+ emit_inline('</strong>')
143
+ end
144
+
145
+ def handle_EM_WORD(word)
146
+ emit_inline('<em>')
147
+ super
148
+ emit_inline('</em>')
149
+ end
150
+
151
+ def handle_TT(code)
152
+ emit_inline('<code>')
153
+ super
154
+ emit_inline('</code>')
155
+ end
156
+
157
+ def handle_STRIKE(nodes)
158
+ emit_inline('<del>')
159
+ super
160
+ emit_inline('</del>')
161
+ end
162
+
163
+ def handle_HARD_BREAK
164
+ emit_inline('<br>')
165
+ end
166
+
167
+ def emit_inline(text)
168
+ @inline_output << text
169
+ end
170
+
171
+ # Returns true if we are processing inside a tidy link label.
172
+
173
+ def in_tidylink_label?
174
+ @in_tidylink_label
175
+ end
118
176
 
119
- def handle_regexp_HARD_BREAK(target)
120
- '<br>'
177
+ # Special handling for tidy link labels.
178
+ # When a tidy link is <tt>{rdoc-image:path/to/image.jpg:alt text}[http://example.com]</tt>,
179
+ # label part is normally considered RDOCLINK <tt>rdoc-image:path/to/image.jpg:alt</tt> and a text <tt>" text"</tt>
180
+ # but RDoc's test code expects the whole label part to be treated as RDOCLINK only in tidy link label.
181
+ # When a tidy link is <tt>{^1}[url]</tt> or <tt>{*1}[url]</tt>, the label part needs to drop leading * or ^.
182
+ # TODO: reconsider this workaround.
183
+
184
+ def apply_tidylink_label_special_handling(label, url)
185
+ # ^1 *1 will be converted to just 1 in tidy link label.
186
+ return label[1..] if label.match?(/\A[*^]\d+\z/)
187
+
188
+ # rdoc-image in label specially allows spaces in alt text.
189
+ return handle_RDOCLINK(label) if label.start_with?('rdoc-image:')
190
+ end
191
+
192
+ def handle_TIDYLINK(label_part, url)
193
+ # When url is an image, ignore label part (maybe bug?) and just generate img tag.
194
+ if url.match?(/\Ahttps?:\/\/.+\.(png|gif|jpg|jpeg|bmp)\z/)
195
+ emit_inline("<img src=\"#{CGI.escapeHTML(url)}\" />")
196
+ return
197
+ elsif url.match?(/\Ardoc-image:/)
198
+ emit_inline(handle_RDOCLINK(url))
199
+ return
200
+ end
201
+
202
+ if label_part.size == 1 && String === label_part[0]
203
+ raw_label = label_part[0]
204
+
205
+ @in_tidylink_label = true
206
+ special = apply_tidylink_label_special_handling(raw_label, url)
207
+ @in_tidylink_label = false
208
+
209
+ if special
210
+ tag = gen_url(CGI.escapeHTML(url), special)
211
+ unless tag.empty?
212
+ emit_inline(tag)
213
+ return
214
+ end
215
+ end
216
+ end
217
+
218
+ tag = gen_url(CGI.escapeHTML(url), '')
219
+ open_tag, close_tag = tag.split(/(?=<\/a>)/, 2)
220
+ valid_tag = open_tag && close_tag
221
+ emit_inline(open_tag) if valid_tag
222
+ @in_tidylink_label = true
223
+ traverse_inline_nodes(label_part)
224
+ @in_tidylink_label = false
225
+ emit_inline(close_tag) if valid_tag
226
+ end
227
+
228
+ def handle_inline(text) # :nodoc:
229
+ @inline_output = +''
230
+ super
231
+ out = @inline_output
232
+ @inline_output = nil
233
+ out
234
+ end
235
+
236
+ # Converts suppressed cross-reference +text+ to HTML by removing the leading backslash.
237
+
238
+ def handle_regexp_SUPPRESSED_CROSSREF(text)
239
+ convert_string(text.delete_prefix('\\'))
121
240
  end
122
241
 
123
242
  ##
@@ -132,9 +251,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
132
251
  # <tt>link:</tt>::
133
252
  # Reference to a local file relative to the output directory.
134
253
 
135
- def handle_regexp_HYPERLINK(target)
136
- url = CGI.escapeHTML(target.text)
254
+ def handle_regexp_HYPERLINK(text)
255
+ return convert_string(text) if in_tidylink_label?
137
256
 
257
+ url = CGI.escapeHTML(text)
138
258
  gen_url url, url
139
259
  end
140
260
 
@@ -147,27 +267,8 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
147
267
  # For the +rdoc-label+ scheme the footnote and label prefixes are stripped
148
268
  # when creating a link. All other contents will be linked verbatim.
149
269
 
150
- def handle_regexp_RDOCLINK(target)
151
- handle_RDOCLINK target.text
152
- end
153
-
154
- ##
155
- # This +target+ is a link where the label is different from the URL
156
- # <tt>label[url]</tt> or <tt>{long label}[url]</tt>
157
-
158
- def handle_regexp_TIDYLINK(target)
159
- text = target.text
160
-
161
- if tidy_link_capturing?
162
- return finish_tidy_link(text)
163
- end
164
-
165
- if text.start_with?('{') && !text.include?('}')
166
- start_tidy_link text
167
- return ''
168
- end
169
-
170
- convert_complete_tidy_link(text)
270
+ def handle_regexp_RDOCLINK(text)
271
+ handle_RDOCLINK text
171
272
  end
172
273
 
173
274
  # :section: Visitor
@@ -181,6 +282,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
181
282
  @res = []
182
283
  @in_list_entry = []
183
284
  @list = []
285
+ @heading_ids = {}
184
286
  end
185
287
 
186
288
  ##
@@ -221,10 +323,15 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
221
323
 
222
324
  def accept_verbatim(verbatim)
223
325
  text = verbatim.text.rstrip
326
+ format = verbatim.format
224
327
 
225
328
  klass = nil
226
329
 
227
- content = if verbatim.ruby? or parseable? text then
330
+ # Apply Ruby syntax highlighting if
331
+ # - explicitly marked as Ruby (via ruby? which accepts :ruby or :rb)
332
+ # - no format specified but the text is parseable as Ruby
333
+ # Otherwise, add language class when applicable and skip Ruby highlighting
334
+ content = if verbatim.ruby? || (format.nil? && parseable?(text))
228
335
  begin
229
336
  tokens = RDoc::Parser::RipperStateLex.parse text
230
337
  klass = ' class="ruby"'
@@ -236,6 +343,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
236
343
  CGI.escapeHTML text
237
344
  end
238
345
  else
346
+ klass = " class=\"#{format}\"" if format
239
347
  CGI.escapeHTML text
240
348
  end
241
349
 
@@ -305,7 +413,14 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
305
413
  def accept_heading(heading)
306
414
  level = [6, heading.level].min
307
415
 
308
- label = heading.label @code_object
416
+ label = deduplicate_heading_id(heading.label(@code_object))
417
+ legacy_label = deduplicate_heading_id(heading.legacy_label(@code_object))
418
+
419
+ # Add legacy anchor before the heading for backward compatibility.
420
+ # This allows old links with label- prefix to still work.
421
+ if @options.output_decoration && !@options.pipe
422
+ @res << "\n<span id=\"#{legacy_label}\" class=\"legacy-anchor\"></span>"
423
+ end
309
424
 
310
425
  @res << if @options.output_decoration
311
426
  "\n<h#{level} id=\"#{label}\">"
@@ -354,6 +469,20 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
354
469
 
355
470
  # :section: Utilities
356
471
 
472
+ ##
473
+ # Returns a unique heading ID, appending -1, -2, etc. for duplicates.
474
+ # Matches GitHub's behavior for duplicate heading anchors.
475
+
476
+ def deduplicate_heading_id(id)
477
+ if @heading_ids.key?(id)
478
+ @heading_ids[id] += 1
479
+ "#{id}-#{@heading_ids[id]}"
480
+ else
481
+ @heading_ids[id] = 0
482
+ id
483
+ end
484
+ end
485
+
357
486
  ##
358
487
  # CGI-escapes +text+
359
488
 
@@ -362,14 +491,18 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
362
491
  end
363
492
 
364
493
  ##
365
- # Generate a link to +url+ with content +text+. Handles the special cases
366
- # for img: and link: described under handle_regexp_HYPERLINK
494
+ # Generates an HTML link or image tag for the given +url+ and +text+.
495
+ #
496
+ # - Image URLs (http/https/link ending in .gif, .png, .jpg, .jpeg, .bmp)
497
+ # become <img> tags
498
+ # - File references (.rb, .rdoc, .md) are converted to .html paths
499
+ # - Anchor URLs (#foo) pass through unchanged for GitHub-style header linking
500
+ # - Footnote links get wrapped in <sup> tags
367
501
 
368
502
  def gen_url(url, text)
369
503
  scheme, url, id = parse_url url
370
504
 
371
- if %w[http https link].include?(scheme) and
372
- url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
505
+ if %w[http https link].include?(scheme) && url =~ /\.(gif|png|jpg|jpeg|bmp)\z/
373
506
  "<img src=\"#{url}\" />"
374
507
  else
375
508
  if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url
@@ -381,9 +514,11 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
381
514
 
382
515
  link = "<a#{id} href=\"#{url}\">#{text}</a>"
383
516
 
384
- link = "<sup>#{link}</sup>" if /"foot/ =~ id
385
-
386
- link
517
+ if /"foot/.match?(id)
518
+ "<sup>#{link}</sup>"
519
+ else
520
+ link
521
+ end
387
522
  end
388
523
  end
389
524
 
@@ -396,15 +531,6 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
396
531
  tags[open_tag ? 0 : 1]
397
532
  end
398
533
 
399
- ##
400
- # Maps attributes to HTML tags
401
-
402
- def init_tags
403
- add_tag :BOLD, "<strong>", "</strong>"
404
- add_tag :TT, "<code>", "</code>"
405
- add_tag :EM, "<em>", "</em>"
406
- end
407
-
408
534
  ##
409
535
  # Returns the HTML tag for +list_type+, possible using a label from
410
536
  # +list_item+
@@ -454,141 +580,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
454
580
  # Converts +item+ to HTML using RDoc::Text#to_html
455
581
 
456
582
  def to_html(item)
457
- super convert_flow @am.flow item
458
- end
459
-
460
- private
461
-
462
- def convert_flow(flow_items)
463
- res = []
464
-
465
- flow_items.each do |item|
466
- case item
467
- when String
468
- append_flow_fragment res, convert_string(item)
469
- when RDoc::Markup::AttrChanger
470
- off_tags res, item
471
- on_tags res, item
472
- when RDoc::Markup::RegexpHandling
473
- append_flow_fragment res, convert_regexp_handling(item)
474
- else
475
- raise "Unknown flow element: #{item.inspect}"
476
- end
477
- end
478
-
479
- res.join
480
- end
481
-
482
- def append_flow_fragment(res, fragment)
483
- return if fragment.nil? || fragment.empty?
484
-
485
- emit_tidy_link_fragment(res, fragment)
486
- end
487
-
488
- def append_to_tidy_label(fragment)
489
- @tidy_link_buffer << fragment
490
- end
491
-
492
- ##
493
- # Matches an entire tidy link with a braced label "{label}[url]".
494
- #
495
- # Capture 1: label contents.
496
- # Capture 2: URL text.
497
- # Capture 3: trailing content.
498
- TIDY_LINK_WITH_BRACES = /\A\{(.*?)\}\[(.*?)\](.*)\z/
499
-
500
- ##
501
- # Matches the tail of a braced tidy link when the opening brace was
502
- # consumed earlier while accumulating the label text.
503
- #
504
- # Capture 1: remaining label content.
505
- # Capture 2: URL text.
506
- # Capture 3: trailing content.
507
- TIDY_LINK_WITH_BRACES_TAIL = /\A(.*?)\}\[(.*?)\](.*)\z/
508
-
509
- ##
510
- # Matches a tidy link with a single-word label "label[url]".
511
- #
512
- # Capture 1: the single-word label (no whitespace).
513
- # Capture 2: URL text between the brackets.
514
- TIDY_LINK_SINGLE_WORD = /\A(\S+)\[(.*?)\](.*)\z/
515
-
516
- def convert_complete_tidy_link(text)
517
- return text unless
518
- text =~ TIDY_LINK_WITH_BRACES or text =~ TIDY_LINK_SINGLE_WORD
519
-
520
- label = $1
521
- url = CGI.escapeHTML($2)
522
-
523
- label_html = if /^rdoc-image:/ =~ label
524
- handle_RDOCLINK(label)
525
- else
526
- render_tidy_link_label(label)
527
- end
528
-
529
- gen_url url, label_html
530
- end
531
-
532
- def emit_tidy_link_fragment(res, fragment)
533
- if tidy_link_capturing?
534
- append_to_tidy_label fragment
535
- else
536
- res << fragment
537
- end
538
- end
539
-
540
- def finish_tidy_link(text)
541
- label_tail, url, trailing = extract_tidy_link_parts(text)
542
- append_to_tidy_label CGI.escapeHTML(label_tail) unless label_tail.empty?
543
-
544
- return '' unless url
545
-
546
- label_html = @tidy_link_buffer
547
- @tidy_link_buffer = nil
548
- link = gen_url(url, label_html)
549
-
550
- return link if trailing.empty?
551
-
552
- link + CGI.escapeHTML(trailing)
553
- end
554
-
555
- def extract_tidy_link_parts(text)
556
- if text =~ TIDY_LINK_WITH_BRACES
557
- [$1, CGI.escapeHTML($2), $3]
558
- elsif text =~ TIDY_LINK_WITH_BRACES_TAIL
559
- [$1, CGI.escapeHTML($2), $3]
560
- elsif text =~ TIDY_LINK_SINGLE_WORD
561
- [$1, CGI.escapeHTML($2), $3]
562
- else
563
- [text, nil, '']
564
- end
565
- end
566
-
567
- def on_tags(res, item)
568
- each_attr_tag(item.turn_on) do |tag|
569
- emit_tidy_link_fragment(res, annotate(tag.on))
570
- @in_tt += 1 if tt? tag
571
- end
572
- end
573
-
574
- def off_tags(res, item)
575
- each_attr_tag(item.turn_off, true) do |tag|
576
- emit_tidy_link_fragment(res, annotate(tag.off))
577
- @in_tt -= 1 if tt? tag
578
- end
579
- end
580
-
581
- def start_tidy_link(text)
582
- @tidy_link_buffer = String.new
583
- append_to_tidy_label CGI.escapeHTML(text.delete_prefix('{'))
584
- end
585
-
586
- def tidy_link_capturing?
587
- !!@tidy_link_buffer
588
- end
589
-
590
- def render_tidy_link_label(label)
591
- RDoc::Markup::LinkLabelToHtml.render(label, @options, @from_path)
583
+ # Ideally, we should convert html characters at handle_PLAIN_TEXT or somewhere else,
584
+ # but we need to convert it here for now because to_html_characters converts pair of backticks to ’‘ and pair of double backticks to ”“.
585
+ # Known bugs: `...` in `<code>def f(...); end</code>` and `(c) in `<a href="(c)">` will be wrongly converted.
586
+ to_html_characters(handle_inline(item))
592
587
  end
593
588
  end
594
589
 
@@ -50,8 +50,6 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
50
50
  # will be processed as a tidylink first and will be broken.
51
51
  crossref_re = @options.hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP
52
52
  @markup.add_regexp_handling crossref_re, :CROSSREF
53
-
54
- add_regexp_handling_TIDYLINK
55
53
  end
56
54
 
57
55
  ##
@@ -63,8 +61,11 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
63
61
 
64
62
  name = name[1..-1] unless @show_hash if name[0, 1] == '#'
65
63
 
66
- if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/
67
- text ||= [CGI.unescape($'), (" at <code>#{$1}</code>" if $~.begin(1))].join("")
64
+ if !name.end_with?('+@', '-@') && match = name.match(/(.*[^#:])?@(.*)/)
65
+ context_name = match[1]
66
+ label = RDoc::Text.decode_legacy_label(match[2])
67
+ text ||= "#{label} at <code>#{context_name}</code>" if context_name
68
+ text ||= label
68
69
  code = false
69
70
  else
70
71
  text ||= name
@@ -80,9 +81,8 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
80
81
  # example, ToHtml is found, even without the <tt>RDoc::Markup::</tt> prefix,
81
82
  # because we look for it in module Markup first.
82
83
 
83
- def handle_regexp_CROSSREF(target)
84
- name = target.text
85
-
84
+ def handle_regexp_CROSSREF(name)
85
+ return convert_string(name) if in_tidylink_label?
86
86
  return name if @options.autolink_excluded_words&.include?(name)
87
87
 
88
88
  return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails
@@ -101,8 +101,8 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
101
101
  # Handles <tt>rdoc-ref:</tt> scheme links and allows RDoc::Markup::ToHtml to
102
102
  # handle other schemes.
103
103
 
104
- def handle_regexp_HYPERLINK(target)
105
- url = target.text
104
+ def handle_regexp_HYPERLINK(url)
105
+ return convert_string(url) if in_tidylink_label?
106
106
 
107
107
  case url
108
108
  when /\Ardoc-ref:/
@@ -120,12 +120,14 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
120
120
  # All other contents are handled by
121
121
  # {the superclass}[rdoc-ref:RDoc::Markup::ToHtml#handle_regexp_RDOCLINK]
122
122
 
123
- def handle_regexp_RDOCLINK(target)
124
- url = target.text
125
-
123
+ def handle_regexp_RDOCLINK(url)
126
124
  case url
127
125
  when /\Ardoc-ref:/
128
- cross_reference $', rdoc_ref: true
126
+ if in_tidylink_label?
127
+ convert_string(url)
128
+ else
129
+ cross_reference $', rdoc_ref: true
130
+ end
129
131
  else
130
132
  super
131
133
  end
@@ -169,14 +171,34 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
169
171
  end
170
172
 
171
173
  if label
174
+ # Decode legacy labels (e.g., "What-27s+Here" -> "What's Here")
175
+ # then convert to GitHub-style anchor format
176
+ decoded_label = RDoc::Text.decode_legacy_label(label)
177
+ formatted_label = RDoc::Text.to_anchor(decoded_label)
178
+
179
+ # Case 1: Path already has an anchor (e.g., method link)
180
+ # Input: C1#method@label -> path="C1.html#method-i-m"
181
+ # Output: C1.html#method-i-m-label
172
182
  if path =~ /#/
173
- path << "-label-#{label}"
174
- elsif ref&.sections&.any? { |section| label == section.title }
175
- path << "##{label}"
183
+ path << "-#{formatted_label}"
184
+
185
+ # Case 2: Label matches a section title
186
+ # Input: C1@Section -> path="C1.html", section "Section" exists
187
+ # Output: C1.html#section (uses section.aref for GitHub-style)
188
+ elsif (section = ref&.sections&.find { |s| decoded_label == s.title })
189
+ path << "##{section.aref}"
190
+
191
+ # Case 3: Ref has an aref (class/module context)
192
+ # Input: C1@heading -> path="C1.html", ref=C1 class
193
+ # Output: C1.html#class-c1-heading
176
194
  elsif ref.respond_to?(:aref)
177
- path << "##{ref.aref}-label-#{label}"
195
+ path << "##{ref.aref}-#{formatted_label}"
196
+
197
+ # Case 4: No context, just the label (e.g., TopLevel/file)
198
+ # Input: README@section -> path="README_md.html"
199
+ # Output: README_md.html#section
178
200
  else
179
- path << "#label-#{label}"
201
+ path << "##{formatted_label}"
180
202
  end
181
203
  end
182
204
 
@@ -184,73 +206,30 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
184
206
  end
185
207
  end
186
208
 
187
- def convert_flow(flow_items, &block)
188
- res = []
189
-
190
- i = 0
191
- while i < flow_items.size
192
- item = flow_items[i]
193
-
194
- case item
195
- when RDoc::Markup::AttrChanger
196
- if !tidy_link_capturing? && (text = convert_tt_crossref(flow_items, i))
197
- text = block.call(text, res) if block
198
- append_flow_fragment res, text
199
- i += 3
200
- next
201
- end
209
+ def handle_TT(code)
210
+ emit_inline(tt_cross_reference(code) || "<code>#{CGI.escapeHTML code}</code>")
211
+ end
202
212
 
203
- off_tags res, item
204
- on_tags res, item
205
- i += 1
206
- when String
207
- text = convert_string(item)
208
- text = block.call(text, res) if block
209
- append_flow_fragment res, text
210
- i += 1
211
- when RDoc::Markup::RegexpHandling
212
- text = convert_regexp_handling(item)
213
- text = block.call(text, res) if block
214
- append_flow_fragment res, text
215
- i += 1
216
- else
217
- raise "Unknown flow element: #{item.inspect}"
218
- end
213
+ # Applies additional special handling on top of the one defined in ToHtml.
214
+ # When a tidy link is <tt>{Foo}[rdoc-ref:Foo]</tt>, the label part is surrounded by <tt><code></code></tt>.
215
+ # TODO: reconsider this workaround.
216
+ def apply_tidylink_label_special_handling(label, url)
217
+ if url == "rdoc-ref:#{label}" && cross_reference(label).include?('<code>')
218
+ "<code>#{convert_string(label)}</code>"
219
+ else
220
+ super
219
221
  end
220
-
221
- res.join('')
222
222
  end
223
223
 
224
- private
225
-
226
- ##
227
- # Detects <tt>...</tt> spans that contain a single cross-reference candidate.
228
- # When the candidate occupies the whole span (aside from trailing
229
- # punctuation), the tt markup is replaced by the resolved cross-reference.
230
-
231
- def convert_tt_crossref(flow_items, index)
232
- opener = flow_items[index]
233
- return unless tt_tag?(opener.turn_on)
234
-
235
- string = flow_items[index + 1]
236
- closer = flow_items[index + 2]
237
-
238
- return unless String === string
239
- return unless RDoc::Markup::AttrChanger === closer
240
- return unless tt_tag?(closer.turn_off, true)
224
+ def tt_cross_reference(code)
225
+ return if in_tidylink_label?
241
226
 
242
227
  crossref_regexp = @options.hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP
243
- match = crossref_regexp.match(string)
244
- return unless match
245
- return unless match.begin(1).zero?
246
-
247
- trailing = match.post_match
248
- # Only convert when the remainder is punctuation/whitespace so other tt text stays literal.
249
- return unless trailing.match?(/\A[[:punct:]\s]*\z/)
250
-
251
- text = cross_reference(string)
252
- return if text == string
228
+ match = crossref_regexp.match(code)
229
+ return unless match && match.begin(1).zero?
230
+ return unless match.post_match.match?(/\A[[:punct:]\s]*\z/)
253
231
 
254
- text
232
+ ref = cross_reference(code)
233
+ ref if ref != code
255
234
  end
256
235
  end