mdl 0.5.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mdl +2 -2
  3. data/lib/mdl.rb +49 -40
  4. data/lib/mdl/cli.rb +92 -90
  5. data/lib/mdl/config.rb +2 -1
  6. data/lib/mdl/doc.rb +52 -59
  7. data/lib/mdl/kramdown_parser.rb +1 -1
  8. data/lib/mdl/rules.rb +224 -169
  9. data/lib/mdl/ruleset.rb +11 -10
  10. data/lib/mdl/style.rb +30 -8
  11. data/lib/mdl/styles/cirosantilli.rb +2 -2
  12. data/lib/mdl/styles/default.rb +1 -1
  13. data/lib/mdl/version.rb +1 -1
  14. data/mdl.gemspec +23 -20
  15. metadata +85 -289
  16. data/.gitignore +0 -13
  17. data/.travis.yml +0 -23
  18. data/CHANGELOG.md +0 -187
  19. data/README.md +0 -86
  20. data/Rakefile +0 -8
  21. data/docs/RULES.md +0 -1047
  22. data/docs/configuration.md +0 -99
  23. data/docs/creating_rules.md +0 -90
  24. data/docs/creating_styles.md +0 -47
  25. data/docs/rolling_a_release.md +0 -49
  26. data/example/markdown_spec.md +0 -897
  27. data/test/fixtures/default_mdlrc +0 -1
  28. data/test/fixtures/dir_with_md_and_markdown/bar.markdown +0 -1
  29. data/test/fixtures/dir_with_md_and_markdown/foo.md +0 -1
  30. data/test/fixtures/front_matter/jekyll_post.md +0 -16
  31. data/test/fixtures/mdlrc_disable_rules +0 -1
  32. data/test/fixtures/mdlrc_disable_tags +0 -1
  33. data/test/fixtures/mdlrc_enable_rules +0 -1
  34. data/test/fixtures/mdlrc_enable_tags +0 -1
  35. data/test/fixtures/my_ruleset.rb +0 -6
  36. data/test/rule_tests/alternate_top_level_header.md +0 -3
  37. data/test/rule_tests/alternate_top_level_header_style.rb +0 -4
  38. data/test/rule_tests/atx_closed_header_spacing.md +0 -17
  39. data/test/rule_tests/atx_header_spacing.md +0 -5
  40. data/test/rule_tests/blockquote_blank_lines.md +0 -31
  41. data/test/rule_tests/blockquote_spaces.md +0 -23
  42. data/test/rule_tests/bulleted_list_2_space_indent.md +0 -6
  43. data/test/rule_tests/bulleted_list_2_space_indent_style.rb +0 -3
  44. data/test/rule_tests/bulleted_list_4_space_indent.md +0 -3
  45. data/test/rule_tests/bulleted_list_not_at_beginning_of_line.md +0 -14
  46. data/test/rule_tests/code_block_consistency.md +0 -11
  47. data/test/rule_tests/code_block_consistency_style.rb +0 -1
  48. data/test/rule_tests/code_block_dollar.md +0 -33
  49. data/test/rule_tests/code_block_dollar_fence.md +0 -29
  50. data/test/rule_tests/code_block_fenced.md +0 -17
  51. data/test/rule_tests/code_block_fenced_style.rb +0 -1
  52. data/test/rule_tests/code_block_indented.md +0 -17
  53. data/test/rule_tests/code_block_indented_style.rb +0 -1
  54. data/test/rule_tests/consecutive_blank_lines.md +0 -11
  55. data/test/rule_tests/consistent_bullet_styles_asterisk.md +0 -3
  56. data/test/rule_tests/consistent_bullet_styles_dash.md +0 -3
  57. data/test/rule_tests/consistent_bullet_styles_plus.md +0 -3
  58. data/test/rule_tests/default_test_style.rb +0 -5
  59. data/test/rule_tests/emphasis_instead_of_headers.md +0 -40
  60. data/test/rule_tests/empty_doc.md +0 -0
  61. data/test/rule_tests/fenced_code_blocks.md +0 -27
  62. data/test/rule_tests/fenced_code_with_nesting.md +0 -73
  63. data/test/rule_tests/fenced_code_without_blank_lines.md +0 -42
  64. data/test/rule_tests/fenced_code_without_blank_lines_style.rb +0 -3
  65. data/test/rule_tests/first_header_bad_atx.md +0 -1
  66. data/test/rule_tests/first_header_bad_setext.md +0 -2
  67. data/test/rule_tests/first_header_good_atx.md +0 -1
  68. data/test/rule_tests/first_header_good_setext.md +0 -2
  69. data/test/rule_tests/first_line_top_level_header_atx.md +0 -3
  70. data/test/rule_tests/first_line_top_level_header_atx_style.rb +0 -2
  71. data/test/rule_tests/first_line_top_level_header_setext.md +0 -4
  72. data/test/rule_tests/first_line_top_level_header_setext_style.rb +0 -2
  73. data/test/rule_tests/fix_102_extra_nodes_in_link_text.md +0 -8
  74. data/test/rule_tests/header_duplicate_content.md +0 -11
  75. data/test/rule_tests/header_duplicate_content_different_nesting.md +0 -11
  76. data/test/rule_tests/header_duplicate_content_different_nesting_style.rb +0 -1
  77. data/test/rule_tests/header_duplicate_content_no_different_nesting.md +0 -13
  78. data/test/rule_tests/header_multiple_toplevel.md +0 -3
  79. data/test/rule_tests/header_mutliple_h1_no_toplevel.md +0 -5
  80. data/test/rule_tests/header_trailing_punctuation.md +0 -11
  81. data/test/rule_tests/header_trailing_punctuation_customized.md +0 -14
  82. data/test/rule_tests/header_trailing_punctuation_customized_style.rb +0 -2
  83. data/test/rule_tests/headers_bad.md +0 -7
  84. data/test/rule_tests/headers_good.md +0 -5
  85. data/test/rule_tests/headers_good_setext_with_atx.md +0 -7
  86. data/test/rule_tests/headers_good_setext_with_atx_style.rb +0 -2
  87. data/test/rule_tests/headers_good_with_issue_numbers.md +0 -12
  88. data/test/rule_tests/headers_surrounding_space_atx.md +0 -12
  89. data/test/rule_tests/headers_surrounding_space_setext.md +0 -15
  90. data/test/rule_tests/headers_with_spaces_at_the_beginning.md +0 -20
  91. data/test/rule_tests/hr_style_dashes.md +0 -22
  92. data/test/rule_tests/hr_style_dashes_style.rb +0 -3
  93. data/test/rule_tests/hr_style_inconsistent.md +0 -22
  94. data/test/rule_tests/hr_style_long.md +0 -22
  95. data/test/rule_tests/hr_style_long_style.rb +0 -3
  96. data/test/rule_tests/hr_style_stars.md +0 -22
  97. data/test/rule_tests/hr_style_stars_style.rb +0 -3
  98. data/test/rule_tests/inconsistent_bullet_indent_same_level.md +0 -4
  99. data/test/rule_tests/inconsistent_bullet_styles_asterisk.md +0 -3
  100. data/test/rule_tests/inconsistent_bullet_styles_dash.md +0 -3
  101. data/test/rule_tests/inconsistent_bullet_styles_plus.md +0 -3
  102. data/test/rule_tests/incorrect_bullet_style_asterisk.md +0 -3
  103. data/test/rule_tests/incorrect_bullet_style_asterisk_style.rb +0 -3
  104. data/test/rule_tests/incorrect_bullet_style_dash.md +0 -3
  105. data/test/rule_tests/incorrect_bullet_style_dash_style.rb +0 -3
  106. data/test/rule_tests/incorrect_bullet_style_plus.md +0 -3
  107. data/test/rule_tests/incorrect_bullet_style_plus_style.rb +0 -3
  108. data/test/rule_tests/incorrect_header_atx.md +0 -6
  109. data/test/rule_tests/incorrect_header_atx_closed.md +0 -6
  110. data/test/rule_tests/incorrect_header_atx_closed_style.rb +0 -2
  111. data/test/rule_tests/incorrect_header_atx_style.rb +0 -2
  112. data/test/rule_tests/incorrect_header_setext.md +0 -6
  113. data/test/rule_tests/incorrect_header_setext_style.rb +0 -2
  114. data/test/rule_tests/inline_html.md +0 -13
  115. data/test/rule_tests/links.md +0 -9
  116. data/test/rule_tests/lists_without_blank_lines.md +0 -75
  117. data/test/rule_tests/long_lines.md +0 -3
  118. data/test/rule_tests/long_lines_100.md +0 -7
  119. data/test/rule_tests/long_lines_100_style.rb +0 -3
  120. data/test/rule_tests/long_lines_code.md +0 -45
  121. data/test/rule_tests/long_lines_code_style.rb +0 -3
  122. data/test/rule_tests/mixed_header_types_atx.md +0 -6
  123. data/test/rule_tests/mixed_header_types_atx_closed.md +0 -6
  124. data/test/rule_tests/mixed_header_types_setext.md +0 -6
  125. data/test/rule_tests/no_first_line_header.md +0 -1
  126. data/test/rule_tests/no_first_line_header_style.rb +0 -1
  127. data/test/rule_tests/no_first_line_top_level_header.md +0 -1
  128. data/test/rule_tests/no_first_line_top_level_header_style.rb +0 -1
  129. data/test/rule_tests/ordered_list_item_prefix.md +0 -13
  130. data/test/rule_tests/ordered_list_item_prefix_ordered.md +0 -13
  131. data/test/rule_tests/ordered_list_item_prefix_ordered_style.rb +0 -3
  132. data/test/rule_tests/reversed_link.md +0 -7
  133. data/test/rule_tests/spaces_after_list_marker.md +0 -74
  134. data/test/rule_tests/spaces_after_list_marker_style.rb +0 -5
  135. data/test/rule_tests/spaces_inside_codespan_elements.md +0 -7
  136. data/test/rule_tests/spaces_inside_emphasis_markers.md +0 -35
  137. data/test/rule_tests/spaces_inside_link_text.md +0 -10
  138. data/test/rule_tests/trailing_spaces_br.md +0 -4
  139. data/test/rule_tests/trailing_spaces_br_style.rb +0 -3
  140. data/test/rule_tests/whitespace_issues.md +0 -3
  141. data/test/setup_tests.rb +0 -5
  142. data/test/test_cli.rb +0 -300
  143. data/test/test_ruledocs.rb +0 -52
  144. data/test/test_rules.rb +0 -58
  145. data/tools/README.md +0 -3
  146. data/tools/docker/Dockerfile +0 -13
  147. data/tools/docker/README.md +0 -19
  148. data/tools/test_location.rb +0 -20
  149. data/tools/view_markdown.rb +0 -11
