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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/CHANGELOG.md +45 -0
  4. data/README.md +1 -0
  5. data/docs/RULES.md +116 -6
  6. data/docs/configuration.md +16 -1
  7. data/docs/creating_rules.md +7 -0
  8. data/lib/mdl.rb +26 -9
  9. data/lib/mdl/cli.rb +60 -9
  10. data/lib/mdl/doc.rb +5 -1
  11. data/lib/mdl/rules.rb +80 -6
  12. data/lib/mdl/ruleset.rb +16 -7
  13. data/lib/mdl/style.rb +6 -0
  14. data/lib/mdl/styles/default.rb +2 -1
  15. data/lib/mdl/styles/relaxed.rb +1 -0
  16. data/lib/mdl/version.rb +1 -1
  17. data/mdl.gemspec +1 -1
  18. data/test/fixtures/default_mdlrc +1 -0
  19. data/test/fixtures/dir_with_md_and_markdown/bar.markdown +1 -0
  20. data/test/fixtures/dir_with_md_and_markdown/foo.md +1 -0
  21. data/test/fixtures/mdlrc_disable_rules +1 -0
  22. data/test/fixtures/mdlrc_disable_tags +1 -0
  23. data/test/fixtures/mdlrc_enable_rules +1 -0
  24. data/test/fixtures/mdlrc_enable_tags +1 -0
  25. data/test/fixtures/my_ruleset.rb +6 -0
  26. data/test/rule_tests/blockquote_spaces.md +6 -4
  27. data/test/rule_tests/bulleted_list_2_space_indent_style.rb +1 -0
  28. data/test/rule_tests/code_block_dollar_fence.md +29 -0
  29. data/test/rule_tests/default_test_style.rb +4 -0
  30. data/test/rule_tests/fenced_code_with_nesting.md +73 -0
  31. data/test/rule_tests/fenced_code_without_blank_lines_style.rb +1 -0
  32. data/test/rule_tests/first_line_top_level_header_atx.md +3 -0
  33. data/test/rule_tests/first_line_top_level_header_atx_style.rb +2 -0
  34. data/test/rule_tests/first_line_top_level_header_setext.md +4 -0
  35. data/test/rule_tests/first_line_top_level_header_setext_style.rb +2 -0
  36. data/test/rule_tests/headers_good_setext_with_atx.md +7 -0
  37. data/test/rule_tests/headers_good_setext_with_atx_style.rb +2 -0
  38. data/test/rule_tests/hr_style_dashes_style.rb +1 -0
  39. data/test/rule_tests/hr_style_long_style.rb +1 -0
  40. data/test/rule_tests/hr_style_stars_style.rb +1 -0
  41. data/test/rule_tests/incorrect_bullet_style_asterisk_style.rb +1 -0
  42. data/test/rule_tests/incorrect_bullet_style_dash_style.rb +1 -0
  43. data/test/rule_tests/incorrect_bullet_style_plus_style.rb +1 -0
  44. data/test/rule_tests/long_lines_100_style.rb +1 -0
  45. data/test/rule_tests/long_lines_code.md +38 -0
  46. data/test/rule_tests/long_lines_code_style.rb +3 -0
  47. data/test/rule_tests/no_first_line_header.md +1 -0
  48. data/test/rule_tests/no_first_line_header_style.rb +1 -0
  49. data/test/rule_tests/no_first_line_top_level_header.md +1 -0
  50. data/test/rule_tests/no_first_line_top_level_header_style.rb +1 -0
  51. data/test/rule_tests/ordered_list_item_prefix_ordered_style.rb +1 -0
  52. data/test/rule_tests/spaces_after_list_marker_style.rb +1 -0
  53. data/test/rule_tests/trailing_spaces_br_style.rb +1 -0
  54. data/test/test_cli.rb +205 -0
  55. data/test/test_ruledocs.rb +8 -1
  56. data/test/test_rules.rb +4 -2
  57. metadata +55 -7
@@ -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` in your home directory.
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.
@@ -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
- rules = RuleSet.load_default
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.tags.join(', ')}) - #{rule.description}"
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
- puts "#{filename}:#{line}: #{id} #{rule.description}"
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
@@ -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 => '~/.mdlrc'
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 = File.expand_path(config[:config_file])
78
- MarkdownLint::Config.from_file(filename) if File.exists?(filename)
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
- inc = parts.select{|p| not p.start_with?('~')}
103
- exc = parts.select{|p| p.start_with?('~')}.map{|p| p[1..-1]}
104
- if to_sym
105
- inc.map!{|p| p.to_sym}
106
- exc.map!{|p| p.to_sym}
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
- {:include => inc, :exclude => exc}
159
+ nil
109
160
  end
110
161
  end
111
162
  end
@@ -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]
@@ -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
- headers.map { |h| doc.element_linenumber(h) \
42
- if doc.header_style(h) != doc_style }.compact
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
- params :line_length => 80
177
+ aliases 'line-length'
178
+ params :line_length => 80, :code_blocks => true, :tables => true
160
179
  check do |doc|
161
- doc.matching_lines(/^.{#{@params[:line_length]}}.*\s/)
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
- if line.strip.match(/^(```|~~~)/)
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
- if line.strip.match(/^(```|~~~)/)
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