Stop running the markdown-it parser unless a custom rule that requires it is present (~7% runtime reduction).

This commit is contained in:
David Anson 2024-08-21 21:19:40 -07:00
parent 697780b3b6
commit ea8d596a9b
5 changed files with 102 additions and 36 deletions

View file

@ -563,7 +563,7 @@ is allowed to continue and report any violations that were found.
Type: `Array` of `Array` of `Function` and plugin parameters
Specifies additional [markdown-it plugins][markdown-it-plugin] to use when
Specifies additional [`markdown-it` plugins][markdown-it-plugin] to use when
parsing input. Plugins can be used to support additional syntax and features for
advanced scenarios.
@ -575,6 +575,13 @@ Each item in the top-level `Array` should be of the form:
[ require("markdown-it-plugin"), plugin_param_0, plugin_param_1, ... ]
```
> Note that `markdown-it` plugins are only called when the `markdown-it` parser
> is invoked. None of the built-in rules use the `markdown-it` parser, so
> `markdown-it` plugins will only be invoked when one or more
> [custom rules][custom-rules] that use the `markdown-it` parser are present.
[custom-rules]: #custom-rules
##### options.noInlineConfig
Type: `Boolean`

View file

@ -1557,7 +1557,6 @@ module.exports.version = "0.34.0";
const path = __webpack_require__(/*! node:path */ "?9a52");
const { promisify } = __webpack_require__(/*! node:util */ "?39e5");
const markdownit = __webpack_require__(/*! markdown-it */ "markdown-it");
const micromark = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs");
// const { deprecatedRuleNames } = require("./constants");
const rules = __webpack_require__(/*! ./rules */ "../lib/rules.js");
@ -1779,13 +1778,17 @@ function freezeToken(token) {
/**
* Annotate tokens with line/lineNumber and freeze them.
*
* @param {MarkdownItToken[]} tokens Array of markdown-it tokens.
* @param {import("markdown-it").Token[]} tokens Array of markdown-it tokens.
* @param {string[]} lines Lines of Markdown content.
* @returns {void}
*/
function annotateAndFreezeTokens(tokens, lines) {
let trMap = null;
for (const token of tokens) {
// eslint-disable-next-line jsdoc/valid-types
/** @type MarkdownItToken[] */
// @ts-ignore
const markdownItTokens = tokens;
for (const token of markdownItTokens) {
// Provide missing maps for table content
if (token.type === "tr_open") {
trMap = token.map;
@ -2075,7 +2078,7 @@ function getEnabledRulesPerLineNumber(
* names.
* @param {string} name Identifier for the content.
* @param {string} content Markdown content.
* @param {Object} md Instance of markdown-it.
* @param {GetMarkdownIt} getMarkdownIt Getter for instance of markdown-it.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[] | null} configParsers Configuration parsers.
* @param {RegExp | null} frontMatter Regular expression for front matter.
@ -2090,7 +2093,7 @@ function lintContent(
aliasToRuleNames,
name,
content,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -2115,8 +2118,11 @@ function lintContent(
configParsers,
aliasToRuleNames
);
const needMarkdownItTokens = ruleList.some(
(rule) => (rule.parser === "markdownit") || (rule.parser === undefined)
);
// Parse content into parser tokens
const markdownitTokens = md.parse(content, {});
const markdownitTokens = needMarkdownItTokens ? getMarkdownIt().parse(content, {}) : [];
const micromarkTokens = micromark.parse(content);
// Hide the content of HTML comments from rules
content = helpers.clearHtmlCommentText(content);
@ -2376,7 +2382,7 @@ function lintContent(
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
* names.
* @param {string} file Path of file to lint.
* @param {Object} md Instance of markdown-it.
* @param {GetMarkdownIt} getMarkdownIt Getter for instance of markdown-it.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[] | null} configParsers Configuration parsers.
* @param {RegExp | null} frontMatter Regular expression for front matter.
@ -2392,7 +2398,7 @@ function lintFile(
ruleList,
aliasToRuleNames,
file,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -2412,7 +2418,7 @@ function lintFile(
aliasToRuleNames,
file,
content,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -2477,12 +2483,16 @@ function lintInput(options, synchronous, callback) {
const noInlineConfig = !!options.noInlineConfig;
const resultVersion = (options.resultVersion === undefined) ?
3 : options.resultVersion;
const md = markdownit({ "html": true });
const markdownItPlugins = options.markdownItPlugins || [];
for (const plugin of markdownItPlugins) {
// @ts-ignore
md.use(...plugin);
}
const getMarkdownIt = () => {
const markdownit = __webpack_require__(/*! markdown-it */ "markdown-it");
const md = markdownit({ "html": true });
const markdownItPlugins = options.markdownItPlugins || [];
for (const plugin of markdownItPlugins) {
// @ts-ignore
md.use(...plugin);
}
return md;
};
const fs = options.fs || __webpack_require__(/*! node:fs */ "?d0ee");
const aliasToRuleNames = mapAliasToRuleNames(ruleList);
const results = newResults(ruleList);
@ -2514,7 +2524,7 @@ function lintInput(options, synchronous, callback) {
ruleList,
aliasToRuleNames,
currentItem,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -2533,7 +2543,7 @@ function lintInput(options, synchronous, callback) {
aliasToRuleNames,
currentItem,
strings[currentItem] || "",
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -2848,6 +2858,13 @@ module.exports = markdownlint;
// Type declarations
/**
* Function to get an instance of the markdown-it parser.
*
* @callback GetMarkdownIt
* @returns {import("markdown-it")}
*/
/**
* Function to implement rule logic.
*

View file

@ -8,7 +8,7 @@ export = markdownlint;
*/
declare function markdownlint(options: Options | null, callback: LintCallback): void;
declare namespace markdownlint {
export { markdownlintSync as sync, readConfig, readConfigSync, getVersion, promises, RuleFunction, RuleParams, MarkdownParsers, ParserMarkdownIt, ParserMicromark, MarkdownItToken, MicromarkTokenType, MicromarkToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintContentCallback, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback, ResolveConfigExtendsCallback };
export { markdownlintSync as sync, readConfig, readConfigSync, getVersion, promises, GetMarkdownIt, RuleFunction, RuleParams, MarkdownParsers, ParserMarkdownIt, ParserMicromark, MarkdownItToken, MicromarkTokenType, MicromarkToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintContentCallback, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback, ResolveConfigExtendsCallback };
}
/**
* Lint specified Markdown files synchronously.
@ -49,6 +49,10 @@ declare namespace promises {
export { extendConfigPromise as extendConfig };
export { readConfigPromise as readConfig };
}
/**
* Function to get an instance of the markdown-it parser.
*/
type GetMarkdownIt = () => any;
/**
* Function to implement rule logic.
*/

View file

@ -4,7 +4,6 @@
const path = require("node:path");
const { promisify } = require("node:util");
const markdownit = require("markdown-it");
const micromark = require("../helpers/micromark.cjs");
// const { deprecatedRuleNames } = require("./constants");
const rules = require("./rules");
@ -226,13 +225,17 @@ function freezeToken(token) {
/**
* Annotate tokens with line/lineNumber and freeze them.
*
* @param {MarkdownItToken[]} tokens Array of markdown-it tokens.
* @param {import("markdown-it").Token[]} tokens Array of markdown-it tokens.
* @param {string[]} lines Lines of Markdown content.
* @returns {void}
*/
function annotateAndFreezeTokens(tokens, lines) {
let trMap = null;
for (const token of tokens) {
// eslint-disable-next-line jsdoc/valid-types
/** @type MarkdownItToken[] */
// @ts-ignore
const markdownItTokens = tokens;
for (const token of markdownItTokens) {
// Provide missing maps for table content
if (token.type === "tr_open") {
trMap = token.map;
@ -522,7 +525,7 @@ function getEnabledRulesPerLineNumber(
* names.
* @param {string} name Identifier for the content.
* @param {string} content Markdown content.
* @param {Object} md Instance of markdown-it.
* @param {GetMarkdownIt} getMarkdownIt Getter for instance of markdown-it.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[] | null} configParsers Configuration parsers.
* @param {RegExp | null} frontMatter Regular expression for front matter.
@ -537,7 +540,7 @@ function lintContent(
aliasToRuleNames,
name,
content,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -562,8 +565,11 @@ function lintContent(
configParsers,
aliasToRuleNames
);
const needMarkdownItTokens = ruleList.some(
(rule) => (rule.parser === "markdownit") || (rule.parser === undefined)
);
// Parse content into parser tokens
const markdownitTokens = md.parse(content, {});
const markdownitTokens = needMarkdownItTokens ? getMarkdownIt().parse(content, {}) : [];
const micromarkTokens = micromark.parse(content);
// Hide the content of HTML comments from rules
content = helpers.clearHtmlCommentText(content);
@ -823,7 +829,7 @@ function lintContent(
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
* names.
* @param {string} file Path of file to lint.
* @param {Object} md Instance of markdown-it.
* @param {GetMarkdownIt} getMarkdownIt Getter for instance of markdown-it.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[] | null} configParsers Configuration parsers.
* @param {RegExp | null} frontMatter Regular expression for front matter.
@ -839,7 +845,7 @@ function lintFile(
ruleList,
aliasToRuleNames,
file,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -859,7 +865,7 @@ function lintFile(
aliasToRuleNames,
file,
content,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -924,12 +930,16 @@ function lintInput(options, synchronous, callback) {
const noInlineConfig = !!options.noInlineConfig;
const resultVersion = (options.resultVersion === undefined) ?
3 : options.resultVersion;
const md = markdownit({ "html": true });
const markdownItPlugins = options.markdownItPlugins || [];
for (const plugin of markdownItPlugins) {
// @ts-ignore
md.use(...plugin);
}
const getMarkdownIt = () => {
const markdownit = require("markdown-it");
const md = markdownit({ "html": true });
const markdownItPlugins = options.markdownItPlugins || [];
for (const plugin of markdownItPlugins) {
// @ts-ignore
md.use(...plugin);
}
return md;
};
const fs = options.fs || require("node:fs");
const aliasToRuleNames = mapAliasToRuleNames(ruleList);
const results = newResults(ruleList);
@ -961,7 +971,7 @@ function lintInput(options, synchronous, callback) {
ruleList,
aliasToRuleNames,
currentItem,
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -980,7 +990,7 @@ function lintInput(options, synchronous, callback) {
aliasToRuleNames,
currentItem,
strings[currentItem] || "",
md,
getMarkdownIt,
config,
configParsers,
frontMatter,
@ -1295,6 +1305,13 @@ module.exports = markdownlint;
// Type declarations
/**
* Function to get an instance of the markdown-it parser.
*
* @callback GetMarkdownIt
* @returns {import("markdown-it")}
*/
/**
* Function to implement rule logic.
*

View file

@ -1109,6 +1109,8 @@ test("markdownItPluginsSingle", (t) => new Promise((resolve) => {
"strings": {
"string": "# Heading\n\nText\n"
},
// Use a markdown-it custom rule so the markdown-it plugin will be run
"customRules": customRules.anyBlockquote,
"markdownItPlugins": [
[ pluginInline, "check_text_plugin", "text", () => t.true(true) ]
]
@ -1126,6 +1128,8 @@ test("markdownItPluginsMultiple", (t) => new Promise((resolve) => {
"strings": {
"string": "# Heading\n\nText H~2~0 text 29^th^ text\n"
},
// Use a markdown-it custom rule so the markdown-it plugin will be run
"customRules": customRules.anyBlockquote,
"markdownItPlugins": [
[ pluginSub ],
[ pluginSup ],
@ -1140,6 +1144,23 @@ test("markdownItPluginsMultiple", (t) => new Promise((resolve) => {
});
}));
test("markdownItPluginsNoMarkdownIt", (t) => new Promise((resolve) => {
t.plan(2);
markdownlint({
"strings": {
"string": "# Heading\n\nText\n"
},
"markdownItPlugins": [
[ pluginInline, "check_text_plugin", "text", () => t.fail() ]
]
}, function callback(err, actual) {
t.falsy(err);
const expected = { "string": [] };
t.deepEqual(actual, expected, "Unexpected issues.");
resolve();
});
}));
test("Pandoc footnote", (t) => new Promise((resolve) => {
t.plan(2);
markdownlint({