@@ -5,8 +5,8 @@ require 'kramdown/parser/gfm'
5
5
 
6
6
  module Kramdown
7
7
  module Parser
8
+ # modified parser class - see comment above
8
9
  class MarkdownLint < Kramdown::Parser::Kramdown
9
-
10
10
  def initialize(source, options)
11
11
  super
12
12
  i = @block_parsers.index(:codeblock_fenced)
@@ -1,4 +1,4 @@
1
- rule "MD001", "Header levels should only increment by one level at a time" do
1
+ rule 'MD001', 'Header levels should only increment by one level at a time' do
2
2
  tags :headers
3
3
  aliases 'header-increment'
4
4
  check do |doc|
@@ -6,26 +6,26 @@ rule "MD001", "Header levels should only increment by one level at a time" do
6
6
  old_level = nil
7
7
  errors = []
8
8
  headers.each do |h|
9
- if old_level and h[:level] > old_level + 1
10
- errors << h[:location]
11
- end
9
+ errors << h[:location] if old_level && (h[:level] > old_level + 1)
12
10
  old_level = h[:level]
13
11
  end
14
12
  errors
15
13
  end
16
14
  end
17
15
 
18
- rule "MD002", "First header should be a top level header" do
16
+ rule 'MD002', 'First header should be a top level header' do
19
17
  tags :headers
