first commit

This commit is contained in:
2026-08-06 12:00:39 +10:00
commit 91221f5a8e
3259 changed files with 569069 additions and 0 deletions
@@ -0,0 +1,85 @@
/**
* @fileoverview Rule to prevent reversed link and image syntax in Markdown.
* @author xbinaryx
*/
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/**
* @import { Heading, Paragraph, TableCell, Html, Image, ImageReference, InlineCode, LinkReference } from "mdast";
* @import { InlineMath } from "mdast-util-math";
* @import { MarkdownRuleDefinition } from "../types.js";
* @typedef {"reversedSyntax"} NoReversedMediaSyntaxMessageIds
* @typedef {[]} NoReversedMediaSyntaxOptions
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReversedMediaSyntaxOptions, MessageIds: NoReversedMediaSyntaxMessageIds }>} NoReversedMediaSyntaxRuleDefinition
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/** Matches reversed link/image syntax like `(text)[url]`, ignoring escaped characters like `\(text\)[url]`. */
const reversedPattern = /(?<=(?<!\\)(?:\\{2})*)\((?<label>(?:\\.|[^()\\]|\([\s\S]*\))*)\)\[(?<url>(?:\\.|[^\]\\\r\n])*)\](?!\()/gu;
//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------
export default /** @satisfies {NoReversedMediaSyntaxRuleDefinition} */ ({
meta: {
type: "problem",
docs: {
recommended: true,
description: "Disallow reversed link and image syntax",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-reversed-media-syntax.md",
},
fixable: "code",
messages: {
reversedSyntax: "Unexpected reversed syntax found. Use [label](URL) syntax instead.",
},
},
create(context) {
const { sourceCode } = context;
/** @type {string[]} */
let buffer;
/** @type {number} */
let nodeStartOffset;
return {
"heading, paragraph, tableCell"(
/** @type {Heading | Paragraph | TableCell} */ node) {
// Use UTF-16 code units so the buffer stays aligned with source offsets.
buffer = sourceCode.getText(node).split("");
// Store the start offset of the node for later calculations.
nodeStartOffset = node.position.start.offset;
},
":matches(heading, paragraph, tableCell) :matches(html, image, imageReference, inlineCode, linkReference, inlineMath)"(
/** @type {Html | Image | ImageReference | InlineCode | LinkReference | InlineMath} */ node) {
const [startOffset, endOffset] = sourceCode.getRange(node);
// Mask the content of `html`, `image`, `imageReference`, `inlineCode`, `linkReference`, and `inlineMath` nodes with whitespaces.
for (let i = startOffset; i < endOffset; i++) {
buffer[i - nodeStartOffset] = " ";
}
},
":matches(heading, paragraph, tableCell):exit"() {
const maskedText = buffer.join("");
/** @type {RegExpExecArray | null} */
let match;
while ((match = reversedPattern.exec(maskedText)) !== null) {
const { label, url } = match.groups;
const startOffset = match.index + nodeStartOffset; // Adjust `reversedPattern` match index to the full source code.
const endOffset = startOffset + match[0].length;
const labelStartOffset = startOffset + 1; // Skip "("
const labelEndOffset = labelStartOffset + label.length;
const urlStartOffset = labelEndOffset + 2; // Skip ")["
const urlEndOffset = urlStartOffset + url.length;
context.report({
loc: {
start: sourceCode.getLocFromIndex(startOffset),
end: sourceCode.getLocFromIndex(endOffset),
},
messageId: "reversedSyntax",
fix(fixer) {
return fixer.replaceTextRange([startOffset, endOffset], `[${sourceCode.text.slice(labelStartOffset, labelEndOffset)}](${sourceCode.text.slice(urlStartOffset, urlEndOffset)})`);
},
});
}
},
};
},
});