As a follow-up to my post from the 11th. "Giving a talk at CopenhagenJS" I got my presentation accepted. So at the CopenhagenJS February meeting I gave a presentation of Markdownlint. The slides are available at SlideShare, but I thought I would do a small write-up on the topic of the presentation here.
First some basic background history on Markdownlint currently comes in two implementations, the first one in Ruby inspired to the implementation in Node. The Node library in addition has a NPM package with a command line interface (markdownlint
), where the Ruby package comes with the command mdl
.
The Node based implementation is the one I use on a daily basis so my emphasis will be on this one. I am primarily working in Visual Studio Code now and therefor work with the plugin available there and the command line tool markdownlint
both based on the Node implementation. Plugins for both Sublime Text and Atom exist.
Now let's look at some details. The linters both specify a set of rules, when applied can be raised when your Markdown is not adhering to the best-practice suggested. All the rules are identified by a key: MDXXX
, where the number identifies a unique part, but instead of your having to work with and remember all these cryptic keys, you can also use human readable aliases.
For my presentation I had prepared a small GitHub repository with all the references used and some examples for pasting and copying, since slides are not always the best reference material for later.
So if we lift the example from the mentioned repository, which is a configuration file for markdownlint
(Node).
{
"default": true
}
This is the most basic setup for markdownlint
it basically enables all the rules. Now lets get back to the rules.
An example, the rule MD013
also has the alias line-length
, which is easier to work with in for example a configuration file. So if we want to disable the line length rule for our linting, we would write as follows:
{
"default": true,
"line-length": false
}
This is also the configuration used in the example GitHub repository as it looks at the time of writing :-)
The examples given above can be written to a file named: .markdownlint.json
, this would mean that the command line tool markdownlint
would pick up on this local configuration if available.
So if we enable MD013
(line-length
) again, we would get the following output:
$ markdownlint README.md
README.md: 7: MD013/line-length Line length [Expected: 80; Actual: 129]
README.md: 13: MD013/line-length Line length [Expected: 80; Actual: 104]
README.md: 14: MD013/line-length Line length [Expected: 80; Actual: 130]
README.md: 15: MD013/line-length Line length [Expected: 80; Actual: 167]
README.md: 17: MD013/line-length Line length [Expected: 80; Actual: 139]
README.md: 18: MD013/line-length Line length [Expected: 80; Actual: 127]
And just to make a swift comparison, mdl
would report the same.
> mdl README.md
README.md:7: MD013 Line length
README.md:13: MD013 Line length
README.md:14: MD013 Line length
README.md:15: MD013 Line length
README.md:17: MD013 Line length
README.md:18: MD013 Line length
A detailed description of the rules is available at https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
The command line is marvelous, but if you use Visual Studio Code like me you are in for a treat. The plugin for Code is based on the same implementation and hence your projects can use the .markdownlint.json
configuration in your editor.
This requires the Markdownlint plugin. The following screenshot demonstrates the report on the same violations of the rule MD013
And to add even more sugar on top check out how the configuration handling reacts.
The plugin comes with builtin handling of the configuration file, so if you hover over a rule, whether it is specified as MDXXX
or the related alias, but will be visible. This is quite handy since it lets you write human readable configuration files, but still with the nifty key to the rule listing available for the respective Markdown linters.
But everything is not hunky-dory. The two implementations are close, this mean that you can use the two tools somewhat interchangeably, but they are not fully compliant and compatible.
The Ruby implementation implements 38 rules from MD001
to MD041
and a the rule MD046
Whereas the Node implements 41 rules from MD001
to MD045
.
Meaning that the problematic rules are:
-
MD042
, only available in the Node implementation -
MD043
, only available in the Node implementation -
MD044
, only available in the Node implementation -
MD045
, only available in the Node implementation -
MD046
, only available in the Ruby implementation
You can see my two gists for Ruby and Node. This might of course change, but that is the state of things at the time of writing, do consult the original listing in the official documentation: Ruby and Node.
If you are aware of the differences and your local Markdownlint configuration does not rely on any of the mentioned holes you can get by with the both tools, but is easier to pick the one of the two which fits your situation the best. Since using both would require to rely on a single configuration so maintaining your rules become a additional work in your project.
Some rules can have additional configuration parameters in addition to enabling and disabling, I have not compared all of these possible configurations between the two implementations for the rule listings for the two implementations, so there might hiding dragons in this area as well.
In addition to the command line and the editor integrations, you can add Markdownlint to your CI/CD setup, you might not be the only contributor to your Markdown products and you cannot assume other are have a similar process or the same tools.
This is a very basic configuration for Travis CI using Markdownlint
language: node_js
node_js:
- '6'
install:
- npm install -g markdownlint-cli
script:
- markdownlint *.md
This works for Node based projects and you can possibly tweak the example for projects based on other languages.
The following is an example for an XML founded project:
language: node_js
node_js:
- '6'
before_install:
- sudo apt-get install -y libxml2-utils
install:
- npm install -g markdownlint-cli
jobs:
include:
- stage: "Documentation test"
name: "Markdown test"
script: markdownlint README.md
- stage: "Product test"
name: "XML/XSD test"
script: xmllint --schema epp.xsd xml/*.xml
Here the product is XML and is therefor linted using another command line tool (xmllint
) and the documentation is tested using markdownlint
. Both examples have been lifted from my GitHub repository. This renders this beautiful representation.
Just for the reference here is a untested example for what a Travis configuration for a Markdownlint setup using mdl
and in Ruby could look like.
language: ruby
install:
- gem install mdl
script:
- mdl *.md
For the presentation I also talked about what a linter is and the rationale for using linters in general, I do not want to touch on this topic in this blog post, perhaps in a future post. Instead I have dug more into the details around the Markdownlint implementations. In the presentation I also mentioned CommonMark I decided not touch on this topic in this post since it would simply run too long, this might also be a topic for a post in the future.
Preparing the presentation and writing this post has identified several areas in which one could put further effort (projects, contributions, PRs).
- Implement
MD042
in the Ruby implementation - Implement
MD043
in the Ruby implementation - Implement
MD044
in the Ruby implementation - Implement
MD045
in the Ruby implementation Implement
MD046
in the Node implementationExperiment with more advanced Travis configurations for more variations of projects and repositories like Rust, Perl whatever might come up
Extend the capabilities of the Node and Ruby implementations so configuration files can be shared and then easily maintained
Finally the grand finale - contact the authors of the two projects an suggest consolidating a standard for Markdownlint and rules, so the different implementations can be compared based against a baseline outlined as a standard. This is very much inspired by the work made for CommonMark and not completely undoable because of the awesome authors behind the existing implementations and the fact that the Node implementation is inspired and based on the Ruby implementation.
I might propagate a lot of the information from this blog post to the mentioned GitHub repository, do however checkout the repository for a list of references used for this post and the initial presentation and possible future findings, tips and example will go there.
Happy Markdown writing and linting...
Top comments (8)
If you run into problems with an error resembling this:
I have written up a TIL about the issue and how to fix it.
Shout out to @lauragift21 for her blog post.
Oh wow! Thanks for the shout-out! Great post too on Markdownlint haven't used it before now but I'll give it a try :)
Oh, wow, jonasbn. Great idea.
I'm wondering whether I should consider adding linting to my
markdown_helper
project and gem.Hiya,
Happy, that you find it great, however unclear on what part. The post ran a bit too long and covered way too much.
The great idea, being adding linting to your tool project?
jonasbn
Sorry not clear on first try. The great idea is your linter.
Oh, it is not mine, I am just a user - I love linters and I happen to be writing a lot of Markdown, so I currently I am in love with Markdownlint :-)
Using primarily the Node implementation, have played around with the Ruby implementation. Btw. the Ruby implementation should perhaps be what you should look at for possible integration with your project: github.com/markdownlint
Hi Jonas! Fantastic post. You might want to change one typo: "Honky-dory" should be Hunky-dory. Cheers!
Corrected, thank you very much