20
18
  aliases 'first-header-h1'
21
19
  params :level => 1
22
20
  check do |doc|
23
21
  first_header = doc.find_type(:header).first
24
- [first_header[:location]] if first_header and first_header[:level] != @params[:level]
22
+ if first_header && (first_header[:level] != @params[:level])
23
+ [first_header[:location]]
24
+ end
25
25
  end
26
26
  end
27
27
 
28
- rule "MD003", "Header style" do
28
+ rule 'MD003', 'Header style' do
29
29
  # Header styles are things like ### and adding underscores
30
30
  # See http://daringfireball.net/projects/markdown/syntax#header
31
31
  tags :headers
@@ -37,47 +37,54 @@ rule "MD003", "Header style" do
37
37
  if headers.empty?
38
38
  nil
39
39
  else
40
- if @params[:style] == :consistent
41
- doc_style = doc.header_style(headers.first)
42
- else
43
- doc_style = @params[:style]
44
- end
40
+ doc_style = if @params[:style] == :consistent
41
+ doc.header_style(headers.first)
42
+ else
43
+ @params[:style]
44
+ end
45
45
  if doc_style == :setext_with_atx
46
- headers.map { |h| doc.element_linenumber(h) \
47
- unless doc.header_style(h) == :setext or \
48
- (doc.header_style(h) == :atx and \
49
- h.options[:level] > 2) }.compact
46
+ headers.map do |h|
47
+ doc.element_linenumber(h) \
48
+ unless (doc.header_style(h) == :setext) || \
49
+ ((doc.header_style(h) == :atx) && \
50
+ (h.options[:level] > 2))
51
+ end.compact
50
52
  else
51
- headers.map { |h| doc.element_linenumber(h) \
52
- if doc.header_style(h) != doc_style }.compact
53
+ headers.map do |h|
54
+ doc.element_linenumber(h) \
55
+ if doc.header_style(h) != doc_style
56
+ end.compact
53
57
  end
54
58
  end
55
59
  end
56
60
  end
57
61
 
58
- rule "MD004", "Unordered list style" do
62
+ rule 'MD004', 'Unordered list style' do
59
63
  tags :bullet, :ul
60
64
  aliases 'ul-style'
61
65
  # :style can be one of :consistent, :asterisk, :plus, :dash
62
66
  params :style => :consistent
63
67
  check do |doc|
64
- bullets = doc.find_type_elements(:ul).map {|l|
65
- doc.find_type_elements(:li, false, l.children)}.flatten
68
+ bullets = doc.find_type_elements(:ul).map do |l|
69
+ doc.find_type_elements(:li, false, l.children)
70
+ end.flatten
66
71
  if bullets.empty?
67
72
  nil
68
73
  else
69
- if @params[:style] == :consistent
70
- doc_style = doc.list_style(bullets.first)
71
- else
72
- doc_style = @params[:style]
73
- end
74
- bullets.map { |b| doc.element_linenumber(b) \
75
- if doc.list_style(b) != doc_style }.compact
74
+ doc_style = if @params[:style] == :consistent
75
+ doc.list_style(bullets.first)
76
+ else
77
+ @params[:style]
78
+ end
79
+ bullets.map do |b|
80
+ doc.element_linenumber(b) \
81
+ if doc.list_style(b) != doc_style
82
+ end.compact
76
83
  end
77
84
  end
78
85
  end
79
86
 
80
- rule "MD005", "Inconsistent indentation for list items at the same level" do
87
+ rule 'MD005', 'Inconsistent indentation for list items at the same level' do
81
88
  tags :bullet, :ul, :indentation
82
89
  aliases 'list-indent'
83
90
  check do |doc|
@@ -97,29 +104,30 @@ rule "MD005", "Inconsistent indentation for list items at the same level" do
97
104
  end
98
105
  end
99
106
 
100
- rule "MD006", "Consider starting bulleted lists at the beginning of the line" do
107
+ rule 'MD006', 'Consider starting bulleted lists at the beginning of the line' do
101
108
  # Starting at the beginning of the line means that indendation for each
102
109
  # bullet level can be identical.
103
110
  tags :bullet, :ul, :indentation
104
111
  aliases 'ul-start-left'
105
112
  check do |doc|
106
- doc.find_type(:ul, false).select{
107
- |e| doc.indent_for(doc.element_line(e)) != 0 }.map{ |e| e[:location] }
113
+ doc.find_type(:ul, false).reject do |e|
114
+ doc.indent_for(doc.element_line(e)) == 0
115
+ end.map { |e| e[:location] }
108
116
  end
109
117
  end
110
118
 
111
- rule "MD007", "Unordered list indentation" do
119
+ rule 'MD007', 'Unordered list indentation' do
112
120
  tags :bullet, :ul, :indentation
113
121
  aliases 'ul-indent'
114
122
  params :indent => 2
115
123
  check do |doc|
116
- indents = []
117
124
  errors = []
118
- indents = doc.find_type(:ul).map {
119
- |e| [doc.indent_for(doc.element_line(e)), doc.element_linenumber(e)] }
125
+ indents = doc.find_type(:ul).map do |e|
126
+ [doc.indent_for(doc.element_line(e)), doc.element_linenumber(e)]
127
+ end
120
128
  curr_indent = indents[0][0] unless indents.empty?
121
129
  indents.each do |indent, linenum|
122
- if indent > curr_indent and indent - curr_indent != @params[:indent]
130
+ if (indent > curr_indent) && (indent - curr_indent != @params[:indent])
123
131
  errors << linenum
124
132
  end
125
133
  curr_indent = indent
@@ -128,7 +136,7 @@ rule "MD007", "Unordered list indentation" do
128
136
  end
129
137
  end
130
138
 
131
- rule "MD009", "Trailing spaces" do
139
+ rule 'MD009', 'Trailing spaces' do
132
140
  tags :whitespace
133
141
  aliases 'no-trailing-spaces'
134
142
  params :br_spaces => 0
@@ -141,7 +149,7 @@ rule "MD009", "Trailing spaces" do
141
149
  end
142
150
  end
143
151
 
144
- rule "MD010", "Hard tabs" do
152
+ rule 'MD010', 'Hard tabs' do
145
153
  tags :whitespace, :hard_tab
146
154
  aliases 'no-hard-tabs'
147
155
  check do |doc|
@@ -149,7 +157,7 @@ rule "MD010", "Hard tabs" do
149
157
  end
150
158
  end
151
159
 
152
- rule "MD011", "Reversed link syntax" do
160
+ rule 'MD011', 'Reversed link syntax' do
153
161
  tags :links
154
162
  aliases 'no-reversed-links'
155
163
  check do |doc|
@@ -157,39 +165,47 @@ rule "MD011", "Reversed link syntax" do
157
165
  end
158
166
  end
159
167
 
160
- rule "MD012", "Multiple consecutive blank lines" do
168
+ rule 'MD012', 'Multiple consecutive blank lines' do
161
169
  tags :whitespace, :blank_lines
162
170
  aliases 'no-multiple-blanks'
163
171
  check do |doc|
164
172
  # Every line in the document that is part of a code block. Blank lines
165
173
  # inside of a code block are acceptable.
166
- codeblock_lines = doc.find_type_elements(:codeblock).map{
167
- |e| (doc.element_linenumber(e)..
168
- doc.element_linenumber(e) + e.value.lines.count).to_a }.flatten
174
+ codeblock_lines = doc.find_type_elements(:codeblock).map do |e|
175
+ (doc.element_linenumber(e)..
176
+ doc.element_linenumber(e) + e.value.lines.count).to_a
177
+ end.flatten
169
178
  blank_lines = doc.matching_lines(/^\s*$/)
170
- cons_blank_lines = blank_lines.each_cons(2).select{
171
- |p, n| n - p == 1}.map{|p, n| n}
179
+ cons_blank_lines = blank_lines.each_cons(2).select do |p, n|
180
+ n - p == 1
181
+ end.map { |_p, n| n }
172
182
  cons_blank_lines - codeblock_lines
173
183
  end
174
184
  end
175
185
 
176
- rule "MD013", "Line length" do
186
+ rule 'MD013', 'Line length' do
177
187
  tags :line_length
178
188
  aliases 'line-length'
179
189
  params :line_length => 80, :code_blocks => true, :tables => true
180
190
  check do |doc|
181
191
  # Every line in the document that is part of a code block.
182
- codeblock_lines = doc.find_type_elements(:codeblock).map{
183
- |e| (doc.element_linenumber(e)..
184
- doc.element_linenumber(e) + e.value.lines.count).to_a }.flatten
192
+ codeblock_lines = doc.find_type_elements(:codeblock).map do |e|
193
+ (doc.element_linenumber(e)..
194
+ doc.element_linenumber(e) + e.value.lines.count).to_a
195
+ end.flatten
185
196
  # Every line in the document that is part of a table.
186
197
  locations = doc.elements
