diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index a311a828..cbddfe40 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -714,10 +714,10 @@ module.exports.frontMatterHasTitle = /** * Returns an object with information about reference links and images. * - * @param {Object} params RuleParams instance. + * @param {import("../helpers/micromark.cjs").Token[]} tokens Micromark tokens. * @returns {Object} Reference link/image data. */ -function getReferenceLinkImageData(params) { +function getReferenceLinkImageData(tokens) { const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); const definitions = new Map(); const definitionLineIndices = []; @@ -726,7 +726,7 @@ function getReferenceLinkImageData(params) { const shortcuts = new Map(); const filteredTokens = micromark.filterByTypes( - params.parsers.micromark.tokens, + tokens, [ // definitionLineIndices "definition", "gfmFootnoteDefinition", @@ -1193,20 +1193,7 @@ const flatTokensSymbol = Symbol("flat-tokens"); /** @typedef {import("markdownlint-micromark").Event} Event */ /** @typedef {import("markdownlint-micromark").ParseOptions} ParseOptions */ /** @typedef {import("markdownlint-micromark").TokenType} TokenType */ - -/** - * Markdown token. - * - * @typedef {Object} Token - * @property {TokenType} type Token type. - * @property {number} startLine Start line (1-based). - * @property {number} startColumn Start column (1-based). - * @property {number} endLine End line (1-based). - * @property {number} endColumn End column (1-based). - * @property {string} text Token text. - * @property {Token[]} children Child tokens. - * @property {Token | null} parent Parent token. - */ +/** @typedef {import("../lib/markdownlint.js").MicromarkToken} Token */ /** * Returns whether a token is an htmlFlow type containing an HTML comment. @@ -1712,17 +1699,16 @@ function validateRuleList(ruleList, synchronous) { for (const [ index, rule ] of ruleList.entries()) { const customIndex = index - rules.length; // eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc - function newError(property) { + function newError(property, value) { return new Error( - "Property '" + property + "' of custom rule at index " + - customIndex + " is incorrect."); + `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`); } for (const property of [ "names", "tags" ]) { const value = rule[property]; if (!result && (!value || !Array.isArray(value) || (value.length === 0) || !value.every(helpers.isString) || value.some(helpers.isEmptyString))) { - result = newError(property); + result = newError(property, value); } } for (const propertyInfo of [ @@ -1732,22 +1718,31 @@ function validateRuleList(ruleList, synchronous) { const property = propertyInfo[0]; const value = rule[property]; if (!result && (!value || (typeof value !== propertyInfo[1]))) { - result = newError(property); + result = newError(property, value); } } + if ( + !result && + (rule.parser !== undefined) && + (rule.parser !== "markdownit") && + !((customIndex < 0) && (rule.parser === "micromark")) && + (rule.parser !== "none") + ) { + result = newError("parser", rule.parser); + } if ( !result && rule.information && !helpers.isUrl(rule.information) ) { - result = newError("information"); + result = newError("information", rule.information); } if ( !result && (rule.asynchronous !== undefined) && (typeof rule.asynchronous !== "boolean") ) { - result = newError("asynchronous"); + result = newError("asynchronous", rule.asynchronous); } if (!result && rule.asynchronous && synchronous) { result = new Error( @@ -2242,18 +2237,26 @@ function lintContent( const lines = content.split(helpers.newLineRe); annotateAndFreezeTokens(markdownitTokens, lines); // Create (frozen) parameters for rules - const parsers = Object.freeze({ + /** @type {MarkdownParsers} */ + // @ts-ignore + const parsersMarkdownIt = Object.freeze({ "markdownit": Object.freeze({ "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, - parsers, - "tokens": markdownitTokens, + "parsers": parsersMarkdownIt, "lines": Object.freeze(lines), "frontMatterLines": Object.freeze(frontMatterLines) }; @@ -2262,9 +2265,9 @@ function lintContent( const codeBlockAndSpanRanges = helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata); const flattenedLists = - helpers.flattenLists(paramsBase.parsers.markdownit.tokens); + helpers.flattenLists(markdownitTokens); const referenceLinkImageData = - helpers.getReferenceLinkImageData(paramsBase); + helpers.getReferenceLinkImageData(micromarkTokens); cache.set({ codeBlockAndSpanRanges, flattenedLists, @@ -2273,12 +2276,27 @@ function lintContent( }); // Function to run for each rule let results = []; - // eslint-disable-next-line jsdoc/require-jsdoc - function forRule(rule) { + /** + * @param {Rule} rule Rule. + * @returns {Promise | 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 @@ -2554,6 +2572,7 @@ function lintInput(options, synchronous, callback) { "description": rule.description, "information": helpers.cloneIfUrl(rule.information), "tags": helpers.cloneIfArray(rule.tags), + "parser": rule.parser, "asynchronous": rule.asynchronous, "function": rule.function })); @@ -2960,22 +2979,27 @@ module.exports = markdownlint; * @returns {void} */ +/* eslint-disable jsdoc/valid-types */ + /** * Rule parameters. * * @typedef {Object} RuleParams * @property {string} name File/string name. * @property {MarkdownParsers} parsers Markdown parser data. - * @property {string[]} lines File/string lines. - * @property {string[]} frontMatterLines Front matter lines. + * @property {readonly string[]} lines File/string lines. + * @property {readonly string[]} frontMatterLines Front matter lines. * @property {RuleConfiguration} config Rule configuration. */ +/* eslint-enable jsdoc/valid-types */ + /** * Markdown parser data. * * @typedef {Object} MarkdownParsers - * @property {ParserMarkdownIt} markdownit Markdown parser data from markdown-it. + * @property {ParserMarkdownIt} markdownit Markdown parser data from markdown-it (only present when Rule.parser is "markdownit"). + * @property {ParserMicromark} micromark Markdown parser data from micromark (only present when Rule.parser is "micromark"). */ /** @@ -2985,6 +3009,13 @@ module.exports = markdownlint; * @property {MarkdownItToken[]} tokens Token objects from markdown-it. */ +/** + * Markdown parser data from micromark. + * + * @typedef {Object} ParserMicromark + * @property {MicromarkToken[]} tokens Token objects from micromark. + */ + /** * markdown-it token. * @@ -3006,6 +3037,22 @@ module.exports = markdownlint; * @property {string} line Line content. */ +/** @typedef {import("markdownlint-micromark").TokenType} MicromarkTokenType */ + +/** + * micromark token. + * + * @typedef {Object} MicromarkToken + * @property {MicromarkTokenType} type Token type. + * @property {number} startLine Start line (1-based). + * @property {number} startColumn Start column (1-based). + * @property {number} endLine End line (1-based). + * @property {number} endColumn End column (1-based). + * @property {string} text Token text. + * @property {MicromarkToken[]} children Child tokens. + * @property {MicromarkToken | null} parent Parent token. + */ + /** * Error-reporting callback. * @@ -3044,6 +3091,7 @@ module.exports = markdownlint; * @property {string} description Rule description. * @property {URL} [information] Link to more information. * @property {string[]} tags Rule tag(s). + * @property {"markdownit" | "micromark" | "none"} parser Parser used. * @property {boolean} [asynchronous] True if asynchronous. * @property {RuleFunction} function Rule implementation. */ @@ -3189,6 +3237,7 @@ module.exports = { "names": [ "MD001", "heading-increment" ], "description": "Heading levels should only increment by one level at a time", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD001(params, onError) { let prevLevel = 0; filterTokens(params, "heading_open", function forToken(token) { @@ -3225,6 +3274,7 @@ module.exports = { "names": [ "MD003", "heading-style" ], "description": "Heading style", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD003(params, onError) { let style = String(params.config.style || "consistent"); filterTokens(params, "heading_open", function forToken(token) { @@ -3293,6 +3343,7 @@ module.exports = { "names": [ "MD004", "ul-style" ], "description": "Unordered list style", "tags": [ "bullet", "ul" ], + "parser": "none", "function": function MD004(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = style; @@ -3369,6 +3420,7 @@ module.exports = { "names": [ "MD005", "list-indent" ], "description": "Inconsistent indentation for list items at the same level", "tags": [ "bullet", "ul", "indentation" ], + "parser": "micromark", "function": function MD005(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -3470,6 +3522,7 @@ module.exports = { "names": [ "MD007", "ul-indent" ], "description": "Unordered list indentation", "tags": [ "bullet", "ul", "indentation" ], + "parser": "micromark", "function": function MD007(params, onError) { const indent = Number(params.config.indent || 2); const startIndented = !!params.config.start_indented; @@ -3563,6 +3616,7 @@ module.exports = { "names": [ "MD009", "no-trailing-spaces" ], "description": "Trailing spaces", "tags": [ "whitespace" ], + "parser": "markdownit", "function": function MD009(params, onError) { let brSpaces = params.config.br_spaces; brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces); @@ -3664,6 +3718,7 @@ module.exports = { "names": [ "MD010", "no-hard-tabs" ], "description": "Hard tabs", "tags": [ "whitespace", "hard_tab" ], + "parser": "markdownit", "function": function MD010(params, onError) { const codeBlocks = params.config.code_blocks; const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; @@ -3737,6 +3792,7 @@ module.exports = { "names": [ "MD011", "no-reversed-links" ], "description": "Reversed link syntax", "tags": [ "links" ], + "parser": "none", "function": function MD011(params, onError) { const exclusions = codeBlockAndSpanRanges(); forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => { @@ -3793,6 +3849,7 @@ module.exports = { "names": [ "MD012", "no-multiple-blanks" ], "description": "Multiple consecutive blank lines", "tags": [ "whitespace", "blank_lines" ], + "parser": "none", "function": function MD012(params, onError) { const maximum = Number(params.config.maximum || 1); let count = 0; @@ -3855,6 +3912,7 @@ module.exports = { "names": [ "MD013", "line-length" ], "description": "Line length", "tags": [ "line_length" ], + "parser": "markdownit", "function": function MD013(params, onError) { const lineLength = Number(params.config.line_length || 80); const headingLineLength = @@ -3948,6 +4006,7 @@ module.exports = { "names": [ "MD014", "commands-show-output" ], "description": "Dollar signs used before commands without showing output", "tags": [ "code" ], + "parser": "markdownit", "function": function MD014(params, onError) { for (const type of [ "code_block", "fence" ]) { filterTokens(params, type, (token) => { @@ -4014,6 +4073,7 @@ module.exports = { "names": [ "MD018", "no-missing-space-atx" ], "description": "No space after hash on atx style heading", "tags": [ "headings", "atx", "spaces" ], + "parser": "none", "function": function MD018(params, onError) { forEachLine(lineMetadata(), (line, lineIndex, inCode) => { if (!inCode && @@ -4062,6 +4122,7 @@ module.exports = { "names": [ "MD019", "no-multiple-space-atx" ], "description": "Multiple spaces after hash on atx style heading", "tags": [ "headings", "atx", "spaces" ], + "parser": "markdownit", "function": function MD019(params, onError) { filterTokens(params, "heading_open", (token) => { if (headingStyleFor(token) === "atx") { @@ -4114,6 +4175,7 @@ module.exports = { "names": [ "MD020", "no-missing-space-closed-atx" ], "description": "No space inside hashes on closed atx style heading", "tags": [ "headings", "atx_closed", "spaces" ], + "parser": "none", "function": function MD020(params, onError) { forEachLine(lineMetadata(), (line, lineIndex, inCode) => { if (!inCode) { @@ -4191,6 +4253,7 @@ module.exports = { "names": [ "MD021", "no-multiple-space-closed-atx" ], "description": "Multiple spaces inside hashes on closed atx style heading", "tags": [ "headings", "atx_closed", "spaces" ], + "parser": "markdownit", "function": function MD021(params, onError) { filterTokens(params, "heading_open", (token) => { if (headingStyleFor(token) === "atx_closed") { @@ -4290,6 +4353,7 @@ module.exports = { "names": [ "MD022", "blanks-around-headings" ], "description": "Headings should be surrounded by blank lines", "tags": [ "headings", "blank_lines" ], + "parser": "micromark", "function": function MD022(params, onError) { const getLinesAbove = getLinesFunction(params.config.lines_above); const getLinesBelow = getLinesFunction(params.config.lines_below); @@ -4391,6 +4455,7 @@ module.exports = { "names": [ "MD023", "heading-start-left" ], "description": "Headings must start at the beginning of the line", "tags": [ "headings", "spaces" ], + "parser": "markdownit", "function": function MD023(params, onError) { filterTokens(params, "heading_open", function forToken(token) { const { lineNumber, line } = token; @@ -4440,6 +4505,7 @@ module.exports = { "names": [ "MD024", "no-duplicate-heading" ], "description": "Multiple headings with the same content", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD024(params, onError) { const siblingsOnly = !!params.config.siblings_only || false; const knownContents = [ null, [] ]; @@ -4496,6 +4562,7 @@ module.exports = { "names": [ "MD025", "single-title", "single-h1" ], "description": "Multiple top-level headings in the same document", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD025(params, onError) { const level = Number(params.config.level || 1); const tag = "h" + level; @@ -4542,6 +4609,7 @@ module.exports = { "names": [ "MD026", "no-trailing-punctuation" ], "description": "Trailing punctuation in heading", "tags": [ "headings" ], + "parser": "micromark", "function": function MD026(params, onError) { let punctuation = params.config.punctuation; punctuation = String( @@ -4605,6 +4673,7 @@ module.exports = { "names": ["MD027", "no-multiple-space-blockquote"], "description": "Multiple spaces after blockquote symbol", "tags": ["blockquote", "whitespace", "indentation"], + "parser": "micromark", "function": function MD027(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -4659,6 +4728,7 @@ module.exports = { "names": [ "MD028", "no-blanks-blockquote" ], "description": "Blank line inside blockquote", "tags": [ "blockquote", "whitespace" ], + "parser": "micromark", "function": function MD028(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -4721,6 +4791,7 @@ module.exports = { "names": [ "MD029", "ol-prefix" ], "description": "Ordered list item prefix", "tags": [ "ol" ], + "parser": "none", "function": function MD029(params, onError) { const style = String(params.config.style || "one_or_ordered"); const filteredLists = flattenedLists().filter((list) => !list.unordered); @@ -4794,6 +4865,7 @@ module.exports = { "names": [ "MD030", "list-marker-space" ], "description": "Spaces after list markers", "tags": [ "ol", "ul", "whitespace" ], + "parser": "micromark", "function": function MD030(params, onError) { const ulSingle = Number(params.config.ul_single || 1); const olSingle = Number(params.config.ol_single || 1); @@ -4872,6 +4944,7 @@ module.exports = { "names": [ "MD031", "blanks-around-fences" ], "description": "Fenced code blocks should be surrounded by blank lines", "tags": [ "code", "blank_lines" ], + "parser": "none", "function": function MD031(params, onError) { const listItems = params.config.list_items; const includeListItems = (listItems === undefined) ? true : !!listItems; @@ -4953,6 +5026,7 @@ module.exports = { "names": [ "MD032", "blanks-around-lists" ], "description": "Lists should be surrounded by blank lines", "tags": [ "bullet", "ul", "ol", "blank_lines" ], + "parser": "micromark", "function": function MD032(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -5020,6 +5094,7 @@ module.exports = { "names": [ "MD033", "no-inline-html" ], "description": "Inline HTML", "tags": [ "html" ], + "parser": "micromark", "function": function MD033(params, onError) { let allowedElements = params.config.allowed_elements; allowedElements = Array.isArray(allowedElements) ? allowedElements : []; @@ -5076,6 +5151,7 @@ module.exports = { "names": [ "MD034", "no-bare-urls" ], "description": "Bare URL used", "tags": [ "links", "url" ], + "parser": "micromark", "function": function MD034(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -5190,6 +5266,7 @@ module.exports = { "names": [ "MD035", "hr-style" ], "description": "Horizontal rule style", "tags": [ "hr" ], + "parser": "micromark", "function": function MD035(params, onError) { let style = String(params.config.style || "consistent").trim(); // eslint-disable-next-line jsdoc/valid-types @@ -5230,6 +5307,7 @@ module.exports = { "names": [ "MD036", "no-emphasis-as-heading" ], "description": "Emphasis used instead of a heading", "tags": [ "headings", "emphasis" ], + "parser": "markdownit", "function": function MD036(params, onError) { let punctuation = params.config.punctuation; punctuation = @@ -5300,6 +5378,7 @@ module.exports = { "names": [ "MD037", "no-space-in-emphasis" ], "description": "Spaces inside emphasis markers", "tags": [ "whitespace", "emphasis" ], + "parser": "micromark", "function": function MD037(params, onError) { // Initialize variables @@ -5423,6 +5502,7 @@ module.exports = { "names": [ "MD038", "no-space-in-code" ], "description": "Spaces inside code span elements", "tags": [ "whitespace", "code" ], + "parser": "micromark", "function": function MD038(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -5527,6 +5607,7 @@ module.exports = { "names": [ "MD039", "no-space-in-links" ], "description": "Spaces inside link text", "tags": [ "whitespace", "links" ], + "parser": "markdownit", "function": function MD039(params, onError) { filterTokens(params, "inline", (token) => { const { children } = token; @@ -5607,6 +5688,7 @@ module.exports = { "names": [ "MD040", "fenced-code-language" ], "description": "Fenced code blocks should have a language specified", "tags": [ "code", "language" ], + "parser": "micromark", "function": function MD040(params, onError) { let allowed = params.config.allowed_languages; allowed = Array.isArray(allowed) ? allowed : []; @@ -5657,6 +5739,7 @@ module.exports = { "names": [ "MD041", "first-line-heading", "first-line-h1" ], "description": "First line in a file should be a top-level heading", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD041(params, onError) { const level = Number(params.config.level || 1); const tag = "h" + level; @@ -5713,6 +5796,7 @@ module.exports = { "names": [ "MD042", "no-empty-links" ], "description": "No empty links", "tags": [ "links" ], + "parser": "markdownit", "function": function MD042(params, onError) { filterTokens(params, "inline", function forToken(token) { let inLink = false; @@ -5776,6 +5860,7 @@ module.exports = { "names": [ "MD043", "required-headings" ], "description": "Required heading structure", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD043(params, onError) { const requiredHeadings = params.config.headings; if (!Array.isArray(requiredHeadings)) { @@ -5859,6 +5944,7 @@ module.exports = { "names": [ "MD044", "proper-names" ], "description": "Proper names should have the correct capitalization", "tags": [ "spelling" ], + "parser": "micromark", "function": function MD044(params, onError) { let names = params.config.names; names = Array.isArray(names) ? names : []; @@ -5980,6 +6066,7 @@ module.exports = { "names": [ "MD045", "no-alt-text" ], "description": "Images should have alternate text (alt text)", "tags": [ "accessibility", "images" ], + "parser": "micromark", "function": function MD045(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -6060,6 +6147,7 @@ module.exports = { "names": [ "MD046", "code-block-style" ], "description": "Code block style", "tags": [ "code" ], + "parser": "micromark", "function": function MD046(params, onError) { let expectedStyle = String(params.config.style || "consistent"); // eslint-disable-next-line jsdoc/valid-types @@ -6104,6 +6192,7 @@ module.exports = { "names": [ "MD047", "single-trailing-newline" ], "description": "Files should end with a single newline character", "tags": [ "blank_lines" ], + "parser": "none", "function": function MD047(params, onError) { const lastLineNumber = params.lines.length; const lastLine = params.lines[lastLineNumber - 1]; @@ -6146,6 +6235,7 @@ module.exports = { "names": [ "MD048", "code-fence-style" ], "description": "Code fence style", "tags": [ "code" ], + "parser": "micromark", "function": function MD048(params, onError) { const style = String(params.config.style || "consistent"); // eslint-disable-next-line jsdoc/valid-types @@ -6263,6 +6353,7 @@ module.exports = [ "names": [ "MD049", "emphasis-style" ], "description": "Emphasis style", "tags": [ "emphasis" ], + "parser": "micromark", "function": function MD049(params, onError) { return impl( params, @@ -6279,6 +6370,7 @@ module.exports = [ "names": [ "MD050", "strong-style" ], "description": "Strong style", "tags": [ "emphasis" ], + "parser": "micromark", "function": function MD050(params, onError) { return impl( params, @@ -6372,6 +6464,7 @@ module.exports = { "names": [ "MD051", "link-fragments" ], "description": "Link fragments should be valid", "tags": [ "links" ], + "parser": "micromark", "function": function MD051(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ @@ -6503,6 +6596,7 @@ module.exports = { "description": "Reference links and images should use a label that is defined", "tags": [ "images", "links" ], + "parser": "none", "function": function MD052(params, onError) { const { config, lines } = params; const shortcutSyntax = config.shortcut_syntax || false; @@ -6555,6 +6649,7 @@ module.exports = { "names": [ "MD053", "link-image-reference-definitions" ], "description": "Link and image reference definitions should be needed", "tags": [ "images", "links" ], + "parser": "none", "function": function MD053(params, onError) { const ignored = new Set(params.config.ignored_definitions || [ "//" ]); const lines = params.lines; @@ -6642,6 +6737,7 @@ module.exports = { "names": [ "MD054", "link-image-style" ], "description": "Link and image style", "tags": [ "images", "links" ], + "parser": "micromark", "function": (params, onError) => { const config = params.config; const autolink = (config.autolink === undefined) || !!config.autolink; @@ -6774,6 +6870,7 @@ module.exports = { "names": [ "MD055", "table-pipe-style" ], "description": "Table pipe style", "tags": [ "table" ], + "parser": "micromark", "function": function MD055(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = style; @@ -6858,6 +6955,7 @@ module.exports = { "names": [ "MD056", "table-column-count" ], "description": "Table column count", "tags": [ "table" ], + "parser": "micromark", "function": function MD056(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/doc/CustomRules.md b/doc/CustomRules.md index a4368b5e..c7366d55 100644 --- a/doc/CustomRules.md +++ b/doc/CustomRules.md @@ -34,6 +34,7 @@ module.exports = { "description": "Rule that reports an error for any blockquote", "information": new URL("https://example.com/rules/any-blockquote"), "tags": [ "test" ], + "parser": "markdownit", "function": function rule(params, onError) { params.parsers.markdownit.tokens.filter(function filterToken(token) { return token.type === "blockquote_open"; @@ -49,8 +50,7 @@ module.exports = { }; ``` -A rule is implemented as an `Object` with one optional and four required -properties: +A rule is implemented as an `Object`: - `names` is a required `Array` of `String` values that identify the rule in output messages and config. @@ -60,6 +60,9 @@ properties: about the rule. - `tags` is a required `Array` of `String` values that groups related rules for easier customization. +- `parser` is a required `String` value `"markdownit" | "none"` that specifies + the parser data used via `params.parsers` (see below). + - Note: The value `"micromark"` is valid but is NOT currently supported. - `asynchronous` is an optional `Boolean` value that indicates whether the rule returns a `Promise` and runs asynchronously. - `function` is a required `Function` that implements the rule and is passed two @@ -67,8 +70,13 @@ properties: - `params` is an `Object` with properties that describe the content being analyzed: - `name` is a `String` that identifies the input file/string. - - `tokens` is an `Array` of [`markdown-it` `Token`s][markdown-it-token] with - added `line` and `lineNumber` properties. + - `parsers` is an `Object` with properties corresponding to the value of + `parser` in the rule definition (see above). + - `markdownit` is an `Object` that provides access to output from the + [`markdown-it`][markdown-it] parser. + - `tokens` is an `Array` of [`markdown-it` `Token`s][markdown-it-token] + with added `line` and `lineNumber` properties. (This property was + previously on the `params` object.) - `lines` is an `Array` of `String` values corresponding to the lines of the input file/string. - `frontMatterLines` is an `Array` of `String` values corresponding to any @@ -145,7 +153,7 @@ Yields the `params` object: ```json { "name": "doc/example.md", - "tokens": [ + "parsers.markdownit.tokens": [ { "type": "heading_open", "tag": "h1", diff --git a/example/typescript/type-check.ts b/example/typescript/type-check.ts index fe9b3422..f9831298 100644 --- a/example/typescript/type-check.ts +++ b/example/typescript/type-check.ts @@ -114,6 +114,7 @@ const testRule: markdownlint.Rule = { "description": "Test rule", "information": new URL("https://example.com/rule-information"), "tags": [ "test-tag" ], + "parser": "none", "function": function rule(params: markdownlint.RuleParams, onError: markdownlint.RuleOnError) { assert(!!params); assert(!!onError); @@ -123,6 +124,9 @@ const testRule: markdownlint.Rule = { "parsers": { "markdownit": { "tokens": [] + }, + "micromark": { + "tokens": [] } }, "lines": [ diff --git a/helpers/README.md b/helpers/README.md index dd0abd25..6c878261 100644 --- a/helpers/README.md +++ b/helpers/README.md @@ -29,6 +29,7 @@ module.exports = { "names": [ "every-n-lines" ], "description": "Rule that reports an error every N lines", "tags": [ "test" ], + "parser": "none", "function": (params, onError) => { const n = params.config.n || 2; forEachLine(getLineMetadata(params), (line, lineIndex) => { diff --git a/helpers/helpers.js b/helpers/helpers.js index de7d76bc..7605bb17 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -702,10 +702,10 @@ module.exports.frontMatterHasTitle = /** * Returns an object with information about reference links and images. * - * @param {Object} params RuleParams instance. + * @param {import("../helpers/micromark.cjs").Token[]} tokens Micromark tokens. * @returns {Object} Reference link/image data. */ -function getReferenceLinkImageData(params) { +function getReferenceLinkImageData(tokens) { const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); const definitions = new Map(); const definitionLineIndices = []; @@ -714,7 +714,7 @@ function getReferenceLinkImageData(params) { const shortcuts = new Map(); const filteredTokens = micromark.filterByTypes( - params.parsers.micromark.tokens, + tokens, [ // definitionLineIndices "definition", "gfmFootnoteDefinition", diff --git a/helpers/micromark.cjs b/helpers/micromark.cjs index 33cf440f..d1b93313 100644 --- a/helpers/micromark.cjs +++ b/helpers/micromark.cjs @@ -15,20 +15,7 @@ const flatTokensSymbol = Symbol("flat-tokens"); /** @typedef {import("markdownlint-micromark").Event} Event */ /** @typedef {import("markdownlint-micromark").ParseOptions} ParseOptions */ /** @typedef {import("markdownlint-micromark").TokenType} TokenType */ - -/** - * Markdown token. - * - * @typedef {Object} Token - * @property {TokenType} type Token type. - * @property {number} startLine Start line (1-based). - * @property {number} startColumn Start column (1-based). - * @property {number} endLine End line (1-based). - * @property {number} endColumn End column (1-based). - * @property {string} text Token text. - * @property {Token[]} children Child tokens. - * @property {Token | null} parent Parent token. - */ +/** @typedef {import("../lib/markdownlint.js").MicromarkToken} Token */ /** * Returns whether a token is an htmlFlow type containing an HTML comment. diff --git a/lib/markdownlint.d.ts b/lib/markdownlint.d.ts index 4747bfb5..6ebe8575 100644 --- a/lib/markdownlint.d.ts +++ b/lib/markdownlint.d.ts @@ -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, MarkdownItToken, 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, 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 }; } /** * Configuration options. @@ -123,11 +123,11 @@ type RuleParams = { /** * File/string lines. */ - lines: string[]; + lines: readonly string[]; /** * Front matter lines. */ - frontMatterLines: string[]; + frontMatterLines: readonly string[]; /** * Rule configuration. */ @@ -138,9 +138,13 @@ type RuleParams = { */ type MarkdownParsers = { /** - * Markdown parser data from markdown-it. + * Markdown parser data from markdown-it (only present when Rule.parser is "markdownit"). */ markdownit: ParserMarkdownIt; + /** + * Markdown parser data from micromark (only present when Rule.parser is "micromark"). + */ + micromark: ParserMicromark; }; /** * Markdown parser data from markdown-it. @@ -151,6 +155,15 @@ type ParserMarkdownIt = { */ tokens: MarkdownItToken[]; }; +/** + * Markdown parser data from micromark. + */ +type ParserMicromark = { + /** + * Token objects from micromark. + */ + tokens: MicromarkToken[]; +}; /** * markdown-it token. */ @@ -216,6 +229,44 @@ type MarkdownItToken = { */ line: string; }; +type MicromarkTokenType = import("markdownlint-micromark").TokenType; +/** + * micromark token. + */ +type MicromarkToken = { + /** + * Token type. + */ + type: MicromarkTokenType; + /** + * Start line (1-based). + */ + startLine: number; + /** + * Start column (1-based). + */ + startColumn: number; + /** + * End line (1-based). + */ + endLine: number; + /** + * End column (1-based). + */ + endColumn: number; + /** + * Token text. + */ + text: string; + /** + * Child tokens. + */ + children: MicromarkToken[]; + /** + * Parent token. + */ + parent: MicromarkToken | null; +}; /** * Error-reporting callback. */ @@ -290,6 +341,10 @@ type Rule = { * Rule tag(s). */ tags: string[]; + /** + * Parser used. + */ + parser: "markdownit" | "micromark" | "none"; /** * True if asynchronous. */ diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 877c8c88..304e4613 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -33,17 +33,16 @@ function validateRuleList(ruleList, synchronous) { for (const [ index, rule ] of ruleList.entries()) { const customIndex = index - rules.length; // eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc - function newError(property) { + function newError(property, value) { return new Error( - "Property '" + property + "' of custom rule at index " + - customIndex + " is incorrect."); + `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`); } for (const property of [ "names", "tags" ]) { const value = rule[property]; if (!result && (!value || !Array.isArray(value) || (value.length === 0) || !value.every(helpers.isString) || value.some(helpers.isEmptyString))) { - result = newError(property); + result = newError(property, value); } } for (const propertyInfo of [ @@ -53,22 +52,31 @@ function validateRuleList(ruleList, synchronous) { const property = propertyInfo[0]; const value = rule[property]; if (!result && (!value || (typeof value !== propertyInfo[1]))) { - result = newError(property); + result = newError(property, value); } } + if ( + !result && + (rule.parser !== undefined) && + (rule.parser !== "markdownit") && + !((customIndex < 0) && (rule.parser === "micromark")) && + (rule.parser !== "none") + ) { + result = newError("parser", rule.parser); + } if ( !result && rule.information && !helpers.isUrl(rule.information) ) { - result = newError("information"); + result = newError("information", rule.information); } if ( !result && (rule.asynchronous !== undefined) && (typeof rule.asynchronous !== "boolean") ) { - result = newError("asynchronous"); + result = newError("asynchronous", rule.asynchronous); } if (!result && rule.asynchronous && synchronous) { result = new Error( @@ -563,18 +571,26 @@ function lintContent( const lines = content.split(helpers.newLineRe); annotateAndFreezeTokens(markdownitTokens, lines); // Create (frozen) parameters for rules - const parsers = Object.freeze({ + /** @type {MarkdownParsers} */ + // @ts-ignore + const parsersMarkdownIt = Object.freeze({ "markdownit": Object.freeze({ "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, - parsers, - "tokens": markdownitTokens, + "parsers": parsersMarkdownIt, "lines": Object.freeze(lines), "frontMatterLines": Object.freeze(frontMatterLines) }; @@ -583,9 +599,9 @@ function lintContent( const codeBlockAndSpanRanges = helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata); const flattenedLists = - helpers.flattenLists(paramsBase.parsers.markdownit.tokens); + helpers.flattenLists(markdownitTokens); const referenceLinkImageData = - helpers.getReferenceLinkImageData(paramsBase); + helpers.getReferenceLinkImageData(micromarkTokens); cache.set({ codeBlockAndSpanRanges, flattenedLists, @@ -594,12 +610,27 @@ function lintContent( }); // Function to run for each rule let results = []; - // eslint-disable-next-line jsdoc/require-jsdoc - function forRule(rule) { + /** + * @param {Rule} rule Rule. + * @returns {Promise | 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 @@ -875,6 +906,7 @@ function lintInput(options, synchronous, callback) { "description": rule.description, "information": helpers.cloneIfUrl(rule.information), "tags": helpers.cloneIfArray(rule.tags), + "parser": rule.parser, "asynchronous": rule.asynchronous, "function": rule.function })); @@ -1281,22 +1313,27 @@ module.exports = markdownlint; * @returns {void} */ +/* eslint-disable jsdoc/valid-types */ + /** * Rule parameters. * * @typedef {Object} RuleParams * @property {string} name File/string name. * @property {MarkdownParsers} parsers Markdown parser data. - * @property {string[]} lines File/string lines. - * @property {string[]} frontMatterLines Front matter lines. + * @property {readonly string[]} lines File/string lines. + * @property {readonly string[]} frontMatterLines Front matter lines. * @property {RuleConfiguration} config Rule configuration. */ +/* eslint-enable jsdoc/valid-types */ + /** * Markdown parser data. * * @typedef {Object} MarkdownParsers - * @property {ParserMarkdownIt} markdownit Markdown parser data from markdown-it. + * @property {ParserMarkdownIt} markdownit Markdown parser data from markdown-it (only present when Rule.parser is "markdownit"). + * @property {ParserMicromark} micromark Markdown parser data from micromark (only present when Rule.parser is "micromark"). */ /** @@ -1306,6 +1343,13 @@ module.exports = markdownlint; * @property {MarkdownItToken[]} tokens Token objects from markdown-it. */ +/** + * Markdown parser data from micromark. + * + * @typedef {Object} ParserMicromark + * @property {MicromarkToken[]} tokens Token objects from micromark. + */ + /** * markdown-it token. * @@ -1327,6 +1371,22 @@ module.exports = markdownlint; * @property {string} line Line content. */ +/** @typedef {import("markdownlint-micromark").TokenType} MicromarkTokenType */ + +/** + * micromark token. + * + * @typedef {Object} MicromarkToken + * @property {MicromarkTokenType} type Token type. + * @property {number} startLine Start line (1-based). + * @property {number} startColumn Start column (1-based). + * @property {number} endLine End line (1-based). + * @property {number} endColumn End column (1-based). + * @property {string} text Token text. + * @property {MicromarkToken[]} children Child tokens. + * @property {MicromarkToken | null} parent Parent token. + */ + /** * Error-reporting callback. * @@ -1365,6 +1425,7 @@ module.exports = markdownlint; * @property {string} description Rule description. * @property {URL} [information] Link to more information. * @property {string[]} tags Rule tag(s). + * @property {"markdownit" | "micromark" | "none"} parser Parser used. * @property {boolean} [asynchronous] True if asynchronous. * @property {RuleFunction} function Rule implementation. */ diff --git a/lib/md001.js b/lib/md001.js index 16ed0ae0..0ec6e460 100644 --- a/lib/md001.js +++ b/lib/md001.js @@ -10,6 +10,7 @@ module.exports = { "names": [ "MD001", "heading-increment" ], "description": "Heading levels should only increment by one level at a time", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD001(params, onError) { let prevLevel = 0; filterTokens(params, "heading_open", function forToken(token) { diff --git a/lib/md003.js b/lib/md003.js index 6b8e5920..7ce3a319 100644 --- a/lib/md003.js +++ b/lib/md003.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD003", "heading-style" ], "description": "Heading style", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD003(params, onError) { let style = String(params.config.style || "consistent"); filterTokens(params, "heading_open", function forToken(token) { diff --git a/lib/md004.js b/lib/md004.js index 03ad3971..4e4c6811 100644 --- a/lib/md004.js +++ b/lib/md004.js @@ -24,6 +24,7 @@ module.exports = { "names": [ "MD004", "ul-style" ], "description": "Unordered list style", "tags": [ "bullet", "ul" ], + "parser": "none", "function": function MD004(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = style; diff --git a/lib/md005.js b/lib/md005.js index 5bb56281..41ab8e14 100644 --- a/lib/md005.js +++ b/lib/md005.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD005", "list-indent" ], "description": "Inconsistent indentation for list items at the same level", "tags": [ "bullet", "ul", "indentation" ], + "parser": "micromark", "function": function MD005(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md007.js b/lib/md007.js index f4aef5f0..5d59afc4 100644 --- a/lib/md007.js +++ b/lib/md007.js @@ -21,6 +21,7 @@ module.exports = { "names": [ "MD007", "ul-indent" ], "description": "Unordered list indentation", "tags": [ "bullet", "ul", "indentation" ], + "parser": "micromark", "function": function MD007(params, onError) { const indent = Number(params.config.indent || 2); const startIndented = !!params.config.start_indented; diff --git a/lib/md009.js b/lib/md009.js index 0e36aa1f..4e182731 100644 --- a/lib/md009.js +++ b/lib/md009.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD009", "no-trailing-spaces" ], "description": "Trailing spaces", "tags": [ "whitespace" ], + "parser": "markdownit", "function": function MD009(params, onError) { let brSpaces = params.config.br_spaces; brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces); diff --git a/lib/md010.js b/lib/md010.js index d18f6760..49370ecf 100644 --- a/lib/md010.js +++ b/lib/md010.js @@ -14,6 +14,7 @@ module.exports = { "names": [ "MD010", "no-hard-tabs" ], "description": "Hard tabs", "tags": [ "whitespace", "hard_tab" ], + "parser": "markdownit", "function": function MD010(params, onError) { const codeBlocks = params.config.code_blocks; const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; diff --git a/lib/md011.js b/lib/md011.js index e3e67d3f..5485f166 100644 --- a/lib/md011.js +++ b/lib/md011.js @@ -14,6 +14,7 @@ module.exports = { "names": [ "MD011", "no-reversed-links" ], "description": "Reversed link syntax", "tags": [ "links" ], + "parser": "none", "function": function MD011(params, onError) { const exclusions = codeBlockAndSpanRanges(); forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => { diff --git a/lib/md012.js b/lib/md012.js index 7ab02bed..cc9cbb63 100644 --- a/lib/md012.js +++ b/lib/md012.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD012", "no-multiple-blanks" ], "description": "Multiple consecutive blank lines", "tags": [ "whitespace", "blank_lines" ], + "parser": "none", "function": function MD012(params, onError) { const maximum = Number(params.config.maximum || 1); let count = 0; diff --git a/lib/md013.js b/lib/md013.js index 5cc6e332..59c7170b 100644 --- a/lib/md013.js +++ b/lib/md013.js @@ -28,6 +28,7 @@ module.exports = { "names": [ "MD013", "line-length" ], "description": "Line length", "tags": [ "line_length" ], + "parser": "markdownit", "function": function MD013(params, onError) { const lineLength = Number(params.config.line_length || 80); const headingLineLength = diff --git a/lib/md014.js b/lib/md014.js index f92b9f67..094c3499 100644 --- a/lib/md014.js +++ b/lib/md014.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD014", "commands-show-output" ], "description": "Dollar signs used before commands without showing output", "tags": [ "code" ], + "parser": "markdownit", "function": function MD014(params, onError) { for (const type of [ "code_block", "fence" ]) { filterTokens(params, type, (token) => { diff --git a/lib/md018.js b/lib/md018.js index daa43c3b..007c227a 100644 --- a/lib/md018.js +++ b/lib/md018.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD018", "no-missing-space-atx" ], "description": "No space after hash on atx style heading", "tags": [ "headings", "atx", "spaces" ], + "parser": "none", "function": function MD018(params, onError) { forEachLine(lineMetadata(), (line, lineIndex, inCode) => { if (!inCode && diff --git a/lib/md019.js b/lib/md019.js index b70fa497..ea7c9ea7 100644 --- a/lib/md019.js +++ b/lib/md019.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD019", "no-multiple-space-atx" ], "description": "Multiple spaces after hash on atx style heading", "tags": [ "headings", "atx", "spaces" ], + "parser": "markdownit", "function": function MD019(params, onError) { filterTokens(params, "heading_open", (token) => { if (headingStyleFor(token) === "atx") { diff --git a/lib/md020.js b/lib/md020.js index 9cae6c40..18887027 100644 --- a/lib/md020.js +++ b/lib/md020.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD020", "no-missing-space-closed-atx" ], "description": "No space inside hashes on closed atx style heading", "tags": [ "headings", "atx_closed", "spaces" ], + "parser": "none", "function": function MD020(params, onError) { forEachLine(lineMetadata(), (line, lineIndex, inCode) => { if (!inCode) { diff --git a/lib/md021.js b/lib/md021.js index 8fb9ff3c..9871958e 100644 --- a/lib/md021.js +++ b/lib/md021.js @@ -13,6 +13,7 @@ module.exports = { "names": [ "MD021", "no-multiple-space-closed-atx" ], "description": "Multiple spaces inside hashes on closed atx style heading", "tags": [ "headings", "atx_closed", "spaces" ], + "parser": "markdownit", "function": function MD021(params, onError) { filterTokens(params, "heading_open", (token) => { if (headingStyleFor(token) === "atx_closed") { diff --git a/lib/md022.js b/lib/md022.js index a1cef0d5..fbb58d74 100644 --- a/lib/md022.js +++ b/lib/md022.js @@ -37,6 +37,7 @@ module.exports = { "names": [ "MD022", "blanks-around-headings" ], "description": "Headings should be surrounded by blank lines", "tags": [ "headings", "blank_lines" ], + "parser": "micromark", "function": function MD022(params, onError) { const getLinesAbove = getLinesFunction(params.config.lines_above); const getLinesBelow = getLinesFunction(params.config.lines_below); diff --git a/lib/md023.js b/lib/md023.js index 548f2a5b..d3644b37 100644 --- a/lib/md023.js +++ b/lib/md023.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD023", "heading-start-left" ], "description": "Headings must start at the beginning of the line", "tags": [ "headings", "spaces" ], + "parser": "markdownit", "function": function MD023(params, onError) { filterTokens(params, "heading_open", function forToken(token) { const { lineNumber, line } = token; diff --git a/lib/md024.js b/lib/md024.js index 108a7777..b876b4fe 100644 --- a/lib/md024.js +++ b/lib/md024.js @@ -10,6 +10,7 @@ module.exports = { "names": [ "MD024", "no-duplicate-heading" ], "description": "Multiple headings with the same content", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD024(params, onError) { const siblingsOnly = !!params.config.siblings_only || false; const knownContents = [ null, [] ]; diff --git a/lib/md025.js b/lib/md025.js index f211abda..7ab3898a 100644 --- a/lib/md025.js +++ b/lib/md025.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD025", "single-title", "single-h1" ], "description": "Multiple top-level headings in the same document", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD025(params, onError) { const level = Number(params.config.level || 1); const tag = "h" + level; diff --git a/lib/md026.js b/lib/md026.js index 915b4304..87b2b7c5 100644 --- a/lib/md026.js +++ b/lib/md026.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD026", "no-trailing-punctuation" ], "description": "Trailing punctuation in heading", "tags": [ "headings" ], + "parser": "micromark", "function": function MD026(params, onError) { let punctuation = params.config.punctuation; punctuation = String( diff --git a/lib/md027.js b/lib/md027.js index 4db14e80..4bf7d412 100644 --- a/lib/md027.js +++ b/lib/md027.js @@ -11,6 +11,7 @@ module.exports = { "names": ["MD027", "no-multiple-space-blockquote"], "description": "Multiple spaces after blockquote symbol", "tags": ["blockquote", "whitespace", "indentation"], + "parser": "micromark", "function": function MD027(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md028.js b/lib/md028.js index 4141d6d3..b310aa4b 100644 --- a/lib/md028.js +++ b/lib/md028.js @@ -13,6 +13,7 @@ module.exports = { "names": [ "MD028", "no-blanks-blockquote" ], "description": "Blank line inside blockquote", "tags": [ "blockquote", "whitespace" ], + "parser": "micromark", "function": function MD028(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md029.js b/lib/md029.js index 4b7fead4..5265fa9d 100644 --- a/lib/md029.js +++ b/lib/md029.js @@ -18,6 +18,7 @@ module.exports = { "names": [ "MD029", "ol-prefix" ], "description": "Ordered list item prefix", "tags": [ "ol" ], + "parser": "none", "function": function MD029(params, onError) { const style = String(params.config.style || "one_or_ordered"); const filteredLists = flattenedLists().filter((list) => !list.unordered); diff --git a/lib/md030.js b/lib/md030.js index ec4d2545..3fdf6375 100644 --- a/lib/md030.js +++ b/lib/md030.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD030", "list-marker-space" ], "description": "Spaces after list markers", "tags": [ "ol", "ul", "whitespace" ], + "parser": "micromark", "function": function MD030(params, onError) { const ulSingle = Number(params.config.ul_single || 1); const olSingle = Number(params.config.ol_single || 1); diff --git a/lib/md031.js b/lib/md031.js index 20ff561f..d0cc203f 100644 --- a/lib/md031.js +++ b/lib/md031.js @@ -13,6 +13,7 @@ module.exports = { "names": [ "MD031", "blanks-around-fences" ], "description": "Fenced code blocks should be surrounded by blank lines", "tags": [ "code", "blank_lines" ], + "parser": "none", "function": function MD031(params, onError) { const listItems = params.config.list_items; const includeListItems = (listItems === undefined) ? true : !!listItems; diff --git a/lib/md032.js b/lib/md032.js index a3a964ec..6e51d72c 100644 --- a/lib/md032.js +++ b/lib/md032.js @@ -41,6 +41,7 @@ module.exports = { "names": [ "MD032", "blanks-around-lists" ], "description": "Lists should be surrounded by blank lines", "tags": [ "bullet", "ul", "ol", "blank_lines" ], + "parser": "micromark", "function": function MD032(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md033.js b/lib/md033.js index 7bed0ca8..77725db9 100644 --- a/lib/md033.js +++ b/lib/md033.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD033", "no-inline-html" ], "description": "Inline HTML", "tags": [ "html" ], + "parser": "micromark", "function": function MD033(params, onError) { let allowedElements = params.config.allowed_elements; allowedElements = Array.isArray(allowedElements) ? allowedElements : []; diff --git a/lib/md034.js b/lib/md034.js index 56d04ed1..66cfde0f 100644 --- a/lib/md034.js +++ b/lib/md034.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD034", "no-bare-urls" ], "description": "Bare URL used", "tags": [ "links", "url" ], + "parser": "micromark", "function": function MD034(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md035.js b/lib/md035.js index 3d4c622e..e56602ca 100644 --- a/lib/md035.js +++ b/lib/md035.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD035", "hr-style" ], "description": "Horizontal rule style", "tags": [ "hr" ], + "parser": "micromark", "function": function MD035(params, onError) { let style = String(params.config.style || "consistent").trim(); // eslint-disable-next-line jsdoc/valid-types diff --git a/lib/md036.js b/lib/md036.js index d38380ee..ae3b4f10 100644 --- a/lib/md036.js +++ b/lib/md036.js @@ -10,6 +10,7 @@ module.exports = { "names": [ "MD036", "no-emphasis-as-heading" ], "description": "Emphasis used instead of a heading", "tags": [ "headings", "emphasis" ], + "parser": "markdownit", "function": function MD036(params, onError) { let punctuation = params.config.punctuation; punctuation = diff --git a/lib/md037.js b/lib/md037.js index 5fb0bf6b..8ddfbc97 100644 --- a/lib/md037.js +++ b/lib/md037.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD037", "no-space-in-emphasis" ], "description": "Spaces inside emphasis markers", "tags": [ "whitespace", "emphasis" ], + "parser": "micromark", "function": function MD037(params, onError) { // Initialize variables diff --git a/lib/md038.js b/lib/md038.js index 91fe4396..096291fa 100644 --- a/lib/md038.js +++ b/lib/md038.js @@ -25,6 +25,7 @@ module.exports = { "names": [ "MD038", "no-space-in-code" ], "description": "Spaces inside code span elements", "tags": [ "whitespace", "code" ], + "parser": "micromark", "function": function MD038(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md039.js b/lib/md039.js index cbf21177..676c603c 100644 --- a/lib/md039.js +++ b/lib/md039.js @@ -13,6 +13,7 @@ module.exports = { "names": [ "MD039", "no-space-in-links" ], "description": "Spaces inside link text", "tags": [ "whitespace", "links" ], + "parser": "markdownit", "function": function MD039(params, onError) { filterTokens(params, "inline", (token) => { const { children } = token; diff --git a/lib/md040.js b/lib/md040.js index b08e3712..a395ce97 100644 --- a/lib/md040.js +++ b/lib/md040.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD040", "fenced-code-language" ], "description": "Fenced code blocks should have a language specified", "tags": [ "code", "language" ], + "parser": "micromark", "function": function MD040(params, onError) { let allowed = params.config.allowed_languages; allowed = Array.isArray(allowed) ? allowed : []; diff --git a/lib/md041.js b/lib/md041.js index 7d91c3e2..143d7533 100644 --- a/lib/md041.js +++ b/lib/md041.js @@ -10,6 +10,7 @@ module.exports = { "names": [ "MD041", "first-line-heading", "first-line-h1" ], "description": "First line in a file should be a top-level heading", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD041(params, onError) { const level = Number(params.config.level || 1); const tag = "h" + level; diff --git a/lib/md042.js b/lib/md042.js index edf9ec41..bb94e776 100644 --- a/lib/md042.js +++ b/lib/md042.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD042", "no-empty-links" ], "description": "No empty links", "tags": [ "links" ], + "parser": "markdownit", "function": function MD042(params, onError) { filterTokens(params, "inline", function forToken(token) { let inLink = false; diff --git a/lib/md043.js b/lib/md043.js index 4d9ba429..9a8ef116 100644 --- a/lib/md043.js +++ b/lib/md043.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD043", "required-headings" ], "description": "Required heading structure", "tags": [ "headings" ], + "parser": "markdownit", "function": function MD043(params, onError) { const requiredHeadings = params.config.headings; if (!Array.isArray(requiredHeadings)) { diff --git a/lib/md044.js b/lib/md044.js index 9a604c2a..504938c7 100644 --- a/lib/md044.js +++ b/lib/md044.js @@ -17,6 +17,7 @@ module.exports = { "names": [ "MD044", "proper-names" ], "description": "Proper names should have the correct capitalization", "tags": [ "spelling" ], + "parser": "micromark", "function": function MD044(params, onError) { let names = params.config.names; names = Array.isArray(names) ? names : []; diff --git a/lib/md045.js b/lib/md045.js index b85d35fc..cc25d970 100644 --- a/lib/md045.js +++ b/lib/md045.js @@ -13,6 +13,7 @@ module.exports = { "names": [ "MD045", "no-alt-text" ], "description": "Images should have alternate text (alt text)", "tags": [ "accessibility", "images" ], + "parser": "micromark", "function": function MD045(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md046.js b/lib/md046.js index 99279bf1..f7c38f43 100644 --- a/lib/md046.js +++ b/lib/md046.js @@ -16,6 +16,7 @@ module.exports = { "names": [ "MD046", "code-block-style" ], "description": "Code block style", "tags": [ "code" ], + "parser": "micromark", "function": function MD046(params, onError) { let expectedStyle = String(params.config.style || "consistent"); // eslint-disable-next-line jsdoc/valid-types diff --git a/lib/md047.js b/lib/md047.js index 83cac2a4..85288821 100644 --- a/lib/md047.js +++ b/lib/md047.js @@ -10,6 +10,7 @@ module.exports = { "names": [ "MD047", "single-trailing-newline" ], "description": "Files should end with a single newline character", "tags": [ "blank_lines" ], + "parser": "none", "function": function MD047(params, onError) { const lastLineNumber = params.lines.length; const lastLine = params.lines[lastLineNumber - 1]; diff --git a/lib/md048.js b/lib/md048.js index eae8e997..685d7b3d 100644 --- a/lib/md048.js +++ b/lib/md048.js @@ -11,6 +11,7 @@ module.exports = { "names": [ "MD048", "code-fence-style" ], "description": "Code fence style", "tags": [ "code" ], + "parser": "micromark", "function": function MD048(params, onError) { const style = String(params.config.style || "consistent"); // eslint-disable-next-line jsdoc/valid-types diff --git a/lib/md049-md050.js b/lib/md049-md050.js index 90af193a..6b0729b1 100644 --- a/lib/md049-md050.js +++ b/lib/md049-md050.js @@ -75,6 +75,7 @@ module.exports = [ "names": [ "MD049", "emphasis-style" ], "description": "Emphasis style", "tags": [ "emphasis" ], + "parser": "micromark", "function": function MD049(params, onError) { return impl( params, @@ -91,6 +92,7 @@ module.exports = [ "names": [ "MD050", "strong-style" ], "description": "Strong style", "tags": [ "emphasis" ], + "parser": "micromark", "function": function MD050(params, onError) { return impl( params, diff --git a/lib/md051.js b/lib/md051.js index 70dacf37..74d81220 100644 --- a/lib/md051.js +++ b/lib/md051.js @@ -67,6 +67,7 @@ module.exports = { "names": [ "MD051", "link-fragments" ], "description": "Link fragments should be valid", "tags": [ "links" ], + "parser": "micromark", "function": function MD051(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/lib/md052.js b/lib/md052.js index a10c22ae..8cb2b308 100644 --- a/lib/md052.js +++ b/lib/md052.js @@ -12,6 +12,7 @@ module.exports = { "description": "Reference links and images should use a label that is defined", "tags": [ "images", "links" ], + "parser": "none", "function": function MD052(params, onError) { const { config, lines } = params; const shortcutSyntax = config.shortcut_syntax || false; diff --git a/lib/md053.js b/lib/md053.js index 74450396..4fbcc9a6 100644 --- a/lib/md053.js +++ b/lib/md053.js @@ -12,6 +12,7 @@ module.exports = { "names": [ "MD053", "link-image-reference-definitions" ], "description": "Link and image reference definitions should be needed", "tags": [ "images", "links" ], + "parser": "none", "function": function MD053(params, onError) { const ignored = new Set(params.config.ignored_definitions || [ "//" ]); const lines = params.lines; diff --git a/lib/md054.js b/lib/md054.js index fb7ed5ab..c4b39c77 100644 --- a/lib/md054.js +++ b/lib/md054.js @@ -27,6 +27,7 @@ module.exports = { "names": [ "MD054", "link-image-style" ], "description": "Link and image style", "tags": [ "images", "links" ], + "parser": "micromark", "function": (params, onError) => { const config = params.config; const autolink = (config.autolink === undefined) || !!config.autolink; diff --git a/lib/md055.js b/lib/md055.js index 02e8b301..b4746596 100644 --- a/lib/md055.js +++ b/lib/md055.js @@ -19,6 +19,7 @@ module.exports = { "names": [ "MD055", "table-pipe-style" ], "description": "Table pipe style", "tags": [ "table" ], + "parser": "micromark", "function": function MD055(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = style; diff --git a/lib/md056.js b/lib/md056.js index 75af2ecd..91877789 100644 --- a/lib/md056.js +++ b/lib/md056.js @@ -13,6 +13,7 @@ module.exports = { "names": [ "MD056", "table-column-count" ], "description": "Table column count", "tags": [ "table" ], + "parser": "micromark", "function": function MD056(params, onError) { // eslint-disable-next-line jsdoc/valid-types /** @type import("../helpers/micromark.cjs").Token[] */ diff --git a/package.json b/package.json index 6143b55d..ab9f9b3b 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "markdown-it-for-inline": "2.0.1", "markdown-it-sub": "2.0.0", "markdown-it-sup": "2.0.0", - "markdownlint-rule-helpers": "0.24.0", "npm-run-all": "4.1.5", "terser-webpack-plugin": "5.3.10", "toml": "3.0.0", diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index 18ab484d..becfe621 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -11,6 +11,8 @@ const { homepage, version } = require("../package.json"); test("customRulesV0", (t) => new Promise((resolve) => { t.plan(4); const customRulesMd = "./test/custom-rules.md"; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": customRules.all, "files": [ customRulesMd ], @@ -77,6 +79,8 @@ test("customRulesV0", (t) => new Promise((resolve) => { test("customRulesV1", (t) => new Promise((resolve) => { t.plan(3); const customRulesMd = "./test/custom-rules.md"; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": customRules.all, "files": [ customRulesMd ], @@ -195,6 +199,8 @@ test("customRulesV1", (t) => new Promise((resolve) => { test("customRulesV2", (t) => new Promise((resolve) => { t.plan(3); const customRulesMd = "./test/custom-rules.md"; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": customRules.all, "files": [ customRulesMd ], @@ -304,6 +310,8 @@ test("customRulesV2", (t) => new Promise((resolve) => { test("customRulesConfig", (t) => new Promise((resolve) => { t.plan(2); const customRulesMd = "./test/custom-rules.md"; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": customRules.all, "files": [ customRulesMd ], @@ -332,6 +340,8 @@ test("customRulesConfig", (t) => new Promise((resolve) => { test("customRulesNpmPackage", (t) => new Promise((resolve) => { t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ require("./rules/npm") ], "strings": { @@ -352,7 +362,7 @@ test("customRulesNpmPackage", (t) => new Promise((resolve) => { })); test("customRulesBadProperty", (t) => { - t.plan(27); + t.plan(30); for (const testCase of [ { "propertyName": "names", @@ -376,6 +386,11 @@ test("customRulesBadProperty", (t) => { "propertyValues": [ null, "string", [], [ null ], [ "" ], [ "string", 10 ] ] }, + { + "propertyName": "parser", + "propertyValues": + [ 10, "string", [] ] + }, { "propertyName": "function", "propertyValues": [ null, "string", [] ] @@ -385,6 +400,8 @@ test("customRulesBadProperty", (t) => { for (const propertyValue of propertyValues) { const badRule = { ...customRules.anyBlockquote }; badRule[propertyName] = propertyValue; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ badRule ] }; @@ -394,9 +411,9 @@ test("customRulesBadProperty", (t) => { }, { "message": - `Property '${propertyName}' of custom rule at index 0 is incorrect.` + `Property '${propertyName}' of custom rule at index 0 is incorrect: '${propertyValue}'.` }, - "Did not get correct exception for missing property." + `Did not get correct exception for property '${propertyName}' value '${propertyValue}'.` ); } } @@ -405,11 +422,14 @@ test("customRulesBadProperty", (t) => { test("customRulesUsedNameName", (t) => new Promise((resolve) => { t.plan(4); markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name", "NO-missing-SPACE-atx" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function noop() {} } ] @@ -429,11 +449,14 @@ test("customRulesUsedNameName", (t) => new Promise((resolve) => { test("customRulesUsedNameTag", (t) => new Promise((resolve) => { t.plan(4); markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name", "HtMl" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function noop() {} } ] @@ -452,17 +475,21 @@ test("customRulesUsedNameTag", (t) => new Promise((resolve) => { test("customRulesUsedTagName", (t) => new Promise((resolve) => { t.plan(4); markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "filler" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function noop() {} }, { "names": [ "name" ], "description": "description", "tags": [ "tag", "NO-missing-SPACE-atx" ], + "parser": "none", "function": function noop() {} } ] @@ -479,8 +506,147 @@ test("customRulesUsedTagName", (t) => new Promise((resolve) => { }); })); +test("customRulesParserUndefined", (t) => { + t.plan(5); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ + const options = { + "customRules": [ + // @ts-ignore + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": + (params) => { + t.true(Object.keys(params).includes("tokens")); + t.is(Object.keys(params.parsers).length, 1); + t.truthy(params.parsers.markdownit); + t.is(Object.keys(params.parsers.markdownit).length, 1); + t.truthy(params.parsers.markdownit.tokens); + } + } + ], + "strings": { + "string": "# Heading\n" + } + }; + return markdownlint.promises.markdownlint(options).then(() => null); +}); + +test("customRulesParserNone", (t) => { + t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ + const options = { + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "parser": "none", + "function": + (params) => { + t.false(Object.keys(params).includes("tokens")); + t.is(Object.keys(params.parsers).length, 0); + } + } + ], + "strings": { + "string": "# Heading\n" + } + }; + return markdownlint.promises.markdownlint(options).then(() => null); +}); + +test("customRulesParserMarkdownIt", (t) => { + t.plan(5); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ + const options = { + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "parser": "markdownit", + "function": + (params) => { + t.false(Object.keys(params).includes("tokens")); + t.is(Object.keys(params.parsers).length, 1); + t.truthy(params.parsers.markdownit); + t.is(Object.keys(params.parsers.markdownit).length, 1); + t.truthy(params.parsers.markdownit.tokens); + } + } + ], + "strings": { + "string": "# Heading\n" + } + }; + return markdownlint.promises.markdownlint(options).then(() => null); +}); + +test("customRulesParserMicromark", (t) => { + t.plan(1); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ + const options = { + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "parser": "micromark", + "function": + () => { + // t.false(Object.keys(params).includes("tokens")); + // t.is(Object.keys(params.parsers).length, 1); + // t.truthy(params.parsers.micromark); + // t.is(Object.keys(params.parsers.micromark).length, 1); + // t.truthy(params.parsers.micromark.tokens); + } + } + ], + "strings": { + "string": "# Heading\n" + } + }; + return markdownlint.promises.markdownlint(options).catch((error) => { + // parser "micromark" currently unsupported for custom rules + t.is(error.message, "Property 'parser' of custom rule at index 0 is incorrect: 'micromark'."); + }); +}); + +test("customRulesParamsTokensSameObject", (t) => { + t.plan(1); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ + const options = { + "customRules": [ + // @ts-ignore + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": + (params) => { + // @ts-ignore + t.is(params.tokens, params.parsers.markdownit.tokens); + } + } + ], + "strings": { + "string": "# Heading\n" + } + }; + return markdownlint.promises.markdownlint(options).then(() => null); +}); + test("customRulesDefinitionStatic", (t) => new Promise((resolve) => { t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { @@ -488,7 +654,9 @@ test("customRulesDefinitionStatic", (t) => new Promise((resolve) => { "description": "description", "information": new URL("https://example.com/information"), "tags": [ "tag" ], + "parser": "none", "function": (params, onError) => { + // @ts-ignore const definition = options.customRules[0]; definition.names[0] = "changed"; definition.description = "changed"; @@ -528,11 +696,14 @@ test("customRulesThrowForFile", (t) => new Promise((resolve) => { t.plan(4); const exceptionMessage = "Test exception message"; markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function throws() { throw new Error(exceptionMessage); } @@ -556,11 +727,14 @@ test("customRulesThrowForFileSync", (t) => { t.throws( function customRuleThrowsCall() { markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function throws() { throw new Error(exceptionMessage); } @@ -580,11 +754,14 @@ test("customRulesThrowForString", (t) => new Promise((resolve) => { t.plan(4); const exceptionMessage = "Test exception message"; markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function throws() { throw new Error(exceptionMessage); } @@ -610,11 +787,14 @@ test("customRulesThrowForStringSync", (t) => { t.throws( function customRuleThrowsCall() { markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function throws() { throw new Error(exceptionMessage); } @@ -635,11 +815,14 @@ test("customRulesThrowForStringSync", (t) => { test("customRulesOnErrorNull", (t) => new Promise((resolve) => { t.plan(4); markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorNull(params, onError) { // @ts-ignore onError(null); @@ -666,13 +849,17 @@ test("customRulesOnErrorNull", (t) => new Promise((resolve) => { test("customRulesOnErrorNullSync", (t) => { t.plan(1); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorNull(params, onError) { + // @ts-ignore onError(null); } } @@ -754,12 +941,15 @@ test("customRulesOnErrorBad", (t) => { badObject[propertyName] = propertyValue; propertyNames = propertyName; } + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorBad(params, onError) { onError(badObject); } @@ -824,12 +1014,15 @@ test("customRulesOnErrorInvalid", (t) => { badObject[propertyName] = propertyValue; propertyNames = propertyName; } + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorInvalid(params, onError) { onError(badObject); } @@ -897,12 +1090,15 @@ test("customRulesOnErrorValid", (t) => { } else { goodObject[propertyName] = propertyValue; } + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorValid(params, onError) { onError(goodObject); } @@ -920,12 +1116,15 @@ test("customRulesOnErrorValid", (t) => { test("customRulesOnErrorLazy", (t) => new Promise((resolve) => { t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorLazy(params, onError) { onError({ "lineNumber": 1, @@ -975,12 +1174,15 @@ test("customRulesOnErrorModified", (t) => new Promise((resolve) => { "insertText": "text" } }; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorModified(params, onError) { onError(errorObject); errorObject.lineNumber = 2; @@ -1026,11 +1228,14 @@ test("customRulesOnErrorModified", (t) => new Promise((resolve) => { test("customRulesOnErrorInvalidHandled", (t) => new Promise((resolve) => { t.plan(2); markdownlint({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorInvalid(params, onError) { onError({ "lineNumber": 13 @@ -1067,11 +1272,14 @@ test("customRulesOnErrorInvalidHandled", (t) => new Promise((resolve) => { test("customRulesOnErrorInvalidHandledSync", (t) => { t.plan(1); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function onErrorInvalid(params, onError) { onError({ "lineNumber": 13, @@ -1105,12 +1313,15 @@ test("customRulesOnErrorInvalidHandledSync", (t) => { test("customRulesFileName", (t) => new Promise((resolve) => { t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function stringName(params) { t.is(params.name, "doc/CustomRules.md", "Incorrect file name"); } @@ -1126,12 +1337,15 @@ test("customRulesFileName", (t) => new Promise((resolve) => { test("customRulesStringName", (t) => new Promise((resolve) => { t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": function stringName(params) { t.is(params.name, "string", "Incorrect string name"); } @@ -1150,11 +1364,14 @@ test("customRulesStringName", (t) => new Promise((resolve) => { test("customRulesOnErrorInformationNotRuleNotError", (t) => { t.plan(1); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": (params, onError) => { onError({ "lineNumber": 1 @@ -1172,12 +1389,15 @@ test("customRulesOnErrorInformationNotRuleNotError", (t) => { test("customRulesOnErrorInformationRuleNotError", (t) => { t.plan(1); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], "information": new URL("https://example.com/rule"), + "parser": "none", "function": (params, onError) => { onError({ "lineNumber": 1 @@ -1199,11 +1419,14 @@ test("customRulesOnErrorInformationRuleNotError", (t) => { test("customRulesOnErrorInformationNotRuleError", (t) => { t.plan(1); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": (params, onError) => { onError({ "lineNumber": 1, @@ -1226,12 +1449,15 @@ test("customRulesOnErrorInformationNotRuleError", (t) => { test("customRulesOnErrorInformationRuleError", (t) => { t.plan(1); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], "information": new URL("https://example.com/rule"), + "parser": "none", "function": (params, onError) => { onError({ "lineNumber": 1, @@ -1254,12 +1480,15 @@ test("customRulesOnErrorInformationRuleError", (t) => { test("customRulesOnErrorInformationRuleErrorUndefined", (t) => { t.plan(1); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], "information": new URL("https://example.com/rule"), + "parser": "none", "function": (params, onError) => { onError({ "lineNumber": 1, @@ -1282,12 +1511,15 @@ test("customRulesOnErrorInformationRuleErrorUndefined", (t) => { test("customRulesOnErrorInformationRuleErrorMultiple", (t) => { t.plan(6); const actualResult = markdownlint.sync({ + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], "information": new URL("https://example.com/rule"), + "parser": "none", "function": (params, onError) => { onError({ "lineNumber": 1, @@ -1356,6 +1588,8 @@ test("customRulesDoc", (t) => new Promise((resolve) => { test("customRulesLintJavaScript", (t) => new Promise((resolve) => { t.plan(2); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": customRules.lintJavaScript, "files": "test/lint-javascript.md" @@ -1393,6 +1627,8 @@ test("customRulesLintJavaScript", (t) => new Promise((resolve) => { test("customRulesValidateJson", (t) => new Promise((resolve) => { t.plan(3); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": customRules.validateJson, "files": "test/validate-json.md" @@ -1425,12 +1661,15 @@ test("customRulesValidateJson", (t) => new Promise((resolve) => { test("customRulesAsyncThrowsInSyncContext", (t) => { t.plan(1); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name1", "name2" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "asynchronous": true, "function": () => {} } @@ -1449,32 +1688,16 @@ test("customRulesAsyncThrowsInSyncContext", (t) => { ); }); -test("customRulesParamsTokensSameObject", (t) => { - t.plan(1); - const options = { - "customRules": [ - { - "names": [ "name" ], - "description": "description", - "tags": [ "tag" ], - "function": - (params) => { - t.is(params.tokens, params.parsers.markdownit.tokens); - } - } - ], - "files": [ "README.md" ] - }; - return markdownlint.promises.markdownlint(options).then(() => null); -}); - test("customRulesParamsAreFrozen", (t) => { + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "function": (params) => { const pending = [ params ]; @@ -1504,6 +1727,8 @@ test("customRulesParamsAreStable", (t) => { t.plan(4); const config1 = { "value1": 10 }; const config2 = { "value2": 20 }; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "config": { "MD010": true, @@ -1518,18 +1743,20 @@ test("customRulesParamsAreStable", (t) => { "description": "description1", "tags": [ "tag" ], "asynchronous": true, + "parser": "none", "function": (params) => { + const { config } = params; t.deepEqual( - params.config, + config, config1, - `Unexpected config in sync path: ${params.config}.` + `Unexpected config in sync path: ${config}.` ); return Promise.resolve().then(() => { t.deepEqual( - params.config, + config, config1, - `Unexpected config in async path: ${params.config}.` + `Unexpected config in async path: ${config}.` ); }); } @@ -1539,6 +1766,7 @@ test("customRulesParamsAreStable", (t) => { "description": "description2", "tags": [ "tag" ], "asynchronous": true, + "parser": "none", "function": (params) => { const { config } = params; @@ -1566,6 +1794,8 @@ test("customRulesParamsAreStable", (t) => { test("customRulesAsyncReadFiles", (t) => { t.plan(3); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { @@ -1574,6 +1804,7 @@ test("customRulesAsyncReadFiles", (t) => { "information": new URL("https://example.com/asyncRule1"), "tags": [ "tag" ], "asynchronous": true, + "parser": "none", "function": (params, onError) => fs.readFile(__filename, "utf8").then( (content) => { @@ -1592,6 +1823,7 @@ test("customRulesAsyncReadFiles", (t) => { "description": "description2", "tags": [ "tag" ], "asynchronous": true, + "parser": "none", "function": async(params, onError) => { const content = await fs.readFile(__filename, "utf8"); @@ -1651,6 +1883,8 @@ test("customRulesAsyncReadFiles", (t) => { test("customRulesAsyncIgnoresSyncReturn", (t) => { t.plan(1); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { @@ -1659,6 +1893,7 @@ test("customRulesAsyncIgnoresSyncReturn", (t) => { "information": new URL("https://example.com/asyncRule"), "tags": [ "tag" ], "asynchronous": false, + "parser": "none", "function": () => new Promise(() => { // Never resolves }) @@ -1669,6 +1904,7 @@ test("customRulesAsyncIgnoresSyncReturn", (t) => { "information": new URL("https://example.com/asyncRule"), "tags": [ "tag" ], "asynchronous": true, + "parser": "none", "function": (params, onError) => new Promise((resolve) => { onError({ "lineNumber": 1 }); resolve(null); @@ -1739,11 +1975,15 @@ for (const flavor of [ ] ]) { const [ name, func ] = flavor; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule[] */ const customRule = [ { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", + // @ts-ignore "function": func } ]; @@ -1873,11 +2113,15 @@ for (const flavor of [ ] ]) { const [ name, func ] = flavor; + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Rule */ const customRule = { "names": [ "name" ], "description": "description", "tags": [ "tag" ], + "parser": "none", "asynchronous": true, + // @ts-ignore "function": func }; for (const inputs of stringScenarios) { diff --git a/test/markdownlint-test-helpers.js b/test/markdownlint-test-helpers.js index 6162c0e9..6cc792eb 100644 --- a/test/markdownlint-test-helpers.js +++ b/test/markdownlint-test-helpers.js @@ -928,16 +928,19 @@ test("expandTildePath", (t) => { test("getReferenceLinkImageData().shortcuts", (t) => { t.plan(1); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "no-shortcut-links" ], "description": "-", "tags": [ "-" ], + "parser": "none", "function": - (params) => { - const { shortcuts } = - helpers.getReferenceLinkImageData(params); + () => { + const { referenceLinkImageData } = require("../lib/cache"); + const { shortcuts } = referenceLinkImageData(); t.is(shortcuts.size, 0, [ ...shortcuts.keys() ].join(", ")); } } diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 9dbb3608..159d4b6d 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -1059,12 +1059,12 @@ test("someCustomRulesHaveValidUrl", (t) => { (Object.getPrototypeOf(rule.information) === URL.prototype)); if (rule === customRules.anyBlockquote) { t.is( - rule.information.href, + rule.information?.href, `${homepage}/blob/main/test/rules/any-blockquote.js` ); } else if (rule === customRules.lettersEX) { t.is( - rule.information.href, + rule.information?.href, `${homepage}/blob/main/test/rules/letters-E-X.js` ); } @@ -1142,12 +1142,15 @@ Text with: [^footnote] test("token-map-spans", (t) => { t.plan(38); + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../lib/markdownlint").Options */ const options = { "customRules": [ { "names": [ "token-map-spans" ], "description": "token-map-spans", "tags": [ "tms" ], + "parser": "markdownit", "function": function tokenMapSpans(params) { const tokenLines = []; let lastLineNumber = -1; diff --git a/test/rules/any-blockquote.js b/test/rules/any-blockquote.js index 67af8e31..59372328 100644 --- a/test/rules/any-blockquote.js +++ b/test/rules/any-blockquote.js @@ -2,7 +2,7 @@ "use strict"; -const { filterTokens } = require("markdownlint-rule-helpers"); +const { filterTokens } = require("../../helpers"); /** @typedef {import("../../lib/markdownlint").MarkdownItToken} MarkdownItToken */ /** @typedef {(MarkdownItToken) => void} FilterTokensCallback */ @@ -16,6 +16,7 @@ module.exports = { "/blob/main/test/rules/any-blockquote.js" ), "tags": [ "test" ], + "parser": "markdownit", "function": (params, onError) => { filterTokens( params, diff --git a/test/rules/every-n-lines.js b/test/rules/every-n-lines.js index abbc8f1c..17114f09 100644 --- a/test/rules/every-n-lines.js +++ b/test/rules/every-n-lines.js @@ -2,13 +2,14 @@ "use strict"; -const { forEachLine, getLineMetadata } = require("markdownlint-rule-helpers"); +const { forEachLine, getLineMetadata } = require("../../helpers"); /** @type import("../../lib/markdownlint").Rule */ module.exports = { "names": [ "every-n-lines" ], "description": "Rule that reports an error every N lines", "tags": [ "test" ], + "parser": "markdownit", "function": (params, onError) => { const n = params.config.n || 2; forEachLine(getLineMetadata(params), (line, lineIndex) => { diff --git a/test/rules/first-line.js b/test/rules/first-line.js index 4d7580c9..e80e2aa6 100644 --- a/test/rules/first-line.js +++ b/test/rules/first-line.js @@ -7,6 +7,7 @@ module.exports = { "names": [ "first-line" ], "description": "Rule that reports an error for the first line", "tags": [ "test" ], + "parser": "none", "function": function rule(params, onError) { // Unconditionally report an error for line 1 onError({ diff --git a/test/rules/letters-E-X.js b/test/rules/letters-E-X.js index f6de35a1..d6b6b339 100644 --- a/test/rules/letters-E-X.js +++ b/test/rules/letters-E-X.js @@ -11,6 +11,7 @@ module.exports = { "/blob/main/test/rules/letters-E-X.js" ), "tags": [ "test" ], + "parser": "markdownit", "function": (params, onError) => { for (const inline of params.parsers.markdownit.tokens.filter( (token) => token.type === "inline" diff --git a/test/rules/lint-javascript.js b/test/rules/lint-javascript.js index 5d96a05b..88b054b2 100644 --- a/test/rules/lint-javascript.js +++ b/test/rules/lint-javascript.js @@ -2,7 +2,7 @@ "use strict"; -const { filterTokens } = require("markdownlint-rule-helpers"); +const { filterTokens } = require("../../helpers"); const eslint = require("eslint"); const eslintInstance = new eslint.ESLint(); const linter = new eslint.Linter(); @@ -29,6 +29,7 @@ module.exports = { "names": [ "lint-javascript" ], "description": "Rule that lints JavaScript code", "tags": [ "test", "lint", "javascript" ], + "parser": "markdownit", "asynchronous": true, "function": (params, onError) => { filterTokens(params, "fence", (fence) => { diff --git a/test/rules/npm/sample-rule.js b/test/rules/npm/sample-rule.js index 691f6d49..56681ddd 100644 --- a/test/rules/npm/sample-rule.js +++ b/test/rules/npm/sample-rule.js @@ -7,6 +7,7 @@ module.exports = { "names": [ "sample-rule" ], "description": "Sample rule", "tags": [ "sample" ], + "parser": "markdownit", "function": function rule(params, onError) { for (const token of params.parsers.markdownit.tokens) { if (token.type === "hr") { diff --git a/test/rules/validate-json.js b/test/rules/validate-json.js index 6db0575e..b0feac8b 100644 --- a/test/rules/validate-json.js +++ b/test/rules/validate-json.js @@ -2,7 +2,7 @@ "use strict"; -const { filterTokens } = require("markdownlint-rule-helpers"); +const { filterTokens } = require("../../helpers"); const { parse, printParseErrorCode } = require("jsonc-parser"); /** @type import("../../lib/markdownlint").Rule */ @@ -10,6 +10,7 @@ module.exports = { "names": [ "validate-json" ], "description": "Rule that validates JSON code", "tags": [ "test", "validate", "json" ], + "parser": "markdownit", "asynchronous": true, "function": (params, onError) => { filterTokens(params, "fence", (fence) => {