rdoc 7.1.0 → 8.0.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +3 -4
  3. data/LICENSE.rdoc +4 -0
  4. data/README.md +43 -2
  5. data/doc/markup_reference/markdown.md +104 -3
  6. data/lib/rdoc/code_object/alias.rb +2 -8
  7. data/lib/rdoc/code_object/any_method.rb +11 -6
  8. data/lib/rdoc/code_object/attr.rb +12 -6
  9. data/lib/rdoc/code_object/class_module.rb +62 -32
  10. data/lib/rdoc/code_object/constant.rb +29 -3
  11. data/lib/rdoc/code_object/context/section.rb +30 -43
  12. data/lib/rdoc/code_object/context.rb +53 -37
  13. data/lib/rdoc/code_object/method_attr.rb +9 -15
  14. data/lib/rdoc/code_object/mixin.rb +3 -0
  15. data/lib/rdoc/code_object/top_level.rb +11 -3
  16. data/lib/rdoc/code_object.rb +2 -4
  17. data/lib/rdoc/comment.rb +1 -66
  18. data/lib/rdoc/cross_reference.rb +8 -30
  19. data/lib/rdoc/encoding.rb +3 -3
  20. data/lib/rdoc/generator/aliki.rb +17 -0
  21. data/lib/rdoc/generator/darkfish.rb +12 -6
  22. data/lib/rdoc/generator/json_index.rb +2 -2
  23. data/lib/rdoc/generator/markup.rb +56 -31
  24. data/lib/rdoc/generator/template/aliki/DESIGN.md +536 -0
  25. data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +1 -1
  26. data/lib/rdoc/generator/template/aliki/_head.rhtml +6 -1
  27. data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +8 -6
  28. data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +8 -6
  29. data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +1 -1
  30. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +2 -2
  31. data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +1 -1
  32. data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +1 -1
  33. data/lib/rdoc/generator/template/aliki/class.rhtml +61 -51
  34. data/lib/rdoc/generator/template/aliki/css/rdoc.css +365 -147
  35. data/lib/rdoc/generator/template/aliki/index.rhtml +1 -1
  36. data/lib/rdoc/generator/template/aliki/js/aliki.js +28 -20
  37. data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
  38. data/lib/rdoc/generator/template/aliki/js/search_controller.js +1 -1
  39. data/lib/rdoc/generator/template/aliki/page.rhtml +1 -1
  40. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
  41. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +2 -2
  42. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +8 -6
  43. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +8 -6
  44. data/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +1 -1
  45. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +1 -1
  46. data/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +1 -1
  47. data/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +5 -5
  48. data/lib/rdoc/generator/template/darkfish/class.rhtml +18 -21
  49. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +0 -1
  50. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +3 -3
  51. data/lib/rdoc/i18n/text.rb +3 -3
  52. data/lib/rdoc/markdown.kpeg +36 -17
  53. data/lib/rdoc/markdown.rb +324 -125
  54. data/lib/rdoc/markup/document.rb +2 -2
  55. data/lib/rdoc/markup/formatter.rb +128 -115
  56. data/lib/rdoc/markup/heading.rb +3 -6
  57. data/lib/rdoc/markup/indented_paragraph.rb +1 -1
  58. data/lib/rdoc/markup/inline_parser.rb +312 -0
  59. data/lib/rdoc/markup/list.rb +2 -2
  60. data/lib/rdoc/markup/list_item.rb +2 -2
  61. data/lib/rdoc/markup/parser.rb +1 -1
  62. data/lib/rdoc/markup/pre_process.rb +0 -25
  63. data/lib/rdoc/markup/to_ansi.rb +52 -5
  64. data/lib/rdoc/markup/to_bs.rb +23 -43
  65. data/lib/rdoc/markup/to_html.rb +279 -226
  66. data/lib/rdoc/markup/to_html_crossref.rb +122 -136
  67. data/lib/rdoc/markup/to_html_snippet.rb +65 -65
  68. data/lib/rdoc/markup/to_joined_paragraph.rb +0 -5
  69. data/lib/rdoc/markup/to_label.rb +22 -23
  70. data/lib/rdoc/markup/to_markdown.rb +62 -38
  71. data/lib/rdoc/markup/to_rdoc.rb +88 -28
  72. data/lib/rdoc/markup/to_table_of_contents.rb +1 -1
  73. data/lib/rdoc/markup/to_test.rb +9 -1
  74. data/lib/rdoc/markup/to_tt_only.rb +10 -23
  75. data/lib/rdoc/markup/verbatim.rb +1 -1
  76. data/lib/rdoc/markup.rb +8 -30
  77. data/lib/rdoc/options.rb +36 -51
  78. data/lib/rdoc/parser/c.rb +7 -6
  79. data/lib/rdoc/parser/changelog.rb +21 -0
  80. data/lib/rdoc/parser/rbs.rb +275 -0
  81. data/lib/rdoc/parser/ruby.rb +955 -2067
  82. data/lib/rdoc/parser/ruby_colorizer.rb +253 -0
  83. data/lib/rdoc/parser.rb +3 -2
  84. data/lib/rdoc/rbs_helper.rb +186 -0
  85. data/lib/rdoc/rdoc.rb +196 -24
  86. data/lib/rdoc/ri/driver.rb +8 -2
  87. data/lib/rdoc/ri/paths.rb +1 -1
  88. data/lib/rdoc/{servlet.rb → ri/servlet.rb} +5 -5
  89. data/lib/rdoc/ri.rb +4 -3
  90. data/lib/rdoc/rubygems_hook.rb +11 -11
  91. data/lib/rdoc/server.rb +460 -0
  92. data/lib/rdoc/stats.rb +147 -124
  93. data/lib/rdoc/store.rb +212 -4
  94. data/lib/rdoc/task.rb +16 -15
  95. data/lib/rdoc/text.rb +24 -117
  96. data/lib/rdoc/token_stream.rb +11 -33
  97. data/lib/rdoc/version.rb +1 -1
  98. data/lib/rdoc.rb +35 -7
  99. data/lib/rubygems_plugin.rb +2 -11
  100. data/rdoc-logo.svg +43 -0
  101. data/rdoc.gemspec +6 -4
  102. metadata +37 -23
  103. data/lib/rdoc/code_object/anon_class.rb +0 -10
  104. data/lib/rdoc/code_object/ghost_method.rb +0 -6
  105. data/lib/rdoc/code_object/meta_method.rb +0 -6
  106. data/lib/rdoc/markup/attr_changer.rb +0 -22
  107. data/lib/rdoc/markup/attr_span.rb +0 -35
  108. data/lib/rdoc/markup/attribute_manager.rb +0 -432
  109. data/lib/rdoc/markup/attributes.rb +0 -70
  110. data/lib/rdoc/markup/regexp_handling.rb +0 -40
  111. data/lib/rdoc/parser/prism_ruby.rb +0 -1100
  112. data/lib/rdoc/parser/ripper_state_lex.rb +0 -302
  113. data/lib/rdoc/parser/ruby_tools.rb +0 -163
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'cgi/escape'
3
3
  require 'cgi/util' unless defined?(CGI::EscapeExt)
