/** * @fileoverview Rule to prevent empty definitions in Markdown. * @author Pixel998 */ //----------------------------------------------------------------------------- // Imports //----------------------------------------------------------------------------- import { normalizeIdentifier } from "micromark-util-normalize-identifier"; import { htmlCommentPattern } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions //----------------------------------------------------------------------------- /** * @import { MarkdownRuleDefinition } from "../types.js"; * @typedef {"emptyDefinition" | "emptyFootnoteDefinition"} NoEmptyDefinitionsMessageIds * @typedef {[{ allowDefinitions?: string[], allowFootnoteDefinitions?: string[], checkFootnoteDefinitions?: boolean }]} NoEmptyDefinitionsOptions * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoEmptyDefinitionsOptions, MessageIds: NoEmptyDefinitionsMessageIds }>} NoEmptyDefinitionsRuleDefinition */ //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- /** * Checks if a string contains only HTML comments. * @param {string} value The input string to check. * @returns {boolean} True if the string contains only HTML comments, false otherwise. */ function isOnlyComments(value) { const withoutComments = value.replace(htmlCommentPattern, ""); return withoutComments.trim().length === 0; } //----------------------------------------------------------------------------- // Rule Definition //----------------------------------------------------------------------------- export default /** @satisfies {NoEmptyDefinitionsRuleDefinition} */ ({ meta: { type: "problem", docs: { recommended: true, description: "Disallow empty definitions", url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-empty-definitions.md", }, messages: { emptyDefinition: "Unexpected empty definition `{{ identifier }}` (label: `{{ label }}`) found.", emptyFootnoteDefinition: "Unexpected empty footnote definition `{{ identifier }}` (label: `{{ label }}`) found.", }, schema: [ { type: "object", properties: { allowDefinitions: { type: "array", items: { type: "string", }, uniqueItems: true, }, allowFootnoteDefinitions: { type: "array", items: { type: "string", }, uniqueItems: true, }, checkFootnoteDefinitions: { type: "boolean", }, }, additionalProperties: false, }, ], defaultOptions: [ { allowDefinitions: ["//"], allowFootnoteDefinitions: [], checkFootnoteDefinitions: true, }, ], }, create(context) { const allowDefinitions = new Set(context.options[0].allowDefinitions.map(identifier => normalizeIdentifier(identifier).toLowerCase())); const allowFootnoteDefinitions = new Set(context.options[0].allowFootnoteDefinitions.map(identifier => normalizeIdentifier(identifier).toLowerCase())); const [{ checkFootnoteDefinitions }] = context.options; return { definition(node) { if ((!node.url || node.url === "#") && !allowDefinitions.has(node.identifier)) { context.report({ loc: node.position, messageId: "emptyDefinition", data: { identifier: node.identifier, label: node.label.trim(), }, }); } }, footnoteDefinition(node) { if (checkFootnoteDefinitions && !allowFootnoteDefinitions.has(node.identifier) && (node.children.length === 0 || node.children.every(child => child.type === "html" && isOnlyComments(child.value)))) { context.report({ loc: node.position, messageId: "emptyFootnoteDefinition", data: { identifier: node.identifier, label: node.label, }, }); } }, }; }, });