A tool to check markdown files and flag style issues
Note: The book edition is still an early release and a work-in-progess.
This is the official documentation for the markdown lint style rules checker reformatted in a single-page book edition.
See the source repo for how the book gets auto-built with a static site builder and hosted on GitHub Pages.
Questions? Comments? Send them along to the wwwmake mailing list/forum Thanks.
Onwards.
Thanks to Mark Harrison and contributors for making it all possible.
A tool to check markdown files and flag style issues.
Markdownlint is written in ruby and is distributed as a rubygem. As long as you have a relatively up to date ruby on your system, markdownlint will be simple to install and use.
To install from rubygems, run:
gem install mdl
To install the latest development version from github:
git clone https://github.com/mivok/markdownlint
cd markdownlint
rake install
Try:
$ mdl -h
Resulting in:
Usage: mdl [options] [FILE.md|DIR ...]
-c, --config FILE The configuration file to use
-g, --git-recurse Only process files known to git when given a directory
-l, --list-rules Don't process any files, just list enabled rules
-r, --rules RULE1,RULE2 Only process these rules
-w, --[no-]warnings Show kramdown warnings
-s, --style STYLE Load the given style
-t, --tags TAG1,TAG2 Only process rules with these tags
-v, --[no-]verbose Increase verbosity
-h, --help Show this message
-V, --version Show version
To have markdownlint check your markdown files, simply run mdl
with the
filenames as a parameter:
mdl README.md
Markdownlint can also take a directory, and it will scan all markdown files within the directory (and nested directories):
mdl docs/
If you don’t specify a filename, markdownlint will use stdin:
cat foo.md | mdl
Markdownlint will output a list of issues it finds, and the line number where the issue is. See RULES.md for information on each issue, as well as how to correct it:
README.md:1: MD013 Line length
README.md:70: MD029 Ordered list item prefix
README.md:71: MD029 Ordered list item prefix
README.md:72: MD029 Ordered list item prefix
README.md:73: MD029 Ordered list item prefix
Markdownlint has many more options you can pass on the command line, run
mdl --help
to see what they are, or see the documentation on
configuring markdownlint.
Not everyone writes markdown in the same way, and there are multiple flavors and styles, each of which are valid. While markdownlint’s default settings will result in markdown files that reflect the author’s preferred markdown authoring preferences, your project may have different guidelines.
It’s not markdownlint’s intention to dictate any one specific style, and in order to support these differing styles and/or preferences, markdownlint supports what are called ‘style files’. A style file is a file describing which rules markdownlint should enable, and also what settings to apply to individual rules. For example, rule MD013 checks for long lines, and by default will report an issue for any line longer than 80 characters. If your project has a different maximum line length limit, or if you don’t want to enforce a line limit at all, then this can be configured in a style file.
For more information on creating style files, see the creating styles document.
This rule is triggered when you skip header levels in a markdown document, for example:
# Header 1
### Header 3
We skipped out a 2nd level header in this document
When using multiple header levels, nested headers should increase by only one level at a time:
# Header 1
## Header 2
### Header 3
#### Header 4
## Another Header 2
### Another Header 3
This rule is triggered when the first header in the document isn’t a h1 header:
## This isn't a H1 header
### Another header
The first header in the document should be a h1 header:
# Start with a H1 header
## Then use a H2 for subsections
Parameters: style (“consistent”, “atx”, “atx_closed”, “setext”, “setext_with_atx”; default “consistent”)
This rule is triggered when different header styles (atx, setext, and ‘closed’ atx) are used in the same document:
# ATX style H1
## Closed ATX style H2 ##
Setext style H1
===============
Be consistent with the style of header used in a document:
# ATX style H1
## ATX style H2
The setext_with_atx doc style allows atx-style headers of level 3 or more in documents with setext style headers:
Setext style H1
===============
Setext style H2
---------------
### ATX style H3
Note: the configured header style can be a specific style to use (atx, atx_closed, setext, setext_with_atx), or simply require that the usage be consistent within the document.
Parameters: style (“consistent”, “asterisk”, “plus”, “dash”; default “consistent”)
This rule is triggered when the symbols used in the document for unordered list items do not match the configured unordered list style:
* Item 1
+ Item 2
- Item 3
To fix this issue, use the configured style for list items throughout the document:
* Item 1
* Item 2
* Item 3
Note: the configured list style can be a specific symbol to use (asterisk, plus, dash), or simply require that the usage be consistent within the document.
This rule is triggered when list items are parsed as being at the same level, but don’t have the same indentation:
* Item 1
* Nested Item 1
* Nested Item 2
* A misaligned item
Usually this rule will be triggered because of a typo. Correct the indentation for the list to fix it:
* Item 1
* Nested Item 1
* Nested Item 2
* Nested Item 3
This rule is triggered when top level lists don’t start at the beginning of a line:
Some text
* List item
* List item
To fix, ensure that top level list items are not indented:
Some test
* List item
* List item
Rationale: Starting lists at the beginning of the line means that nested list items can all be indented by the same amount when an editor’s indent function or the tab key is used to indent. Starting a list 1 space in means that the indent of the first nested list is less than the indent of the second level (3 characters if you use 4 space tabs, or 1 character if you use 2 space tabs).
Parameters: indent (number; default 2)
This rule is triggered when list items are not indented by the configured number of spaces (default: 2).
Example:
* List item
* Nested list item indented by 3 spaces
Corrected Example:
* List item
* Nested list item indented by 2 spaces
Rationale (2 space indent): indenting by 2 spaces allows the content of a nested list to be in line with the start of the content of the parent list when a single space is used after the list marker.
Rationale (4 space indent): Same indent as code blocks, simpler for editors to implement. See http://www.cirosantilli.com/markdown-styleguide/#indented-lists for more information.
In addition, this is a compatibility issue with multi-markdown parsers, which require a 4 space indents. See http://support.markedapp.com/discussions/problems/21-sub-lists-not-indenting for a description of the problem.
Parameters: br_spaces (number; default: 0)
This rule is triggered on any lines that end with whitespace. To fix this, find the line that is triggered and remove any trailing spaces from the end.
The br_spaces parameter allows an exception to this rule for a specific amount of trailing spaces used to insert an explicit line break/br element. For example, set br_spaces to 2 to allow exactly 2 spaces at the end of a line.
Note: you have to set br_spaces to 2 or higher for this exception to take effect - you can’t insert a br element with just a single trailing space, so if you set br_spaces to 1, the exception will be disabled, just as if it was set to the default of 0.
This rule is triggered by any lines that contain hard tab characters instead of using spaces for indentation. To fix this, replace any hard tab characters with spaces instead.
Example:
Some text
* hard tab character used to indent the list item
Corrected example:
Some text
* Spaces used to indent the list item instead
This rule is triggered when text that appears to be a link is encountered, but
where the syntax appears to have been reversed (the []
and ()
are
reversed):
(Incorrect link syntax)[http://www.example.com/]
To fix this, swap the []
and ()
around:
[Correct link syntax](http://www.example.com/)
This rule is triggered when there are multiple consecutive blank lines in the document:
Some text here
Some more text here
To fix this, delete the offending lines:
Some text here
Some more text here
Note: this rule will not be triggered if there are multiple consecutive blank lines inside code blocks.
Parameters: line_length, code_blocks, tables (number; default 80, boolean; default true)
This rule is triggered when there are lines that are longer than the configured line length (default: 80 characters). To fix this, split the line up into multiple lines.
This rule has an exception where there is no whitespace beyond the configured line length. This allows you to still include items such as long URLs without being forced to break them in the middle.
You also have the option to exclude this rule for code blocks and tables. To
do this, set the code_blocks
and/or tables
parameters to false.
Code blocks are included in this rule by default since it is often a requirement for document readability, and tentatively compatible with code rules. Still, some languages do not lend themselves to short lines.
This rule is triggered when there are code blocks showing shell commands to be typed, and the shell commands are preceded by dollar signs ($):
$ ls
$ cat foo
$ less bar
The dollar signs are unnecessary in the above situation, and should not be included:
ls
cat foo
less bar
However, an exception is made when there is a need to distinguish between typed commands and command output, as in the following example:
$ ls
foo bar
$ cat foo
Hello world
$ cat bar
baz
Rationale: it is easier to copy and paste and less noisy if the dollar signs are omitted when they are not needed. See http://www.cirosantilli.com/markdown-styleguide/#dollar-signs-in-shell-code for more information.
This rule is triggered when spaces are missing after the hash characters in an atx style header:
#Header 1
##Header 2
To fix this, separate the header text from the hash character by a single space:
# Header 1
## Header 2
This rule is triggered when more than one space is used to separate the header text from the hash characters in an atx style header:
# Header 1
## Header 2
To fix this, separate the header text from the hash character by a single space:
# Header 1
## Header 2
This rule is triggered when spaces are missing inside the hash characters in a closed atx style header:
#Header 1#
##Header 2##
To fix this, separate the header text from the hash character by a single space:
# Header 1 #
## Header 2 ##
Note: this rule will fire if either side of the header is missing spaces.
This rule is triggered when more than one space is used to separate the header text from the hash characters in a closed atx style header:
# Header 1 #
## Header 2 ##
To fix this, separate the header text from the hash character by a single space:
# Header 1 #
## Header 2 ##
Note: this rule will fire if either side of the header contains multiple spaces.
This rule is triggered when headers (any style) are either not preceded or not followed by a blank line:
# Header 1
Some text
Some more text
## Header 2
To fix this, ensure that all headers have a blank line both before and after (except where the header is at the beginning or end of the document):
# Header 1
Some text
Some more text
## Header 2
Rationale: Aside from aesthetic reasons, some parsers, including kramdown, will not parse headers that don’t have a blank line before, and will parse them as regular text.
This rule is triggered when a header is indented by one or more spaces:
Some text
# Indented header
To fix this, ensure that all headers start at the beginning of the line:
Some text
# Header
Rationale: Headers that don’t start at the beginning of the line will not be parsed as headers, and will instead appear as regular text.
This rule is triggered if there are multiple headers in the document that have the same text:
# Some text
## Some text
To fix this, ensure that the content of each header is different:
# Some text
## Some more text
Rationale: Some markdown parses generate anchors for headers based on the header name, and having headers with the same content can cause problems with this.
This rule is triggered when a top level header is in use (the first line of the file is a h1 header), and more than one h1 header is in use in the document:
# Top level header
# Another top level header
To fix, structure your document so that there is a single h1 header that is the title for the document, and all later headers are h2 or lower level headers:
# Title
## Header
## Another header
Rationale: A top level header is a h1 on the first line of the file, and serves as the title for the document. If this convention is in use, then there can not be more than one title for the document, and the entire document should be contained within this header.
Parameters: punctuation (string; default “.,;:!?”)
This rule is triggered on any header that has a punctuation character as the last character in the line:
# This is a header.
To fix this, remove any trailing punctuation:
# This is a header
Note: The punctuation parameter can be used to specify what characters class
as punctuation at the end of the header. For example, you can set it to
'.,;:!'
to allow headers with question marks in them, such as might be used
in an FAQ.
This rule is triggered when blockquotes have more than one space after the
blockquote (>
) symbol:
> This is a block quote with bad indentation
> there should only be one.
To fix, remove any extraneous space:
> This is a blockquote with correct
> indentation.
This rule is triggered when two blockquote blocks are separated by nothing except for a blank line:
> This is a blockquote
> which is immediately followed by
> this blockquote. Unfortunately
> In some parsers, these are treated as the same blockquote.
To fix this, ensure that any blockquotes that are right next to each other have some text in between:
> This is a blockquote.
And Jimmy also said:
> This too is a blockquote.
Alternatively, if they are supposed to be the same quote, then add the blockquote symbol at the beginning of the blank line:
> This is a blockquote.
>
> This is the same blockquote.
Rationale: Some markdown parsers will treat two blockquotes separated by one or more blank lines as the same blockquote, while others will treat them as separate blockquotes.
Parameters: style (“one”, “ordered”; default “one”)
This rule is triggered on ordered lists that do not either start with ‘1.’ or do not have a prefix that increases in numerical order (depending on the configured style, which defaults to ‘one’).
Example valid list if the style is configured as ‘one’:
1. Do this.
1. Do that.
1. Done.
Example valid list if the style is configured as ‘ordered’:
1. Do this.
2. Do that.
3. Done.
Parameters: ul_single, ol_single, ul_multi, ol_multi (number, default 1)
This rule checks for the number of spaces between a list marker (e.g. ‘-
’,
‘*
’, ‘+
’ or ‘1.
’) and the text of the list item.
The number of spaces checked for depends on the document style in use, but the default is 1 space after any list marker:
* Foo
* Bar
* Baz
1. Foo
1. Bar
1. Baz
1. Foo
* Bar
1. Baz
A document style may change the number of spaces after unordered list items and ordered list items independently, as well as based on whether the content of every item in the list consists of a single paragraph, or multiple paragraphs (including sub-lists and code blocks).
For example, the style guide at http://www.cirosantilli.com/markdown-styleguide/#spaces-after-marker specifies that 1 space after the list marker should be used if every item in the list fits within a single paragraph, but to use 2 or 3 spaces (for ordered and unordered lists respectively) if there are multiple paragraphs of content inside the list:
* Foo
* Bar
* Baz
vs.
* Foo
Second paragraph
* Bar
or
1. Foo
Second paragraph
1. Bar
To fix this, ensure the correct number of spaces are used after list marker for your selected document style.
This rule is triggered when fenced code blocks are either not preceded or not followed by a blank line:
Some text
```
Code block
```
```
Another code block
```
Some more text
To fix this, ensure that all fenced code blocks have a blank line both before and after (except where the block is at the beginning or end of the document):
Some text
```
Code block
```
```
Another code block
```
Some more text
Rationale: Aside from aesthetic reasons, some parsers, including kramdown, will not parse fenced code blocks that don’t have blank lines before and after them.
This rule is triggered when lists (of any kind) are either not preceded or not followed by a blank line:
Some text
* Some
* List
1. Some
2. List
Some text
To fix this, ensure that all lists have a blank line both before and after (except where the block is at the beginning or end of the document):
Some text
* Some
* List
1. Some
2. List
Some text
Rationale: Aside from aesthetic reasons, some parsers, including kramdown, will not parse lists that don’t have blank lines before and after them.
Note: List items without hanging indents are a violation of this rule; list items with hanging indents are okay:
* This is
not okay
* This is
okay
This rule is triggered whenever raw HTML is used in a markdown document:
<h1>Inline HTML header</h1>
To fix this, use ‘pure’ markdown instead of including raw HTML:
# Markdown header
Rationale: Raw HTML is allowed in markdown, but this rule is included for those who want their documents to only include “pure” markdown, or for those who are rendering markdown documents in something other than HTML.
This rule is triggered whenever a URL is given that isn’t surrounded by angle brackets:
For more information, see http://www.example.com/.
To fix this, add angle brackets around the URL:
For more information, see <http://www.example.com/>.
Rationale: Without angle brackets, the URL isn’t converted into a link in many markdown parsers.
Note: if you do want a bare URL without it being converted into a link, enclose it in a code block, otherwise in some markdown parsers it will be converted:
`http://www.example.com`
Parameters: style (“consistent”, “—”, “***”, or other string specifying the horizontal rule; default “consistent”)
This rule is triggered when inconsistent styles of horizontal rules are used in the document:
---
- - -
***
* * *
****
To fix this, ensure any horizontal rules used in the document are consistent, or match the given style if the rule is so configured:
---
---
Note: by default, this rule is configured to just require that all horizontal rules in the document are the same, and will trigger if any of the horizontal rules are different than the first one encountered in the document. If you want to configure the rule to match a specific style, the parameter given to the ‘style’ option is a string containing the exact horizontal rule text that is allowed.
This check looks for instances where emphasized (i.e. bold or italic) text is used to separate sections, where a header should be used instead:
**My document**
Lorem ipsum dolor sit amet...
_Another section_
Consectetur adipiscing elit, sed do eiusmod.
To fix this, use markdown headers instead of emphasized text to denote sections:
# My document
Lorem ipsum dolor sit amet...
## Another section
Consectetur adipiscing elit, sed do eiusmod.
Note: this rule looks for paragraphs that consist entirely of emphasized text. It won’t fire on emphasis used within regular text.
This rule is triggered when emphasis markers (bold, italic) are used, but they have spaces between the markers and the text:
Here is some ** bold ** text.
Here is some * italic * text.
Here is some more __ bold __ text.
Here is some more _ italic _ text.
To fix this, remove the spaces around the emphasis markers:
Here is some **bold** text.
Here is some *italic* text.
Here is some more __bold__ text.
Here is some more _italic_ text.
Rationale: Emphasis is only parsed as such when the asterisks/underscores aren’t completely surrounded by spaces. This rule attempts to detect where they were surrounded by spaces, but it appears that emphasized text was intended by the author.
This rule is triggered on code span elements that have spaces right inside the backticks:
` some text `
`some text `
` some text`
To fix this, remove the spaces inside the codespan markers:
`some text`
This rule is triggered on links that have spaces surrounding the link text:
[ a link ](http://www.example.com/)
To fix this, remove the spaces surrounding the link text:
[a link](http://www.example.com/)
This rule is triggered when fenced code blocks are used, but a language isn’t specified:
```
#!/bin/bash
echo Hello world
```
To fix this, add a language specifier to the code block:
```bash
#!/bin/bash
echo Hello world
```
This rule is triggered when the first line in the file isn’t a top level (h1) header:
```
This is a file without a header
```
To fix this, add a header to the top of your file:
```
# File with header
This is a file with a top level header
```
Markdownlint has several options you can configure both on the command line,
or in markdownlint’s configuration file: .mdlrc
, first looked for from the
working directory, then in your home directory.
While markdownlint will work perfectly well out of the box, this page
documents some of the options you can change to suit your needs.
In general, anything you pass on the command line can also be put into
~/.mdlrc
with the same option. For example, if you pass --style foo
on the
command line, you can make this the default by putting style "foo"
into your
~/.mdlrc
file.
Verbose - Print additional information about what markdownlint is doing.
-v
, --verbose
verbose true
Show warnings - Kramdown will generate warnings of its own for some issues found with documents during parsing, and markdownlint can print these out in addition to using the built in rules. This option enables/disables that behavior.
-w
, --warnings
show_kramdown_warnings true
Recurse using files known to git - When mdl is given a directory name on the command line, it will recurse into that directory looking for markdown files to process. If this option is enabled, it will use git to look for files instead, and ignore any files git doesn’t know about.
-g
, --git-recurse
git_recurse true
Tags - Limit the rules mdl enables to those containing the provided tags.
-t tag1,tag2
, --tags tag1,tag2
, -t ~tag1,~tag2
tags "tag1", "tag2"
Rules - Limit the rules mdl enables to those provided in this option.
-r MD001,MD002
, --rules MD001,MD002
, -r ~MD001,~MD002
rules "MD001", "MD002"
If a rule or tag ID is preceded by a tilde (~
), then it disables the
matching rules instead of enabling them, starting with all rules being enabled.
Note: if both --rules
and --tags
are provided, then a given rule has to
both be in the list of enabled rules, as well as be tagged with one of the
tags provided with the --tags
option. Use the -l/--list-rules
option to
test this behavior.
Style - Select which style mdl uses. A ‘style’ is a file containing a list of enabled/disable rules, as well as options for some rules that take them. For example, one style might enforce a line length of 80 characters, while another might choose 72 characters, and another might have no line length limit at all (rule MD013).
-s style_name
, --style style_name
style "style_name"
Note: the value for style_name
must either end with .rb
or have /
in it
in order to tell mdl
to look for a custom style, and not a built-in style.
Rulesets - Load a custom ruleset file. This option allows you to load custom rules in addition to those included with markdownlint.
-u ruleset.rb,ruleset2.rb
, --rules ruleset.rb,ruleset2.rb
rulesets 'ruleset.rb', 'ruleset2.rb'
No default ruleset - Skip loading the default ruleset file included with markdownlint. Use this option if you only want to load custom rulesets.
-d
, --skip-default-ruleset
skip_default_ruleset true
A ‘style’ in markdownlint is simply a ruby file specifying the list of enabled and disabled rules, as well as specifying parameters for any rules that need parameters different than the defaults.
The various options you can use in a style file are:
rule - include a specific rule.
rule 'MD001'
exclude_rule - exclude a previously included rule. Used if you want to include all except for a few rules.
exclude_rule 'MD000'
tag - include all rules that are tagged with a specific value
tag :whitespace
exclude_tag - exclude all rules tagged with the specified tag
exclude_tag :line_length
Note that tags are specified as symbols, and rule names as strings, just as in the rule definitions themselves.
The last matching option wins, so you should always put 'all'
at the top of
the file (if you want to include all rules), then tags (and tag excludes),
then specific rules. In other words, go from least to most specific.
If you specify any parameters after a rule ID, then those values will be used for the rules instead of the default. You only need to specify parameters for any values you wish to override. For example, the default values for the parameters in MD030 (spaces after list markers) are all 1. If you still want the spaces after the list markers to be 1 in some cases, then you can exclude those parameters:
rule 'MD030', :ol_multi => 2, :ul_multi => 3
Even if a rule is included already by a tag specification (or ‘all’), it is not a problem to add a specific ‘rule’ entry in order to set custom parameters, and is in fact necessary to do so.
Rules are written in ruby, using a rule DSL for defining rules. A rule looks like:
rule "MD000", "Rule description" do
tags :foo, :bar
aliases 'rule-name'
params :style => :foo
check do |doc|
# check code goes here
# return a list of line numbers that break the rule, or an empty list
# (or nil) if there are no problems.
end
end
The first line specifies the rule name and description. By convention, built in markdownlint rules use the prefix ‘MD’ followed by a number to identify rules. Any custom rules should use an alternate prefix to avoid conflicting with current or future rules. The description is simply a short description explaining what the rule is checking, which will be printed alongside the rule name when rules are triggered.
Next, the rule’s tags are specified. These are simply ruby symbols, and can be
used by a user to limit which rules are checks. For example, if your rule
checks whitespace usage in a document, you can add the :whitespace
tag, and
users who don’t care about whitespace can exclude that tag on the command line
or in style files.
You can also specify aliases for the rule, which can be used to refer to the rule with a human-readable name rather than MD000. To do this, add then with the ‘aliases’ directive. Whenever you refer to a rule, such as for including/excluding in the configuration or in style files, you can use an alias for the rule instead of its ID.
After that, any parameters the rule takes are specified. If your rule checks
for a specific number of things, or if you can envision multiple variants of
the same rule, then you should add parameters to allow your rule to be
customized in a style file. Any parameters specified here are accessible
inside the check itself using params[:foo]
.
Finally, the check itself is specified. This is simply a ruby block that should return a list of line numbers for any issues found. If no line numbers are found, you can either return an empty list, or nil, whichever is easiest for your check.
The check takes a single parameter 'doc'
, which is an object containing a
representation of the markdown document along with several helper functions
used for making rules. The doc.rb file is documented
using rdoc, and you will want to take a look there to see all the methods you
can use, as well as look at some of the existing rules, but a quick summary is
as follows:
doc
- Object containing a representation of the markdown documentdoc.lines
- The raw markdown file as an array of lines
doc.element_line(element)
doc.parsed
- The kramdown internal representation of the doc. Most of the
time you will want to interact with the parsed version of the document
rather than looking at doc.lines
.doc.find_type_elements
- A method to find all elements of a given type.
You pass the type as a symbol, such as :ul
or :p
. Most element types
match the name of the element in HTML output. This method returns a list of
the matching elements.doc.find_type
- This is like doc.find_type_elements
, but returns just
the options hashes (see below) for each element. This is useful if you don’t
need all the element information, but you do need the line numbers.doc.element_line_number
- Pass in an element (or an options hash), and
this will return the line number for the element. You need to return the
line number in the list of errors.The document contains an internal representation of the markdown document as parsed by kramdown. Kramdown’s representation of the document is as a tree of ‘element’ objects. The following is a quick summary of those objects:
:li
,
:p
, :text
:location
- line number of element:element_level
- A value filled in by markdownlint to denote the nesting
level of the element, i.e. how deep in the tree is it.-u
option. See
rules.rb
for an example of what a rules file looks like. Use the -d
option if you
don’t want to load markdownlint’s default ruleset.-a
option to display rule aliases instead of MDxxx rule
IDs.