mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-17 14:30:12 +01:00
Add includesSorted function, use for faster searches of sorted arrays.
This commit is contained in:
parent
d7c0d195d7
commit
9b9532e163
8 changed files with 86 additions and 50 deletions
16
lib/md009.js
16
lib/md009.js
|
|
@ -3,6 +3,8 @@
|
|||
"use strict";
|
||||
|
||||
const shared = require("./shared");
|
||||
const { addError, filterTokens, forEachLine, includesSorted, rangeFromRegExp,
|
||||
trimRight } = shared;
|
||||
|
||||
const trailingSpaceRe = /\s+$/;
|
||||
|
||||
|
|
@ -20,24 +22,24 @@ module.exports = {
|
|||
(listItemEmptyLines === undefined) ? false : !!listItemEmptyLines;
|
||||
const listItemLineNumbers = [];
|
||||
if (allowListItemEmptyLines) {
|
||||
shared.filterTokens(params, "list_item_open", function forToken(token) {
|
||||
filterTokens(params, "list_item_open", (token) => {
|
||||
for (let i = token.map[0]; i < token.map[1]; i++) {
|
||||
listItemLineNumbers.push(i + 1);
|
||||
}
|
||||
});
|
||||
listItemLineNumbers.sort((a, b) => a - b);
|
||||
}
|
||||
const expected = (brSpaces < 2) ? 0 : brSpaces;
|
||||
shared.forEachLine(function forLine(line, lineIndex) {
|
||||
forEachLine((line, lineIndex) => {
|
||||
const lineNumber = lineIndex + 1;
|
||||
if (trailingSpaceRe.test(line) &&
|
||||
(listItemLineNumbers.indexOf(lineNumber) === -1)) {
|
||||
const actual = line.length - shared.trimRight(line).length;
|
||||
!includesSorted(listItemLineNumbers, lineNumber)) {
|
||||
const actual = line.length - trimRight(line).length;
|
||||
if (expected !== actual) {
|
||||
shared.addError(onError, lineNumber,
|
||||
addError(onError, lineNumber,
|
||||
"Expected: " + (expected === 0 ? "" : "0 or ") +
|
||||
expected + "; Actual: " + actual,
|
||||
null,
|
||||
shared.rangeFromRegExp(line, trailingSpaceRe));
|
||||
null, rangeFromRegExp(line, trailingSpaceRe));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
"use strict";
|
||||
|
||||
const shared = require("./shared");
|
||||
const {
|
||||
addErrorDetailIf, filterTokens, forEachHeading, forEachLine, rangeFromRegExp
|
||||
} = shared;
|
||||
const { addErrorDetailIf, filterTokens, forEachHeading, forEachLine,
|
||||
includesSorted, rangeFromRegExp } = shared;
|
||||
|
||||
const longLineRePrefix = "^(.{";
|
||||
const longLineRePostfix = "})(.*\\s.*)$";
|
||||
|
|
@ -59,13 +58,13 @@ module.exports = {
|
|||
});
|
||||
forEachLine((line, lineIndex, inCode, onFence, inTable) => {
|
||||
const lineNumber = lineIndex + 1;
|
||||
const isHeading = headingLineNumbers.indexOf(lineNumber) >= 0;
|
||||
const isHeading = includesSorted(headingLineNumbers, lineNumber);
|
||||
const length = isHeading ? headingLineLength : lineLength;
|
||||
const lengthRe = isHeading ? longHeadingLineRe : longLineRe;
|
||||
if ((includeCodeBlocks || !inCode) &&
|
||||
(includeTables || !inTable) &&
|
||||
(includeHeadings || !isHeading) &&
|
||||
(linkOnlyLineNumbers.indexOf(lineNumber) < 0) &&
|
||||
!includesSorted(linkOnlyLineNumbers, lineNumber) &&
|
||||
lengthRe.test(line) &&
|
||||
!labelRe.test(line)) {
|
||||
addErrorDetailIf(onError, lineNumber, length, line.length,
|
||||
|
|
|
|||
11
lib/md024.js
11
lib/md024.js
|
|
@ -3,6 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const shared = require("./shared");
|
||||
const { addErrorContext, forEachHeading } = shared;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD024", "no-duplicate-heading", "no-duplicate-header" ],
|
||||
|
|
@ -14,7 +15,7 @@ module.exports = {
|
|||
const knownContents = [ null, [] ];
|
||||
let lastLevel = 1;
|
||||
let knownContent = knownContents[lastLevel];
|
||||
shared.forEachHeading(params, function forHeading(heading, content) {
|
||||
forEachHeading(params, (heading, content) => {
|
||||
if (siblingsOnly) {
|
||||
const newLevel = heading.tag.slice(1);
|
||||
while (lastLevel < newLevel) {
|
||||
|
|
@ -27,11 +28,11 @@ module.exports = {
|
|||
}
|
||||
knownContent = knownContents[newLevel];
|
||||
}
|
||||
if (knownContent.indexOf(content) === -1) {
|
||||
knownContent.push(content);
|
||||
} else {
|
||||
shared.addErrorContext(onError, heading.lineNumber,
|
||||
if (knownContent.includes(content)) {
|
||||
addErrorContext(onError, heading.lineNumber,
|
||||
heading.line.trim());
|
||||
} else {
|
||||
knownContent.push(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
31
lib/md033.js
31
lib/md033.js
|
|
@ -3,6 +3,9 @@
|
|||
"use strict";
|
||||
|
||||
const shared = require("./shared");
|
||||
const {
|
||||
addError, filterTokens, forEachInlineChild, newLineRe, rangeFromRegExp
|
||||
} = shared;
|
||||
|
||||
const htmlRe = /<[^>]*>/;
|
||||
|
||||
|
|
@ -12,30 +15,22 @@ module.exports = {
|
|||
"tags": [ "html" ],
|
||||
"function": function MD033(params, onError) {
|
||||
const allowedElements = (params.config.allowed_elements || [])
|
||||
.map(function forElement(element) {
|
||||
return element.toLowerCase();
|
||||
});
|
||||
.map((element) => element.toLowerCase());
|
||||
function forToken(token) {
|
||||
token.content.split(shared.newLineRe)
|
||||
.forEach(function forLine(line, offset) {
|
||||
token.content.split(newLineRe)
|
||||
.forEach((line, offset) => {
|
||||
const allowed = (line.match(/<[^/\s>!]*/g) || [])
|
||||
.filter(function forElement(element) {
|
||||
return element.length > 1;
|
||||
})
|
||||
.map(function forElement(element) {
|
||||
return element.slice(1).toLowerCase();
|
||||
})
|
||||
.filter(function forElement(element) {
|
||||
return allowedElements.indexOf(element) === -1;
|
||||
});
|
||||
.filter((element) => element.length > 1)
|
||||
.map((element) => element.slice(1).toLowerCase())
|
||||
.filter((element) => !allowedElements.includes(element));
|
||||
if (allowed.length) {
|
||||
shared.addError(onError, token.lineNumber + offset,
|
||||
addError(onError, token.lineNumber + offset,
|
||||
"Element: " + allowed[0], null,
|
||||
shared.rangeFromRegExp(token.line, htmlRe));
|
||||
rangeFromRegExp(token.line, htmlRe));
|
||||
}
|
||||
});
|
||||
}
|
||||
shared.filterTokens(params, "html_block", forToken);
|
||||
shared.forEachInlineChild(params, "html_inline", forToken);
|
||||
filterTokens(params, "html_block", forToken);
|
||||
forEachInlineChild(params, "html_inline", forToken);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
"use strict";
|
||||
|
||||
const shared = require("./shared");
|
||||
const { addErrorContext, forEachInlineChild } = shared;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD037", "no-space-in-emphasis" ],
|
||||
"description": "Spaces inside emphasis markers",
|
||||
"tags": [ "whitespace", "emphasis" ],
|
||||
"function": function MD037(params, onError) {
|
||||
shared.forEachInlineChild(params, "text", function forToken(token) {
|
||||
forEachInlineChild(params, "text", (token) => {
|
||||
let left = true;
|
||||
let match = /(?:^|\s)(\*\*?|__?)\s.*[^\\]\1/.exec(token.content);
|
||||
if (!match) {
|
||||
|
|
@ -19,11 +20,11 @@ module.exports = {
|
|||
if (match) {
|
||||
const fullText = match[0];
|
||||
const line = params.lines[token.lineNumber - 1];
|
||||
if (line.indexOf(fullText) !== -1) {
|
||||
if (line.includes(fullText)) {
|
||||
const text = fullText.trim();
|
||||
const column = line.indexOf(text) + 1;
|
||||
const length = text.length;
|
||||
shared.addErrorContext(onError, token.lineNumber,
|
||||
addErrorContext(onError, token.lineNumber,
|
||||
text, left, !left, [ column, length ]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
lib/md044.js
24
lib/md044.js
|
|
@ -3,6 +3,8 @@
|
|||
"use strict";
|
||||
|
||||
const shared = require("./shared");
|
||||
const { addErrorDetailIf, bareUrlRe, escapeForRegExp, filterTokens,
|
||||
forEachInlineChild, newLineRe } = shared;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD044", "proper-names" ],
|
||||
|
|
@ -12,35 +14,35 @@ module.exports = {
|
|||
const names = params.config.names || [];
|
||||
const codeBlocks = params.config.code_blocks;
|
||||
const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||
names.forEach(function forName(name) {
|
||||
const escapedName = shared.escapeForRegExp(name);
|
||||
names.forEach((name) => {
|
||||
const escapedName = escapeForRegExp(name);
|
||||
const namePattern = "\\S*\\b(" + escapedName + ")\\b\\S*";
|
||||
const anyNameRe = new RegExp(namePattern, "gi");
|
||||
function forToken(token) {
|
||||
const fenceOffset = (token.type === "fence") ? 1 : 0;
|
||||
token.content.split(shared.newLineRe)
|
||||
.forEach(function forLine(line, index) {
|
||||
token.content.split(newLineRe)
|
||||
.forEach((line, index) => {
|
||||
let match = null;
|
||||
while ((match = anyNameRe.exec(line)) !== null) {
|
||||
const fullMatch = match[0];
|
||||
if (!shared.bareUrlRe.test(fullMatch)) {
|
||||
if (!bareUrlRe.test(fullMatch)) {
|
||||
const wordMatch = fullMatch
|
||||
.replace(/^\W*/, "").replace(/\W*$/, "");
|
||||
if (names.indexOf(wordMatch) === -1) {
|
||||
if (!names.includes(wordMatch)) {
|
||||
const lineNumber = token.lineNumber + index + fenceOffset;
|
||||
const range = [ match.index + 1, wordMatch.length ];
|
||||
shared.addErrorDetailIf(onError, lineNumber,
|
||||
addErrorDetailIf(onError, lineNumber,
|
||||
name, match[1], null, null, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
shared.forEachInlineChild(params, "text", forToken);
|
||||
forEachInlineChild(params, "text", forToken);
|
||||
if (includeCodeBlocks) {
|
||||
shared.forEachInlineChild(params, "code_inline", forToken);
|
||||
shared.filterTokens(params, "code_block", forToken);
|
||||
shared.filterTokens(params, "fence", forToken);
|
||||
forEachInlineChild(params, "code_inline", forToken);
|
||||
filterTokens(params, "code_block", forToken);
|
||||
filterTokens(params, "fence", forToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,23 @@ module.exports.isBlankLine = function isBlankLine(line) {
|
|||
return !line || !line.trim() || !line.replace(blankLineRe, "").trim();
|
||||
};
|
||||
|
||||
// Returns true iff the sorted array contains the specified element
|
||||
module.exports.includesSorted = function includesSorted(array, element) {
|
||||
let left = 0;
|
||||
let right = array.length - 1;
|
||||
while (left <= right) {
|
||||
const mid = (left + right) >> 1;
|
||||
if (array[mid] < element) {
|
||||
left = mid + 1;
|
||||
} else if (array[mid] > element) {
|
||||
right = mid - 1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Replaces the text of all properly-formatted HTML comments with whitespace
|
||||
// This preserves the line/column information for the rest of the document
|
||||
// Trailing whitespace is avoided with a '\' character in the last column
|
||||
|
|
@ -89,7 +106,7 @@ module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
|
|||
if ((comment.length > 0) &&
|
||||
(comment[0] !== ">") &&
|
||||
(comment[comment.length - 1] !== "-") &&
|
||||
(comment.indexOf("--") === -1) &&
|
||||
!comment.includes("--") &&
|
||||
(text.slice(i, j + htmlCommentEnd.length)
|
||||
.search(inlineCommentRe) === -1)) {
|
||||
const blanks = comment
|
||||
|
|
|
|||
|
|
@ -1432,6 +1432,25 @@ module.exports.isBlankLine = function isBlankLine(test) {
|
|||
test.done();
|
||||
};
|
||||
|
||||
module.exports.includesSorted = function includesSorted(test) {
|
||||
test.expect(154);
|
||||
const inputs = [
|
||||
[ ],
|
||||
[ 8 ],
|
||||
[ 7, 11 ],
|
||||
[ 0, 1, 2, 3, 5, 8, 13 ],
|
||||
[ 2, 3, 5, 7, 11, 13, 17, 19 ],
|
||||
[ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 ],
|
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ]
|
||||
];
|
||||
inputs.forEach((input) => {
|
||||
for (let i = 0; i <= 21; i++) {
|
||||
test.equal(shared.includesSorted(input, i), input.includes(i));
|
||||
}
|
||||
});
|
||||
test.done();
|
||||
};
|
||||
|
||||
module.exports.trimLeftRight = function trimLeftRight(test) {
|
||||
const inputs = [
|
||||
"text text",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue