Commit ad534355 authored by blackst0ne's avatar blackst0ne Committed by Bob Van Landuyt

Remove Banzai::Renderer::CommonMark::HTML renderer

parent 8da8bc98
---
name: use_cmark_renderer
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61792
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345744
milestone: '14.6'
type: development
group: group::project management
default_enabled: false
...@@ -16,37 +16,60 @@ module Banzai ...@@ -16,37 +16,60 @@ module Banzai
# can be used for a single render). So you get `id=fn1-4335` and `id=fn2-4335`. # can be used for a single render). So you get `id=fn1-4335` and `id=fn2-4335`.
# #
class FootnoteFilter < HTML::Pipeline::Filter class FootnoteFilter < HTML::Pipeline::Filter
INTEGER_PATTERN = /\A\d+\z/.freeze FOOTNOTE_ID_PREFIX = 'fn-'
FOOTNOTE_ID_PREFIX = 'fn' FOOTNOTE_LINK_ID_PREFIX = 'fnref-'
FOOTNOTE_LINK_ID_PREFIX = 'fnref' FOOTNOTE_LI_REFERENCE_PATTERN = /\A#{FOOTNOTE_ID_PREFIX}.+\z/.freeze
FOOTNOTE_LI_REFERENCE_PATTERN = /\A#{FOOTNOTE_ID_PREFIX}\d+\z/.freeze FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}.+\z/.freeze
FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}\d+\z/.freeze
FOOTNOTE_START_NUMBER = 1 CSS_SECTION = "ol > li a[href^=\"\##{FOOTNOTE_LINK_ID_PREFIX}\"]"
CSS_SECTION = "ol > li[id=#{FOOTNOTE_ID_PREFIX}#{FOOTNOTE_START_NUMBER}]"
XPATH_SECTION = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION).freeze XPATH_SECTION = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION).freeze
CSS_FOOTNOTE = 'sup > a[id]' CSS_FOOTNOTE = 'sup > a[id]'
XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze
# only needed when feature flag use_cmark_renderer is turned off
INTEGER_PATTERN = /\A\d+\z/.freeze
FOOTNOTE_ID_PREFIX_OLD = 'fn'
FOOTNOTE_LINK_ID_PREFIX_OLD = 'fnref'
FOOTNOTE_LI_REFERENCE_PATTERN_OLD = /\A#{FOOTNOTE_ID_PREFIX_OLD}\d+\z/.freeze
FOOTNOTE_LINK_REFERENCE_PATTERN_OLD = /\A#{FOOTNOTE_LINK_ID_PREFIX_OLD}\d+\z/.freeze
FOOTNOTE_START_NUMBER = 1
CSS_SECTION_OLD = "ol > li[id=#{FOOTNOTE_ID_PREFIX_OLD}#{FOOTNOTE_START_NUMBER}]"
XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze
def call def call
return doc unless first_footnote = doc.at_xpath(XPATH_SECTION) xpath_section = Feature.enabled?(:use_cmark_renderer) ? XPATH_SECTION : XPATH_SECTION_OLD
return doc unless first_footnote = doc.at_xpath(xpath_section)
# Sanitization stripped off the section wrapper - add it back in # Sanitization stripped off the section wrapper - add it back in
first_footnote.parent.wrap('<section class="footnotes">') if Feature.enabled?(:use_cmark_renderer)
first_footnote.parent.parent.parent.wrap('<section class="footnotes" data-footnotes>')
else
first_footnote.parent.wrap('<section class="footnotes">')
end
rand_suffix = "-#{random_number}" rand_suffix = "-#{random_number}"
modified_footnotes = {} modified_footnotes = {}
doc.xpath(XPATH_FOOTNOTE).each do |link_node| doc.xpath(XPATH_FOOTNOTE).each do |link_node|
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX) if Feature.enabled?(:use_cmark_renderer)
node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath("li[id=#{fn_id(ref_num)}]") ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
else
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD)
end
node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath("li[id=#{fn_id(ref_num)}]")
footnote_node = doc.at_xpath(node_xpath) footnote_node = doc.at_xpath(node_xpath)
if INTEGER_PATTERN.match?(ref_num) && (footnote_node || modified_footnotes[ref_num]) if footnote_node || modified_footnotes[ref_num]
next if Feature.disabled?(:use_cmark_renderer) && !INTEGER_PATTERN.match?(ref_num)
link_node[:href] += rand_suffix link_node[:href] += rand_suffix
link_node[:id] += rand_suffix link_node[:id] += rand_suffix
# Sanitization stripped off class - add it back in # Sanitization stripped off class - add it back in
link_node.parent.append_class('footnote-ref') link_node.parent.append_class('footnote-ref')
link_node['data-footnote-ref'] = nil if Feature.enabled?(:use_cmark_renderer)
unless modified_footnotes[ref_num] unless modified_footnotes[ref_num]
footnote_node[:id] += rand_suffix footnote_node[:id] += rand_suffix
...@@ -55,6 +78,7 @@ module Banzai ...@@ -55,6 +78,7 @@ module Banzai
if backref_node if backref_node
backref_node[:href] += rand_suffix backref_node[:href] += rand_suffix
backref_node.append_class('footnote-backref') backref_node.append_class('footnote-backref')
backref_node['data-footnote-backref'] = nil if Feature.enabled?(:use_cmark_renderer)
end end
modified_footnotes[ref_num] = true modified_footnotes[ref_num] = true
...@@ -72,11 +96,13 @@ module Banzai ...@@ -72,11 +96,13 @@ module Banzai
end end
def fn_id(num) def fn_id(num)
"#{FOOTNOTE_ID_PREFIX}#{num}" prefix = Feature.enabled?(:use_cmark_renderer) ? FOOTNOTE_ID_PREFIX : FOOTNOTE_ID_PREFIX_OLD
"#{prefix}#{num}"
end end
def fnref_id(num) def fnref_id(num)
"#{FOOTNOTE_LINK_ID_PREFIX}#{num}" prefix = Feature.enabled?(:use_cmark_renderer) ? FOOTNOTE_LINK_ID_PREFIX : FOOTNOTE_LINK_ID_PREFIX_OLD
"#{prefix}#{num}"
end end
end end
end end
......
...@@ -13,8 +13,7 @@ module Banzai ...@@ -13,8 +13,7 @@ module Banzai
EXTENSIONS = [ EXTENSIONS = [
:autolink, # provides support for automatically converting URLs to anchor tags. :autolink, # provides support for automatically converting URLs to anchor tags.
:strikethrough, # provides support for strikethroughs. :strikethrough, # provides support for strikethroughs.
:table, # provides support for tables. :table # provides support for tables.
:tagfilter # strips out several "unsafe" HTML tags from being used: https://github.github.com/gfm/#disallowed-raw-html-extension-
].freeze ].freeze
PARSE_OPTIONS = [ PARSE_OPTIONS = [
...@@ -23,36 +22,63 @@ module Banzai ...@@ -23,36 +22,63 @@ module Banzai
:VALIDATE_UTF8 # replace illegal sequences with the replacement character U+FFFD. :VALIDATE_UTF8 # replace illegal sequences with the replacement character U+FFFD.
].freeze ].freeze
RENDER_OPTIONS_C = [
:GITHUB_PRE_LANG, # use GitHub-style <pre lang> for fenced code blocks.
:FOOTNOTES, # render footnotes.
:FULL_INFO_STRING, # include full info strings of code blocks in separate attribute.
:UNSAFE # allow raw/custom HTML and unsafe links.
].freeze
# The `:GITHUB_PRE_LANG` option is not used intentionally because # The `:GITHUB_PRE_LANG` option is not used intentionally because
# it renders a fence block with language as `<pre lang="LANG"><code>some code\n</code></pre>` # it renders a fence block with language as `<pre lang="LANG"><code>some code\n</code></pre>`
# while GitLab's syntax is `<pre><code lang="LANG">some code\n</code></pre>`. # while GitLab's syntax is `<pre><code lang="LANG">some code\n</code></pre>`.
# If in the future the syntax is about to be made GitHub-compatible, please, add `:GITHUB_PRE_LANG` render option below # If in the future the syntax is about to be made GitHub-compatible, please, add `:GITHUB_PRE_LANG` render option below
# and remove `code_block` method from `lib/banzai/renderer/common_mark/html.rb`. # and remove `code_block` method from `lib/banzai/renderer/common_mark/html.rb`.
RENDER_OPTIONS = [ RENDER_OPTIONS_RUBY = [
# as of commonmarker 0.18.0, we need to use :UNSAFE to get the same as the original :DEFAULT # as of commonmarker 0.18.0, we need to use :UNSAFE to get the same as the original :DEFAULT
# https://github.com/gjtorikian/commonmarker/pull/81 # https://github.com/gjtorikian/commonmarker/pull/81
:UNSAFE :UNSAFE # allow raw/custom HTML and unsafe links.
].freeze
RENDER_OPTIONS_SOURCEPOS = RENDER_OPTIONS + [
:SOURCEPOS # enable embedding of source position information
].freeze ].freeze
def initialize(context) def initialize(context)
@context = context @context = context
@renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) @renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer)
end end
def render(text) def render(text)
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, EXTENSIONS) if Feature.enabled?(:use_cmark_renderer)
CommonMarker.render_html(text, render_options, extensions)
else
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions)
@renderer.render(doc) @renderer.render(doc)
end
end end
private private
def extensions
if Feature.enabled?(:use_cmark_renderer)
EXTENSIONS
else
EXTENSIONS + [
:tagfilter # strips out several "unsafe" HTML tags from being used: https://github.github.com/gfm/#disallowed-raw-html-extension-
].freeze
end
end
def render_options def render_options
@context[:no_sourcepos] ? RENDER_OPTIONS : RENDER_OPTIONS_SOURCEPOS @context[:no_sourcepos] ? render_options_no_sourcepos : render_options_sourcepos
end
def render_options_no_sourcepos
Feature.enabled?(:use_cmark_renderer) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
end
def render_options_sourcepos
render_options_no_sourcepos + [
:SOURCEPOS # enable embedding of source position information
].freeze
end end
end end
end end
......
...@@ -8,10 +8,8 @@ module Banzai ...@@ -8,10 +8,8 @@ module Banzai
NOT_LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-((%5C|\\).+?)-#{LITERAL_KEYWORD}}.freeze NOT_LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-((%5C|\\).+?)-#{LITERAL_KEYWORD}}.freeze
SPAN_REGEX = %r{<span>(.*?)</span>}.freeze SPAN_REGEX = %r{<span>(.*?)</span>}.freeze
CSS_A = 'a' CSS_A = 'a'
XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
CSS_CODE = 'code'
XPATH_CODE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_CODE).freeze
def call def call
return doc unless result[:escaped_literals] return doc unless result[:escaped_literals]
...@@ -34,12 +32,22 @@ module Banzai ...@@ -34,12 +32,22 @@ module Banzai
node.attributes['title'].value = node.attributes['title'].value.gsub(SPAN_REGEX, '\1') if node.attributes['title'] node.attributes['title'].value = node.attributes['title'].value.gsub(SPAN_REGEX, '\1') if node.attributes['title']
end end
doc.xpath(XPATH_CODE).each do |node| doc.xpath(lang_tag).each do |node|
node.attributes['lang'].value = node.attributes['lang'].value.gsub(SPAN_REGEX, '\1') if node.attributes['lang'] node.attributes['lang'].value = node.attributes['lang'].value.gsub(SPAN_REGEX, '\1') if node.attributes['lang']
end end
doc doc
end end
private
def lang_tag
if Feature.enabled?(:use_cmark_renderer)
Gitlab::Utils::Nokogiri.css_to_xpath('pre')
else
Gitlab::Utils::Nokogiri.css_to_xpath('code')
end
end
end end
end end
end end
...@@ -5,18 +5,15 @@ require "asciidoctor_plantuml/plantuml" ...@@ -5,18 +5,15 @@ require "asciidoctor_plantuml/plantuml"
module Banzai module Banzai
module Filter module Filter
# HTML that replaces all `code plantuml` tags with PlantUML img tags. # HTML that replaces all `lang plantuml` tags with PlantUML img tags.
# #
class PlantumlFilter < HTML::Pipeline::Filter class PlantumlFilter < HTML::Pipeline::Filter
CSS = 'pre > code[lang="plantuml"]'
XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
def call def call
return doc unless settings.plantuml_enabled? && doc.at_xpath(XPATH) return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag)
plantuml_setup plantuml_setup
doc.xpath(XPATH).each do |node| doc.xpath(lang_tag).each do |node|
img_tag = Nokogiri::HTML::DocumentFragment.parse( img_tag = Nokogiri::HTML::DocumentFragment.parse(
Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {})) Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {}))
node.parent.replace(img_tag) node.parent.replace(img_tag)
...@@ -27,6 +24,15 @@ module Banzai ...@@ -27,6 +24,15 @@ module Banzai
private private
def lang_tag
@lang_tag ||=
if Feature.enabled?(:use_cmark_renderer)
Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
else
Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze
end
end
def settings def settings
Gitlab::CurrentSettings.current_application_settings Gitlab::CurrentSettings.current_application_settings
end end
......
...@@ -54,8 +54,13 @@ module Banzai ...@@ -54,8 +54,13 @@ module Banzai
return unless node.name == 'a' || node.name == 'li' return unless node.name == 'a' || node.name == 'li'
return unless node.has_attribute?('id') return unless node.has_attribute?('id')
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN if Feature.enabled?(:use_cmark_renderer)
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
else
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN_OLD
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN_OLD
end
node.remove_attribute('id') node.remove_attribute('id')
end end
......
...@@ -11,7 +11,7 @@ module Banzai ...@@ -11,7 +11,7 @@ module Banzai
class SyntaxHighlightFilter < HTML::Pipeline::Filter class SyntaxHighlightFilter < HTML::Pipeline::Filter
include OutputSafety include OutputSafety
PARAMS_DELIMITER = ':' LANG_PARAMS_DELIMITER = ':'
LANG_PARAMS_ATTR = 'data-lang-params' LANG_PARAMS_ATTR = 'data-lang-params'
CSS = 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code' CSS = 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code'
...@@ -27,7 +27,7 @@ module Banzai ...@@ -27,7 +27,7 @@ module Banzai
def highlight_node(node) def highlight_node(node)
css_classes = +'code highlight js-syntax-highlight' css_classes = +'code highlight js-syntax-highlight'
lang, lang_params = parse_lang_params(node.attr('lang')) lang, lang_params = parse_lang_params(node)
sourcepos = node.parent.attr('data-sourcepos') sourcepos = node.parent.attr('data-sourcepos')
retried = false retried = false
...@@ -56,7 +56,7 @@ module Banzai ...@@ -56,7 +56,7 @@ module Banzai
retry retry
end end
sourcepos_attr = sourcepos ? "data-sourcepos=\"#{sourcepos}\"" : "" sourcepos_attr = sourcepos ? "data-sourcepos=\"#{sourcepos}\"" : ''
highlighted = %(<pre #{sourcepos_attr} class="#{css_classes}" highlighted = %(<pre #{sourcepos_attr} class="#{css_classes}"
lang="#{language}" lang="#{language}"
...@@ -69,13 +69,36 @@ module Banzai ...@@ -69,13 +69,36 @@ module Banzai
private private
def parse_lang_params(language) def parse_lang_params(node)
node = node.parent if Feature.enabled?(:use_cmark_renderer)
# Commonmarker's FULL_INFO_STRING render option works with the space delimiter.
# But the current behavior of GitLab's markdown renderer is different - it grabs everything as the single
# line, including language and its options. To keep backward compatability, we have to parse the old format and
# merge with the new one.
#
# Behaviors before separating language and its parameters:
# Old ones:
# "```ruby with options```" -> '<pre><code lang="ruby with options">'.
# "```ruby:with:options```" -> '<pre><code lang="ruby:with:options">'.
#
# New ones:
# "```ruby with options```" -> '<pre><code lang="ruby" data-meta="with options">'.
# "```ruby:with:options```" -> '<pre><code lang="ruby:with:options">'.
language = node.attr('lang')
return unless language return unless language
lang, params = language.split(PARAMS_DELIMITER, 2) language, language_params = language.split(LANG_PARAMS_DELIMITER, 2)
formatted_params = %(#{LANG_PARAMS_ATTR}="#{escape_once(params)}") if params
if Feature.enabled?(:use_cmark_renderer)
language_params = [node.attr('data-meta'), language_params].compact.join(' ')
end
formatted_language_params = format_language_params(language_params)
[lang, formatted_params] [language, formatted_language_params]
end end
# Separate method so it can be instrumented. # Separate method so it can be instrumented.
...@@ -95,6 +118,12 @@ module Banzai ...@@ -95,6 +118,12 @@ module Banzai
def use_rouge?(language) def use_rouge?(language)
(%w(math suggestion) + ::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES).exclude?(language) (%w(math suggestion) + ::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES).exclude?(language)
end end
def format_language_params(language_params)
return if language_params.blank?
%(#{LANG_PARAMS_ATTR}="#{escape_once(language_params)}")
end
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
# Remove this entire file when removing `use_cmark_renderer` feature flag and switching to the CMARK html renderer.
# https://gitlab.com/gitlab-org/gitlab/-/issues/345744
module Banzai module Banzai
module Renderer module Renderer
module CommonMark module CommonMark
......
...@@ -7,7 +7,11 @@ module Gitlab ...@@ -7,7 +7,11 @@ module Gitlab
register_for 'gitlab-html-pipeline' register_for 'gitlab-html-pipeline'
def format(node, lang, opts) def format(node, lang, opts)
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>) if Feature.enabled?(:use_cmark_renderer)
%(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
else
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>)
end
end end
end end
end end
......
...@@ -5,34 +5,42 @@ require 'spec_helper' ...@@ -5,34 +5,42 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::FootnoteFilter do RSpec.describe Banzai::Filter::FootnoteFilter do
include FilterSpecHelper include FilterSpecHelper
# first[^1] and second[^second] # rubocop:disable Style/AsciiComments
# first[^1] and second[^second] and third[^_😄_]
# [^1]: one # [^1]: one
# [^second]: two # [^second]: two
# [^_😄_]: three
# rubocop:enable Style/AsciiComments
let(:footnote) do let(:footnote) do
<<~EOF <<~EOF.strip_heredoc
<p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p> <p>first<sup><a href="#fn-1" id="fnref-1">1</a></sup> and second<sup><a href="#fn-second" id="fnref-second">2</a></sup> and third<sup><a href="#fn-_%F0%9F%98%84_" id="fnref-_%F0%9F%98%84_">3</a></sup></p>
<p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p>
<ol> <ol>
<li id="fn1"> <li id="fn-1">
<p>one <a href="#fnref1">↩</a></p> <p>one <a href="#fnref-1" aria-label="Back to content">↩</a></p>
</li> </li>
<li id="fn2"> <li id="fn-second">
<p>two <a href="#fnref2">↩</a></p> <p>two <a href="#fnref-second" aria-label="Back to content">↩</a></p>
</li>\n<li id="fn-_%F0%9F%98%84_">
<p>three <a href="#fnref-_%F0%9F%98%84_" aria-label="Back to content">↩</a></p>
</li> </li>
</ol> </ol>
EOF EOF
end end
let(:filtered_footnote) do let(:filtered_footnote) do
<<~EOF <<~EOF.strip_heredoc
<p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p> <p>first<sup class="footnote-ref"><a href="#fn-1-#{identifier}" id="fnref-1-#{identifier}" data-footnote-ref="">1</a></sup> and second<sup class="footnote-ref"><a href="#fn-second-#{identifier}" id="fnref-second-#{identifier}" data-footnote-ref="">2</a></sup> and third<sup class="footnote-ref"><a href="#fn-_%F0%9F%98%84_-#{identifier}" id="fnref-_%F0%9F%98%84_-#{identifier}" data-footnote-ref="">3</a></sup></p>
<p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p>
<section class="footnotes"><ol> <section class=\"footnotes\" data-footnotes><ol>
<li id="fn1-#{identifier}"> <li id="fn-1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p> <p>one <a href="#fnref-1-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref="">↩</a></p>
</li>
<li id="fn-second-#{identifier}">
<p>two <a href="#fnref-second-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref="">↩</a></p>
</li> </li>
<li id="fn2-#{identifier}"> <li id="fn-_%F0%9F%98%84_-#{identifier}">
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref">↩</a></p> <p>three <a href="#fnref-_%F0%9F%98%84_-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref="">↩</a></p>
</li> </li>
</ol></section> </ol></section>
EOF EOF
...@@ -41,10 +49,56 @@ RSpec.describe Banzai::Filter::FootnoteFilter do ...@@ -41,10 +49,56 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
context 'when footnotes exist' do context 'when footnotes exist' do
let(:doc) { filter(footnote) } let(:doc) { filter(footnote) }
let(:link_node) { doc.css('sup > a').first } let(:link_node) { doc.css('sup > a').first }
let(:identifier) { link_node[:id].delete_prefix('fnref1-') } let(:identifier) { link_node[:id].delete_prefix('fnref-1-') }
it 'properly adds the necessary ids and classes' do it 'properly adds the necessary ids and classes' do
expect(doc.to_html).to eq filtered_footnote expect(doc.to_html).to eq filtered_footnote
end end
context 'using ruby-based HTML renderer' do
# first[^1] and second[^second]
# [^1]: one
# [^second]: two
let(:footnote) do
<<~EOF
<p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p>
<p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p>
<ol>
<li id="fn1">
<p>one <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>two <a href="#fnref2">↩</a></p>
</li>
</ol>
EOF
end
let(:filtered_footnote) do
<<~EOF
<p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
<p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p>
<section class="footnotes"><ol>
<li id="fn1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p>
</li>
<li id="fn2-#{identifier}">
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref">↩</a></p>
</li>
</ol></section>
EOF
end
let(:doc) { filter(footnote) }
let(:identifier) { link_node[:id].delete_prefix('fnref1-') }
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'properly adds the necessary ids and classes' do
expect(doc.to_html).to eq filtered_footnote
end
end
end end
end end
...@@ -5,90 +5,125 @@ require 'spec_helper' ...@@ -5,90 +5,125 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::MarkdownFilter do RSpec.describe Banzai::Filter::MarkdownFilter do
include FilterSpecHelper include FilterSpecHelper
describe 'markdown engine from context' do shared_examples_for 'renders correct markdown' do
it 'defaults to CommonMark' do describe 'markdown engine from context' do
expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| it 'defaults to CommonMark' do
expect(instance).to receive(:render).and_return('test') expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end
filter('test')
end end
filter('test') it 'uses CommonMark' do
end expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end
it 'uses CommonMark' do filter('test', { markdown_engine: :common_mark })
expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end end
filter('test', { markdown_engine: :common_mark })
end end
end
describe 'code block' do describe 'code block' do
context 'using CommonMark' do context 'using CommonMark' do
before do before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```", no_sourcepos: true)
if Feature.enabled?(:use_cmark_renderer)
expect(result).to start_with('<pre lang="html"><code>')
else
expect(result).to start_with('<pre><code lang="html">')
end
end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```", no_sourcepos: true)
expect(result).to start_with('<pre><code>')
end
it 'works with utf8 chars in language' do
result = filter("```日\nsome code\n```", no_sourcepos: true)
if Feature.enabled?(:use_cmark_renderer)
expect(result).to start_with('<pre lang="日"><code>')
else
expect(result).to start_with('<pre><code lang="日">')
end
end
it 'works with additional language parameters' do
result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true)
if Feature.enabled?(:use_cmark_renderer)
expect(result).to start_with('<pre lang="ruby:red" data-meta="gem foo"><code>')
else
expect(result).to start_with('<pre><code lang="ruby:red gem foo">')
end
end
end end
end
it 'adds language to lang attribute when specified' do describe 'source line position' do
result = filter("```html\nsome code\n```", no_sourcepos: true) context 'using CommonMark' do
before do
expect(result).to start_with('<pre><code lang="html">') stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```", no_sourcepos: true)
expect(result).to start_with('<pre><code>')
end
it 'works with utf8 chars in language' do it 'defaults to add data-sourcepos' do
result = filter("```日\nsome code\n```", no_sourcepos: true) result = filter('test')
expect(result).to start_with('<pre><code lang="日">') expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>'
end end
it 'works with additional language parameters' do it 'disables data-sourcepos' do
result = filter("```ruby:red gem\nsome code\n```", no_sourcepos: true) result = filter('test', no_sourcepos: true)
expect(result).to start_with('<pre><code lang="ruby:red gem">') expect(result).to eq '<p>test</p>'
end
end end
end end
end
describe 'source line position' do describe 'footnotes in tables' do
context 'using CommonMark' do it 'processes footnotes in table cells' do
before do text = <<-MD.strip_heredoc
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) | Column1 |
end | --------- |
| foot [^1] |
it 'defaults to add data-sourcepos' do [^1]: a footnote
result = filter('test') MD
expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>' result = filter(text, no_sourcepos: true)
end
it 'disables data-sourcepos' do expect(result).to include('<td>foot <sup')
result = filter('test', no_sourcepos: true)
expect(result).to eq '<p>test</p>' if Feature.enabled?(:use_cmark_renderer)
expect(result).to include('<section class="footnotes" data-footnotes>')
else
expect(result).to include('<section class="footnotes">')
end
end end
end end
end end
describe 'footnotes in tables' do context 'using ruby-based HTML renderer' do
it 'processes footnotes in table cells' do before do
text = <<-MD.strip_heredoc stub_feature_flags(use_cmark_renderer: false)
| Column1 | end
| --------- |
| foot [^1] |
[^1]: a footnote
MD
result = filter(text, no_sourcepos: true) it_behaves_like 'renders correct markdown'
end
expect(result).to include('<td>foot <sup') context 'using c-based HTML renderer' do
expect(result).to include('<section class="footnotes">') before do
stub_feature_flags(use_cmark_renderer: true)
end end
it_behaves_like 'renders correct markdown'
end end
end end
...@@ -5,30 +5,67 @@ require 'spec_helper' ...@@ -5,30 +5,67 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::PlantumlFilter do RSpec.describe Banzai::Filter::PlantumlFilter do
include FilterSpecHelper include FilterSpecHelper
it 'replaces plantuml pre tag with img tag' do shared_examples_for 'renders correct markdown' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") it 'replaces plantuml pre tag with img tag' do
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output input = if Feature.enabled?(:use_cmark_renderer)
'<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
else
'<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
end
output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output
end
it 'does not replace plantuml pre tag with img tag if disabled' do
stub_application_setting(plantuml_enabled: false)
if Feature.enabled?(:use_cmark_renderer)
input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<pre lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
else
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<pre><code lang="plantuml">Bob -&gt; Sara : Hello</code></pre>'
end
doc = filter(input)
expect(doc.to_s).to eq output
end
it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
input = if Feature.enabled?(:use_cmark_renderer)
'<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
else
'<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
end
output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output
end
end end
it 'does not replace plantuml pre tag with img tag if disabled' do context 'using ruby-based HTML renderer' do
stub_application_setting(plantuml_enabled: false) before do
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' stub_feature_flags(use_cmark_renderer: false)
output = '<pre><code lang="plantuml">Bob -&gt; Sara : Hello</code></pre>' end
doc = filter(input)
expect(doc.to_s).to eq output it_behaves_like 'renders correct markdown'
end end
it 'does not replace plantuml pre tag with img tag if url is invalid' do context 'using c-based HTML renderer' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") before do
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' stub_feature_flags(use_cmark_renderer: true)
output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>' end
doc = filter(input)
expect(doc.to_s).to eq output it_behaves_like 'renders correct markdown'
end end
end end
...@@ -45,10 +45,10 @@ RSpec.describe Banzai::Filter::SanitizationFilter do ...@@ -45,10 +45,10 @@ RSpec.describe Banzai::Filter::SanitizationFilter do
it 'allows `text-align` property in `style` attribute on table elements' do it 'allows `text-align` property in `style` attribute on table elements' do
html = <<~HTML html = <<~HTML
<table> <table>
<tr><th style="text-align: center">Head</th></tr> <tr><th style="text-align: center">Head</th></tr>
<tr><td style="text-align: right">Body</th></tr> <tr><td style="text-align: right">Body</th></tr>
</table> </table>
HTML HTML
doc = filter(html) doc = filter(html)
...@@ -140,14 +140,14 @@ RSpec.describe Banzai::Filter::SanitizationFilter do ...@@ -140,14 +140,14 @@ RSpec.describe Banzai::Filter::SanitizationFilter do
describe 'footnotes' do describe 'footnotes' do
it 'allows correct footnote id property on links' do it 'allows correct footnote id property on links' do
exp = %q(<a href="#fn1" id="fnref1">foo/bar.md</a>) exp = %q(<a href="#fn-first" id="fnref-first">foo/bar.md</a>)
act = filter(exp) act = filter(exp)
expect(act.to_html).to eq exp expect(act.to_html).to eq exp
end end
it 'allows correct footnote id property on li element' do it 'allows correct footnote id property on li element' do
exp = %q(<ol><li id="fn1">footnote</li></ol>) exp = %q(<ol><li id="fn-last">footnote</li></ol>)
act = filter(exp) act = filter(exp)
expect(act.to_html).to eq exp expect(act.to_html).to eq exp
...@@ -156,7 +156,7 @@ RSpec.describe Banzai::Filter::SanitizationFilter do ...@@ -156,7 +156,7 @@ RSpec.describe Banzai::Filter::SanitizationFilter do
it 'removes invalid id for footnote links' do it 'removes invalid id for footnote links' do
exp = %q(<a href="#fn1">link</a>) exp = %q(<a href="#fn1">link</a>)
%w[fnrefx test xfnref1].each do |id| %w[fnrefx test xfnref-1].each do |id|
act = filter(%(<a href="#fn1" id="#{id}">link</a>)) act = filter(%(<a href="#fn1" id="#{id}">link</a>))
expect(act.to_html).to eq exp expect(act.to_html).to eq exp
...@@ -166,18 +166,58 @@ RSpec.describe Banzai::Filter::SanitizationFilter do ...@@ -166,18 +166,58 @@ RSpec.describe Banzai::Filter::SanitizationFilter do
it 'removes invalid id for footnote li' do it 'removes invalid id for footnote li' do
exp = %q(<ol><li>footnote</li></ol>) exp = %q(<ol><li>footnote</li></ol>)
%w[fnx test xfn1].each do |id| %w[fnx test xfn-1].each do |id|
act = filter(%(<ol><li id="#{id}">footnote</li></ol>)) act = filter(%(<ol><li id="#{id}">footnote</li></ol>))
expect(act.to_html).to eq exp expect(act.to_html).to eq exp
end end
end end
it 'allows footnotes numbered higher than 9' do context 'using ruby-based HTML renderer' do
exp = %q(<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>) before do
act = filter(exp) stub_feature_flags(use_cmark_renderer: false)
end
expect(act.to_html).to eq exp it 'allows correct footnote id property on links' do
exp = %q(<a href="#fn1" id="fnref1">foo/bar.md</a>)
act = filter(exp)
expect(act.to_html).to eq exp
end
it 'allows correct footnote id property on li element' do
exp = %q(<ol><li id="fn1">footnote</li></ol>)
act = filter(exp)
expect(act.to_html).to eq exp
end
it 'removes invalid id for footnote links' do
exp = %q(<a href="#fn1">link</a>)
%w[fnrefx test xfnref1].each do |id|
act = filter(%(<a href="#fn1" id="#{id}">link</a>))
expect(act.to_html).to eq exp
end
end
it 'removes invalid id for footnote li' do
exp = %q(<ol><li>footnote</li></ol>)
%w[fnx test xfn1].each do |id|
act = filter(%(<ol><li id="#{id}">footnote</li></ol>))
expect(act.to_html).to eq exp
end
end
it 'allows footnotes numbered higher than 9' do
exp = %q(<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>)
act = filter(exp)
expect(act.to_html).to eq exp
end
end end
end end
end end
......
...@@ -31,29 +31,29 @@ RSpec.describe Banzai::Pipeline::FullPipeline do ...@@ -31,29 +31,29 @@ RSpec.describe Banzai::Pipeline::FullPipeline do
describe 'footnotes' do describe 'footnotes' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:html) { described_class.to_html(footnote_markdown, project: project) } let(:html) { described_class.to_html(footnote_markdown, project: project) }
let(:identifier) { html[/.*fnref1-(\d+).*/, 1] } let(:identifier) { html[/.*fnref-1-(\d+).*/, 1] }
let(:footnote_markdown) do let(:footnote_markdown) do
<<~EOF <<~EOF
first[^1] and second[^second] and twenty[^twenty] first[^1] and second[^😄second] and twenty[^_twenty]
[^1]: one [^1]: one
[^second]: two [^😄second]: two
[^twenty]: twenty [^_twenty]: twenty
EOF EOF
end end
let(:filtered_footnote) do let(:filtered_footnote) do
<<~EOF <<~EOF.strip_heredoc
<p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn3-#{identifier}" id="fnref3-#{identifier}">3</a></sup></p> <p dir="auto">first<sup class="footnote-ref"><a href="#fn-1-#{identifier}" id="fnref-1-#{identifier}" data-footnote-ref="">1</a></sup> and second<sup class="footnote-ref"><a href="#fn-%F0%9F%98%84second-#{identifier}" id="fnref-%F0%9F%98%84second-#{identifier}" data-footnote-ref="">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn-_twenty-#{identifier}" id="fnref-_twenty-#{identifier}" data-footnote-ref="">3</a></sup></p>
<section class="footnotes"><ol> <section class="footnotes" data-footnotes><ol>
<li id="fn1-#{identifier}"> <li id="fn-1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> <p>one <a href="#fnref-1-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li> </li>
<li id="fn2-#{identifier}"> <li id="fn-%F0%9F%98%84second-#{identifier}">
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> <p>two <a href="#fnref-%F0%9F%98%84second-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li> </li>
<li id="fn3-#{identifier}"> <li id="fn-_twenty-#{identifier}">
<p>twenty <a href="#fnref3-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> <p>twenty <a href="#fnref-_twenty-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li> </li>
</ol></section> </ol></section>
EOF EOF
...@@ -64,6 +64,47 @@ RSpec.describe Banzai::Pipeline::FullPipeline do ...@@ -64,6 +64,47 @@ RSpec.describe Banzai::Pipeline::FullPipeline do
expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote
end end
context 'using ruby-based HTML renderer' do
let(:html) { described_class.to_html(footnote_markdown, project: project) }
let(:identifier) { html[/.*fnref1-(\d+).*/, 1] }
let(:footnote_markdown) do
<<~EOF
first[^1] and second[^second] and twenty[^twenty]
[^1]: one
[^second]: two
[^twenty]: twenty
EOF
end
let(:filtered_footnote) do
<<~EOF
<p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn3-#{identifier}" id="fnref3-#{identifier}">3</a></sup></p>
<section class="footnotes"><ol>
<li id="fn1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li>
<li id="fn2-#{identifier}">
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li>
<li id="fn3-#{identifier}">
<p>twenty <a href="#fnref3-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li>
</ol></section>
EOF
end
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'properly adds the necessary ids and classes' do
stub_commonmark_sourcepos_disabled
expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote
end
end
end end
describe 'links are detected as malicious' do describe 'links are detected as malicious' do
......
...@@ -5,18 +5,7 @@ require 'spec_helper' ...@@ -5,18 +5,7 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
describe 'backslash escapes' do shared_examples_for 'renders correct markdown' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
def correct_html_included(markdown, expected)
result = described_class.call(markdown, {})
expect(result[:output].to_html).to include(expected)
result
end
describe 'CommonMark tests', :aggregate_failures do describe 'CommonMark tests', :aggregate_failures do
it 'converts all reference punctuation to literals' do it 'converts all reference punctuation to literals' do
reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS
...@@ -79,10 +68,19 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do ...@@ -79,10 +68,19 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do
end end
describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do
let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) }
it 'renders correct html' do
if Feature.enabled?(:use_cmark_renderer)
correct_html_included(markdown, %Q(<pre data-sourcepos="1:1-3:3" lang="foo@bar"><code>foo\n</code></pre>))
else
correct_html_included(markdown, %Q(<code lang="foo@bar">foo\n</code>))
end
end
where(:markdown, :expected) do where(:markdown, :expected) do
%q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>) %q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>)
%Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>) %Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>)
%Q(``` foo\\@bar\nfoo\n```) | %Q(<code lang="foo@bar">foo\n</code>)
end end
with_them do with_them do
...@@ -91,4 +89,33 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do ...@@ -91,4 +89,33 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do
end end
end end
end end
describe 'backslash escapes' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
def correct_html_included(markdown, expected)
result = described_class.call(markdown, {})
expect(result[:output].to_html).to include(expected)
result
end
context 'using ruby-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: false)
end
it_behaves_like 'renders correct markdown'
end
context 'using c-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: true)
end
it_behaves_like 'renders correct markdown'
end
end
end end
This diff is collapsed.
...@@ -92,9 +92,16 @@ module StubGitlabCalls ...@@ -92,9 +92,16 @@ module StubGitlabCalls
end end
def stub_commonmark_sourcepos_disabled def stub_commonmark_sourcepos_disabled
render_options =
if Feature.enabled?(:use_cmark_renderer)
Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS_C
else
Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS_RUBY
end
allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark)
.to receive(:render_options) .to receive(:render_options)
.and_return(Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS) .and_return(render_options)
end end
private private
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment