mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-22 05:40:48 +02:00
Update MD022/MD031/MD032 to report fixInfo for violations, normalize input to fixErrors.
This commit is contained in:
parent
2cd27c58f2
commit
a062e7c6bd
6 changed files with 357 additions and 38 deletions
|
@ -53,6 +53,7 @@ A rule is implemented as an `Object` with four required properties:
|
||||||
- `context` is an optional `String` with relevant text surrounding the error location.
|
- `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.
|
- `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:
|
- `fixInfo` is an optional `Object` with information about how to fix the error:
|
||||||
|
- `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.
|
- `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 with the edit.
|
- `deleteCount` is an optional `Number` specifying the count of characters to delete with the edit.
|
||||||
- `insertText` is an optional `String` specifying text to insert as part of the edit.
|
- `insertText` is an optional `String` specifying text to insert as part of the edit.
|
||||||
|
|
|
@ -359,19 +359,19 @@ module.exports.addErrorDetailIf = function addErrorDetailIf(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adds an error object with context via the onError callback
|
// Adds an error object with context via the onError callback
|
||||||
module.exports.addErrorContext =
|
module.exports.addErrorContext = function addErrorContext(
|
||||||
function addErrorContext(onError, lineNumber, context, left, right, range) {
|
onError, lineNumber, context, left, right, range, fixInfo) {
|
||||||
if (context.length <= 30) {
|
if (context.length <= 30) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
} else if (left && right) {
|
} else if (left && right) {
|
||||||
context = context.substr(0, 15) + "..." + context.substr(-15);
|
context = context.substr(0, 15) + "..." + context.substr(-15);
|
||||||
} else if (right) {
|
} else if (right) {
|
||||||
context = "..." + context.substr(-30);
|
context = "..." + context.substr(-30);
|
||||||
} else {
|
} else {
|
||||||
context = context.substr(0, 30) + "...";
|
context = context.substr(0, 30) + "...";
|
||||||
}
|
}
|
||||||
addError(onError, lineNumber, null, context, range);
|
addError(onError, lineNumber, null, context, range, fixInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a range object for a line by applying a RegExp
|
// Returns a range object for a line by applying a RegExp
|
||||||
module.exports.rangeFromRegExp = function rangeFromRegExp(line, regexp) {
|
module.exports.rangeFromRegExp = function rangeFromRegExp(line, regexp) {
|
||||||
|
@ -403,20 +403,46 @@ module.exports.frontMatterHasTitle =
|
||||||
// Applies as many fixes as possible to the input
|
// Applies as many fixes as possible to the input
|
||||||
module.exports.fixErrors = function fixErrors(input, errors) {
|
module.exports.fixErrors = function fixErrors(input, errors) {
|
||||||
const lines = input.split(newLineRe);
|
const lines = input.split(newLineRe);
|
||||||
errors.filter((error) => !!error.fixInfo).forEach((error) => {
|
// Normalize fixInfo objects
|
||||||
const { lineNumber, fixInfo } = error;
|
const fixInfos = errors.filter((error) => !!error.fixInfo).map((error) => {
|
||||||
const editColumn = fixInfo.editColumn || 1;
|
const { fixInfo } = error;
|
||||||
const deleteCount = fixInfo.deleteCount || 0;
|
return {
|
||||||
const insertText = fixInfo.insertText || "";
|
"lineNumber": fixInfo.lineNumber || error.lineNumber,
|
||||||
|
"editColumn": fixInfo.editColumn || 1,
|
||||||
|
"deleteCount": fixInfo.deleteCount || 0,
|
||||||
|
"insertText": fixInfo.insertText || ""
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// Sort bottom-to-top, deletes last, right-to-left, long-to-short
|
||||||
|
fixInfos.sort((a, b) => (
|
||||||
|
(b.lineNumber - a.lineNumber) ||
|
||||||
|
((a.deleteCount === -1) ? 1 : ((b.deleteCount === -1) ? -1 : 0)) ||
|
||||||
|
(b.editColumn - a.editColumn) ||
|
||||||
|
(b.insertText.length - a.insertText.length)
|
||||||
|
));
|
||||||
|
// Apply all fixes
|
||||||
|
let lastLineIndex = -1;
|
||||||
|
let lastEditIndex = -1;
|
||||||
|
fixInfos.forEach((fixInfo) => {
|
||||||
|
const { lineNumber, editColumn, deleteCount, insertText } = fixInfo;
|
||||||
const lineIndex = lineNumber - 1;
|
const lineIndex = lineNumber - 1;
|
||||||
const editIndex = editColumn - 1;
|
const editIndex = editColumn - 1;
|
||||||
const line = lines[lineIndex];
|
if (
|
||||||
lines[lineIndex] =
|
(lineIndex !== lastLineIndex) ||
|
||||||
(deleteCount === -1) ?
|
((editIndex + deleteCount) < lastEditIndex) ||
|
||||||
null :
|
(deleteCount === -1)
|
||||||
line.slice(0, editIndex) +
|
) {
|
||||||
insertText +
|
const line = lines[lineIndex];
|
||||||
line.slice(editIndex + deleteCount);
|
lines[lineIndex] =
|
||||||
|
(deleteCount === -1) ?
|
||||||
|
null :
|
||||||
|
line.slice(0, editIndex) +
|
||||||
|
insertText +
|
||||||
|
line.slice(editIndex + deleteCount);
|
||||||
|
}
|
||||||
|
lastLineIndex = lineIndex;
|
||||||
|
lastEditIndex = editIndex;
|
||||||
});
|
});
|
||||||
|
// Return corrected input
|
||||||
return lines.filter((line) => line !== null).join("\n");
|
return lines.filter((line) => line !== null).join("\n");
|
||||||
};
|
};
|
||||||
|
|
29
lib/md022.js
29
lib/md022.js
|
@ -22,16 +22,33 @@ module.exports = {
|
||||||
const [ topIndex, nextIndex ] = token.map;
|
const [ topIndex, nextIndex ] = token.map;
|
||||||
for (let i = 0; i < linesAbove; i++) {
|
for (let i = 0; i < linesAbove; i++) {
|
||||||
if (!isBlankLine(lines[topIndex - i - 1])) {
|
if (!isBlankLine(lines[topIndex - i - 1])) {
|
||||||
addErrorDetailIf(onError, topIndex + 1, linesAbove, i, "Above",
|
addErrorDetailIf(
|
||||||
lines[topIndex].trim());
|
onError,
|
||||||
return;
|
topIndex + 1,
|
||||||
|
linesAbove,
|
||||||
|
i,
|
||||||
|
"Above",
|
||||||
|
lines[topIndex].trim(),
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"insertText": "\n"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < linesBelow; i++) {
|
for (let i = 0; i < linesBelow; i++) {
|
||||||
if (!isBlankLine(lines[nextIndex + i])) {
|
if (!isBlankLine(lines[nextIndex + i])) {
|
||||||
addErrorDetailIf(onError, topIndex + 1, linesBelow, i, "Below",
|
addErrorDetailIf(
|
||||||
lines[topIndex].trim());
|
onError,
|
||||||
return;
|
topIndex + 1,
|
||||||
|
linesBelow,
|
||||||
|
i,
|
||||||
|
"Below",
|
||||||
|
lines[topIndex].trim(),
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"lineNumber": nextIndex + 1,
|
||||||
|
"insertText": "\n"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
20
lib/md031.js
20
lib/md031.js
|
@ -14,10 +14,22 @@ module.exports = {
|
||||||
const includeListItems = (listItems === undefined) ? true : !!listItems;
|
const includeListItems = (listItems === undefined) ? true : !!listItems;
|
||||||
const { lines } = params;
|
const { lines } = params;
|
||||||
forEachLine(lineMetadata(), (line, i, inCode, onFence, inTable, inItem) => {
|
forEachLine(lineMetadata(), (line, i, inCode, onFence, inTable, inItem) => {
|
||||||
if ((((onFence > 0) && !isBlankLine(lines[i - 1])) ||
|
const onTopFence = (onFence > 0);
|
||||||
((onFence < 0) && !isBlankLine(lines[i + 1]))) &&
|
const onBottomFence = (onFence < 0);
|
||||||
(includeListItems || !inItem)) {
|
if ((includeListItems || !inItem) &&
|
||||||
addErrorContext(onError, i + 1, lines[i].trim());
|
((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 { addErrorContext, isBlankLine } = require("../helpers");
|
||||||
const { flattenedLists } = require("./cache");
|
const { flattenedLists } = require("./cache");
|
||||||
|
|
||||||
|
const quotePrefixRe = /^[>\s]*/;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"names": [ "MD032", "blanks-around-lists" ],
|
"names": [ "MD032", "blanks-around-lists" ],
|
||||||
"description": "Lists should be surrounded by blank lines",
|
"description": "Lists should be surrounded by blank lines",
|
||||||
|
@ -14,11 +16,34 @@ module.exports = {
|
||||||
flattenedLists().filter((list) => !list.nesting).forEach((list) => {
|
flattenedLists().filter((list) => !list.nesting).forEach((list) => {
|
||||||
const firstIndex = list.open.map[0];
|
const firstIndex = list.open.map[0];
|
||||||
if (!isBlankLine(lines[firstIndex - 1])) {
|
if (!isBlankLine(lines[firstIndex - 1])) {
|
||||||
addErrorContext(onError, firstIndex + 1, lines[firstIndex].trim());
|
const line = lines[firstIndex];
|
||||||
|
const quotePrefix = line.match(quotePrefixRe)[0].trimEnd();
|
||||||
|
addErrorContext(
|
||||||
|
onError,
|
||||||
|
firstIndex + 1,
|
||||||
|
line.trim(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"insertText": `${quotePrefix}\n`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const lastIndex = list.lastLineIndex - 1;
|
const lastIndex = list.lastLineIndex - 1;
|
||||||
if (!isBlankLine(lines[lastIndex + 1])) {
|
if (!isBlankLine(lines[lastIndex + 1])) {
|
||||||
addErrorContext(onError, lastIndex + 1, lines[lastIndex].trim());
|
const line = lines[lastIndex];
|
||||||
|
const quotePrefix = line.match(quotePrefixRe)[0].trimEnd();
|
||||||
|
addErrorContext(
|
||||||
|
onError,
|
||||||
|
lastIndex + 1,
|
||||||
|
line.trim(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"lineNumber": lastIndex + 2,
|
||||||
|
"insertText": `${quotePrefix}\n`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1898,7 +1898,7 @@ module.exports.forEachInlineCodeSpan = function forEachInlineCodeSpan(test) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.fixErrors = function fixErrors(test) {
|
module.exports.fixErrors = function fixErrors(test) {
|
||||||
test.expect(10);
|
test.expect(23);
|
||||||
const testCases = [
|
const testCases = [
|
||||||
[
|
[
|
||||||
"Hello world.",
|
"Hello world.",
|
||||||
|
@ -2022,6 +2022,244 @@ module.exports.fixErrors = function fixErrors(test) {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Hello"
|
"Hello"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello\nworld",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"lineNumber": 2,
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"deleteCount": -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"world"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello\nworld",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 2,
|
||||||
|
"deleteCount": -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Hello"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"fixInfo": {
|
||||||
|
"editColumn": 4,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"fixInfo": {
|
||||||
|
"editColumn": 10,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Helo word"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"fixInfo": {
|
||||||
|
"editColumn": 10,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"fixInfo": {
|
||||||
|
"editColumn": 4,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Helo word"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello\nworld",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"deleteCount": -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"insertText": "Big "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"world"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello\nworld",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"deleteCount": -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 2,
|
||||||
|
"deleteCount": -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"insertText": "aa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"insertText": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aaHello world"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"insertText": "a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"insertText": "bb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bbHello world"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 6,
|
||||||
|
"insertText": " big"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Hello big orld"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 8,
|
||||||
|
"deleteCount": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"deleteCount": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Hello wld"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"deleteCount": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 8,
|
||||||
|
"deleteCount": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Hello wld"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"insertText": "z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Hello zworld"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Hello world",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"insertText": "z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fixInfo": {
|
||||||
|
"lineNumber": 1,
|
||||||
|
"editColumn": 7,
|
||||||
|
"deleteCount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Hello zworld"
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
testCases.forEach((testCase) => {
|
testCases.forEach((testCase) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue