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

118 lines
4.7 KiB
JavaScript

/**
* @fileoverview Rule to prevent non-complaint link references.
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------
import { illegalShorthandTailPattern } from "../util.js";
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/**
* @import { Position } from "unist";
* @import { Text } from "mdast";
* @import { MarkdownRuleDefinition } from "../types.js";
* @import { MarkdownSourceCode } from "../language/markdown-source-code.js";
* @typedef {"invalidLabelRef"} NoInvalidLabelRefsMessageIds
* @typedef {[]} NoInvalidLabelRefsOptions
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoInvalidLabelRefsOptions, MessageIds: NoInvalidLabelRefsMessageIds }>} NoInvalidLabelRefsRuleDefinition
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/** matches i.e., `[foo][bar]` */
const labelPattern = /\]\[([^\]]+)\]/u;
/**
* Finds missing references in a node.
* @param {Text} node The node to check.
* @param {MarkdownSourceCode} sourceCode The Markdown source code object.
* @returns {Array<{label:string,position:Position}>} The missing references.
*/
function findInvalidLabelReferences(node, sourceCode) {
const nodeText = sourceCode.getText(node);
const docText = sourceCode.text;
const invalid = [];
let startIndex = 0;
/*
* This loop works by searching the string inside the node for the next
* label reference. If it finds one, it checks to see if there is any
* white space between the [ and ]. If there is, it reports an error.
* It then moves the start index to the end of the label reference and
* continues searching the text until the end of the text is found.
*/
while (startIndex < nodeText.length) {
const value = nodeText.slice(startIndex);
const match = value.match(labelPattern);
if (!match) {
break;
}
if (!illegalShorthandTailPattern.test(match[0])) {
startIndex += match.index + match[0].length;
continue;
}
/*
* Adjust `labelPattern` match index to the full source code.
*/
const startOffset = startIndex + match.index + node.position.start.offset;
const endOffset = startOffset + match[0].length;
/*
* Search the entire document text to find the preceding open bracket.
*/
const lastOpenBracketIndex = docText.lastIndexOf("[", startOffset);
if (lastOpenBracketIndex === -1) {
startIndex += match.index + match[0].length;
continue;
}
/*
* Note: `label` can contain leading and trailing newlines, so we need to
* take that into account when calculating the line and column offsets.
*/
const label = docText
.slice(lastOpenBracketIndex, endOffset)
.match(/!?\[([^\]]+)\]/u)[1];
invalid.push({
label: label.trim(),
position: {
start: sourceCode.getLocFromIndex(startOffset + 1),
end: sourceCode.getLocFromIndex(endOffset),
},
});
startIndex += match.index + match[0].length;
}
return invalid;
}
//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------
export default /** @satisfies {NoInvalidLabelRefsRuleDefinition} */ ({
meta: {
type: "problem",
docs: {
recommended: true,
description: "Disallow invalid label references",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-invalid-label-refs.md",
},
messages: {
invalidLabelRef: "Label reference '{{label}}' is invalid due to white space between [ and ].",
},
},
create(context) {
const { sourceCode } = context;
return {
text(node) {
const invalidReferences = findInvalidLabelReferences(node, sourceCode);
for (const invalidReference of invalidReferences) {
context.report({
loc: invalidReference.position,
messageId: "invalidLabelRef",
data: {
label: invalidReference.label,
},
});
}
},
};
},
});