/** * @fileoverview Rule to prevent spaces around emphasis markers in Markdown. * @author Pixel998 */ //----------------------------------------------------------------------------- // Type Definitions //----------------------------------------------------------------------------- /** * @import { SourceRange } from "@eslint/core"; * @import { Heading, Paragraph, TableCell, Text } from "mdast"; * @import { MarkdownRuleDefinition } from "../types.js"; * @typedef {"spaceInEmphasis"} NoSpaceInEmphasisMessageIds * @typedef {[{ checkStrikethrough?: boolean }]} NoSpaceInEmphasisOptions * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoSpaceInEmphasisOptions, MessageIds: NoSpaceInEmphasisMessageIds }>} NoSpaceInEmphasisRuleDefinition */ //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- const whitespacePattern = /[ \t]/u; /** * Creates a marker pattern based on whether strikethrough should be included. * @param {boolean} checkStrikethrough Whether to include strikethrough markers. * @returns {RegExp} The marker pattern. */ function createMarkerPattern(checkStrikethrough) { return checkStrikethrough ? /(?<=(? text"( /** @type {Text} */ node) { const [startOffset, endOffset] = sourceCode.getRange(node); const parentNodeStartOffset = // Parent node can be `Heading`, `Paragraph`, or `TableCell`. sourceCode.getParent(node).position.start.offset; // Add the content of a `Text` node into the current buffer at the correct offsets. for (let i = startOffset; i < endOffset; i++) { buffer[i - parentNodeStartOffset] = sourceCode.text[i]; } }, ":matches(heading, paragraph, tableCell):exit"( /** @type {Heading | Paragraph | TableCell} */ node) { const maskedText = buffer.join(""); /** @type {Map} */ const markerGroups = new Map(); /** @type {RegExpExecArray | null} */ let match; while ((match = markerPattern.exec(maskedText)) !== null) { const marker = match[0]; const startOffset = // Adjust `markerPattern` match index to the full source code. match.index + node.position.start.offset; const endOffset = startOffset + marker.length; if (!markerGroups.has(marker)) { markerGroups.set(marker, []); } markerGroups.get(marker).push([startOffset, endOffset]); } for (const group of markerGroups.values()) { for (let i = 0; i < group.length - 1; i += 2) { const startMarker = group[i]; reportWhitespace(startMarker[1], startMarker[0], startMarker[1] + 2); const endMarker = group[i + 1]; reportWhitespace(endMarker[0] - 1, endMarker[0] - 2, endMarker[1]); } } }, }; }, });