4
+ require 'prism'
4
5
 
5
6
  ##
6
7
  # Outputs RDoc markup as HTML.
@@ -40,22 +41,104 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
40
41
 
41
42
  # :section:
42
43
 
44
+ # Maps an encoding to a Hash of characters properly transcoded for that
45
+ # encoding.
46
+ #
47
+ # See also encode_fallback.
48
+
49
+ TO_HTML_CHARACTERS = Hash.new do |h, encoding|
50
+ h[encoding] = {
51
+ close_dquote: encode_fallback('”', encoding, '"'),
52
+ close_squote: encode_fallback('’', encoding, '\''),
53
+ copyright: encode_fallback('©', encoding, '(c)'),
54
+ ellipsis: encode_fallback('…', encoding, '...'),
55
+ dot_ellipsis: encode_fallback('.…', encoding, '....'),
56
+ em_dash: encode_fallback('—', encoding, '---'),
57
+ en_dash: encode_fallback('–', encoding, '--'),
58
+ open_dquote: encode_fallback('“', encoding, '"'),
59
+ open_squote: encode_fallback('‘', encoding, '\''),
60
+ trademark: encode_fallback('®', encoding, '(r)'),
61
+ }
62
+ end
63
+
64
+ HTML_CHARACTER_ALIASES = {
65
+ '(c)' => :copyright,
66
+ '(C)' => :copyright,
67
+ '(r)' => :trademark,
68
+ '(R)' => :trademark,
69
+ '---' => :em_dash,
70
+ '--' => :en_dash,
71
+ '....' => :dot_ellipsis,
72
+ '...' => :ellipsis,
73
+ '``' => :open_dquote,
74
+ "''" => :close_dquote,
75
+ }
76
+
77
+ # Transcodes +character+ to +encoding+ with a +fallback+ character.
78
+
79
+ def self.encode_fallback(character, encoding, fallback)
80
+ character.encode(
81
+ encoding,
82
+ fallback: { character => fallback },
83
+ undef: :replace,
84
+ replace: fallback
85
+ )
86
+ end
87
+
88
+ # Converts ascii quote pairs to multibyte quote characters
89
+ class QuoteConverter
90
+
91
+ def initialize
92
+ @in_dquote = false
93
+ @in_squote = false
94
+ end
95
+
96
+ def convert(quote, after_word:)
97
+ case quote
98
+ when '"'
99
+ type = @in_dquote ? :close_dquote : :open_dquote
100
+ @in_dquote = !@in_dquote
101
+ when "'"
102
+ if @in_squote
103
+ type = :close_squote
104
+ @in_squote = false
105
+ elsif after_word
106
+ # Mary's dog, my parents' house: do not start paired quotes
107
+ type = :close_squote
108
+ else
109
+ type = :open_squote
110
+ @in_squote = true
111
+ end
112
+ when '`'
113
+ # Opening quote of <tt>`quoted sentence'</tt>.
114
+ # This will conflict with code blocks <tt>`puts('hello')`</tt> in the future.
115
+ if !@in_squote && !after_word
116
+ type = :open_squote
117
+ @in_squote = true
118
+ end
119
+ end
120
+ TO_HTML_CHARACTERS[quote.encoding][type] if type
121
+ end
122
+ end
123
+
43
124
  ##
