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
|
[`markdown-it` plugins][markdown-it-plugin] are needed, they should be `use`d by
|
||||||
the caller before returning the `markdown-it` instance.
|
the caller before returning the `markdown-it` instance.
|
||||||
|
|
||||||
For compatibility with previous versions of `markdownlint`, this function can be
|
For compatibility with previous versions of `markdownlint`, this function should
|
||||||
implemented like:
|
be similar to:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import markdownIt from "markdown-it";
|
import markdownIt from "markdown-it";
|
||||||
const markdownItFactory = () => markdownIt({ "html": true });
|
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
|
> 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
|
> 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
|
> invoked when one or more [custom rules][custom-rules] are present that use the
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ options = {
|
||||||
"frontMatter": /---/,
|
"frontMatter": /---/,
|
||||||
"handleRuleFailures": false,
|
"handleRuleFailures": false,
|
||||||
"noInlineConfig": false,
|
"noInlineConfig": false,
|
||||||
"markdownItFactory": () => new markdownIt()
|
"markdownItFactory": () => markdownIt()
|
||||||
};
|
};
|
||||||
|
|
||||||
assertLintResults(lintSync(options));
|
assertLintResults(lintSync(options));
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
const { newLineRe } = require("../helpers");
|
const { newLineRe } = require("../helpers");
|
||||||
|
|
||||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
// @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
|
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
||||||
/** @typedef {import("markdownlint").MarkdownItToken} MarkdownItToken */
|
/** @typedef {import("markdownlint").MarkdownItToken} MarkdownItToken */
|
||||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
// @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.
|
* 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} content Markdown content.
|
||||||
* @param {string[]} lines Lines of Markdown content.
|
* @param {string[]} lines Lines of Markdown content.
|
||||||
* @returns {MarkdownItToken[]} Array of markdown-it tokens.
|
* @returns {MarkdownItToken[]} Array of markdown-it tokens.
|
||||||
*/
|
*/
|
||||||
function getMarkdownItTokens(markdownItFactory, content, lines) {
|
function getMarkdownItTokens(markdownIt, content, lines) {
|
||||||
const markdownIt = markdownItFactory();
|
|
||||||
const tokens = markdownIt.parse(content, {});
|
const tokens = markdownIt.parse(content, {});
|
||||||
annotateAndFreezeTokens(tokens, lines);
|
annotateAndFreezeTokens(tokens, lines);
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,7 @@ export type MarkdownIt = {
|
||||||
/**
|
/**
|
||||||
* Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
|
* 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.
|
* Configuration options.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -443,8 +443,7 @@ function getEnabledRulesPerLineNumber(
|
||||||
* Lints a string containing Markdown content.
|
* Lints a string containing Markdown content.
|
||||||
*
|
*
|
||||||
* @param {Rule[]} ruleList List of rules.
|
* @param {Rule[]} ruleList List of rules.
|
||||||
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
|
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
|
||||||
* names.
|
|
||||||
* @param {string} name Identifier for the content.
|
* @param {string} name Identifier for the content.
|
||||||
* @param {string} content Markdown content.
|
* @param {string} content Markdown content.
|
||||||
* @param {MarkdownItFactory} markdownItFactory Function to create a markdown-it parser.
|
* @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} handleRuleFailures Whether to handle exceptions in rules.
|
||||||
* @param {boolean} noInlineConfig Whether to allow inline configuration.
|
* @param {boolean} noInlineConfig Whether to allow inline configuration.
|
||||||
* @param {number} resultVersion Version of the LintResults object to return.
|
* @param {number} resultVersion Version of the LintResults object to return.
|
||||||
|
* @param {boolean} synchronous Whether to execute synchronously.
|
||||||
* @param {LintContentCallback} callback Callback (err, result) function.
|
* @param {LintContentCallback} callback Callback (err, result) function.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
|
|
@ -469,7 +469,10 @@ function lintContent(
|
||||||
handleRuleFailures,
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
|
synchronous,
|
||||||
callback) {
|
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)
|
// Remove UTF-8 byte order marker (if present)
|
||||||
content = content.replace(/^\uFEFF/, "");
|
content = content.replace(/^\uFEFF/, "");
|
||||||
// Remove front matter
|
// Remove front matter
|
||||||
|
|
@ -501,252 +504,267 @@ function lintContent(
|
||||||
content = helpers.clearHtmlCommentText(content);
|
content = helpers.clearHtmlCommentText(content);
|
||||||
// Parse content into lines and get markdown-it tokens
|
// Parse content into lines and get markdown-it tokens
|
||||||
const lines = content.split(helpers.newLineRe);
|
const lines = content.split(helpers.newLineRe);
|
||||||
const markdownitTokens = needMarkdownItTokens ?
|
// Function to run after fetching markdown-it tokens (when needed)
|
||||||
requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory, preClearedContent, lines) :
|
const lintContentInternal = (markdownitTokens) => {
|
||||||
[];
|
// Create (frozen) parameters for rules
|
||||||
// Create (frozen) parameters for rules
|
/** @type {MarkdownParsers} */
|
||||||
/** @type {MarkdownParsers} */
|
// @ts-ignore
|
||||||
// @ts-ignore
|
const parsersMarkdownIt = Object.freeze({
|
||||||
const parsersMarkdownIt = Object.freeze({
|
"markdownit": Object.freeze({
|
||||||
"markdownit": Object.freeze({
|
"tokens": markdownitTokens
|
||||||
"tokens": markdownitTokens
|
})
|
||||||
})
|
|
||||||
});
|
|
||||||
/** @type {MarkdownParsers} */
|
|
||||||
// @ts-ignore
|
|
||||||
const parsersMicromark = Object.freeze({
|
|
||||||
"micromark": Object.freeze({
|
|
||||||
"tokens": micromarkTokens
|
|
||||||
})
|
|
||||||
});
|
|
||||||
/** @type {MarkdownParsers} */
|
|
||||||
// @ts-ignore
|
|
||||||
const parsersNone = Object.freeze({});
|
|
||||||
const paramsBase = {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
"lines": Object.freeze(lines),
|
|
||||||
"frontMatterLines": Object.freeze(frontMatterLines)
|
|
||||||
};
|
|
||||||
cacheInitialize({
|
|
||||||
...paramsBase,
|
|
||||||
"parsers": parsersMicromark,
|
|
||||||
"config": null
|
|
||||||
});
|
|
||||||
// Function to run for each rule
|
|
||||||
let results = [];
|
|
||||||
/**
|
|
||||||
* @param {Rule} rule Rule.
|
|
||||||
* @returns {Promise<void> | null} Promise.
|
|
||||||
*/
|
|
||||||
const forRule = (rule) => {
|
|
||||||
// Configure rule
|
|
||||||
const ruleName = rule.names[0].toUpperCase();
|
|
||||||
const tokens = {};
|
|
||||||
let parsers = parsersNone;
|
|
||||||
if (rule.parser === undefined) {
|
|
||||||
tokens.tokens = markdownitTokens;
|
|
||||||
parsers = parsersMarkdownIt;
|
|
||||||
} else if (rule.parser === "markdownit") {
|
|
||||||
parsers = parsersMarkdownIt;
|
|
||||||
} else if (rule.parser === "micromark") {
|
|
||||||
parsers = parsersMicromark;
|
|
||||||
}
|
|
||||||
const params = {
|
|
||||||
...paramsBase,
|
|
||||||
...tokens,
|
|
||||||
parsers,
|
|
||||||
"config": effectiveConfig[ruleName]
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
||||||
function throwError(property) {
|
|
||||||
throw new Error(
|
|
||||||
`Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
||||||
function onError(errorInfo) {
|
|
||||||
if (!errorInfo ||
|
|
||||||
!helpers.isNumber(errorInfo.lineNumber) ||
|
|
||||||
(errorInfo.lineNumber < 1) ||
|
|
||||||
(errorInfo.lineNumber > lines.length)) {
|
|
||||||
throwError("lineNumber");
|
|
||||||
}
|
|
||||||
const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
|
|
||||||
if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (errorInfo.detail &&
|
|
||||||
!helpers.isString(errorInfo.detail)) {
|
|
||||||
throwError("detail");
|
|
||||||
}
|
|
||||||
if (errorInfo.context &&
|
|
||||||
!helpers.isString(errorInfo.context)) {
|
|
||||||
throwError("context");
|
|
||||||
}
|
|
||||||
if (errorInfo.information &&
|
|
||||||
!helpers.isUrl(errorInfo.information)) {
|
|
||||||
throwError("information");
|
|
||||||
}
|
|
||||||
if (errorInfo.range &&
|
|
||||||
(!Array.isArray(errorInfo.range) ||
|
|
||||||
(errorInfo.range.length !== 2) ||
|
|
||||||
!helpers.isNumber(errorInfo.range[0]) ||
|
|
||||||
(errorInfo.range[0] < 1) ||
|
|
||||||
!helpers.isNumber(errorInfo.range[1]) ||
|
|
||||||
(errorInfo.range[1] < 1) ||
|
|
||||||
((errorInfo.range[0] + errorInfo.range[1] - 1) >
|
|
||||||
lines[errorInfo.lineNumber - 1].length))) {
|
|
||||||
throwError("range");
|
|
||||||
}
|
|
||||||
const fixInfo = errorInfo.fixInfo;
|
|
||||||
const cleanFixInfo = {};
|
|
||||||
if (fixInfo) {
|
|
||||||
if (!helpers.isObject(fixInfo)) {
|
|
||||||
throwError("fixInfo");
|
|
||||||
}
|
|
||||||
if (fixInfo.lineNumber !== undefined) {
|
|
||||||
if ((!helpers.isNumber(fixInfo.lineNumber) ||
|
|
||||||
(fixInfo.lineNumber < 1) ||
|
|
||||||
(fixInfo.lineNumber > lines.length))) {
|
|
||||||
throwError("fixInfo.lineNumber");
|
|
||||||
}
|
|
||||||
cleanFixInfo.lineNumber =
|
|
||||||
fixInfo.lineNumber + frontMatterLines.length;
|
|
||||||
}
|
|
||||||
const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
|
|
||||||
if (fixInfo.editColumn !== undefined) {
|
|
||||||
if ((!helpers.isNumber(fixInfo.editColumn) ||
|
|
||||||
(fixInfo.editColumn < 1) ||
|
|
||||||
(fixInfo.editColumn >
|
|
||||||
lines[effectiveLineNumber - 1].length + 1))) {
|
|
||||||
throwError("fixInfo.editColumn");
|
|
||||||
}
|
|
||||||
cleanFixInfo.editColumn = fixInfo.editColumn;
|
|
||||||
}
|
|
||||||
if (fixInfo.deleteCount !== undefined) {
|
|
||||||
if ((!helpers.isNumber(fixInfo.deleteCount) ||
|
|
||||||
(fixInfo.deleteCount < -1) ||
|
|
||||||
(fixInfo.deleteCount >
|
|
||||||
lines[effectiveLineNumber - 1].length))) {
|
|
||||||
throwError("fixInfo.deleteCount");
|
|
||||||
}
|
|
||||||
cleanFixInfo.deleteCount = fixInfo.deleteCount;
|
|
||||||
}
|
|
||||||
if (fixInfo.insertText !== undefined) {
|
|
||||||
if (!helpers.isString(fixInfo.insertText)) {
|
|
||||||
throwError("fixInfo.insertText");
|
|
||||||
}
|
|
||||||
cleanFixInfo.insertText = fixInfo.insertText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const information = errorInfo.information || rule.information;
|
|
||||||
results.push({
|
|
||||||
lineNumber,
|
|
||||||
"ruleName": rule.names[0],
|
|
||||||
"ruleNames": rule.names,
|
|
||||||
"ruleDescription": rule.description,
|
|
||||||
"ruleInformation": information ? information.href : null,
|
|
||||||
"errorDetail": errorInfo.detail || null,
|
|
||||||
"errorContext": errorInfo.context || null,
|
|
||||||
"errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
|
|
||||||
"fixInfo": fixInfo ? cleanFixInfo : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Call (possibly external) rule function to report errors
|
|
||||||
const catchCallsOnError = (error) => onError({
|
|
||||||
"lineNumber": 1,
|
|
||||||
"detail": `This rule threw an exception: ${error.message || error}`
|
|
||||||
});
|
});
|
||||||
const invokeRuleFunction = () => rule.function(params, onError);
|
/** @type {MarkdownParsers} */
|
||||||
if (rule.asynchronous) {
|
// @ts-ignore
|
||||||
// Asynchronous rule, ensure it returns a Promise
|
const parsersMicromark = Object.freeze({
|
||||||
const ruleFunctionPromise =
|
"micromark": Object.freeze({
|
||||||
Promise.resolve().then(invokeRuleFunction);
|
"tokens": micromarkTokens
|
||||||
return handleRuleFailures ?
|
})
|
||||||
ruleFunctionPromise.catch(catchCallsOnError) :
|
});
|
||||||
ruleFunctionPromise;
|
/** @type {MarkdownParsers} */
|
||||||
}
|
// @ts-ignore
|
||||||
// Synchronous rule
|
const parsersNone = Object.freeze({});
|
||||||
try {
|
const paramsBase = {
|
||||||
invokeRuleFunction();
|
name,
|
||||||
} catch (error) {
|
version,
|
||||||
if (handleRuleFailures) {
|
"lines": Object.freeze(lines),
|
||||||
catchCallsOnError(error);
|
"frontMatterLines": Object.freeze(frontMatterLines)
|
||||||
} else {
|
};
|
||||||
throw error;
|
cacheInitialize({
|
||||||
|
...paramsBase,
|
||||||
|
"parsers": parsersMicromark,
|
||||||
|
"config": null
|
||||||
|
});
|
||||||
|
// Function to run for each rule
|
||||||
|
let results = [];
|
||||||
|
/**
|
||||||
|
* @param {Rule} rule Rule.
|
||||||
|
* @returns {Promise<void> | null} Promise.
|
||||||
|
*/
|
||||||
|
const forRule = (rule) => {
|
||||||
|
// Configure rule
|
||||||
|
const ruleName = rule.names[0].toUpperCase();
|
||||||
|
const tokens = {};
|
||||||
|
let parsers = parsersNone;
|
||||||
|
if (rule.parser === undefined) {
|
||||||
|
tokens.tokens = markdownitTokens;
|
||||||
|
parsers = parsersMarkdownIt;
|
||||||
|
} else if (rule.parser === "markdownit") {
|
||||||
|
parsers = parsersMarkdownIt;
|
||||||
|
} else if (rule.parser === "micromark") {
|
||||||
|
parsers = parsersMicromark;
|
||||||
}
|
}
|
||||||
}
|
const params = {
|
||||||
return null;
|
...paramsBase,
|
||||||
};
|
...tokens,
|
||||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
parsers,
|
||||||
function formatResults() {
|
"config": effectiveConfig[ruleName]
|
||||||
// Sort results by rule name by line number
|
|
||||||
results.sort((a, b) => (
|
|
||||||
a.ruleName.localeCompare(b.ruleName) ||
|
|
||||||
a.lineNumber - b.lineNumber
|
|
||||||
));
|
|
||||||
if (resultVersion < 3) {
|
|
||||||
// Remove fixInfo and multiple errors for the same rule and line number
|
|
||||||
const noPrevious = {
|
|
||||||
"ruleName": null,
|
|
||||||
"lineNumber": -1
|
|
||||||
};
|
};
|
||||||
results = results.filter((error, index, array) => {
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||||
delete error.fixInfo;
|
function throwError(property) {
|
||||||
const previous = array[index - 1] || noPrevious;
|
throw new Error(
|
||||||
return (
|
`Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
|
||||||
(error.ruleName !== previous.ruleName) ||
|
}
|
||||||
(error.lineNumber !== previous.lineNumber)
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||||
);
|
function onError(errorInfo) {
|
||||||
|
if (!errorInfo ||
|
||||||
|
!helpers.isNumber(errorInfo.lineNumber) ||
|
||||||
|
(errorInfo.lineNumber < 1) ||
|
||||||
|
(errorInfo.lineNumber > lines.length)) {
|
||||||
|
throwError("lineNumber");
|
||||||
|
}
|
||||||
|
const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
|
||||||
|
if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (errorInfo.detail &&
|
||||||
|
!helpers.isString(errorInfo.detail)) {
|
||||||
|
throwError("detail");
|
||||||
|
}
|
||||||
|
if (errorInfo.context &&
|
||||||
|
!helpers.isString(errorInfo.context)) {
|
||||||
|
throwError("context");
|
||||||
|
}
|
||||||
|
if (errorInfo.information &&
|
||||||
|
!helpers.isUrl(errorInfo.information)) {
|
||||||
|
throwError("information");
|
||||||
|
}
|
||||||
|
if (errorInfo.range &&
|
||||||
|
(!Array.isArray(errorInfo.range) ||
|
||||||
|
(errorInfo.range.length !== 2) ||
|
||||||
|
!helpers.isNumber(errorInfo.range[0]) ||
|
||||||
|
(errorInfo.range[0] < 1) ||
|
||||||
|
!helpers.isNumber(errorInfo.range[1]) ||
|
||||||
|
(errorInfo.range[1] < 1) ||
|
||||||
|
((errorInfo.range[0] + errorInfo.range[1] - 1) >
|
||||||
|
lines[errorInfo.lineNumber - 1].length))) {
|
||||||
|
throwError("range");
|
||||||
|
}
|
||||||
|
const fixInfo = errorInfo.fixInfo;
|
||||||
|
const cleanFixInfo = {};
|
||||||
|
if (fixInfo) {
|
||||||
|
if (!helpers.isObject(fixInfo)) {
|
||||||
|
throwError("fixInfo");
|
||||||
|
}
|
||||||
|
if (fixInfo.lineNumber !== undefined) {
|
||||||
|
if ((!helpers.isNumber(fixInfo.lineNumber) ||
|
||||||
|
(fixInfo.lineNumber < 1) ||
|
||||||
|
(fixInfo.lineNumber > lines.length))) {
|
||||||
|
throwError("fixInfo.lineNumber");
|
||||||
|
}
|
||||||
|
cleanFixInfo.lineNumber =
|
||||||
|
fixInfo.lineNumber + frontMatterLines.length;
|
||||||
|
}
|
||||||
|
const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber;
|
||||||
|
if (fixInfo.editColumn !== undefined) {
|
||||||
|
if ((!helpers.isNumber(fixInfo.editColumn) ||
|
||||||
|
(fixInfo.editColumn < 1) ||
|
||||||
|
(fixInfo.editColumn >
|
||||||
|
lines[effectiveLineNumber - 1].length + 1))) {
|
||||||
|
throwError("fixInfo.editColumn");
|
||||||
|
}
|
||||||
|
cleanFixInfo.editColumn = fixInfo.editColumn;
|
||||||
|
}
|
||||||
|
if (fixInfo.deleteCount !== undefined) {
|
||||||
|
if ((!helpers.isNumber(fixInfo.deleteCount) ||
|
||||||
|
(fixInfo.deleteCount < -1) ||
|
||||||
|
(fixInfo.deleteCount >
|
||||||
|
lines[effectiveLineNumber - 1].length))) {
|
||||||
|
throwError("fixInfo.deleteCount");
|
||||||
|
}
|
||||||
|
cleanFixInfo.deleteCount = fixInfo.deleteCount;
|
||||||
|
}
|
||||||
|
if (fixInfo.insertText !== undefined) {
|
||||||
|
if (!helpers.isString(fixInfo.insertText)) {
|
||||||
|
throwError("fixInfo.insertText");
|
||||||
|
}
|
||||||
|
cleanFixInfo.insertText = fixInfo.insertText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const information = errorInfo.information || rule.information;
|
||||||
|
results.push({
|
||||||
|
lineNumber,
|
||||||
|
"ruleName": rule.names[0],
|
||||||
|
"ruleNames": rule.names,
|
||||||
|
"ruleDescription": rule.description,
|
||||||
|
"ruleInformation": information ? information.href : null,
|
||||||
|
"errorDetail": errorInfo.detail || null,
|
||||||
|
"errorContext": errorInfo.context || null,
|
||||||
|
"errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
|
||||||
|
"fixInfo": fixInfo ? cleanFixInfo : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Call (possibly external) rule function to report errors
|
||||||
|
const catchCallsOnError = (error) => onError({
|
||||||
|
"lineNumber": 1,
|
||||||
|
"detail": `This rule threw an exception: ${error.message || error}`
|
||||||
});
|
});
|
||||||
}
|
const invokeRuleFunction = () => rule.function(params, onError);
|
||||||
if (resultVersion === 0) {
|
if (rule.asynchronous) {
|
||||||
// Return a dictionary of rule->[line numbers]
|
// Asynchronous rule, ensure it returns a Promise
|
||||||
const dictionary = {};
|
const ruleFunctionPromise =
|
||||||
for (const error of results) {
|
Promise.resolve().then(invokeRuleFunction);
|
||||||
const ruleLines = dictionary[error.ruleName] || [];
|
return handleRuleFailures ?
|
||||||
ruleLines.push(error.lineNumber);
|
ruleFunctionPromise.catch(catchCallsOnError) :
|
||||||
dictionary[error.ruleName] = ruleLines;
|
ruleFunctionPromise;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// Synchronous rule
|
||||||
results = dictionary;
|
try {
|
||||||
} else if (resultVersion === 1) {
|
invokeRuleFunction();
|
||||||
// Use ruleAlias instead of ruleNames
|
} catch (error) {
|
||||||
for (const error of results) {
|
if (handleRuleFailures) {
|
||||||
error.ruleAlias = error.ruleNames[1] || error.ruleName;
|
catchCallsOnError(error);
|
||||||
delete error.ruleNames;
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return null;
|
||||||
// resultVersion 2 or 3: Remove unwanted ruleName
|
};
|
||||||
for (const error of results) {
|
const formatResults = () => {
|
||||||
delete error.ruleName;
|
// Sort results by rule name by line number
|
||||||
|
results.sort((a, b) => (
|
||||||
|
a.ruleName.localeCompare(b.ruleName) ||
|
||||||
|
a.lineNumber - b.lineNumber
|
||||||
|
));
|
||||||
|
if (resultVersion < 3) {
|
||||||
|
// Remove fixInfo and multiple errors for the same rule and line number
|
||||||
|
const noPrevious = {
|
||||||
|
"ruleName": null,
|
||||||
|
"lineNumber": -1
|
||||||
|
};
|
||||||
|
results = results.filter((error, index, array) => {
|
||||||
|
delete error.fixInfo;
|
||||||
|
const previous = array[index - 1] || noPrevious;
|
||||||
|
return (
|
||||||
|
(error.ruleName !== previous.ruleName) ||
|
||||||
|
(error.lineNumber !== previous.lineNumber)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (resultVersion === 0) {
|
||||||
|
// Return a dictionary of rule->[line numbers]
|
||||||
|
const dictionary = {};
|
||||||
|
for (const error of results) {
|
||||||
|
const ruleLines = dictionary[error.ruleName] || [];
|
||||||
|
ruleLines.push(error.lineNumber);
|
||||||
|
dictionary[error.ruleName] = ruleLines;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
results = dictionary;
|
||||||
|
} else if (resultVersion === 1) {
|
||||||
|
// Use ruleAlias instead of ruleNames
|
||||||
|
for (const error of results) {
|
||||||
|
error.ruleAlias = error.ruleNames[1] || error.ruleName;
|
||||||
|
delete error.ruleNames;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// resultVersion 2 or 3: Remove unwanted ruleName
|
||||||
|
for (const error of results) {
|
||||||
|
delete error.ruleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
// Run all rules
|
||||||
|
const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
|
||||||
|
const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
|
||||||
|
const ruleListAsyncFirst = [
|
||||||
|
...ruleListAsync,
|
||||||
|
...ruleListSync
|
||||||
|
];
|
||||||
|
const callbackSuccess = () => callback(null, formatResults());
|
||||||
|
try {
|
||||||
|
const ruleResults = ruleListAsyncFirst.map(forRule);
|
||||||
|
if (ruleListAsync.length > 0) {
|
||||||
|
Promise.all(ruleResults.slice(0, ruleListAsync.length))
|
||||||
|
.then(callbackSuccess)
|
||||||
|
.catch(callbackError);
|
||||||
|
} else {
|
||||||
|
callbackSuccess();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
callbackError(error);
|
||||||
|
} finally {
|
||||||
|
cacheInitialize();
|
||||||
}
|
}
|
||||||
return results;
|
};
|
||||||
}
|
if (!needMarkdownItTokens || synchronous) {
|
||||||
// Run all rules
|
// Need/able to call into markdown-it and lintContentInternal synchronously
|
||||||
const ruleListAsync = enabledRuleList.filter((rule) => rule.asynchronous);
|
const markdownItTokens = needMarkdownItTokens ?
|
||||||
const ruleListSync = enabledRuleList.filter((rule) => !rule.asynchronous);
|
requireMarkdownItCjs().getMarkdownItTokens(markdownItFactory(), preClearedContent, lines) :
|
||||||
const ruleListAsyncFirst = [
|
[];
|
||||||
...ruleListAsync,
|
lintContentInternal(markdownItTokens);
|
||||||
...ruleListSync
|
} else {
|
||||||
];
|
// Need to call into markdown-it and lintContentInternal asynchronously
|
||||||
const callbackSuccess = () => callback(null, formatResults());
|
Promise.all([
|
||||||
const callbackError =
|
// eslint-disable-next-line no-inline-comments
|
||||||
(error) => callback(error instanceof Error ? error : new Error(error));
|
import(/* webpackMode: "eager" */ "./markdownit.cjs"),
|
||||||
try {
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
const ruleResults = ruleListAsyncFirst.map(forRule);
|
new Promise((resolve) => resolve(markdownItFactory()))
|
||||||
if (ruleListAsync.length > 0) {
|
]).then(([ markdownitCjs, markdownIt ]) => {
|
||||||
Promise.all(ruleResults.slice(0, ruleListAsync.length))
|
const markdownItTokens = markdownitCjs.getMarkdownItTokens(markdownIt, preClearedContent, lines);
|
||||||
.then(callbackSuccess)
|
lintContentInternal(markdownItTokens);
|
||||||
.catch(callbackError);
|
}).catch(callbackError);
|
||||||
} else {
|
|
||||||
callbackSuccess();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
callbackError(error);
|
|
||||||
} finally {
|
|
||||||
cacheInitialize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -799,6 +817,7 @@ function lintFile(
|
||||||
handleRuleFailures,
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
|
synchronous,
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -919,6 +938,7 @@ function lintInput(options, synchronous, callback) {
|
||||||
handleRuleFailures,
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
|
synchronous,
|
||||||
lintWorkerCallback
|
lintWorkerCallback
|
||||||
);
|
);
|
||||||
} else if (concurrency === 0) {
|
} 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.
|
* Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
|
||||||
*
|
*
|
||||||
* @callback MarkdownItFactory
|
* @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) => {
|
test("customRulesMarkdownItParamsTokensSameObject", (t) => {
|
||||||
t.plan(1);
|
t.plan(1);
|
||||||
/** @type {import("markdownlint").Options} */
|
/** @type {import("markdownlint").Options} */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue