From f24f98e14639773f7e1bd910fa8769ae91eee6ec Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 15 Feb 2018 21:35:58 -0800 Subject: [PATCH] Add support for using custom rules. --- .eslintrc.json | 2 +- README.md | 23 ++- doc/CustomRules.md | 1 + lib/markdownlint.js | 149 ++++++++++--------- test/custom-rules.md | 12 ++ test/markdownlint-test.js | 277 ++++++++++++++++++++++++++++++++++++ test/rules/blockquote.js | 22 +++ test/rules/every-n-lines.js | 23 +++ test/rules/letters-E-X.js | 28 ++++ test/rules/package.json | 13 ++ test/rules/rules.js | 18 +++ 11 files changed, 497 insertions(+), 71 deletions(-) create mode 100644 doc/CustomRules.md create mode 100644 test/custom-rules.md create mode 100644 test/rules/blockquote.js create mode 100644 test/rules/every-n-lines.js create mode 100644 test/rules/letters-E-X.js create mode 100644 test/rules/package.json create mode 100644 test/rules/rules.js diff --git a/.eslintrc.json b/.eslintrc.json index 75470021..4eb6928a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -63,7 +63,7 @@ "max-len": "error", "max-lines": "off", "max-nested-callbacks": "error", - "max-params": ["error", 7], + "max-params": ["error", 8], "max-statements": ["error", 20], "max-statements-per-line": "error", "multiline-comment-style": ["error", "separate-lines"], diff --git a/README.md b/README.md index a85f9389..625fc517 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,26 @@ Type: `Object` 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 Type: `Array` of `String` @@ -316,7 +336,8 @@ The default value: /^(---|\+\+\+)$[^]*?^\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 --- diff --git a/doc/CustomRules.md b/doc/CustomRules.md new file mode 100644 index 00000000..70644910 --- /dev/null +++ b/doc/CustomRules.md @@ -0,0 +1 @@ +# TBD diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 157ed948..1afca735 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -9,54 +9,58 @@ var rules = require("./rules"); var shared = require("./shared"); // Class for results with toString for pretty display -function Results() {} -Results.prototype.toString = function resultsToString(useAlias) { - var that = this; - var ruleNameToRule = null; - var results = []; - Object.keys(that).forEach(function forFile(file) { - var fileResults = that[file]; - if (Array.isArray(fileResults)) { - fileResults.forEach(function forResult(result) { - var ruleMoniker = result.ruleNames ? - result.ruleNames.join("/") : - (result.ruleName + "/" + result.ruleAlias); - results.push( - file + ": " + - result.lineNumber + ": " + - ruleMoniker + " " + - result.ruleDescription + - (result.errorDetail ? - " [" + result.errorDetail + "]" : - "") + - (result.errorContext ? - " [Context: \"" + result.errorContext + "\"]" : - "")); - }); - } else { - if (!ruleNameToRule) { - ruleNameToRule = {}; - rules.forEach(function forRule(rule) { - var ruleName = rule.names[0].toUpperCase(); - ruleNameToRule[ruleName] = rule; +function newResults(ruleSet) { + function Results() {} + Results.prototype.toString = function resultsToString(useAlias) { + var that = this; + var ruleNameToRule = null; + var results = []; + Object.keys(that).forEach(function forFile(file) { + var fileResults = that[file]; + if (Array.isArray(fileResults)) { + fileResults.forEach(function forResult(result) { + var ruleMoniker = result.ruleNames ? + result.ruleNames.join("/") : + (result.ruleName + "/" + result.ruleAlias); + results.push( + file + ": " + + result.lineNumber + ": " + + ruleMoniker + " " + + result.ruleDescription + + (result.errorDetail ? + " [" + result.errorDetail + "]" : + "") + + (result.errorContext ? + " [Context: \"" + result.errorContext + "\"]" : + "")); + }); + } else { + if (!ruleNameToRule) { + ruleNameToRule = {}; + ruleSet.forEach(function forRule(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]; - var ruleResults = fileResults[ruleName]; - ruleResults.forEach(function forLine(lineNumber) { - var result = - file + ": " + - lineNumber + ": " + - rule.names[useAlias ? 1 : 0] + " " + - rule.description; - results.push(result); - }); - }); - } - }); - return results.join("\n"); -}; + }); + return results.join("\n"); + }; + return new Results(); +} // Remove front matter (if present at beginning of content) function removeFrontMatter(content, frontMatter) { @@ -151,16 +155,16 @@ function mapAliasToRuleNames(ruleList) { return aliasToRuleNames; } -// Merge rules/tags and sanitize config -function mergeRulesAndSanitize(config, aliasToRuleNames) { +// Apply (and normalize) config +function getEffectiveConfig(ruleSet, config, aliasToRuleNames) { var defaultKey = Object.keys(config).filter(function forKey(key) { return key.toUpperCase() === "DEFAULT"; }); var ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]]; - var mergedRules = {}; - rules.forEach(function forRule(rule) { + var effectiveConfig = {}; + ruleSet.forEach(function forRule(rule) { var ruleName = rule.names[0].toUpperCase(); - mergedRules[ruleName] = ruleDefault; + effectiveConfig[ruleName] = ruleDefault; }); Object.keys(config).forEach(function forKey(key) { var value = config[key]; @@ -173,21 +177,22 @@ function mergeRulesAndSanitize(config, aliasToRuleNames) { } var keyUpper = key.toUpperCase(); (aliasToRuleNames[keyUpper] || []).forEach(function forRule(ruleName) { - mergedRules[ruleName] = value; + effectiveConfig[ruleName] = value; }); }); - return mergedRules; + return effectiveConfig; } // Create mapping of enabled rules per line function getEnabledRulesPerLineNumber( - lines, frontMatterLines, noInlineConfig, mergedRules, aliasToRuleNames) { + ruleSet, lines, frontMatterLines, noInlineConfig, + effectiveConfig, aliasToRuleNames) { var enabledRules = {}; var allRuleNames = []; - rules.forEach(function forRule(rule) { + ruleSet.forEach(function forRule(rule) { var ruleName = rule.names[0].toUpperCase(); allRuleNames.push(ruleName); - enabledRules[ruleName] = !!mergedRules[ruleName]; + enabledRules[ruleName] = !!effectiveConfig[ruleName]; }); function forMatch(match) { var enabled = match[1].toUpperCase() === "EN"; @@ -229,7 +234,7 @@ function uniqueFilterForSortedErrors(value, index, array) { // Lints a single string function lintContent( - content, config, frontMatter, noInlineConfig, resultVersion) { + ruleSet, content, config, frontMatter, noInlineConfig, resultVersion) { // Remove UTF-8 byte order marker (if present) if (content.charCodeAt(0) === 0xfeff) { content = content.slice(1); @@ -244,10 +249,11 @@ function lintContent( var tokens = md.parse(content, {}); var lines = content.split(shared.newLineRe); var tokenLists = annotateTokens(tokens, lines); - var aliasToRuleNames = mapAliasToRuleNames(rules); - var mergedRules = mergeRulesAndSanitize(config, aliasToRuleNames); + var aliasToRuleNames = mapAliasToRuleNames(ruleSet); + var effectiveConfig = getEffectiveConfig(ruleSet, config, aliasToRuleNames); var enabledRulesPerLineNumber = getEnabledRulesPerLineNumber( - lines, frontMatterLines, noInlineConfig, mergedRules, aliasToRuleNames); + ruleSet, lines, frontMatterLines, noInlineConfig, + effectiveConfig, aliasToRuleNames); // Create parameters for rules var params = { "tokens": tokens, @@ -257,10 +263,11 @@ function lintContent( }; // Run each rule var result = (resultVersion === 0) ? {} : []; - rules.forEach(function forRule(rule) { + ruleSet.forEach(function forRule(rule) { // Configure rule - var ruleName = rule.names[0].toUpperCase(); - params.config = mergedRules[ruleName]; + var ruleNameFriendly = rule.names[0]; + var ruleName = ruleNameFriendly.toUpperCase(); + params.config = effectiveConfig[ruleName]; var errors = []; function onError(errorInfo) { errors.push({ @@ -286,8 +293,8 @@ function lintContent( var errorObject = {}; errorObject.lineNumber = error.lineNumber; if (resultVersion === 1) { - errorObject.ruleName = rule.names[0]; - errorObject.ruleAlias = rule.names[1]; + errorObject.ruleName = ruleNameFriendly; + errorObject.ruleAlias = rule.names[1] || rule.names[0]; } else { errorObject.ruleNames = rule.names; } @@ -299,9 +306,9 @@ function lintContent( }); if (filteredErrors.length) { if (resultVersion === 0) { - result[ruleName] = filteredErrors; + result[ruleNameFriendly] = filteredErrors; } else { - result.push.apply(result, filteredErrors); + Array.prototype.push.apply(result, filteredErrors); } } } @@ -311,6 +318,7 @@ function lintContent( // Lints a single file function lintFile( + ruleSet, file, config, frontMatter, @@ -323,7 +331,7 @@ function lintFile( return callback(err); } var result = lintContent( - content, config, frontMatter, noInlineConfig, resultVersion); + ruleSet, content, config, frontMatter, noInlineConfig, resultVersion); callback(null, result); } // Make a/synchronous call to read file @@ -338,6 +346,7 @@ function lintInput(options, synchronous, callback) { // Normalize inputs options = options || {}; callback = callback || function noop() {}; + var ruleSet = rules.concat(options.customRules || []); var files = []; if (Array.isArray(options.files)) { files = options.files.slice(); @@ -351,12 +360,13 @@ function lintInput(options, synchronous, callback) { var noInlineConfig = !!options.noInlineConfig; var resultVersion = (options.resultVersion === undefined) ? 2 : options.resultVersion; - var results = new Results(); + var results = newResults(ruleSet); // Helper to lint the next file in the array function lintFilesArray() { var file = files.shift(); if (file) { lintFile( + ruleSet, file, config, frontMatter, @@ -378,6 +388,7 @@ function lintInput(options, synchronous, callback) { // Lint strings Object.keys(strings).forEach(function forKey(key) { var result = lintContent( + ruleSet, strings[key] || "", config, frontMatter, diff --git a/test/custom-rules.md b/test/custom-rules.md new file mode 100644 index 00000000..ecfc65d3 --- /dev/null +++ b/test/custom-rules.md @@ -0,0 +1,12 @@ +# Heading + +Sample text. + +Sample text. + +Sample text. + +Sample text. + + +> Blockquote diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 5eb161f8..05ee0601 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -8,6 +8,7 @@ var tv4 = require("tv4"); var markdownlint = require("../lib/markdownlint"); var shared = require("../lib/shared"); var rules = require("../lib/rules"); +var customRules = require("./rules"); var defaultConfig = require("./markdownlint-test-default-config.json"); var configSchema = require("../schema/markdownlint-config-schema.json"); @@ -890,6 +891,7 @@ module.exports.readmeHeaders = function readmeHeaders(test) { "## API", "### Linting", "#### options", + "##### options.customRules", "##### options.files", "##### options.strings", "##### options.config", @@ -1546,3 +1548,278 @@ module.exports.configBadChildJsonSync = function configBadChildJsonSync(test) { }, "Did not get exception for bad child JSON."); 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(); + }); +}; diff --git a/test/rules/blockquote.js b/test/rules/blockquote.js new file mode 100644 index 00000000..c95901ec --- /dev/null +++ b/test/rules/blockquote.js @@ -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 + }); + }); + } +}; diff --git a/test/rules/every-n-lines.js b/test/rules/every-n-lines.js new file mode 100644 index 00000000..3eff6a39 --- /dev/null +++ b/test/rules/every-n-lines.js @@ -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 + }); + } + }); + } +}; diff --git a/test/rules/letters-E-X.js b/test/rules/letters-E-X.js new file mode 100644 index 00000000..9f34023e --- /dev/null +++ b/test/rules/letters-E-X.js @@ -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 + }); + } + }); + }); + } +}; diff --git a/test/rules/package.json b/test/rules/package.json new file mode 100644 index 00000000..d486389d --- /dev/null +++ b/test/rules/package.json @@ -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 +} diff --git a/test/rules/rules.js b/test/rules/rules.js new file mode 100644 index 00000000..bbba7028 --- /dev/null +++ b/test/rules/rules.js @@ -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 +];