Add regular expressions from vscode-markdownlint for error range in results.

This commit is contained in:
David Anson 2016-10-31 22:53:46 -07:00
parent 09ae51fdaa
commit dcf0462c22
12 changed files with 228 additions and 61 deletions

View file

@ -2,6 +2,30 @@
var shared = require("./shared");
// Range regular expressions
var atxClosedHeaderNoSpaceRe = /(?:^#+\S)|(?:\S#+\s*$)/;
var atxClosedHeaderSpaceRe = /(?:^#+\s\s+?\S)|(?:\S\s\s+?#+\s*$)/;
var atxHeaderSpaceRe = /^#+\s*\S/;
var bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/;
var dollarCommandRe = /^(\s*)(\$\s)/;
var emptyLinkRe = /\[[^\]]*\](?=(?:\((?:#?|(?:<>))\))|(?:\[[^\]]*\]))/;
var htmlRe = /<[^>]*>/;
var listItemMarkerRe = /^[\s>]*(?:[\*\+\-]|\d+\.)\s+/;
var reversedLinkRe = /\([^)]+\)\[[^\]^][^\]]*\]/;
var spaceAfterBlockQuote = />\s+\S/;
var spaceBeforeHeaderRe = /^\s+\S/;
var spaceInsideCodeRe = /`(?:(?:\s[^`]*)|(?:[^`]*\s))`/;
var spaceInsideEmphasisRe = /(\*\*?|__?)(?:(?:\s.+)|(?:.+\s))\1/;
var spaceInsideLinkRe = /\[(?:(?:\s[^\]]*)|(?:[^\]]*\s))\](?=\(\S*\))/;
var tabRe = /\t+/;
var trailingPunctuationRe = /.$/;
var trailingSpaceRe = /\s+$/;
var defaultLineLength = 80;
function longLineReFunc(options) {
var lineLength = options.line_length || defaultLineLength;
return new RegExp("^(.{" + lineLength + "})(.*\\s.*)$");
}
// Escapes a string for use in a RegExp
function escapeForRegExp(str) {
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
@ -162,6 +186,7 @@ module.exports = [
"desc": "Header levels should only increment by one level at a time",
"tags": [ "headers" ],
"aliases": [ "header-increment" ],
"regexp": null,
"func": function MD001(params, errors) {
var prevLevel = 0;
filterTokens(params, "heading_open", function forToken(token) {
@ -180,6 +205,7 @@ module.exports = [
"desc": "First header should be a top level header",
"tags": [ "headers" ],
"aliases": [ "first-header-h1" ],
"regexp": null,
"func": function MD002(params, errors) {
var level = params.options.level || 1;
var tag = "h" + level;
@ -198,6 +224,7 @@ module.exports = [
"desc": "Header style",
"tags": [ "headers" ],
"aliases": [ "header-style" ],
"regexp": null,
"func": function MD003(params, errors) {
var style = params.options.style || "consistent";
filterTokens(params, "heading_open", function forToken(token) {
@ -234,6 +261,7 @@ module.exports = [
"desc": "Unordered list style",
"tags": [ "bullet", "ul" ],
"aliases": [ "ul-style" ],
"regexp": listItemMarkerRe,
"func": function MD004(params, errors) {
var style = params.options.style || "consistent";
var expectedStyle = style;
@ -268,6 +296,7 @@ module.exports = [
"desc": "Inconsistent indentation for list items at the same level",
"tags": [ "bullet", "ul", "indentation" ],
"aliases": [ "list-indent" ],
"regexp": listItemMarkerRe,
"func": function MD005(params, errors) {
flattenLists(params).forEach(function forList(list) {
var indent = indentFor(list.items[0]);
@ -283,6 +312,7 @@ module.exports = [
"desc": "Consider starting bulleted lists at the beginning of the line",
"tags": [ "bullet", "ul", "indentation" ],
"aliases": [ "ul-start-left" ],
"regexp": listItemMarkerRe,
"func": function MD006(params, errors) {
flattenLists(params).forEach(function forList(list) {
if (list.unordered && !list.nesting) {
@ -297,6 +327,7 @@ module.exports = [
"desc": "Unordered list indentation",
"tags": [ "bullet", "ul", "indentation" ],
"aliases": [ "ul-indent" ],
"regexp": listItemMarkerRe,
"func": function MD007(params, errors) {
var optionsIndent = params.options.indent || 2;
var prevIndent = 0;
@ -318,10 +349,11 @@ module.exports = [
"desc": "Trailing spaces",
"tags": [ "whitespace" ],
"aliases": [ "no-trailing-spaces" ],
"regexp": trailingSpaceRe,
"func": function MD009(params, errors) {
var brSpaces = params.options.br_spaces || 0;
params.lines.forEach(function forLine(line, lineIndex) {
if (/\s$/.test(line)) {
if (trailingSpaceRe.test(line)) {
var expected = (brSpaces < 2) ? 0 : brSpaces;
errors.addDetailIf(lineIndex + 1,
expected, line.length - line.trimRight().length);
@ -335,11 +367,12 @@ module.exports = [
"desc": "Hard tabs",
"tags": [ "whitespace", "hard_tab" ],
"aliases": [ "no-hard-tabs" ],
"regexp": tabRe,
"func": function MD010(params, errors) {
var codeBlocks = params.options.code_blocks;
var includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (/\t/.test(line) && (!inCode || includeCodeBlocks)) {
if (tabRe.test(line) && (!inCode || includeCodeBlocks)) {
errors.addDetail(lineIndex + 1,
"Column: " + (line.indexOf("\t") + 1));
}
@ -352,9 +385,10 @@ module.exports = [
"desc": "Reversed link syntax",
"tags": [ "links" ],
"aliases": [ "no-reversed-links" ],
"regexp": reversedLinkRe,
"func": function MD011(params, errors) {
forEachInlineChild(params, "text", function forToken(token) {
var match = /\([^)]+\)\[[^\]^][^\]]*\]/.exec(token.content);
var match = reversedLinkRe.exec(token.content);
if (match) {
errors.addDetail(token.lineNumber, match[0]);
}
@ -367,6 +401,7 @@ module.exports = [
"desc": "Multiple consecutive blank lines",
"tags": [ "whitespace", "blank_lines" ],
"aliases": [ "no-multiple-blanks" ],
"regexp": null,
"func": function MD012(params, errors) {
var maximum = params.options.maximum || 1;
var count = 0;
@ -384,13 +419,14 @@ module.exports = [
"desc": "Line length",
"tags": [ "line_length" ],
"aliases": [ "line-length" ],
"regexp": longLineReFunc,
"func": function MD013(params, errors) {
var lineLength = params.options.line_length || 80;
var lineLength = params.options.line_length || defaultLineLength;
var codeBlocks = params.options.code_blocks;
var includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
var tables = params.options.tables;
var includeTables = (tables === undefined) ? true : !!tables;
var re = new RegExp("^.{" + lineLength + "}.*\\s");
var re = longLineReFunc(params.options);
forEachLine(params,
function forLine(line, lineIndex, inCode, onFence, inTable) {
if ((includeCodeBlocks || !inCode) &&
@ -407,13 +443,14 @@ module.exports = [
"desc": "Dollar signs used before commands without showing output",
"tags": [ "code" ],
"aliases": [ "commands-show-output" ],
"regexp": dollarCommandRe,
"func": function MD014(params, errors) {
[ "code_block", "fence" ].forEach(function forType(type) {
filterTokens(params, type, function forToken(token) {
var allBlank = true;
if (token.content && token.content.split(shared.newLineRe)
.every(function forLine(line) {
return !line || (allBlank = false) || /^\$\s/.test(line);
return !line || (allBlank = false) || dollarCommandRe.test(line);
}) && !allBlank) {
errors.addContext(token.lineNumber,
token.content.split(shared.newLineRe)[0].trim());
@ -428,6 +465,7 @@ module.exports = [
"desc": "No space after hash on atx style header",
"tags": [ "headers", "atx", "spaces" ],
"aliases": [ "no-missing-space-atx" ],
"regexp": atxHeaderSpaceRe,
"func": function MD018(params, errors) {
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
@ -442,6 +480,7 @@ module.exports = [
"desc": "Multiple spaces after hash on atx style header",
"tags": [ "headers", "atx", "spaces" ],
"aliases": [ "no-multiple-space-atx" ],
"regexp": atxHeaderSpaceRe,
"func": function MD019(params, errors) {
filterTokens(params, "heading_open", function forToken(token) {
if ((headingStyleFor(token) === "atx") &&
@ -457,6 +496,7 @@ module.exports = [
"desc": "No space inside hashes on closed atx style header",
"tags": [ "headers", "atx_closed", "spaces" ],
"aliases": [ "no-missing-space-closed-atx" ],
"regexp": atxClosedHeaderNoSpaceRe,
"func": function MD020(params, errors) {
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line)) {
@ -475,6 +515,7 @@ module.exports = [
"desc": "Multiple spaces inside hashes on closed atx style header",
"tags": [ "headers", "atx_closed", "spaces" ],
"aliases": [ "no-multiple-space-closed-atx" ],
"regexp": atxClosedHeaderSpaceRe,
"func": function MD021(params, errors) {
filterTokens(params, "heading_open", function forToken(token) {
if (headingStyleFor(token) === "atx_closed") {
@ -493,6 +534,7 @@ module.exports = [
"desc": "Headers should be surrounded by blank lines",
"tags": [ "headers", "blank_lines" ],
"aliases": [ "blanks-around-headers" ],
"regexp": null,
"func": function MD022(params, errors) {
var prevHeadingLineNumber = 0;
var prevMaxLineIndex = -1;
@ -527,9 +569,10 @@ module.exports = [
"desc": "Headers must start at the beginning of the line",
"tags": [ "headers", "spaces" ],
"aliases": [ "header-start-left" ],
"regexp": spaceBeforeHeaderRe,
"func": function MD023(params, errors) {
filterTokens(params, "heading_open", function forToken(token) {
if (/^\s/.test(token.line)) {
if (spaceBeforeHeaderRe.test(token.line)) {
errors.addContext(token.lineNumber, token.line);
}
});
@ -541,6 +584,7 @@ module.exports = [
"desc": "Multiple headers with the same content",
"tags": [ "headers" ],
"aliases": [ "no-duplicate-header" ],
"regexp": null,
"func": function MD024(params, errors) {
var knownContent = [];
forEachHeading(params, function forHeading(heading, content) {
@ -558,6 +602,7 @@ module.exports = [
"desc": "Multiple top level headers in the same document",
"tags": [ "headers" ],
"aliases": [ "single-h1" ],
"regexp": null,
"func": function MD025(params, errors) {
var level = params.options.level || 1;
var tag = "h" + level;
@ -579,6 +624,7 @@ module.exports = [
"desc": "Trailing punctuation in header",
"tags": [ "headers" ],
"aliases": [ "no-trailing-punctuation" ],
"regexp": trailingPunctuationRe,
"func": function MD026(params, errors) {
var punctuation = params.options.punctuation || ".,;:!?";
var re = new RegExp("[" + punctuation + "]$");
@ -597,6 +643,7 @@ module.exports = [
"desc": "Multiple spaces after blockquote symbol",
"tags": [ "blockquote", "whitespace", "indentation" ],
"aliases": [ "no-multiple-space-blockquote" ],
"regexp": spaceAfterBlockQuote,
"func": function MD027(params, errors) {
var blockquoteNesting = 0;
var listItemNesting = 0;
@ -632,6 +679,7 @@ module.exports = [
"desc": "Blank line inside blockquote",
"tags": [ "blockquote", "whitespace" ],
"aliases": [ "no-blanks-blockquote" ],
"regexp": null,
"func": function MD028(params, errors) {
var prevToken = {};
params.tokens.forEach(function forToken(token) {
@ -649,6 +697,7 @@ module.exports = [
"desc": "Ordered list item prefix",
"tags": [ "ol" ],
"aliases": [ "ol-prefix" ],
"regexp": listItemMarkerRe,
"func": function MD029(params, errors) {
var style = params.options.style || "one";
flattenLists(params).forEach(function forList(list) {
@ -672,6 +721,7 @@ module.exports = [
"desc": "Spaces after list markers",
"tags": [ "ol", "ul", "whitespace" ],
"aliases": [ "list-marker-space" ],
"regexp": listItemMarkerRe,
"func": function MD030(params, errors) {
var ulSingle = params.options.ul_single || 1;
var olSingle = params.options.ol_single || 1;
@ -697,6 +747,7 @@ module.exports = [
"desc": "Fenced code blocks should be surrounded by blank lines",
"tags": [ "code", "blank_lines" ],
"aliases": [ "blanks-around-fences" ],
"regexp": null,
"func": function MD031(params, errors) {
var lines = params.lines;
forEachLine(params, function forLine(line, i, inCode, onFence) {
@ -713,13 +764,14 @@ module.exports = [
"desc": "Lists should be surrounded by blank lines",
"tags": [ "bullet", "ul", "ol", "blank_lines" ],
"aliases": [ "blanks-around-lists" ],
"regexp": null,
"func": function MD032(params, errors) {
var inList = false;
var prevLine = "";
forEachLine(params, function forLine(line, lineIndex, inCode, onFence) {
if (!inCode || onFence) {
var lineTrim = line.trim();
var listMarker = /^([\*\+\-]|(\d+\.))\s/.test(lineTrim);
var listMarker = listItemMarkerRe.test(lineTrim);
if (listMarker && !inList && !/^($|\s)/.test(prevLine)) {
errors.addContext(lineIndex + 1, lineTrim);
} else if (!listMarker && inList && !/^($|\s)/.test(line)) {
@ -737,6 +789,7 @@ module.exports = [
"desc": "Inline HTML",
"tags": [ "html" ],
"aliases": [ "no-inline-html" ],
"regexp": htmlRe,
"func": function MD033(params, errors) {
var allowedElements = (params.options.allowed_elements || [])
.map(function forElement(element) {
@ -771,6 +824,7 @@ module.exports = [
"desc": "Bare URL used",
"tags": [ "links", "url" ],
"aliases": [ "no-bare-urls" ],
"regexp": bareUrlRe,
"func": function MD034(params, errors) {
filterTokens(params, "inline", function forToken(token) {
var inLink = false;
@ -782,7 +836,7 @@ module.exports = [
inLink = false;
} else if ((child.type === "text") &&
!inLink &&
(match = /(http|ftp)s?:\/\/[^\s]*/.exec(child.content))) {
(match = bareUrlRe.exec(child.content))) {
errors.addContext(child.lineNumber, match[0]);
}
});
@ -795,6 +849,7 @@ module.exports = [
"desc": "Horizontal rule style",
"tags": [ "hr" ],
"aliases": [ "hr-style" ],
"regexp": null,
"func": function MD035(params, errors) {
var style = params.options.style || "consistent";
filterTokens(params, "hr", function forToken(token) {
@ -812,6 +867,7 @@ module.exports = [
"desc": "Emphasis used instead of a header",
"tags": [ "headers", "emphasis" ],
"aliases": [ "no-emphasis-as-header" ],
"regexp": null,
"func": function MD036(params, errors) {
var punctuation = params.options.punctuation || ".,;:!?";
var re = new RegExp("[" + punctuation + "]$");
@ -853,6 +909,7 @@ module.exports = [
"desc": "Spaces inside emphasis markers",
"tags": [ "whitespace", "emphasis" ],
"aliases": [ "no-space-in-emphasis" ],
"regexp": spaceInsideEmphasisRe,
"func": function MD037(params, errors) {
forEachInlineChild(params, "text", function forToken(token) {
var left = /\s(\*\*?|__?)\s.+\1/.exec(token.content);
@ -871,6 +928,7 @@ module.exports = [
"desc": "Spaces inside code span elements",
"tags": [ "whitespace", "code" ],
"aliases": [ "no-space-in-code" ],
"regexp": spaceInsideCodeRe,
"func": function MD038(params, errors) {
forEachInlineChild(params, "code_inline",
function forToken(token, inline) {
@ -893,6 +951,7 @@ module.exports = [
"desc": "Spaces inside link text",
"tags": [ "whitespace", "links" ],
"aliases": [ "no-space-in-links" ],
"regexp": spaceInsideLinkRe,
"func": function MD039(params, errors) {
filterTokens(params, "inline", function forToken(token) {
var inLink = false;
@ -922,6 +981,7 @@ module.exports = [
"desc": "Fenced code blocks should have a language specified",
"tags": [ "code", "language" ],
"aliases": [ "fenced-code-language" ],
"regexp": null,
"func": function MD040(params, errors) {
filterTokens(params, "fence", function forToken(token) {
if (!token.info.trim()) {
@ -936,6 +996,7 @@ module.exports = [
"desc": "First line in file should be a top level header",
"tags": [ "headers" ],
"aliases": [ "first-line-h1" ],
"regexp": null,
"func": function MD041(params, errors) {
var level = params.options.level || 1;
var tag = "h" + level;
@ -962,6 +1023,7 @@ module.exports = [
"desc": "No empty links",
"tags": [ "links" ],
"aliases": [ "no-empty-links" ],
"regexp": emptyLinkRe,
"func": function MD042(params, errors) {
filterTokens(params, "inline", function forToken(token) {
var inLink = false;
@ -994,6 +1056,7 @@ module.exports = [
"desc": "Required header structure",
"tags": [ "headers" ],
"aliases": [ "required-headers" ],
"regexp": null,
"func": function MD043(params, errors) {
var requiredHeaders = params.options.headers;
if (requiredHeaders) {