mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2026-03-10 04:12:33 +01:00
Expose shared.js helper code for custom rule authors (fixes #134).
This commit is contained in:
parent
f614f3e1ce
commit
7e980401b8
52 changed files with 283 additions and 184 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
|
||||
require("./shared");
|
||||
require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
|
||||
require("./shared");
|
||||
require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -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+$/;
|
||||
|
|
|
|||
|
|
@ -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+/;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorDetailIf, forEachLine } = require("./shared");
|
||||
const { addErrorDetailIf, forEachLine } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -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 = "^(.{";
|
||||
|
|
|
|||
13
lib/md014.js
13
lib/md014.js
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const { addErrorContext, atxHeadingSpaceRe, forEachLine,
|
||||
rangeFromRegExp } = require("./shared");
|
||||
rangeFromRegExp } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
11
lib/md019.js
11
lib/md019.js
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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*$)/;
|
||||
|
|
|
|||
11
lib/md021.js
11
lib/md021.js
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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" ],
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" ],
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
12
lib/md027.js
12
lib/md027.js
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const { addErrorDetailIf, listItemMarkerRe, orderedListItemMarkerRe,
|
||||
rangeFromRegExp } = require("./shared");
|
||||
rangeFromRegExp } = require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
const listStyleExamples = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
|
||||
require("./shared");
|
||||
require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, forEachLine, isBlankLine } = require("./shared");
|
||||
const { addErrorContext, forEachLine, isBlankLine } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, isBlankLine } = require("./shared");
|
||||
const { addErrorContext, isBlankLine } = require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -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 = /<[^>]*>/;
|
||||
|
||||
|
|
|
|||
11
lib/md034.js
11
lib/md034.js
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" ],
|
||||
|
|
|
|||
11
lib/md038.js
11
lib/md038.js
|
|
@ -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 ]);
|
||||
}
|
||||
|
|
|
|||
13
lib/md039.js
13
lib/md039.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" ],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
385
lib/shared.js
385
lib/shared.js
|
|
@ -1,385 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
// Regular expression for matching common newline characters
|
||||
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
|
||||
module.exports.newLineRe = /\r[\n\u0085]?|[\n\u2424\u2028\u0085]/;
|
||||
|
||||
// Regular expression for matching common front matter (YAML and TOML)
|
||||
module.exports.frontMatterRe =
|
||||
/((^---$[^]*?^---$)|(^\+\+\+$[^]*?^(\+\+\+|\.\.\.)$))(\r\n|\r|\n|$)/m;
|
||||
|
||||
// Regular expression for matching inline disable/enable comments
|
||||
const inlineCommentRe =
|
||||
/<!--\s*markdownlint-(dis|en)able((?:\s+[a-z0-9_-]+)*)\s*-->/ig;
|
||||
module.exports.inlineCommentRe = inlineCommentRe;
|
||||
|
||||
// Regular expressions for range matching
|
||||
module.exports.atxHeadingSpaceRe = /^#+\s*\S/;
|
||||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/i;
|
||||
module.exports.listItemMarkerRe = /^[\s>]*(?:[*+-]|\d+[.)])\s+/;
|
||||
module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
|
||||
|
||||
// readFile options for reading with the UTF-8 encoding
|
||||
module.exports.utf8Encoding = { "encoding": "utf8" };
|
||||
|
||||
// Trims whitespace from the left (start) of a string
|
||||
function trimLeft(str) {
|
||||
return str.replace(/^\s*/, "");
|
||||
}
|
||||
module.exports.trimLeft = trimLeft;
|
||||
|
||||
// Trims whitespace from the right (end) of a string
|
||||
module.exports.trimRight = function trimRight(str) {
|
||||
return str.replace(/\s*$/, "");
|
||||
};
|
||||
|
||||
// Applies key/value pairs from src to dst, returning dst
|
||||
function assign(dst, src) {
|
||||
Object.keys(src).forEach(function forKey(key) {
|
||||
dst[key] = src[key];
|
||||
});
|
||||
return dst;
|
||||
}
|
||||
module.exports.assign = assign;
|
||||
|
||||
// Clones the key/value pairs of obj, returning the clone
|
||||
module.exports.clone = function clone(obj) {
|
||||
return assign({}, obj);
|
||||
};
|
||||
|
||||
// Returns true iff the input is a number
|
||||
module.exports.isNumber = function isNumber(obj) {
|
||||
return typeof obj === "number";
|
||||
};
|
||||
|
||||
// Returns true iff the input is a string
|
||||
module.exports.isString = function isString(obj) {
|
||||
return typeof obj === "string";
|
||||
};
|
||||
|
||||
// Returns true iff the input string is empty
|
||||
module.exports.isEmptyString = function isEmptyString(str) {
|
||||
return str.length === 0;
|
||||
};
|
||||
|
||||
// Returns true iff the input line is blank (no content)
|
||||
// Example: Contains nothing, whitespace, or comments
|
||||
const blankLineRe = />|(?:<!--.*?-->)/g;
|
||||
module.exports.isBlankLine = function isBlankLine(line) {
|
||||
return !line || !line.trim() || !line.replace(blankLineRe, "").trim();
|
||||
};
|
||||
|
||||
// Returns true iff the sorted array contains the specified element
|
||||
module.exports.includesSorted = function includesSorted(array, element) {
|
||||
let left = 0;
|
||||
let right = array.length - 1;
|
||||
while (left <= right) {
|
||||
/* eslint-disable no-bitwise */
|
||||
const mid = (left + right) >> 1;
|
||||
if (array[mid] < element) {
|
||||
left = mid + 1;
|
||||
} else if (array[mid] > element) {
|
||||
right = mid - 1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Replaces the text of all properly-formatted HTML comments with whitespace
|
||||
// This preserves the line/column information for the rest of the document
|
||||
// Trailing whitespace is avoided with a '\' character in the last column
|
||||
// See https://www.w3.org/TR/html5/syntax.html#comments for details
|
||||
const htmlCommentBegin = "<!--";
|
||||
const htmlCommentEnd = "-->";
|
||||
module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
|
||||
let i = 0;
|
||||
while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
|
||||
let j = text.indexOf(htmlCommentEnd, i);
|
||||
if (j === -1) {
|
||||
j = text.length;
|
||||
text += "\\";
|
||||
}
|
||||
const comment = text.slice(i + htmlCommentBegin.length, j);
|
||||
if ((comment.length > 0) &&
|
||||
(comment[0] !== ">") &&
|
||||
(comment[comment.length - 1] !== "-") &&
|
||||
!comment.includes("--") &&
|
||||
(text.slice(i, j + htmlCommentEnd.length)
|
||||
.search(inlineCommentRe) === -1)) {
|
||||
const blanks = comment
|
||||
.replace(/[^\r\n]/g, " ")
|
||||
.replace(/ ([\r\n])/g, "\\$1");
|
||||
text = text.slice(0, i + htmlCommentBegin.length) +
|
||||
blanks + text.slice(j);
|
||||
}
|
||||
i = j + htmlCommentEnd.length;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
// Escapes a string for use in a RegExp
|
||||
module.exports.escapeForRegExp = function escapeForRegExp(str) {
|
||||
return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
};
|
||||
|
||||
// Returns the indent for a token
|
||||
function indentFor(token) {
|
||||
const line = token.line.replace(/^[\s>]*(> |>)/, "");
|
||||
return line.length - trimLeft(line).length;
|
||||
}
|
||||
module.exports.indentFor = indentFor;
|
||||
|
||||
// Returns the heading style for a heading token
|
||||
module.exports.headingStyleFor = function headingStyleFor(token) {
|
||||
if ((token.map[1] - token.map[0]) === 1) {
|
||||
if (/[^\\]#\s*$/.test(token.line)) {
|
||||
return "atx_closed";
|
||||
}
|
||||
return "atx";
|
||||
}
|
||||
return "setext";
|
||||
};
|
||||
|
||||
// Calls the provided function for each matching token
|
||||
function filterTokens(params, type, handler) {
|
||||
params.tokens.forEach(function forToken(token) {
|
||||
if (token.type === type) {
|
||||
handler(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports.filterTokens = filterTokens;
|
||||
|
||||
// Get line metadata array
|
||||
module.exports.getLineMetadata = function getLineMetadata(params) {
|
||||
const lineMetadata = params.lines.map(function mapLine(line, index) {
|
||||
return [ line, index, false, 0, false ];
|
||||
});
|
||||
filterTokens(params, "fence", function forToken(token) {
|
||||
lineMetadata[token.map[0]][3] = 1;
|
||||
lineMetadata[token.map[1] - 1][3] = -1;
|
||||
for (let i = token.map[0] + 1; i < token.map[1] - 1; i++) {
|
||||
lineMetadata[i][2] = true;
|
||||
}
|
||||
});
|
||||
filterTokens(params, "code_block", function forToken(token) {
|
||||
for (let i = token.map[0]; i < token.map[1]; i++) {
|
||||
lineMetadata[i][2] = true;
|
||||
}
|
||||
});
|
||||
filterTokens(params, "table_open", function forToken(token) {
|
||||
for (let i = token.map[0]; i < token.map[1]; i++) {
|
||||
lineMetadata[i][4] = true;
|
||||
}
|
||||
});
|
||||
return lineMetadata;
|
||||
};
|
||||
|
||||
// Calls the provided function for each line (with context)
|
||||
module.exports.forEachLine = function forEachLine(lineMetadata, handler) {
|
||||
lineMetadata.forEach(function forMetadata(metadata) {
|
||||
// Parameters: line, lineIndex, inCode, onFence, inTable
|
||||
handler(...metadata);
|
||||
});
|
||||
};
|
||||
|
||||
// Returns (nested) lists as a flat array (in order)
|
||||
module.exports.flattenLists = function flattenLists(params) {
|
||||
const flattenedLists = [];
|
||||
const stack = [];
|
||||
let current = null;
|
||||
let lastWithMap = { "map": [ 0, 1 ] };
|
||||
params.tokens.forEach(function forToken(token) {
|
||||
if ((token.type === "bullet_list_open") ||
|
||||
(token.type === "ordered_list_open")) {
|
||||
// Save current context and start a new one
|
||||
stack.push(current);
|
||||
current = {
|
||||
"unordered": (token.type === "bullet_list_open"),
|
||||
"parentsUnordered": !current ||
|
||||
(current.unordered && current.parentsUnordered),
|
||||
"open": token,
|
||||
"indent": indentFor(token),
|
||||
"parentIndent": (current && current.indent) || 0,
|
||||
"items": [],
|
||||
"nesting": stack.length - 1,
|
||||
"lastLineIndex": -1,
|
||||
"insert": flattenedLists.length
|
||||
};
|
||||
} else if ((token.type === "bullet_list_close") ||
|
||||
(token.type === "ordered_list_close")) {
|
||||
// Finalize current context and restore previous
|
||||
current.lastLineIndex = lastWithMap.map[1];
|
||||
flattenedLists.splice(current.insert, 0, current);
|
||||
delete current.insert;
|
||||
current = stack.pop();
|
||||
} else if (token.type === "list_item_open") {
|
||||
// Add list item
|
||||
current.items.push(token);
|
||||
} else if (token.map) {
|
||||
// Track last token with map
|
||||
lastWithMap = token;
|
||||
}
|
||||
});
|
||||
return flattenedLists;
|
||||
};
|
||||
|
||||
// Calls the provided function for each specified inline child token
|
||||
module.exports.forEachInlineChild =
|
||||
function forEachInlineChild(params, type, handler) {
|
||||
filterTokens(params, "inline", function forToken(token) {
|
||||
token.children.forEach(function forChild(child) {
|
||||
if (child.type === type) {
|
||||
handler(child, token);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Calls the provided function for each heading's content
|
||||
module.exports.forEachHeading = function forEachHeading(params, handler) {
|
||||
let heading = null;
|
||||
params.tokens.forEach(function forToken(token) {
|
||||
if (token.type === "heading_open") {
|
||||
heading = token;
|
||||
} else if (token.type === "heading_close") {
|
||||
heading = null;
|
||||
} else if ((token.type === "inline") && heading) {
|
||||
handler(heading, token.content);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Calls the provided function for each inline code span's content
|
||||
module.exports.forEachInlineCodeSpan =
|
||||
function forEachInlineCodeSpan(input, handler) {
|
||||
let currentLine = 0;
|
||||
let currentColumn = 0;
|
||||
let index = 0;
|
||||
while (index < input.length) {
|
||||
let startIndex = -1;
|
||||
let startLine = -1;
|
||||
let startColumn = -1;
|
||||
let tickCount = 0;
|
||||
let currentTicks = 0;
|
||||
// Deliberate <= so trailing 0 completes the last span (ex: "text `code`")
|
||||
for (; index <= input.length; index++) {
|
||||
const char = input[index];
|
||||
if (char === "`") {
|
||||
// Count backticks at start or end of code span
|
||||
currentTicks++;
|
||||
if ((startIndex === -1) || (startColumn === -1)) {
|
||||
startIndex = index + 1;
|
||||
}
|
||||
} else {
|
||||
if ((startIndex >= 0) &&
|
||||
(startColumn >= 0) &&
|
||||
(tickCount === currentTicks)) {
|
||||
// Found end backticks; invoke callback for code span
|
||||
handler(
|
||||
input.substring(startIndex, index - currentTicks),
|
||||
startLine, startColumn, tickCount);
|
||||
startIndex = -1;
|
||||
startColumn = -1;
|
||||
} else if ((startIndex >= 0) && (startColumn === -1)) {
|
||||
// Found start backticks
|
||||
tickCount = currentTicks;
|
||||
startLine = currentLine;
|
||||
startColumn = currentColumn;
|
||||
}
|
||||
// Not in backticks
|
||||
currentTicks = 0;
|
||||
}
|
||||
if (char === "\n") {
|
||||
// On next line
|
||||
currentLine++;
|
||||
currentColumn = 0;
|
||||
} else if ((char === "\\") &&
|
||||
((startIndex === -1) || (startColumn === -1))) {
|
||||
// Escape character outside code, skip next
|
||||
index++;
|
||||
currentColumn += 2;
|
||||
} else {
|
||||
// On next column
|
||||
currentColumn++;
|
||||
}
|
||||
}
|
||||
if (startIndex >= 0) {
|
||||
// Restart loop after unmatched start backticks (ex: "`text``code``")
|
||||
index = startIndex;
|
||||
currentLine = startLine;
|
||||
currentColumn = startColumn;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Adds a generic error object via the onError callback
|
||||
function addError(onError, lineNumber, detail, context, range) {
|
||||
onError({
|
||||
"lineNumber": lineNumber,
|
||||
"detail": detail,
|
||||
"context": context,
|
||||
"range": range
|
||||
});
|
||||
}
|
||||
module.exports.addError = addError;
|
||||
|
||||
// Adds an error object with details conditionally via the onError callback
|
||||
module.exports.addErrorDetailIf = function addErrorDetailIf(
|
||||
onError, lineNumber, expected, actual, detail, context, range) {
|
||||
if (expected !== actual) {
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
"Expected: " + expected + "; Actual: " + actual +
|
||||
(detail ? "; " + detail : ""),
|
||||
context,
|
||||
range);
|
||||
}
|
||||
};
|
||||
|
||||
// Adds an error object with context via the onError callback
|
||||
module.exports.addErrorContext =
|
||||
function addErrorContext(onError, lineNumber, context, left, right, range) {
|
||||
if (context.length <= 30) {
|
||||
// Nothing to do
|
||||
} else if (left && right) {
|
||||
context = context.substr(0, 15) + "..." + context.substr(-15);
|
||||
} else if (right) {
|
||||
context = "..." + context.substr(-30);
|
||||
} else {
|
||||
context = context.substr(0, 30) + "...";
|
||||
}
|
||||
addError(onError, lineNumber, null, context, range);
|
||||
};
|
||||
|
||||
// Returns a range object for a line by applying a RegExp
|
||||
module.exports.rangeFromRegExp = function rangeFromRegExp(line, regexp) {
|
||||
let range = null;
|
||||
const match = line.match(regexp);
|
||||
if (match) {
|
||||
let column = match.index + 1;
|
||||
let length = match[0].length;
|
||||
if (match[2]) {
|
||||
column += match[1].length;
|
||||
length -= match[1].length;
|
||||
}
|
||||
range = [ column, length ];
|
||||
}
|
||||
return range;
|
||||
};
|
||||
|
||||
// Determines if the front matter includes a title
|
||||
module.exports.frontMatterHasTitle =
|
||||
function frontMatterHasTitle(frontMatterLines, frontMatterTitlePattern) {
|
||||
const ignoreFrontMatter =
|
||||
(frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern;
|
||||
const frontMatterTitleRe =
|
||||
new RegExp(frontMatterTitlePattern || "^\\s*title\\s*[:=]", "i");
|
||||
return !ignoreFrontMatter &&
|
||||
frontMatterLines.some((line) => frontMatterTitleRe.test(line));
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue