Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protect included code snippets from "recursive" highlighting #1441

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
28 changes: 19 additions & 9 deletions lib/yard/templates/helpers/html_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def urlencode(text)
# @param [Symbol] markup examples are +:markdown+, +:textile+, +:rdoc+.
# To add a custom markup type, see {MarkupHelper}
# @return [String] the HTML
def htmlify(text, markup = options.markup)
def htmlify(text, markup = options.markup, internal = false)
markup_meth = "html_markup_#{markup}"
return text unless respond_to?(markup_meth)
return "" unless text
Expand All @@ -66,7 +66,7 @@ def htmlify(text, markup = options.markup)
end
html = resolve_links(html)
unless [:text, :none, :pre, :ruby].include?(markup)
html = parse_codeblocks(html)
html = parse_codeblocks(html, internal)
end
html
end
Expand Down Expand Up @@ -202,6 +202,11 @@ def html_syntax_highlight(source, type = nil)

new_type, source = parse_lang_for_codeblock(source)
type ||= new_type || :ruby

if type.to_s == "ruby" && source =~ /<span\s+class=/
return source
end

meth = "html_syntax_highlight_#{type}"
respond_to?(meth) ? send(meth, source) : h(source)
end
Expand Down Expand Up @@ -294,7 +299,7 @@ def link_include_object(obj)

# Inserts an include link while respecting inlining
def insert_include(text, markup = options.markup)
htmlify(text, markup).gsub(%r{\A\s*<p>|</p>\s*\Z}, '')
htmlify(text, markup, true).gsub(%r{\A\s*<p>|</p>\s*\Z}, '')
end

# (see BaseHelper#link_object)
Expand Down Expand Up @@ -637,20 +642,25 @@ def parse_lang_for_codeblock(source)
# @param [String] html the html to search for code in
# @return [String] highlighted html
# @see #html_syntax_highlight
def parse_codeblocks(html)
def parse_codeblocks(html, internal = false)
html.gsub(%r{<pre((?:\s+\w+="(?:.+?)")*)\s*>(?:\s*<code((?:\s+\w+="(?:.+?)")*)\s*>)?(.+?)(?:</code>\s*)?</pre>}m) do
string = $3
pre_match, code_match, string = $1, $2, $3

# handle !!!LANG prefix to send to html_syntax_highlight_LANG
language, = parse_lang_for_codeblock(string)
language ||= detect_lang_in_codeblock_attributes($1, $2)
language ||= detect_lang_in_codeblock_attributes(pre_match, code_match)
language ||= object.source_type

if options.highlight
pre_attrs = [%(class="code #{language}")]

if options.highlight && /\bdata-highlighted="true"/ !~ pre_match
string = html_syntax_highlight(CGI.unescapeHTML(string), language)
pre_attrs << 'data-highlighted="true"' if internal
end
classes = ['code', language].compact.join(' ')
%(<pre class="#{classes}"><code class="#{language}">#{string}</code></pre>)

pre_attrs = pre_attrs.compact.join(' ')

%(<pre #{pre_attrs}><code class="#{language}">#{string}</code></pre>)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/yard/templates/helpers/html_syntax_highlight_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def html_syntax_highlight_ruby_ripper(source)
end
output
rescue Parser::ParserSyntaxError
source =~ /^<span\s+class=/ ? source : h(source)
h(source)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a subtle but problematic change that I think will cause a regression. This change was explicitly added to permit pre-formatted HTML extra-files to be added via: yard - OTHER.html where OTHER.html might include:

<pre class="code ruby"><code class="ruby"><span class='kw'>return</span></code></pre>

image

The output of this section would generate double encoded output if you ran it through this block:

image

If removing this HTML detection causes a failure in your implementation, you may have to rethink part of your approach.

end

def html_syntax_highlight_ruby_legacy(source)
Expand Down
30 changes: 30 additions & 0 deletions spec/templates/helpers/html_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,30 @@ def options
expect(htmlify("test {include:file:foo.rdoc}", :rdoc).gsub(/[\r?\n]+/, '')).to eq '<p>test HI</p>'
end

it "preserves syntax highlighting in {include:} snippets" do
load_markup_provider(:rdoc)
expect(File).to receive(:file?).with('foo.rdoc').and_return(true)
expect(File).to receive(:read).with('foo.rdoc').and_return(
" !!!ruby\n x = 1\n"
)
expect(htmlify("{include:file:foo.rdoc}", :rdoc).gsub(/[\r?\n]+/, '')).
not_to include("&lt;")
end

it "escapes {include:} html snippets only once" do
expect(self).to receive(:html_syntax_highlight_html) do |source|
%(<strong>#{CGI.escapeHTML(source)}</strong>)
end.at_least(:once)

load_markup_provider(:rdoc)
expect(File).to receive(:file?).with('foo.md').and_return(true)
expect(File).to receive(:read).with('foo.md').and_return(
" !!!html\n <h1>\n"
)
expect(htmlify("{include:file:foo.md}", :markdown).gsub(/[\r?\n]+/, '')).
to include(%(<strong>&lt;h1&gt;</strong>))
end

it "does not autolink URLs inside of {} (markdown specific)" do
log.enter_level(Logger::FATAL) do
pending 'This test depends on markdown' unless markup_class(:markdown)
Expand Down Expand Up @@ -648,6 +672,12 @@ def subject.respond_to?(method, include_all = false)
)
end

it "wraps but doesn't alter ruby snippets that already contain highlighting tags" do
expect(subject.htmlify('<pre lang="ruby"><span class="kw">for</span></pre>', :html)).to eq(
'<pre class="code ruby"><code class="ruby"><span class="kw">for</span></code></pre>'
)
end

it "highlights source when matching a pre lang= tag" do
expect(subject.htmlify('<pre lang="foo"><code>x = 1</code></pre>', :html)).to eq(
'<pre class="code foo"><code class="foo">x = 1</code></pre>'
Expand Down