Add includesSorted function, use for faster searches of sorted arrays.

This commit is contained in:
David Anson 2019-03-28 22:06:42 -07:00
parent d7c0d195d7
commit 9b9532e163
8 changed files with 86 additions and 50 deletions

View file

@ -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));
}
}
});

View file

@ -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,

View file

@ -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);
}
});
}

View file

@ -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);
}
};

View file

@ -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 ]);
}
}

View file

@ -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);
}
});
}

View file

@ -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

View file

@ -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",