187
- .map { |e| [e.options[:location], e] }
188
- .reject { |l, _| l.nil? }
189
- table_lines = locations.map.with_index {
190
- |(l, e), i| (i + 1 < locations.size ?
191
- (l..locations[i+1].first - 1) :
192
- (l..doc.lines.count)).to_a if e.type == :table }.flatten
198
+ .map { |e| [e.options[:location], e] }
199
+ .reject { |l, _| l.nil? }
200
+ table_lines = locations.map.with_index do |(l, e), i|
201
+ if e.type == :table
202
+ if i + 1 < locations.size
203
+ (l..locations[i + 1].first - 1).to_a
204
+ else
205
+ (l..doc.lines.count).to_a
206
+ end
207
+ end
208
+ end.flatten
193
209
  overlines = doc.matching_lines(/^.{#{@params[:line_length]}}.*\s/)
194
210
  overlines -= codeblock_lines unless params[:code_blocks]
195
211
  overlines -= table_lines unless params[:tables]
@@ -197,18 +213,18 @@ rule "MD013", "Line length" do
197
213
  end
198
214
  end
199
215
 
200
- rule "MD014", "Dollar signs used before commands without showing output" do
216
+ rule 'MD014', 'Dollar signs used before commands without showing output' do
201
217
  tags :code
202
218
  aliases 'commands-show-output'
203
219
  check do |doc|
204
- doc.find_type_elements(:codeblock).select{
205
- |e| not e.value.empty? and
206
- not e.value.split(/\n+/).map{|l| l.match(/^\$\s/)}.include?(nil)
207
- }.map{|e| doc.element_linenumber(e)}
220
+ doc.find_type_elements(:codeblock).select do |e|
221
+ !e.value.empty? and
222
+ !e.value.split(/\n+/).map { |l| l.match(/^\$\s/) }.include?(nil)
223
+ end.map { |e| doc.element_linenumber(e) }
208
224
  end
209
225
  end
210
226
 
211
- rule "MD018", "No space after hash on atx style header" do
227
+ rule 'MD018', 'No space after hash on atx style header' do
212
228
  tags :headers, :atx, :spaces
213
229
  aliases 'no-missing-space-atx'
214
230
  check do |doc|
@@ -218,7 +234,7 @@ rule "MD018", "No space after hash on atx style header" do
218
234
  end
219
235
  end
220
236
 
221
- rule "MD019", "Multiple spaces after hash on atx style header" do
237
+ rule 'MD019', 'Multiple spaces after hash on atx style header' do
222
238
  tags :headers, :atx, :spaces
223
239
  aliases 'no-multiple-space-atx'
224
240
  check do |doc|
@@ -228,7 +244,7 @@ rule "MD019", "Multiple spaces after hash on atx style header" do
228
244
  end
229
245
  end
230
246
 
231
- rule "MD020", "No space inside hashes on closed atx style header" do
247
+ rule 'MD020', 'No space inside hashes on closed atx style header' do
232
248
  tags :headers, :atx_closed, :spaces
233
249
  aliases 'no-missing-space-closed-atx'
234
250
  check do |doc|
@@ -240,7 +256,7 @@ rule "MD020", "No space inside hashes on closed atx style header" do
240
256
  end
241
257
  end
242
258
 
243
- rule "MD021", "Multiple spaces inside hashes on closed atx style header" do
259
+ rule 'MD021', 'Multiple spaces inside hashes on closed atx style header' do
244
260
  tags :headers, :atx_closed, :spaces
245
261
  aliases 'no-multiple-space-closed-atx'
246
262
  check do |doc|
@@ -252,7 +268,7 @@ rule "MD021", "Multiple spaces inside hashes on closed atx style header" do
252
268
  end
253
269
  end
254
270
 
255
- rule "MD022", "Headers should be surrounded by blank lines" do
271
+ rule 'MD022', 'Headers should be surrounded by blank lines' do
256
272
  tags :headers, :blank_lines
257
273
  aliases 'blanks-around-headers'
258
274
  check do |doc|
@@ -261,13 +277,11 @@ rule "MD022", "Headers should be surrounded by blank lines" do
261
277
  header_bad = false
262
278
  linenum = doc.element_linenumber(h)
263
279
  # Check previous line
264
- if linenum > 1 and not doc.lines[linenum - 2].empty?
265
- header_bad = true
266
- end
280
+ header_bad = true if (linenum > 1) && !doc.lines[linenum - 2].empty?
267
281
  # Check next line
268
282
  next_line_idx = doc.header_style(h) == :setext ? linenum + 1 : linenum
269
283
  next_line = doc.lines[next_line_idx]
270
- header_bad = true if not next_line.nil? and not next_line.empty?
284
+ header_bad = true if !next_line.nil? && !next_line.empty?
271
285
  errors << linenum if header_bad
272
286
  end
273
287
  # Kramdown requires that headers start on a block boundary, so in most
@@ -275,16 +289,14 @@ rule "MD022", "Headers should be surrounded by blank lines" do
275
289
  # to check regular text and pick out headers ourselves too
276
290
  doc.find_type_elements(:p, false).each do |p|
277
291
  linenum = doc.element_linenumber(p)
278
- text = p.children.select { |e| e.type == :text }.map {|e| e.value }.join
292
+ text = p.children.select { |e| e.type == :text }.map(&:value).join
279
293
  lines = text.split("\n")
280
- prev_lines = ["", ""]
294
+ prev_lines = ['', '']
281
295
  lines.each do |line|
282
296
  # First look for ATX style headers without blank lines before
283
- if line.match(/^\#{1,6}/) and not prev_lines[1].empty?
284
- errors << linenum
285
- end
297
+ errors << linenum if line.match(/^\#{1,6}/) && !prev_lines[1].empty?
286
298
  # Next, look for setext style
287
- if line.match(/^(-+|=+)\s*$/) and not prev_lines[0].empty?
299
+ if line.match(/^(-+|=+)\s*$/) && !prev_lines[0].empty?
288
300
  errors << linenum - 1
289
301
  end
290
302
  linenum += 1
@@ -296,7 +308,7 @@ rule "MD022", "Headers should be surrounded by blank lines" do
296
308
  end
297
309
  end
298
310
 
299
- rule "MD023", "Headers must start at the beginning of the line" do
311
+ rule 'MD023', 'Headers must start at the beginning of the line' do
300
312
  tags :headers, :spaces
301
313
  aliases 'header-start-left'
302
314
  check do |doc|
@@ -311,14 +323,12 @@ rule "MD023", "Headers must start at the beginning of the line" do
311
323
  doc.find_type_elements(:p, false).each do |p|
312
324
  linenum = doc.element_linenumber(p)
313
325
  lines = doc.extract_text(p)
314
- prev_line = ""
326
+ prev_line = ''
315
327
  lines.each do |line|
316
328
  # First look for ATX style headers
317
- if line.match(/^\s+\#{1,6}/)
318
- errors << linenum
319
- end
329
+ errors << linenum if line.match(/^\s+\#{1,6}/)
320
330
  # Next, look for setext style
321
- if line.match(/^\s+(-+|=+)\s*$/) and not prev_line.empty?
331
+ if line.match(/^\s+(-+|=+)\s*$/) && !prev_line.empty?
322
332
  errors << linenum - 1
323
333
  end
324
334
  linenum += 1
@@ -329,7 +339,7 @@ rule "MD023", "Headers must start at the beginning of the line" do
329
339
  end
330
340
  end
331
341
 
332
- rule "MD024", "Multiple headers with the same content" do
342
+ rule 'MD024', 'Multiple headers with the same content' do
333
343
  tags :headers
334
344
  aliases 'no-duplicate-header'
335
345
  params :allow_different_nesting => false
@@ -357,8 +367,8 @@ rule "MD024", "Multiple headers with the same content" do
357
367
  stack.pop
358
368
  elsif current_level < level
359
369
  stack.push([text])
360
- else
361
- same_nesting_duplicates.add(header) if stack.last.include?(text)
370
+ elsif stack.last.include?(text)
371
+ same_nesting_duplicates.add(header)
362
372
  end
363
373
 
364
374
  current_level = level
@@ -371,30 +381,34 @@ rule "MD024", "Multiple headers with the same content" do
371
381
  end
372
382
  end
373
383
 
374
- rule "MD025", "Multiple top level headers in the same document" do
384
+ rule 'MD025', 'Multiple top level headers in the same document' do
375
385
  tags :headers
376
386
  aliases 'single-h1'
377
387
  params :level => 1
378
388
  check do |doc|
379
- headers = doc.find_type(:header, false).select { |h| h[:level] == params[:level] }
380
- if not headers.empty? and doc.element_linenumber(headers[0]) == 1
389
+ headers = doc.find_type(:header, false).select do |h|
390
+ h[:level] == params[:level]
391
+ end
392
+ if !headers.empty? && (doc.element_linenumber(headers[0]) == 1)
381
393
  headers[1..-1].map { |h| doc.element_linenumber(h) }
382
394
  end
383
395
  end
384
396
  end
385
397
 
386
- rule "MD026", "Trailing punctuation in header" do
398
+ rule 'MD026', 'Trailing punctuation in header' do
387
399
  tags :headers
388
400
  aliases 'no-trailing-punctuation'
389
401
  params :punctuation => '.,;:!?'
390
402
  check do |doc|
391
- doc.find_type(:header).select {
392
- |h| h[:raw_text].match(/[#{params[:punctuation]}]$/) }.map {
393
- |h| doc.element_linenumber(h) }
403
+ doc.find_type(:header).select do |h|
404
+ h[:raw_text].match(/[#{params[:punctuation]}]$/)
405
+ end.map do |h|
406
+ doc.element_linenumber(h)
407
+ end
394
408
  end
395
409
  end
396
410
 
397
- rule "MD027", "Multiple spaces after blockquote symbol" do
411
+ rule 'MD027', 'Multiple spaces after blockquote symbol' do
398
412
  tags :blockquote, :whitespace, :indentation
399
413
  aliases 'no-multiple-space-blockquote'
400
414
  check do |doc|
@@ -403,7 +417,7 @@ rule "MD027", "Multiple spaces after blockquote symbol" do
403
417
  linenum = doc.element_linenumber(e)
404
418
  lines = doc.extract_text(e, /^\s*> /)
405
419
  lines.each do |line|
406
- errors << linenum if line.start_with?(" ")
420
+ errors << linenum if line.start_with?(' ')
407
421
  linenum += 1
408
422
  end
409
423
  end
@@ -411,7 +425,7 @@ rule "MD027", "Multiple spaces after blockquote symbol" do
411
425
  end
412
426
  end
413
427
 
414
- rule "MD028", "Blank line inside blockquote" do
428
+ rule 'MD028', 'Blank line inside blockquote' do
415
429
  tags :blockquote, :whitespace
416
430
  aliases 'no-blanks-blockquote'
417
431
  check do |doc|
@@ -420,7 +434,7 @@ rule "MD028", "Blank line inside blockquote" do
420
434
  elements.each do |e|
421
435
  prev.shift
422
436
  prev << e.type
423
- if prev == [:blockquote, :blank, :blockquote]
437
+ if prev == %i{blockquote blank blockquote}
424
438
  # The current location is the start of the second blockquote, so the
425
439
  # line before will be a blank line in between the two, or at least the
426
440
  # lowest blank line if there are more than one.
@@ -435,42 +449,49 @@ rule "MD028", "Blank line inside blockquote" do
435
449
  end
436
450
  end
437
451
 
438
- rule "MD029", "Ordered list item prefix" do
452
+ rule 'MD029', 'Ordered list item prefix' do
439
453
  tags :ol
440
454
  aliases 'ol-prefix'
441
455
  # Style can be :one or :ordered
442
456
  params :style => :one
443
457
  check do |doc|
444
- if params[:style] == :ordered
445
- doc.find_type_elements(:ol).map { |l|
446
- doc.find_type_elements(:li, false, l.children).map.with_index { |i, idx|
447
- doc.element_linenumber(i) \
448
- unless doc.element_line(i).strip.start_with?("#{idx+1}. ")
449
- }
450
- }.flatten.compact
451
- elsif params[:style] == :one
452
- doc.find_type_elements(:ol).map { |l|
453
- doc.find_type_elements(:li, false, l.children) }.flatten.map { |i|
454
- doc.element_linenumber(i) \
455
- unless doc.element_line(i).strip.start_with?('1. ') }.compact
458
+ case params[:style]
459
+ when :ordered
460
+ doc.find_type_elements(:ol).map do |l|
461
+ doc.find_type_elements(:li, false, l.children)
462
+ .map.with_index do |i, idx|
463
+ unless doc.element_line(i).strip.start_with?("#{idx + 1}. ")
464
+ doc.element_linenumber(i)
465
+ end
466
+ end
467
+ end.flatten.compact
468
+ when :one
469
+ doc.find_type_elements(:ol).map do |l|
470
+ doc.find_type_elements(:li, false, l.children)
471
+ end.flatten.map do |i|
472
+ unless doc.element_line(i).strip.start_with?('1. ')
473
+ doc.element_linenumber(i)
474
+ end
475
+ end.compact
456
476
  end
457
477
  end
458
478
  end
459
479
 
460
- rule "MD030", "Spaces after list markers" do
480
+ rule 'MD030', 'Spaces after list markers' do
461
481
  tags :ol, :ul, :whitespace
462
482
  aliases 'list-marker-space'
463
483
  params :ul_single => 1, :ol_single => 1, :ul_multi => 1, :ol_multi => 1
464
484
  check do |doc|
465
485
  errors = []
466
- doc.find_type_elements([:ul, :ol]).each do |l|
486
+ doc.find_type_elements(%i{ul ol}).each do |l|
467
487
  list_type = l.type.to_s
468
488
  items = doc.find_type_elements(:li, false, l.children)
469
489
  # The entire list is to use the multi-paragraph spacing rule if any of
470
490
  # the items in it have multiple paragraphs/other block items.
471
- srule = items.map { |i| i.children.length }.max > 1 ? "multi" : "single"
491
+ srule = items.map { |i| i.children.length }.max > 1 ? 'multi' : 'single'
472
492
  items.each do |i|
473
- actual_spaces = doc.element_line(i).match(/^\s*\S+(\s+)/)[1].length
493
+ actual_spaces = doc.element_line(i).gsub(/^> /, '')
494
+ .match(/^\s*\S+(\s+)/)[1].length
474
495
  required_spaces = params["#{list_type}_#{srule}".to_sym]
475
496
  errors << doc.element_linenumber(i) if required_spaces != actual_spaces
476
497
  end
@@ -479,7 +500,7 @@ rule "MD030", "Spaces after list markers" do
479
500
  end
480
501
  end
481
502
 
482
- rule "MD031", "Fenced code blocks should be surrounded by blank lines" do
503
+ rule 'MD031', 'Fenced code blocks should be surrounded by blank lines' do
483
504
  tags :code, :blank_lines
484
505
  aliases 'blanks-around-fences'
485
506
  check do |doc|
@@ -488,23 +509,29 @@ rule "MD031", "Fenced code blocks should be surrounded by blank lines" do
488
509
  # blocks without surrounding whitespace, so examine the lines directly.
489
510
  in_code = false
490
511
  fence = nil
491
- lines = [ "" ] + doc.lines + [ "" ]
512
+ lines = [''] + doc.lines + ['']
492
513
  lines.each_with_index do |line, linenum|
493
514
  line.strip.match(/^(`{3,}|~{3,})/)
494
- if $1 and (not in_code or $1.slice(0, fence.length) == fence)
495
- fence = in_code ? nil : $1
496
- in_code = !in_code
497
- if (in_code and not lines[linenum - 1].empty?) or
498
- (not in_code and not lines[linenum + 1].empty?)
499
- errors << linenum
500
- end
515
+ unless Regexp.last_match(1) &&
516
+ (
517
+ !in_code ||
518
+ (Regexp.last_match(1).slice(0, fence.length) == fence)
519
+ )
520
+ next
521
+ end
522
+
523
+ fence = in_code ? nil : Regexp.last_match(1)
524
+ in_code = !in_code
525
+ if (in_code && !lines[linenum - 1].empty?) ||
526
+ (!in_code && !lines[linenum + 1].empty?)
527
+ errors << linenum
501
528
  end
502
529
  end
503
530
  errors
504
531
  end
505
532
  end
506
533
 
507
- rule "MD032", "Lists should be surrounded by blank lines" do
534
+ rule 'MD032', 'Lists should be surrounded by blank lines' do
508
535
  tags :bullet, :ul, :ol, :blank_lines
509
536
  aliases 'blanks-around-lists'
510
537
  check do |doc|
@@ -514,20 +541,24 @@ rule "MD032", "Lists should be surrounded by blank lines" do
514
541
  in_list = false
515
542
  in_code = false
516
543
  fence = nil
517
- prev_line = ""
544
+ prev_line = ''
518
545
  doc.lines.each_with_index do |line, linenum|
519
- if not in_code
520
- list_marker = line.strip.match(/^([\*\+\-]|(\d+\.))\s/)
521
- if list_marker and not in_list and not prev_line.match(/^($|\s)/)
546
+ next if line.strip == '{:toc}'
547
+
548
+ unless in_code
549
+ list_marker = line.strip.match(/^([*+\-]|(\d+\.))\s/)
550
+ if list_marker && !in_list && !prev_line.match(/^($|\s)/)
522
551
  errors << linenum + 1
523
- elsif not list_marker and in_list and not line.match(/^($|\s)/)
552
+ elsif !list_marker && in_list && !line.match(/^($|\s)/)
524
553
  errors << linenum
525
554
  end
526
555
  in_list = list_marker
527
556
  end
528
557
  line.strip.match(/^(`{3,}|~{3,})/)
529
- if $1 and (not in_code or $1.slice(0, fence.length) == fence)
530
- fence = in_code ? nil : $1
558
+ if Regexp.last_match(1) && (
559
+ !in_code || (Regexp.last_match(1).slice(0, fence.length) == fence)
560
+ )
561
+ fence = in_code ? nil : Regexp.last_match(1)
531
562
  in_code = !in_code
532
563
  in_list = false
533
564
  end
@@ -537,7 +568,7 @@ rule "MD032", "Lists should be surrounded by blank lines" do
537
568
  end
538
569
  end
539
570
 
540
- rule "MD033", "Inline HTML" do
571
+ rule 'MD033', 'Inline HTML' do
541
572
  tags :html
542
573
  aliases 'no-inline-html'
543
574
  check do |doc|
@@ -545,15 +576,15 @@ rule "MD033", "Inline HTML" do
545
576
  end
546
577
  end
547
578
 
548
- rule "MD034", "Bare URL used" do
579
+ rule 'MD034', 'Bare URL used' do
549
580
  tags :links, :url
550
581
  aliases 'no-bare-urls'
551
582
  check do |doc|
552
- doc.matching_text_element_lines(/https?:\/\//)
583
+ doc.matching_text_element_lines(%r{https?://})
553
584
  end
554
585
  end
555
586
 
556
- rule "MD035", "Horizontal rule style" do
587
+ rule 'MD035', 'Horizontal rule style' do
557
588
  tags :hr
558
589
  aliases 'hr-style'
559
590
  params :style => :consistent
@@ -562,17 +593,19 @@ rule "MD035", "Horizontal rule style" do
562
593
  if hrs.empty?
563
594
  []
564
595
  else
565
- if params[:style] == :consistent
566
- doc_style = doc.element_line(hrs[0])
567
- else
568
- doc_style = params[:style]
569
- end
570
- doc.element_linenumbers(hrs.select{|e| doc.element_line(e) != doc_style})
596
+ doc_style = if params[:style] == :consistent
597
+ doc.element_line(hrs[0])
598
+ else
599
+ params[:style]
600
+ end
601
+ doc.element_linenumbers(
602
+ hrs.reject { |e| doc.element_line(e) == doc_style },
603
+ )
571
604
  end
572
605
  end
573
606
  end
574
607
 
575
- rule "MD036", "Emphasis used instead of a header" do
608
+ rule 'MD036', 'Emphasis used instead of a header' do
576
609
  tags :headers, :emphasis
577
610
  aliases 'no-emphasis-as-header'
578
611
  params :punctuation => '.,;:!?'
@@ -582,17 +615,20 @@ rule "MD036", "Emphasis used instead of a header" do
582
615
  errors = []
583
616
  doc.find_type_elements(:p, false).each do |p|
584
617
  next if p.children.length > 1
585
- next unless [:em, :strong].include?(p.children[0].type)
586
- lines = doc.extract_text(p.children[0], "", false)
618
+ next unless %i{em strong}.include?(p.children[0].type)
619
+
620
+ lines = doc.extract_text(p.children[0], '', false)
587
621
  next if lines.length > 1
622
+ next if lines.empty?
588
623
  next if lines[0].match(/[#{params[:punctuation]}]$/)
624
+
589
625
  errors << doc.element_linenumber(p)
590
626
  end
591
627
  errors
592
628
  end
593
629
  end
594
630
 
595
- rule "MD037", "Spaces inside emphasis markers" do
631
+ rule 'MD037', 'Spaces inside emphasis markers' do
596
632
  tags :whitespace, :emphasis
597
633
  aliases 'no-space-in-emphasis'
598
634
  check do |doc|
@@ -604,54 +640,59 @@ rule "MD037", "Spaces inside emphasis markers" do
604
640
  end
605
641
  end
606
642
 
607
- rule "MD038", "Spaces inside code span elements" do
643
+ rule 'MD038', 'Spaces inside code span elements' do
608
644
  tags :whitespace, :code
609
645
  aliases 'no-space-in-code'
610
646
  check do |doc|
611
647
  # We only want to check single line codespan elements and not fenced code
612
648
  # block that happen to be parsed as code spans.
613
- doc.element_linenumbers(doc.find_type_elements(:codespan).select{
614
- |i| i.value.match(/(^\s|\s$)/) and not i.value.include?("\n")})
649
+ doc.element_linenumbers(
650
+ doc.find_type_elements(:codespan).select do |i|
651
+ i.value.match(/(^\s|\s$)/) and !i.value.include?("\n")
652
+ end,
653
+ )
615
654
  end
616
655
  end
617
656
 
618
- rule "MD039", "Spaces inside link text" do
657
+ rule 'MD039', 'Spaces inside link text' do
619
658
  tags :whitespace, :links
620
659
  aliases 'no-space-in-links'
621
660
  check do |doc|
622
661
  doc.element_linenumbers(
623
- doc.find_type_elements(:a).select{|e|
624
- e.children[0].type == :text and (
625
- e.children.first.value.start_with?(" ") or
626
- e.children.last.value.end_with?(" "))}
662
+ doc.find_type_elements(:a).reject { |e| e.children.empty? }.select do |e|
663
+ e.children.first.type == :text && e.children.last.type == :text and (
664
+ e.children.first.value.start_with?(' ') or
665
+ e.children.last.value.end_with?(' '))
666
+ end,
627
667
  )
628
668
  end
629
669
  end
630
670
 
631
- rule "MD040", "Fenced code blocks should have a language specified" do
671
+ rule 'MD040', 'Fenced code blocks should have a language specified' do
632
672
  tags :code, :language
633
673
  aliases 'fenced-code-language'
634
674
  check do |doc|
635
675
  # Kramdown parses code blocks with language settings as code blocks with
636
676
  # the class attribute set to language-languagename.
637
- doc.element_linenumbers(doc.find_type_elements(:codeblock).select{|i|
638
- not i.attr['class'].to_s.start_with?("language-") and
639
- not doc.element_line(i).start_with?(" ")})
677
+ doc.element_linenumbers(doc.find_type_elements(:codeblock).select do |i|
678
+ !i.attr['class'].to_s.start_with?('language-') and
679
+ !doc.element_line(i).start_with?(' ')
680
+ end)
640
681
  end
641
682
  end
642
683
 
643
- rule "MD041", "First line in file should be a top level header" do
684
+ rule 'MD041', 'First line in file should be a top level header' do
644
685
  tags :headers
645
686
  aliases 'first-line-h1'
646
687
  params :level => 1
647
688
  check do |doc|
648
689
  first_header = doc.find_type(:header).first
649
- [1] if first_header.nil? or first_header[:location] != 1 \
650
- or first_header[:level] != params[:level]
690
+ [1] if first_header.nil? || (first_header[:location] != 1) \
691
+ || (first_header[:level] != params[:level])
651
692
  end
652
693
  end
653
694
 
654
- rule "MD046", "Code block style" do
695
+ rule 'MD046', 'Code block style' do
655
696
  tags :code
656
697
  aliases 'code-block-style'
657
698
  params :style => :fenced
@@ -661,18 +702,32 @@ rule "MD046", "Code block style" do
661
702
  doc.find_type_elements(:codeblock).select do |i|
662
703
  # for consistent we determine the first one
663
704
  if style == :consistent
664
- if doc.element_line(i).start_with?(" ")
665
- style = :indented
666
- else
667
- style = :fenced
668
- end
705
+ style = if doc.element_line(i).start_with?(' ')
706
+ :indented
707
+ else
708
+ :fenced
709
+ end
669
710
  end
670
- if @params[:style] == :fenced
671
- doc.element_line(i).start_with?(" ")
711
+ if style == :fenced
712
+ # if our parent is a list or a codeblock, we need to ignore
713
+ # its spaces, plus 4 more
714
+ parent = i.options[:parent]
715
+ ignored_spaces = 0
716
+ if parent
717
+ parent.options.delete(:children)
718
+ parent.options.delete(:parent)
719
+ if %i{li codeblock}.include?(parent.type)
720
+ linenum = doc.element_linenumbers([parent]).first
721
+ indent = doc.indent_for(doc.lines[linenum - 1])
722
+ ignored_spaces = indent + 4
723
+ end
724
+ end
725
+ start = ' ' * ignored_spaces
726
+ doc.element_line(i).start_with?("#{start} ")
672
727
  else
673
- !doc.element_line(i).start_with?(" ")
728
+ !doc.element_line(i).start_with?(' ')
674
729
  end
675
- end
730
+ end,
676
731
  )
677
732
  end
678
733
  end