44
125
  # Creates a new formatter that will output HTML
45
126
 
46
- def initialize(options, markup = nil)
47
- super
127
+ def initialize(pipe: false, output_decoration: true)
128
+ super()
48
129
 
130
+ @pipe = pipe
131
+ @output_decoration = output_decoration
49
132
  @code_object = nil
50
133
  @from_path = ''
51
134
  @in_list_entry = nil
52
135
  @list = nil
53
136
  @th = nil
137
+ @quote_converter = nil
138
+ @in_tidylink_label = false
54
139
  @hard_break = "<br>\n"
55
140
 
56
141
  init_regexp_handlings
57
-
58
- init_tags
59
142
  end
60
143
 
61
144
  # :section: Regexp Handling
@@ -72,6 +155,15 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
72
155
  # external links
73
156
  @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/,
74
157
  :HYPERLINK)
158
+
159
+ # suppress crossref: \#method \::method \ClassName \method_with_underscores
160
+ @markup.add_regexp_handling(/\\(?:[#:A-Z]|[a-z]+_[a-z0-9])/, :SUPPRESSED_CROSSREF)
161
+
162
+ @markup.add_regexp_handling(Regexp.union(HTML_CHARACTER_ALIASES.keys), :HTML_CHARACTERS)
163
+
164
+ @markup.add_regexp_handling(/\b['"`]/, :QUOTE_AFTER_WORD)
165
+ @markup.add_regexp_handling(/\B['"`]/, :QUOTE_NOT_AFTER_WORD)
166
+
75
167
  init_link_notation_regexp_handlings
76
168
  end
77
169
 
@@ -80,7 +172,6 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
80
172
 
81
173
  def init_link_notation_regexp_handlings
82
174
  add_regexp_handling_RDOCLINK
83
- add_regexp_handling_TIDYLINK
84
175
  end
85
176
 
86
177
  def handle_RDOCLINK(url) # :nodoc:
@@ -88,6 +179,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
88
179
  when /^rdoc-ref:/
89
180
  CGI.escapeHTML($')
90
181
  when /^rdoc-label:/
182
+ return CGI.escapeHTML(url) if in_tidylink_label?
91
183
  text = $'
92
184
 
93
185
  text = case text
@@ -113,11 +205,143 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
113
205
  end
114
206
  end
115
207
 
116
- ##
117
- # +target+ is a <code><br></code>
208
+ def handle_PLAIN_TEXT(text)
209
+ emit_inline(convert_string(text))
210
+ end
211
+
212
+ def handle_REGEXP_HANDLING_TEXT(text)
213
+ emit_inline(text)
214
+ end
215
+
216
+ def handle_BOLD(nodes)
217
+ emit_inline('<strong>')
218
+ super
219
+ emit_inline('</strong>')
220
+ end
221
+
222
+ def handle_EM(nodes)
223
+ emit_inline('<em>')
224
+ super
225
+ emit_inline('</em>')
226
+ end
227
+
228
+ def handle_BOLD_WORD(word)
229
+ emit_inline('<strong>')
230
+ super
231
+ emit_inline('</strong>')
232
+ end
233
+
234
+ def handle_EM_WORD(word)
235
+ emit_inline('<em>')
236
+ super
237
+ emit_inline('</em>')
238
+ end
118
239
 
119
- def handle_regexp_HARD_BREAK(target)
120
- '<br>'
240
+ def handle_TT(code)
241
+ emit_inline('<code>')
242
+ super
243
+ emit_inline('</code>')
244
+ end
245
+
246
+ def handle_STRIKE(nodes)
247
+ emit_inline('<del>')
248
+ super
249
+ emit_inline('</del>')
250
+ end
251
+
252
+ def handle_HARD_BREAK
253
+ emit_inline('<br>')
254
+ end
255
+
256
+ def emit_inline(text)
257
+ @inline_output << text
258
+ end
259
+
260
+ # Returns true if we are processing inside a tidy link label.
261
+
262
+ def in_tidylink_label?
263
+ @in_tidylink_label
264
+ end
265
+
266
+ # Special handling for tidy link labels.
267
+ # When a tidy link is <tt>{rdoc-image:path/to/image.jpg:alt text}[http://example.com]</tt>,
268
+ # label part is normally considered RDOCLINK <tt>rdoc-image:path/to/image.jpg:alt</tt> and a text <tt>" text"</tt>
269
+ # but RDoc's test code expects the whole label part to be treated as RDOCLINK only in tidy link label.
270
+ # When a tidy link is <tt>{^1}[url]</tt> or <tt>{*1}[url]</tt>, the label part needs to drop leading * or ^.
271
+ # TODO: reconsider this workaround.
272
+
273
+ def apply_tidylink_label_special_handling(label, url)
274
+ # ^1 *1 will be converted to just 1 in tidy link label.
275
+ return label[1..] if label.match?(/\A[*^]\d+\z/)
276
+
277
+ # rdoc-image in label specially allows spaces in alt text.
278
+ return handle_RDOCLINK(label) if label.start_with?('rdoc-image:')
279
+ end
280
+
281
+ def handle_TIDYLINK(label_part, url)
282
+ # When url is an image, ignore label part (maybe bug?) and just generate img tag.
283
+ if url.match?(/\Ahttps?:\/\/.+\.(png|gif|jpg|jpeg|bmp)\z/)
284
+ emit_inline("<img src=\"#{CGI.escapeHTML(url)}\" />")
285
+ return
286
+ elsif url.match?(/\Ardoc-image:/)
287
+ emit_inline(handle_RDOCLINK(url))
288
+ return
289
+ end
290
+
291
+ if label_part.size == 1 && String === label_part[0]
292
+ raw_label = label_part[0]
293
+
294
+ @in_tidylink_label = true
295
+ special = apply_tidylink_label_special_handling(raw_label, url)
296
+ @in_tidylink_label = false
297
+
298
+ if special
299
+ tag = gen_url(CGI.escapeHTML(url), special)
300
+ unless tag.empty?
301
+ emit_inline(tag)
302
+ return
303
+ end
304
+ end
305
+ end
306
+
307
+ tag = gen_url(CGI.escapeHTML(url), '')
308
+ open_tag, close_tag = tag.split(/(?=<\/a>)/, 2)
309
+ valid_tag = open_tag && close_tag
310
+ emit_inline(open_tag) if valid_tag
311
+ @in_tidylink_label = true
312
+ traverse_inline_nodes(label_part)
313
+ @in_tidylink_label = false
314
+ emit_inline(close_tag) if valid_tag
315
+ end
316
+
317
+ def handle_inline(text) # :nodoc:
318
+ @inline_output = +''
319
+ @quote_converter = QuoteConverter.new
320
+ super
321
+ out = @inline_output
322
+ @inline_output = nil
323
+ @quote_converter = nil
324
+ out
325
+ end
326
+
327
+ # Converts <tt>(c), (r), --, --- , ..., ...., ``, ''</tt> to HTML characters.
328
+ def handle_regexp_HTML_CHARACTERS(text)
329
+ name = HTML_CHARACTER_ALIASES[text]
330
+ TO_HTML_CHARACTERS[text.encoding][name] if name
331
+ end
332
+
333
+ def handle_regexp_QUOTE_NOT_AFTER_WORD(text)
334
+ @quote_converter.convert(text, after_word: false) || convert_string(text)
335
+ end
336
+
337
+ def handle_regexp_QUOTE_AFTER_WORD(text)
338
+ @quote_converter.convert(text, after_word: true) || convert_string(text)
339
+ end
340
+
341
+ # Converts suppressed cross-reference +text+ to HTML by removing the leading backslash.
342
+
343
+ def handle_regexp_SUPPRESSED_CROSSREF(text)
344
+ convert_string(text.delete_prefix('\\'))
121
345
  end
122
346
 
123
347
  ##
@@ -132,9 +356,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
132
356
  # <tt>link:</tt>::
133
357
  # Reference to a local file relative to the output directory.
134
358
 
135
- def handle_regexp_HYPERLINK(target)
136
- url = CGI.escapeHTML(target.text)
359
+ def handle_regexp_HYPERLINK(text)
360
+ return convert_string(text) if in_tidylink_label?
137
361
 
362
+ url = CGI.escapeHTML(text)
138
363
  gen_url url, url
139
364
  end
140
365
 
@@ -147,27 +372,8 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
147
372
  # For the +rdoc-label+ scheme the footnote and label prefixes are stripped
148
373
  # when creating a link. All other contents will be linked verbatim.
149
374
 
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)
375
+ def handle_regexp_RDOCLINK(text)
376
+ handle_RDOCLINK text
171
377
  end
172
378
 
173
379
  # :section: Visitor
@@ -181,6 +387,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
181
387
  @res = []
182
388
  @in_list_entry = []
183
389
  @list = []
390
+ @heading_ids = {}
184
391
  end
185
392
 
186
393
  ##
@@ -209,13 +416,22 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
209
416
  def accept_paragraph(paragraph)
210
417
  @res << "\n<p>"
211
418
  text = paragraph.text @hard_break
212
- text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K\r?\n(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) {
419
+ text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K(?:\r?\n)+(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) {
213
420
  defined?($2) && ' '
214
421
  }
215
422
  @res << to_html(text)
216
423
  @res << "</p>\n"
217
424
  end
218
425
 
426
+ # Generate syntax highlighted html for ruby-like text.
427
+
428
+ def parsable_text_to_html(text)
429
+ tokens = RDoc::Parser::RubyColorizer.colorize(text)
430
+ result = RDoc::TokenStream.to_html tokens
431
+ result = result + "\n" unless result.end_with?("\n")
432
+ result
433
+ end
434
+
219
435
  ##
220
436
  # Adds +verbatim+ to the output
221
437
 
@@ -223,29 +439,19 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
223
439
  text = verbatim.text.rstrip
224
440
  format = verbatim.format
225
441
 
226
- klass = nil
227
-
228
442
  # Apply Ruby syntax highlighting if
229
443
  # - explicitly marked as Ruby (via ruby? which accepts :ruby or :rb)
230
444
  # - no format specified but the text is parseable as Ruby
231
445
  # Otherwise, add language class when applicable and skip Ruby highlighting
232
- content = if verbatim.ruby? || (format.nil? && parseable?(text))
233
- begin
234
- tokens = RDoc::Parser::RipperStateLex.parse text
235
- klass = ' class="ruby"'
236
-
237
- result = RDoc::TokenStream.to_html tokens
238
- result = result + "\n" unless "\n" == result[-1]
239
- result
240
- rescue
241
- CGI.escapeHTML text
242
- end
243
- else
244
- klass = " class=\"#{format}\"" if format
245
- CGI.escapeHTML text
246
- end
247
-
248
- if @options.pipe then
446
+ if verbatim.ruby? || (format.nil? && parseable?(text))
447
+ content = parsable_text_to_html(text)
448
+ klass = ' class="ruby"'
449
+ else
450
+ content = CGI.escapeHTML text
451
+ klass = " class=\"#{format}\"" if format
452
+ end
453
+
454
+ if @pipe
249
455
  @res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n"
250
456
  else
251
457
  @res << "\n<pre#{klass}>#{content}</pre>\n"
@@ -311,22 +517,22 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
311
517
  def accept_heading(heading)
312
518
  level = [6, heading.level].min
313
519
 
314
- label = heading.label @code_object
315
- legacy_label = heading.legacy_label @code_object
520
+ label = deduplicate_heading_id(heading.label(@code_object))
521
+ legacy_label = deduplicate_heading_id(heading.legacy_label(@code_object))
316
522
 
317
523
  # Add legacy anchor before the heading for backward compatibility.
318
524
  # This allows old links with label- prefix to still work.
319
- if @options.output_decoration && !@options.pipe
525
+ if @output_decoration && !@pipe
320
526
  @res << "\n<span id=\"#{legacy_label}\" class=\"legacy-anchor\"></span>"
321
527
  end
322
528
 
323
- @res << if @options.output_decoration
529
+ @res << if @output_decoration
324
530
  "\n<h#{level} id=\"#{label}\">"
325
531
  else
326
532
  "\n<h#{level}>"
327
533
  end
328
534
 
329
- if @options.pipe
535
+ if @pipe
330
536
  @res << to_html(heading.text)
331
537
  else
332
538
  @res << "<a href=\"##{label}\">#{to_html(heading.text)}</a>"
@@ -367,6 +573,20 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
367
573
 
368
574
  # :section: Utilities
369
575
 
576
+ ##
577
+ # Returns a unique heading ID, appending -1, -2, etc. for duplicates.
578
+ # Matches GitHub's behavior for duplicate heading anchors.
579
+
580
+ def deduplicate_heading_id(id)
581
+ if @heading_ids.key?(id)
582
+ @heading_ids[id] += 1
583
+ "#{id}-#{@heading_ids[id]}"
584
+ else
585
+ @heading_ids[id] = 0
586
+ id
587
+ end
588
+ end
589
+
370
590
  ##
371
591
  # CGI-escapes +text+
372
592
 
@@ -415,16 +635,6 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
415
635
  tags[open_tag ? 0 : 1]
416
636
  end
417
637
 
418
- ##
419
- # Maps attributes to HTML tags
420
-
421
- def init_tags
422
- add_tag :BOLD, "<strong>", "</strong>"
423
- add_tag :TT, "<code>", "</code>"
424
- add_tag :EM, "<em>", "</em>"
425
- add_tag :STRIKE, "<del>", "</del>"
426
- end
427
-
428
638
  ##
429
639
  # Returns the HTML tag for +list_type+, possible using a label from
430
640
  # +list_item+
@@ -460,170 +670,13 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
460
670
  # Returns true if text is valid ruby syntax
461
671
 
462
672
  def parseable?(text)
463
- verbose, $VERBOSE = $VERBOSE, nil
464
- catch(:valid) do
465
- eval("BEGIN { throw :valid, true }\n#{text}")
466
- end
467
- rescue SyntaxError
468
- false
469
- ensure
470
- $VERBOSE = verbose
673
+ Prism.parse_success?(text)
471
674
  end
472
675
 
473
676
  ##
474
677
  # Converts +item+ to HTML using RDoc::Text#to_html
475
678
 
476
679
  def to_html(item)
477
- super convert_flow @am.flow item
478
- end
479
-
480
- private
481
-
482
- def convert_flow(flow_items)
483
- res = []
484
-
485
- flow_items.each do |item|
486
- case item
487
- when String
488
- append_flow_fragment res, convert_string(item)
489
- when RDoc::Markup::AttrChanger
490
- off_tags res, item
491
- on_tags res, item
492
- when RDoc::Markup::RegexpHandling
493
- append_flow_fragment res, convert_regexp_handling(item)
494
- else
495
- raise "Unknown flow element: #{item.inspect}"
496
- end
497
- end
498
-
499
- res.join
500
- end
501
-
502
- def append_flow_fragment(res, fragment)
503
- return if fragment.nil? || fragment.empty?
504
-
505
- emit_tidy_link_fragment(res, fragment)
506
- end
507
-
508
- def append_to_tidy_label(fragment)
509
- @tidy_link_buffer << fragment
510
- end
511
-
512
- ##
513
- # Matches an entire tidy link with a braced label "{label}[url]".
514
- #
515
- # Capture 1: label contents.
516
- # Capture 2: URL text.
517
- # Capture 3: trailing content.
518
- TIDY_LINK_WITH_BRACES = /\A\{(.*?)\}\[(.*?)\](.*)\z/
519
-
520
- ##
521
- # Matches the tail of a braced tidy link when the opening brace was
522
- # consumed earlier while accumulating the label text.
523
- #
524
- # Capture 1: remaining label content.
525
- # Capture 2: URL text.
526
- # Capture 3: trailing content.
527
- TIDY_LINK_WITH_BRACES_TAIL = /\A(.*?)\}\[(.*?)\](.*)\z/
528
-
529
- ##
530
- # Matches a tidy link with a single-word label "label[url]".
531
- #
532
- # Capture 1: the single-word label (no whitespace).
533
- # Capture 2: URL text between the brackets.
534
- TIDY_LINK_SINGLE_WORD = /\A(\S+)\[(.*?)\](.*)\z/
535
-
536
- def convert_complete_tidy_link(text)
537
- return text unless
538
- text =~ TIDY_LINK_WITH_BRACES or text =~ TIDY_LINK_SINGLE_WORD
539
-
540
- label = $1
541
- url = CGI.escapeHTML($2)
542
-
543
- label_html = if /^rdoc-image:/ =~ label
544
- handle_RDOCLINK(label)
545
- else
546
- render_tidy_link_label(label)
547
- end
548
-
549
- gen_url url, label_html
550
- end
551
-
552
- def emit_tidy_link_fragment(res, fragment)
553
- if tidy_link_capturing?
554
- append_to_tidy_label fragment
555
- else
556
- res << fragment
557
- end
558
- end
559
-
560
- def finish_tidy_link(text)
561
- label_tail, url, trailing = extract_tidy_link_parts(text)
562
- append_to_tidy_label CGI.escapeHTML(label_tail) unless label_tail.empty?
563
-
564
- return '' unless url
565
-
566
- label_html = @tidy_link_buffer
567
- @tidy_link_buffer = nil
568
- link = gen_url(url, label_html)
569
-
570
- return link if trailing.empty?
571
-
572
- link + CGI.escapeHTML(trailing)
573
- end
574
-
575
- def extract_tidy_link_parts(text)
576
- if text =~ TIDY_LINK_WITH_BRACES
577
- [$1, CGI.escapeHTML($2), $3]
578
- elsif text =~ TIDY_LINK_WITH_BRACES_TAIL
579
- [$1, CGI.escapeHTML($2), $3]
580
- elsif text =~ TIDY_LINK_SINGLE_WORD
581
- [$1, CGI.escapeHTML($2), $3]
582
- else
583
- [text, nil, '']
584
- end
585
- end
586
-
587
- def on_tags(res, item)
588
- each_attr_tag(item.turn_on) do |tag|
589
- emit_tidy_link_fragment(res, annotate(tag.on))
590
- @in_tt += 1 if tt? tag
591
- end
592
- end
593
-
594
- def off_tags(res, item)
595
- each_attr_tag(item.turn_off, true) do |tag|
596
- emit_tidy_link_fragment(res, annotate(tag.off))
597
- @in_tt -= 1 if tt? tag
598
- end
599
- end
600
-
601
- def start_tidy_link(text)
602
- @tidy_link_buffer = String.new
603
- append_to_tidy_label CGI.escapeHTML(text.delete_prefix('{'))
604
- end
605
-
606
- def tidy_link_capturing?
607
- !!@tidy_link_buffer
608
- end
609
-
610
- def render_tidy_link_label(label)
611
- RDoc::Markup::LinkLabelToHtml.render(label, @options, @from_path)
612
- end
613
- end
614
-
615
- ##
616
- # Formatter dedicated to rendering tidy link labels without mutating the
617
- # calling formatter's state.
618
-
619
- class RDoc::Markup::LinkLabelToHtml < RDoc::Markup::ToHtml
620
- def self.render(label, options, from_path)
621
- new(options, from_path).to_html(label)
622
- end
623
-
624
- def initialize(options, from_path = nil)
625
- super(options)
626
-
627
- self.from_path = from_path if from_path
680
+ handle_inline(item)
628
681
  end
629
682
  end