mdl 0.2.1 → 0.3.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/.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
|