Skip to content

Commit dfcb452

Browse files
committed
Cache all top-level calls to filterByTypes (~7% runtime reduction).
1 parent 85e704f commit dfcb452

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+427
-481
lines changed

demo/markdownlint-browser.js

Lines changed: 213 additions & 240 deletions
Large diffs are not rendered by default.

lib/cache.js

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,69 @@
22

33
"use strict";
44

5+
const helpers = require("../helpers");
6+
const { filterByTypes } = require("../helpers/micromark.cjs");
7+
8+
/** @type {Map<string, object>} */
59
const map = new Map();
10+
// eslint-disable-next-line no-undef-init
11+
let params = undefined;
12+
13+
/**
14+
* Initializes (resets) the cache.
15+
*
16+
* @param {import("./markdownlint").RuleParams} [p] Rule parameters object.
17+
* @returns {void}
18+
*/
19+
function initialize(p) {
20+
map.clear();
21+
params = p;
22+
}
623

7-
module.exports.set = (keyValuePairs) => {
8-
for (const [ key, value ] of Object.entries(keyValuePairs)) {
9-
map.set(key, value);
24+
/**
25+
* Gets a cached object value - computes it and caches it.
26+
*
27+
* @param {string} name Cache object name.
28+
* @param {Function} getValue Getter for object value.
29+
* @returns {Object} Object value.
30+
*/
31+
function getCached(name, getValue) {
32+
if (map.has(name)) {
33+
return map.get(name);
1034
}
11-
};
12-
module.exports.clear = () => map.clear();
35+
const value = getValue();
36+
map.set(name, value);
37+
return value;
38+
}
1339

14-
module.exports.referenceLinkImageData =
15-
() => map.get("referenceLinkImageData");
40+
/**
41+
* Filters a list of Micromark tokens by type and caches the result.
42+
*
43+
* @param {import("./markdownlint").MicromarkTokenType[]} types Types to allow.
44+
* @param {boolean} [htmlFlow] Whether to include htmlFlow content.
45+
* @returns {import("./markdownlint").MicromarkToken[]} Filtered tokens.
46+
*/
47+
function filterByTypesCached(types, htmlFlow) {
48+
return getCached(
49+
types.join("|"),
50+
() => filterByTypes(params.parsers.micromark.tokens, types, htmlFlow)
51+
);
52+
}
53+
54+
/**
55+
* Gets a reference link and image data object.
56+
*
57+
* @returns {Object} Reference link and image data object.
58+
*/
59+
function getReferenceLinkImageData() {
60+
return getCached(
61+
getReferenceLinkImageData.name,
62+
() => helpers.getReferenceLinkImageData(params.parsers.micromark.tokens)
63+
);
64+
}
65+
66+
module.exports = {
67+
initialize,
68+
filterByTypesCached,
69+
getReferenceLinkImageData
70+
};

lib/markdownlint.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -604,14 +604,13 @@ function lintContent(
604604
const parsersNone = Object.freeze({});
605605
const paramsBase = {
606606
name,
607-
"parsers": parsersMarkdownIt,
608607
"lines": Object.freeze(lines),
609608
"frontMatterLines": Object.freeze(frontMatterLines)
610609
};
611-
const referenceLinkImageData =
612-
helpers.getReferenceLinkImageData(micromarkTokens);
613-
cache.set({
614-
referenceLinkImageData
610+
cache.initialize({
611+
...paramsBase,
612+
"parsers": parsersMicromark,
613+
"config": null
615614
});
616615
// Function to run for each rule
617616
let results = [];
@@ -826,7 +825,7 @@ function lintContent(
826825
} catch (error) {
827826
callbackError(error);
828827
} finally {
829-
cache.clear();
828+
cache.initialize();
830829
}
831830
}
832831

lib/md001.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"use strict";
44

55
const { addErrorDetailIf } = require("../helpers");
6-
const { filterByTypes, getHeadingLevel } = require("../helpers/micromark.cjs");
6+
const { getHeadingLevel } = require("../helpers/micromark.cjs");
7+
const { filterByTypesCached } = require("./cache");
78

89
// eslint-disable-next-line jsdoc/valid-types
910
/** @type import("./markdownlint").Rule */
@@ -14,11 +15,7 @@ module.exports = {
1415
"parser": "micromark",
1516
"function": function MD001(params, onError) {
1617
let prevLevel = Number.MAX_SAFE_INTEGER;
17-
const headings = filterByTypes(
18-
params.parsers.micromark.tokens,
19-
[ "atxHeading", "setextHeading" ]
20-
);
21-
for (const heading of headings) {
18+
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
2219
const level = getHeadingLevel(heading);
2320
if (level > prevLevel) {
2421
addErrorDetailIf(

lib/md003.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"use strict";
44

55
const { addErrorDetailIf } = require("../helpers");
6-
const { filterByTypes, getHeadingLevel, getHeadingStyle } =
7-
require("../helpers/micromark.cjs");
6+
const { getHeadingLevel, getHeadingStyle } = require("../helpers/micromark.cjs");
7+
const { filterByTypesCached } = require("./cache");
88

99
// eslint-disable-next-line jsdoc/valid-types
1010
/** @type import("./markdownlint").Rule */
@@ -15,11 +15,7 @@ module.exports = {
1515
"parser": "micromark",
1616
"function": function MD003(params, onError) {
1717
let style = String(params.config.style || "consistent");
18-
const headings = filterByTypes(
19-
params.parsers.micromark.tokens,
20-
[ "atxHeading", "setextHeading" ]
21-
);
22-
for (const heading of headings) {
18+
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
2319
const styleForToken = getHeadingStyle(heading);
2420
if (style === "consistent") {
2521
style = styleForToken;

lib/md004.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"use strict";
44

55
const { addErrorDetailIf } = require("../helpers");
6-
const { filterByTypes, getDescendantsByType, getTokenParentOfType } = require("../helpers/micromark.cjs");
6+
const { getDescendantsByType, getTokenParentOfType } = require("../helpers/micromark.cjs");
7+
const { filterByTypesCached } = require("./cache");
78

89
const markerToStyle = {
910
"-": "dash",
@@ -39,11 +40,12 @@ module.exports = {
3940
const style = String(params.config.style || "consistent");
4041
let expectedStyle = validStyles.has(style) ? style : "dash";
4142
const nestingStyles = [];
42-
for (const listUnordered of filterByTypes(params.parsers.micromark.tokens, [ "listUnordered" ])) {
43+
for (const listUnordered of filterByTypesCached([ "listUnordered" ])) {
4344
let nesting = 0;
4445
if (style === "sublist") {
4546
/** @type {import("../helpers/micromark.cjs").Token | null} */
4647
let parent = listUnordered;
48+
// @ts-ignore
4749
while ((parent = getTokenParentOfType(parent, [ "listOrdered", "listUnordered" ]))) {
4850
nesting++;
4951
}

lib/md005.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"use strict";
44

55
const { addError, addErrorDetailIf } = require("../helpers");
6-
const { filterByTypes } = require("../helpers/micromark.cjs");
6+
const { filterByTypesCached } = require("./cache");
77

88
// eslint-disable-next-line jsdoc/valid-types
99
/** @type import("./markdownlint").Rule */
@@ -13,11 +13,7 @@ module.exports = {
1313
"tags": [ "bullet", "ul", "indentation" ],
1414
"parser": "micromark",
1515
"function": function MD005(params, onError) {
16-
const lists = filterByTypes(
17-
params.parsers.micromark.tokens,
18-
[ "listOrdered", "listUnordered" ]
19-
);
20-
for (const list of lists) {
16+
for (const list of filterByTypesCached([ "listOrdered", "listUnordered" ])) {
2117
const expectedIndent = list.startColumn - 1;
2218
let expectedEnd = 0;
2319
let endMatching = false;

lib/md007.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"use strict";
44

55
const { addErrorDetailIf } = require("../helpers");
6-
const { filterByTypes, getTokenParentOfType } = require("../helpers/micromark.cjs");
6+
const { getTokenParentOfType } = require("../helpers/micromark.cjs");
7+
const { filterByTypesCached } = require("./cache");
78

89
// eslint-disable-next-line jsdoc/valid-types
910
/** @type import("markdownlint-micromark").TokenType[] */
@@ -27,10 +28,7 @@ module.exports = {
2728
const startIndent = Number(params.config.start_indent || indent);
2829
const unorderedListNesting = new Map();
2930
let lastBlockQuotePrefix = null;
30-
const tokens = filterByTypes(
31-
params.parsers.micromark.tokens,
32-
unorderedListTypes
33-
);
31+
const tokens = filterByTypesCached(unorderedListTypes);
3432
for (const token of tokens) {
3533
const { endColumn, parent, startColumn, startLine, type } = token;
3634
if (type === "blockQuotePrefix") {
@@ -40,6 +38,7 @@ module.exports = {
4038
/** @type {import("../helpers/micromark.cjs").Token | null} */
4139
let current = token;
4240
while (
41+
// @ts-ignore
4342
(current = getTokenParentOfType(current, unorderedParentTypes))
4443
) {
4544
if (current.type === "listUnordered") {

lib/md009.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"use strict";
44

55
const { addError } = require("../helpers");
6-
const { addRangeToSet, filterByTypes } = require("../helpers/micromark.cjs");
6+
const { addRangeToSet } = require("../helpers/micromark.cjs");
7+
const { filterByTypesCached } = require("./cache");
78

89
// eslint-disable-next-line jsdoc/valid-types
910
/** @type import("./markdownlint").Rule */
@@ -17,17 +18,16 @@ module.exports = {
1718
brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces);
1819
const listItemEmptyLines = !!params.config.list_item_empty_lines;
1920
const strict = !!params.config.strict;
20-
const { tokens } = params.parsers.micromark;
2121
const codeBlockLineNumbers = new Set();
22-
for (const codeBlock of filterByTypes(tokens, [ "codeFenced" ])) {
22+
for (const codeBlock of filterByTypesCached([ "codeFenced" ])) {
2323
addRangeToSet(codeBlockLineNumbers, codeBlock.startLine + 1, codeBlock.endLine - 1);
2424
}
25-
for (const codeBlock of filterByTypes(tokens, [ "codeIndented" ])) {
25+
for (const codeBlock of filterByTypesCached([ "codeIndented" ])) {
2626
addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
2727
}
2828
const listItemLineNumbers = new Set();
2929
if (listItemEmptyLines) {
30-
for (const listBlock of filterByTypes(tokens, [ "listOrdered", "listUnordered" ])) {
30+
for (const listBlock of filterByTypesCached([ "listOrdered", "listUnordered" ])) {
3131
addRangeToSet(listItemLineNumbers, listBlock.startLine, listBlock.endLine);
3232
let trailingIndent = true;
3333
for (let i = listBlock.children.length - 1; i >= 0; i--) {
@@ -53,10 +53,10 @@ module.exports = {
5353
const paragraphLineNumbers = new Set();
5454
const codeInlineLineNumbers = new Set();
5555
if (strict) {
56-
for (const paragraph of filterByTypes(tokens, [ "paragraph" ])) {
56+
for (const paragraph of filterByTypesCached([ "paragraph" ])) {
5757
addRangeToSet(paragraphLineNumbers, paragraph.startLine, paragraph.endLine - 1);
5858
}
59-
for (const codeText of filterByTypes(tokens, [ "codeText" ])) {
59+
for (const codeText of filterByTypesCached([ "codeText" ])) {
6060
addRangeToSet(codeInlineLineNumbers, codeText.startLine, codeText.endLine - 1);
6161
}
6262
}

lib/md010.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"use strict";
44

55
const { addError, withinAnyRange } = require("../helpers");
6-
const { filterByTypes, getDescendantsByType, getExclusionsForToken } = require("../helpers/micromark.cjs");
6+
const { getDescendantsByType, getExclusionsForToken } = require("../helpers/micromark.cjs");
7+
const { filterByTypesCached } = require("./cache");
78

89
const tabRe = /\t+/g;
910

@@ -36,10 +37,7 @@ module.exports = {
3637
} else {
3738
exclusionTypes.push("codeFenced", "codeIndented", "codeText");
3839
}
39-
const codeTokens = filterByTypes(
40-
params.parsers.micromark.tokens,
41-
exclusionTypes
42-
).filter((token) => {
40+
const codeTokens = filterByTypesCached(exclusionTypes).filter((token) => {
4341
if ((token.type === "codeFenced") && (ignoreCodeLanguages.size > 0)) {
4442
const fenceInfos = getDescendantsByType(token, [ "codeFencedFence", "codeFencedFenceInfo" ]);
4543
return fenceInfos.every((fenceInfo) => ignoreCodeLanguages.has(fenceInfo.text.toLowerCase()));

0 commit comments

Comments
 (0)