mdl 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/CHANGELOG.md +45 -0
- data/README.md +1 -0
- data/docs/RULES.md +116 -6
- data/docs/configuration.md +16 -1
- data/docs/creating_rules.md +7 -0
- data/lib/mdl.rb +26 -9
- data/lib/mdl/cli.rb +60 -9
- data/lib/mdl/doc.rb +5 -1
- data/lib/mdl/rules.rb +80 -6
- data/lib/mdl/ruleset.rb +16 -7
- data/lib/mdl/style.rb +6 -0
- data/lib/mdl/styles/default.rb +2 -1
- data/lib/mdl/styles/relaxed.rb +1 -0
- data/lib/mdl/version.rb +1 -1
- data/mdl.gemspec +1 -1
- data/test/fixtures/default_mdlrc +1 -0
- data/test/fixtures/dir_with_md_and_markdown/bar.markdown +1 -0
- data/test/fixtures/dir_with_md_and_markdown/foo.md +1 -0
- data/test/fixtures/mdlrc_disable_rules +1 -0
- data/test/fixtures/mdlrc_disable_tags +1 -0
- data/test/fixtures/mdlrc_enable_rules +1 -0
- data/test/fixtures/mdlrc_enable_tags +1 -0
- data/test/fixtures/my_ruleset.rb +6 -0
- data/test/rule_tests/blockquote_spaces.md +6 -4
- data/test/rule_tests/bulleted_list_2_space_indent_style.rb +1 -0
- data/test/rule_tests/code_block_dollar_fence.md +29 -0
- data/test/rule_tests/default_test_style.rb +4 -0
- data/test/rule_tests/fenced_code_with_nesting.md +73 -0
- data/test/rule_tests/fenced_code_without_blank_lines_style.rb +1 -0
- data/test/rule_tests/first_line_top_level_header_atx.md +3 -0
- data/test/rule_tests/first_line_top_level_header_atx_style.rb +2 -0
- data/test/rule_tests/first_line_top_level_header_setext.md +4 -0
- data/test/rule_tests/first_line_top_level_header_setext_style.rb +2 -0
- data/test/rule_tests/headers_good_setext_with_atx.md +7 -0
- data/test/rule_tests/headers_good_setext_with_atx_style.rb +2 -0
- data/test/rule_tests/hr_style_dashes_style.rb +1 -0
- data/test/rule_tests/hr_style_long_style.rb +1 -0
- data/test/rule_tests/hr_style_stars_style.rb +1 -0
- data/test/rule_tests/incorrect_bullet_style_asterisk_style.rb +1 -0
- data/test/rule_tests/incorrect_bullet_style_dash_style.rb +1 -0
- data/test/rule_tests/incorrect_bullet_style_plus_style.rb +1 -0
- data/test/rule_tests/long_lines_100_style.rb +1 -0
- data/test/rule_tests/long_lines_code.md +38 -0
- data/test/rule_tests/long_lines_code_style.rb +3 -0
- data/test/rule_tests/no_first_line_header.md +1 -0
- data/test/rule_tests/no_first_line_header_style.rb +1 -0
- data/test/rule_tests/no_first_line_top_level_header.md +1 -0
- data/test/rule_tests/no_first_line_top_level_header_style.rb +1 -0
- data/test/rule_tests/ordered_list_item_prefix_ordered_style.rb +1 -0
- data/test/rule_tests/spaces_after_list_marker_style.rb +1 -0
- data/test/rule_tests/trailing_spaces_br_style.rb +1 -0
- data/test/test_cli.rb +205 -0
- data/test/test_ruledocs.rb +8 -1
- data/test/test_rules.rb +4 -2
- metadata +55 -7
data/docs/configuration.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# Mdl configuration
|
2
2
|
|
3
3
|
Markdownlint has several options you can configure both on the command line,
|
4
|
-
or in markdownlint's configuration file: `.mdlrc
|
4
|
+
or in markdownlint's configuration file: `.mdlrc`, first looked for from the
|
5
|
+
working directory, then in your home directory.
|
5
6
|
While markdownlint will work perfectly well out of the box, this page
|
6
7
|
documents some of the options you can change to suit your needs.
|
7
8
|
|
@@ -72,3 +73,17 @@ might choose 72 characters, and another might have no line length limit at all
|
|
72
73
|
|
73
74
|
Note: the value for `style_name` must either end with `.rb` or have `/` in it
|
74
75
|
in order to tell `mdl` to look for a custom style, and not a built-in style.
|
76
|
+
|
77
|
+
Rulesets - Load a custom ruleset file. This option allows you to load custom
|
78
|
+
rules in addition to those included with markdownlint.
|
79
|
+
|
80
|
+
* Command line: `-u ruleset.rb,ruleset2.rb`, `--rules ruleset.rb,ruleset2.rb`
|
81
|
+
* Config file: `rulesets 'ruleset.rb', 'ruleset2.rb'`
|
82
|
+
* Default: Don't load any additional rulesets
|
83
|
+
|
84
|
+
No default ruleset - Skip loading the default ruleset file included with
|
85
|
+
markdownlint. Use this option if you only want to load custom rulesets.
|
86
|
+
|
87
|
+
* Command line: `-d`, `--skip-default-ruleset`
|
88
|
+
* Config file: `skip_default_ruleset true`
|
89
|
+
* Default: Load the default ruleset.
|
data/docs/creating_rules.md
CHANGED
@@ -5,6 +5,7 @@ like:
|
|
5
5
|
|
6
6
|
rule "MD000", "Rule description" do
|
7
7
|
tags :foo, :bar
|
8
|
+
aliases 'rule-name'
|
8
9
|
params :style => :foo
|
9
10
|
check do |doc|
|
10
11
|
# check code goes here
|
@@ -26,6 +27,12 @@ checks whitespace usage in a document, you can add the `:whitespace` tag, and
|
|
26
27
|
users who don't care about whitespace can exclude that tag on the command line
|
27
28
|
or in style files.
|
28
29
|
|
30
|
+
You can also specify aliases for the rule, which can be used to refer to the
|
31
|
+
rule with a human-readable name rather than MD000. To do this, add then with
|
32
|
+
the 'aliases' directive. Whenever you refer to a rule, such as for
|
33
|
+
including/excluding in the configuration or in style files, you can use an
|
34
|
+
alias for the rule instead of its ID.
|
35
|
+
|
29
36
|
After that, any parameters the rule takes are specified. If your rule checks
|
30
37
|
for a specific number of things, or if you can envision multiple variants of
|
31
38
|
the same rule, then you should add parameters to allow your rule to be
|
data/lib/mdl.rb
CHANGED
@@ -9,16 +9,27 @@ require 'mdl/version'
|
|
9
9
|
require 'kramdown'
|
10
10
|
|
11
11
|
module MarkdownLint
|
12
|
-
def self.run
|
12
|
+
def self.run(argv=ARGV)
|
13
13
|
cli = MarkdownLint::CLI.new
|
14
|
-
cli.run
|
15
|
-
|
14
|
+
cli.run(argv)
|
15
|
+
ruleset = RuleSet.new
|
16
|
+
unless Config[:skip_default_ruleset]
|
17
|
+
ruleset.load_default
|
18
|
+
end
|
19
|
+
unless Config[:rulesets].nil?
|
20
|
+
Config[:rulesets].each do |r|
|
21
|
+
ruleset.load(r)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
rules = ruleset.rules
|
16
25
|
Style.load(Config[:style], rules)
|
17
26
|
# Rule option filter
|
18
27
|
if Config[:rules]
|
19
|
-
rules.select! {|r| Config[:rules][:include].include?(r)
|
28
|
+
rules.select! {|r, v| Config[:rules][:include].include?(r) or
|
29
|
+
!(Config[:rules][:include] & v.aliases).empty? } \
|
20
30
|
unless Config[:rules][:include].empty?
|
21
|
-
rules.select! {|r| not Config[:rules][:exclude].include?(r)
|
31
|
+
rules.select! {|r, v| not Config[:rules][:exclude].include?(r) and
|
32
|
+
(Config[:rules][:exclude] & v.aliases).empty? } \
|
22
33
|
unless Config[:rules][:exclude].empty?
|
23
34
|
end
|
24
35
|
# Tag option filter
|
@@ -33,7 +44,9 @@ module MarkdownLint
|
|
33
44
|
puts "Enabled rules:"
|
34
45
|
rules.each do |id, rule|
|
35
46
|
if Config[:verbose]
|
36
|
-
puts "#{id} (#{rule.
|
47
|
+
puts "#{id} (#{rule.aliases.join(', ')}) [#{rule.tags.join(', ')}] - #{rule.description}"
|
48
|
+
elsif Config[:show_aliases]
|
49
|
+
puts "#{rule.aliases.first || id} - #{rule.description}"
|
37
50
|
else
|
38
51
|
puts "#{id} - #{rule.description}"
|
39
52
|
end
|
@@ -46,10 +59,10 @@ module MarkdownLint
|
|
46
59
|
if Dir.exist?(filename)
|
47
60
|
if Config[:git_recurse]
|
48
61
|
Dir.chdir(filename) do
|
49
|
-
cli.cli_arguments[i] = %x(git ls-files '*.md').split("\n")
|
62
|
+
cli.cli_arguments[i] = %x(git ls-files '*.md' '*.markdown').split("\n")
|
50
63
|
end
|
51
64
|
else
|
52
|
-
cli.cli_arguments[i] = Dir["#{filename}/**/*.md"]
|
65
|
+
cli.cli_arguments[i] = Dir["#{filename}/**/*.{md,markdown}"]
|
53
66
|
end
|
54
67
|
end
|
55
68
|
end
|
@@ -72,7 +85,11 @@ module MarkdownLint
|
|
72
85
|
next if error_lines.nil? or error_lines.empty?
|
73
86
|
status = 1
|
74
87
|
error_lines.each do |line|
|
75
|
-
|
88
|
+
if Config[:show_aliases]
|
89
|
+
puts "#{filename}:#{line}: #{rule.aliases.first || id} #{rule.description}"
|
90
|
+
else
|
91
|
+
puts "#{filename}:#{line}: #{id} #{rule.description}"
|
92
|
+
end
|
76
93
|
end
|
77
94
|
end
|
78
95
|
end
|
data/lib/mdl/cli.rb
CHANGED
@@ -4,13 +4,21 @@ module MarkdownLint
|
|
4
4
|
class CLI
|
5
5
|
include Mixlib::CLI
|
6
6
|
|
7
|
+
CONFIG_FILE = '.mdlrc'
|
8
|
+
|
7
9
|
banner "Usage: #{File.basename($0)} [options] [FILE.md|DIR ...]"
|
8
10
|
|
11
|
+
option :show_aliases,
|
12
|
+
:short => '-a',
|
13
|
+
:long => '--[no-]show-aliases',
|
14
|
+
:description => 'Show rule alias instead of rule ID when viewing rules',
|
15
|
+
:boolean => true
|
16
|
+
|
9
17
|
option :config_file,
|
10
18
|
:short => '-c',
|
11
19
|
:long => '--config FILE',
|
12
20
|
:description => 'The configuration file to use',
|
13
|
-
:default =>
|
21
|
+
:default => "#{CONFIG_FILE}"
|
14
22
|
|
15
23
|
option :verbose,
|
16
24
|
:short => '-v',
|
@@ -53,6 +61,18 @@ module MarkdownLint
|
|
53
61
|
:boolean => true,
|
54
62
|
:description => "Only process files known to git when given a directory"
|
55
63
|
|
64
|
+
option :rulesets,
|
65
|
+
:short => '-u',
|
66
|
+
:long => '--rulesets RULESET1,RULESET2',
|
67
|
+
:proc => Proc.new { |v| v.split(',') },
|
68
|
+
:description => "Specify additional ruleset files to load"
|
69
|
+
|
70
|
+
option :skip_default_ruleset,
|
71
|
+
:short => '-d',
|
72
|
+
:long => '--skip-default-ruleset',
|
73
|
+
:boolean => true,
|
74
|
+
:description => "Don't load the default markdownlint ruleset"
|
75
|
+
|
56
76
|
option :help,
|
57
77
|
:on => :tail,
|
58
78
|
:short => '-h',
|
@@ -73,9 +93,20 @@ module MarkdownLint
|
|
73
93
|
|
74
94
|
def run(argv=ARGV)
|
75
95
|
parse_options(argv)
|
96
|
+
|
76
97
|
# Load the config file if it's present
|
77
|
-
filename =
|
78
|
-
|
98
|
+
filename = CLI.probe_config_file(config[:config_file])
|
99
|
+
# Only fall back to ~/.mdlrc if we are using the default value for -c
|
100
|
+
if filename.nil? and config[:config_file] == CONFIG_FILE
|
101
|
+
filename = File.expand_path("~/#{CONFIG_FILE}")
|
102
|
+
end
|
103
|
+
|
104
|
+
if not filename.nil? and File.exist?(filename)
|
105
|
+
MarkdownLint::Config.from_file(filename)
|
106
|
+
if config[:verbose]
|
107
|
+
puts "Loaded config from #{filename}"
|
108
|
+
end
|
109
|
+
end
|
79
110
|
|
80
111
|
# Put values in the config file
|
81
112
|
MarkdownLint::Config.merge!(config)
|
@@ -99,13 +130,33 @@ module MarkdownLint
|
|
99
130
|
if parts.class == String
|
100
131
|
parts = parts.split(',')
|
101
132
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
133
|
+
if parts.class == Array
|
134
|
+
inc = parts.select{|p| not p.start_with?('~')}
|
135
|
+
exc = parts.select{|p| p.start_with?('~')}.map{|p| p[1..-1]}
|
136
|
+
if to_sym
|
137
|
+
inc.map!{|p| p.to_sym}
|
138
|
+
exc.map!{|p| p.to_sym}
|
139
|
+
end
|
140
|
+
{:include => inc, :exclude => exc}
|
141
|
+
else
|
142
|
+
# We already converted the string into a list of include/exclude
|
143
|
+
# pairs, so just return as is
|
144
|
+
parts
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.probe_config_file(path)
|
149
|
+
# Probe up only for plain filenames
|
150
|
+
if path != File.basename(path)
|
151
|
+
return File.expand_path(path)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Look for a file up from the working dir
|
155
|
+
Pathname.new(path).ascend do |p|
|
156
|
+
config_file = p.join(CONFIG_FILE)
|
157
|
+
return config_file if File.exist?(CONFIG_FILE)
|
107
158
|
end
|
108
|
-
|
159
|
+
nil
|
109
160
|
end
|
110
161
|
end
|
111
162
|
end
|
data/lib/mdl/doc.rb
CHANGED
@@ -217,6 +217,10 @@ module MarkdownLint
|
|
217
217
|
matches = []
|
218
218
|
find_type_elements_except(:text, exclude_nested).each do |e|
|
219
219
|
first_line = e.options[:location]
|
220
|
+
# We'll error out if kramdown doesn't have location information for
|
221
|
+
# the current element. It's better to just not match in these cases
|
222
|
+
# rather than crash.
|
223
|
+
next if first_line.nil?
|
220
224
|
lines = e.value.split("\n")
|
221
225
|
lines.each_with_index do |l, i|
|
222
226
|
matches << first_line + i if re.match(l)
|
@@ -242,7 +246,7 @@ module MarkdownLint
|
|
242
246
|
lines = element.children.map { |e|
|
243
247
|
if e.type == :text
|
244
248
|
e.value
|
245
|
-
elsif [:strong, :em, :p].include?(e.type)
|
249
|
+
elsif [:strong, :em, :p, :codespan].include?(e.type)
|
246
250
|
extract_text(e, prefix).join("\n")
|
247
251
|
elsif e.type == :smart_quote
|
248
252
|
quotes[e.value]
|
data/lib/mdl/rules.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
rule "MD001", "Header levels should only increment by one level at a time" do
|
2
2
|
tags :headers
|
3
|
+
aliases 'header-increment'
|
3
4
|
check do |doc|
|
4
5
|
headers = doc.find_type(:header)
|
5
6
|
old_level = nil
|
@@ -16,6 +17,7 @@ end
|
|
16
17
|
|
17
18
|
rule "MD002", "First header should be a h1 header" do
|
18
19
|
tags :headers
|
20
|
+
aliases 'first-header-h1'
|
19
21
|
check do |doc|
|
20
22
|
first_header = doc.find_type(:header).first
|
21
23
|
[first_header[:location]] if first_header and first_header[:level] != 1
|
@@ -26,6 +28,7 @@ rule "MD003", "Header style" do
|
|
26
28
|
# Header styles are things like ### and adding underscores
|
27
29
|
# See http://daringfireball.net/projects/markdown/syntax#header
|
28
30
|
tags :headers
|
31
|
+
aliases 'header-style'
|
29
32
|
# :style can be one of :consistent, :atx, :atx_closed, :setext
|
30
33
|
params :style => :consistent
|
31
34
|
check do |doc|
|
@@ -38,14 +41,22 @@ rule "MD003", "Header style" do
|
|
38
41
|
else
|
39
42
|
doc_style = @params[:style]
|
40
43
|
end
|
41
|
-
|
42
|
-
|
44
|
+
if doc_style == :setext_with_atx
|
45
|
+
headers.map { |h| doc.element_linenumber(h) \
|
46
|
+
unless doc.header_style(h) == :setext or \
|
47
|
+
(doc.header_style(h) == :atx and \
|
48
|
+
h.options[:level] > 2) }.compact
|
49
|
+
else
|
50
|
+
headers.map { |h| doc.element_linenumber(h) \
|
51
|
+
if doc.header_style(h) != doc_style }.compact
|
52
|
+
end
|
43
53
|
end
|
44
54
|
end
|
45
55
|
end
|
46
56
|
|
47
57
|
rule "MD004", "Unordered list style" do
|
48
58
|
tags :bullet, :ul
|
59
|
+
aliases 'ul-style'
|
49
60
|
# :style can be one of :consistent, :asterisk, :plus, :dash
|
50
61
|
params :style => :consistent
|
51
62
|
check do |doc|
|
@@ -67,6 +78,7 @@ end
|
|
67
78
|
|
68
79
|
rule "MD005", "Inconsistent indentation for list items at the same level" do
|
69
80
|
tags :bullet, :ul, :indentation
|
81
|
+
aliases 'list-indent'
|
70
82
|
check do |doc|
|
71
83
|
bullets = doc.find_type(:li)
|
72
84
|
errors = []
|
@@ -88,6 +100,7 @@ rule "MD006", "Consider starting bulleted lists at the beginning of the line" do
|
|
88
100
|
# Starting at the beginning of the line means that indendation for each
|
89
101
|
# bullet level can be identical.
|
90
102
|
tags :bullet, :ul, :indentation
|
103
|
+
aliases 'ul-start-left'
|
91
104
|
check do |doc|
|
92
105
|
doc.find_type(:ul, false).select{
|
93
106
|
|e| doc.indent_for(doc.element_line(e)) != 0 }.map{ |e| e[:location] }
|
@@ -96,6 +109,7 @@ end
|
|
96
109
|
|
97
110
|
rule "MD007", "Unordered list indentation" do
|
98
111
|
tags :bullet, :ul, :indentation
|
112
|
+
aliases 'ul-indent'
|
99
113
|
params :indent => 2
|
100
114
|
check do |doc|
|
101
115
|
indents = []
|
@@ -115,6 +129,7 @@ end
|
|
115
129
|
|
116
130
|
rule "MD009", "Trailing spaces" do
|
117
131
|
tags :whitespace
|
132
|
+
aliases 'no-trailing-spaces'
|
118
133
|
params :br_spaces => 0
|
119
134
|
check do |doc|
|
120
135
|
errors = doc.matching_lines(/\s$/)
|
@@ -127,6 +142,7 @@ end
|
|
127
142
|
|
128
143
|
rule "MD010", "Hard tabs" do
|
129
144
|
tags :whitespace, :hard_tab
|
145
|
+
aliases 'no-hard-tabs'
|
130
146
|
check do |doc|
|
131
147
|
doc.matching_lines(/\t/)
|
132
148
|
end
|
@@ -134,6 +150,7 @@ end
|
|
134
150
|
|
135
151
|
rule "MD011", "Reversed link syntax" do
|
136
152
|
tags :links
|
153
|
+
aliases 'no-reversed-links'
|
137
154
|
check do |doc|
|
138
155
|
doc.matching_text_element_lines(/\([^)]+\)\[[^\]]+\]/)
|
139
156
|
end
|
@@ -141,6 +158,7 @@ end
|
|
141
158
|
|
142
159
|
rule "MD012", "Multiple consecutive blank lines" do
|
143
160
|
tags :whitespace, :blank_lines
|
161
|
+
aliases 'no-multiple-blanks'
|
144
162
|
check do |doc|
|
145
163
|
# Every line in the document that is part of a code block. Blank lines
|
146
164
|
# inside of a code block are acceptable.
|
@@ -156,14 +174,31 @@ end
|
|
156
174
|
|
157
175
|
rule "MD013", "Line length" do
|
158
176
|
tags :line_length
|
159
|
-
|
177
|
+
aliases 'line-length'
|
178
|
+
params :line_length => 80, :code_blocks => true, :tables => true
|
160
179
|
check do |doc|
|
161
|
-
|
180
|
+
# Every line in the document that is part of a code block.
|
181
|
+
codeblock_lines = doc.find_type_elements(:codeblock).map{
|
182
|
+
|e| (doc.element_linenumber(e)..
|
183
|
+
doc.element_linenumber(e) + e.value.count('\n') - 1).to_a }.flatten
|
184
|
+
# Every line in the document that is part of a table.
|
185
|
+
locations = doc.elements
|
186
|
+
.map { |e| [e.options[:location], e] }
|
187
|
+
.reject { |l, _| l.nil? }
|
188
|
+
table_lines = locations.map.with_index {
|
189
|
+
|(l, e), i| (i + 1 < locations.size ?
|
190
|
+
(l..locations[i+1].first - 1) :
|
191
|
+
(l..doc.lines.count)).to_a if e.type == :table }.flatten
|
192
|
+
overlines = doc.matching_lines(/^.{#{@params[:line_length]}}.*\s/)
|
193
|
+
overlines -= codeblock_lines unless params[:code_blocks]
|
194
|
+
overlines -= table_lines unless params[:tables]
|
195
|
+
overlines
|
162
196
|
end
|
163
197
|
end
|
164
198
|
|
165
199
|
rule "MD014", "Dollar signs used before commands without showing output" do
|
166
200
|
tags :code
|
201
|
+
aliases 'commands-show-output'
|
167
202
|
check do |doc|
|
168
203
|
doc.find_type_elements(:codeblock).select{
|
169
204
|
|e| not e.value.empty? and
|
@@ -174,6 +209,7 @@ end
|
|
174
209
|
|
175
210
|
rule "MD018", "No space after hash on atx style header" do
|
176
211
|
tags :headers, :atx, :spaces
|
212
|
+
aliases 'no-missing-space-atx'
|
177
213
|
check do |doc|
|
178
214
|
doc.find_type_elements(:header).select do |h|
|
179
215
|
doc.header_style(h) == :atx and doc.element_line(h).match(/^#+[^#\s]/)
|
@@ -183,6 +219,7 @@ end
|
|
183
219
|
|
184
220
|
rule "MD019", "Multiple spaces after hash on atx style header" do
|
185
221
|
tags :headers, :atx, :spaces
|
222
|
+
aliases 'no-multiple-space-atx'
|
186
223
|
check do |doc|
|
187
224
|
doc.find_type_elements(:header).select do |h|
|
188
225
|
doc.header_style(h) == :atx and doc.element_line(h).match(/^#+\s\s/)
|
@@ -192,6 +229,7 @@ end
|
|
192
229
|
|
193
230
|
rule "MD020", "No space inside hashes on closed atx style header" do
|
194
231
|
tags :headers, :atx_closed, :spaces
|
232
|
+
aliases 'no-missing-space-closed-atx'
|
195
233
|
check do |doc|
|
196
234
|
doc.find_type_elements(:header).select do |h|
|
197
235
|
doc.header_style(h) == :atx_closed \
|
@@ -203,6 +241,7 @@ end
|
|
203
241
|
|
204
242
|
rule "MD021", "Multiple spaces inside hashes on closed atx style header" do
|
205
243
|
tags :headers, :atx_closed, :spaces
|
244
|
+
aliases 'no-multiple-space-closed-atx'
|
206
245
|
check do |doc|
|
207
246
|
doc.find_type_elements(:header).select do |h|
|
208
247
|
doc.header_style(h) == :atx_closed \
|
@@ -214,6 +253,7 @@ end
|
|
214
253
|
|
215
254
|
rule "MD022", "Headers should be surrounded by blank lines" do
|
216
255
|
tags :headers, :blank_lines
|
256
|
+
aliases 'blanks-around-headers'
|
217
257
|
check do |doc|
|
218
258
|
errors = []
|
219
259
|
doc.find_type_elements(:header).each do |h|
|
@@ -257,6 +297,7 @@ end
|
|
257
297
|
|
258
298
|
rule "MD023", "Headers must start at the beginning of the line" do
|
259
299
|
tags :headers, :spaces
|
300
|
+
aliases 'header-start-left'
|
260
301
|
check do |doc|
|
261
302
|
errors = []
|
262
303
|
# The only type of header with spaces actually parsed as such is setext
|
@@ -289,6 +330,7 @@ end
|
|
289
330
|
|
290
331
|
rule "MD024", "Multiple headers with the same content" do
|
291
332
|
tags :headers
|
333
|
+
aliases 'no-duplicate-header'
|
292
334
|
check do |doc|
|
293
335
|
header_content = Set.new
|
294
336
|
doc.find_type(:header).select do |h|
|
@@ -299,6 +341,7 @@ end
|
|
299
341
|
|
300
342
|
rule "MD025", "Multiple top level headers in the same document" do
|
301
343
|
tags :headers
|
344
|
+
aliases 'single-h1'
|
302
345
|
check do |doc|
|
303
346
|
headers = doc.find_type(:header).select { |h| h[:level] == 1 }
|
304
347
|
if not headers.empty? and doc.element_linenumber(headers[0]) == 1
|
@@ -309,6 +352,7 @@ end
|
|
309
352
|
|
310
353
|
rule "MD026", "Trailing punctuation in header" do
|
311
354
|
tags :headers
|
355
|
+
aliases 'no-trailing-punctuation'
|
312
356
|
params :punctuation => '.,;:!?'
|
313
357
|
check do |doc|
|
314
358
|
doc.find_type(:header).select {
|
@@ -319,6 +363,7 @@ end
|
|
319
363
|
|
320
364
|
rule "MD027", "Multiple spaces after blockquote symbol" do
|
321
365
|
tags :blockquote, :whitespace, :indentation
|
366
|
+
aliases 'no-multiple-space-blockquote'
|
322
367
|
check do |doc|
|
323
368
|
errors = []
|
324
369
|
doc.find_type_elements(:blockquote).each do |e|
|
@@ -335,6 +380,7 @@ end
|
|
335
380
|
|
336
381
|
rule "MD028", "Blank line inside blockquote" do
|
337
382
|
tags :blockquote, :whitespace
|
383
|
+
aliases 'no-blanks-blockquote'
|
338
384
|
check do |doc|
|
339
385
|
def check_blockquote(errors, elements)
|
340
386
|
prev = [nil, nil, nil]
|
@@ -358,6 +404,7 @@ end
|
|
358
404
|
|
359
405
|
rule "MD029", "Ordered list item prefix" do
|
360
406
|
tags :ol
|
407
|
+
aliases 'ol-prefix'
|
361
408
|
# Style can be :one or :ordered
|
362
409
|
params :style => :one
|
363
410
|
check do |doc|
|
@@ -379,6 +426,7 @@ end
|
|
379
426
|
|
380
427
|
rule "MD030", "Spaces after list markers" do
|
381
428
|
tags :ol, :ul, :whitespace
|
429
|
+
aliases 'list-marker-space'
|
382
430
|
params :ul_single => 1, :ol_single => 1, :ul_multi => 1, :ol_multi => 1
|
383
431
|
check do |doc|
|
384
432
|
errors = []
|
@@ -400,14 +448,18 @@ end
|
|
400
448
|
|
401
449
|
rule "MD031", "Fenced code blocks should be surrounded by blank lines" do
|
402
450
|
tags :code, :blank_lines
|
451
|
+
aliases 'blanks-around-fences'
|
403
452
|
check do |doc|
|
404
453
|
errors = []
|
405
454
|
# Some parsers (including kramdown) have trouble detecting fenced code
|
406
455
|
# blocks without surrounding whitespace, so examine the lines directly.
|
407
456
|
in_code = false
|
457
|
+
fence = nil
|
408
458
|
lines = [ "" ] + doc.lines + [ "" ]
|
409
459
|
lines.each_with_index do |line, linenum|
|
410
|
-
|
460
|
+
line.strip.match(/^(`{3,}|~{3,})/)
|
461
|
+
if $1 and (not in_code or $1.slice(0, fence.length) == fence)
|
462
|
+
fence = in_code ? nil : $1
|
411
463
|
in_code = !in_code
|
412
464
|
if (in_code and not lines[linenum - 1].empty?) or
|
413
465
|
(not in_code and not lines[linenum + 1].empty?)
|
@@ -421,12 +473,14 @@ end
|
|
421
473
|
|
422
474
|
rule "MD032", "Lists should be surrounded by blank lines" do
|
423
475
|
tags :bullet, :ul, :ol, :blank_lines
|
476
|
+
aliases 'blanks-around-lists'
|
424
477
|
check do |doc|
|
425
478
|
errors = []
|
426
479
|
# Some parsers (including kramdown) have trouble detecting lists
|
427
480
|
# without surrounding whitespace, so examine the lines directly.
|
428
481
|
in_list = false
|
429
482
|
in_code = false
|
483
|
+
fence = nil
|
430
484
|
prev_line = ""
|
431
485
|
doc.lines.each_with_index do |line, linenum|
|
432
486
|
if not in_code
|
@@ -438,7 +492,9 @@ rule "MD032", "Lists should be surrounded by blank lines" do
|
|
438
492
|
end
|
439
493
|
in_list = list_marker
|
440
494
|
end
|
441
|
-
|
495
|
+
line.strip.match(/^(`{3,}|~{3,})/)
|
496
|
+
if $1 and (not in_code or $1.slice(0, fence.length) == fence)
|
497
|
+
fence = in_code ? nil : $1
|
442
498
|
in_code = !in_code
|
443
499
|
in_list = false
|
444
500
|
end
|
@@ -450,6 +506,7 @@ end
|
|
450
506
|
|
451
507
|
rule "MD033", "Inline HTML" do
|
452
508
|
tags :html
|
509
|
+
aliases 'no-inline-html'
|
453
510
|
check do |doc|
|
454
511
|
doc.element_linenumbers(doc.find_type(:html_element))
|
455
512
|
end
|
@@ -457,6 +514,7 @@ end
|
|
457
514
|
|
458
515
|
rule "MD034", "Bare URL used" do
|
459
516
|
tags :links, :url
|
517
|
+
aliases 'no-bare-urls'
|
460
518
|
check do |doc|
|
461
519
|
doc.matching_text_element_lines(/https?:\/\//)
|
462
520
|
end
|
@@ -464,6 +522,7 @@ end
|
|
464
522
|
|
465
523
|
rule "MD035", "Horizontal rule style" do
|
466
524
|
tags :hr
|
525
|
+
aliases 'hr-style'
|
467
526
|
params :style => :consistent
|
468
527
|
check do |doc|
|
469
528
|
hrs = doc.find_type(:hr)
|
@@ -482,6 +541,7 @@ end
|
|
482
541
|
|
483
542
|
rule "MD036", "Emphasis used instead of a header" do
|
484
543
|
tags :headers, :emphasis
|
544
|
+
aliases 'no-emphasis-as-header'
|
485
545
|
check do |doc|
|
486
546
|
# We are looking for a paragraph consisting entirely of emphasized
|
487
547
|
# (italic/bold) text.
|
@@ -492,6 +552,7 @@ end
|
|
492
552
|
|
493
553
|
rule "MD037", "Spaces inside emphasis markers" do
|
494
554
|
tags :whitespace, :emphasis
|
555
|
+
aliases 'no-space-in-emphasis'
|
495
556
|
check do |doc|
|
496
557
|
# Kramdown doesn't parse emphasis with spaces, which means we can just
|
497
558
|
# look for emphasis patterns inside regular text with spaces just inside
|
@@ -503,6 +564,7 @@ end
|
|
503
564
|
|
504
565
|
rule "MD038", "Spaces inside code span elements" do
|
505
566
|
tags :whitespace, :code
|
567
|
+
aliases 'no-space-in-code'
|
506
568
|
check do |doc|
|
507
569
|
# We only want to check single line codespan elements and not fenced code
|
508
570
|
# block that happen to be parsed as code spans.
|
@@ -513,6 +575,7 @@ end
|
|
513
575
|
|
514
576
|
rule "MD039", "Spaces inside link text" do
|
515
577
|
tags :whitespace, :links
|
578
|
+
aliases 'no-space-in-links'
|
516
579
|
check do |doc|
|
517
580
|
doc.element_linenumbers(doc.find_type_elements(:a).select{|e|
|
518
581
|
e.children[0].type == :text and
|
@@ -523,6 +586,7 @@ end
|
|
523
586
|
|
524
587
|
rule "MD040", "Fenced code blocks should have a language specified" do
|
525
588
|
tags :code, :language
|
589
|
+
aliases 'fenced-code-language'
|
526
590
|
check do |doc|
|
527
591
|
# Kramdown parses code blocks with language settings as code blocks with
|
528
592
|
# the class attribute set to language-languagename.
|
@@ -531,3 +595,13 @@ rule "MD040", "Fenced code blocks should have a language specified" do
|
|
531
595
|
not doc.element_line(i).start_with?(" ")})
|
532
596
|
end
|
533
597
|
end
|
598
|
+
|
599
|
+
rule "MD041", "First line in file should be a top level header" do
|
600
|
+
tags :headers
|
601
|
+
aliases 'first-line-h1'
|
602
|
+
check do |doc|
|
603
|
+
first_header = doc.find_type(:header).first
|
604
|
+
[1] if first_header.nil? or first_header[:location] != 1 \
|
605
|
+
or first_header[:level] != 1
|
606
|
+
end
|
607
|
+
end
|