mdl 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/mdl +2 -2
- data/lib/mdl.rb +41 -32
- data/lib/mdl/cli.rb +92 -90
- data/lib/mdl/config.rb +2 -1
- data/lib/mdl/doc.rb +45 -54
- data/lib/mdl/kramdown_parser.rb +1 -1
- data/lib/mdl/rules.rb +206 -167
- data/lib/mdl/ruleset.rb +11 -10
- data/lib/mdl/style.rb +18 -16
- data/lib/mdl/styles/cirosantilli.rb +2 -2
- data/lib/mdl/styles/default.rb +1 -1
- data/lib/mdl/version.rb +1 -1
- data/mdl.gemspec +20 -18
- metadata +63 -35
data/lib/mdl/kramdown_parser.rb
CHANGED
data/lib/mdl/rules.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
rule
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
52
|
-
|
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
|
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
|
65
|
-
doc.find_type_elements(:li, false, l.children)
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
bullets.map
|
75
|
-
|
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
|
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
|
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).
|
107
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
168
|
-
|
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
|
-
|
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
|
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
|
-
|
184
|
-
|
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
|
-
|
188
|
-
|
189
|
-
table_lines = locations.map.with_index
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
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
|
-
|
206
|
-
|
207
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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}/)
|
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*$/)
|
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
|
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*$/)
|
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
|
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
|
-
|
361
|
-
same_nesting_duplicates.add(header)
|
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
|
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
|
380
|
-
|
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
|
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
|
-
|
393
|
-
|
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
|
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
|
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 ==
|
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
|
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
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
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
|
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(
|
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 ?
|
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).gsub(/^> /,'')
|
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
|
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 = [
|
512
|
+
lines = [''] + doc.lines + ['']
|
492
513
|
lines.each_with_index do |line, linenum|
|
493
514
|
line.strip.match(/^(`{3,}|~{3,})/)
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
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
|
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,21 +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
546
|
next if line.strip == '{:toc}'
|
520
|
-
|
521
|
-
|
522
|
-
|
547
|
+
|
548
|
+
unless in_code
|
549
|
+
list_marker = line.strip.match(/^([*+\-]|(\d+\.))\s/)
|
550
|
+
if list_marker && !in_list && !prev_line.match(/^($|\s)/)
|
523
551
|
errors << linenum + 1
|
524
|
-
elsif
|
552
|
+
elsif !list_marker && in_list && !line.match(/^($|\s)/)
|
525
553
|
errors << linenum
|
526
554
|
end
|
527
555
|
in_list = list_marker
|
528
556
|
end
|
529
557
|
line.strip.match(/^(`{3,}|~{3,})/)
|
530
|
-
if
|
531
|
-
|
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)
|
532
562
|
in_code = !in_code
|
533
563
|
in_list = false
|
534
564
|
end
|
@@ -538,7 +568,7 @@ rule "MD032", "Lists should be surrounded by blank lines" do
|
|
538
568
|
end
|
539
569
|
end
|
540
570
|
|
541
|
-
rule
|
571
|
+
rule 'MD033', 'Inline HTML' do
|
542
572
|
tags :html
|
543
573
|
aliases 'no-inline-html'
|
544
574
|
check do |doc|
|
@@ -546,15 +576,15 @@ rule "MD033", "Inline HTML" do
|
|
546
576
|
end
|
547
577
|
end
|
548
578
|
|
549
|
-
rule
|
579
|
+
rule 'MD034', 'Bare URL used' do
|
550
580
|
tags :links, :url
|
551
581
|
aliases 'no-bare-urls'
|
552
582
|
check do |doc|
|
553
|
-
doc.matching_text_element_lines(
|
583
|
+
doc.matching_text_element_lines(%r{https?://})
|
554
584
|
end
|
555
585
|
end
|
556
586
|
|
557
|
-
rule
|
587
|
+
rule 'MD035', 'Horizontal rule style' do
|
558
588
|
tags :hr
|
559
589
|
aliases 'hr-style'
|
560
590
|
params :style => :consistent
|
@@ -563,17 +593,19 @@ rule "MD035", "Horizontal rule style" do
|
|
563
593
|
if hrs.empty?
|
564
594
|
[]
|
565
595
|
else
|
566
|
-
if params[:style] == :consistent
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
doc.element_linenumbers(
|
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
|
+
)
|
572
604
|
end
|
573
605
|
end
|
574
606
|
end
|
575
607
|
|
576
|
-
rule
|
608
|
+
rule 'MD036', 'Emphasis used instead of a header' do
|
577
609
|
tags :headers, :emphasis
|
578
610
|
aliases 'no-emphasis-as-header'
|
579
611
|
params :punctuation => '.,;:!?'
|
@@ -583,18 +615,20 @@ rule "MD036", "Emphasis used instead of a header" do
|
|
583
615
|
errors = []
|
584
616
|
doc.find_type_elements(:p, false).each do |p|
|
585
617
|
next if p.children.length > 1
|
586
|
-
next unless
|
587
|
-
|
618
|
+
next unless %i{em strong}.include?(p.children[0].type)
|
619
|
+
|
620
|
+
lines = doc.extract_text(p.children[0], '', false)
|
588
621
|
next if lines.length > 1
|
589
622
|
next if lines.empty?
|
590
623
|
next if lines[0].match(/[#{params[:punctuation]}]$/)
|
624
|
+
|
591
625
|
errors << doc.element_linenumber(p)
|
592
626
|
end
|
593
627
|
errors
|
594
628
|
end
|
595
629
|
end
|
596
630
|
|
597
|
-
rule
|
631
|
+
rule 'MD037', 'Spaces inside emphasis markers' do
|
598
632
|
tags :whitespace, :emphasis
|
599
633
|
aliases 'no-space-in-emphasis'
|
600
634
|
check do |doc|
|
@@ -606,54 +640,59 @@ rule "MD037", "Spaces inside emphasis markers" do
|
|
606
640
|
end
|
607
641
|
end
|
608
642
|
|
609
|
-
rule
|
643
|
+
rule 'MD038', 'Spaces inside code span elements' do
|
610
644
|
tags :whitespace, :code
|
611
645
|
aliases 'no-space-in-code'
|
612
646
|
check do |doc|
|
613
647
|
# We only want to check single line codespan elements and not fenced code
|
614
648
|
# block that happen to be parsed as code spans.
|
615
|
-
doc.element_linenumbers(
|
616
|
-
|
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
|
+
)
|
617
654
|
end
|
618
655
|
end
|
619
656
|
|
620
|
-
rule
|
657
|
+
rule 'MD039', 'Spaces inside link text' do
|
621
658
|
tags :whitespace, :links
|
622
659
|
aliases 'no-space-in-links'
|
623
660
|
check do |doc|
|
624
661
|
doc.element_linenumbers(
|
625
|
-
doc.find_type_elements(:a).reject{|e|e.children.empty?}.select
|
662
|
+
doc.find_type_elements(:a).reject { |e| e.children.empty? }.select do |e|
|
626
663
|
e.children.first.type == :text && e.children.last.type == :text and (
|
627
|
-
e.children.first.value.start_with?(
|
628
|
-
e.children.last.value.end_with?(
|
664
|
+
e.children.first.value.start_with?(' ') or
|
665
|
+
e.children.last.value.end_with?(' '))
|
666
|
+
end,
|
629
667
|
)
|
630
668
|
end
|
631
669
|
end
|
632
670
|
|
633
|
-
rule
|
671
|
+
rule 'MD040', 'Fenced code blocks should have a language specified' do
|
634
672
|
tags :code, :language
|
635
673
|
aliases 'fenced-code-language'
|
636
674
|
check do |doc|
|
637
675
|
# Kramdown parses code blocks with language settings as code blocks with
|
638
676
|
# the class attribute set to language-languagename.
|
639
|
-
doc.element_linenumbers(doc.find_type_elements(:codeblock).select
|
640
|
-
|
641
|
-
|
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)
|
642
681
|
end
|
643
682
|
end
|
644
683
|
|
645
|
-
rule
|
684
|
+
rule 'MD041', 'First line in file should be a top level header' do
|
646
685
|
tags :headers
|
647
686
|
aliases 'first-line-h1'
|
648
687
|
params :level => 1
|
649
688
|
check do |doc|
|
650
689
|
first_header = doc.find_type(:header).first
|
651
|
-
[1] if first_header.nil?
|
652
|
-
|
690
|
+
[1] if first_header.nil? || (first_header[:location] != 1) \
|
691
|
+
|| (first_header[:level] != params[:level])
|
653
692
|
end
|
654
693
|
end
|
655
694
|
|
656
|
-
rule
|
695
|
+
rule 'MD046', 'Code block style' do
|
657
696
|
tags :code
|
658
697
|
aliases 'code-block-style'
|
659
698
|
params :style => :fenced
|
@@ -663,11 +702,11 @@ rule "MD046", "Code block style" do
|
|
663
702
|
doc.find_type_elements(:codeblock).select do |i|
|
664
703
|
# for consistent we determine the first one
|
665
704
|
if style == :consistent
|
666
|
-
if doc.element_line(i).start_with?(
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
705
|
+
style = if doc.element_line(i).start_with?(' ')
|
706
|
+
:indented
|
707
|
+
else
|
708
|
+
:fenced
|
709
|
+
end
|
671
710
|
end
|
672
711
|
if style == :fenced
|
673
712
|
# if our parent is a list or a codeblock, we need to ignore
|
@@ -677,7 +716,7 @@ rule "MD046", "Code block style" do
|
|
677
716
|
if parent
|
678
717
|
parent.options.delete(:children)
|
679
718
|
parent.options.delete(:parent)
|
680
|
-
if
|
719
|
+
if %i{li codeblock}.include?(parent.type)
|
681
720
|
linenum = doc.element_linenumbers([parent]).first
|
682
721
|
indent = doc.indent_for(doc.lines[linenum - 1])
|
683
722
|
ignored_spaces = indent + 4
|
@@ -686,9 +725,9 @@ rule "MD046", "Code block style" do
|
|
686
725
|
start = ' ' * ignored_spaces
|
687
726
|
doc.element_line(i).start_with?("#{start} ")
|
688
727
|
else
|
689
|
-
!doc.element_line(i).start_with?(
|
728
|
+
!doc.element_line(i).start_with?(' ')
|
690
729
|
end
|
691
|
-
end
|
730
|
+
end,
|
692
731
|
)
|
693
732
|
end
|
694
733
|
end
|