diff --git a/lib/exports.d.mts b/lib/exports.d.mts index 74f6dab1a..6f4e85e61 100644 --- a/lib/exports.d.mts +++ b/lib/exports.d.mts @@ -6,6 +6,7 @@ export type LintCallback = import("./markdownlint.mjs").LintCallback; export type LintContentCallback = import("./markdownlint.mjs").LintContentCallback; export type LintError = import("./markdownlint.mjs").LintError; export type LintResults = import("./markdownlint.mjs").LintResults; +export type MarkdownItFactory = import("./markdownlint.mjs").MarkdownItFactory; export type MarkdownItToken = import("./markdownlint.mjs").MarkdownItToken; export type MarkdownParsers = import("./markdownlint.mjs").MarkdownParsers; export type MicromarkToken = import("./markdownlint.mjs").MicromarkToken; diff --git a/lib/exports.mjs b/lib/exports.mjs index 8fdef2e2a..76c4bea18 100644 --- a/lib/exports.mjs +++ b/lib/exports.mjs @@ -10,6 +10,7 @@ export { applyFix, applyFixes, getVersion } from "./markdownlint.mjs"; /** @typedef {import("./markdownlint.mjs").LintContentCallback} LintContentCallback */ /** @typedef {import("./markdownlint.mjs").LintError} LintError */ /** @typedef {import("./markdownlint.mjs").LintResults} LintResults */ +/** @typedef {import("./markdownlint.mjs").MarkdownItFactory} MarkdownItFactory */ /** @typedef {import("./markdownlint.mjs").MarkdownItToken} MarkdownItToken */ /** @typedef {import("./markdownlint.mjs").MarkdownParsers} MarkdownParsers */ /** @typedef {import("./markdownlint.mjs").MicromarkToken} MicromarkToken */ diff --git a/lib/markdownit.cjs b/lib/markdownit.cjs index 29ac03973..9cd043813 100644 --- a/lib/markdownit.cjs +++ b/lib/markdownit.cjs @@ -8,6 +8,8 @@ const { newLineRe } = require("../helpers"); /** @typedef {import("markdownlint").MarkdownItToken} MarkdownItToken */ // @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529 /** @typedef {import("markdownlint").Plugin} Plugin */ +// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529 +/** @typedef {import("markdownlint").MarkdownItFactory} MarkdownItFactory */ /** * @callback InlineCodeSpanCallback @@ -97,7 +99,7 @@ function freezeToken(token) { /** * Annotate tokens with line/lineNumber and freeze them. * - * @param {Object[]} tokens Array of markdown-it tokens. + * @param {MarkdownItToken[]} tokens Array of markdown-it tokens. * @param {string[]} lines Lines of Markdown content. * @returns {void} */ @@ -152,10 +154,10 @@ function annotateAndFreezeTokens(tokens, lines) { /** * Gets an array of markdown-it tokens for the input. * - * @param {Function} markdownItFactory ... + * @param {MarkdownItFactory} markdownItFactory ... * @param {string} content 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) { const markdownIt = markdownItFactory(); diff --git a/lib/markdownlint.d.mts b/lib/markdownlint.d.mts index 405c2df3d..8f8f1abc5 100644 --- a/lib/markdownlint.d.mts +++ b/lib/markdownlint.d.mts @@ -357,10 +357,23 @@ export type Rule = { */ function: RuleFunction; }; +/** + * Method used by the markdown-it parser to parse input. + */ +export type MarkdownItParse = (src: string, env: any) => any[]; +/** + * Instance of the markdown-it parser. + */ +export type MarkdownIt = { + /** + * Method to parse input. + */ + parse: MarkdownItParse; +}; /** * Gets an instance of the markdown-it parser. Any plugins should already have been loaded. */ -export type MarkdownItFactory = () => Function; +export type MarkdownItFactory = () => MarkdownIt; /** * Configuration options. */ diff --git a/lib/markdownlint.mjs b/lib/markdownlint.mjs index 0ebecff1d..75a146312 100644 --- a/lib/markdownlint.mjs +++ b/lib/markdownlint.mjs @@ -860,7 +860,7 @@ function lintInput(options, synchronous, callback) { options.resultVersion; const markdownItFactory = options.markdownItFactory || - (() => { throw new Error("The option 'markdownItFactory' needed to be set (due to the option 'customRules' including a rule requiring the 'markdown-it' parser), but 'markdownItFactory' was not set."); }); + (() => { throw new Error("The option 'markdownItFactory' was required (due to the option 'customRules' including a rule requiring the 'markdown-it' parser), but 'markdownItFactory' was not set."); }); const fs = options.fs || nodeFs; const aliasToRuleNames = mapAliasToRuleNames(ruleList); const results = newResults(ruleList); @@ -1476,11 +1476,27 @@ export function getVersion() { * @property {RuleFunction} function Rule implementation. */ +/** + * Method used by the markdown-it parser to parse input. + * + * @callback MarkdownItParse + * @param {string} src Source string. + * @param {Object} env Environment sandbox. + * @returns {import("markdown-it").Token[]} Tokens. + */ + +/** + * Instance of the markdown-it parser. + * + * @typedef MarkdownIt + * @property {MarkdownItParse} parse Method to parse input. + */ + /** * Gets an instance of the markdown-it parser. Any plugins should already have been loaded. * * @callback MarkdownItFactory - * @returns {Function} Instance of the markdown-it parser. + * @returns {MarkdownIt} Instance of the markdown-it parser. */ /** @@ -1511,7 +1527,7 @@ export function getVersion() { * * @callback ToStringCallback * @param {boolean} [ruleAliases] True to use rule aliases. - * @returns {string} + * @returns {string} Pretty-printed results. */ /** diff --git a/test/markdownlint-test-custom-rules.mjs b/test/markdownlint-test-custom-rules.mjs index 996d08a6b..b9dd4d252 100644 --- a/test/markdownlint-test-custom-rules.mjs +++ b/test/markdownlint-test-custom-rules.mjs @@ -14,6 +14,8 @@ import { __filename, importWithTypeJson } from "./esm-helpers.mjs"; const packageJson = await importWithTypeJson(import.meta, "../package.json"); const { homepage, version } = packageJson; +const markdownItFactory = () => markdownIt({ "html": true }); + test("customRulesV0", (t) => new Promise((resolve) => { t.plan(4); const customRulesMd = "./test/custom-rules.md"; @@ -21,7 +23,7 @@ test("customRulesV0", (t) => new Promise((resolve) => { const options = { "customRules": customRules.all, "files": [ customRulesMd ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "resultVersion": 0 }; lintAsync(options, function callback(err, actualResult) { @@ -94,7 +96,7 @@ test("customRulesV1", (t) => new Promise((resolve) => { const options = { "customRules": customRules.all, "files": [ customRulesMd ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "resultVersion": 1 }; lintAsync(options, function callback(err, actualResult) { @@ -226,7 +228,7 @@ test("customRulesV2", (t) => new Promise((resolve) => { const options = { "customRules": customRules.all, "files": [ customRulesMd ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "resultVersion": 2 }; lintAsync(options, function callback(err, actualResult) { @@ -355,7 +357,7 @@ test("customRulesConfig", (t) => new Promise((resolve) => { }, "letters-e-x": false }, - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "resultVersion": 0 }; lintAsync(options, function callback(err, actualResult) { @@ -381,7 +383,7 @@ test("customRulesNpmPackage", (t) => new Promise((resolve) => { require("./rules/npm"), require("markdownlint-rule-extended-ascii") ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "strings": { "string": "# Text\n\n---\n\nText ✅\n" }, @@ -561,12 +563,12 @@ test("customRulesParserUndefined", (t) => { } } ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "strings": { "string": "# Heading\n" } }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); test("customRulesParserNone", (t) => { @@ -590,7 +592,7 @@ test("customRulesParserNone", (t) => { "string": "# Heading\n" } }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); test("customRulesParserMarkdownIt", (t) => { @@ -613,12 +615,12 @@ test("customRulesParserMarkdownIt", (t) => { } } ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "strings": { "string": "# Heading\n" } }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); test("customRulesParserMicromark", (t) => { @@ -645,7 +647,33 @@ test("customRulesParserMicromark", (t) => { "string": "# Heading\n" } }; - return lintPromise(options).then(() => null); + return lintPromise(options); +}); + +test("customRulesMarkdownItFactoryUndefined", (t) => { + t.plan(1); + /** @type {import("markdownlint").Options} */ + const options = { + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "parser": "markdownit", + "function": () => {} + } + ], + "strings": { + "string": "# Heading\n" + } + }; + t.throws( + () => lintSync(options), + { + "message": "The option 'markdownItFactory' was required (due to the option 'customRules' including a rule requiring the 'markdown-it' parser), but 'markdownItFactory' was not set." + }, + "No exception when markdownItFactory is undefined." + ); }); test("customRulesMarkdownItParamsTokensSameObject", (t) => { @@ -665,12 +693,12 @@ test("customRulesMarkdownItParamsTokensSameObject", (t) => { } } ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "strings": { "string": "# Heading\n" } }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); test("customRulesMarkdownItTokensSnapshot", (t) => { @@ -689,14 +717,14 @@ test("customRulesMarkdownItTokensSnapshot", (t) => { } } ], - "markdownItFactory": () => markdownIt({ "html": true }), + markdownItFactory, "noInlineConfig": true }; return fs .readFile("./test/every-markdown-syntax.md", "utf8") .then((content) => { options.strings = { "content": content.split(newLineRe).join("\n") }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); }); @@ -722,7 +750,7 @@ test("customRulesMicromarkTokensSnapshot", (t) => { .readFile("./test/every-markdown-syntax.md", "utf8") .then((content) => { options.strings = { "content": content.split(newLineRe).join("\n") }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); }); @@ -1676,7 +1704,7 @@ test("customRulesLintJavaScript", (t) => new Promise((resolve) => { const options = { "customRules": customRules.lintJavaScript, "files": "test/lint-javascript.md", - "markdownItFactory": () => markdownIt({ "html": true }) + markdownItFactory }; lintAsync(options, (err, actual) => { t.falsy(err); @@ -1705,7 +1733,7 @@ test("customRulesValidateJson", (t) => new Promise((resolve) => { const options = { "customRules": customRules.validateJson, "files": "test/validate-json.md", - "markdownItFactory": () => markdownIt({ "html": true }) + markdownItFactory }; lintAsync(options, (err, actual) => { t.falsy(err); @@ -1805,9 +1833,9 @@ test("customRulesParamsAreFrozen", (t) => { } ], "files": [ "README.md" ], - "markdownItFactory": () => markdownIt({ "html": true }) + markdownItFactory }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); test("customRulesParamsAreStable", (t) => { @@ -1875,7 +1903,7 @@ test("customRulesParamsAreStable", (t) => { "string": "# Heading" } }; - return lintPromise(options).then(() => null); + return lintPromise(options); }); test("customRulesAsyncReadFiles", (t) => { diff --git a/test/markdownlint-test.mjs b/test/markdownlint-test.mjs index d570377c3..a74836fa3 100644 --- a/test/markdownlint-test.mjs +++ b/test/markdownlint-test.mjs @@ -35,7 +35,7 @@ const ajvOptions = { * Gets an instance of a markdown-it factory, suitable for use with options.markdownItFactory. * * @param {import("../lib/markdownlint.mjs").Plugin[]} markdownItPlugins Additional markdown-it plugins. - * @returns {Function} Instance of the markdown-it parser. + * @returns {import("../lib/markdownlint.mjs").MarkdownItFactory} Instance of the markdown-it parser. */ function getMarkdownItFactory(markdownItPlugins) { return () => { @@ -1260,7 +1260,7 @@ test("token-map-spans", (t) => { } ], "files": [ "./test/token-map-spans.md" ], - "markdownItFactory": () => markdownIt({ "html": true }) + "markdownItFactory": getMarkdownItFactory([]) }; lintSync(options); });