mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-16 14:00:13 +01:00
Merge fixInfo branch to introduce automatic fix ability for built-in and custom rules (fixes #80).
This commit is contained in:
commit
fa9e08cf53
63 changed files with 2247 additions and 266 deletions
11
README.md
11
README.md
|
|
@ -416,15 +416,20 @@ Specifies which version of the `result` object to return (see the "Usage" sectio
|
|||
below for examples).
|
||||
|
||||
Passing a `resultVersion` of `0` corresponds to the original, simple format where
|
||||
each error is identified by rule name and line number. This is deprecated.
|
||||
each error is identified by rule name and line number. *This is deprecated.*
|
||||
|
||||
Passing a `resultVersion` of `1` corresponds to a detailed format where each error
|
||||
includes information about the line number, rule name, alias, description, as well
|
||||
as any additional detail or context that is available. This is deprecated.
|
||||
as any additional detail or context that is available. *This is deprecated.*
|
||||
|
||||
Passing a `resultVersion` of `2` corresponds to a detailed format where each error
|
||||
includes information about the line number, rule names, description, as well as any
|
||||
additional detail or context that is available. This is the default.
|
||||
additional detail or context that is available. *This is the default.*
|
||||
|
||||
Passing a `resultVersion` of `3` corresponds to the detailed version `2` format
|
||||
with additional information about how to fix automatically-fixable errors. In this
|
||||
mode, all errors that occur on each line are reported (other versions report only
|
||||
the first error for each rule).
|
||||
|
||||
##### options.markdownItPlugins
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ A rule is implemented as an `Object` with four required properties:
|
|||
- `function` is a synchronous `Function` that implements the rule and is passed two parameters:
|
||||
- `params` is an `Object` with properties that describe the content being analyzed:
|
||||
- `name` is a `String` that identifies the input file/string.
|
||||
- `tokens` is an `Array` of [`markdown-it` `Token` objects](https://markdown-it.github.io/markdown-it/#Token) with added `line` and `lineNumber` properties.
|
||||
- `tokens` is an `Array` of [`markdown-it` `Token` objects](https://markdown-it.github.io/markdown-it/#Token)
|
||||
with added `line` and `lineNumber` properties.
|
||||
- `lines` is an `Array` of `String` values corresponding to the lines of the input file/string.
|
||||
- `frontMatterLines` is an `Array` of `String` values corresponding to any front matter (not present in `lines`).
|
||||
- `config` is an `Object` corresponding to the rule's entry in `options.config` (if present).
|
||||
|
|
@ -52,6 +53,14 @@ A rule is implemented as an `Object` with four required properties:
|
|||
- `details` is an optional `String` with information about what caused the error.
|
||||
- `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.
|
||||
- `fixInfo` is an optional `Object` with information about how to fix the error (all properties are optional, but
|
||||
at least one of `deleteCount` and `insertText` should be present; when applying a fix, the delete should be
|
||||
performed before the insert):
|
||||
- `lineNumber` is an optional `Number` specifying the 1-based line number of the edit.
|
||||
- `editColumn` is an optional `Number` specifying the 1-based column number of the edit.
|
||||
- `deleteCount` is an optional `Number` specifying the count of characters to delete.
|
||||
- `insertText` is an optional `String` specifying the text to insert. `\n` is the platform-independent way to add
|
||||
a line break; line breaks should be added at the beginning of a line instead of at the end).
|
||||
|
||||
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).
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const os = require("os");
|
||||
|
||||
// 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]/;
|
||||
const newLineRe = /\r\n?|\n/g;
|
||||
module.exports.newLineRe = newLineRe;
|
||||
|
||||
// Regular expression for matching common front matter (YAML and TOML)
|
||||
module.exports.frontMatterRe =
|
||||
|
|
@ -18,8 +21,7 @@ const inlineCommentRe =
|
|||
module.exports.inlineCommentRe = inlineCommentRe;
|
||||
|
||||
// Regular expressions for range matching
|
||||
module.exports.atxHeadingSpaceRe = /^#+\s*\S/;
|
||||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/i;
|
||||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/ig;
|
||||
module.exports.listItemMarkerRe = /^[\s>]*(?:[*+-]|\d+[.)])\s+/;
|
||||
module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
|
||||
|
||||
|
|
@ -44,6 +46,11 @@ module.exports.isEmptyString = function isEmptyString(str) {
|
|||
return str.length === 0;
|
||||
};
|
||||
|
||||
// Returns true iff the input is an object
|
||||
module.exports.isObject = function isObject(obj) {
|
||||
return (obj !== null) && (typeof obj === "object") && !Array.isArray(obj);
|
||||
};
|
||||
|
||||
// Returns true iff the input line is blank (no content)
|
||||
// Example: Contains nothing, whitespace, or comments
|
||||
const blankLineRe = />|(?:<!--.*?-->)/g;
|
||||
|
|
@ -312,7 +319,8 @@ module.exports.forEachInlineCodeSpan =
|
|||
currentLine++;
|
||||
currentColumn = 0;
|
||||
} else if ((char === "\\") &&
|
||||
((startIndex === -1) || (startColumn === -1))) {
|
||||
((startIndex === -1) || (startColumn === -1)) &&
|
||||
(input[index + 1] !== "\n")) {
|
||||
// Escape character outside code, skip next
|
||||
index++;
|
||||
currentColumn += 2;
|
||||
|
|
@ -331,19 +339,20 @@ module.exports.forEachInlineCodeSpan =
|
|||
};
|
||||
|
||||
// Adds a generic error object via the onError callback
|
||||
function addError(onError, lineNumber, detail, context, range) {
|
||||
function addError(onError, lineNumber, detail, context, range, fixInfo) {
|
||||
onError({
|
||||
"lineNumber": lineNumber,
|
||||
"detail": detail,
|
||||
"context": context,
|
||||
"range": range
|
||||
lineNumber,
|
||||
detail,
|
||||
context,
|
||||
range,
|
||||
fixInfo
|
||||
});
|
||||
}
|
||||
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) {
|
||||
onError, lineNumber, expected, actual, detail, context, range, fixInfo) {
|
||||
if (expected !== actual) {
|
||||
addError(
|
||||
onError,
|
||||
|
|
@ -351,24 +360,25 @@ module.exports.addErrorDetailIf = function addErrorDetailIf(
|
|||
"Expected: " + expected + "; Actual: " + actual +
|
||||
(detail ? "; " + detail : ""),
|
||||
context,
|
||||
range);
|
||||
range,
|
||||
fixInfo);
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
module.exports.addErrorContext = function addErrorContext(
|
||||
onError, lineNumber, context, left, right, range, fixInfo) {
|
||||
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, fixInfo);
|
||||
};
|
||||
|
||||
// Returns a range object for a line by applying a RegExp
|
||||
module.exports.rangeFromRegExp = function rangeFromRegExp(line, regexp) {
|
||||
|
|
@ -396,3 +406,127 @@ module.exports.frontMatterHasTitle =
|
|||
return !ignoreFrontMatter &&
|
||||
frontMatterLines.some((line) => frontMatterTitleRe.test(line));
|
||||
};
|
||||
|
||||
// Gets the most common line ending, falling back to platform default
|
||||
function getPreferredLineEnding(input) {
|
||||
let cr = 0;
|
||||
let lf = 0;
|
||||
let crlf = 0;
|
||||
const endings = input.match(newLineRe) || [];
|
||||
endings.forEach((ending) => {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (ending) {
|
||||
case "\r":
|
||||
cr++;
|
||||
break;
|
||||
case "\n":
|
||||
lf++;
|
||||
break;
|
||||
case "\r\n":
|
||||
crlf++;
|
||||
break;
|
||||
}
|
||||
});
|
||||
let preferredLineEnding = null;
|
||||
if (!cr && !lf && !crlf) {
|
||||
preferredLineEnding = os.EOL;
|
||||
} else if ((lf >= crlf) && (lf >= cr)) {
|
||||
preferredLineEnding = "\n";
|
||||
} else if (crlf >= cr) {
|
||||
preferredLineEnding = "\r\n";
|
||||
} else {
|
||||
preferredLineEnding = "\r";
|
||||
}
|
||||
return preferredLineEnding;
|
||||
}
|
||||
module.exports.getPreferredLineEnding = getPreferredLineEnding;
|
||||
|
||||
// Normalizes the fields of a fixInfo object
|
||||
function normalizeFixInfo(fixInfo, lineNumber) {
|
||||
return {
|
||||
"lineNumber": fixInfo.lineNumber || lineNumber,
|
||||
"editColumn": fixInfo.editColumn || 1,
|
||||
"deleteCount": fixInfo.deleteCount || 0,
|
||||
"insertText": fixInfo.insertText || ""
|
||||
};
|
||||
}
|
||||
|
||||
// Fixes the specifide error on a line
|
||||
function applyFix(line, fixInfo, lineEnding) {
|
||||
const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo);
|
||||
const editIndex = editColumn - 1;
|
||||
return (deleteCount === -1) ?
|
||||
null :
|
||||
line.slice(0, editIndex) +
|
||||
insertText.replace(/\n/g, lineEnding || "\n") +
|
||||
line.slice(editIndex + deleteCount);
|
||||
}
|
||||
module.exports.applyFix = applyFix;
|
||||
|
||||
// Applies as many fixes as possible to the input lines
|
||||
module.exports.applyFixes = function applyFixes(input, errors) {
|
||||
const lineEnding = getPreferredLineEnding(input);
|
||||
const lines = input.split(newLineRe);
|
||||
// Normalize fixInfo objects
|
||||
let fixInfos = errors
|
||||
.filter((error) => error.fixInfo)
|
||||
.map((error) => normalizeFixInfo(error.fixInfo, error.lineNumber));
|
||||
// Sort bottom-to-top, line-deletes last, right-to-left, long-to-short
|
||||
fixInfos.sort((a, b) => {
|
||||
const aDeletingLine = (a.deleteCount === -1);
|
||||
const bDeletingLine = (b.deleteCount === -1);
|
||||
return (
|
||||
(b.lineNumber - a.lineNumber) ||
|
||||
(aDeletingLine ? 1 : (bDeletingLine ? -1 : 0)) ||
|
||||
(b.editColumn - a.editColumn) ||
|
||||
(b.insertText.length - a.insertText.length)
|
||||
);
|
||||
});
|
||||
// Remove duplicate entries (needed for following collapse step)
|
||||
let lastFixInfo = {};
|
||||
fixInfos = fixInfos.filter((fixInfo) => {
|
||||
const unique = (
|
||||
(fixInfo.lineNumber !== lastFixInfo.lineNumber) ||
|
||||
(fixInfo.editColumn !== lastFixInfo.editColumn) ||
|
||||
(fixInfo.deleteCount !== lastFixInfo.deleteCount) ||
|
||||
(fixInfo.insertText !== lastFixInfo.insertText)
|
||||
);
|
||||
lastFixInfo = fixInfo;
|
||||
return unique;
|
||||
});
|
||||
// Collapse insert/no-delete and no-insert/delete for same line/column
|
||||
lastFixInfo = {};
|
||||
fixInfos.forEach((fixInfo) => {
|
||||
if (
|
||||
(fixInfo.lineNumber === lastFixInfo.lineNumber) &&
|
||||
(fixInfo.editColumn === lastFixInfo.editColumn) &&
|
||||
!fixInfo.insertText &&
|
||||
(fixInfo.deleteCount > 0) &&
|
||||
lastFixInfo.insertText &&
|
||||
!lastFixInfo.deleteCount) {
|
||||
fixInfo.insertText = lastFixInfo.insertText;
|
||||
lastFixInfo.lineNumber = 0;
|
||||
}
|
||||
lastFixInfo = fixInfo;
|
||||
});
|
||||
fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber);
|
||||
// Apply all (remaining/updated) fixes
|
||||
let lastLineIndex = -1;
|
||||
let lastEditIndex = -1;
|
||||
fixInfos.forEach((fixInfo) => {
|
||||
const { lineNumber, editColumn, deleteCount } = fixInfo;
|
||||
const lineIndex = lineNumber - 1;
|
||||
const editIndex = editColumn - 1;
|
||||
if (
|
||||
(lineIndex !== lastLineIndex) ||
|
||||
((editIndex + deleteCount) < lastEditIndex) ||
|
||||
(deleteCount === -1)
|
||||
) {
|
||||
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
|
||||
}
|
||||
lastLineIndex = lineIndex;
|
||||
lastEditIndex = editIndex;
|
||||
});
|
||||
// Return corrected input
|
||||
return lines.filter((line) => line !== null).join(lineEnding);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -172,11 +172,20 @@ function annotateTokens(tokens, lines) {
|
|||
}
|
||||
// Annotate children with lineNumber
|
||||
let lineNumber = token.lineNumber;
|
||||
const codeSpanExtraLines = [];
|
||||
helpers.forEachInlineCodeSpan(
|
||||
token.content,
|
||||
function handleInlineCodeSpan(code) {
|
||||
codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1);
|
||||
}
|
||||
);
|
||||
(token.children || []).forEach(function forChild(child) {
|
||||
child.lineNumber = lineNumber;
|
||||
child.line = lines[lineNumber - 1];
|
||||
if ((child.type === "softbreak") || (child.type === "hardbreak")) {
|
||||
lineNumber++;
|
||||
} else if (child.type === "code_inline") {
|
||||
lineNumber += codeSpanExtraLines.shift();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -296,6 +305,11 @@ function lineNumberComparison(a, b) {
|
|||
return a.lineNumber - b.lineNumber;
|
||||
}
|
||||
|
||||
// Function to return true for all inputs
|
||||
function filterAllValues() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function to return unique values from a sorted errors array
|
||||
function uniqueFilterForSortedErrors(value, index, array) {
|
||||
return (index === 0) || (value.lineNumber > array[index - 1].lineNumber);
|
||||
|
|
@ -377,11 +391,43 @@ function lintContent(
|
|||
lines[errorInfo.lineNumber - 1].length))) {
|
||||
throwError("range");
|
||||
}
|
||||
const fixInfo = errorInfo.fixInfo;
|
||||
if (fixInfo) {
|
||||
if (!helpers.isObject(fixInfo)) {
|
||||
throwError("fixInfo");
|
||||
}
|
||||
if ((fixInfo.lineNumber !== undefined) &&
|
||||
(!helpers.isNumber(fixInfo.lineNumber) ||
|
||||
(fixInfo.lineNumber < 1) ||
|
||||
(fixInfo.lineNumber > lines.length))) {
|
||||
throwError("fixInfo.lineNumber");
|
||||
}
|
||||
const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
|
||||
if ((fixInfo.editColumn !== undefined) &&
|
||||
(!helpers.isNumber(fixInfo.editColumn) ||
|
||||
(fixInfo.editColumn < 1) ||
|
||||
(fixInfo.editColumn >
|
||||
lines[effectiveLineNumber - 1].length + 1))) {
|
||||
throwError("fixInfo.editColumn");
|
||||
}
|
||||
if ((fixInfo.deleteCount !== undefined) &&
|
||||
(!helpers.isNumber(fixInfo.deleteCount) ||
|
||||
(fixInfo.deleteCount < -1) ||
|
||||
(fixInfo.deleteCount >
|
||||
lines[effectiveLineNumber - 1].length))) {
|
||||
throwError("fixInfo.deleteCount");
|
||||
}
|
||||
if ((fixInfo.insertText !== undefined) &&
|
||||
!helpers.isString(fixInfo.insertText)) {
|
||||
throwError("fixInfo.insertText");
|
||||
}
|
||||
}
|
||||
errors.push({
|
||||
"lineNumber": errorInfo.lineNumber + frontMatterLines.length,
|
||||
"detail": errorInfo.detail || null,
|
||||
"context": errorInfo.context || null,
|
||||
"range": errorInfo.range || null
|
||||
"range": errorInfo.range || null,
|
||||
"fixInfo": errorInfo.fixInfo || null
|
||||
});
|
||||
}
|
||||
// Call (possibly external) rule function
|
||||
|
|
@ -401,7 +447,9 @@ function lintContent(
|
|||
if (errors.length) {
|
||||
errors.sort(lineNumberComparison);
|
||||
const filteredErrors = errors
|
||||
.filter(uniqueFilterForSortedErrors)
|
||||
.filter((resultVersion === 3) ?
|
||||
filterAllValues :
|
||||
uniqueFilterForSortedErrors)
|
||||
.filter(function removeDisabledRules(error) {
|
||||
return enabledRulesPerLineNumber[error.lineNumber][ruleName];
|
||||
})
|
||||
|
|
@ -423,6 +471,9 @@ function lintContent(
|
|||
errorObject.errorDetail = error.detail;
|
||||
errorObject.errorContext = error.context;
|
||||
errorObject.errorRange = error.range;
|
||||
if (resultVersion === 3) {
|
||||
errorObject.fixInfo = error.fixInfo;
|
||||
}
|
||||
return errorObject;
|
||||
});
|
||||
if (filteredErrors.length) {
|
||||
|
|
|
|||
17
lib/md006.js
17
lib/md006.js
|
|
@ -13,10 +13,19 @@ module.exports = {
|
|||
"tags": [ "bullet", "ul", "indentation" ],
|
||||
"function": function MD006(params, onError) {
|
||||
flattenedLists().forEach((list) => {
|
||||
if (list.unordered && !list.nesting) {
|
||||
addErrorDetailIf(onError, list.open.lineNumber,
|
||||
0, list.indent, null, null,
|
||||
rangeFromRegExp(list.open.line, listItemMarkerRe));
|
||||
if (list.unordered && !list.nesting && (list.indent !== 0)) {
|
||||
const { lineNumber, line } = list.open;
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineNumber,
|
||||
0,
|
||||
list.indent,
|
||||
null,
|
||||
null,
|
||||
rangeFromRegExp(line, listItemMarkerRe),
|
||||
{
|
||||
"deleteCount": line.length - line.trimLeft().length
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
24
lib/md009.js
24
lib/md009.js
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addError, filterTokens, forEachLine, includesSorted, rangeFromRegExp } =
|
||||
const { addError, filterTokens, forEachLine, includesSorted } =
|
||||
require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
const trailingSpaceRe = /\s+$/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD009", "no-trailing-spaces" ],
|
||||
"description": "Trailing spaces",
|
||||
|
|
@ -34,14 +32,22 @@ module.exports = {
|
|||
forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => {
|
||||
inFencedCode += onFence;
|
||||
const lineNumber = lineIndex + 1;
|
||||
if ((!inCode || inFencedCode) && trailingSpaceRe.test(line) &&
|
||||
const trailingSpaces = line.length - line.trimRight().length;
|
||||
if ((!inCode || inFencedCode) && trailingSpaces &&
|
||||
!includesSorted(listItemLineNumbers, lineNumber)) {
|
||||
const actual = line.length - line.trimRight().length;
|
||||
if (expected !== actual) {
|
||||
addError(onError, lineNumber,
|
||||
if (expected !== trailingSpaces) {
|
||||
const column = line.length - trailingSpaces + 1;
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
"Expected: " + (expected === 0 ? "" : "0 or ") +
|
||||
expected + "; Actual: " + actual,
|
||||
null, rangeFromRegExp(line, trailingSpaceRe));
|
||||
expected + "; Actual: " + trailingSpaces,
|
||||
null,
|
||||
[ column, trailingSpaces ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": trailingSpaces
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
24
lib/md010.js
24
lib/md010.js
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addError, forEachLine, rangeFromRegExp } = require("../helpers");
|
||||
const { addError, forEachLine } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
const tabRe = /\t+/;
|
||||
const tabRe = /\t+/g;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD010", "no-hard-tabs" ],
|
||||
|
|
@ -15,9 +15,23 @@ module.exports = {
|
|||
const codeBlocks = params.config.code_blocks;
|
||||
const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
||||
if (tabRe.test(line) && (!inCode || includeCodeBlocks)) {
|
||||
addError(onError, lineIndex + 1, "Column: " + (line.indexOf("\t") + 1),
|
||||
null, rangeFromRegExp(line, tabRe));
|
||||
if (!inCode || includeCodeBlocks) {
|
||||
let match = null;
|
||||
while ((match = tabRe.exec(line)) !== null) {
|
||||
const column = match.index + 1;
|
||||
const length = match[0].length;
|
||||
addError(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
"Column: " + column,
|
||||
null,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": "".padEnd(length)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
30
lib/md011.js
30
lib/md011.js
|
|
@ -2,20 +2,36 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addError, forEachInlineChild, rangeFromRegExp } = require("../helpers");
|
||||
const { addError, forEachInlineChild, unescapeMarkdown } =
|
||||
require("../helpers");
|
||||
|
||||
const reversedLinkRe = /\([^)]+\)\[[^\]^][^\]]*]/;
|
||||
const reversedLinkRe = /\(([^)]+)\)\[([^\]^][^\]]*)]/g;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD011", "no-reversed-links" ],
|
||||
"description": "Reversed link syntax",
|
||||
"tags": [ "links" ],
|
||||
"function": function MD011(params, onError) {
|
||||
forEachInlineChild(params, "text", function forToken(token) {
|
||||
const match = reversedLinkRe.exec(token.content);
|
||||
if (match) {
|
||||
addError(onError, token.lineNumber, match[0], null,
|
||||
rangeFromRegExp(token.line, reversedLinkRe));
|
||||
forEachInlineChild(params, "text", (token) => {
|
||||
const { lineNumber, content } = token;
|
||||
let match = null;
|
||||
while ((match = reversedLinkRe.exec(content)) !== null) {
|
||||
const [ reversedLink, linkText, linkDestination ] = match;
|
||||
const line = params.lines[lineNumber - 1];
|
||||
const column = unescapeMarkdown(line).indexOf(reversedLink) + 1;
|
||||
const length = reversedLink.length;
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
reversedLink,
|
||||
null,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": `[${linkText}](${linkDestination})`
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
12
lib/md012.js
12
lib/md012.js
|
|
@ -15,7 +15,17 @@ module.exports = {
|
|||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
||||
count = (inCode || line.trim().length) ? 0 : count + 1;
|
||||
if (maximum < count) {
|
||||
addErrorDetailIf(onError, lineIndex + 1, maximum, count);
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
maximum,
|
||||
count,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"deleteCount": -1
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
20
lib/md018.js
20
lib/md018.js
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, atxHeadingSpaceRe, forEachLine,
|
||||
rangeFromRegExp } = require("../helpers");
|
||||
const { addErrorContext, forEachLine } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -12,9 +11,20 @@ module.exports = {
|
|||
"tags": [ "headings", "headers", "atx", "spaces" ],
|
||||
"function": function MD018(params, onError) {
|
||||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
||||
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
|
||||
addErrorContext(onError, lineIndex + 1, line.trim(), null,
|
||||
null, rangeFromRegExp(line, atxHeadingSpaceRe));
|
||||
if (!inCode && /^#+[^#\s]/.test(line) && !/#\s*$/.test(line)) {
|
||||
const hashCount = /^#+/.exec(line)[0].length;
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
line.trim(),
|
||||
null,
|
||||
null,
|
||||
[ 1, hashCount + 1 ],
|
||||
{
|
||||
"editColumn": hashCount + 1,
|
||||
"insertText": " "
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
33
lib/md019.js
33
lib/md019.js
|
|
@ -2,20 +2,37 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, atxHeadingSpaceRe, filterTokens, headingStyleFor,
|
||||
rangeFromRegExp } = require("../helpers");
|
||||
const { addErrorContext, filterTokens, headingStyleFor } =
|
||||
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) {
|
||||
filterTokens(params, "heading_open", function forToken(token) {
|
||||
if ((headingStyleFor(token) === "atx") &&
|
||||
/^#+\s\s/.test(token.line)) {
|
||||
addErrorContext(onError, token.lineNumber, token.line.trim(),
|
||||
null, null,
|
||||
rangeFromRegExp(token.line, atxHeadingSpaceRe));
|
||||
filterTokens(params, "heading_open", (token) => {
|
||||
if (headingStyleFor(token) === "atx") {
|
||||
const { line, lineNumber } = token;
|
||||
const match = /^(#+)(\s{2,})(?:\S)/.exec(line);
|
||||
if (match) {
|
||||
const [
|
||||
,
|
||||
{ "length": hashLength },
|
||||
{ "length": spacesLength }
|
||||
] = match;
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
line.trim(),
|
||||
null,
|
||||
null,
|
||||
[ 1, hashLength + spacesLength + 1 ],
|
||||
{
|
||||
"editColumn": hashLength + 1,
|
||||
"deleteCount": spacesLength - 1
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
54
lib/md020.js
54
lib/md020.js
|
|
@ -2,23 +2,59 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, forEachLine, rangeFromRegExp } = require("../helpers");
|
||||
const { addErrorContext, forEachLine } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
|
||||
const atxClosedHeadingNoSpaceRe = /(?:^#+[^#\s])|(?:[^#\s]#+\s*$)/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD020", "no-missing-space-closed-atx" ],
|
||||
"description": "No space inside hashes on closed atx style heading",
|
||||
"tags": [ "headings", "headers", "atx_closed", "spaces" ],
|
||||
"function": function MD020(params, onError) {
|
||||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
||||
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line)) {
|
||||
const left = /^#+[^#\s]/.test(line);
|
||||
const right = /[^#\s]#+$/.test(line);
|
||||
if (left || right) {
|
||||
addErrorContext(onError, lineIndex + 1, line.trim(), left,
|
||||
right, rangeFromRegExp(line, atxClosedHeadingNoSpaceRe));
|
||||
if (!inCode) {
|
||||
const match =
|
||||
/^(#+)(\s*)([^#]+?[^#\\])(\s*)((?:\\#)?)(#+)(\s*)$/.exec(line);
|
||||
if (match) {
|
||||
const [
|
||||
,
|
||||
leftHash,
|
||||
{ "length": leftSpaceLength },
|
||||
content,
|
||||
{ "length": rightSpaceLength },
|
||||
rightEscape,
|
||||
rightHash,
|
||||
{ "length": trailSpaceLength }
|
||||
] = match;
|
||||
const leftHashLength = leftHash.length;
|
||||
const rightHashLength = rightHash.length;
|
||||
const left = !leftSpaceLength;
|
||||
const right = !rightSpaceLength || rightEscape;
|
||||
const rightEscapeReplacement = rightEscape ? `${rightEscape} ` : "";
|
||||
if (left || right) {
|
||||
const range = left ?
|
||||
[
|
||||
1,
|
||||
leftHashLength + 1
|
||||
] :
|
||||
[
|
||||
line.length - trailSpaceLength - rightHashLength,
|
||||
rightHashLength + 1
|
||||
];
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
line.trim(),
|
||||
left,
|
||||
right,
|
||||
range,
|
||||
{
|
||||
"editColumn": 1,
|
||||
"deleteCount": line.length,
|
||||
"insertText":
|
||||
`${leftHash} ${content} ${rightEscapeReplacement}${rightHash}`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
53
lib/md021.js
53
lib/md021.js
|
|
@ -2,24 +2,57 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, filterTokens, headingStyleFor, rangeFromRegExp } =
|
||||
const { addErrorContext, filterTokens, headingStyleFor } =
|
||||
require("../helpers");
|
||||
|
||||
const atxClosedHeadingSpaceRe = /(?:^#+\s\s+?\S)|(?:\S\s\s+?#+\s*$)/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD021", "no-multiple-space-closed-atx" ],
|
||||
"description": "Multiple spaces inside hashes on closed atx style heading",
|
||||
"tags": [ "headings", "headers", "atx_closed", "spaces" ],
|
||||
"function": function MD021(params, onError) {
|
||||
filterTokens(params, "heading_open", function forToken(token) {
|
||||
filterTokens(params, "heading_open", (token) => {
|
||||
if (headingStyleFor(token) === "atx_closed") {
|
||||
const left = /^#+\s\s/.test(token.line);
|
||||
const right = /\s\s#+$/.test(token.line);
|
||||
if (left || right) {
|
||||
addErrorContext(onError, token.lineNumber, token.line.trim(),
|
||||
left, right,
|
||||
rangeFromRegExp(token.line, atxClosedHeadingSpaceRe));
|
||||
const { line, lineNumber } = token;
|
||||
const match = /^(#+)(\s+)([^#]+?)(\s+)(#+)(\s*)$/.exec(line);
|
||||
if (match) {
|
||||
const [
|
||||
,
|
||||
leftHash,
|
||||
{ "length": leftSpaceLength },
|
||||
content,
|
||||
{ "length": rightSpaceLength },
|
||||
rightHash,
|
||||
{ "length": trailSpaceLength }
|
||||
] = match;
|
||||
const left = leftSpaceLength > 1;
|
||||
const right = rightSpaceLength > 1;
|
||||
if (left || right) {
|
||||
const length = line.length;
|
||||
const leftHashLength = leftHash.length;
|
||||
const rightHashLength = rightHash.length;
|
||||
const range = left ?
|
||||
[
|
||||
1,
|
||||
leftHashLength + leftSpaceLength + 1
|
||||
] :
|
||||
[
|
||||
length - trailSpaceLength - rightHashLength - rightSpaceLength,
|
||||
rightSpaceLength + rightHashLength + 1
|
||||
];
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
line.trim(),
|
||||
left,
|
||||
right,
|
||||
range,
|
||||
{
|
||||
"editColumn": 1,
|
||||
"deleteCount": length,
|
||||
"insertText": `${leftHash} ${content} ${rightHash}`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
37
lib/md022.js
37
lib/md022.js
|
|
@ -20,20 +20,41 @@ module.exports = {
|
|||
const { lines } = params;
|
||||
filterTokens(params, "heading_open", (token) => {
|
||||
const [ topIndex, nextIndex ] = token.map;
|
||||
let actualAbove = 0;
|
||||
for (let i = 0; i < linesAbove; i++) {
|
||||
if (!isBlankLine(lines[topIndex - i - 1])) {
|
||||
addErrorDetailIf(onError, topIndex + 1, linesAbove, i, "Above",
|
||||
lines[topIndex].trim());
|
||||
return;
|
||||
if (isBlankLine(lines[topIndex - i - 1])) {
|
||||
actualAbove++;
|
||||
}
|
||||
}
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
topIndex + 1,
|
||||
linesAbove,
|
||||
actualAbove,
|
||||
"Above",
|
||||
lines[topIndex].trim(),
|
||||
null,
|
||||
{
|
||||
"insertText": "".padEnd(linesAbove - actualAbove, "\n")
|
||||
});
|
||||
let actualBelow = 0;
|
||||
for (let i = 0; i < linesBelow; i++) {
|
||||
if (!isBlankLine(lines[nextIndex + i])) {
|
||||
addErrorDetailIf(onError, topIndex + 1, linesBelow, i, "Below",
|
||||
lines[topIndex].trim());
|
||||
return;
|
||||
if (isBlankLine(lines[nextIndex + i])) {
|
||||
actualBelow++;
|
||||
}
|
||||
}
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
topIndex + 1,
|
||||
linesBelow,
|
||||
actualBelow,
|
||||
"Below",
|
||||
lines[topIndex].trim(),
|
||||
null,
|
||||
{
|
||||
"lineNumber": nextIndex + 1,
|
||||
"insertText": "".padEnd(linesBelow - actualBelow, "\n")
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
26
lib/md023.js
26
lib/md023.js
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, filterTokens, rangeFromRegExp } =
|
||||
require("../helpers");
|
||||
const { addErrorContext, filterTokens } = require("../helpers");
|
||||
|
||||
const spaceBeforeHeadingRe = /^((?:\s+)|(?:[>\s]+\s\s))[^>\s]/;
|
||||
|
||||
|
|
@ -13,9 +12,26 @@ module.exports = {
|
|||
"tags": [ "headings", "headers", "spaces" ],
|
||||
"function": function MD023(params, onError) {
|
||||
filterTokens(params, "heading_open", function forToken(token) {
|
||||
if (spaceBeforeHeadingRe.test(token.line)) {
|
||||
addErrorContext(onError, token.lineNumber, token.line, null,
|
||||
null, rangeFromRegExp(token.line, spaceBeforeHeadingRe));
|
||||
const { lineNumber, line } = token;
|
||||
const match = line.match(spaceBeforeHeadingRe);
|
||||
if (match) {
|
||||
const [ prefixAndFirstChar, prefix ] = match;
|
||||
let deleteCount = prefix.length;
|
||||
const prefixLengthNoSpace = prefix.trimRight().length;
|
||||
if (prefixLengthNoSpace) {
|
||||
deleteCount -= prefixLengthNoSpace - 1;
|
||||
}
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
line,
|
||||
null,
|
||||
null,
|
||||
[ 1, prefixAndFirstChar.length ],
|
||||
{
|
||||
"editColumn": prefixLengthNoSpace + 1,
|
||||
"deleteCount": deleteCount
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
29
lib/md026.js
29
lib/md026.js
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addError, allPunctuation, escapeForRegExp, forEachHeading,
|
||||
rangeFromRegExp } = require("../helpers");
|
||||
const { addError, allPunctuation, escapeForRegExp, forEachHeading } =
|
||||
require("../helpers");
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD026", "no-trailing-punctuation" ],
|
||||
|
|
@ -15,13 +15,26 @@ module.exports = {
|
|||
punctuation = allPunctuation;
|
||||
}
|
||||
const trailingPunctuationRe =
|
||||
new RegExp("[" + escapeForRegExp(punctuation) + "]$");
|
||||
forEachHeading(params, (heading, content) => {
|
||||
const match = trailingPunctuationRe.exec(content);
|
||||
new RegExp("\\s*[" + escapeForRegExp(punctuation) + "]+$");
|
||||
forEachHeading(params, (heading) => {
|
||||
const { line, lineNumber } = heading;
|
||||
const trimmedLine = line.replace(/[\s#]*$/, "");
|
||||
const match = trailingPunctuationRe.exec(trimmedLine);
|
||||
if (match) {
|
||||
addError(onError, heading.lineNumber,
|
||||
"Punctuation: '" + match[0] + "'", null,
|
||||
rangeFromRegExp(heading.line, trailingPunctuationRe));
|
||||
const fullMatch = match[0];
|
||||
const column = match.index + 1;
|
||||
const length = fullMatch.length;
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
`Punctuation: '${fullMatch}'`,
|
||||
null,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
56
lib/md027.js
56
lib/md027.js
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, newLineRe, rangeFromRegExp } = require("../helpers");
|
||||
const { addErrorContext, newLineRe } = require("../helpers");
|
||||
|
||||
const spaceAfterBlockQuote = /^\s*(?:>\s+)+\S/;
|
||||
const spaceAfterBlockQuoteRe = /^((?:\s*>)+)(\s{2,})\S/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD027", "no-multiple-space-blockquote" ],
|
||||
|
|
@ -13,31 +13,43 @@ module.exports = {
|
|||
"function": function MD027(params, onError) {
|
||||
let blockquoteNesting = 0;
|
||||
let listItemNesting = 0;
|
||||
params.tokens.forEach(function forToken(token) {
|
||||
if (token.type === "blockquote_open") {
|
||||
params.tokens.forEach((token) => {
|
||||
const { content, lineNumber, type } = token;
|
||||
if (type === "blockquote_open") {
|
||||
blockquoteNesting++;
|
||||
} else if (token.type === "blockquote_close") {
|
||||
} else if (type === "blockquote_close") {
|
||||
blockquoteNesting--;
|
||||
} else if (token.type === "list_item_open") {
|
||||
} else if (type === "list_item_open") {
|
||||
listItemNesting++;
|
||||
} else if (token.type === "list_item_close") {
|
||||
} else if (type === "list_item_close") {
|
||||
listItemNesting--;
|
||||
} else if ((token.type === "inline") && (blockquoteNesting > 0)) {
|
||||
const multipleSpaces = listItemNesting ?
|
||||
/^(\s*>)+\s\s+>/.test(token.line) :
|
||||
/^(\s*>)+\s\s/.test(token.line);
|
||||
if (multipleSpaces) {
|
||||
addErrorContext(onError, token.lineNumber, token.line, null,
|
||||
null, rangeFromRegExp(token.line, spaceAfterBlockQuote));
|
||||
}
|
||||
token.content.split(newLineRe)
|
||||
.forEach(function forLine(line, offset) {
|
||||
if (/^\s/.test(line)) {
|
||||
addErrorContext(onError, token.lineNumber + offset,
|
||||
"> " + line, null, null,
|
||||
rangeFromRegExp(line, spaceAfterBlockQuote));
|
||||
} else if ((type === "inline") && blockquoteNesting) {
|
||||
const lineCount = content.split(newLineRe).length;
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const line = params.lines[lineNumber + i - 1];
|
||||
const match = line.match(spaceAfterBlockQuoteRe);
|
||||
if (match) {
|
||||
const [
|
||||
fullMatch,
|
||||
{ "length": blockquoteLength },
|
||||
{ "length": spaceLength }
|
||||
] = match;
|
||||
if (!listItemNesting || (fullMatch[fullMatch.length - 1] === ">")) {
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber + i,
|
||||
line,
|
||||
null,
|
||||
null,
|
||||
[ 1, fullMatch.length ],
|
||||
{
|
||||
"editColumn": blockquoteLength + 1,
|
||||
"deleteCount": spaceLength - 1
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
19
lib/md028.js
19
lib/md028.js
|
|
@ -10,12 +10,29 @@ module.exports = {
|
|||
"tags": [ "blockquote", "whitespace" ],
|
||||
"function": function MD028(params, onError) {
|
||||
let prevToken = {};
|
||||
let prevLineNumber = null;
|
||||
params.tokens.forEach(function forToken(token) {
|
||||
if ((token.type === "blockquote_open") &&
|
||||
(prevToken.type === "blockquote_close")) {
|
||||
addError(onError, token.lineNumber - 1);
|
||||
for (
|
||||
let lineNumber = prevLineNumber;
|
||||
lineNumber < token.lineNumber;
|
||||
lineNumber++) {
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"deleteCount": -1
|
||||
});
|
||||
}
|
||||
}
|
||||
prevToken = token;
|
||||
if (token.type === "blockquote_open") {
|
||||
prevLineNumber = token.map[1] + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
28
lib/md030.js
28
lib/md030.js
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
|
||||
require("../helpers");
|
||||
const { addErrorDetailIf } = require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -22,10 +21,27 @@ module.exports = {
|
|||
(allSingle ? ulSingle : ulMulti) :
|
||||
(allSingle ? olSingle : olMulti);
|
||||
list.items.forEach((item) => {
|
||||
const match = /^[\s>]*\S+(\s+)/.exec(item.line);
|
||||
addErrorDetailIf(onError, item.lineNumber,
|
||||
expectedSpaces, (match ? match[1].length : 0), null, null,
|
||||
rangeFromRegExp(item.line, listItemMarkerRe));
|
||||
const { line, lineNumber } = item;
|
||||
const match = /^[\s>]*\S+(\s*)/.exec(line);
|
||||
const [ { "length": matchLength }, { "length": actualSpaces } ] = match;
|
||||
let fixInfo = null;
|
||||
if ((expectedSpaces !== actualSpaces) && (line.length > matchLength)) {
|
||||
fixInfo = {
|
||||
"editColumn": matchLength - actualSpaces + 1,
|
||||
"deleteCount": actualSpaces,
|
||||
"insertText": "".padEnd(expectedSpaces)
|
||||
};
|
||||
}
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineNumber,
|
||||
expectedSpaces,
|
||||
actualSpaces,
|
||||
null,
|
||||
null,
|
||||
[ 1, matchLength ],
|
||||
fixInfo
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
20
lib/md031.js
20
lib/md031.js
|
|
@ -14,10 +14,22 @@ module.exports = {
|
|||
const includeListItems = (listItems === undefined) ? true : !!listItems;
|
||||
const { lines } = params;
|
||||
forEachLine(lineMetadata(), (line, i, inCode, onFence, inTable, inItem) => {
|
||||
if ((((onFence > 0) && !isBlankLine(lines[i - 1])) ||
|
||||
((onFence < 0) && !isBlankLine(lines[i + 1]))) &&
|
||||
(includeListItems || !inItem)) {
|
||||
addErrorContext(onError, i + 1, lines[i].trim());
|
||||
const onTopFence = (onFence > 0);
|
||||
const onBottomFence = (onFence < 0);
|
||||
if ((includeListItems || !inItem) &&
|
||||
((onTopFence && !isBlankLine(lines[i - 1])) ||
|
||||
(onBottomFence && !isBlankLine(lines[i + 1])))) {
|
||||
addErrorContext(
|
||||
onError,
|
||||
i + 1,
|
||||
lines[i].trim(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"lineNumber": i + (onTopFence ? 1 : 2),
|
||||
"insertText": "\n"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
29
lib/md032.js
29
lib/md032.js
|
|
@ -5,6 +5,8 @@
|
|||
const { addErrorContext, isBlankLine } = require("../helpers");
|
||||
const { flattenedLists } = require("./cache");
|
||||
|
||||
const quotePrefixRe = /^[>\s]*/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD032", "blanks-around-lists" ],
|
||||
"description": "Lists should be surrounded by blank lines",
|
||||
|
|
@ -14,11 +16,34 @@ module.exports = {
|
|||
flattenedLists().filter((list) => !list.nesting).forEach((list) => {
|
||||
const firstIndex = list.open.map[0];
|
||||
if (!isBlankLine(lines[firstIndex - 1])) {
|
||||
addErrorContext(onError, firstIndex + 1, lines[firstIndex].trim());
|
||||
const line = lines[firstIndex];
|
||||
const quotePrefix = line.match(quotePrefixRe)[0].trimRight();
|
||||
addErrorContext(
|
||||
onError,
|
||||
firstIndex + 1,
|
||||
line.trim(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"insertText": `${quotePrefix}\n`
|
||||
});
|
||||
}
|
||||
const lastIndex = list.lastLineIndex - 1;
|
||||
if (!isBlankLine(lines[lastIndex + 1])) {
|
||||
addErrorContext(onError, lastIndex + 1, lines[lastIndex].trim());
|
||||
const line = lines[lastIndex];
|
||||
const quotePrefix = line.match(quotePrefixRe)[0].trimRight();
|
||||
addErrorContext(
|
||||
onError,
|
||||
lastIndex + 1,
|
||||
line.trim(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"lineNumber": lastIndex + 2,
|
||||
"insertText": `${quotePrefix}\n`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
32
lib/md034.js
32
lib/md034.js
|
|
@ -18,15 +18,29 @@ module.exports = {
|
|||
inLink = true;
|
||||
} else if (type === "link_close") {
|
||||
inLink = false;
|
||||
} else if ((type === "text") && !inLink &&
|
||||
(match = bareUrlRe.exec(content))) {
|
||||
const [ bareUrl ] = match;
|
||||
const index = line.indexOf(content);
|
||||
const range = (index === -1) ? null : [
|
||||
line.indexOf(content) + match.index + 1,
|
||||
bareUrl.length
|
||||
];
|
||||
addErrorContext(onError, lineNumber, bareUrl, null, null, range);
|
||||
} else if ((type === "text") && !inLink) {
|
||||
while ((match = bareUrlRe.exec(content)) !== null) {
|
||||
const [ bareUrl ] = match;
|
||||
const index = line.indexOf(content);
|
||||
const range = (index === -1) ? null : [
|
||||
line.indexOf(content) + match.index + 1,
|
||||
bareUrl.length
|
||||
];
|
||||
const fixInfo = range ? {
|
||||
"editColumn": range[0],
|
||||
"deleteCount": range[1],
|
||||
"insertText": `<${bareUrl}>`
|
||||
} : null;
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
bareUrl,
|
||||
null,
|
||||
null,
|
||||
range,
|
||||
fixInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
52
lib/md037.js
52
lib/md037.js
|
|
@ -4,29 +4,49 @@
|
|||
|
||||
const { addErrorContext, forEachInlineChild } = require("../helpers");
|
||||
|
||||
const leftSpaceRe = /(?:^|\s)(\*\*?|__?)\s.*[^\\]\1/g;
|
||||
const rightSpaceRe = /(?:^|[^\\])(\*\*?|__?).+\s\1(?:\s|$)/g;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD037", "no-space-in-emphasis" ],
|
||||
"description": "Spaces inside emphasis markers",
|
||||
"tags": [ "whitespace", "emphasis" ],
|
||||
"function": function MD037(params, onError) {
|
||||
forEachInlineChild(params, "text", (token) => {
|
||||
let left = true;
|
||||
let match = /(?:^|\s)(\*\*?|__?)\s.*[^\\]\1/.exec(token.content);
|
||||
if (!match) {
|
||||
left = false;
|
||||
match = /(?:^|[^\\])(\*\*?|__?).+\s\1(?:\s|$)/.exec(token.content);
|
||||
}
|
||||
if (match) {
|
||||
const fullText = match[0];
|
||||
const line = params.lines[token.lineNumber - 1];
|
||||
if (line.includes(fullText)) {
|
||||
const text = fullText.trim();
|
||||
const column = line.indexOf(text) + 1;
|
||||
const length = text.length;
|
||||
addErrorContext(onError, token.lineNumber,
|
||||
text, left, !left, [ column, length ]);
|
||||
const { content, lineNumber } = token;
|
||||
const columnsReported = [];
|
||||
[ leftSpaceRe, rightSpaceRe ].forEach((spaceRe, index) => {
|
||||
let match = null;
|
||||
while ((match = spaceRe.exec(content)) !== null) {
|
||||
const [ fullText, marker ] = match;
|
||||
const line = params.lines[lineNumber - 1];
|
||||
if (line.includes(fullText)) {
|
||||
const text = fullText.trim();
|
||||
const column = line.indexOf(text) + 1;
|
||||
if (!columnsReported.includes(column)) {
|
||||
const length = text.length;
|
||||
const markerLength = marker.length;
|
||||
const emphasized =
|
||||
text.slice(markerLength, length - markerLength);
|
||||
const fixedText = `${marker}${emphasized.trim()}${marker}`;
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
text,
|
||||
index === 0,
|
||||
index !== 0,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": fixedText
|
||||
}
|
||||
);
|
||||
columnsReported.push(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
34
lib/md038.js
34
lib/md038.js
|
|
@ -5,8 +5,8 @@
|
|||
const { addErrorContext, filterTokens, forEachInlineCodeSpan, newLineRe } =
|
||||
require("../helpers");
|
||||
|
||||
const startRe = /^\s([^`]|$)/;
|
||||
const endRe = /[^`]\s$/;
|
||||
const leftSpaceRe = /^\s([^`]|$)/;
|
||||
const rightSpaceRe = /[^`]\s$/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD038", "no-space-in-code" ],
|
||||
|
|
@ -22,22 +22,42 @@ module.exports = {
|
|||
let rangeIndex = columnIndex - tickCount;
|
||||
let rangeLength = code.length + (2 * tickCount);
|
||||
let rangeLineOffset = 0;
|
||||
let fixIndex = columnIndex;
|
||||
let fixLength = code.length;
|
||||
const codeLines = code.split(newLineRe);
|
||||
const left = startRe.test(code);
|
||||
const right = !left && endRe.test(code);
|
||||
const left = leftSpaceRe.test(code);
|
||||
const right = !left && rightSpaceRe.test(code);
|
||||
if (right && (codeLines.length > 1)) {
|
||||
rangeIndex = 0;
|
||||
rangeLineOffset = codeLines.length - 1;
|
||||
fixIndex = 0;
|
||||
}
|
||||
if (left || right) {
|
||||
const codeLinesRange = codeLines[rangeLineOffset];
|
||||
if (codeLines.length > 1) {
|
||||
rangeLength = codeLines[rangeLineOffset].length + tickCount;
|
||||
rangeLength = codeLinesRange.length + tickCount;
|
||||
fixLength = codeLinesRange.length;
|
||||
}
|
||||
const context = tokenLines[lineIndex + rangeLineOffset]
|
||||
.substring(rangeIndex, rangeIndex + rangeLength);
|
||||
const codeLinesRangeTrim = codeLinesRange.trim();
|
||||
const fixText =
|
||||
(codeLinesRangeTrim.startsWith("`") ? " " : "") +
|
||||
codeLinesRangeTrim +
|
||||
(codeLinesRangeTrim.endsWith("`") ? " " : "");
|
||||
addErrorContext(
|
||||
onError, token.lineNumber + lineIndex + rangeLineOffset,
|
||||
context, left, right, [ rangeIndex + 1, rangeLength ]);
|
||||
onError,
|
||||
token.lineNumber + lineIndex + rangeLineOffset,
|
||||
context,
|
||||
left,
|
||||
right,
|
||||
[ rangeIndex + 1, rangeLength ],
|
||||
{
|
||||
"editColumn": fixIndex + 1,
|
||||
"deleteCount": fixLength,
|
||||
"insertText": fixText
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
34
lib/md039.js
34
lib/md039.js
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, filterTokens, rangeFromRegExp } =
|
||||
require("../helpers");
|
||||
const { addErrorContext, filterTokens } = require("../helpers");
|
||||
|
||||
const spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=\(\S*\))/;
|
||||
|
||||
|
|
@ -12,10 +11,13 @@ module.exports = {
|
|||
"description": "Spaces inside link text",
|
||||
"tags": [ "whitespace", "links" ],
|
||||
"function": function MD039(params, onError) {
|
||||
filterTokens(params, "inline", function forToken(token) {
|
||||
filterTokens(params, "inline", (token) => {
|
||||
const { children } = token;
|
||||
let { lineNumber } = token;
|
||||
let inLink = false;
|
||||
let linkText = "";
|
||||
token.children.forEach(function forChild(child) {
|
||||
let lineIndex = 0;
|
||||
children.forEach((child) => {
|
||||
if (child.type === "link_open") {
|
||||
inLink = true;
|
||||
linkText = "";
|
||||
|
|
@ -24,10 +26,28 @@ module.exports = {
|
|||
const left = linkText.trimLeft().length !== linkText.length;
|
||||
const right = linkText.trimRight().length !== linkText.length;
|
||||
if (left || right) {
|
||||
addErrorContext(onError, token.lineNumber,
|
||||
"[" + linkText + "]", left, right,
|
||||
rangeFromRegExp(token.line, spaceInLinkRe));
|
||||
const line = params.lines[lineNumber - 1];
|
||||
const match = line.slice(lineIndex).match(spaceInLinkRe);
|
||||
const column = match.index + lineIndex + 1;
|
||||
const length = match[0].length;
|
||||
lineIndex = column + length - 1;
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
`[${linkText}]`,
|
||||
left,
|
||||
right,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column + 1,
|
||||
"deleteCount": length - 2,
|
||||
"insertText": linkText.trim()
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (child.type === "softbreak") {
|
||||
lineNumber++;
|
||||
lineIndex = 0;
|
||||
} else if (inLink) {
|
||||
linkText += child.content;
|
||||
}
|
||||
|
|
|
|||
27
lib/md044.js
27
lib/md044.js
|
|
@ -29,9 +29,30 @@ module.exports = {
|
|||
.replace(/^\W*/, "").replace(/\W*$/, "");
|
||||
if (!names.includes(wordMatch)) {
|
||||
const lineNumber = token.lineNumber + index + fenceOffset;
|
||||
const range = [ match.index + 1, wordMatch.length ];
|
||||
addErrorDetailIf(onError, lineNumber,
|
||||
name, match[1], null, null, range);
|
||||
const fullLine = params.lines[lineNumber - 1];
|
||||
let matchIndex = match.index;
|
||||
const matchLength = wordMatch.length;
|
||||
const fullLineWord =
|
||||
fullLine.slice(matchIndex, matchIndex + matchLength);
|
||||
if (fullLineWord !== wordMatch) {
|
||||
// Attempt to fix bad offset due to inline content
|
||||
matchIndex = fullLine.indexOf(wordMatch);
|
||||
}
|
||||
const range = [ matchIndex + 1, matchLength ];
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineNumber,
|
||||
name,
|
||||
match[1],
|
||||
null,
|
||||
null,
|
||||
range,
|
||||
{
|
||||
"editColumn": matchIndex + 1,
|
||||
"deleteCount": matchLength,
|
||||
"insertText": name
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
lib/md047.js
12
lib/md047.js
|
|
@ -12,7 +12,17 @@ module.exports = {
|
|||
const lastLineNumber = params.lines.length;
|
||||
const lastLine = params.lines[lastLineNumber - 1];
|
||||
if (!isBlankLine(lastLine)) {
|
||||
addError(onError, lastLineNumber);
|
||||
addError(
|
||||
onError,
|
||||
lastLineNumber,
|
||||
null,
|
||||
null,
|
||||
[ lastLine.length, 1 ],
|
||||
{
|
||||
"insertText": "\n",
|
||||
"editColumn": lastLine.length + 1
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
19
test/atx-heading-spacing-trailing-spaces.md
Normal file
19
test/atx-heading-spacing-trailing-spaces.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# atx-heading-spacing-trailing-spaces
|
||||
|
||||
<!-- markdownlint-disable heading-style -->
|
||||
|
||||
##Heading 1 {MD018}
|
||||
|
||||
## Heading 2 {MD019}
|
||||
|
||||
##Heading 3 {MD020} ##
|
||||
|
||||
## Heading 4 {MD020}##
|
||||
|
||||
##Heading 5 {MD020}##
|
||||
|
||||
## Heading 5 {MD021} ##
|
||||
|
||||
## Heading 6 {MD021} ##
|
||||
|
||||
## Heading 7 {MD021} ##
|
||||
|
|
@ -26,6 +26,6 @@ Some text
|
|||
|
||||
Expected errors:
|
||||
|
||||
{MD028:5} {MD028:8} {MD028:10} {MD028:17}
|
||||
{MD028:5} {MD028:7} {MD028:8} {MD028:10} {MD028:17}
|
||||
{MD009:10} (trailing space is intentional)
|
||||
{MD012:8} (multiple blank lines are intentional)
|
||||
|
|
|
|||
|
|
@ -27,17 +27,17 @@ long line long line long line long line long line long line long line long line
|
|||
# Heading 5 {MD019}
|
||||
|
||||
#Heading 6 {MD020} #
|
||||
# Heading 7 {MD021} {MD022} {MD023} {MD003} #
|
||||
|
||||
# Heading 7 {MD021} {MD003} #
|
||||
|
||||
# Heading 8
|
||||
|
||||
# Heading 8
|
||||
|
||||
{MD024:34}
|
||||
|
||||
{MD024:35}
|
||||
Note: Can not break MD025 and MD002 in the same file
|
||||
|
||||
# Heading 9 {MD026}.
|
||||
# Heading 9 {MD023} {MD026}.
|
||||
|
||||
> {MD027}
|
||||
|
||||
|
|
@ -78,4 +78,7 @@ code fence without language {MD040:73} {MD046:73}
|
|||
|
||||
markdownLint {MD044}
|
||||
|
||||
 {MD045} {MD047}
|
||||
 {MD045}
|
||||
## Heading 10 {MD022}
|
||||
|
||||
EOF {MD047}
|
||||
30
test/detailed-results-MD001-MD010.md.fixed
Normal file
30
test/detailed-results-MD001-MD010.md.fixed
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
## One
|
||||
|
||||
#### Two
|
||||
|
||||
### Three ###
|
||||
|
||||
* Alpha
|
||||
* Bravo
|
||||
|
||||
- Charlie
|
||||
|
||||
* Delta
|
||||
* Echo
|
||||
|
||||
Text
|
||||
|
||||
Text text
|
||||
|
||||
1. One
|
||||
2. Two
|
||||
3. Three
|
||||
4. Four
|
||||
5. Five
|
||||
6. Six
|
||||
7. Seven
|
||||
8. Eight
|
||||
9. Nine
|
||||
10. Ten
|
||||
11. Eleven
|
||||
12. Twelve
|
||||
|
|
@ -22,3 +22,5 @@ A (reversed)[link] example.
|
|||
## Multiple spaces E ##
|
||||
|
||||
## Multiple spaces F ##
|
||||
|
||||
*Another* (reversed)[link] example.
|
||||
|
|
|
|||
25
test/detailed-results-MD011-MD021.md.fixed
Normal file
25
test/detailed-results-MD011-MD021.md.fixed
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Top level heading
|
||||
|
||||
<!-- markdownlint-disable MD003 -->
|
||||
|
||||
A [reversed](link) example.
|
||||
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
|
||||
|
||||
## 123456789 123456789 123456789 123456789 123456789 123456789
|
||||
|
||||
$ command with no output
|
||||
|
||||
## No space A
|
||||
|
||||
## Multiple spaces B
|
||||
|
||||
## No space C ##
|
||||
|
||||
## No space D ##
|
||||
|
||||
## Multiple spaces E ##
|
||||
|
||||
## Multiple spaces F ##
|
||||
|
||||
*Another* [reversed](link) example.
|
||||
|
|
@ -8,6 +8,15 @@
|
|||
"errorContext": null,
|
||||
"errorRange": [3, 16]
|
||||
},
|
||||
{
|
||||
"lineNumber": 26,
|
||||
"ruleNames": [ "MD011", "no-reversed-links" ],
|
||||
"ruleDescription": "Reversed link syntax",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md011",
|
||||
"errorDetail": "(reversed)[link]",
|
||||
"errorContext": null,
|
||||
"errorRange": [11, 16]
|
||||
},
|
||||
{
|
||||
"lineNumber": 7,
|
||||
"ruleNames": [ "MD012", "no-multiple-blanks" ],
|
||||
|
|
|
|||
19
test/detailed-results-MD022-MD030.md.fixed
Normal file
19
test/detailed-results-MD022-MD030.md.fixed
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Heading
|
||||
|
||||
Text
|
||||
|
||||
# Heading
|
||||
|
||||
## Another heading
|
||||
|
||||
> Multiple spaces
|
||||
> Blank line above
|
||||
|
||||
1. Alpha
|
||||
3. Beta
|
||||
|
||||
> > Multiple spaces, multiple blockquotes
|
||||
> >
|
||||
> > > Multiple spaces, multiple blockquotes
|
||||
> > >
|
||||
> > > Multiple spaces, multiple blockquotes
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md027",
|
||||
"errorDetail": null,
|
||||
"errorContext": "> > > Multiple spaces, multip...",
|
||||
"errorRange": [ 1, 8 ]
|
||||
"errorRange": [ 1, 4 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 9,
|
||||
|
|
|
|||
13
test/detailed-results-MD030-warning-message.md.fixed
Normal file
13
test/detailed-results-MD030-warning-message.md.fixed
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#
|
||||
|
||||
-
|
||||
|
||||
1.
|
||||
|
||||
- a
|
||||
|
||||
1. a
|
||||
|
||||
- a
|
||||
|
||||
1. a
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md030",
|
||||
"errorDetail": "Expected: 1; Actual: 0",
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
"errorRange": [1, 1]
|
||||
},
|
||||
{
|
||||
"lineNumber": 5,
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md030",
|
||||
"errorDetail": "Expected: 1; Actual: 0",
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
"errorRange": [1, 2]
|
||||
},
|
||||
{
|
||||
"lineNumber": 11,
|
||||
|
|
|
|||
|
|
@ -56,3 +56,5 @@ text text ```` code
|
|||
span code
|
||||
span```` text
|
||||
text.
|
||||
|
||||
Text [ space](link) text [space ](link) text [ space ](link) text.
|
||||
|
|
|
|||
62
test/detailed-results-MD031-MD040.md.fixed
Normal file
62
test/detailed-results-MD031-MD040.md.fixed
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
```js
|
||||
debugger;
|
||||
```
|
||||
|
||||
* List
|
||||
|
||||
Inline<hr/>HTML
|
||||
|
||||
Bare <https://example.com> link
|
||||
|
||||
---
|
||||
***
|
||||
|
||||
*Emphasis*
|
||||
|
||||
Space *inside* emphasis
|
||||
|
||||
Space `inside` code span
|
||||
|
||||
Space [inside](link) text
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
space ``inside`` code
|
||||
space `inside` of `code` elements
|
||||
`space` inside `of` code `elements`
|
||||
space ``inside`` of ``code`` elements
|
||||
`` ` embedded backtick``
|
||||
``embedded backtick` ``
|
||||
|
||||
some *space* in *some* emphasis
|
||||
some *space* in *some* emphasis
|
||||
some *space* in **some** emphasis
|
||||
some _space_ in _some_ emphasis
|
||||
some __space__ in __some__ emphasis
|
||||
|
||||
Text
|
||||
text `code
|
||||
span` text
|
||||
text.
|
||||
|
||||
Text
|
||||
text `code
|
||||
span` text
|
||||
text.
|
||||
|
||||
* List
|
||||
|
||||
---
|
||||
|
||||
Text
|
||||
text ```code
|
||||
span code
|
||||
span code``` text
|
||||
text
|
||||
text text ````code
|
||||
span code
|
||||
span```` text
|
||||
text.
|
||||
|
||||
Text [space](link) text [space](link) text [space](link) text.
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md033",
|
||||
"errorDetail": "Element: hr",
|
||||
"errorContext": null,
|
||||
"errorRange": [7, 5]
|
||||
"errorRange": [ 7, 5 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 8,
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md034",
|
||||
"errorDetail": null,
|
||||
"errorContext": "https://example.com",
|
||||
"errorRange": [6, 19]
|
||||
"errorRange": [ 6, 19 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 11,
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md037",
|
||||
"errorDetail": null,
|
||||
"errorContext": "* inside *",
|
||||
"errorRange": [7, 10]
|
||||
"errorRange": [ 7, 10 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 31,
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md038",
|
||||
"errorDetail": null,
|
||||
"errorContext": "` inside `",
|
||||
"errorRange": [7, 10]
|
||||
"errorRange": [ 7, 10 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 24,
|
||||
|
|
@ -222,7 +222,16 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[ inside ]",
|
||||
"errorRange": [7, 10]
|
||||
"errorRange": [ 7, 10 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 60,
|
||||
"ruleNames": [ "MD039", "no-space-in-links" ],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[ space]",
|
||||
"errorRange": [ 6, 8 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 21,
|
||||
|
|
|
|||
27
test/detailed-results-MD041-MD050.md.fixed
Normal file
27
test/detailed-results-MD041-MD050.md.fixed
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Not a heading
|
||||
|
||||
An [empty]() link
|
||||
|
||||
An [empty](#) link with fragment
|
||||
|
||||
An [empty](<>) link with angle brackets
|
||||
|
||||
This is a test file for the markdownlint package.
|
||||
|
||||
This is a paragraph
|
||||
about markdownlint
|
||||
that capitalizes the
|
||||
name wrong twice:
|
||||
markdownlint.
|
||||
|
||||
A [normal](link) and an [empty one]() and a [fragment](#one).
|
||||
|
||||
An image without alternate text 
|
||||
|
||||
```text
|
||||
Fenced code
|
||||
```
|
||||
|
||||
Indented code
|
||||
|
||||
Missing newline character
|
||||
|
|
@ -105,6 +105,6 @@
|
|||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md047",
|
||||
"errorDetail": null,
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
"errorRange": [ 25, 1 ]
|
||||
}
|
||||
]
|
||||
27
test/detailed-results-blanks-around-headings-0-2.md.fixed
Normal file
27
test/detailed-results-blanks-around-headings-0-2.md.fixed
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Blanks Around Headings
|
||||
|
||||
|
||||
## Apple
|
||||
|
||||
|
||||
Text
|
||||
## Banana
|
||||
|
||||
|
||||
Text
|
||||
## Cherry
|
||||
|
||||
|
||||
Text
|
||||
## Durian ##
|
||||
|
||||
|
||||
Text
|
||||
|
||||
---
|
||||
Elderberry
|
||||
----------
|
||||
|
||||
|
||||
Text
|
||||
## Fig
|
||||
31
test/detailed-results-blanks-around-headings-3-0.md.fixed
Normal file
31
test/detailed-results-blanks-around-headings-3-0.md.fixed
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Blanks Around Headings
|
||||
|
||||
|
||||
|
||||
## Apple
|
||||
Text
|
||||
|
||||
|
||||
|
||||
## Banana
|
||||
Text
|
||||
|
||||
|
||||
|
||||
## Cherry
|
||||
Text
|
||||
|
||||
|
||||
|
||||
## Durian ##
|
||||
Text
|
||||
|
||||
|
||||
|
||||
Elderberry
|
||||
----------
|
||||
Text
|
||||
|
||||
|
||||
|
||||
## Fig
|
||||
26
test/detailed-results-blanks-around-headings.md.fixed
Normal file
26
test/detailed-results-blanks-around-headings.md.fixed
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Blanks Around Headings
|
||||
|
||||
## Apple
|
||||
|
||||
Text
|
||||
|
||||
## Banana
|
||||
|
||||
Text
|
||||
|
||||
## Cherry
|
||||
|
||||
Text
|
||||
|
||||
## Durian ##
|
||||
|
||||
Text
|
||||
|
||||
---
|
||||
|
||||
Elderberry
|
||||
----------
|
||||
|
||||
Text
|
||||
|
||||
## Fig
|
||||
6
test/detailed-results-front-matter.md.fixed
Normal file
6
test/detailed-results-front-matter.md.fixed
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
front: matter
|
||||
---
|
||||
Text
|
||||
|
||||
Text
|
||||
107
test/detailed-results-html-tags.md.fixed
Normal file
107
test/detailed-results-html-tags.md.fixed
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Detailed HTML Results
|
||||
|
||||
Text
|
||||
|
||||
<em>Block block</em>
|
||||
|
||||
Text <em>inline inline</em> text
|
||||
|
||||
Text
|
||||
|
||||
<strong>Block block</strong>
|
||||
|
||||
Text <strong>inline inline</strong> text
|
||||
|
||||
Text
|
||||
|
||||
<p>
|
||||
Block
|
||||
block <em>block</em> block
|
||||
block
|
||||
block <strong>block</strong> block
|
||||
block
|
||||
block <em>block</em> block <strong>block</strong> block
|
||||
block <strong>block</strong> block <em>block</em> block
|
||||
</p>
|
||||
|
||||
Text
|
||||
|
||||
<strong><em>Block</em> block</strong>
|
||||
|
||||
Text <strong><em>inline</em> inline</strong> text
|
||||
|
||||
Text
|
||||
|
||||
<em><strong>Block</strong> block</em>
|
||||
|
||||
Text <em><strong>inline</strong> inline</em> text
|
||||
|
||||
Text
|
||||
|
||||
Text <em>inline</em> text <strong>inline</strong> text <em>inline</em> text
|
||||
|
||||
Text <strong>inline</strong> text <em>inline</em> text <strong>inline</strong> text
|
||||
|
||||
Text
|
||||
|
||||
\<not>Block block\</not>
|
||||
|
||||
\\<problem>Block block\\</problem>
|
||||
|
||||
<not\>Block block</not\>
|
||||
|
||||
Text \<not>inline inline\</not> text
|
||||
|
||||
Text \\<problem>inline inline\\</problem> text
|
||||
|
||||
Text <not\>inline inline</not\> text
|
||||
|
||||
Text
|
||||
|
||||
> Text <em>inline inline</em> text
|
||||
> text <strong>inline inline</strong> text
|
||||
|
||||
Text
|
||||
|
||||
Text <em>inline inline</em> text
|
||||
text <strong>inline inline</strong> text
|
||||
|
||||
Text
|
||||
|
||||
```html
|
||||
Text <em>inline inline</em> text
|
||||
text <strong>inline inline</strong> text
|
||||
```
|
||||
|
||||
Text
|
||||
|
||||
`<em>`
|
||||
|
||||
Text ``<em>`` text
|
||||
|
||||
Text `<em>` text ``<em>`` text ```<em>``` text
|
||||
|
||||
Text `<em>` text <em>inline</em> text
|
||||
|
||||
Text ``text <em> text`` text
|
||||
|
||||
Text
|
||||
|
||||
Text <a href="#anchor">inline</a> text
|
||||
text <img src="src.png"/> text
|
||||
|
||||
Text
|
||||
|
||||
<name@example.com> is an email autolink.
|
||||
|
||||
Another email autolink: <first+last@ex.exa-mple.com>.
|
||||
|
||||
Text
|
||||
|
||||
<foo-bar-baz> is an HTML element.
|
||||
|
||||
But <foo.bar.baz> is not an autolink or HTML element.
|
||||
And neither is <foo_bar>.
|
||||
Nor <123abc>.
|
||||
|
||||
Text
|
||||
|
|
@ -24,3 +24,5 @@ Code https://example.com/code?type=fence code
|
|||
Text <https://example.com/same> more text https://example.com/same still more text <https://example.com/same> done
|
||||
|
||||
Text <https://example.com/same> more \* text https://example.com/same more \[ text <https://example.com/same> done
|
||||
|
||||
Text https://example.com/first more text https://example.com/second still more text https://example.com/third done
|
||||
|
|
|
|||
28
test/detailed-results-links.md.fixed
Normal file
28
test/detailed-results-links.md.fixed
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Detailed Link Results
|
||||
|
||||
Text <https://example.com/> text
|
||||
|
||||
Text <https://example.com/brackets> text <https://example.com/bare> text
|
||||
|
||||
Text <https://example.com/bare> text <https://example.com/brackets> text
|
||||
|
||||
Text `code https://example.com/code code` text <https://example.com/> text
|
||||
|
||||
> Text <https://example.com/brackets> text <https://example.com/bare> text
|
||||
|
||||
Text <https://example.com/dir>
|
||||
text <https://example.com/file.txt>
|
||||
text <https://example.com/dir/dir>
|
||||
text <https://example.com/dir/dir/file?query=param>
|
||||
|
||||
```text
|
||||
Code https://example.com/code?type=fence code
|
||||
```
|
||||
|
||||
Code https://example.com/code?type=indent code
|
||||
|
||||
Text <https://example.com/same> more text <https://example.com/same> still more text <https://example.com/same> done
|
||||
|
||||
Text <https://example.com/same> more \* text https://example.com/same more \[ text <https://example.com/same> done
|
||||
|
||||
Text <https://example.com/first> more text <https://example.com/second> still more text <https://example.com/third> done
|
||||
|
|
@ -88,5 +88,14 @@
|
|||
"errorDetail": null,
|
||||
"errorContext": "https://example.com/same",
|
||||
"errorRange": null
|
||||
},
|
||||
{
|
||||
"lineNumber": 28,
|
||||
"ruleNames": [ "MD034", "no-bare-urls" ],
|
||||
"ruleDescription": "Bare URL used",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md034",
|
||||
"errorDetail": null,
|
||||
"errorContext": "https://example.com/first",
|
||||
"errorRange": [ 6, 25 ]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# Ordered list examples
|
||||
|
||||
9. Item
|
||||
19
test/detailed-results-ordered-list-item-prefix-zero.md.fixed
Normal file
19
test/detailed-results-ordered-list-item-prefix-zero.md.fixed
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Ordered list examples
|
||||
|
||||
text
|
||||
|
||||
0. Item
|
||||
0. Item
|
||||
0. Item
|
||||
|
||||
text
|
||||
|
||||
1. Item
|
||||
1. Item
|
||||
1. Item
|
||||
|
||||
text
|
||||
|
||||
1. Item
|
||||
2. Item
|
||||
3. Item
|
||||
|
|
@ -23,3 +23,10 @@
|
|||
## Heading/Full-Width {MD026} !
|
||||
|
||||
## Heading/Full-Width {MD026} ?
|
||||
|
||||
<!-- markdownlint-disable heading-style -->
|
||||
|
||||
## Heading {MD026} alternate ? ##
|
||||
|
||||
Heading {MD026} alternate too ?
|
||||
-------------------------------
|
||||
|
|
|
|||
20
test/headings-without-content.md
Normal file
20
test/headings-without-content.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Headings Without Content
|
||||
|
||||
<!-- markdownlint-disable single-title heading-style -->
|
||||
<!-- markdownlint-disable no-duplicate-heading no-trailing-spaces -->
|
||||
|
||||
#
|
||||
|
||||
#
|
||||
|
||||
#
|
||||
|
||||
#
|
||||
|
||||
##
|
||||
|
||||
##
|
||||
|
||||
##
|
||||
|
||||
##
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Hard tab {MD010}
|
||||
|
||||
Hard tabs hard tabs {MD010}
|
||||
|
||||
<!-- Hard tab -->
|
||||
|
||||
<!--Hard tab-->
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
# Heading
|
||||
|
||||
```text
|
||||
hello
|
||||
world
|
||||
```
|
||||
|
|
@ -66,3 +66,8 @@ text ` code {MD038}
|
|||
span code
|
||||
span` text
|
||||
text.
|
||||
|
||||
"<!--"
|
||||
-->
|
||||
Text `code
|
||||
code code `text` {MD038}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,11 @@
|
|||
|
||||
- one {MD032}
|
||||
1. two {MD032}
|
||||
1. three {MD032}
|
||||
- four {MD032}
|
||||
- three {MD032}
|
||||
|
||||
1. one {MD032}
|
||||
- two {MD006} {MD032}
|
||||
- three {MD032}
|
||||
1. four {MD032}
|
||||
1. three {MD032}
|
||||
|
||||
## Correct nesting, same type
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,3 +5,30 @@ However, this shouldn't trigger inside code blocks:
|
|||
myObj.getFiles("test")[0]
|
||||
|
||||
Nor inline code: `myobj.getFiles("test")[0]`
|
||||
|
||||
Two (issues)[https://www.example.com/one] in {MD011} {MD034}
|
||||
the (same text)[https://www.example.com/two]. {MD011} {MD034}
|
||||
|
||||
<!-- markdownlint-disable line-length -->
|
||||
Two (issues)[https://www.example.com/three] on the (same line)[https://www.example.com/four]. {MD011} {MD034}
|
||||
|
||||
`code code
|
||||
code`
|
||||
(reversed)[link] {MD011}
|
||||
|
||||
text
|
||||
text `code
|
||||
code code
|
||||
code` text
|
||||
text
|
||||
text (reversed)[link] text {MD011}
|
||||
|
||||
## Escaped JavaScript Content
|
||||
|
||||
var IDENT_RE = '([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*'; {MD011}
|
||||
|
||||
begin: /\B(([\/.])[\w\-.\/=]+)+/, {MD011}
|
||||
|
||||
{begin: '%r\\(', end: '\\)[a-z]*'} {MD011}
|
||||
|
||||
return /(?:(?:(^|\/)[!.])|[*?+()|\[\]{}]|[+@]\()/.test(str); {MD011}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ List with multiple paragraphs and incorrect spacing
|
|||
|
||||
* Foo {MD030}
|
||||
|
||||
Here is the second paragraph
|
||||
Here is the second paragraph
|
||||
|
||||
* Bar {MD030}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,3 +30,14 @@
|
|||
|
||||
The following shouldn't break anything:
|
||||
[](/images/Screenshot.png)
|
||||
|
||||
function CodeButNotCode(input) {
|
||||
return input.replace(/[- ]([a-z])/g, "one"); // {MD039}
|
||||
}
|
||||
|
||||
function MoreCodeButNotCode(input) {
|
||||
input = input.replace(/[- ]([a-z])/g, "two"); // {MD039}
|
||||
input = input.toLowerCase();
|
||||
input = input.replace(/[- ]([a-z])/g, "three"); // {MD039}
|
||||
return input;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue