Add support for using custom rules.

This commit is contained in:
David Anson 2018-02-15 21:35:58 -08:00
parent 4619a8c824
commit f24f98e146
11 changed files with 497 additions and 71 deletions

View file

@ -63,7 +63,7 @@
"max-len": "error", "max-len": "error",
"max-lines": "off", "max-lines": "off",
"max-nested-callbacks": "error", "max-nested-callbacks": "error",
"max-params": ["error", 7], "max-params": ["error", 8],
"max-statements": ["error", 20], "max-statements": ["error", 20],
"max-statements-per-line": "error", "max-statements-per-line": "error",
"multiline-comment-style": ["error", "separate-lines"], "multiline-comment-style": ["error", "separate-lines"],

View file

@ -184,6 +184,26 @@ Type: `Object`
Configures the function. Configures the function.
##### options.customRules
Type: `Array` of `Object`
List of custom rules to include with the default rule set for linting.
Each array element should define a rule. Rules are typically exported by another
package, but can be defined inline.
Example:
```js
var extraRules = require("extraRules");
var options = {
"customRules": [ extraRules.one, extraRules.two ]
};
```
See [CustomRules.md](doc/CustomRules.md) for details about authoring custom rules.
##### options.files ##### options.files
Type: `Array` of `String` Type: `Array` of `String`
@ -316,7 +336,8 @@ The default value:
/^(---|\+\+\+)$[^]*?^\1$(\r\n|\r|\n)/m /^(---|\+\+\+)$[^]*?^\1$(\r\n|\r|\n)/m
``` ```
Ignores [YAML](https://en.wikipedia.org/wiki/YAML) and [TOML](https://en.wikipedia.org/wiki/TOML) such as: Ignores [YAML](https://en.wikipedia.org/wiki/YAML) and
[TOML](https://en.wikipedia.org/wiki/TOML) such as:
```text ```text
--- ---

1
doc/CustomRules.md Normal file
View file

@ -0,0 +1 @@
# TBD

View file

@ -9,54 +9,58 @@ var rules = require("./rules");
var shared = require("./shared"); var shared = require("./shared");
// Class for results with toString for pretty display // Class for results with toString for pretty display
function Results() {} function newResults(ruleSet) {
Results.prototype.toString = function resultsToString(useAlias) { function Results() {}
var that = this; Results.prototype.toString = function resultsToString(useAlias) {
var ruleNameToRule = null; var that = this;
var results = []; var ruleNameToRule = null;
Object.keys(that).forEach(function forFile(file) { var results = [];
var fileResults = that[file]; Object.keys(that).forEach(function forFile(file) {
if (Array.isArray(fileResults)) { var fileResults = that[file];
fileResults.forEach(function forResult(result) { if (Array.isArray(fileResults)) {
var ruleMoniker = result.ruleNames ? fileResults.forEach(function forResult(result) {
result.ruleNames.join("/") : var ruleMoniker = result.ruleNames ?
(result.ruleName + "/" + result.ruleAlias); result.ruleNames.join("/") :
results.push( (result.ruleName + "/" + result.ruleAlias);
file + ": " + results.push(
result.lineNumber + ": " + file + ": " +
ruleMoniker + " " + result.lineNumber + ": " +
result.ruleDescription + ruleMoniker + " " +
(result.errorDetail ? result.ruleDescription +
" [" + result.errorDetail + "]" : (result.errorDetail ?
"") + " [" + result.errorDetail + "]" :
(result.errorContext ? "") +
" [Context: \"" + result.errorContext + "\"]" : (result.errorContext ?
"")); " [Context: \"" + result.errorContext + "\"]" :
}); ""));
} else { });
if (!ruleNameToRule) { } else {
ruleNameToRule = {}; if (!ruleNameToRule) {
rules.forEach(function forRule(rule) { ruleNameToRule = {};
var ruleName = rule.names[0].toUpperCase(); ruleSet.forEach(function forRule(rule) {
ruleNameToRule[ruleName] = rule; var ruleName = rule.names[0].toUpperCase();
ruleNameToRule[ruleName] = rule;
});
}
Object.keys(fileResults).forEach(function forRule(ruleName) {
var rule = ruleNameToRule[ruleName.toUpperCase()];
var ruleResults = fileResults[ruleName];
ruleResults.forEach(function forLine(lineNumber) {
var nameIndex = Math.min(useAlias ? 1 : 0, rule.names.length - 1);
var result =
file + ": " +
lineNumber + ": " +
rule.names[nameIndex] + " " +
rule.description;
results.push(result);
});
}); });
} }
Object.keys(fileResults).forEach(function forRule(ruleName) { });
var rule = ruleNameToRule[ruleName]; return results.join("\n");
var ruleResults = fileResults[ruleName]; };
ruleResults.forEach(function forLine(lineNumber) { return new Results();
var result = }
file + ": " +
lineNumber + ": " +
rule.names[useAlias ? 1 : 0] + " " +
rule.description;
results.push(result);
});
});
}
});
return results.join("\n");
};
// Remove front matter (if present at beginning of content) // Remove front matter (if present at beginning of content)
function removeFrontMatter(content, frontMatter) { function removeFrontMatter(content, frontMatter) {
@ -151,16 +155,16 @@ function mapAliasToRuleNames(ruleList) {
return aliasToRuleNames; return aliasToRuleNames;
} }
// Merge rules/tags and sanitize config // Apply (and normalize) config
function mergeRulesAndSanitize(config, aliasToRuleNames) { function getEffectiveConfig(ruleSet, config, aliasToRuleNames) {
var defaultKey = Object.keys(config).filter(function forKey(key) { var defaultKey = Object.keys(config).filter(function forKey(key) {
return key.toUpperCase() === "DEFAULT"; return key.toUpperCase() === "DEFAULT";
}); });
var ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]]; var ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]];
var mergedRules = {}; var effectiveConfig = {};
rules.forEach(function forRule(rule) { ruleSet.forEach(function forRule(rule) {
var ruleName = rule.names[0].toUpperCase(); var ruleName = rule.names[0].toUpperCase();
mergedRules[ruleName] = ruleDefault; effectiveConfig[ruleName] = ruleDefault;
}); });
Object.keys(config).forEach(function forKey(key) { Object.keys(config).forEach(function forKey(key) {
var value = config[key]; var value = config[key];
@ -173,21 +177,22 @@ function mergeRulesAndSanitize(config, aliasToRuleNames) {
} }
var keyUpper = key.toUpperCase(); var keyUpper = key.toUpperCase();
(aliasToRuleNames[keyUpper] || []).forEach(function forRule(ruleName) { (aliasToRuleNames[keyUpper] || []).forEach(function forRule(ruleName) {
mergedRules[ruleName] = value; effectiveConfig[ruleName] = value;
}); });
}); });
return mergedRules; return effectiveConfig;
} }
// Create mapping of enabled rules per line // Create mapping of enabled rules per line
function getEnabledRulesPerLineNumber( function getEnabledRulesPerLineNumber(
lines, frontMatterLines, noInlineConfig, mergedRules, aliasToRuleNames) { ruleSet, lines, frontMatterLines, noInlineConfig,
effectiveConfig, aliasToRuleNames) {
var enabledRules = {}; var enabledRules = {};
var allRuleNames = []; var allRuleNames = [];
rules.forEach(function forRule(rule) { ruleSet.forEach(function forRule(rule) {
var ruleName = rule.names[0].toUpperCase(); var ruleName = rule.names[0].toUpperCase();
allRuleNames.push(ruleName); allRuleNames.push(ruleName);
enabledRules[ruleName] = !!mergedRules[ruleName]; enabledRules[ruleName] = !!effectiveConfig[ruleName];
}); });
function forMatch(match) { function forMatch(match) {
var enabled = match[1].toUpperCase() === "EN"; var enabled = match[1].toUpperCase() === "EN";
@ -229,7 +234,7 @@ function uniqueFilterForSortedErrors(value, index, array) {
// Lints a single string // Lints a single string
function lintContent( function lintContent(
content, config, frontMatter, noInlineConfig, resultVersion) { ruleSet, content, config, frontMatter, noInlineConfig, resultVersion) {
// Remove UTF-8 byte order marker (if present) // Remove UTF-8 byte order marker (if present)
if (content.charCodeAt(0) === 0xfeff) { if (content.charCodeAt(0) === 0xfeff) {
content = content.slice(1); content = content.slice(1);
@ -244,10 +249,11 @@ function lintContent(
var tokens = md.parse(content, {}); var tokens = md.parse(content, {});
var lines = content.split(shared.newLineRe); var lines = content.split(shared.newLineRe);
var tokenLists = annotateTokens(tokens, lines); var tokenLists = annotateTokens(tokens, lines);
var aliasToRuleNames = mapAliasToRuleNames(rules); var aliasToRuleNames = mapAliasToRuleNames(ruleSet);
var mergedRules = mergeRulesAndSanitize(config, aliasToRuleNames); var effectiveConfig = getEffectiveConfig(ruleSet, config, aliasToRuleNames);
var enabledRulesPerLineNumber = getEnabledRulesPerLineNumber( var enabledRulesPerLineNumber = getEnabledRulesPerLineNumber(
lines, frontMatterLines, noInlineConfig, mergedRules, aliasToRuleNames); ruleSet, lines, frontMatterLines, noInlineConfig,
effectiveConfig, aliasToRuleNames);
// Create parameters for rules // Create parameters for rules
var params = { var params = {
"tokens": tokens, "tokens": tokens,
@ -257,10 +263,11 @@ function lintContent(
}; };
// Run each rule // Run each rule
var result = (resultVersion === 0) ? {} : []; var result = (resultVersion === 0) ? {} : [];
rules.forEach(function forRule(rule) { ruleSet.forEach(function forRule(rule) {
// Configure rule // Configure rule
var ruleName = rule.names[0].toUpperCase(); var ruleNameFriendly = rule.names[0];
params.config = mergedRules[ruleName]; var ruleName = ruleNameFriendly.toUpperCase();
params.config = effectiveConfig[ruleName];
var errors = []; var errors = [];
function onError(errorInfo) { function onError(errorInfo) {
errors.push({ errors.push({
@ -286,8 +293,8 @@ function lintContent(
var errorObject = {}; var errorObject = {};
errorObject.lineNumber = error.lineNumber; errorObject.lineNumber = error.lineNumber;
if (resultVersion === 1) { if (resultVersion === 1) {
errorObject.ruleName = rule.names[0]; errorObject.ruleName = ruleNameFriendly;
errorObject.ruleAlias = rule.names[1]; errorObject.ruleAlias = rule.names[1] || rule.names[0];
} else { } else {
errorObject.ruleNames = rule.names; errorObject.ruleNames = rule.names;
} }
@ -299,9 +306,9 @@ function lintContent(
}); });
if (filteredErrors.length) { if (filteredErrors.length) {
if (resultVersion === 0) { if (resultVersion === 0) {
result[ruleName] = filteredErrors; result[ruleNameFriendly] = filteredErrors;
} else { } else {
result.push.apply(result, filteredErrors); Array.prototype.push.apply(result, filteredErrors);
} }
} }
} }
@ -311,6 +318,7 @@ function lintContent(
// Lints a single file // Lints a single file
function lintFile( function lintFile(
ruleSet,
file, file,
config, config,
frontMatter, frontMatter,
@ -323,7 +331,7 @@ function lintFile(
return callback(err); return callback(err);
} }
var result = lintContent( var result = lintContent(
content, config, frontMatter, noInlineConfig, resultVersion); ruleSet, content, config, frontMatter, noInlineConfig, resultVersion);
callback(null, result); callback(null, result);
} }
// Make a/synchronous call to read file // Make a/synchronous call to read file
@ -338,6 +346,7 @@ function lintInput(options, synchronous, callback) {
// Normalize inputs // Normalize inputs
options = options || {}; options = options || {};
callback = callback || function noop() {}; callback = callback || function noop() {};
var ruleSet = rules.concat(options.customRules || []);
var files = []; var files = [];
if (Array.isArray(options.files)) { if (Array.isArray(options.files)) {
files = options.files.slice(); files = options.files.slice();
@ -351,12 +360,13 @@ function lintInput(options, synchronous, callback) {
var noInlineConfig = !!options.noInlineConfig; var noInlineConfig = !!options.noInlineConfig;
var resultVersion = (options.resultVersion === undefined) ? var resultVersion = (options.resultVersion === undefined) ?
2 : options.resultVersion; 2 : options.resultVersion;
var results = new Results(); var results = newResults(ruleSet);
// Helper to lint the next file in the array // Helper to lint the next file in the array
function lintFilesArray() { function lintFilesArray() {
var file = files.shift(); var file = files.shift();
if (file) { if (file) {
lintFile( lintFile(
ruleSet,
file, file,
config, config,
frontMatter, frontMatter,
@ -378,6 +388,7 @@ function lintInput(options, synchronous, callback) {
// Lint strings // Lint strings
Object.keys(strings).forEach(function forKey(key) { Object.keys(strings).forEach(function forKey(key) {
var result = lintContent( var result = lintContent(
ruleSet,
strings[key] || "", strings[key] || "",
config, config,
frontMatter, frontMatter,

12
test/custom-rules.md Normal file
View file

@ -0,0 +1,12 @@
# Heading
Sample text.
<!-- markdownlint-disable letters-e-x -->
Sample text.
<!-- markdownlint-enable LETTERS-E-X -->
Sample text.
<!-- markdownlint-disable TeSt -->
Sample text.
<!-- markdownlint-enable TEST -->
> Blockquote

View file

@ -8,6 +8,7 @@ var tv4 = require("tv4");
var markdownlint = require("../lib/markdownlint"); var markdownlint = require("../lib/markdownlint");
var shared = require("../lib/shared"); var shared = require("../lib/shared");
var rules = require("../lib/rules"); var rules = require("../lib/rules");
var customRules = require("./rules");
var defaultConfig = require("./markdownlint-test-default-config.json"); var defaultConfig = require("./markdownlint-test-default-config.json");
var configSchema = require("../schema/markdownlint-config-schema.json"); var configSchema = require("../schema/markdownlint-config-schema.json");
@ -890,6 +891,7 @@ module.exports.readmeHeaders = function readmeHeaders(test) {
"## API", "## API",
"### Linting", "### Linting",
"#### options", "#### options",
"##### options.customRules",
"##### options.files", "##### options.files",
"##### options.strings", "##### options.strings",
"##### options.config", "##### options.config",
@ -1546,3 +1548,278 @@ module.exports.configBadChildJsonSync = function configBadChildJsonSync(test) {
}, "Did not get exception for bad child JSON."); }, "Did not get exception for bad child JSON.");
test.done(); test.done();
}; };
module.exports.customRulesV0 = function customRulesV0(test) {
test.expect(4);
var customRulesMd = "./test/custom-rules.md";
var options = {
"customRules": customRules.all,
"files": [ customRulesMd ],
"resultVersion": 0
};
markdownlint(options, function callback(err, actualResult) {
test.ifError(err);
var expectedResult = {};
expectedResult[customRulesMd] = {
"blockquote": [ 12 ],
"every-n-lines": [ 2, 4, 6, 10, 12 ],
"letters-E-X": [ 3, 7 ]
};
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
var actualMessage = actualResult.toString();
var expectedMessage =
"./test/custom-rules.md: 12: blockquote" +
" Rule that reports an error for blockquotes\n" +
"./test/custom-rules.md: 2: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 4: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 6: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 10: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 12: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 3: letters-E-X" +
" Rule that reports an error for lines with the letters 'EX'\n" +
"./test/custom-rules.md: 7: letters-E-X" +
" Rule that reports an error for lines with the letters 'EX'";
test.equal(actualMessage, expectedMessage, "Incorrect message (name).");
actualMessage = actualResult.toString(true);
expectedMessage =
"./test/custom-rules.md: 12: blockquote" +
" Rule that reports an error for blockquotes\n" +
"./test/custom-rules.md: 2: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 4: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 6: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 10: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 12: every-n-lines" +
" Rule that reports an error every N lines\n" +
"./test/custom-rules.md: 3: letter-E-letter-X" +
" Rule that reports an error for lines with the letters 'EX'\n" +
"./test/custom-rules.md: 7: letter-E-letter-X" +
" Rule that reports an error for lines with the letters 'EX'";
test.equal(actualMessage, expectedMessage, "Incorrect message (alias).");
test.done();
});
};
module.exports.customRulesV1 = function customRulesV1(test) {
test.expect(3);
var customRulesMd = "./test/custom-rules.md";
var options = {
"customRules": customRules.all,
"files": [ customRulesMd ],
"resultVersion": 1
};
markdownlint(options, function callback(err, actualResult) {
test.ifError(err);
var expectedResult = {};
expectedResult[customRulesMd] = [
{ "lineNumber": 12,
"ruleName": "blockquote",
"ruleAlias": "blockquote",
"ruleDescription": "Rule that reports an error for blockquotes",
"errorDetail": "Blockquote spans 1 line(s).",
"errorContext": "> Block",
"errorRange": null },
{ "lineNumber": 2,
"ruleName": "every-n-lines",
"ruleAlias": "every-n-lines",
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 2",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 4,
"ruleName": "every-n-lines",
"ruleAlias": "every-n-lines",
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 4",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 6,
"ruleName": "every-n-lines",
"ruleAlias": "every-n-lines",
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 6",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 10,
"ruleName": "every-n-lines",
"ruleAlias": "every-n-lines",
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 10",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 12,
"ruleName": "every-n-lines",
"ruleAlias": "every-n-lines",
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 12",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 3,
"ruleName": "letters-E-X",
"ruleAlias": "letter-E-letter-X",
"ruleDescription":
"Rule that reports an error for lines with the letters 'EX'",
"errorDetail": null,
"errorContext": "text",
"errorRange": null },
{ "lineNumber": 7,
"ruleName": "letters-E-X",
"ruleAlias": "letter-E-letter-X",
"ruleDescription":
"Rule that reports an error for lines with the letters 'EX'",
"errorDetail": null,
"errorContext": "text",
"errorRange": null }
];
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
var actualMessage = actualResult.toString();
var expectedMessage =
"./test/custom-rules.md: 12: blockquote/blockquote" +
" Rule that reports an error for blockquotes" +
" [Blockquote spans 1 line(s).] [Context: \"> Block\"]\n" +
"./test/custom-rules.md: 2: every-n-lines/every-n-lines" +
" Rule that reports an error every N lines [Line number 2]\n" +
"./test/custom-rules.md: 4: every-n-lines/every-n-lines" +
" Rule that reports an error every N lines [Line number 4]\n" +
"./test/custom-rules.md: 6: every-n-lines/every-n-lines" +
" Rule that reports an error every N lines [Line number 6]\n" +
"./test/custom-rules.md: 10: every-n-lines/every-n-lines" +
" Rule that reports an error every N lines [Line number 10]\n" +
"./test/custom-rules.md: 12: every-n-lines/every-n-lines" +
" Rule that reports an error every N lines [Line number 12]\n" +
"./test/custom-rules.md: 3: letters-E-X/letter-E-letter-X" +
" Rule that reports an error for lines with the letters 'EX'" +
" [Context: \"text\"]\n" +
"./test/custom-rules.md: 7: letters-E-X/letter-E-letter-X" +
" Rule that reports an error for lines with the letters 'EX'" +
" [Context: \"text\"]";
test.equal(actualMessage, expectedMessage, "Incorrect message.");
test.done();
});
};
module.exports.customRulesV2 = function customRulesV2(test) {
test.expect(3);
var customRulesMd = "./test/custom-rules.md";
var options = {
"customRules": customRules.all,
"files": [ customRulesMd ],
"resultVersion": 2
};
markdownlint(options, function callback(err, actualResult) {
test.ifError(err);
var expectedResult = {};
expectedResult[customRulesMd] = [
{ "lineNumber": 12,
"ruleNames": [ "blockquote" ],
"ruleDescription": "Rule that reports an error for blockquotes",
"errorDetail": "Blockquote spans 1 line(s).",
"errorContext": "> Block",
"errorRange": null },
{ "lineNumber": 2,
"ruleNames": [ "every-n-lines" ],
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 2",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 4,
"ruleNames": [ "every-n-lines" ],
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 4",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 6,
"ruleNames": [ "every-n-lines" ],
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 6",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 10,
"ruleNames": [ "every-n-lines" ],
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 10",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 12,
"ruleNames": [ "every-n-lines" ],
"ruleDescription": "Rule that reports an error every N lines",
"errorDetail": "Line number 12",
"errorContext": null,
"errorRange": null },
{ "lineNumber": 3,
"ruleNames": [ "letters-E-X", "letter-E-letter-X", "contains-ex" ],
"ruleDescription":
"Rule that reports an error for lines with the letters 'EX'",
"errorDetail": null,
"errorContext": "text",
"errorRange": null },
{ "lineNumber": 7,
"ruleNames": [ "letters-E-X", "letter-E-letter-X", "contains-ex" ],
"ruleDescription":
"Rule that reports an error for lines with the letters 'EX'",
"errorDetail": null,
"errorContext": "text",
"errorRange": null }
];
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
var actualMessage = actualResult.toString();
var expectedMessage =
"./test/custom-rules.md: 12: blockquote" +
" Rule that reports an error for blockquotes" +
" [Blockquote spans 1 line(s).] [Context: \"> Block\"]\n" +
"./test/custom-rules.md: 2: every-n-lines" +
" Rule that reports an error every N lines [Line number 2]\n" +
"./test/custom-rules.md: 4: every-n-lines" +
" Rule that reports an error every N lines [Line number 4]\n" +
"./test/custom-rules.md: 6: every-n-lines" +
" Rule that reports an error every N lines [Line number 6]\n" +
"./test/custom-rules.md: 10: every-n-lines" +
" Rule that reports an error every N lines [Line number 10]\n" +
"./test/custom-rules.md: 12: every-n-lines" +
" Rule that reports an error every N lines [Line number 12]\n" +
"./test/custom-rules.md: 3: letters-E-X/letter-E-letter-X/contains-ex" +
" Rule that reports an error for lines with the letters 'EX'" +
" [Context: \"text\"]\n" +
"./test/custom-rules.md: 7: letters-E-X/letter-E-letter-X/contains-ex" +
" Rule that reports an error for lines with the letters 'EX'" +
" [Context: \"text\"]";
test.equal(actualMessage, expectedMessage, "Incorrect message.");
test.done();
});
};
module.exports.customRulesConfig = function customRulesConfig(test) {
test.expect(2);
var customRulesMd = "./test/custom-rules.md";
var options = {
"customRules": customRules.all,
"files": [ customRulesMd ],
"config": {
"blockquote": true,
"every-n-lines": {
"n": 3
},
"letters-e-x": false
},
"resultVersion": 0
};
markdownlint(options, function callback(err, actualResult) {
test.ifError(err);
var expectedResult = {};
expectedResult[customRulesMd] = {
"blockquote": [ 12 ],
"every-n-lines": [ 3, 6, 12 ],
"letters-E-X": [ 7 ]
};
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
test.done();
});
};

22
test/rules/blockquote.js Normal file
View file

@ -0,0 +1,22 @@
// @ts-check
"use strict";
module.exports = {
"names": [ "blockquote" ],
"description": "Rule that reports an error for blockquotes",
"tags": [ "test" ],
"function": function rule(params, onError) {
params.tokens.filter(function filterToken(token) {
return token.type === "blockquote_open";
}).forEach(function forToken(blockquote) {
var lines = blockquote.map[1] - blockquote.map[0];
onError({
"lineNumber": blockquote.lineNumber,
"detail": "Blockquote spans " + lines + " line(s).",
"context": blockquote.line.substr(0, 7),
"range": null
});
});
}
};

View file

@ -0,0 +1,23 @@
// @ts-check
"use strict";
module.exports = {
"names": [ "every-n-lines" ],
"description": "Rule that reports an error every N lines",
"tags": [ "test" ],
"function": function rule(params, onError) {
var n = params.config.n || 2;
params.lines.forEach(function forLine(line, lineIndex) {
var lineNumber = lineIndex + 1;
if ((lineNumber % n) === 0) {
onError({
"lineNumber": lineNumber,
"detail": "Line number " + lineNumber,
"context": null,
"range": null
});
}
});
}
};

28
test/rules/letters-E-X.js Normal file
View file

@ -0,0 +1,28 @@
// @ts-check
"use strict";
module.exports = {
"names": [ "letters-E-X", "letter-E-letter-X", "contains-ex" ],
"description": "Rule that reports an error for lines with the letters 'EX'",
"tags": [ "test" ],
"function": function rule(params, onError) {
params.tokens.filter(function filterToken(token) {
return token.type === "inline";
}).forEach(function forToken(inline) {
inline.children.filter(function filterChild(child) {
return child.type === "text";
}).forEach(function forChild(text) {
var index = text.content.toLowerCase().indexOf("ex");
if (index !== -1) {
onError({
"lineNumber": text.lineNumber,
"detail": null,
"context": text.content.substr(index - 1, 4),
"range": null
});
}
});
});
}
};

13
test/rules/package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "markdownlint-rules-test",
"version": "0.0.1",
"description": "Package of markdownlint custom rules used for testing",
"main": "rules.js",
"author": "David Anson (https://dlaa.me/)",
"homepage": "https://github.com/DavidAnson/markdownlint",
"license": "MIT",
"keywords": [
"markdownlint-rules"
],
"private": true
}

18
test/rules/rules.js Normal file
View file

@ -0,0 +1,18 @@
// @ts-check
"use strict";
var blockquote = require("./blockquote");
module.exports.blockquote = blockquote;
var everyNLines = require("./every-n-lines");
module.exports.everyNLines = everyNLines;
var lettersEX = require("./letters-E-X");
module.exports.lettersEX = lettersEX;
module.exports.all = [
blockquote,
everyNLines,
lettersEX
];