Reimplement MD043/required-headings using micromark tokens.

This commit is contained in:
David Anson 2024-06-22 14:37:32 -07:00
parent 1513e3803b
commit ec957029a5
2 changed files with 46 additions and 28 deletions

View file

@ -5878,8 +5878,8 @@ module.exports = {
const { addErrorContext, addErrorDetailIf, forEachHeading } = const { addErrorContext, addErrorDetailIf } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js");
__webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); const { filterByTypes, getHeadingLevel, getHeadingText } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs");
// eslint-disable-next-line jsdoc/valid-types // eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */ /** @type import("./markdownlint").Rule */
@ -5887,7 +5887,7 @@ module.exports = {
"names": [ "MD043", "required-headings" ], "names": [ "MD043", "required-headings" ],
"description": "Required heading structure", "description": "Required heading structure",
"tags": [ "headings" ], "tags": [ "headings" ],
"parser": "markdownit", "parser": "micromark",
"function": function MD043(params, onError) { "function": function MD043(params, onError) {
const requiredHeadings = params.config.headings; const requiredHeadings = params.config.headings;
if (!Array.isArray(requiredHeadings)) { if (!Array.isArray(requiredHeadings)) {
@ -5895,20 +5895,22 @@ module.exports = {
return; return;
} }
const matchCase = params.config.match_case || false; const matchCase = params.config.match_case || false;
const levels = {};
for (const level of [ 1, 2, 3, 4, 5, 6 ]) {
levels["h" + level] = "######".substr(-level);
}
let i = 0; let i = 0;
let matchAny = false; let matchAny = false;
let hasError = false; let hasError = false;
let anyHeadings = false; let anyHeadings = false;
const getExpected = () => requiredHeadings[i++] || "[None]"; const getExpected = () => requiredHeadings[i++] || "[None]";
const handleCase = (str) => (matchCase ? str : str.toLowerCase()); const handleCase = (str) => (matchCase ? str : str.toLowerCase());
forEachHeading(params, (heading, content) => { const headings = filterByTypes(
params.parsers.micromark.tokens,
[ "atxHeading", "setextHeading" ]
);
for (const heading of headings) {
if (!hasError) { if (!hasError) {
const headingText = getHeadingText(heading);
const headingLevel = getHeadingLevel(heading);
anyHeadings = true; anyHeadings = true;
const actual = levels[heading.tag] + " " + content; const actual = `${"".padEnd(headingLevel, "#")} ${headingText}`;
const expected = getExpected(); const expected = getExpected();
if (expected === "*") { if (expected === "*") {
const nextExpected = getExpected(); const nextExpected = getExpected();
@ -5923,12 +5925,16 @@ module.exports = {
} else if (matchAny) { } else if (matchAny) {
i--; i--;
} else { } else {
addErrorDetailIf(onError, heading.lineNumber, addErrorDetailIf(
expected, actual); onError,
heading.startLine,
expected,
actual
);
hasError = true; hasError = true;
} }
} }
}); }
const extraHeadings = requiredHeadings.length - i; const extraHeadings = requiredHeadings.length - i;
if ( if (
!hasError && !hasError &&
@ -5936,8 +5942,11 @@ module.exports = {
((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) && ((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) &&
(anyHeadings || !requiredHeadings.every((heading) => heading === "*")) (anyHeadings || !requiredHeadings.every((heading) => heading === "*"))
) { ) {
addErrorContext(onError, params.lines.length, addErrorContext(
requiredHeadings[i]); onError,
params.lines.length,
requiredHeadings[i]
);
} }
} }
}; };

View file

@ -2,8 +2,8 @@
"use strict"; "use strict";
const { addErrorContext, addErrorDetailIf, forEachHeading } = const { addErrorContext, addErrorDetailIf } = require("../helpers");
require("../helpers"); const { filterByTypes, getHeadingLevel, getHeadingText } = require("../helpers/micromark.cjs");
// eslint-disable-next-line jsdoc/valid-types // eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */ /** @type import("./markdownlint").Rule */
@ -11,7 +11,7 @@ module.exports = {
"names": [ "MD043", "required-headings" ], "names": [ "MD043", "required-headings" ],
"description": "Required heading structure", "description": "Required heading structure",
"tags": [ "headings" ], "tags": [ "headings" ],
"parser": "markdownit", "parser": "micromark",
"function": function MD043(params, onError) { "function": function MD043(params, onError) {
const requiredHeadings = params.config.headings; const requiredHeadings = params.config.headings;
if (!Array.isArray(requiredHeadings)) { if (!Array.isArray(requiredHeadings)) {
@ -19,20 +19,22 @@ module.exports = {
return; return;
} }
const matchCase = params.config.match_case || false; const matchCase = params.config.match_case || false;
const levels = {};
for (const level of [ 1, 2, 3, 4, 5, 6 ]) {
levels["h" + level] = "######".substr(-level);
}
let i = 0; let i = 0;
let matchAny = false; let matchAny = false;
let hasError = false; let hasError = false;
let anyHeadings = false; let anyHeadings = false;
const getExpected = () => requiredHeadings[i++] || "[None]"; const getExpected = () => requiredHeadings[i++] || "[None]";
const handleCase = (str) => (matchCase ? str : str.toLowerCase()); const handleCase = (str) => (matchCase ? str : str.toLowerCase());
forEachHeading(params, (heading, content) => { const headings = filterByTypes(
params.parsers.micromark.tokens,
[ "atxHeading", "setextHeading" ]
);
for (const heading of headings) {
if (!hasError) { if (!hasError) {
const headingText = getHeadingText(heading);
const headingLevel = getHeadingLevel(heading);
anyHeadings = true; anyHeadings = true;
const actual = levels[heading.tag] + " " + content; const actual = `${"".padEnd(headingLevel, "#")} ${headingText}`;
const expected = getExpected(); const expected = getExpected();
if (expected === "*") { if (expected === "*") {
const nextExpected = getExpected(); const nextExpected = getExpected();
@ -47,12 +49,16 @@ module.exports = {
} else if (matchAny) { } else if (matchAny) {
i--; i--;
} else { } else {
addErrorDetailIf(onError, heading.lineNumber, addErrorDetailIf(
expected, actual); onError,
heading.startLine,
expected,
actual
);
hasError = true; hasError = true;
} }
} }
}); }
const extraHeadings = requiredHeadings.length - i; const extraHeadings = requiredHeadings.length - i;
if ( if (
!hasError && !hasError &&
@ -60,8 +66,11 @@ module.exports = {
((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) && ((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) &&
(anyHeadings || !requiredHeadings.every((heading) => heading === "*")) (anyHeadings || !requiredHeadings.every((heading) => heading === "*"))
) { ) {
addErrorContext(onError, params.lines.length, addErrorContext(
requiredHeadings[i]); onError,
params.lines.length,
requiredHeadings[i]
);
} }
} }
}; };