Add rule aliases, support throughout (fixes #1).

This commit is contained in:
David Anson 2016-01-12 21:29:17 -08:00
parent b7342485d9
commit 9546cc520e
9 changed files with 283 additions and 101 deletions

114
README.md
View file

@ -38,45 +38,45 @@ cases come directly from that project.
[`markdownlint` demo](http://dlaa.me/markdownlint/), an interactive, in-browser
playground for learning and exploring.
## Rules
## Rules / Aliases
* **MD001** - Header levels should only increment by one level at a time
* **MD002** - First header should be a h1 header
* **MD003** - Header style
* **MD004** - Unordered list style
* **MD005** - Inconsistent indentation for list items at the same level
* **MD006** - Consider starting bulleted lists at the beginning of the line
* **MD007** - Unordered list indentation
* **MD009** - Trailing spaces
* **MD010** - Hard tabs
* **MD011** - Reversed link syntax
* **MD012** - Multiple consecutive blank lines
* **MD013** - Line length
* **MD014** - Dollar signs used before commands without showing output
* **MD018** - No space after hash on atx style header
* **MD019** - Multiple spaces after hash on atx style header
* **MD020** - No space inside hashes on closed atx style header
* **MD021** - Multiple spaces inside hashes on closed atx style header
* **MD022** - Headers should be surrounded by blank lines
* **MD023** - Headers must start at the beginning of the line
* **MD024** - Multiple headers with the same content
* **MD025** - Multiple top level headers in the same document
* **MD026** - Trailing punctuation in header
* **MD027** - Multiple spaces after blockquote symbol
* **MD028** - Blank line inside blockquote
* **MD029** - Ordered list item prefix
* **MD030** - Spaces after list markers
* **MD031** - Fenced code blocks should be surrounded by blank lines
* **MD032** - Lists should be surrounded by blank lines
* **MD033** - Inline HTML
* **MD034** - Bare URL used
* **MD035** - Horizontal rule style
* **MD036** - Emphasis used instead of a header
* **MD037** - Spaces inside emphasis markers
* **MD038** - Spaces inside code span elements
* **MD039** - Spaces inside link text
* **MD040** - Fenced code blocks should have a language specified
* **MD041** - First line in file should be a top level header
* **MD001** *header-increment* - Header levels should only increment by one level at a time
* **MD002** *first-header-h1* - First header should be a h1 header
* **MD003** *header-style* - Header style
* **MD004** *ul-style* - Unordered list style
* **MD005** *list-indent* - Inconsistent indentation for list items at the same level
* **MD006** *ul-start-left* - Consider starting bulleted lists at the beginning of the line
* **MD007** *ul-indent* - Unordered list indentation
* **MD009** *no-trailing-spaces* - Trailing spaces
* **MD010** *no-hard-tabs* - Hard tabs
* **MD011** *no-reversed-links* - Reversed link syntax
* **MD012** *no-multiple-blanks* - Multiple consecutive blank lines
* **MD013** *line-length* - Line length
* **MD014** *commands-show-output* - Dollar signs used before commands without showing output
* **MD018** *no-missing-space-atx* - No space after hash on atx style header
* **MD019** *no-multiple-space-atx* - Multiple spaces after hash on atx style header
* **MD020** *no-missing-space-closed-atx* - No space inside hashes on closed atx style header
* **MD021** *no-multiple-space-closed-atx* - Multiple spaces inside hashes on closed atx style header
* **MD022** *blanks-around-headers* - Headers should be surrounded by blank lines
* **MD023** *header-start-left* - Headers must start at the beginning of the line
* **MD024** *no-duplicate-header* - Multiple headers with the same content
* **MD025** *single-h1* - Multiple top level headers in the same document
* **MD026** *no-trailing-punctuation* - Trailing punctuation in header
* **MD027** *no-multiple-space-blockquote* - Multiple spaces after blockquote symbol
* **MD028** *no-blanks-blockquote* - Blank line inside blockquote
* **MD029** *ol-prefix* - Ordered list item prefix
* **MD030** *list-marker-space* - Spaces after list markers
* **MD031** *blanks-around-fences* - Fenced code blocks should be surrounded by blank lines
* **MD032** *blanks-around-lists* - Lists should be surrounded by blank lines
* **MD033** *no-inline-html* - Inline HTML
* **MD034** *no-bare-urls* - Bare URL used
* **MD035** *hr-style* - Horizontal rule style
* **MD036** *no-emphasis-as-header* - Emphasis used instead of a header
* **MD037** *no-space-in-emphasis* - Spaces inside emphasis markers
* **MD038** *no-space-in-code* - Spaces inside code span elements
* **MD039** *no-space-in-links* - Spaces inside link text
* **MD040** *fenced-code-language* - Fenced code blocks should have a language specified
* **MD041** *first-line-h1* - First line in file should be a top level header
See [Rules.md](doc/Rules.md) for more details.
@ -229,7 +229,7 @@ Type: `Object` mapping `String` to `Boolean | Object`
Configures the rules to use.
Object keys are rule names and values are the rule's configuration.
Object keys are rule names or aliases and values are the rule's configuration.
The value `false` disables a rule, `true` enables its default configuration,
and passing an object customizes its settings. Setting the special `default`
rule to `true` or `false` includes/excludes all rules by default. Enabling or
@ -237,7 +237,7 @@ disabling a tag name (ex: `whitespace`) affects all rules having that tag.
The `default` rule is applied first, then keys are processed in order from top
to bottom with later values overriding earlier ones. Keys (including rule names,
tags, and `default`) are not case-sensitive.
aliases, tags, and `default`) are not case-sensitive.
Example:
@ -246,7 +246,7 @@ Example:
"default": true,
"MD003": { "style": "atx_closed" },
"MD007": { "indent": 4 },
"MD009": false,
"no-hard-tabs": false,
"whitespace": false
}
```
@ -276,7 +276,8 @@ Standard completion callback.
Type: `Object`
Call `result.toString()` for convenience or see below for an example of the
structure of the `result` object.
structure of the `result` object. Passing the value `true` to `toString()`
uses rule aliases (ex: `no-hard-tabs`) instead of names (ex: `MD010`).
## Usage
@ -300,14 +301,7 @@ markdownlint(options, function callback(err, result) {
});
```
Or invoke `markdownlint.sync` for a synchronous call:
```js
var result = markdownlint.sync(options);
console.log(result.toString());
```
Output of both calls:
Output:
```text
bad.string: 3: MD010 Hard tabs
@ -318,6 +312,24 @@ bad.md: 1: MD018 No space after hash on atx style header
bad.md: 3: MD018 No space after hash on atx style header
```
Or invoke `markdownlint.sync` for a synchronous call:
```js
var result = markdownlint.sync(options);
console.log(result.toString(true));
```
Output:
```text
bad.string: 3: no-hard-tabs Hard tabs
bad.string: 1: no-missing-space-atx No space after hash on atx style header
bad.string: 3: no-missing-space-atx No space after hash on atx style header
bad.md: 3: no-hard-tabs Hard tabs
bad.md: 1: no-missing-space-atx No space after hash on atx style header
bad.md: 3: no-missing-space-atx No space after hash on atx style header
```
To examine the `result` object directly:
```js
@ -420,7 +432,7 @@ bad.md: 3: MD018 No space after hash on atx style header
## Browser
`markdownlint` works in the browser.
`markdownlint` also works in the browser.
Generate normal and minified scripts with:

View file

@ -8,6 +8,8 @@ versions of the examples.
Tags: headers
Aliases: header-increment
This rule is triggered when you skip header levels in a markdown document, for
example:
@ -37,6 +39,8 @@ level at a time:
Tags: headers
Aliases: first-header-h1
This rule is triggered when the first header in the document isn't a h1 header:
## This isn't a H1 header
@ -53,6 +57,8 @@ The first header in the document should be a h1 header:
Tags: headers
Aliases: header-style
Parameters: style ("consistent", "atx", "atx_closed", "setext", "setext_with_atx"; default "consistent")
This rule is triggered when different header styles (atx, setext, and 'closed'
@ -90,6 +96,8 @@ consistent within the document.
Tags: bullet, ul
Aliases: ul-style
Parameters: style ("consistent", "asterisk", "plus", "dash"; default "consistent")
This rule is triggered when the symbols used in the document for unordered
@ -114,6 +122,8 @@ document.
Tags: bullet, ul, indentation
Aliases: list-indent
This rule is triggered when list items are parsed as being at the same level,
but don't have the same indentation:
@ -134,6 +144,8 @@ for the list to fix it:
Tags: bullet, ul, indentation
Aliases: ul-start-left
This rule is triggered when top level lists don't start at the beginning of a
line:
@ -160,6 +172,8 @@ characters if you use 4 space tabs, or 1 character if you use 2 space tabs).
Tags: bullet, ul, indentation
Aliases: ul-indent
Parameters: indent (number; default 2)
This rule is triggered when list items are not indented by the configured
@ -193,6 +207,8 @@ for a description of the problem.
Tags: whitespace
Aliases: no-trailing-spaces
Parameters: br_spaces (number; default: 0)
This rule is triggered on any lines that end with whitespace. To fix this,
@ -211,6 +227,8 @@ set to the default of 0.
Tags: whitespace, hard_tab
Aliases: no-hard-tabs
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.
@ -231,6 +249,8 @@ Corrected example:
Tags: links
Aliases: no-reversed-links
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):
@ -245,6 +265,8 @@ To fix this, swap the `[]` and `()` around:
Tags: whitespace, blank_lines
Aliases: no-multiple-blanks
This rule is triggered when there are multiple consecutive blank lines in the
document:
@ -266,6 +288,8 @@ lines inside code blocks.
Tags: line_length
Aliases: line-length
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
@ -287,6 +311,8 @@ rules. Still, some languages do not lend themselves to short lines.
Tags: code
Aliases: commands-show-output
This rule is triggered when there are code blocks showing shell commands to be
typed, and the shell commands are preceded by dollar signs ($):
@ -320,6 +346,8 @@ for more information.
Tags: headers, atx, spaces
Aliases: no-missing-space-atx
This rule is triggered when spaces are missing after the hash characters
in an atx style header:
@ -338,6 +366,8 @@ space:
Tags: headers, atx, spaces
Aliases: no-multiple-space-atx
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:
@ -356,6 +386,8 @@ space:
Tags: headers, atx_closed, spaces
Aliases: no-missing-space-closed-atx
This rule is triggered when spaces are missing inside the hash characters
in a closed atx style header:
@ -376,6 +408,8 @@ Note: this rule will fire if either side of the header is missing spaces.
Tags: headers, atx_closed, spaces
Aliases: no-multiple-space-closed-atx
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:
@ -397,6 +431,8 @@ spaces.
Tags: headers, blank_lines
Aliases: blanks-around-headers
This rule is triggered when headers (any style) are either not preceded or not
followed by a blank line:
@ -425,6 +461,8 @@ regular text.
Tags: headers, spaces
Aliases: header-start-left
This rule is triggered when a header is indented by one or more spaces:
Some text
@ -444,6 +482,8 @@ parsed as headers, and will instead appear as regular text.
Tags: headers
Aliases: no-duplicate-header
This rule is triggered if there are multiple headers in the document that have
the same text:
@ -465,6 +505,8 @@ this.
Tags: headers
Aliases: single-h1
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:
@ -492,6 +534,8 @@ should be contained within this header.
Tags: headers
Aliases: no-trailing-punctuation
Parameters: punctuation (string; default ".,;:!?")
This rule is triggered on any header that has a punctuation character as the
@ -512,6 +556,8 @@ in an FAQ.
Tags: blockquote, whitespace, indentation
Aliases: no-multiple-space-blockquote
This rule is triggered when blockquotes have more than one space after the
blockquote (`>`) symbol:
@ -527,6 +573,8 @@ To fix, remove any extraneous space:
Tags: blockquote, whitespace
Aliases: no-blanks-blockquote
This rule is triggered when two blockquote blocks are separated by nothing
except for a blank line:
@ -560,6 +608,8 @@ separate blockquotes.
Tags: ol
Aliases: ol-prefix
Parameters: style ("one", "ordered"; default "one")
This rule is triggered on ordered lists that do not either start with '1.' or
@ -582,6 +632,8 @@ Example valid list if the style is configured as 'ordered':
Tags: ol, ul, whitespace
Aliases: list-marker-space
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. '`-`',
@ -641,6 +693,8 @@ for your selected document style.
Tags: code, blank_lines
Aliases: blanks-around-fences
This rule is triggered when fenced code blocks are either not preceded or not
followed by a blank line:
@ -676,6 +730,8 @@ not parse fenced code blocks that don't have blank lines before and after them.
Tags: bullet, ul, ol, blank_lines
Aliases: blanks-around-lists
This rule is triggered when lists (of any kind) are either not preceded or not
followed by a blank line:
@ -716,6 +772,8 @@ items with hanging indents are okay:
Tags: html
Aliases: no-inline-html
Parameters: allowed_elements (array of string; default empty)
This rule is triggered whenever raw HTML is used in a markdown document:
@ -736,6 +794,8 @@ Note: To allow specific HTML elements, use the 'allowed_elements' parameter.
Tags: links, url
Aliases: no-bare-urls
This rule is triggered whenever a URL is given that isn't surrounded by angle
brackets:
@ -758,6 +818,8 @@ converted:
Tags: hr
Aliases: hr-style
Parameters: style ("consistent", "---", "***", or other string specifying the
horizontal rule; default "consistent")
@ -792,6 +854,8 @@ is allowed.
Tags: headers, emphasis
Aliases: no-emphasis-as-header
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:
@ -821,6 +885,8 @@ It won't fire on emphasis used within regular text.
Tags: whitespace, emphasis
Aliases: no-space-in-emphasis
This rule is triggered when emphasis markers (bold, italic) are used, but they
have spaces between the markers and the text:
@ -851,6 +917,8 @@ intended by the author.
Tags: whitespace, code
Aliases: no-space-in-code
This rule is triggered on code span elements that have spaces right inside the
backticks:
@ -868,6 +936,8 @@ To fix this, remove the spaces inside the codespan markers:
Tags: whitespace, links
Aliases: no-space-in-links
This rule is triggered on links that have spaces surrounding the link text:
[ a link ](http://www.example.com/)
@ -880,6 +950,8 @@ To fix this, remove the spaces surrounding the link text:
Tags: code, language
Aliases: fenced-code-language
This rule is triggered when fenced code blocks are used, but a language isn't
specified:
@ -899,6 +971,8 @@ To fix this, add a language specifier to the code block:
Tags: headers
Aliases: first-line-h1
This rule is triggered when the first line in the file isn't a top level (h1)
header:

View file

@ -24,6 +24,6 @@ markdownlint(options, function callback(err, result) {
}
});
// Make a synchronous call
// Make a synchronous call, passing true to toString()
var result = markdownlint.sync(options);
console.log(result.toString());
console.log(result.toString(true));

View file

@ -7,38 +7,46 @@ var shared = require("./shared");
// Mappings from rule to description and tag to rules
var allRuleNames = [];
var ruleToDescription = {};
var tagUpperToRules = {};
var ruleNameToRule = {};
var idUpperToRuleNames = {};
rules.forEach(function forRule(rule) {
allRuleNames.push(rule.name);
ruleToDescription[rule.name] = rule.desc;
ruleNameToRule[rule.name] = rule;
// The following is useful for updating README.md
// console.log("* **" + rule.name + "** - " + rule.desc);
// console.log("* **" + rule.name + "** *" +
// rule.aliases.join(", ") + "* - " + rule.desc);
rule.tags.forEach(function forTag(tag) {
var tagUpper = tag.toUpperCase();
var tags = tagUpperToRules[tagUpper] || [];
tags.push(rule.name);
tagUpperToRules[tagUpper] = tags;
var ruleNames = idUpperToRuleNames[tagUpper] || [];
ruleNames.push(rule.name);
idUpperToRuleNames[tagUpper] = ruleNames;
});
rule.aliases.forEach(function forAlias(alias) {
var aliasUpper = alias.toUpperCase();
idUpperToRuleNames[aliasUpper] = [ rule.name ];
});
});
// The following is useful for updating README.md
// Object.keys(tagToRules).sort().forEach(function forTag(tag) {
// console.log("* **" + tag + "** - " + tagToRules[tag].join(", "));
// Object.keys(idUpperToRuleNames).sort().forEach(function forTag(tag) {
// console.log("* **" + tag + "** - " + idUpperToRuleNames[tag].join(", "));
// });
// Class for results with toString for pretty display
function Results() { }
Results.prototype.toString = function resultsToString() {
Results.prototype.toString = function resultsToString(useAlias) {
var that = this;
var results = [];
Object.keys(that).forEach(function forFile(file) {
var fileResults = that[file];
Object.keys(fileResults).forEach(function forRule(rule) {
var ruleResults = fileResults[rule];
Object.keys(fileResults).forEach(function forRule(ruleName) {
var rule = ruleNameToRule[ruleName];
var ruleResults = fileResults[ruleName];
ruleResults.forEach(function forLine(lineNumber) {
var result =
file + ": " + lineNumber + ": " +
rule + " " + ruleToDescription[rule];
file + ": " +
lineNumber + ": " +
(useAlias ? rule.aliases[0] : rule.name) + " " +
rule.desc;
results.push(result);
});
});
@ -114,10 +122,10 @@ function lintContent(content, config, frontMatter) { // eslint-disable-line
value = false;
}
var keyUpper = key.toUpperCase();
if (ruleToDescription[keyUpper]) {
if (ruleNameToRule[keyUpper]) {
mergedRules[keyUpper] = value;
} else if (tagUpperToRules[keyUpper]) {
tagUpperToRules[keyUpper].forEach(function forRule(ruleName) {
} else if (idUpperToRuleNames[keyUpper]) {
idUpperToRuleNames[keyUpper].forEach(function forRule(ruleName) {
mergedRules[ruleName] = value;
});
}
@ -133,10 +141,10 @@ function lintContent(content, config, frontMatter) { // eslint-disable-line
match[2].trim().toUpperCase().split(/\s+/) :
allRuleNames;
items.forEach(function forItem(nameUpper) {
if (ruleToDescription[nameUpper]) {
if (ruleNameToRule[nameUpper]) {
enabledRules[nameUpper] = enabled;
} else if (tagUpperToRules[nameUpper]) {
tagUpperToRules[nameUpper].forEach(function forRule(ruleName) {
} else if (idUpperToRuleNames[nameUpper]) {
idUpperToRuleNames[nameUpper].forEach(function forRule(ruleName) {
enabledRules[ruleName] = enabled;
});
}

View file

@ -153,6 +153,7 @@ module.exports = [
"name": "MD001",
"desc": "Header levels should only increment by one level at a time",
"tags": [ "headers" ],
"aliases": [ "header-increment" ],
"func": function MD001(params, errors) {
var prevLevel = 0;
filterTokens(params, "heading_open", function forToken(token) {
@ -169,6 +170,7 @@ module.exports = [
"name": "MD002",
"desc": "First header should be a h1 header",
"tags": [ "headers" ],
"aliases": [ "first-header-h1" ],
"func": function MD002(params, errors) {
params.tokens.every(function forToken(token) {
if (token.type === "heading_open") {
@ -186,6 +188,7 @@ module.exports = [
"name": "MD003",
"desc": "Header style",
"tags": [ "headers" ],
"aliases": [ "header-style" ],
"func": function MD003(params, errors) {
var style = params.options.style || "consistent";
filterTokens(params, "heading_open", function forToken(token) {
@ -207,6 +210,7 @@ module.exports = [
"name": "MD004",
"desc": "Unordered list style",
"tags": [ "bullet", "ul" ],
"aliases": [ "ul-style" ],
"func": function MD004(params, errors) {
var style = params.options.style || "consistent";
flattenLists(params).forEach(function forList(list) {
@ -228,6 +232,7 @@ module.exports = [
"name": "MD005",
"desc": "Inconsistent indentation for list items at the same level",
"tags": [ "bullet", "ul", "indentation" ],
"aliases": [ "list-indent" ],
"func": function MD005(params, errors) {
flattenLists(params).forEach(function forList(list) {
var indent = indentFor(list.items[0]);
@ -244,6 +249,7 @@ module.exports = [
"name": "MD006",
"desc": "Consider starting bulleted lists at the beginning of the line",
"tags": [ "bullet", "ul", "indentation" ],
"aliases": [ "ul-start-left" ],
"func": function MD006(params, errors) {
flattenLists(params).forEach(function forList(list) {
if (!list.ordered && !list.nesting && indentFor(list.open)) {
@ -257,6 +263,7 @@ module.exports = [
"name": "MD007",
"desc": "Unordered list indentation",
"tags": [ "bullet", "ul", "indentation" ],
"aliases": [ "ul-indent" ],
"func": function MD007(params, errors) {
var optionsIndent = params.options.indent || 2;
var prevIndent = 0;
@ -277,6 +284,7 @@ module.exports = [
"name": "MD009",
"desc": "Trailing spaces",
"tags": [ "whitespace" ],
"aliases": [ "no-trailing-spaces" ],
"func": function MD009(params, errors) {
var brSpaces = params.options.br_spaces || 0;
params.lines.forEach(function forLine(line, lineIndex) {
@ -293,6 +301,7 @@ module.exports = [
"name": "MD010",
"desc": "Hard tabs",
"tags": [ "whitespace", "hard_tab" ],
"aliases": [ "no-hard-tabs" ],
"func": function MD010(params, errors) {
params.lines.forEach(function forLine(line, lineIndex) {
if (/\t/.test(line)) {
@ -306,6 +315,7 @@ module.exports = [
"name": "MD011",
"desc": "Reversed link syntax",
"tags": [ "links" ],
"aliases": [ "no-reversed-links" ],
"func": function MD011(params, errors) {
forEachInlineChild(params, "text", function forToken(token) {
if (/\([^)]+\)\[[^\]]+\]/.test(token.content)) {
@ -319,6 +329,7 @@ module.exports = [
"name": "MD012",
"desc": "Multiple consecutive blank lines",
"tags": [ "whitespace", "blank_lines" ],
"aliases": [ "no-multiple-blanks" ],
"func": function MD012(params, errors) {
var prevLine = "-";
forEachLine(params, function forLine(line, lineIndex, inCode) {
@ -335,6 +346,7 @@ module.exports = [
"name": "MD013",
"desc": "Line length",
"tags": [ "line_length" ],
"aliases": [ "line-length" ],
"func": function MD013(params, errors) {
var lineLength = params.options.line_length || 80;
var codeBlocks = params.options.code_blocks;
@ -357,6 +369,7 @@ module.exports = [
"name": "MD014",
"desc": "Dollar signs used before commands without showing output",
"tags": [ "code" ],
"aliases": [ "commands-show-output" ],
"func": function MD014(params, errors) {
[ "code_block", "fence" ].forEach(function forType(type) {
filterTokens(params, type, function forToken(token) {
@ -376,6 +389,7 @@ module.exports = [
"name": "MD018",
"desc": "No space after hash on atx style header",
"tags": [ "headers", "atx", "spaces" ],
"aliases": [ "no-missing-space-atx" ],
"func": function MD018(params, errors) {
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
@ -389,6 +403,7 @@ module.exports = [
"name": "MD019",
"desc": "Multiple spaces after hash on atx style header",
"tags": [ "headers", "atx", "spaces" ],
"aliases": [ "no-multiple-space-atx" ],
"func": function MD019(params, errors) {
filterTokens(params, "heading_open", function forToken(token) {
if ((headingStyleFor(token) === "atx") &&
@ -403,6 +418,7 @@ module.exports = [
"name": "MD020",
"desc": "No space inside hashes on closed atx style header",
"tags": [ "headers", "atx_closed", "spaces" ],
"aliases": [ "no-missing-space-closed-atx" ],
"func": function MD020(params, errors) {
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line) &&
@ -417,6 +433,7 @@ module.exports = [
"name": "MD021",
"desc": "Multiple spaces inside hashes on closed atx style header",
"tags": [ "headers", "atx_closed", "spaces" ],
"aliases": [ "no-multiple-space-closed-atx" ],
"func": function MD021(params, errors) {
filterTokens(params, "heading_open", function forToken(token) {
if ((headingStyleFor(token) === "atx_closed") &&
@ -431,6 +448,7 @@ module.exports = [
"name": "MD022",
"desc": "Headers should be surrounded by blank lines",
"tags": [ "headers", "blank_lines" ],
"aliases": [ "blanks-around-headers" ],
"func": function MD022(params, errors) {
var prevHeadingLineNumber = 0;
var prevMaxLineIndex = -1;
@ -471,6 +489,7 @@ module.exports = [
"name": "MD023",
"desc": "Headers must start at the beginning of the line",
"tags": [ "headers", "spaces" ],
"aliases": [ "header-start-left" ],
"func": function MD023(params, errors) {
filterTokens(params, "heading_open", function forToken(token) {
if (/^\s/.test(token.line)) {
@ -484,6 +503,7 @@ module.exports = [
"name": "MD024",
"desc": "Multiple headers with the same content",
"tags": [ "headers" ],
"aliases": [ "no-duplicate-header" ],
"func": function MD024(params, errors) {
var knownContent = [];
forEachHeading(params, function forHeading(heading, content) {
@ -500,6 +520,7 @@ module.exports = [
"name": "MD025",
"desc": "Multiple top level headers in the same document",
"tags": [ "headers" ],
"aliases": [ "single-h1" ],
"func": function MD025(params, errors) {
var hasTopLevelHeading = false;
filterTokens(params, "heading_open", function forToken(token) {
@ -518,6 +539,7 @@ module.exports = [
"name": "MD026",
"desc": "Trailing punctuation in header",
"tags": [ "headers" ],
"aliases": [ "no-trailing-punctuation" ],
"func": function MD026(params, errors) {
var punctuation = params.options.punctuation || ".,;:!?";
var re = new RegExp("[" + punctuation + "]$");
@ -533,6 +555,7 @@ module.exports = [
"name": "MD027",
"desc": "Multiple spaces after blockquote symbol",
"tags": [ "blockquote", "whitespace", "indentation" ],
"aliases": [ "no-multiple-space-blockquote" ],
"func": function MD027(params, errors) {
var inBlockquote = false;
params.tokens.forEach(function forToken(token) {
@ -557,6 +580,7 @@ module.exports = [
"name": "MD028",
"desc": "Blank line inside blockquote",
"tags": [ "blockquote", "whitespace" ],
"aliases": [ "no-blanks-blockquote" ],
"func": function MD028(params, errors) {
var prevToken = {};
params.tokens.forEach(function forToken(token) {
@ -573,6 +597,7 @@ module.exports = [
"name": "MD029",
"desc": "Ordered list item prefix",
"tags": [ "ol" ],
"aliases": [ "ol-prefix" ],
"func": function MD029(params, errors) {
var style = params.options.style || "one";
flattenLists(params).forEach(function forList(list) {
@ -596,6 +621,7 @@ module.exports = [
"name": "MD030",
"desc": "Spaces after list markers",
"tags": [ "ol", "ul", "whitespace" ],
"aliases": [ "list-marker-space" ],
"func": function MD030(params, errors) {
var ulSingle = params.options.ul_single || 1;
var olSingle = params.options.ol_single || 1;
@ -621,6 +647,7 @@ module.exports = [
"name": "MD031",
"desc": "Fenced code blocks should be surrounded by blank lines",
"tags": [ "code", "blank_lines" ],
"aliases": [ "blanks-around-fences" ],
"func": function MD031(params, errors) {
var lines = params.lines;
forEachLine(params, function forLine(line, i, inCode, onFence) {
@ -636,6 +663,7 @@ module.exports = [
"name": "MD032",
"desc": "Lists should be surrounded by blank lines",
"tags": [ "bullet", "ul", "ol", "blank_lines" ],
"aliases": [ "blanks-around-lists" ],
"func": function MD032(params, errors) {
var inList = false;
var prevLine = "";
@ -658,6 +686,7 @@ module.exports = [
"name": "MD033",
"desc": "Inline HTML",
"tags": [ "html" ],
"aliases": [ "no-inline-html" ],
"func": function MD033(params, errors) {
var allowedElements = (params.options.allowed_elements || [])
.map(function forElement(element) {
@ -690,6 +719,7 @@ module.exports = [
"name": "MD034",
"desc": "Bare URL used",
"tags": [ "links", "url" ],
"aliases": [ "no-bare-urls" ],
"func": function MD034(params, errors) {
filterTokens(params, "inline", function forToken(token) {
var inLink = false;
@ -712,6 +742,7 @@ module.exports = [
"name": "MD035",
"desc": "Horizontal rule style",
"tags": [ "hr" ],
"aliases": [ "hr-style" ],
"func": function MD035(params, errors) {
var style = params.options.style || "consistent";
filterTokens(params, "hr", function forToken(token) {
@ -729,6 +760,7 @@ module.exports = [
"name": "MD036",
"desc": "Emphasis used instead of a header",
"tags": [ "headers", "emphasis" ],
"aliases": [ "no-emphasis-as-header" ],
"func": function MD036(params, errors) {
function base(token) {
if (token.type === "paragraph_open") {
@ -760,6 +792,7 @@ module.exports = [
"name": "MD037",
"desc": "Spaces inside emphasis markers",
"tags": [ "whitespace", "emphasis" ],
"aliases": [ "no-space-in-emphasis" ],
"func": function MD037(params, errors) {
forEachInlineChild(params, "text", function forToken(token) {
if (/\s(\*\*?|__?)\s.+\1/.test(token.content) ||
@ -774,6 +807,7 @@ module.exports = [
"name": "MD038",
"desc": "Spaces inside code span elements",
"tags": [ "whitespace", "code" ],
"aliases": [ "no-space-in-code" ],
"func": function MD038(params, errors) {
forEachInlineChild(params, "code_inline",
function forToken(token, inline) {
@ -788,6 +822,7 @@ module.exports = [
"name": "MD039",
"desc": "Spaces inside link text",
"tags": [ "whitespace", "links" ],
"aliases": [ "no-space-in-links" ],
"func": function MD039(params, errors) {
filterTokens(params, "inline", function forToken(token) {
var inLink = false;
@ -821,6 +856,7 @@ module.exports = [
"name": "MD040",
"desc": "Fenced code blocks should have a language specified",
"tags": [ "code", "language" ],
"aliases": [ "fenced-code-language" ],
"func": function MD040(params, errors) {
filterTokens(params, "fence", function forToken(token) {
if (!token.info.trim()) {
@ -834,6 +870,7 @@ module.exports = [
"name": "MD041",
"desc": "First line in file should be a top level header",
"tags": [ "headers" ],
"aliases": [ "first-line-h1" ],
"func": function MD041(params, errors) {
var firstHeader = null;
params.tokens.every(function forToken(token) {

View file

@ -8,7 +8,7 @@ module.exports.frontMatterRe = /^---$[^]*?^---$(\r\n|\r|\n)/m;
// Regular expression for matching inline disable/enable comments
module.exports.inlineCommentRe =
/<!--\s*markdownlint-(dis|en)able((?:\s+[a-z0-9_]+)*)\s*-->/ig;
/<!--\s*markdownlint-(dis|en)able((?:\s+[a-z0-9_\-]+)*)\s*-->/ig;
// readFile options for reading with the UTF-8 encoding
module.exports.utf8Encoding = { "encoding": "utf8" };

View file

@ -4,10 +4,10 @@
"default": true,
"whitespace": false,
"line_length": false,
"MD006": false,
"MD007": false,
"MD033": false,
"MD034": false,
"MD040": false,
"MD041": false
"ul-start-left": false,
"ul-indent": false,
"no-inline-html": false,
"no-bare-urls": false,
"fenced-code-language": false,
"first-line-h1": false
}

View file

@ -60,9 +60,9 @@ hard tab / space * in * emphasis / space ` in ` code
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable NotATag MD038 -->
<!-- markdownlint-disable NotATag no-space-in-code -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code
<!-- markdownlint-enable NotATag MD038 -->
<!-- markdownlint-enable NotATag nO-sPaCe-In-CoDe -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}

View file

@ -77,7 +77,8 @@ fs.readdirSync("./test").forEach(function forFile(file) {
module.exports.projectFiles = function projectFiles(test) {
test.expect(2);
var options = {
"files": [ "README.md" ]
"files": [ "README.md" ],
"config": { "MD013": false }
};
markdownlint(options, function callback(err, actual) {
test.ifError(err);
@ -88,7 +89,7 @@ module.exports.projectFiles = function projectFiles(test) {
};
module.exports.resultFormatting = function resultFormatting(test) {
test.expect(3);
test.expect(4);
var options = {
"files": [
"./test/atx_header_spacing.md",
@ -121,13 +122,26 @@ module.exports.resultFormatting = function resultFormatting(test) {
" Multiple spaces after hash on atx style header\n" +
"./test/first_header_bad_atx.md: 1: MD002" +
" First header should be a h1 header";
test.equal(actualMessage, expectedMessage, "Incorrect message.");
test.equal(actualMessage, expectedMessage, "Incorrect message (name).");
actualMessage = actualResult.toString(true);
expectedMessage =
"./test/atx_header_spacing.md: 3: first-header-h1" +
" First header should be a h1 header\n" +
"./test/atx_header_spacing.md: 1: no-missing-space-atx" +
" No space after hash on atx style header\n" +
"./test/atx_header_spacing.md: 3: no-multiple-space-atx" +
" Multiple spaces after hash on atx style header\n" +
"./test/atx_header_spacing.md: 5: no-multiple-space-atx" +
" Multiple spaces after hash on atx style header\n" +
"./test/first_header_bad_atx.md: 1: first-header-h1" +
" First header should be a h1 header";
test.equal(actualMessage, expectedMessage, "Incorrect message (alias).");
test.done();
});
};
module.exports.resultFormattingSync = function resultFormattingSync(test) {
test.expect(2);
test.expect(3);
var options = {
"files": [
"./test/atx_header_spacing.md",
@ -159,7 +173,20 @@ module.exports.resultFormattingSync = function resultFormattingSync(test) {
" Multiple spaces after hash on atx style header\n" +
"./test/first_header_bad_atx.md: 1: MD002" +
" First header should be a h1 header";
test.equal(actualMessage, expectedMessage, "Incorrect message.");
test.equal(actualMessage, expectedMessage, "Incorrect message (name).");
actualMessage = actualResult.toString(true);
expectedMessage =
"./test/atx_header_spacing.md: 3: first-header-h1" +
" First header should be a h1 header\n" +
"./test/atx_header_spacing.md: 1: no-missing-space-atx" +
" No space after hash on atx style header\n" +
"./test/atx_header_spacing.md: 3: no-multiple-space-atx" +
" Multiple spaces after hash on atx style header\n" +
"./test/atx_header_spacing.md: 5: no-multiple-space-atx" +
" Multiple spaces after hash on atx style header\n" +
"./test/first_header_bad_atx.md: 1: first-header-h1" +
" First header should be a h1 header";
test.equal(actualMessage, expectedMessage, "Incorrect message (alias).");
test.done();
};
@ -302,7 +329,7 @@ module.exports.disableRules = function disableRules(test) {
"MD002": false,
"default": true,
"MD019": false,
"MD041": false
"first-line-h1": false
}
};
markdownlint(options, function callback(err, actualResult) {
@ -328,7 +355,7 @@ module.exports.enableRules = function enableRules(test) {
"config": {
"MD002": true,
"default": false,
"MD019": true
"no-multiple-space-atx": true
}
};
markdownlint(options, function callback(err, actualResult) {
@ -357,7 +384,7 @@ module.exports.enableRulesMixedCase = function enableRulesMixedCase(test) {
"config": {
"Md002": true,
"DeFaUlT": false,
"Md019": true
"nO-mUlTiPlE-sPaCe-AtX": true
}
};
markdownlint(options, function callback(err, actualResult) {
@ -697,6 +724,23 @@ module.exports.ruleNamesUpperCase = function ruleNamesUpperCase(test) {
test.done();
};
module.exports.uniqueAliases = function uniqueAliases(test) {
test.expect(74);
var tags = [];
rules.forEach(function forRule(rule) {
Array.prototype.push.apply(tags, rule.tags);
});
var aliases = [];
rules.forEach(function forRule(rule) {
rule.aliases.forEach(function forAlias(alias) {
test.ok(tags.indexOf(alias) === -1, "Alias not unique in tags.");
test.ok(aliases.indexOf(alias) === -1, "Alias not unique in aliases.");
aliases.push(alias);
});
});
test.done();
};
module.exports.readme = function readme(test) {
test.expect(97);
var tagToRules = {};
@ -733,7 +777,8 @@ module.exports.readme = function readme(test) {
test.ok(rule,
"Missing rule implementation for " + token.content + ".");
if (rule) {
var expected = "**" + rule.name + "** - " + rule.desc;
var expected = "**" + rule.name + "** *" +
rule.aliases.join(", ") + "* - " + rule.desc;
test.equal(token.content, expected, "Rule mismatch.");
}
} else if (inTags) {
@ -755,7 +800,7 @@ module.exports.readme = function readme(test) {
};
module.exports.doc = function doc(test) {
test.expect(199);
test.expect(274);
fs.readFile("doc/Rules.md", shared.utf8Encoding,
function readFile(err, contents) {
test.ifError(err);
@ -763,11 +808,14 @@ module.exports.doc = function doc(test) {
var inHeading = false;
var rule = null;
var ruleHasTags = true;
var ruleHasAliases = true;
var ruleUsesParams = null;
var tagParameterRe = /, |: | /;
function testTagsAndParams() {
var tagAliasParameterRe = /, |: | /;
function testTagsAliasesParams() {
test.ok(ruleHasTags,
"Missing tags for rule " + (rule || {}).name + ".");
test.ok(ruleHasAliases,
"Missing aliases for rule " + (rule || {}).name + ".");
test.ok(!ruleUsesParams,
"Missing parameters for rule " + (rule || {}).name + ".");
}
@ -778,9 +826,9 @@ module.exports.doc = function doc(test) {
inHeading = false;
} else if (token.type === "inline") {
if (inHeading) {
testTagsAndParams();
testTagsAliasesParams();
rule = rulesLeft.shift();
ruleHasTags = false;
ruleHasTags = ruleHasAliases = false;
test.ok(rule,
"Missing rule implementation for " + token.content + ".");
test.equal(token.content, rule.name + " - " + rule.desc,
@ -793,13 +841,16 @@ module.exports.doc = function doc(test) {
});
}
} else if (/^Tags: /.test(token.content) && rule) {
var tags = token.content.split(tagParameterRe).slice(1);
test.deepEqual(tags, rule.tags,
"Tag mismatch for rule " + rule.name + ".");
test.deepEqual(token.content.split(tagAliasParameterRe).slice(1),
rule.tags, "Tag mismatch for rule " + rule.name + ".");
ruleHasTags = true;
} else if (/^Aliases: /.test(token.content) && rule) {
test.deepEqual(token.content.split(tagAliasParameterRe).slice(1),
rule.aliases, "Alias mismatch for rule " + rule.name + ".");
ruleHasAliases = true;
} else if (/^Parameters: /.test(token.content) && rule) {
var inDetails = false;
var parameters = token.content.split(tagParameterRe)
var parameters = token.content.split(tagAliasParameterRe)
.slice(1)
.filter(function forPart(part) {
inDetails = inDetails || (part[0] === "(");
@ -814,7 +865,7 @@ module.exports.doc = function doc(test) {
var ruleLeft = rulesLeft.shift();
test.ok(!ruleLeft,
"Missing rule documentation for " + (ruleLeft || {}).name + ".");
testTagsAndParams();
testTagsAliasesParams();
test.done();
});
};