Remove state from shared.js, move to cache.js.

This commit is contained in:
David Anson 2019-04-10 21:26:59 -07:00
parent 827e1acb56
commit f614f3e1ce
17 changed files with 124 additions and 98 deletions

24
lib/cache.js Normal file
View file

@ -0,0 +1,24 @@
// @ts-check
"use strict";
let lineMetadata = null;
module.exports.lineMetadata = (value) => {
if (value) {
lineMetadata = value;
}
return lineMetadata;
};
let flattenedLists = null;
module.exports.flattenedLists = (value) => {
if (value) {
flattenedLists = value;
}
return flattenedLists;
};
module.exports.clear = () => {
lineMetadata = null;
flattenedLists = null;
};

View file

@ -8,6 +8,7 @@ const { URL } = require("url");
const markdownIt = require("markdown-it");
const rules = require("./rules");
const shared = require("./shared");
const cache = require("./cache");
const deprecatedRuleNames = [ "MD002" ];
@ -327,7 +328,8 @@ function lintContent(
lines,
frontMatterLines
};
shared.makeTokenCache(params);
cache.lineMetadata(shared.getLineMetadata(params));
cache.flattenedLists(shared.flattenLists(params));
// Function to run for each rule
const result = (resultVersion === 0) ? {} : [];
function forRule(rule) {
@ -411,10 +413,10 @@ function lintContent(
try {
ruleList.forEach(forRule);
} catch (ex) {
shared.makeTokenCache(null);
cache.clear();
return callback(ex);
}
shared.makeTokenCache(null);
cache.clear();
return callback(null, result);
}

View file

@ -2,7 +2,9 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, listItemMarkerRe,
rangeFromRegExp } = require("./shared");
const { flattenedLists } = require("./cache");
// Returns the unordered list style for a list item token
function unorderedListStyleFor(token) {
@ -25,12 +27,12 @@ module.exports = {
const style = params.config.style || "consistent";
let expectedStyle = style;
const nestingStyles = [];
shared.flattenLists().forEach(function forList(list) {
flattenedLists().forEach((list) => {
if (list.unordered) {
if (expectedStyle === "consistent") {
expectedStyle = unorderedListStyleFor(list.items[0]);
}
list.items.forEach(function forItem(item) {
list.items.forEach((item) => {
const itemStyle = unorderedListStyleFor(item);
if (style === "sublist") {
const nesting = list.nesting;
@ -38,14 +40,14 @@ module.exports = {
(itemStyle !== nestingStyles[nesting - 1])) {
nestingStyles[nesting] = itemStyle;
} else {
shared.addErrorDetailIf(onError, item.lineNumber,
addErrorDetailIf(onError, item.lineNumber,
nestingStyles[nesting], itemStyle, null, null,
shared.rangeFromRegExp(item.line, shared.listItemMarkerRe));
rangeFromRegExp(item.line, listItemMarkerRe));
}
} else {
shared.addErrorDetailIf(onError, item.lineNumber,
addErrorDetailIf(onError, item.lineNumber,
expectedStyle, itemStyle, null, null,
shared.rangeFromRegExp(item.line, shared.listItemMarkerRe));
rangeFromRegExp(item.line, listItemMarkerRe));
}
});
}

View file

@ -2,26 +2,28 @@
"use strict";
const shared = require("./shared");
const { addError, addErrorDetailIf, indentFor, listItemMarkerRe,
orderedListItemMarkerRe, rangeFromRegExp } = require("./shared");
const { flattenedLists } = require("./cache");
module.exports = {
"names": [ "MD005", "list-indent" ],
"description": "Inconsistent indentation for list items at the same level",
"tags": [ "bullet", "ul", "indentation" ],
"function": function MD005(params, onError) {
shared.flattenLists().forEach(function forList(list) {
flattenedLists().forEach((list) => {
const expectedIndent = list.indent;
let expectedEnd = 0;
let actualEnd = -1;
let endMatching = false;
list.items.forEach(function forItem(item) {
const actualIndent = shared.indentFor(item);
list.items.forEach((item) => {
const actualIndent = indentFor(item);
if (list.unordered) {
shared.addErrorDetailIf(onError, item.lineNumber,
addErrorDetailIf(onError, item.lineNumber,
expectedIndent, actualIndent, null, null,
shared.rangeFromRegExp(item.line, shared.listItemMarkerRe));
rangeFromRegExp(item.line, listItemMarkerRe));
} else {
const match = shared.orderedListItemMarkerRe.exec(item.line);
const match = orderedListItemMarkerRe.exec(item.line);
actualEnd = match && match[0].length;
expectedEnd = expectedEnd || actualEnd;
if ((expectedIndent !== actualIndent) || endMatching) {
@ -31,8 +33,8 @@ module.exports = {
const detail = endMatching ?
`Expected: (${expectedEnd}); Actual: (${actualEnd})` :
`Expected: ${expectedIndent}; Actual: ${actualIndent}`;
shared.addError(onError, item.lineNumber, detail, null,
shared.rangeFromRegExp(item.line, shared.listItemMarkerRe));
addError(onError, item.lineNumber, detail, null,
rangeFromRegExp(item.line, listItemMarkerRe));
}
}
}

View file

@ -2,7 +2,9 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
require("./shared");
const { flattenedLists } = require("./cache");
module.exports = {
"names": [ "MD006", "ul-start-left" ],
@ -10,11 +12,11 @@ module.exports = {
"Consider starting bulleted lists at the beginning of the line",
"tags": [ "bullet", "ul", "indentation" ],
"function": function MD006(params, onError) {
shared.flattenLists().forEach(function forList(list) {
flattenedLists().forEach((list) => {
if (list.unordered && !list.nesting) {
shared.addErrorDetailIf(onError, list.open.lineNumber,
addErrorDetailIf(onError, list.open.lineNumber,
0, list.indent, null, null,
shared.rangeFromRegExp(list.open.line, shared.listItemMarkerRe));
rangeFromRegExp(list.open.line, listItemMarkerRe));
}
});
}

View file

@ -2,7 +2,9 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
require("./shared");
const { flattenedLists } = require("./cache");
module.exports = {
"names": [ "MD007", "ul-indent" ],
@ -10,11 +12,11 @@ module.exports = {
"tags": [ "bullet", "ul", "indentation" ],
"function": function MD007(params, onError) {
const optionsIndent = params.config.indent || 2;
shared.flattenLists().forEach(function forList(list) {
flattenedLists().forEach((list) => {
if (list.unordered && list.parentsUnordered && list.indent) {
shared.addErrorDetailIf(onError, list.open.lineNumber,
addErrorDetailIf(onError, list.open.lineNumber,
list.parentIndent + optionsIndent, list.indent, null, null,
shared.rangeFromRegExp(list.open.line, shared.listItemMarkerRe));
rangeFromRegExp(list.open.line, listItemMarkerRe));
}
});
}

View file

@ -2,9 +2,9 @@
"use strict";
const shared = require("./shared");
const { addError, filterTokens, forEachLine, includesSorted, rangeFromRegExp,
trimRight } = shared;
trimRight } = require("./shared");
const { lineMetadata } = require("./cache");
const trailingSpaceRe = /\s+$/;
@ -30,7 +30,7 @@ module.exports = {
listItemLineNumbers.sort((a, b) => a - b);
}
const expected = (brSpaces < 2) ? 0 : brSpaces;
forEachLine((line, lineIndex) => {
forEachLine(lineMetadata(), (line, lineIndex) => {
const lineNumber = lineIndex + 1;
if (trailingSpaceRe.test(line) &&
!includesSorted(listItemLineNumbers, lineNumber)) {

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addError, forEachLine, rangeFromRegExp } = require("./shared");
const { lineMetadata } = require("./cache");
const tabRe = /\t+/;
@ -13,11 +14,10 @@ module.exports = {
"function": function MD010(params, onError) {
const codeBlocks = params.config.code_blocks;
const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
shared.forEachLine(function forLine(line, lineIndex, inCode) {
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
if (tabRe.test(line) && (!inCode || includeCodeBlocks)) {
shared.addError(onError, lineIndex + 1,
"Column: " + (line.indexOf("\t") + 1), null,
shared.rangeFromRegExp(line, tabRe));
addError(onError, lineIndex + 1, "Column: " + (line.indexOf("\t") + 1),
null, rangeFromRegExp(line, tabRe));
}
});
}

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, forEachLine } = require("./shared");
const { lineMetadata } = require("./cache");
module.exports = {
"names": [ "MD012", "no-multiple-blanks" ],
@ -11,10 +12,10 @@ module.exports = {
"function": function MD012(params, onError) {
const maximum = params.config.maximum || 1;
let count = 0;
shared.forEachLine(function forLine(line, lineIndex, inCode) {
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
count = (inCode || line.trim().length) ? 0 : count + 1;
if (maximum < count) {
shared.addErrorDetailIf(onError, lineIndex + 1, maximum, count);
addErrorDetailIf(onError, lineIndex + 1, maximum, count);
}
});
}

View file

@ -2,9 +2,9 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, filterTokens, forEachHeading, forEachLine,
includesSorted, rangeFromRegExp } = shared;
includesSorted, rangeFromRegExp } = require("./shared");
const { lineMetadata } = require("./cache");
const longLineRePrefix = "^(.{";
const longLineRePostfix = "})(.*\\s.*)$";
@ -56,7 +56,7 @@ module.exports = {
linkOnlyLineNumbers.push(token.lineNumber);
}
});
forEachLine((line, lineIndex, inCode, onFence, inTable) => {
forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence, inTable) => {
const lineNumber = lineIndex + 1;
const isHeading = includesSorted(headingLineNumbers, lineNumber);
const length = isHeading ? headingLineLength : lineLength;

View file

@ -2,17 +2,19 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, atxHeadingSpaceRe, forEachLine,
rangeFromRegExp } = require("./shared");
const { lineMetadata } = require("./cache");
module.exports = {
"names": [ "MD018", "no-missing-space-atx" ],
"description": "No space after hash on atx style heading",
"tags": [ "headings", "headers", "atx", "spaces" ],
"function": function MD018(params, onError) {
shared.forEachLine(function forLine(line, lineIndex, inCode) {
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
shared.addErrorContext(onError, lineIndex + 1, line.trim(), null,
null, shared.rangeFromRegExp(line, shared.atxHeadingSpaceRe));
addErrorContext(onError, lineIndex + 1, line.trim(), null,
null, rangeFromRegExp(line, atxHeadingSpaceRe));
}
});
}

View file

@ -2,7 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, forEachLine, rangeFromRegExp } = require("./shared");
const { lineMetadata } = require("./cache");
const atxClosedHeadingNoSpaceRe = /(?:^#+[^#\s])|(?:[^#\s]#+\s*$)/;
@ -11,13 +12,13 @@ module.exports = {
"description": "No space inside hashes on closed atx style heading",
"tags": [ "headings", "headers", "atx_closed", "spaces" ],
"function": function MD020(params, onError) {
shared.forEachLine(function forLine(line, lineIndex, inCode) {
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line)) {
const left = /^#+[^#\s]/.test(line);
const right = /[^#\s]#+$/.test(line);
if (left || right) {
shared.addErrorContext(onError, lineIndex + 1, line.trim(), left,
right, shared.rangeFromRegExp(line, atxClosedHeadingNoSpaceRe));
addErrorContext(onError, lineIndex + 1, line.trim(), left,
right, rangeFromRegExp(line, atxClosedHeadingNoSpaceRe));
}
}
});

View file

@ -2,7 +2,9 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, listItemMarkerRe, orderedListItemMarkerRe,
rangeFromRegExp } = require("./shared");
const { flattenedLists } = require("./cache");
const listStyleExamples = {
"one": "1/1/1",
@ -16,21 +18,21 @@ module.exports = {
"tags": [ "ol" ],
"function": function MD029(params, onError) {
const style = params.config.style || "one_or_ordered";
shared.flattenLists().forEach(function forList(list) {
flattenedLists().forEach((list) => {
if (!list.unordered) {
let listStyle = style;
if (listStyle === "one_or_ordered") {
const second = (list.items.length > 1) &&
shared.orderedListItemMarkerRe.exec(list.items[1].line);
orderedListItemMarkerRe.exec(list.items[1].line);
listStyle = (second && (second[1] !== "1")) ? "ordered" : "one";
}
let number = (listStyle === "zero") ? 0 : 1;
list.items.forEach(function forItem(item) {
const match = shared.orderedListItemMarkerRe.exec(item.line);
shared.addErrorDetailIf(onError, item.lineNumber,
list.items.forEach((item) => {
const match = orderedListItemMarkerRe.exec(item.line);
addErrorDetailIf(onError, item.lineNumber,
String(number), !match || match[1],
"Style: " + listStyleExamples[listStyle], null,
shared.rangeFromRegExp(item.line, shared.listItemMarkerRe));
rangeFromRegExp(item.line, listItemMarkerRe));
if (listStyle === "ordered") {
number++;
}

View file

@ -2,7 +2,9 @@
"use strict";
const shared = require("./shared");
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } =
require("./shared");
const { flattenedLists } = require("./cache");
module.exports = {
"names": [ "MD030", "list-marker-space" ],
@ -13,17 +15,17 @@ module.exports = {
const olSingle = params.config.ol_single || 1;
const ulMulti = params.config.ul_multi || 1;
const olMulti = params.config.ol_multi || 1;
shared.flattenLists().forEach(function forList(list) {
flattenedLists().forEach((list) => {
const lineCount = list.lastLineIndex - list.open.map[0];
const allSingle = lineCount === list.items.length;
const expectedSpaces = list.unordered ?
(allSingle ? ulSingle : ulMulti) :
(allSingle ? olSingle : olMulti);
list.items.forEach(function forItem(item) {
list.items.forEach((item) => {
const match = /^[\s>]*\S+(\s+)/.exec(item.line);
shared.addErrorDetailIf(onError, item.lineNumber,
addErrorDetailIf(onError, item.lineNumber,
expectedSpaces, (match ? match[1].length : 0), null, null,
shared.rangeFromRegExp(item.line, shared.listItemMarkerRe));
rangeFromRegExp(item.line, listItemMarkerRe));
});
});
}

View file

@ -2,8 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, forEachLine, isBlankLine } = shared;
const { addErrorContext, forEachLine, isBlankLine } = require("./shared");
const { lineMetadata } = require("./cache");
module.exports = {
"names": [ "MD031", "blanks-around-fences" ],
@ -11,7 +11,7 @@ module.exports = {
"tags": [ "code", "blank_lines" ],
"function": function MD031(params, onError) {
const { lines } = params;
forEachLine(function forLine(line, i, inCode, onFence) {
forEachLine(lineMetadata(), (line, i, inCode, onFence) => {
if (((onFence > 0) && !isBlankLine(lines[i - 1])) ||
((onFence < 0) && !isBlankLine(lines[i + 1]))) {
addErrorContext(onError, i + 1, lines[i].trim());

View file

@ -2,8 +2,8 @@
"use strict";
const shared = require("./shared");
const { addErrorContext, flattenLists, isBlankLine } = shared;
const { addErrorContext, isBlankLine } = require("./shared");
const { flattenedLists } = require("./cache");
module.exports = {
"names": [ "MD032", "blanks-around-lists" ],
@ -11,7 +11,7 @@ module.exports = {
"tags": [ "bullet", "ul", "ol", "blank_lines" ],
"function": function MD032(params, onError) {
const { lines } = params;
flattenLists().filter((list) => !list.nesting).forEach((list) => {
flattenedLists().filter((list) => !list.nesting).forEach((list) => {
const firstIndex = list.open.map[0];
if (!isBlankLine(lines[firstIndex - 1])) {
addErrorContext(onError, firstIndex + 1, lines[firstIndex].trim());

View file

@ -154,15 +154,8 @@ function filterTokens(params, type, handler) {
}
module.exports.filterTokens = filterTokens;
let tokenCache = null;
// Caches line metadata and flattened lists for reuse
function makeTokenCache(params) {
if (!params) {
tokenCache = null;
return;
}
// Initialize line metadata array
// Get line metadata array
module.exports.getLineMetadata = function getLineMetadata(params) {
const lineMetadata = params.lines.map(function mapLine(line, index) {
return [ line, index, false, 0, false ];
});
@ -183,8 +176,19 @@ function makeTokenCache(params) {
lineMetadata[i][4] = true;
}
});
return lineMetadata;
};
// Flatten lists
// Calls the provided function for each line (with context)
module.exports.forEachLine = function forEachLine(lineMetadata, handler) {
lineMetadata.forEach(function forMetadata(metadata) {
// Parameters: line, lineIndex, inCode, onFence, inTable
handler(...metadata);
});
};
// Returns (nested) lists as a flat array (in order)
module.exports.flattenLists = function flattenLists(params) {
const flattenedLists = [];
const stack = [];
let current = null;
@ -221,22 +225,7 @@ function makeTokenCache(params) {
lastWithMap = token;
}
});
// Cache results
tokenCache = {
"params": params,
"lineMetadata": lineMetadata,
"flattenedLists": flattenedLists
};
}
module.exports.makeTokenCache = makeTokenCache;
// Calls the provided function for each line (with context)
module.exports.forEachLine = function forEachLine(callback) {
tokenCache.lineMetadata.forEach(function forMetadata(metadata) {
// Parameters: line, lineIndex, inCode, onFence, inTable
callback(...metadata);
});
return flattenedLists;
};
// Calls the provided function for each specified inline child token
@ -328,11 +317,6 @@ module.exports.forEachInlineCodeSpan =
}
};
// Returns (nested) lists as a flat array (in order)
module.exports.flattenLists = function flattenLists() {
return tokenCache.flattenedLists;
};
// Adds a generic error object via the onError callback
function addError(onError, lineNumber, detail, context, range) {
onError({