mdl 0.9.0 → 0.10.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.
- 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
|