mdl 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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