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