qiita-markdown 0.44.0 → 1.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -2
  3. data/.rubocop.yml +0 -4
  4. data/.rubocop_todo.yml +15 -239
  5. data/CHANGELOG.md +15 -0
  6. data/README.md +5 -3
  7. data/lib/qiita/markdown/filters/checkbox.rb +5 -1
  8. data/lib/qiita/markdown/filters/code_block.rb +13 -13
  9. data/lib/qiita/markdown/filters/custom_block.rb +8 -6
  10. data/lib/qiita/markdown/filters/external_link.rb +2 -0
  11. data/lib/qiita/markdown/filters/final_sanitizer.rb +126 -120
  12. data/lib/qiita/markdown/filters/footnote.rb +2 -0
  13. data/lib/qiita/markdown/filters/group_mention.rb +2 -2
  14. data/lib/qiita/markdown/filters/heading_anchor.rb +44 -0
  15. data/lib/qiita/markdown/filters/html_toc.rb +67 -0
  16. data/lib/qiita/markdown/filters/image_link.rb +6 -6
  17. data/lib/qiita/markdown/filters/inline_code_color.rb +8 -8
  18. data/lib/qiita/markdown/filters/mention.rb +11 -9
  19. data/lib/qiita/markdown/filters/qiita_marker.rb +55 -0
  20. data/lib/qiita/markdown/filters/simplify.rb +1 -0
  21. data/lib/qiita/markdown/filters/syntax_highlight.rb +4 -4
  22. data/lib/qiita/markdown/filters/truncate.rb +1 -3
  23. data/lib/qiita/markdown/filters/user_input_sanitizer.rb +34 -29
  24. data/lib/qiita/markdown/processor.rb +2 -1
  25. data/lib/qiita/markdown/summary_processor.rb +1 -1
  26. data/lib/qiita/markdown/transformers/filter_attributes.rb +1 -0
  27. data/lib/qiita/markdown/transformers/filter_iframe.rb +1 -2
  28. data/lib/qiita/markdown/transformers/filter_script.rb +1 -1
  29. data/lib/qiita/markdown/transformers/strip_invalid_node.rb +1 -3
  30. data/lib/qiita/markdown/version.rb +1 -1
  31. data/lib/qiita/markdown.rb +4 -5
  32. data/qiita-markdown.gemspec +7 -8
  33. data/spec/qiita/markdown/filters/checkbox_spec.rb +28 -0
  34. data/spec/qiita/markdown/filters/heading_anchor_spec.rb +73 -0
  35. data/spec/qiita/markdown/filters/html_toc_spec.rb +223 -0
  36. data/spec/qiita/markdown/filters/qiita_marker_spec.rb +60 -0
  37. data/spec/qiita/markdown/processor_spec.rb +64 -70
  38. data/spec/qiita/markdown/summary_processor_spec.rb +4 -4
  39. metadata +80 -102
  40. data/benchmark/heading_anchor_rendering.rb +0 -248
  41. data/benchmark/sample.md +0 -317
  42. data/lib/qiita/markdown/filters/greenmat.rb +0 -38
  43. data/lib/qiita/markdown/greenmat/heading_rendering.rb +0 -61
  44. data/lib/qiita/markdown/greenmat/html_renderer.rb +0 -60
  45. data/lib/qiita/markdown/greenmat/html_toc_renderer.rb +0 -78
  46. data/spec/qiita/markdown/filters/greenmat_spec.rb +0 -15
  47. data/spec/qiita/markdown/greenmat/html_toc_renderer_spec.rb +0 -156
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Qiita
2
4
  module Markdown
3
5
  module Filters
@@ -9,44 +11,45 @@ module Qiita
9
11
  # generated by other filters.
10
12
  #
11
13
  # @see Qiita::Markdown::Filters::UserInputSanitizerr
12
- class FinalSanitizer < HTML::Pipeline::Filter
14
+ class FinalSanitizer < ::HTML::Pipeline::Filter
13
15
  RULE = {
14
16
  attributes: {
15
- "a" => [
16
- "data-hovercard-target-name",
17
- "data-hovercard-target-type",
18
- "href",
19
- "rel",
17
+ "a" => %w[
18
+ data-hovercard-target-name
19
+ data-hovercard-target-type
20
+ href
21
+ rel
20
22
  ],
21
23
  "blockquote" => Embed::Tweet::ATTRIBUTES,
22
- "iframe" => [
23
- "allowfullscreen",
24
- "frameborder",
25
- "height",
26
- "marginheight",
27
- "marginwidth",
28
- "scrolling",
29
- "src",
30
- "style",
31
- "width",
24
+ "iframe" => %w[
25
+ allowfullscreen
26
+ frameborder
27
+ height
28
+ loading
29
+ marginheight
30
+ marginwidth
31
+ scrolling
32
+ src
33
+ style
34
+ width
32
35
  ],
33
36
  "img" => [
34
37
  "src",
35
38
  ],
36
- "input" => [
37
- "checked",
38
- "disabled",
39
- "type",
39
+ "input" => %w[
40
+ checked
41
+ disabled
42
+ type
40
43
  ],
41
- "div" => [
42
- "itemscope",
43
- "itemtype",
44
+ "div" => %w[
45
+ itemscope
46
+ itemtype
44
47
  ],
45
48
  "p" => Embed::CodePen::ATTRIBUTES,
46
- "script" => [
47
- "async",
48
- "src",
49
- "type",
49
+ "script" => %w[
50
+ async
51
+ src
52
+ type
50
53
  ].concat(
51
54
  Embed::SpeekerDeck::ATTRIBUTES,
52
55
  Embed::Docswell::ATTRIBUTES,
@@ -60,104 +63,107 @@ module Qiita
60
63
  "th" => [
61
64
  "style",
62
65
  ],
63
- "video" => [
64
- "src",
65
- "autoplay",
66
- "controls",
67
- "loop",
68
- "muted",
69
- "poster",
66
+ "video" => %w[
67
+ src
68
+ autoplay
69
+ controls
70
+ loop
71
+ muted
72
+ poster
70
73
  ],
71
- all: [
72
- "abbr",
73
- "align",
74
- "alt",
75
- "border",
76
- "cellpadding",
77
- "cellspacing",
78
- "cite",
79
- "class",
80
- "color",
81
- "cols",
82
- "colspan",
83
- "data-lang",
84
- "datetime",
85
- "height",
86
- "hreflang",
87
- "id",
88
- "itemprop",
89
- "lang",
90
- "name",
91
- "rowspan",
92
- "tabindex",
93
- "target",
94
- "title",
95
- "width",
74
+ all: %w[
75
+ abbr
76
+ align
77
+ alt
78
+ border
79
+ cellpadding
80
+ cellspacing
81
+ cite
82
+ class
83
+ color
84
+ cols
85
+ colspan
86
+ data-lang
87
+ data-sourcepos
88
+ datetime
89
+ height
90
+ hreflang
91
+ id
92
+ itemprop
93
+ lang
94
+ name
95
+ rowspan
96
+ tabindex
97
+ target
98
+ title
99
+ width
96
100
  ],
97
101
  },
98
102
  css: {
99
- properties: [
100
- "background-color",
101
- "border",
102
- "text-align",
103
+ properties: %w[
104
+ background-color
105
+ border
106
+ text-align
103
107
  ],
104
108
  },
105
- elements: [
106
- "a",
107
- "b",
108
- "blockquote",
109
- "br",
110
- "code",
111
- "dd",
112
- "del",
113
- "details",
114
- "div",
115
- "dl",
116
- "dt",
117
- "em",
118
- "font",
119
- "h1",
120
- "h2",
121
- "h3",
122
- "h4",
123
- "h5",
124
- "h6",
125
- "h7",
126
- "h8",
127
- "hr",
128
- "i",
129
- "img",
130
- "input",
131
- "ins",
132
- "kbd",
133
- "li",
134
- "ol",
135
- "p",
136
- "pre",
137
- "q",
138
- "rp",
139
- "rt",
140
- "ruby",
141
- "s",
142
- "samp",
143
- "script",
144
- "iframe",
145
- "span",
146
- "strike",
147
- "strong",
148
- "sub",
149
- "summary",
150
- "sup",
151
- "table",
152
- "tbody",
153
- "td",
154
- "tfoot",
155
- "th",
156
- "thead",
157
- "tr",
158
- "tt",
159
- "ul",
160
- "var",
109
+ elements: %w[
110
+ a
111
+ b
112
+ blockquote
113
+ br
114
+ caption
115
+ code
116
+ dd
117
+ del
118
+ details
119
+ div
120
+ dl
121
+ dt
122
+ em
123
+ font
124
+ h1
125
+ h2
126
+ h3
127
+ h4
128
+ h5
129
+ h6
130
+ h7
131
+ h8
132
+ hr
133
+ i
134
+ img
135
+ input
136
+ ins
137
+ kbd
138
+ li
139
+ ol
140
+ p
141
+ pre
142
+ q
143
+ rp
144
+ rt
145
+ ruby
146
+ s
147
+ samp
148
+ script
149
+ iframe
150
+ section
151
+ span
152
+ strike
153
+ strong
154
+ sub
155
+ summary
156
+ sup
157
+ table
158
+ tbody
159
+ td
160
+ tfoot
161
+ th
162
+ thead
163
+ tr
164
+ tt
165
+ ul
166
+ var
161
167
  ],
162
168
  protocols: {
163
169
  "a" => {
@@ -200,7 +206,7 @@ module Qiita
200
206
  rule[:attributes][:all] = rule[:attributes][:all] + [:data]
201
207
  rule[:elements] = RULE[:elements] + ["video"]
202
208
  rule[:transformers] = rule[:transformers] - [Transformers::FilterScript, Transformers::FilterIframe]
203
- end
209
+ end.freeze
204
210
 
205
211
  def call
206
212
  ::Sanitize.clean_node!(doc, rule)
@@ -6,6 +6,7 @@ module Qiita
6
6
  doc.search("sup > a").each do |a|
7
7
  footnote = find_footnote(a)
8
8
  next unless footnote
9
+
9
10
  a[:title] = footnote.text.gsub(/\A\n/, "").gsub(/ ↩\n\z/, "")
10
11
  end
11
12
  doc
@@ -16,6 +17,7 @@ module Qiita
16
17
  def find_footnote(a)
17
18
  href = a["href"]
18
19
  return nil if !href || href.match(/\A#fn\d+\z/).nil?
20
+
19
21
  doc.search(href).first
20
22
  end
21
23
  end
@@ -21,9 +21,9 @@ module Qiita
21
21
  GROUP_IDENTIFIER_PATTERN = %r{
22
22
  (?:^|\W)
23
23
  @((?>[a-z\d][a-z\d-]{2,31}))
24
- \/
24
+ /
25
25
  ([A-Za-z\d][A-Za-z\d-]{0,62}[A-Za-z\d])
26
- (?!\/)
26
+ (?!/)
27
27
  (?=
28
28
  \.+[ \t\W]|
29
29
  \.+$|
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qiita
4
+ module Markdown
5
+ module Filters
6
+ class HeadingAnchor < ::HTML::Pipeline::Filter
7
+ def call
8
+ doc.search("h1, h2, h3, h4, h5, h6").each do |heading|
9
+ heading["id"] = suffixed_id(heading)
10
+ end
11
+
12
+ doc
13
+ end
14
+
15
+ private
16
+
17
+ def counter
18
+ @counter ||= ::Hash.new(0)
19
+ end
20
+
21
+ def get_count(id)
22
+ counter[id]
23
+ end
24
+
25
+ def increment_count(id)
26
+ counter[id] += 1
27
+ end
28
+
29
+ def heading_id(node)
30
+ node.text.downcase.gsub(/[^\p{Word}\- ]/u, "").tr(" ", "-")
31
+ end
32
+
33
+ def suffixed_id(node)
34
+ id = heading_id(node)
35
+ count = get_count(id)
36
+ suffix = count.positive? ? "-#{count}" : ""
37
+ increment_count(id)
38
+
39
+ "#{id}#{suffix}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qiita
4
+ module Markdown
5
+ module Filters
6
+ class HtmlToc < ::HTML::Pipeline::Filter
7
+ # @return [Nokogiri::HTML::DocumentFragment]
8
+ def call
9
+ headings = doc.search("h1, h2, h3, h4, h5, h6")
10
+ return "" if headings.empty?
11
+
12
+ toc = %W[<ul>\n]
13
+ top_level = nil
14
+ last_level = nil
15
+ depth = 1
16
+
17
+ headings.each do |node|
18
+ heading_rank = node.name.match(/h(\d)/)[1].to_i
19
+
20
+ # The first heading is displayed as the top level.
21
+ # The following headings, of higher rank than the first, are placed as top level.
22
+ top_level ||= heading_rank
23
+ current_level = [heading_rank, top_level].max
24
+
25
+ link = toc_with_link(node.text, node.attributes["id"]&.value)
26
+ toc << (nest_string(last_level, current_level) + link)
27
+
28
+ depth += current_level - last_level if last_level
29
+
30
+ last_level = current_level
31
+ end
32
+
33
+ toc << ("</li>\n</ul>\n" * depth)
34
+ toc.join
35
+ end
36
+
37
+ private
38
+
39
+ # @param text [String]
40
+ # @param id [String]
41
+ # @return [String]
42
+ def toc_with_link(text, id)
43
+ %(<a href="##{id}">#{CGI.escapeHTML(text)}</a>\n)
44
+ end
45
+
46
+ # @param last_level [Integer, nil]
47
+ # @param current_level [Integer]
48
+ # @return [String]
49
+ def nest_string(last_level, current_level)
50
+ if last_level.nil?
51
+ return "<li>\n"
52
+ elsif current_level == last_level
53
+ return "</li>\n<li>\n"
54
+ elsif current_level > last_level
55
+ level_difference = current_level - last_level
56
+ return "<ul>\n<li>\n" * level_difference
57
+ elsif current_level < last_level
58
+ level_difference = last_level - current_level
59
+ return %(#{"</li>\n</ul>\n" * level_difference}</li>\n<li>\n)
60
+ end
61
+
62
+ ""
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -4,12 +4,12 @@ module Qiita
4
4
  class ImageLink < HTML::Pipeline::Filter
5
5
  def call
6
6
  doc.search("img").each do |img|
7
- unless img.ancestors.any? { |ancestor| ancestor.name == "a" }
8
- outer = Nokogiri::HTML.fragment(%(<a href="#{img['src']}" target="_blank"></a>))
9
- inner = img.clone
10
- outer.at("a").add_child(inner)
11
- img.replace(outer)
12
- end
7
+ next if img.ancestors.any? { |ancestor| ancestor.name == "a" }
8
+
9
+ outer = Nokogiri::HTML.fragment(%(<a href="#{img['src']}" target="_blank"></a>))
10
+ inner = img.clone
11
+ outer.at("a").add_child(inner)
12
+ img.replace(outer)
13
13
  end
14
14
  doc
15
15
  end
@@ -6,15 +6,15 @@ module Qiita
6
6
 
7
7
  REGEXPS = Regexp.union(
8
8
  /\#(?:\h{3}|\h{6})/,
9
- /rgba?\(\s*(?:\d+(?:\,|\s)\s*){2}\d+\s*\)/,
10
- /rgba?\(\s*(?:\d+%(?:\,|\s)\s*){2}\d+%\s*\)/,
11
- /rgba?\(\s*(?:\d+\,\s*){3}\d*\.?\d+%?\s*\)/,
12
- /rgba?\(\s*(?:\d+\s*){2}\d+\s*\/\s*\d?\.?\d+%?\s*\)/,
13
- /rgba?\(\s*(?:\d+%\s*){2}\d+%\s*\/\s*\d?\.?\d+%?\s*\)/,
14
- /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\,\s*\d+%\,\s*\d+%\s*\)/,
9
+ /rgba?\(\s*(?:\d+(?:,|\s)\s*){2}\d+\s*\)/,
10
+ /rgba?\(\s*(?:\d+%(?:,|\s)\s*){2}\d+%\s*\)/,
11
+ /rgba?\(\s*(?:\d+,\s*){3}\d*\.?\d+%?\s*\)/,
12
+ %r{rgba?\(\s*(?:\d+\s*){2}\d+\s*/\s*\d?\.?\d+%?\s*\)},
13
+ %r{rgba?\(\s*(?:\d+%\s*){2}\d+%\s*/\s*\d?\.?\d+%?\s*\)},
14
+ /hsla?\(\s*\d+(?:deg|rad|grad|turn)?,\s*\d+%,\s*\d+%\s*\)/,
15
15
  /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\s+\d+%\s+\d+%\s*\)/,
16
- /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\,\s*(?:\d+%\,\s*){2}\d?\.?\d+%?\s*\)/,
17
- /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\s+\d+%\s+\d+%\s*\/\s*\d?\.?\d+%?\s*\)/,
16
+ /hsla?\(\s*\d+(?:deg|rad|grad|turn)?,\s*(?:\d+%,\s*){2}\d?\.?\d+%?\s*\)/,
17
+ %r{hsla?\(\s*\d+(?:deg|rad|grad|turn)?\s+\d+%\s+\d+%\s*/\s*\d?\.?\d+%?\s*\)},
18
18
  )
19
19
 
20
20
  COLOR_CODE_PATTERN = /\A\s*(#{REGEXPS})\s*\z/
@@ -8,17 +8,17 @@ module Qiita
8
8
  class Mention < HTML::Pipeline::MentionFilter
9
9
  IGNORE_PARENTS = ::HTML::Pipeline::MentionFilter::IGNORE_PARENTS + Set["blockquote"]
10
10
 
11
- MentionPattern = /
11
+ MentionPattern = %r{
12
12
  (?:^|\W)
13
- @((?>[\w][\w-]{0,30}\w(?:@github)?))
14
- (?!\/)
13
+ @((?>\w[\w-]{0,30}\w(?:@github)?))
14
+ (?!/)
15
15
  (?=
16
16
  \.+[ \t\W]|
17
17
  \.+$|
18
18
  [^0-9a-zA-Z_.]|
19
19
  $
20
20
  )
21
- /ix
21
+ }ix
22
22
 
23
23
  # @note Override to use another IGNORE_PARENTS
24
24
  def call
@@ -28,8 +28,10 @@ module Qiita
28
28
  content = node.to_html
29
29
  next unless content.include?("@")
30
30
  next if has_ancestor?(node, IGNORE_PARENTS)
31
+
31
32
  html = mention_link_filter(content, base_url, info_url, username_pattern)
32
33
  next if html == content
34
+
33
35
  node.replace(html)
34
36
  end
35
37
  doc
@@ -38,22 +40,22 @@ module Qiita
38
40
  # @note Override to use customized MentionPattern and allowed_usernames logic.
39
41
  def mention_link_filter(text, _, _, _)
40
42
  text.gsub(MentionPattern) do |match|
41
- name = $1
43
+ name = ::Regexp.last_match(1)
42
44
  case
43
45
  when allowed_usernames && name == "all"
44
46
  result[:mentioned_usernames] |= allowed_usernames
45
47
  match.sub(
46
48
  "@#{name}",
47
- %[<a href="/" class="user-mention" title="#{name}">@#{name}</a>]
49
+ %(<a href="/" class="user-mention" title="#{name}">@#{name}</a>),
48
50
  )
49
- when allowed_usernames && !allowed_usernames.include?(name) || name == "all"
51
+ when (allowed_usernames && !allowed_usernames.include?(name)) || name == "all"
50
52
  match
51
53
  else
52
54
  result[:mentioned_usernames] |= [name]
53
55
  url = File.join(base_url, name)
54
56
  match.sub(
55
57
  "@#{name}",
56
- %[<a href="#{url}" class="user-mention js-hovercard" title="#{name}" data-hovercard-target-type="user" data-hovercard-target-name="#{name}">@#{name}</a>]
58
+ %(<a href="#{url}" class="user-mention js-hovercard" title="#{name}" data-hovercard-target-type="user" data-hovercard-target-name="#{name}">@#{name}</a>),
57
59
  )
58
60
  end
59
61
  end
@@ -66,7 +68,7 @@ module Qiita
66
68
  end
67
69
 
68
70
  def has_ancestor?(node, tags)
69
- super || node.parent.parent && node.parent.parent["class"] == "code-lang"
71
+ super || (node.parent.parent && node.parent.parent["class"] == "code-lang")
70
72
  end
71
73
  end
72
74
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qiita
4
+ module Markdown
5
+ module Filters
6
+ class QiitaMarker < ::HTML::Pipeline::TextFilter
7
+ DEFAULT_OPTIONS = {
8
+ footnotes: true,
9
+ sourcepos: false,
10
+ }.freeze
11
+
12
+ # @return [Nokogiri::HTML::DocumentFragment]
13
+ def call
14
+ ::Nokogiri::HTML.fragment(render(@text))
15
+ end
16
+
17
+ private
18
+
19
+ # @param text [String]
20
+ # @return [String]
21
+ def render(text)
22
+ ::QiitaMarker.render_html(text, qiita_marker_options, qiita_marker_extensions)
23
+ end
24
+
25
+ def qiita_marker_options
26
+ options_to_append = (options[:footnotes] ? [:FOOTNOTES] : [])
27
+ .concat(options[:sourcepos] ? [:SOURCEPOS] : [])
28
+ @qiita_marker_options ||= %i[
29
+ HARDBREAKS
30
+ UNSAFE
31
+ LIBERAL_HTML_TAG
32
+ STRIKETHROUGH_DOUBLE_TILDE
33
+ TABLE_PREFER_STYLE_ATTRIBUTES
34
+ CODE_DATA_METADATA
35
+ MENTION_NO_EMPHASIS
36
+ AUTOLINK_CLASS_NAME
37
+ ].concat(options_to_append)
38
+ end
39
+
40
+ def qiita_marker_extensions
41
+ @qiita_marker_extensions ||= %i[
42
+ table
43
+ strikethrough
44
+ autolink
45
+ custom_block
46
+ ]
47
+ end
48
+
49
+ def options
50
+ @options ||= DEFAULT_OPTIONS.merge(context[:markdown] || {})
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -38,6 +38,7 @@ module Qiita
38
38
  doc.traverse do |node|
39
39
  next unless node.element?
40
40
  next if SIMPLE_ELEMENTS.include?(node.name)
41
+
41
42
  node.replace(node.children)
42
43
  end
43
44
  end
@@ -54,7 +54,7 @@ module Qiita
54
54
  end
55
55
 
56
56
  def call
57
- outer = Nokogiri::HTML.fragment(%Q[<div class="code-frame" data-lang="#{language}">])
57
+ outer = Nokogiri::HTML.fragment(%(<div class="code-frame" data-lang="#{language}">))
58
58
  frame = outer.at("div")
59
59
  frame.add_child(filename_node) if filename
60
60
  frame.add_child(highlighted_node)
@@ -72,7 +72,7 @@ module Qiita
72
72
  end
73
73
 
74
74
  def filename_node
75
- %Q[<div class="code-lang"><span class="bold">#{filename}</span></div>]
75
+ %(<div class="code-lang"><span class="bold">#{filename}</span></div>)
76
76
  end
77
77
 
78
78
  def has_inline_php?
@@ -87,7 +87,7 @@ module Qiita
87
87
  if specific_language && Rouge::Lexer.find(specific_language)
88
88
  begin
89
89
  highlight(specific_language).presence or raise
90
- rescue
90
+ rescue StandardError
91
91
  highlight(@default_language)
92
92
  end
93
93
  else
@@ -100,7 +100,7 @@ module Qiita
100
100
  end
101
101
 
102
102
  def language_node
103
- Nokogiri::HTML.fragment(%Q[<div class="code-frame" data-lang="#{language}"></div>])
103
+ Nokogiri::HTML.fragment(%(<div class="code-frame" data-lang="#{language}"></div>))
104
104
  end
105
105
 
106
106
  def specific_language
@@ -51,9 +51,7 @@ module Qiita
51
51
  node.content.each_char.with_index do |char, index|
52
52
  current_char_is_blank = char.strip.empty?
53
53
 
54
- if !@previous_char_was_blank || !current_char_is_blank
55
- @current_length += 1
56
- end
54
+ @current_length += 1 if !@previous_char_was_blank || !current_char_is_blank
57
55
 
58
56
  @previous_char_was_blank = current_char_is_blank
59
57