Add support for disabling/enabling rules within Markdown content (fixes #5).

This commit is contained in:
David Anson 2015-09-26 16:55:33 -07:00
parent 071bba88fc
commit 31141cc3ed
10 changed files with 192 additions and 42 deletions

View file

@ -104,6 +104,33 @@ See [Rules.md](doc/Rules.md) for more details.
* **url** - MD034
* **whitespace** - MD009, MD010, MD012, MD027, MD028, MD030, MD037, MD038, MD039
## Configuration
Rules can be enabled, disabled, and configured via `options.config` (described
below) to define the expected behavior for a set of inputs. To enable or disable
rules within a file, add one of these markers to the appropriate place (HTML
comments don't appear in the final markup):
* Disable all rules: `<!-- markdownlint-disable -->`
* Enable all rules: `<!-- markdownlint-enable -->`
* Disable one or more rules: `<!-- markdownlint-disable MD001 MD002 -->`
* Enable one or more rules: `<!-- markdownlint-enable MD001 MD002 -->`
For example:
```md
<!-- markdownlint-disable MD037 -->
deliberate space * in * emphasis
<!-- markdownlint-enable MD037 -->
```
Changes take effect starting with the line a comment is on, so the following
has no effect:
```md
space * in * emphasis <!-- markdownlint-disable --> <!-- markdownlint-enable -->
```
## API
Standard asynchronous interface:
@ -209,7 +236,8 @@ rule to `true` or `false` includes/excludes all rules by default. Enabling or
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.
to bottom with later values overriding earlier ones. Keys (including rule names,
tags, and `default`) are not case-sensitive.
Example:

View file

@ -10,7 +10,7 @@
var openFile = document.getElementById("openFile");
// Variables
var markdownit = window.markdownit();
var markdownit = window.markdownit({ "html": true });
var newLineRe = /\r\n|\r|\n/;
var rulesMd = "https://github.com/DavidAnson/markdownlint" +
"/blob/master/doc/Rules.md";

View file

@ -6,9 +6,11 @@ var rules = require("./rules");
var shared = require("./shared");
// Mappings from rule to description and tag to rules
var allRuleNames = [];
var ruleToDescription = {};
var tagUpperToRules = {};
rules.forEach(function forRule(rule) {
allRuleNames.push(rule.name);
ruleToDescription[rule.name] = rule.desc;
// The following is useful for updating README.md
// console.log("* **" + rule.name + "** - " + rule.desc);
@ -55,11 +57,11 @@ function uniqueFilterForSorted(value, index, array) {
}
// Lints a single string
function lintContent(content, config, frontMatter) {
function lintContent(content, config, frontMatter) { // eslint-disable-line
// Remove front matter (if present at beginning of content)
var frontMatterLines = 0;
if (frontMatter) {
var frontMatterMatch = frontMatter.exec(content);
var frontMatterMatch = content.match(frontMatter);
if (frontMatterMatch && !frontMatterMatch.index) {
var contentMatched = frontMatterMatch[0];
content = content.slice(contentMatched.length);
@ -93,12 +95,6 @@ function lintContent(content, config, frontMatter) {
}
tokenLists[token.type].push(token);
});
// Create parameters for rules
var params = {
"tokens": tokens,
"tokenLists": tokenLists,
"lines": lines
};
// Merge rules/tags and sanitize config
var defaultKey = Object.keys(config).filter(function forKey(key) {
return key.toUpperCase() === "DEFAULT";
@ -126,22 +122,64 @@ function lintContent(content, config, frontMatter) {
});
}
});
// Run each enabled rule
// Create mapping of enabled rules per line
var enabledRules = {};
rules.forEach(function forRule(rule) {
enabledRules[rule.name] = !!mergedRules[rule.name];
});
function forMatch(match) {
var enabled = match[1].toUpperCase() === "EN";
var items = match[2] ?
match[2].trim().toUpperCase().split(/\s+/) :
allRuleNames;
items.forEach(function forItem(nameUpper) {
if (ruleToDescription[nameUpper]) {
enabledRules[nameUpper] = enabled;
} else if (tagUpperToRules[nameUpper]) {
tagUpperToRules[nameUpper].forEach(function forRule(ruleName) {
enabledRules[ruleName] = enabled;
});
}
});
}
var enabledRulesPerLineNumber = [ null ];
lines.forEach(function forLine(line) {
var match = shared.inlineCommentRe.exec(line);
if (match) {
enabledRules = shared.clone(enabledRules);
while (match) {
forMatch(match);
match = shared.inlineCommentRe.exec(line);
}
}
enabledRulesPerLineNumber.push(enabledRules);
});
// Create parameters for rules
var params = {
"tokens": tokens,
"tokenLists": tokenLists,
"lines": lines
};
// Run each rule
var result = {};
rules.forEach(function forRule(rule) {
if (mergedRules[rule.name]) {
// Configure rule
params.options = mergedRules[rule.name];
var errors = [];
rule.func(params, errors);
// Record any errors
if (errors.length) {
errors.sort(numberComparison);
result[rule.name] = errors
.filter(uniqueFilterForSorted)
.map(function adjustLineNumbers(error) {
return error + frontMatterLines;
});
// Configure rule
params.options = mergedRules[rule.name];
var errors = [];
rule.func(params, errors);
// Record any errors (significant performance benefit from length check)
if (errors.length) {
errors.sort(numberComparison);
var filteredErrors = errors
.filter(uniqueFilterForSorted)
.filter(function removeDisabledRules(lineNumber) {
return enabledRulesPerLineNumber[lineNumber][rule.name];
})
.map(function adjustLineNumbers(error) {
return error + frontMatterLines;
});
if (filteredErrors.length) {
result[rule.name] = filteredErrors;
}
}
});

View file

@ -640,12 +640,13 @@ module.exports = [
"desc": "Inline HTML",
"tags": [ "html" ],
"func": function MD033(params, errors) {
filterTokens(params, "html_block", function forToken(token) {
errors.push(token.lineNumber);
});
forEachInlineChild(params, "html_inline", function forToken(token) {
errors.push(token.lineNumber);
});
function forToken(token) {
if (token.content.search(shared.inlineCommentRe) === -1) {
errors.push(token.lineNumber);
}
}
filterTokens(params, "html_block", forToken);
forEachInlineChild(params, "html_inline", forToken);
}
},

View file

@ -6,5 +6,23 @@ module.exports.newLineRe = /\r\n|\r|\n/;
// Regular expression for matching common front matter
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;
// readFile options for reading with the UTF-8 encoding
module.exports.utf8Encoding = { "encoding": "utf8" };
// Applies key/value pairs from src to dst, returning dst
function assign(dst, src) {
Object.keys(src).forEach(function forKey(key) {
dst[key] = src[key];
});
return dst;
}
module.exports.assign = assign;
// Clones the key/value pairs of obj, returning the clone
module.exports.clone = function clone(obj) {
return assign({}, obj);
};

View file

@ -27,8 +27,6 @@
"cpy": "^3.4.0",
"eslint": "^0.24.1",
"istanbul": "^0.3.17",
"lodash.assign": "^3.2.0",
"lodash.clone": "^3.0.2",
"nodeunit": "^0.9.1",
"q": "^1.4.0",
"rimraf": "^2.4.2",

View file

@ -0,0 +1,69 @@
# Header
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable-->
hard tab / space * in * emphasis / space ` in ` code
<!--MARKDOWNLINT-ENABLE -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable MD010-->
hard tab / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-ENABLE MD010 -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable MD010 MD038 -->
hard tab / space * in * emphasis {MD037} / space ` in ` code
<!-- MARKDOWNLINT-enable MD010 MD038 -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
before <!-- markdownlint-disable MD010 --> <!-- markdownlint-disable MD038 --> after
hard tab / space * in * emphasis {MD037} / space ` in ` code
before<!-- markdownlint-enable MD010 --><!-- markdownlint-enable MD038 -->after
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable hard_tab code -->
hard tab / space * in * emphasis {MD037} / space ` in ` code
<!-- markdownlint-enable whitespace -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
hard tab {MD010} <!-- markdownlint-disable --> <!-- markdownlint-enable -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
hard tab <!-- markdownlint-disable md010 -->
<!-- markdownlint-enable md010 -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-enable -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable -->
<!-- markdownlint-disable -->
hard tab / space * in * emphasis / space ` in ` code
<!-- markdownlint-enable -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
<!-- markdownlint-disable NotATag MD038 -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code
<!-- markdownlint-enable NotATag MD038 -->
hard tab {MD010} / space * in * emphasis {MD037} / space ` in ` code {MD038}
embedded <b>{MD033}</b> HTML

View file

@ -1,4 +0,0 @@
{
"default": true,
"MD003": false
}

View file

@ -1,3 +1,5 @@
<!-- markdownlint-disable MD003 -->
* list
* list

View file

@ -3,8 +3,6 @@
var fs = require("fs");
var path = require("path");
var md = require("markdown-it")();
var assign = require("lodash.assign");
var clone = require("lodash.clone");
var Q = require("q");
var markdownlint = require("../lib/markdownlint");
var shared = require("../lib/shared");
@ -26,11 +24,11 @@ function createTestForFile(file) {
});
},
function noConfigFile() {
return null;
return {};
})
.then(
function lintWithConfig(config) {
var mergedConfig = assign(clone(defaultConfig), config);
var mergedConfig = shared.assign(shared.clone(defaultConfig), config);
return Q.nfcall(markdownlint, {
"files": [ file ],
"config": mergedConfig
@ -416,7 +414,8 @@ module.exports.enableTag = function enableTag(test) {
],
"config": {
"default": false,
"spaces": true
"spaces": true,
"notatag": true
}
};
markdownlint(options, function callback(err, actualResult) {
@ -442,7 +441,8 @@ module.exports.enableTagMixedCase = function enableTagMixedCase(test) {
],
"config": {
"DeFaUlT": false,
"SpAcEs": true
"SpAcEs": true,
"NoTaTaG": true
}
};
markdownlint(options, function callback(err, actualResult) {