mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-16 05:50:13 +01:00
Allow options.markdownItFactory to be implemented asynchronously so the markdown-it parser import can be deferred.
This commit is contained in:
parent
d4b981bcb3
commit
44c302fe0b
6 changed files with 448 additions and 249 deletions
12
README.md
12
README.md
|
|
@ -580,14 +580,22 @@ declaring the dependency and returning an instance from this factory. If any
|
|||
[`markdown-it` plugins][markdown-it-plugin] are needed, they should be `use`d by
|
||||
the caller before returning the `markdown-it` instance.
|
||||
|
||||
For compatibility with previous versions of `markdownlint`, this function can be
|
||||
implemented like:
|
||||
For compatibility with previous versions of `markdownlint`, this function should
|
||||
be similar to:
|
||||
|
||||
```javascript
|
||||
import markdownIt from "markdown-it";
|
||||
const markdownItFactory = () => markdownIt({ "html": true });
|
||||
```
|
||||
|
||||
When an asynchronous implementation of `lint` is being invoked (e.g., via
|
||||
`markdownlint/async` or `markdownlint/promise`), this function can return a
|
||||
`Promise` in order to defer the import of `markdown-it`:
|
||||
|
||||
```javascript
|
||||
const markdownItFactory = () => import("markdown-it").then((module) => module.default({ "html": true }));
|
||||
```
|
||||
|
||||
> Note that this function is only invoked when a `markdown-it` parser is
|
||||
> needed. None of the built-in rules use the `markdown-it` parser, so it is only
|
||||
> invoked when one or more [custom rules][custom-rules] are present that use the
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ options = {
|
|||
"frontMatter": /---/,
|
||||
"handleRuleFailures": false,
|
||||
"noInlineConfig": false,
|
||||
"markdownItFactory": () => new markdownIt()
|
||||
"markdownItFactory": () => markdownIt()
|
||||
};
|
||||
|
||||
assertLintResults(lintSync(options));
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
const { newLineRe } = require("../helpers");
|
||||
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
||||
/** @typedef {import("markdownlint").MarkdownItFactory} MarkdownItFactory */
|
||||
/** @typedef {import("markdownlint").MarkdownIt} MarkdownIt */
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
||||
/** @typedef {import("markdownlint").MarkdownItToken} MarkdownItToken */
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
||||
|
|
@ -154,13 +154,12 @@ function annotateAndFreezeTokens(tokens, lines) {
|
|||
/**
|
||||
* Gets an array of markdown-it tokens for the input.
|
||||
*
|
||||
* @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
|
||||
* @param {MarkdownIt} markdownIt Instance of the markdown-it parser.
|
||||
* @param {string} content Markdown content.
|
||||
* @param {string[]} lines Lines of Markdown content.
|
||||
* @returns {MarkdownItToken[]} Array of markdown-it tokens.
|
||||
*/
|
||||
function getMarkdownItTokens(markdownItFactory, content, lines) {
|
||||
const markdownIt = markdownItFactory();
|
||||
function getMarkdownItTokens(markdownIt, content, lines) {
|
||||
const tokens = markdownIt.parse(content, {});
|
||||
annotateAndFreezeTokens(tokens, lines);
|
||||
return tokens;
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ export type MarkdownIt = {
|
|||
/**
|
||||
* Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
|
||||
*/
|
||||
export type MarkdownItFactory = () => MarkdownIt;
|
||||
export type MarkdownItFactory = () => MarkdownIt | Promise<MarkdownIt>;
|
||||
/**
|
||||
* Configuration options.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -443,8 +443,7 @@ function getEnabledRulesPerLineNumber(
|
|||
* Lints a string containing Markdown content.
|
||||
*
|
||||
* @param {Rule[]} ruleList List of rules.
|
||||
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
|
||||
* names.
|
||||
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
|
||||
* @param {string} name Identifier for the content.
|
||||
* @param {string} content Markdown content.
|
||||
* @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
|
||||
|
|
@ -454,6 +453,7 @@ function getEnabledRulesPerLineNumber(
|
|||
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
|
||||
* @param {boolean} noInlineConfig Whether to allow inline configuration.
|
||||
* @param {number} resultVersion Version of the LintResults object to return.
|
||||
* @param {boolean} synchronous Whether to execute synchronously.
|
||||
* @param {LintContentCallback} callback Callback (err, result) function.
|
||||
* @returns {void}
|
||||
*/
|
||||
|
|
@ -469,7 +469,10 @@ function lintContent(
|
|||
handleRuleFailures,
|
||||
noInlineConfig,
|
||||
resultVersion,
|
||||
synchronous,
|
||||
callback) {
|
||||
// Provide a consistent error-reporting callback
|
||||
const callbackError = (error) => callback(error instanceof Error ? error : new Error(error));
|
||||
// Remove UTF-8 byte order marker (if present)
|
||||
content = content.replace(/^\uFEFF/, "");
|
||||
// Remove front matter
|
||||
|
|
@ -501,9 +504,8 @@ function lintContent(
|
|||
content = helpers.clearHtmlCommentText(content);
|
||||
// Parse content into lines and get markdown-it tokens
|
||||
const lines = content.split(helpers.newLineRe);
|
||||
const markdownitTokens = needMarkdownItTokens ?
|
||||
requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory, preClearedContent, lines) :
|
||||
[];
|
||||
// Function to run after fetching markdown-it tokens (when needed)
|
||||
const lintContentInternal = (markdownitTokens) => {
|
||||
// Create (frozen) parameters for rules
|
||||
/** @type {MarkdownParsers} */
|
||||
// @ts-ignore
|
||||
|
|
@ -678,8 +680,7 @@ function lintContent(
|
|||
}
|
||||
return null;
|
||||
};
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function formatResults() {
|
||||
const formatResults = () => {
|
||||
// Sort results by rule name by line number
|
||||
results.sort((a, b) => (
|
||||
a.ruleName.localeCompare(b.ruleName) ||
|
||||
|
|
@ -723,7 +724,7 @@ function lintContent(
|
|||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
};
|
||||
// Run all rules
|
||||
const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
|
||||
const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
|
||||
|
|
@ -732,8 +733,6 @@ function lintContent(
|
|||
...ruleListSync
|
||||
];
|
||||
const callbackSuccess = () => callback(null, formatResults());
|
||||
const callbackError =
|
||||
(error) => callback(error instanceof Error ? error : new Error(error));
|
||||
try {
|
||||
const ruleResults = ruleListAsyncFirst.map(forRule);
|
||||
if (ruleListAsync.length > 0) {
|
||||
|
|
@ -748,6 +747,25 @@ function lintContent(
|
|||
} finally {
|
||||
cacheInitialize();
|
||||
}
|
||||
};
|
||||
if (!needMarkdownItTokens || synchronous) {
|
||||
// Need/able to call into markdown-it and lintContentInternal synchronously
|
||||
const markdownItTokens = needMarkdownItTokens ?
|
||||
requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), preClearedContent, lines) :
|
||||
[];
|
||||
lintContentInternal(markdownItTokens);
|
||||
} else {
|
||||
// Need to call into markdown-it and lintContentInternal asynchronously
|
||||
Promise.all([
|
||||
// eslint-disable-next-line no-inline-comments
|
||||
import(/* webpackMode: "eager" */ "./markdownit.cjs"),
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
new Promise((resolve) => resolve(markdownItFactory()))
|
||||
]).then(([ markdownitCjs, markdownIt ]) => {
|
||||
const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, preClearedContent, lines);
|
||||
lintContentInternal(markdownItTokens);
|
||||
}).catch(callbackError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -799,6 +817,7 @@ function lintFile(
|
|||
handleRuleFailures,
|
||||
noInlineConfig,
|
||||
resultVersion,
|
||||
synchronous,
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
|
@ -919,6 +938,7 @@ function lintInput(options, synchronous, callback) {
|
|||
handleRuleFailures,
|
||||
noInlineConfig,
|
||||
resultVersion,
|
||||
synchronous,
|
||||
lintWorkerCallback
|
||||
);
|
||||
} else if (concurrency === 0) {
|
||||
|
|
@ -1494,7 +1514,7 @@ export function getVersion() {
|
|||
* Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
|
||||
*
|
||||
* @callback MarkdownItFactory
|
||||
* @returns {MarkdownIt} Instance of the markdown-it parser.
|
||||
* @returns {MarkdownIt|Promise<MarkdownIt>} Instance of the markdown-it parser.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -676,6 +676,178 @@ test("customRulesMarkdownItFactoryUndefined", (t) => {
|
|||
);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItFactoryNotNeededSync", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "none",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => t.fail(),
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
}
|
||||
};
|
||||
t.pass();
|
||||
return lintSync(options);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItFactoryNeededSync", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "markdownit",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => t.pass() && markdownIt(),
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
}
|
||||
};
|
||||
return lintSync(options);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItFactoryNotNeededAsync", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "none",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => t.fail(),
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
}
|
||||
};
|
||||
t.pass();
|
||||
return lintPromise(options);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItFactoryNeededAsyncRunsSync", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "markdownit",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => t.pass() && markdownIt(),
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
}
|
||||
};
|
||||
return lintPromise(options);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItFactoryNeededAsyncRunsAsync", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "markdownit",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => t.pass() && Promise.resolve(markdownIt()),
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
}
|
||||
};
|
||||
return lintPromise(options);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItFactoryNeededAsyncRunsAsyncWithImport", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "markdownit",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => import("markdown-it").then((module) => t.pass() && module.default()),
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
}
|
||||
};
|
||||
return lintPromise(options);
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItInstanceCanBeReusedSync", (t) => {
|
||||
t.plan(1);
|
||||
const markdownItInstance = markdownItFactory();
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "markdownit",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => markdownItInstance,
|
||||
"strings": {
|
||||
"string": "# Heading"
|
||||
}
|
||||
};
|
||||
t.deepEqual(lintSync(options), lintSync(options));
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItInstanceCanBeReusedAsync", async(t) => {
|
||||
t.plan(1);
|
||||
const markdownItInstance = markdownItFactory();
|
||||
/** @type {import("markdownlint").Options} */
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"parser": "markdownit",
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"markdownItFactory": () => Promise.resolve(markdownItInstance),
|
||||
"strings": {
|
||||
"string": "# Heading"
|
||||
}
|
||||
};
|
||||
t.deepEqual(await lintPromise(options), await lintPromise(options));
|
||||
});
|
||||
|
||||
test("customRulesMarkdownItParamsTokensSameObject", (t) => {
|
||||
t.plan(1);
|
||||
/** @type {import("markdownlint").Options} */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue