Files
LearnNode/node-course/node_modules/@eslint/markdown/dist/rules/no-duplicate-headings.js
T
2026-08-06 12:00:39 +10:00

102 lines
3.8 KiB
JavaScript

/**
* @fileoverview Rule to prevent duplicate headings in Markdown.
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/**
* @import { MarkdownRuleDefinition } from "../types.js";
* @typedef {"duplicateHeading"} NoDuplicateHeadingsMessageIds
* @typedef {[{ checkSiblingsOnly?: boolean }]} NoDuplicateHeadingsOptions
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoDuplicateHeadingsOptions, MessageIds: NoDuplicateHeadingsMessageIds }>} NoDuplicateHeadingsRuleDefinition
*/
//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------
export default /** @satisfies {NoDuplicateHeadingsRuleDefinition} */ ({
meta: {
type: "problem",
docs: {
description: "Disallow duplicate headings in the same document",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-duplicate-headings.md",
},
messages: {
duplicateHeading: 'Duplicate heading "{{text}}" found.',
},
schema: [
{
type: "object",
properties: {
checkSiblingsOnly: {
type: "boolean",
},
},
additionalProperties: false,
},
],
defaultOptions: [{ checkSiblingsOnly: false }],
},
create(context) {
const [{ checkSiblingsOnly }] = context.options;
/** @type {Map<number, Set<string>>} */
const headingsByLevel = checkSiblingsOnly
? new Map([
[1, new Set()],
[2, new Set()],
[3, new Set()],
[4, new Set()],
[5, new Set()],
[6, new Set()],
])
: new Map([[1, new Set()]]);
let lastLevel = 1;
let currentLevelHeadings = headingsByLevel.get(lastLevel);
/** @type {string} */
let headingChildrenSequence;
/** @type {string} */
let headingText;
return {
heading(node) {
headingChildrenSequence = "";
headingText = "";
if (checkSiblingsOnly) {
const currentLevel = node.depth;
if (currentLevel < lastLevel) {
for (let level = lastLevel; level > currentLevel; level--) {
headingsByLevel.get(level).clear();
}
}
lastLevel = currentLevel;
currentLevelHeadings = headingsByLevel.get(currentLevel);
}
},
"heading *"({ type, value }) {
if (value) {
headingChildrenSequence += `[${type},${value}]`; // We use a custom sequence representation to keep track of heading children.
if (type !== "html") {
headingText += value;
}
}
else {
headingChildrenSequence += `[${type}]`;
}
},
"heading:exit"(node) {
if (currentLevelHeadings.has(headingChildrenSequence)) {
context.report({
loc: node.position,
messageId: "duplicateHeading",
data: {
text: headingText,
},
});
}
else {
currentLevelHeadings.add(headingChildrenSequence);
}
},
};
},
});