mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2026-01-25 16:36:09 +01:00
Re-implement MD038 to handle multi-line spans better and rely less on RegExp.
This commit is contained in:
parent
3b49414183
commit
ff50da3b42
7 changed files with 235 additions and 40 deletions
67
lib/md038.js
67
lib/md038.js
|
|
@ -4,49 +4,42 @@
|
|||
|
||||
const shared = require("./shared");
|
||||
|
||||
const inlineCodeSpansRe = /(?:^|[^\\])((`+)((?:[\s\S]*?[^`])|)\2(?!`))/g;
|
||||
const startRe = /^\s([^`]|$)/;
|
||||
const endRe = /[^`]\s$/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD038", "no-space-in-code" ],
|
||||
"description": "Spaces inside code span elements",
|
||||
"tags": [ "whitespace", "code" ],
|
||||
"function": function MD038(params, onError) {
|
||||
let lastParent = null;
|
||||
shared.forEachInlineChild(params, "code_inline",
|
||||
function forToken(token, parent) {
|
||||
if (lastParent !== parent) {
|
||||
lastParent = parent;
|
||||
inlineCodeSpansRe.lastIndex = 0;
|
||||
}
|
||||
const match = inlineCodeSpansRe.exec(parent.content);
|
||||
const content = (match || [])[3];
|
||||
const leftError = /^\s([^`]|$)/.test(content);
|
||||
const rightError = /[^`]\s$/.test(content);
|
||||
if (leftError || rightError) {
|
||||
const inlineCodeSpan = match[1];
|
||||
const leftContent = parent.content.substr(0,
|
||||
match.index + (match[0].length - inlineCodeSpan.length));
|
||||
const leftContentLines = leftContent.split(shared.newLineRe);
|
||||
const inlineCodeSpanLines = inlineCodeSpan.split(shared.newLineRe);
|
||||
let range = [
|
||||
leftContentLines[leftContentLines.length - 1].length + 1,
|
||||
inlineCodeSpanLines[0].length
|
||||
];
|
||||
if (leftError) {
|
||||
shared.addErrorContext(onError, token.lineNumber,
|
||||
inlineCodeSpan, true, false, range);
|
||||
} else {
|
||||
if (inlineCodeSpanLines.length > 1) {
|
||||
range = [
|
||||
1,
|
||||
inlineCodeSpanLines[inlineCodeSpanLines.length - 1].length
|
||||
];
|
||||
shared.filterTokens(params, "inline", (token) => {
|
||||
if (token.children.some((child) => child.type === "code_inline")) {
|
||||
const tokenLines = params.lines.slice(token.map[0], token.map[1]);
|
||||
shared.forEachInlineCodeSpan(
|
||||
tokenLines.join("\n"),
|
||||
(code, lineIndex, columnIndex, tickCount) => {
|
||||
let rangeIndex = columnIndex - tickCount;
|
||||
let rangeLength = code.length + (2 * tickCount);
|
||||
let rangeLineOffset = 0;
|
||||
const codeLines = code.split(shared.newLineRe);
|
||||
const left = startRe.test(code);
|
||||
const right = !left && endRe.test(code);
|
||||
if (right && (codeLines.length > 1)) {
|
||||
rangeIndex = 0;
|
||||
rangeLineOffset = codeLines.length - 1;
|
||||
}
|
||||
shared.addErrorContext(onError,
|
||||
token.lineNumber + content.split(shared.newLineRe).length - 1,
|
||||
inlineCodeSpan, false, true, range);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (left || right) {
|
||||
if (codeLines.length > 1) {
|
||||
rangeLength = codeLines[rangeLineOffset].length + tickCount;
|
||||
}
|
||||
const context = tokenLines[lineIndex + rangeLineOffset]
|
||||
.substring(rangeIndex, rangeIndex + rangeLength);
|
||||
shared.addErrorContext(
|
||||
onError, token.lineNumber + lineIndex + rangeLineOffset,
|
||||
context, left, right, [ rangeIndex + 1, rangeLength ]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -239,6 +239,69 @@ module.exports.forEachHeading = function forEachHeading(params, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
// Calls the provided function for each inline code span's content
|
||||
module.exports.forEachInlineCodeSpan =
|
||||
function forEachInlineCodeSpan(input, callback) {
|
||||
let currentLine = 0;
|
||||
let currentColumn = 0;
|
||||
let index = 0;
|
||||
while (index < input.length) {
|
||||
let startIndex = -1;
|
||||
let startLine = -1;
|
||||
let startColumn = -1;
|
||||
let tickCount = 0;
|
||||
let currentTicks = 0;
|
||||
// Deliberate <= so trailing 0 completes the last span (ex: "text `code`")
|
||||
for (; index <= input.length; index++) {
|
||||
const char = input[index];
|
||||
if (char === "`") {
|
||||
// Count backticks at start or end of code span
|
||||
currentTicks++;
|
||||
if ((startIndex === -1) || (startColumn === -1)) {
|
||||
startIndex = index + 1;
|
||||
}
|
||||
} else {
|
||||
if ((startIndex >= 0) &&
|
||||
(startColumn >= 0) &&
|
||||
(tickCount === currentTicks)) {
|
||||
// Found end backticks; invoke callback for code span
|
||||
callback(
|
||||
input.substring(startIndex, index - currentTicks),
|
||||
startLine, startColumn, tickCount);
|
||||
startIndex = -1;
|
||||
startColumn = -1;
|
||||
} else if ((startIndex >= 0) && (startColumn === -1)) {
|
||||
// Found start backticks
|
||||
tickCount = currentTicks;
|
||||
startLine = currentLine;
|
||||
startColumn = currentColumn;
|
||||
}
|
||||
// Not in backticks
|
||||
currentTicks = 0;
|
||||
}
|
||||
if (char === "\n") {
|
||||
// On next line
|
||||
currentLine++;
|
||||
currentColumn = 0;
|
||||
} else if ((char === "\\") &&
|
||||
((startIndex === -1) || (startColumn === -1))) {
|
||||
// Escape character outside code, skip next
|
||||
index++;
|
||||
currentColumn += 2;
|
||||
} else {
|
||||
// On next column
|
||||
currentColumn++;
|
||||
}
|
||||
}
|
||||
if (startIndex >= 0) {
|
||||
// Restart loop after unmatched start backticks (ex: "`text``code``")
|
||||
index = startIndex;
|
||||
currentLine = startLine;
|
||||
currentColumn = startColumn;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns (nested) lists as a flat array (in order)
|
||||
module.exports.flattenLists = function flattenLists() {
|
||||
return tokenCache.flattenedLists;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue