Expose shared.js helper code for custom rule authors (fixes #134).

This commit is contained in:
David Anson 2019-04-13 11:18:57 -07:00
parent f614f3e1ce
commit 7e980401b8
52 changed files with 283 additions and 184 deletions

View file

@ -53,6 +53,8 @@ A rule is implemented as an `Object` with four required properties:
- `context` is an optional `String` with relevant text surrounding the error location.
- `range` is an optional `Array` with two `Number` values identifying the 1-based column and length of the error.
The collection of helper functions shared by the built-in rules is available for use by custom rules in the [markdownlint-rule-helpers package](https://www.npmjs.com/package/markdownlint-rule-helpers).
## Examples
- [Simple rules used by the project's test cases](../test/rules)

21
helpers/LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2019 David Anson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

50
helpers/README.md Normal file
View file

@ -0,0 +1,50 @@
# markdownlint-rule-helpers
> A collection of `markdownlint` helper functions for custom rules
## Overview
The [Markdown](https://en.wikipedia.org/wiki/Markdown) linter
[`markdownlint`](https://github.com/DavidAnson/markdownlint) offers a variety of built-in validation
[rules](https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md) and supports the
creation of [custom rules](https://github.com/DavidAnson/markdownlint/blob/master/doc/CustomRules.md).
The internal rules share various helper functions; this package exposes those for reuse by custom rules.
## API
_Undocumented_ - This package exports the internal functions as-is. The APIs were not originally meant
to be public, are not officially supported, and may change from release to release. There are brief
descriptive comments above each function, but no [JSDoc](https://en.m.wikipedia.org/wiki/JSDoc)
annotations. That said, some of what's here will be useful to custom rule authors and may avoid
duplicating code.
## Example
```js
const { forEachLine, getLineMetadata } = require("markdownlint-rule-helpers");
module.exports = {
"names": [ "every-n-lines" ],
"description": "Rule that reports an error every N lines",
"tags": [ "test" ],
"function": (params, onError) => {
const n = params.config.n || 2;
forEachLine(getLineMetadata(params), (line, lineIndex) => {
const lineNumber = lineIndex + 1;
if ((lineNumber % n) === 0) {
onError({
"lineNumber": lineNumber,
"detail": "Line number " + lineNumber
});
}
});
}
};
```
See also: [`markdownlint` built-in rule implementations](https://github.com/DavidAnson/markdownlint/tree/master/lib).
## Tests
_None_ - The entire body of code is tested to 100% coverage by the core `markdownlint` project,
so there are no additional tests here.

18
helpers/package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name": "markdownlint-rule-helpers",
"version": "0.1.0",
"description": "A collection of markdownlint helper functions for custom rules",
"main": "helpers.js",
"author": "David Anson (https://dlaa.me/)",
"license": "MIT",
"homepage": "https://github.com/DavidAnson/markdownlint",
"repository": {
"type": "git",
"url": "https://github.com/DavidAnson/markdownlint.git"
},
"bugs": "https://github.com/DavidAnson/markdownlint/issues",
"keywords": [
"markdownlint",
"markdownlint-rule"
]
}

View file

@ -7,7 +7,7 @@ const path = require("path");
const { URL } = require("url");
const markdownIt = require("markdown-it");
const rules = require("./rules");
const shared = require("./shared");
const helpers = require("../helpers");
const cache = require("./cache");
const deprecatedRuleNames = [ "MD002" ];
@ -31,7 +31,7 @@ function validateRuleList(ruleList) {
const value = rule[property];
if (!result &&
(!value || !Array.isArray(value) || (value.length === 0) ||
!value.every(shared.isString) || value.some(shared.isEmptyString))) {
!value.every(helpers.isString) || value.some(helpers.isEmptyString))) {
result = newError(property);
}
});
@ -134,7 +134,7 @@ function removeFrontMatter(content, frontMatter) {
if (frontMatterMatch && !frontMatterMatch.index) {
const contentMatched = frontMatterMatch[0];
content = content.slice(contentMatched.length);
frontMatterLines = contentMatched.split(shared.newLineRe);
frontMatterLines = contentMatched.split(helpers.newLineRe);
if (frontMatterLines.length &&
(frontMatterLines[frontMatterLines.length - 1] === "")) {
frontMatterLines.length--;
@ -269,12 +269,12 @@ function getEnabledRulesPerLineNumber(
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
lines.forEach(function forLine(line) {
if (!noInlineConfig) {
let match = shared.inlineCommentRe.exec(line);
let match = helpers.inlineCommentRe.exec(line);
if (match) {
enabledRules = shared.clone(enabledRules);
enabledRules = helpers.clone(enabledRules);
while (match) {
forMatch(match);
match = shared.inlineCommentRe.exec(line);
match = helpers.inlineCommentRe.exec(line);
}
}
}
@ -310,10 +310,10 @@ function lintContent(
const removeFrontMatterResult = removeFrontMatter(content, frontMatter);
const frontMatterLines = removeFrontMatterResult.frontMatterLines;
// Ignore the content of HTML comments
content = shared.clearHtmlCommentText(removeFrontMatterResult.content);
content = helpers.clearHtmlCommentText(removeFrontMatterResult.content);
// Parse content into tokens and lines
const tokens = md.parse(content, {});
const lines = content.split(shared.newLineRe);
const lines = content.split(helpers.newLineRe);
annotateTokens(tokens, lines);
const aliasToRuleNames = mapAliasToRuleNames(ruleList);
const effectiveConfig =
@ -328,8 +328,8 @@ function lintContent(
lines,
frontMatterLines
};
cache.lineMetadata(shared.getLineMetadata(params));
cache.flattenedLists(shared.flattenLists(params));
cache.lineMetadata(helpers.getLineMetadata(params));
cache.flattenedLists(helpers.flattenLists(params));
// Function to run for each rule
const result = (resultVersion === 0) ? {} : [];
function forRule(rule) {
@ -345,22 +345,22 @@ function lintContent(
function onError(errorInfo) {
if (!errorInfo ||
!errorInfo.lineNumber ||
!shared.isNumber(errorInfo.lineNumber)) {
!helpers.isNumber(errorInfo.lineNumber)) {
throwError("lineNumber");
}
if (errorInfo.detail &&
!shared.isString(errorInfo.detail)) {
!helpers.isString(errorInfo.detail)) {
throwError("detail");
}
if (errorInfo.context &&
!shared.isString(errorInfo.context)) {
!helpers.isString(errorInfo.context)) {
throwError("context");
}
if (errorInfo.range &&
(!Array.isArray(errorInfo.range) ||
(errorInfo.range.length !== 2) ||
!shared.isNumber(errorInfo.range[0]) ||
!shared.isNumber(errorInfo.range[1]))) {
!helpers.isNumber(errorInfo.range[0]) ||
!helpers.isNumber(errorInfo.range[1]))) {
throwError("range");
}
errors.push({
@ -440,9 +440,9 @@ function lintFile(
}
// Make a/synchronous call to read file
if (synchronous) {
lintContentWrapper(null, fs.readFileSync(file, shared.utf8Encoding));
lintContentWrapper(null, fs.readFileSync(file, helpers.utf8Encoding));
} else {
fs.readFile(file, shared.utf8Encoding, lintContentWrapper);
fs.readFile(file, helpers.utf8Encoding, lintContentWrapper);
}
}
@ -466,7 +466,7 @@ function lintInput(options, synchronous, callback) {
const stringsKeys = Object.keys(strings);
const config = options.config || { "default": true };
const frontMatter = (options.frontMatter === undefined) ?
shared.frontMatterRe : options.frontMatter;
helpers.frontMatterRe : options.frontMatter;
const noInlineConfig = !!options.noInlineConfig;
const resultVersion = (options.resultVersion === undefined) ?
2 : options.resultVersion;
@ -590,7 +590,7 @@ function readConfig(file, parsers, callback) {
parsers = null;
}
// Read file
fs.readFile(file, shared.utf8Encoding, (err, content) => {
fs.readFile(file, helpers.utf8Encoding, (err, content) => {
if (err) {
return callback(err);
}
@ -608,7 +608,7 @@ function readConfig(file, parsers, callback) {
if (errr) {
return callback(errr);
}
return callback(null, shared.assign(extendsConfig, config));
return callback(null, helpers.assign(extendsConfig, config));
});
}
return callback(null, config);
@ -624,7 +624,7 @@ function readConfig(file, parsers, callback) {
*/
function readConfigSync(file, parsers) {
// Read file
const content = fs.readFileSync(file, shared.utf8Encoding);
const content = fs.readFileSync(file, helpers.utf8Encoding);
// Try to parse file
const { config, message } = parseConfiguration(file, content, parsers);
if (!config) {
@ -634,7 +634,7 @@ function readConfigSync(file, parsers) {
const configExtends = config.extends;
if (configExtends) {
delete config.extends;
return shared.assign(
return helpers.assign(
readConfigSync(path.resolve(path.dirname(file), configExtends), parsers),
config);
}

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, filterTokens } = require("../helpers");
module.exports = {
"names": [ "MD001", "heading-increment", "header-increment" ],
@ -10,10 +10,10 @@ module.exports = {
"tags": [ "headings", "headers" ],
"function": function MD001(params, onError) {
let prevLevel = 0;
shared.filterTokens(params, "heading_open", function forToken(token) {
filterTokens(params, "heading_open", function forToken(token) {
const level = parseInt(token.tag.slice(1), 10);
if (prevLevel && (level > prevLevel)) {
shared.addErrorDetailIf(onError, token.lineNumber,
addErrorDetailIf(onError, token.lineNumber,
"h" + (prevLevel + 1), "h" + level);
}
prevLevel = level;

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf } = require("../helpers");
module.exports = {
"names": [ "MD002", "first-heading-h1", "first-header-h1" ],
@ -13,7 +13,7 @@ module.exports = {
const tag = "h" + level;
params.tokens.every(function forToken(token) {
if (token.type === "heading_open") {
shared.addErrorDetailIf(onError, token.lineNumber, tag, token.tag);
addErrorDetailIf(onError, token.lineNumber, tag, token.tag);
return false;
}
return true;

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, filterTokens, headingStyleFor } =
require("../helpers");
module.exports = {
"names": [ "MD003", "heading-style", "header-style" ],
@ -10,8 +11,8 @@ module.exports = {
"tags": [ "headings", "headers" ],
"function": function MD003(params, onError) {
let style = params.config.style || "consistent";
shared.filterTokens(params, "heading_open", function forToken(token) {
const styleForToken = shared.headingStyleFor(token);
filterTokens(params, "heading_open", function forToken(token) {
const styleForToken = headingStyleFor(token);
if (style === "consistent") {
style = styleForToken;
}
@ -32,7 +33,7 @@ module.exports = {
} else if (style === "setext_with_atx_closed") {
expected = h12 ? "setext" : "atx_closed";
}
shared.addErrorDetailIf(onError, token.lineNumber,
addErrorDetailIf(onError, token.lineNumber,
expected, styleForToken);
}
}

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorDetailIf, listItemMarkerRe,
rangeFromRegExp } = require("./shared");
rangeFromRegExp } = require("../helpers");
const { flattenedLists } = require("./cache");
// Returns the unordered list style for a list item token

View file

@ -3,7 +3,7 @@
"use strict";
const { addError, addErrorDetailIf, indentFor, listItemMarkerRe,
orderedListItemMarkerRe, rangeFromRegExp } = require("./shared");
orderedListItemMarkerRe, rangeFromRegExp } = require("../helpers");
const { flattenedLists } = require("./cache");
module.exports = {

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
require("./shared");
require("../helpers");
const { flattenedLists } = require("./cache");
module.exports = {

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
require("./shared");
require("../helpers");
const { flattenedLists } = require("./cache");
module.exports = {

View file

@ -3,7 +3,7 @@
"use strict";
const { addError, filterTokens, forEachLine, includesSorted, rangeFromRegExp,
trimRight } = require("./shared");
trimRight } = require("../helpers");
const { lineMetadata } = require("./cache");
const trailingSpaceRe = /\s+$/;

View file

@ -2,7 +2,7 @@
"use strict";
const { addError, forEachLine, rangeFromRegExp } = require("./shared");
const { addError, forEachLine, rangeFromRegExp } = require("../helpers");
const { lineMetadata } = require("./cache");
const tabRe = /\t+/;

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addError, forEachInlineChild, rangeFromRegExp } = require("../helpers");
const reversedLinkRe = /\([^)]+\)\[[^\]^][^\]]*]/;
@ -11,11 +11,11 @@ module.exports = {
"description": "Reversed link syntax",
"tags": [ "links" ],
"function": function MD011(params, onError) {
shared.forEachInlineChild(params, "text", function forToken(token) {
forEachInlineChild(params, "text", function forToken(token) {
const match = reversedLinkRe.exec(token.content);
if (match) {
shared.addError(onError, token.lineNumber, match[0], null,
shared.rangeFromRegExp(token.line, reversedLinkRe));
addError(onError, token.lineNumber, match[0], null,
rangeFromRegExp(token.line, reversedLinkRe));
}
});
}

View file

@ -2,7 +2,7 @@
"use strict";
const { addErrorDetailIf, forEachLine } = require("./shared");
const { addErrorDetailIf, forEachLine } = require("../helpers");
const { lineMetadata } = require("./cache");
module.exports = {

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorDetailIf, filterTokens, forEachHeading, forEachLine,
includesSorted, rangeFromRegExp } = require("./shared");
includesSorted, rangeFromRegExp } = require("../helpers");
const { lineMetadata } = require("./cache");
const longLineRePrefix = "^(.{";

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, newLineRe, rangeFromRegExp } =
require("../helpers");
const dollarCommandRe = /^(\s*)(\$\s)/;
@ -12,15 +13,15 @@ module.exports = {
"tags": [ "code" ],
"function": function MD014(params, onError) {
[ "code_block", "fence" ].forEach(function forType(type) {
shared.filterTokens(params, type, function forToken(token) {
filterTokens(params, type, function forToken(token) {
let allBlank = true;
if (token.content && token.content.split(shared.newLineRe)
if (token.content && token.content.split(newLineRe)
.every(function forLine(line) {
return !line || (allBlank = false) || dollarCommandRe.test(line);
}) && !allBlank) {
shared.addErrorContext(onError, token.lineNumber,
token.content.split(shared.newLineRe)[0].trim(), null, null,
shared.rangeFromRegExp(token.line, dollarCommandRe));
addErrorContext(onError, token.lineNumber,
token.content.split(newLineRe)[0].trim(), null, null,
rangeFromRegExp(token.line, dollarCommandRe));
}
});
});

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorContext, atxHeadingSpaceRe, forEachLine,
rangeFromRegExp } = require("./shared");
rangeFromRegExp } = require("../helpers");
const { lineMetadata } = require("./cache");
module.exports = {

View file

@ -2,19 +2,20 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, atxHeadingSpaceRe, filterTokens, headingStyleFor,
rangeFromRegExp } = require("../helpers");
module.exports = {
"names": [ "MD019", "no-multiple-space-atx" ],
"description": "Multiple spaces after hash on atx style heading",
"tags": [ "headings", "headers", "atx", "spaces" ],
"function": function MD019(params, onError) {
shared.filterTokens(params, "heading_open", function forToken(token) {
if ((shared.headingStyleFor(token) === "atx") &&
filterTokens(params, "heading_open", function forToken(token) {
if ((headingStyleFor(token) === "atx") &&
/^#+\s\s/.test(token.line)) {
shared.addErrorContext(onError, token.lineNumber, token.line.trim(),
addErrorContext(onError, token.lineNumber, token.line.trim(),
null, null,
shared.rangeFromRegExp(token.line, shared.atxHeadingSpaceRe));
rangeFromRegExp(token.line, atxHeadingSpaceRe));
}
});
}

View file

@ -2,7 +2,7 @@
"use strict";
const { addErrorContext, forEachLine, rangeFromRegExp } = require("./shared");
const { addErrorContext, forEachLine, rangeFromRegExp } = require("../helpers");
const { lineMetadata } = require("./cache");
const atxClosedHeadingNoSpaceRe = /(?:^#+[^#\s])|(?:[^#\s]#+\s*$)/;

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, headingStyleFor, rangeFromRegExp } =
require("../helpers");
const atxClosedHeadingSpaceRe = /(?:^#+\s\s+?\S)|(?:\S\s\s+?#+\s*$)/;
@ -11,14 +12,14 @@ module.exports = {
"description": "Multiple spaces inside hashes on closed atx style heading",
"tags": [ "headings", "headers", "atx_closed", "spaces" ],
"function": function MD021(params, onError) {
shared.filterTokens(params, "heading_open", function forToken(token) {
if (shared.headingStyleFor(token) === "atx_closed") {
filterTokens(params, "heading_open", function forToken(token) {
if (headingStyleFor(token) === "atx_closed") {
const left = /^#+\s\s/.test(token.line);
const right = /\s\s#+$/.test(token.line);
if (left || right) {
shared.addErrorContext(onError, token.lineNumber, token.line.trim(),
addErrorContext(onError, token.lineNumber, token.line.trim(),
left, right,
shared.rangeFromRegExp(token.line, atxClosedHeadingSpaceRe));
rangeFromRegExp(token.line, atxClosedHeadingSpaceRe));
}
}
});

View file

@ -2,8 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, filterTokens, isBlankLine } = shared;
const { addErrorDetailIf, filterTokens, isBlankLine } = require("../helpers");
module.exports = {
"names": [ "MD022", "blanks-around-headings", "blanks-around-headers" ],

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, rangeFromRegExp } =
require("../helpers");
const spaceBeforeHeadingRe = /^((?:\s+)|(?:[>\s]+\s\s))[^>\s]/;
@ -11,10 +12,10 @@ module.exports = {
"description": "Headings must start at the beginning of the line",
"tags": [ "headings", "headers", "spaces" ],
"function": function MD023(params, onError) {
shared.filterTokens(params, "heading_open", function forToken(token) {
filterTokens(params, "heading_open", function forToken(token) {
if (spaceBeforeHeadingRe.test(token.line)) {
shared.addErrorContext(onError, token.lineNumber, token.line, null,
null, shared.rangeFromRegExp(token.line, spaceBeforeHeadingRe));
addErrorContext(onError, token.lineNumber, token.line, null,
null, rangeFromRegExp(token.line, spaceBeforeHeadingRe));
}
});
}

View file

@ -2,8 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, forEachHeading } = shared;
const { addErrorContext, forEachHeading } = require("../helpers");
module.exports = {
"names": [ "MD024", "no-duplicate-heading", "no-duplicate-header" ],

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, frontMatterHasTitle } =
require("../helpers");
module.exports = {
"names": [ "MD025", "single-title", "single-h1" ],
@ -12,15 +13,15 @@ module.exports = {
const level = params.config.level || 1;
const tag = "h" + level;
const foundFrontMatterTitle =
shared.frontMatterHasTitle(
frontMatterHasTitle(
params.frontMatterLines,
params.config.front_matter_title
);
let hasTopLevelHeading = false;
shared.filterTokens(params, "heading_open", function forToken(token) {
filterTokens(params, "heading_open", function forToken(token) {
if (token.tag === tag) {
if (hasTopLevelHeading || foundFrontMatterTitle) {
shared.addErrorContext(onError, token.lineNumber,
addErrorContext(onError, token.lineNumber,
token.line.trim());
} else if (token.lineNumber === 1) {
hasTopLevelHeading = true;

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addError, forEachHeading, rangeFromRegExp } = require("../helpers");
module.exports = {
"names": [ "MD026", "no-trailing-punctuation" ],
@ -11,12 +11,12 @@ module.exports = {
"function": function MD026(params, onError) {
const punctuation = params.config.punctuation || ".,;:!?";
const trailingPunctuationRe = new RegExp("[" + punctuation + "]$");
shared.forEachHeading(params, function forHeading(heading, content) {
forEachHeading(params, function forHeading(heading, content) {
const match = trailingPunctuationRe.exec(content);
if (match) {
shared.addError(onError, heading.lineNumber,
addError(onError, heading.lineNumber,
"Punctuation: '" + match[0] + "'", null,
shared.rangeFromRegExp(heading.line, trailingPunctuationRe));
rangeFromRegExp(heading.line, trailingPunctuationRe));
}
});
}

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, newLineRe, rangeFromRegExp } = require("../helpers");
const spaceAfterBlockQuote = /^\s*(?:>\s+)+\S/;
@ -27,15 +27,15 @@ module.exports = {
/^(\s*>)+\s\s+>/.test(token.line) :
/^(\s*>)+\s\s/.test(token.line);
if (multipleSpaces) {
shared.addErrorContext(onError, token.lineNumber, token.line, null,
null, shared.rangeFromRegExp(token.line, spaceAfterBlockQuote));
addErrorContext(onError, token.lineNumber, token.line, null,
null, rangeFromRegExp(token.line, spaceAfterBlockQuote));
}
token.content.split(shared.newLineRe)
token.content.split(newLineRe)
.forEach(function forLine(line, offset) {
if (/^\s/.test(line)) {
shared.addErrorContext(onError, token.lineNumber + offset,
addErrorContext(onError, token.lineNumber + offset,
"> " + line, null, null,
shared.rangeFromRegExp(line, spaceAfterBlockQuote));
rangeFromRegExp(line, spaceAfterBlockQuote));
}
});
}

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addError } = require("../helpers");
module.exports = {
"names": [ "MD028", "no-blanks-blockquote" ],
@ -13,7 +13,7 @@ module.exports = {
params.tokens.forEach(function forToken(token) {
if ((token.type === "blockquote_open") &&
(prevToken.type === "blockquote_close")) {
shared.addError(onError, token.lineNumber - 1);
addError(onError, token.lineNumber - 1);
}
prevToken = token;
});

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorDetailIf, listItemMarkerRe, orderedListItemMarkerRe,
rangeFromRegExp } = require("./shared");
rangeFromRegExp } = require("../helpers");
const { flattenedLists } = require("./cache");
const listStyleExamples = {

View file

@ -3,7 +3,7 @@
"use strict";
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
require("./shared");
require("../helpers");
const { flattenedLists } = require("./cache");
module.exports = {

View file

@ -2,7 +2,7 @@
"use strict";
const { addErrorContext, forEachLine, isBlankLine } = require("./shared");
const { addErrorContext, forEachLine, isBlankLine } = require("../helpers");
const { lineMetadata } = require("./cache");
module.exports = {

View file

@ -2,7 +2,7 @@
"use strict";
const { addErrorContext, isBlankLine } = require("./shared");
const { addErrorContext, isBlankLine } = require("../helpers");
const { flattenedLists } = require("./cache");
module.exports = {

View file

@ -2,10 +2,8 @@
"use strict";
const shared = require("./shared");
const {
addError, filterTokens, forEachInlineChild, newLineRe, rangeFromRegExp
} = shared;
const { addError, filterTokens, forEachInlineChild, newLineRe,
rangeFromRegExp } = require("../helpers");
const htmlRe = /<[^>]*>/;

View file

@ -2,14 +2,15 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, bareUrlRe, filterTokens, rangeFromRegExp } =
require("../helpers");
module.exports = {
"names": [ "MD034", "no-bare-urls" ],
"description": "Bare URL used",
"tags": [ "links", "url" ],
"function": function MD034(params, onError) {
shared.filterTokens(params, "inline", function forToken(token) {
filterTokens(params, "inline", function forToken(token) {
let inLink = false;
token.children.forEach(function forChild(child) {
let match = null;
@ -19,9 +20,9 @@ module.exports = {
inLink = false;
} else if ((child.type === "text") &&
!inLink &&
(match = shared.bareUrlRe.exec(child.content))) {
shared.addErrorContext(onError, child.lineNumber, match[0], null,
null, shared.rangeFromRegExp(child.line, shared.bareUrlRe));
(match = bareUrlRe.exec(child.content))) {
addErrorContext(onError, child.lineNumber, match[0], null,
null, rangeFromRegExp(child.line, bareUrlRe));
}
});
});

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, filterTokens } = require("../helpers");
module.exports = {
"names": [ "MD035", "hr-style" ],
@ -10,12 +10,12 @@ module.exports = {
"tags": [ "hr" ],
"function": function MD035(params, onError) {
let style = params.config.style || "consistent";
shared.filterTokens(params, "hr", function forToken(token) {
filterTokens(params, "hr", function forToken(token) {
const lineTrim = token.line.trim();
if (style === "consistent") {
style = lineTrim;
}
shared.addErrorDetailIf(onError, token.lineNumber, style, lineTrim);
addErrorDetailIf(onError, token.lineNumber, style, lineTrim);
});
}
};

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorContext } = require("../helpers");
module.exports = {
"names": [ "MD036", "no-emphasis-as-heading", "no-emphasis-as-header" ],
@ -24,7 +24,7 @@ module.exports = {
(children[0].type === "em_open")) &&
(children[1].type === "text") &&
!re.test(children[1].content)) {
shared.addErrorContext(onError, t.lineNumber,
addErrorContext(onError, t.lineNumber,
children[1].content);
}
return base;

View file

@ -2,8 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, forEachInlineChild } = shared;
const { addErrorContext, forEachInlineChild } = require("../helpers");
module.exports = {
"names": [ "MD037", "no-space-in-emphasis" ],

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, forEachInlineCodeSpan, newLineRe } =
require("../helpers");
const startRe = /^\s([^`]|$)/;
const endRe = /[^`]\s$/;
@ -12,16 +13,16 @@ module.exports = {
"description": "Spaces inside code span elements",
"tags": [ "whitespace", "code" ],
"function": function MD038(params, onError) {
shared.filterTokens(params, "inline", (token) => {
filterTokens(params, "inline", (token) => {
if (token.children.some((child) => child.type === "code_inline")) {
const tokenLines = params.lines.slice(token.map[0], token.map[1]);
shared.forEachInlineCodeSpan(
forEachInlineCodeSpan(
tokenLines.join("\n"),
(code, lineIndex, columnIndex, tickCount) => {
let rangeIndex = columnIndex - tickCount;
let rangeLength = code.length + (2 * tickCount);
let rangeLineOffset = 0;
const codeLines = code.split(shared.newLineRe);
const codeLines = code.split(newLineRe);
const left = startRe.test(code);
const right = !left && endRe.test(code);
if (right && (codeLines.length > 1)) {
@ -34,7 +35,7 @@ module.exports = {
}
const context = tokenLines[lineIndex + rangeLineOffset]
.substring(rangeIndex, rangeIndex + rangeLength);
shared.addErrorContext(
addErrorContext(
onError, token.lineNumber + lineIndex + rangeLineOffset,
context, left, right, [ rangeIndex + 1, rangeLength ]);
}

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, rangeFromRegExp, trimLeft, trimRight } =
require("../helpers");
const spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=\(\S*\))/;
@ -11,7 +12,7 @@ module.exports = {
"description": "Spaces inside link text",
"tags": [ "whitespace", "links" ],
"function": function MD039(params, onError) {
shared.filterTokens(params, "inline", function forToken(token) {
filterTokens(params, "inline", function forToken(token) {
let inLink = false;
let linkText = "";
token.children.forEach(function forChild(child) {
@ -20,12 +21,12 @@ module.exports = {
linkText = "";
} else if (child.type === "link_close") {
inLink = false;
const left = shared.trimLeft(linkText).length !== linkText.length;
const right = shared.trimRight(linkText).length !== linkText.length;
const left = trimLeft(linkText).length !== linkText.length;
const right = trimRight(linkText).length !== linkText.length;
if (left || right) {
shared.addErrorContext(onError, token.lineNumber,
addErrorContext(onError, token.lineNumber,
"[" + linkText + "]", left, right,
shared.rangeFromRegExp(token.line, spaceInLinkRe));
rangeFromRegExp(token.line, spaceInLinkRe));
}
} else if (inLink) {
linkText += child.content;

View file

@ -2,16 +2,16 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens } = require("../helpers");
module.exports = {
"names": [ "MD040", "fenced-code-language" ],
"description": "Fenced code blocks should have a language specified",
"tags": [ "code", "language" ],
"function": function MD040(params, onError) {
shared.filterTokens(params, "fence", function forToken(token) {
filterTokens(params, "fence", function forToken(token) {
if (!token.info.trim()) {
shared.addErrorContext(onError, token.lineNumber, token.line);
addErrorContext(onError, token.lineNumber, token.line);
}
});
}

View file

@ -2,7 +2,7 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, frontMatterHasTitle } = require("../helpers");
module.exports = {
"names": [ "MD041", "first-line-heading", "first-line-h1" ],
@ -12,7 +12,7 @@ module.exports = {
const level = params.config.level || 1;
const tag = "h" + level;
const foundFrontMatterTitle =
shared.frontMatterHasTitle(
frontMatterHasTitle(
params.frontMatterLines,
params.config.front_matter_title
);
@ -22,7 +22,7 @@ module.exports = {
return true;
}
if ((token.type !== "heading_open") || (token.tag !== tag)) {
shared.addErrorContext(onError, token.lineNumber, token.line);
addErrorContext(onError, token.lineNumber, token.line);
}
return false;
});

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, filterTokens, rangeFromRegExp } =
require("../helpers");
const emptyLinkRe = /\[[^\]]*](?:\((?:#?|(?:<>))\))/;
@ -11,7 +12,7 @@ module.exports = {
"description": "No empty links",
"tags": [ "links" ],
"function": function MD042(params, onError) {
shared.filterTokens(params, "inline", function forToken(token) {
filterTokens(params, "inline", function forToken(token) {
let inLink = false;
let linkText = "";
let emptyLink = false;
@ -27,9 +28,9 @@ module.exports = {
} else if (child.type === "link_close") {
inLink = false;
if (emptyLink) {
shared.addErrorContext(onError, child.lineNumber,
addErrorContext(onError, child.lineNumber,
"[" + linkText + "]()", null, null,
shared.rangeFromRegExp(child.line, emptyLinkRe));
rangeFromRegExp(child.line, emptyLinkRe));
}
} else if (inLink) {
linkText += child.content;

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, addErrorDetailIf, forEachHeading } =
require("../helpers");
module.exports = {
"names": [ "MD043", "required-headings", "required-headers" ],
@ -18,7 +19,7 @@ module.exports = {
let i = 0;
let optional = false;
let errorCount = 0;
shared.forEachHeading(params, function forHeading(heading, content) {
forEachHeading(params, function forHeading(heading, content) {
if (!errorCount) {
const actual = levels[heading.tag] + " " + content;
const expected = requiredHeadings[i++] || "[None]";
@ -29,14 +30,14 @@ module.exports = {
} else if (optional) {
i--;
} else {
shared.addErrorDetailIf(onError, heading.lineNumber,
addErrorDetailIf(onError, heading.lineNumber,
expected, actual);
errorCount++;
}
}
});
if ((i < requiredHeadings.length) && !errorCount) {
shared.addErrorContext(onError, params.lines.length,
addErrorContext(onError, params.lines.length,
requiredHeadings[i]);
}
}

View file

@ -2,9 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, bareUrlRe, escapeForRegExp, filterTokens,
forEachInlineChild, newLineRe } = shared;
forEachInlineChild, newLineRe } = require("../helpers");
module.exports = {
"names": [ "MD044", "proper-names" ],

View file

@ -2,16 +2,16 @@
"use strict";
const shared = require("./shared");
const { addError, forEachInlineChild } = require("../helpers");
module.exports = {
"names": [ "MD045", "no-alt-text" ],
"description": "Images should have alternate text (alt text)",
"tags": [ "accessibility", "images" ],
"function": function MD045(params, onError) {
shared.forEachInlineChild(params, "image", function forToken(token) {
forEachInlineChild(params, "image", function forToken(token) {
if (token.content === "") {
shared.addError(onError, token.lineNumber);
addError(onError, token.lineNumber);
}
});
}

View file

@ -16,7 +16,7 @@
"test-cover": "istanbul cover node_modules/nodeunit/bin/nodeunit test/markdownlint-test.js",
"test-extra": "nodeunit test/markdownlint-test-extra.js",
"debug": "node debug node_modules/nodeunit/bin/nodeunit",
"lint": "eslint lib test schema && eslint --env browser --global markdownit --global markdownlint --rule \"no-unused-vars: 0, no-extend-native: 0, max-statements: 0, no-console: 0, no-var: 0\" demo && eslint --rule \"no-console: 0, no-invalid-this: 0, no-shadow: 0, object-property-newline: 0\" example",
"lint": "eslint lib helpers test schema && eslint --env browser --global markdownit --global markdownlint --rule \"no-unused-vars: 0, no-extend-native: 0, max-statements: 0, no-console: 0, no-var: 0\" demo && eslint --rule \"no-console: 0, no-invalid-this: 0, no-shadow: 0, object-property-newline: 0\" example",
"build-config-schema": "node schema/build-config-schema.js",
"build-demo": "cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && cpy file-header.js . --rename=markdownlint-browser.js && tsc --allowJs --resolveJsonModule --outDir ../lib-es3 ../lib/markdownlint.js && browserify ../lib-es3/lib/markdownlint.js --standalone markdownlint >> markdownlint-browser.js && uglifyjs markdownlint-browser.js --compress --mangle --comments --output markdownlint-browser.min.js",
"build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2",

View file

@ -4,7 +4,7 @@ const fs = require("fs");
const path = require("path");
const glob = require("glob");
const markdownlint = require("../lib/markdownlint");
const shared = require("../lib/shared");
const { newLineRe, utf8Encoding } = require("../helpers");
module.exports.typeTestFiles = function typeTestFiles(test) {
// Simulates typing each test file to validate handling of partial input
@ -15,7 +15,7 @@ module.exports.typeTestFiles = function typeTestFiles(test) {
},
"resultVersion": 0
});
const contentLineCount = content.split(shared.newLineRe).length;
const contentLineCount = content.split(newLineRe).length;
Object.keys(results.content).forEach(function forKey(ruleName) {
results.content[ruleName].forEach(function forLine(line) {
test.ok((line >= 1) && (line <= contentLineCount),
@ -28,7 +28,7 @@ module.exports.typeTestFiles = function typeTestFiles(test) {
files.forEach(function forFile(file) {
if (/\.md$/.test(file)) {
let content = fs.readFileSync(
path.join("./test", file), shared.utf8Encoding);
path.join("./test", file), utf8Encoding);
while (content) {
validate(file, content);
content = content.slice(0, -1);

View file

@ -11,7 +11,7 @@ const pluginSup = require("markdown-it-sup");
const tv4 = require("tv4");
const packageJson = require("../package.json");
const markdownlint = require("../lib/markdownlint");
const shared = require("../lib/shared");
const helpers = require("../helpers");
const rules = require("../lib/rules");
const customRules = require("./rules/rules.js");
const defaultConfig = require("./markdownlint-test-default-config.json");
@ -39,7 +39,7 @@ function createTestForFile(file) {
const actualPromise = promisify(fs.stat, configFile)
.then(
function configFileExists() {
return promisify(fs.readFile, configFile, shared.utf8Encoding)
return promisify(fs.readFile, configFile, helpers.utf8Encoding)
.then(JSON.parse);
},
function noConfigFile() {
@ -48,7 +48,7 @@ function createTestForFile(file) {
.then(
function lintWithConfig(config) {
const mergedConfig =
shared.assign(shared.clone(defaultConfig), config);
helpers.assign(helpers.clone(defaultConfig), config);
return promisify(markdownlint, {
"files": [ file ],
"config": mergedConfig,
@ -56,7 +56,7 @@ function createTestForFile(file) {
});
});
const expectedPromise = detailedResults ?
promisify(fs.readFile, resultsFile, shared.utf8Encoding)
promisify(fs.readFile, resultsFile, helpers.utf8Encoding)
.then(
function fileContents(contents) {
const errorObjects = JSON.parse(contents);
@ -68,10 +68,10 @@ function createTestForFile(file) {
});
return errorObjects;
}) :
promisify(fs.readFile, file, shared.utf8Encoding)
promisify(fs.readFile, file, helpers.utf8Encoding)
.then(
function fileContents(contents) {
const lines = contents.split(shared.newLineRe);
const lines = contents.split(helpers.newLineRe);
const results = {};
lines.forEach(function forLine(line, lineNum) {
const regex = /\{(MD\d+)(?::(\d+))?\}/g;
@ -114,7 +114,8 @@ module.exports.projectFiles = function projectFiles(test) {
const options = {
"files": [
"README.md",
"CONTRIBUTING.md"
"CONTRIBUTING.md",
"helpers/README.md"
],
"noInlineConfig": true,
"config": {
@ -126,7 +127,8 @@ module.exports.projectFiles = function projectFiles(test) {
test.ifError(err);
const expected = {
"README.md": [],
"CONTRIBUTING.md": []
"CONTRIBUTING.md": [],
"helpers/README.md": []
};
test.deepEqual(actual, expected, "Issue(s) with project files.");
test.done();
@ -1098,7 +1100,7 @@ module.exports.readme = function readme(test) {
tagToRules[tag] = tagRules;
});
});
fs.readFile("README.md", shared.utf8Encoding,
fs.readFile("README.md", helpers.utf8Encoding,
function readFile(err, contents) {
test.ifError(err);
const rulesLeft = rules.slice();
@ -1156,7 +1158,7 @@ module.exports.readme = function readme(test) {
module.exports.doc = function doc(test) {
test.expect(312);
fs.readFile("doc/Rules.md", shared.utf8Encoding,
fs.readFile("doc/Rules.md", helpers.utf8Encoding,
function readFile(err, contents) {
test.ifError(err);
const rulesLeft = rules.slice();
@ -1323,7 +1325,7 @@ function clearHtmlCommentTextValid(test) {
"<!--",
" \\"
];
const actual = shared.clearHtmlCommentText(validComments.join("\n"));
const actual = helpers.clearHtmlCommentText(validComments.join("\n"));
const expected = validResult.join("\n");
test.equal(actual, expected);
test.done();
@ -1350,7 +1352,7 @@ function clearHtmlCommentTextInvalid(test) {
"<!--text--->",
"<!--te--xt-->"
];
const actual = shared.clearHtmlCommentText(invalidComments.join("\n"));
const actual = helpers.clearHtmlCommentText(invalidComments.join("\n"));
const expected = invalidComments.join("\n");
test.equal(actual, expected);
test.done();
@ -1371,7 +1373,7 @@ function clearHtmlCommentTextNonGreedy(test) {
"<!-- --> -->",
"<!----> -->"
];
const actual = shared.clearHtmlCommentText(nonGreedyComments.join("\n"));
const actual = helpers.clearHtmlCommentText(nonGreedyComments.join("\n"));
const expected = nonGreedyResult.join("\n");
test.equal(actual, expected);
test.done();
@ -1394,7 +1396,7 @@ function clearHtmlCommentTextEmbedded(test) {
"text<!-- markdownlint-disable MD010 -->text",
"text<!-- -->text"
];
const actual = shared.clearHtmlCommentText(embeddedComments.join("\n"));
const actual = helpers.clearHtmlCommentText(embeddedComments.join("\n"));
const expected = embeddedResult.join("\n");
test.equal(actual, expected);
test.done();
@ -1421,7 +1423,7 @@ module.exports.isBlankLine = function isBlankLine(test) {
"> <!--text-->",
">><!--text-->"
];
blankLines.forEach((line) => test.ok(shared.isBlankLine(line), line));
blankLines.forEach((line) => test.ok(helpers.isBlankLine(line), line));
const nonBlankLines = [
"text",
" text ",
@ -1432,7 +1434,7 @@ module.exports.isBlankLine = function isBlankLine(test) {
"<!--",
"-->"
];
nonBlankLines.forEach((line) => test.ok(!shared.isBlankLine(line), line));
nonBlankLines.forEach((line) => test.ok(!helpers.isBlankLine(line), line));
test.done();
};
@ -1449,7 +1451,7 @@ module.exports.includesSorted = function includesSorted(test) {
];
inputs.forEach((input) => {
for (let i = 0; i <= 21; i++) {
test.equal(shared.includesSorted(input, i), input.includes(i));
test.equal(helpers.includesSorted(input, i), input.includes(i));
}
});
test.done();
@ -1475,9 +1477,9 @@ module.exports.trimLeftRight = function trimLeftRight(test) {
];
test.expect(inputs.length * 2);
inputs.forEach(function forInput(input) {
test.equal(shared.trimLeft(input), input.trimLeft(),
test.equal(helpers.trimLeft(input), input.trimLeft(),
"trimLeft incorrect for '" + input + "'");
test.equal(shared.trimRight(input), input.trimRight(),
test.equal(helpers.trimRight(input), input.trimRight(),
"trimRight incorrect for '" + input + "'");
});
test.done();
@ -1571,7 +1573,7 @@ module.exports.forEachInlineCodeSpan = function forEachInlineCodeSpan(test) {
];
testCases.forEach((testCase) => {
const [ input, expecteds ] = testCase;
shared.forEachInlineCodeSpan(input, (code, line, column, ticks) => {
helpers.forEachInlineCodeSpan(input, (code, line, column, ticks) => {
const [ expectedCode, expectedLine, expectedColumn, expectedTicks ] =
expecteds.shift();
test.equal(code, expectedCode, input);
@ -1611,9 +1613,9 @@ module.exports.configMultiple = function configMultiple(test) {
markdownlint.readConfig("./test/config/config-grandparent.json",
function callback(err, actual) {
test.ifError(err);
const expected = shared.assign(
shared.assign(
shared.assign({}, require("./config/config-child.json")),
const expected = helpers.assign(
helpers.assign(
helpers.assign({}, require("./config/config-child.json")),
require("./config/config-parent.json")),
require("./config/config-grandparent.json"));
delete expected.extends;
@ -1689,9 +1691,9 @@ module.exports.configMultipleYaml = function configMultipleYaml(test) {
[ require("js-yaml").safeLoad ],
function callback(err, actual) {
test.ifError(err);
const expected = shared.assign(
shared.assign(
shared.assign({}, require("./config/config-child.json")),
const expected = helpers.assign(
helpers.assign(
helpers.assign({}, require("./config/config-child.json")),
require("./config/config-parent.json")),
require("./config/config-grandparent.json"));
delete expected.extends;
@ -1707,9 +1709,9 @@ module.exports.configMultipleHybrid = function configMultipleHybrid(test) {
[ JSON.parse, require("toml").parse, require("js-yaml").safeLoad ],
function callback(err, actual) {
test.ifError(err);
const expected = shared.assign(
shared.assign(
shared.assign({}, require("./config/config-child.json")),
const expected = helpers.assign(
helpers.assign(
helpers.assign({}, require("./config/config-child.json")),
require("./config/config-parent.json")),
require("./config/config-grandparent.json"));
delete expected.extends;
@ -1756,9 +1758,9 @@ module.exports.configMultipleSync = function configMultipleSync(test) {
test.expect(1);
const actual =
markdownlint.readConfigSync("./test/config/config-grandparent.json");
const expected = shared.assign(
shared.assign(
shared.assign({}, require("./config/config-child.json")),
const expected = helpers.assign(
helpers.assign(
helpers.assign({}, require("./config/config-child.json")),
require("./config/config-parent.json")),
require("./config/config-grandparent.json"));
delete expected.extends;
@ -1837,9 +1839,9 @@ module.exports.configMultipleYamlSync = function configMultipleYamlSync(test) {
test.expect(1);
const actual = markdownlint.readConfigSync(
"./test/config/config-grandparent.yaml", [ require("js-yaml").safeLoad ]);
const expected = shared.assign(
shared.assign(
shared.assign({}, require("./config/config-child.json")),
const expected = helpers.assign(
helpers.assign(
helpers.assign({}, require("./config/config-child.json")),
require("./config/config-parent.json")),
require("./config/config-grandparent.json"));
delete expected.extends;
@ -1853,9 +1855,9 @@ function configMultipleHybridSync(test) {
const actual = markdownlint.readConfigSync(
"./test/config/config-grandparent-hybrid.yaml",
[ JSON.parse, require("toml").parse, require("js-yaml").safeLoad ]);
const expected = shared.assign(
shared.assign(
shared.assign({}, require("./config/config-child.json")),
const expected = helpers.assign(
helpers.assign(
helpers.assign({}, require("./config/config-child.json")),
require("./config/config-parent.json")),
require("./config/config-grandparent.json"));
delete expected.extends;
@ -2266,7 +2268,7 @@ module.exports.customRulesBadProperty = function customRulesBadProperty(test) {
].forEach(function forProperty(property) {
const propertyName = property[0];
property[1].forEach(function forPropertyValue(propertyValue) {
const badRule = shared.clone(customRules.anyBlockquote);
const badRule = helpers.clone(customRules.anyBlockquote);
badRule[propertyName] = propertyValue;
const options = {
"customRules": [ badRule ]

View file

@ -3,6 +3,7 @@
"use strict";
const { URL } = require("url");
const { filterTokens } = require("../../helpers");
module.exports = {
"names": [ "any-blockquote" ],
@ -12,10 +13,8 @@ module.exports = {
"/blob/master/test/rules/any-blockquote.js"
),
"tags": [ "test" ],
"function": function rule(params, onError) {
params.tokens.filter(function filterToken(token) {
return token.type === "blockquote_open";
}).forEach(function forToken(blockquote) {
"function": (params, onError) => {
filterTokens(params, "blockquote_open", (blockquote) => {
const lines = blockquote.map[1] - blockquote.map[0];
onError({
"lineNumber": blockquote.lineNumber,

View file

@ -2,13 +2,15 @@
"use strict";
const { forEachLine, getLineMetadata } = require("../../helpers");
module.exports = {
"names": [ "every-n-lines" ],
"description": "Rule that reports an error every N lines",
"tags": [ "test" ],
"function": function rule(params, onError) {
"function": (params, onError) => {
const n = params.config.n || 2;
params.lines.forEach(function forLine(line, lineIndex) {
forEachLine(getLineMetadata(params), (line, lineIndex) => {
const lineNumber = lineIndex + 1;
if ((lineNumber % n) === 0) {
onError({