Skip to content

Commit bd9cada

Browse files
authored
Prevent style rendering in code blocks (#1536)
### Before <img width="110" height="38" alt="Screenshot 2025-12-31 at 23 44 19" src="https://github.com/user-attachments/assets/43819b7d-0e87-44f7-9ab8-51ce53e2e50a" /> ### After <img width="121" height="31" alt="Screenshot 2025-12-31 at 23 43 58" src="https://github.com/user-attachments/assets/a08919cb-81b4-4092-bd02-c10fbd2852c8" />
1 parent 41889a4 commit bd9cada

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

lib/rdoc/markup/attribute_manager.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def initialize
9595
add_html "b", :BOLD, true
9696
add_html "tt", :TT, true
9797
add_html "code", :TT, true
98+
99+
@word_pair_chars = @matching_word_pairs.keys.join
100+
101+
# Matches a word pair delimiter (*, _, +) that is NOT already protected.
102+
# Used by #protect_code_markup to escape delimiters inside <code>/<tt> tags.
103+
@unprotected_word_pair_regexp = /([#{@word_pair_chars}])(?!#{PROTECT_ATTR})/
98104
end
99105

100106
##
@@ -164,7 +170,7 @@ def convert_attrs_matching_word_pairs(str, attrs, exclusive)
164170
}.keys
165171
return if tags.empty?
166172
tags = "[#{tags.join("")}](?!#{PROTECT_ATTR})"
167-
all_tags = "[#{@matching_word_pairs.keys.join("")}](?!#{PROTECT_ATTR})"
173+
all_tags = "[#{@word_pair_chars}](?!#{PROTECT_ATTR})"
168174

169175
re = /(?:^|\W|#{all_tags})\K(#{tags})(\1*[#\\]?[\w:#{PROTECT_ATTR}.\/\[\]-]+?\S?)\1(?!\1)(?=#{all_tags}|\W|$)/
170176

@@ -245,6 +251,20 @@ def mask_protected_sequences
245251
@str.gsub!(/\\(\\[#{Regexp.escape @protectable.join}])/m, "\\1")
246252
end
247253

254+
##
255+
# Protects word pair delimiters (*, _, +) inside
256+
# <code> and <tt> tags from being processed as inline formatting.
257+
# For example, *bold* in +*bold*+ will NOT be rendered as bold.
258+
259+
def protect_code_markup
260+
@str.gsub!(/<(code|tt)>(.*?)<\/\1>/im) do
261+
tag = $1
262+
content = $2
263+
escaped = content.gsub(@unprotected_word_pair_regexp, "\\1#{PROTECT_ATTR}")
264+
"<#{tag}>#{escaped}</#{tag}>"
265+
end
266+
end
267+
248268
##
249269
# Unescapes regexp handling sequences of text
250270

@@ -308,6 +328,7 @@ def flow(str)
308328
@str = str.dup
309329

310330
mask_protected_sequences
331+
protect_code_markup
311332

312333
@attrs = RDoc::Markup::AttrSpan.new @str.length, @exclusive_bitmap
313334

test/rdoc/markup/attribute_manager_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,30 @@ def test_convert_attrs_ignores_code
202202
assert_equal 'foo <CODE>__send__</CODE> bar', output('foo <code>__send__</code> bar')
203203
end
204204

205+
def test_convert_attrs_ignores_bold_inside_code
206+
assert_equal 'foo <CODE>*bold*</CODE> bar', output('foo <code>*bold*</code> bar')
207+
end
208+
209+
def test_convert_attrs_ignores_em_inside_code
210+
assert_equal 'foo <CODE>_em_</CODE> bar', output('foo <code>_em_</code> bar')
211+
end
212+
213+
def test_convert_attrs_ignores_tt_inside_code
214+
assert_equal 'foo <CODE>+tt+</CODE> bar', output('foo <code>+tt+</code> bar')
215+
end
216+
217+
def test_convert_attrs_ignores_bold_inside_tt
218+
assert_equal 'foo <CODE>*bold*</CODE> bar', output('foo <tt>*bold*</tt> bar')
219+
end
220+
221+
def test_convert_attrs_ignores_em_inside_tt
222+
assert_equal 'foo <CODE>_em_</CODE> bar', output('foo <tt>_em_</tt> bar')
223+
end
224+
225+
def test_convert_attrs_ignores_tt_inside_tt
226+
assert_equal 'foo <CODE>+tt+</CODE> bar', output('foo <tt>+tt+</tt> bar')
227+
end
228+
205229
def test_convert_attrs_ignores_tt
206230
assert_equal 'foo <CODE>__send__</CODE> bar', output('foo <tt>__send__</tt> bar')
207231
end

test/rdoc/rdoc_markdown_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,16 @@ def test_markdown_link_with_styled_label
12831283
assert_includes html, '<a href="https://example.com">Link to <code>Foo</code> and <code>Bar</code> and <code>Baz</code></a>'
12841284
end
12851285

1286+
def test_code_span_preserves_inline_formatting_chars
1287+
# Code spans should display formatting characters literally, not as styling
1288+
doc = parse "Code: `*bold*` and `_em_` and `+tt+`"
1289+
html = @to_html.convert doc
1290+
1291+
assert_includes html, '<code>*bold*</code>'
1292+
assert_includes html, '<code>_em_</code>'
1293+
assert_includes html, '<code>+tt+</code>'
1294+
end
1295+
12861296
def parse(text)
12871297
@parser.parse text
12881298
end

0 commit comments

Comments
 (0)