@zinkawaii/eslint-plugin-markdown 0.0.0 → 8.0.3-patch.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +3 -0
- package/dist/index.d.mts +165 -0
- package/dist/index.mjs +93 -0
- package/dist/rules-CjI0B_8H.mjs +2357 -0
- package/package.json +40 -1
|
@@ -0,0 +1,2357 @@
|
|
|
1
|
+
import { createParser } from "satorigear";
|
|
2
|
+
import { ConfigCommentParser, Directive, TextSourceCodeBase, VisitNodeStep } from "@eslint/plugin-kit";
|
|
3
|
+
import { normalizeIdentifier } from "micromark-util-normalize-identifier";
|
|
4
|
+
import GithubSlugger from "github-slugger";
|
|
5
|
+
// vendor/src/processor.js
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview Processes Markdown files for consumption by ESLint.
|
|
8
|
+
* @author Brandon Mills
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @import { LintMessage, RuleTextEdit, SourceRange } from "@eslint/core";
|
|
12
|
+
* @import { Node, Parent, Code, Html } from "mdast";
|
|
13
|
+
* @import { Block, RangeMap } from "./types.js";
|
|
14
|
+
*/
|
|
15
|
+
const UNSATISFIABLE_RULES = /* @__PURE__ */ new Set(["eol-last", "unicode-bom"]);
|
|
16
|
+
const SUPPORTS_AUTOFIX = true;
|
|
17
|
+
const BOM = "";
|
|
18
|
+
/**
|
|
19
|
+
* @type {Map<string, Block[]>}
|
|
20
|
+
*/
|
|
21
|
+
const blocksCache = /* @__PURE__ */ new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Performs a depth-first traversal of the Markdown AST.
|
|
24
|
+
* @param {Node} node A Markdown AST node.
|
|
25
|
+
* @param {{[key: string]: (() => void) | ((node: Code) => void) | ((node: Html) => void)}} callbacks A map of node types to callbacks.
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
function traverse(node, callbacks) {
|
|
29
|
+
if (callbacks[node.type])
|
|
30
|
+
/** @type {(node: Node) => void} */ callbacks[node.type](node);
|
|
31
|
+
else
|
|
32
|
+
/** @type {() => void} */ callbacks["*"]();
|
|
33
|
+
const parent = node;
|
|
34
|
+
if (typeof parent.children !== "undefined") for (let i = 0; i < parent.children.length; i++) traverse(parent.children[i], callbacks);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extracts `eslint-*` or `global` comments from HTML comments if present.
|
|
38
|
+
* @param {string} html The text content of an HTML AST node.
|
|
39
|
+
* @returns {string} The comment's text without the opening and closing tags or
|
|
40
|
+
* an empty string if the text is not an ESLint HTML comment.
|
|
41
|
+
*/
|
|
42
|
+
function getComment(html) {
|
|
43
|
+
const commentStart = "<!--";
|
|
44
|
+
const commentEnd = "-->";
|
|
45
|
+
const regex = /^(?:eslint\b|global\s)/u;
|
|
46
|
+
if (html.slice(0, 4) !== commentStart || html.slice(-3) !== commentEnd) return "";
|
|
47
|
+
const comment = html.slice(4, -3);
|
|
48
|
+
if (!regex.test(comment.trim())) return "";
|
|
49
|
+
return comment;
|
|
50
|
+
}
|
|
51
|
+
const leadingWhitespaceRegex = /^[>\s]*/u;
|
|
52
|
+
/**
|
|
53
|
+
* Gets the offset for the first column of the node's first line in the
|
|
54
|
+
* original source text.
|
|
55
|
+
* @param {Code} node A Markdown code block AST node.
|
|
56
|
+
* @returns {number} The offset for the first column of the node's first line.
|
|
57
|
+
*/
|
|
58
|
+
function getBeginningOfLineOffset(node) {
|
|
59
|
+
return node.position.start.offset - node.position.start.column + 1;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Gets the leading text, typically whitespace with possible blockquote chars,
|
|
63
|
+
* used to indent a code block.
|
|
64
|
+
* @param {string} text The text of the file.
|
|
65
|
+
* @param {Code} node A Markdown code block AST node.
|
|
66
|
+
* @returns {string} The text from the start of the first line to the opening
|
|
67
|
+
* fence of the code block.
|
|
68
|
+
*/
|
|
69
|
+
function getIndentText(text, node) {
|
|
70
|
+
return leadingWhitespaceRegex.exec(text.slice(getBeginningOfLineOffset(node)))[0];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* When applying fixes, the postprocess step needs to know how to map fix ranges
|
|
74
|
+
* from their location in the linted JS to the original offset in the Markdown.
|
|
75
|
+
* Configuration comments and indentation trimming both complicate this process.
|
|
76
|
+
*
|
|
77
|
+
* Configuration comments appear in the linted JS but not in the Markdown code
|
|
78
|
+
* block. Fixes to configuration comments would cause undefined behavior and
|
|
79
|
+
* should be ignored during postprocessing. Fixes to actual code after
|
|
80
|
+
* configuration comments need to be mapped back to the code block after
|
|
81
|
+
* removing any offset due to configuration comments.
|
|
82
|
+
*
|
|
83
|
+
* Fenced code blocks can be indented by up to three spaces at the opening
|
|
84
|
+
* fence. Inside of a list, for example, this indent can be in addition to the
|
|
85
|
+
* indent already required for list item children. Leading whitespace inside
|
|
86
|
+
* indented code blocks is trimmed up to the level of the opening fence and does
|
|
87
|
+
* not appear in the linted code. Further, lines can have less leading
|
|
88
|
+
* whitespace than the opening fence, so not all lines are guaranteed to have
|
|
89
|
+
* the same column offset as the opening fence.
|
|
90
|
+
*
|
|
91
|
+
* The source code of a non-configuration-comment line in the linted JS is a
|
|
92
|
+
* suffix of the corresponding line in the Markdown code block. There are no
|
|
93
|
+
* differences within the line, so the mapping need only provide the offset
|
|
94
|
+
* delta at the beginning of each line.
|
|
95
|
+
* @param {string} text The text of the file.
|
|
96
|
+
* @param {Code} node A Markdown code block AST node.
|
|
97
|
+
* @param {string[]} comments List of configuration comment strings that will be
|
|
98
|
+
* inserted at the beginning of the code block.
|
|
99
|
+
* @returns {RangeMap[]} A list of offset-based adjustments, where lookups are
|
|
100
|
+
* done based on the `js` key, which represents the range in the linted JS,
|
|
101
|
+
* and the `md` key is the offset delta that, when added to the JS range,
|
|
102
|
+
* returns the corresponding location in the original Markdown source.
|
|
103
|
+
*/
|
|
104
|
+
function getBlockRangeMap(text, node, comments) {
|
|
105
|
+
const startOffset = getBeginningOfLineOffset(node);
|
|
106
|
+
const lines = text.slice(startOffset, node.position.end.offset).split("\n");
|
|
107
|
+
const baseIndent = getIndentText(text, node).length;
|
|
108
|
+
const commentLength = comments.reduce((len, comment) => len + comment.length + 1, 0);
|
|
109
|
+
const rangeMap = [{
|
|
110
|
+
indent: baseIndent,
|
|
111
|
+
js: 0,
|
|
112
|
+
md: 0
|
|
113
|
+
}];
|
|
114
|
+
let jsOffset = commentLength;
|
|
115
|
+
let mdOffset = startOffset + lines[0].length + 1;
|
|
116
|
+
for (let i = 0; i + 1 < lines.length; i++) {
|
|
117
|
+
const line = lines[i + 1];
|
|
118
|
+
const leadingWhitespaceLength = leadingWhitespaceRegex.exec(line)[0].length;
|
|
119
|
+
const trimLength = Math.min(baseIndent, leadingWhitespaceLength);
|
|
120
|
+
rangeMap.push({
|
|
121
|
+
indent: trimLength,
|
|
122
|
+
js: jsOffset,
|
|
123
|
+
md: mdOffset + trimLength - jsOffset
|
|
124
|
+
});
|
|
125
|
+
mdOffset += line.length + 1;
|
|
126
|
+
jsOffset += line.length - trimLength + 1;
|
|
127
|
+
}
|
|
128
|
+
return rangeMap;
|
|
129
|
+
}
|
|
130
|
+
const codeBlockFileNameRegex = /filename=(?<quote>["'])(?<filename>.*?)\1/u;
|
|
131
|
+
/**
|
|
132
|
+
* Parses the file name from a block meta, if available.
|
|
133
|
+
* @param {Block} block A code block.
|
|
134
|
+
* @returns {string | null | undefined} The filename, if parsed from block meta.
|
|
135
|
+
*/
|
|
136
|
+
function fileNameFromMeta(block) {
|
|
137
|
+
return block.meta?.match(codeBlockFileNameRegex)?.groups.filename.replaceAll(/\s+/gu, "_");
|
|
138
|
+
}
|
|
139
|
+
const languageToFileExtension = {
|
|
140
|
+
javascript: "js",
|
|
141
|
+
ecmascript: "js",
|
|
142
|
+
typescript: "ts",
|
|
143
|
+
markdown: "md"
|
|
144
|
+
};
|
|
145
|
+
const defaultParser = createParser();
|
|
146
|
+
/**
|
|
147
|
+
* Extracts lintable code blocks from Markdown text.
|
|
148
|
+
* @param {string} sourceText The text of the file.
|
|
149
|
+
* @param {string} filename The filename of the file.
|
|
150
|
+
* @returns {Array<{ filename: string, text: string }>} Source code blocks to lint.
|
|
151
|
+
*/
|
|
152
|
+
function preprocess(sourceText, filename) {
|
|
153
|
+
const text = sourceText.startsWith(BOM) ? sourceText.slice(1) : sourceText;
|
|
154
|
+
const ast = defaultParser.parse(text);
|
|
155
|
+
/** @type {Block[]} */
|
|
156
|
+
const blocks = [];
|
|
157
|
+
blocksCache.set(filename, blocks);
|
|
158
|
+
/**
|
|
159
|
+
* During the depth-first traversal, keep track of any sequences of HTML
|
|
160
|
+
* comment nodes containing `eslint-*` or `global` comments. If a code
|
|
161
|
+
* block immediately follows such a sequence, insert the comments at the
|
|
162
|
+
* top of the code block. Any non-ESLint comment or other node type breaks
|
|
163
|
+
* and empties the sequence.
|
|
164
|
+
* @type {string[]}
|
|
165
|
+
*/
|
|
166
|
+
let htmlComments = [];
|
|
167
|
+
traverse(ast, {
|
|
168
|
+
"*"() {
|
|
169
|
+
htmlComments = [];
|
|
170
|
+
},
|
|
171
|
+
/**
|
|
172
|
+
* Visit a code node.
|
|
173
|
+
* @param {Code} node The visited node.
|
|
174
|
+
* @returns {void}
|
|
175
|
+
*/
|
|
176
|
+
code(node) {
|
|
177
|
+
if (node.lang) {
|
|
178
|
+
/** @type {string[]} */
|
|
179
|
+
const comments = [];
|
|
180
|
+
for (const comment of htmlComments) {
|
|
181
|
+
if (comment.trim() === "eslint-skip") {
|
|
182
|
+
htmlComments = [];
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
comments.push(`/*${comment}*/`);
|
|
186
|
+
}
|
|
187
|
+
htmlComments = [];
|
|
188
|
+
blocks.push({
|
|
189
|
+
...node,
|
|
190
|
+
baseIndentText: getIndentText(text, node),
|
|
191
|
+
comments,
|
|
192
|
+
rangeMap: getBlockRangeMap(text, node, comments)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
/**
|
|
197
|
+
* Visit an HTML node.
|
|
198
|
+
* @param {Html} node The visited node.
|
|
199
|
+
* @returns {void}
|
|
200
|
+
*/
|
|
201
|
+
html(node) {
|
|
202
|
+
const comment = getComment(node.value);
|
|
203
|
+
if (comment) htmlComments.push(comment);
|
|
204
|
+
else htmlComments = [];
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return blocks.map((block, index) => {
|
|
208
|
+
const [language] = block.lang.trim().split(" ");
|
|
209
|
+
const fileExtension = Object.hasOwn(languageToFileExtension, language) ? languageToFileExtension[language] : language;
|
|
210
|
+
return {
|
|
211
|
+
filename: fileNameFromMeta(block) ?? `${index}.${fileExtension}`,
|
|
212
|
+
text: [
|
|
213
|
+
...block.comments,
|
|
214
|
+
block.value,
|
|
215
|
+
""
|
|
216
|
+
].join("\n")
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Adjusts a fix in a code block.
|
|
222
|
+
* @param {Block} block A code block.
|
|
223
|
+
* @param {RuleTextEdit} fix A fix to adjust.
|
|
224
|
+
* @returns {RuleTextEdit} The fix with adjusted ranges.
|
|
225
|
+
*/
|
|
226
|
+
function adjustFix(block, fix) {
|
|
227
|
+
return {
|
|
228
|
+
range: fix.range.map((range) => {
|
|
229
|
+
let i = 1;
|
|
230
|
+
while (i < block.rangeMap.length && block.rangeMap[i].js <= range) i++;
|
|
231
|
+
return range + block.rangeMap[i - 1].md;
|
|
232
|
+
}),
|
|
233
|
+
text: fix.text.replace(/\n/gu, `\n${block.baseIndentText}`)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Creates a map function that adjusts messages in a code block.
|
|
238
|
+
* @param {Block} block A code block.
|
|
239
|
+
* @returns {(message: LintMessage) => LintMessage | null} A function that adjusts messages in a code block.
|
|
240
|
+
*/
|
|
241
|
+
function adjustBlock(block) {
|
|
242
|
+
const leadingCommentLines = block.comments.reduce((count, comment) => count + comment.split("\n").length, 0);
|
|
243
|
+
const blockStart = block.position.start.line;
|
|
244
|
+
/**
|
|
245
|
+
* Adjusts ESLint messages to point to the correct location in the Markdown.
|
|
246
|
+
* @param {LintMessage} message A message from ESLint.
|
|
247
|
+
* @returns {LintMessage} The same message, but adjusted to the correct location.
|
|
248
|
+
*/
|
|
249
|
+
return function adjustMessage(message) {
|
|
250
|
+
if (!Number.isInteger(message.line)) return {
|
|
251
|
+
...message,
|
|
252
|
+
line: blockStart,
|
|
253
|
+
column: block.position.start.column
|
|
254
|
+
};
|
|
255
|
+
const lineInCode = message.line - leadingCommentLines;
|
|
256
|
+
if (lineInCode < 1 || lineInCode >= block.rangeMap.length) return null;
|
|
257
|
+
/** @type {Pick<LintMessage, "line" | "column" | "endLine" | "suggestions">} */
|
|
258
|
+
const out = {
|
|
259
|
+
line: lineInCode + blockStart,
|
|
260
|
+
column: message.column + block.rangeMap[lineInCode].indent
|
|
261
|
+
};
|
|
262
|
+
if (Number.isInteger(message.endLine)) out.endLine = message.endLine - leadingCommentLines + blockStart;
|
|
263
|
+
if (Array.isArray(message.suggestions)) out.suggestions = message.suggestions.map((suggestion) => ({
|
|
264
|
+
...suggestion,
|
|
265
|
+
fix: adjustFix(block, suggestion.fix)
|
|
266
|
+
}));
|
|
267
|
+
const adjustedFix = {};
|
|
268
|
+
if (message.fix) adjustedFix.fix = adjustFix(block, message.fix);
|
|
269
|
+
return {
|
|
270
|
+
...message,
|
|
271
|
+
...out,
|
|
272
|
+
...adjustedFix
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Excludes unsatisfiable rules from the list of messages.
|
|
278
|
+
* @param {LintMessage} message A message from the linter.
|
|
279
|
+
* @returns {boolean} True if the message should be included in output.
|
|
280
|
+
*/
|
|
281
|
+
function excludeUnsatisfiableRules(message) {
|
|
282
|
+
return message && !UNSATISFIABLE_RULES.has(message.ruleId);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Transforms generated messages for output.
|
|
286
|
+
* @param {Array<LintMessage[]>} messages An array containing one array of messages
|
|
287
|
+
* for each code block returned from `preprocess`.
|
|
288
|
+
* @param {string} filename The filename of the file
|
|
289
|
+
* @returns {LintMessage[]} A flattened array of messages with mapped locations.
|
|
290
|
+
*/
|
|
291
|
+
function postprocess(messages, filename) {
|
|
292
|
+
const blocks = blocksCache.get(filename);
|
|
293
|
+
blocksCache.delete(filename);
|
|
294
|
+
return messages.flatMap((group, i) => {
|
|
295
|
+
const adjust = adjustBlock(blocks[i]);
|
|
296
|
+
return group.map(adjust).filter(excludeUnsatisfiableRules);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const processor = {
|
|
300
|
+
meta: {
|
|
301
|
+
name: "@eslint/markdown/markdown",
|
|
302
|
+
version: "8.0.3"
|
|
303
|
+
},
|
|
304
|
+
preprocess,
|
|
305
|
+
postprocess,
|
|
306
|
+
supportsAutofix: SUPPORTS_AUTOFIX
|
|
307
|
+
};
|
|
308
|
+
// vendor/src/util.js
|
|
309
|
+
/**
|
|
310
|
+
* @fileoverview Utility Library
|
|
311
|
+
* @author Nicholas C. Zakas
|
|
312
|
+
*/
|
|
313
|
+
/**
|
|
314
|
+
* Line ending pattern to match all line endings (CRLF, CR, LF). (CommonMark spec)
|
|
315
|
+
* @see https://spec.commonmark.org/0.31.2/#line-ending
|
|
316
|
+
*/
|
|
317
|
+
const lineEndingPattern = /\r\n|[\r\n]/u;
|
|
318
|
+
/**
|
|
319
|
+
* CommonMark does not allow any white space between the brackets in a reference link.
|
|
320
|
+
* If that pattern is detected, then it's treated as text and not as a link. This pattern
|
|
321
|
+
* is used to detect that situation.
|
|
322
|
+
*/
|
|
323
|
+
const illegalShorthandTailPattern = /\]\[\s+\]$/u;
|
|
324
|
+
/**
|
|
325
|
+
* Regular expression to match HTML comments, including multiline comments.
|
|
326
|
+
*/
|
|
327
|
+
const htmlCommentPattern = /<!--[\s\S]*?-->/gu;
|
|
328
|
+
/**
|
|
329
|
+
* Checks if a frontmatter block contains a title matching the given pattern.
|
|
330
|
+
* @param {string} value The frontmatter content.
|
|
331
|
+
* @param {RegExp|null} pattern The pattern to match against.
|
|
332
|
+
* @returns {boolean} Whether a title was found.
|
|
333
|
+
*/
|
|
334
|
+
function frontmatterHasTitle(value, pattern) {
|
|
335
|
+
if (!pattern) return false;
|
|
336
|
+
const lines = value.split(lineEndingPattern);
|
|
337
|
+
for (const line of lines) if (pattern.test(line)) return true;
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Normalizes a Markdown reference identifier.
|
|
342
|
+
* @param {string} identifier The identifier to normalize.
|
|
343
|
+
* @returns {string} The normalized lowercase identifier.
|
|
344
|
+
*/
|
|
345
|
+
function normalizeIdentifier$1(identifier) {
|
|
346
|
+
return normalizeIdentifier(identifier).toLowerCase();
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Replaces all HTML comments with whitespace.
|
|
350
|
+
* This preserves offsets and locations of characters
|
|
351
|
+
* outside HTML comments by keeping line breaks and replacing
|
|
352
|
+
* other code units with a space character.
|
|
353
|
+
* @param {string} value The string to remove HTML comments from.
|
|
354
|
+
* @returns {string} The string with HTML comments removed.
|
|
355
|
+
*/
|
|
356
|
+
function stripHtmlComments(value) {
|
|
357
|
+
return value.replace(htmlCommentPattern, (match) => match.replace(/[^\r\n]/g, " "));
|
|
358
|
+
}
|
|
359
|
+
// vendor/src/language/markdown-source-code.js
|
|
360
|
+
/**
|
|
361
|
+
* @fileoverview The MarkdownSourceCode class.
|
|
362
|
+
* @author Nicholas C. Zakas
|
|
363
|
+
*/
|
|
364
|
+
/**
|
|
365
|
+
* @import { Position } from "unist";
|
|
366
|
+
* @import { Parent, Root, Node, Html } from "mdast";
|
|
367
|
+
* @import { TraversalStep, FileProblem, DirectiveType, RulesConfig } from "@eslint/core";
|
|
368
|
+
* @import { MarkdownLanguageOptions } from "../types.js";
|
|
369
|
+
*/
|
|
370
|
+
const commentParser = new ConfigCommentParser();
|
|
371
|
+
const configCommentStart = /<!--\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|-->)/u;
|
|
372
|
+
const htmlComment = /<!--(.*?)-->/gsu;
|
|
373
|
+
/**
|
|
374
|
+
* Represents an inline config comment in the source code.
|
|
375
|
+
*/
|
|
376
|
+
var InlineConfigComment = class {
|
|
377
|
+
/**
|
|
378
|
+
* The comment text.
|
|
379
|
+
* @type {string}
|
|
380
|
+
*/
|
|
381
|
+
value;
|
|
382
|
+
/**
|
|
383
|
+
* The position of the comment in the source code.
|
|
384
|
+
* @type {Position}
|
|
385
|
+
*/
|
|
386
|
+
position;
|
|
387
|
+
/**
|
|
388
|
+
* Creates a new instance.
|
|
389
|
+
* @param {Object} options The options for the instance.
|
|
390
|
+
* @param {string} options.value The comment text.
|
|
391
|
+
* @param {Position} options.position The position of the comment in the source code.
|
|
392
|
+
*/
|
|
393
|
+
constructor({ value, position }) {
|
|
394
|
+
this.value = value.trim();
|
|
395
|
+
this.position = position;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Extracts inline configuration comments from an HTML node.
|
|
400
|
+
* @param {Html} node The HTML node to extract comments from.
|
|
401
|
+
* @param {MarkdownSourceCode} sourceCode The Markdown source code object.
|
|
402
|
+
* @returns {Array<InlineConfigComment>} The inline configuration comments found in the node.
|
|
403
|
+
*/
|
|
404
|
+
function extractInlineConfigCommentsFromHTML(node, sourceCode) {
|
|
405
|
+
if (!configCommentStart.test(node.value)) return [];
|
|
406
|
+
/** @type {Array<InlineConfigComment>} */
|
|
407
|
+
const comments = [];
|
|
408
|
+
/** @type {RegExpExecArray | null} */
|
|
409
|
+
let match;
|
|
410
|
+
while (match = htmlComment.exec(node.value)) if (configCommentStart.test(match[0])) {
|
|
411
|
+
const startOffset = match.index + node.position.start.offset;
|
|
412
|
+
const endOffset = startOffset + match[0].length;
|
|
413
|
+
comments.push(new InlineConfigComment({
|
|
414
|
+
value: match[1].trim(),
|
|
415
|
+
position: {
|
|
416
|
+
start: {
|
|
417
|
+
...sourceCode.getLocFromIndex(startOffset),
|
|
418
|
+
offset: startOffset
|
|
419
|
+
},
|
|
420
|
+
end: {
|
|
421
|
+
...sourceCode.getLocFromIndex(endOffset),
|
|
422
|
+
offset: endOffset
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
427
|
+
return comments;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Markdown Source Code Object
|
|
431
|
+
* @extends {TextSourceCodeBase<{LangOptions: MarkdownLanguageOptions, RootNode: Root, SyntaxElementWithLoc: Node, ConfigNode: { value: string; position: Position }}>}
|
|
432
|
+
*/
|
|
433
|
+
var MarkdownSourceCode = class extends TextSourceCodeBase {
|
|
434
|
+
/**
|
|
435
|
+
* Cached traversal steps.
|
|
436
|
+
* @type {Array<VisitNodeStep>|undefined}
|
|
437
|
+
*/
|
|
438
|
+
#steps;
|
|
439
|
+
/**
|
|
440
|
+
* Cache of parent nodes.
|
|
441
|
+
* @type {WeakMap<Node, Parent|undefined>}
|
|
442
|
+
*/
|
|
443
|
+
#parents = /* @__PURE__ */ new WeakMap();
|
|
444
|
+
/**
|
|
445
|
+
* Collection of HTML nodes. Used to find directive comments.
|
|
446
|
+
* @type {Array<Html>}
|
|
447
|
+
*/
|
|
448
|
+
#htmlNodes = [];
|
|
449
|
+
/**
|
|
450
|
+
* Collection of inline configuration comments.
|
|
451
|
+
* @type {Array<InlineConfigComment>}
|
|
452
|
+
*/
|
|
453
|
+
#inlineConfigComments;
|
|
454
|
+
/**
|
|
455
|
+
* The AST of the source code.
|
|
456
|
+
* @type {Root}
|
|
457
|
+
*/
|
|
458
|
+
ast = void 0;
|
|
459
|
+
/**
|
|
460
|
+
* Creates a new instance.
|
|
461
|
+
* @param {Object} options The options for the instance.
|
|
462
|
+
* @param {string} options.text The source code text.
|
|
463
|
+
* @param {Root} options.ast The root AST node.
|
|
464
|
+
*/
|
|
465
|
+
constructor({ text, ast }) {
|
|
466
|
+
super({
|
|
467
|
+
ast,
|
|
468
|
+
text,
|
|
469
|
+
lineEndingPattern
|
|
470
|
+
});
|
|
471
|
+
this.ast = ast;
|
|
472
|
+
this.traverse();
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Returns the parent of the given node.
|
|
476
|
+
* @param {Node} node The node to get the parent of.
|
|
477
|
+
* @returns {Parent|undefined} The parent of the node.
|
|
478
|
+
*/
|
|
479
|
+
getParent(node) {
|
|
480
|
+
return this.#parents.get(node);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Returns an array of all inline configuration nodes found in the
|
|
484
|
+
* source code.
|
|
485
|
+
* @returns {Array<InlineConfigComment>} An array of all inline configuration nodes.
|
|
486
|
+
*/
|
|
487
|
+
getInlineConfigNodes() {
|
|
488
|
+
if (!this.#inlineConfigComments) this.#inlineConfigComments = this.#htmlNodes.flatMap((htmlNode) => extractInlineConfigCommentsFromHTML(htmlNode, this));
|
|
489
|
+
return this.#inlineConfigComments;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Returns an all directive nodes that enable or disable rules along with any problems
|
|
493
|
+
* encountered while parsing the directives.
|
|
494
|
+
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
|
|
495
|
+
* that ESLint needs to further process the directives.
|
|
496
|
+
*/
|
|
497
|
+
getDisableDirectives() {
|
|
498
|
+
/** @type {Array<FileProblem>} */
|
|
499
|
+
const problems = [];
|
|
500
|
+
/** @type {Array<Directive>} */
|
|
501
|
+
const directives = [];
|
|
502
|
+
this.getInlineConfigNodes().forEach((comment) => {
|
|
503
|
+
const { label, value, justification: justificationPart } = commentParser.parseDirective(comment.value);
|
|
504
|
+
if (label === "eslint-disable-line" && comment.position.start.line !== comment.position.end.line) {
|
|
505
|
+
const message = `${label} comment should not span multiple lines.`;
|
|
506
|
+
problems.push({
|
|
507
|
+
ruleId: null,
|
|
508
|
+
message,
|
|
509
|
+
loc: comment.position
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
switch (label) {
|
|
514
|
+
case "eslint-disable":
|
|
515
|
+
case "eslint-enable":
|
|
516
|
+
case "eslint-disable-next-line":
|
|
517
|
+
case "eslint-disable-line": {
|
|
518
|
+
const directiveType = label.slice(7);
|
|
519
|
+
directives.push(new Directive({
|
|
520
|
+
type: directiveType,
|
|
521
|
+
node: comment,
|
|
522
|
+
value,
|
|
523
|
+
justification: justificationPart
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
return {
|
|
529
|
+
problems,
|
|
530
|
+
directives
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Returns inline rule configurations along with any problems
|
|
535
|
+
* encountered while parsing the configurations.
|
|
536
|
+
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:Position}>}} Information
|
|
537
|
+
* that ESLint needs to further process the rule configurations.
|
|
538
|
+
*/
|
|
539
|
+
applyInlineConfig() {
|
|
540
|
+
/** @type {Array<FileProblem>} */
|
|
541
|
+
const problems = [];
|
|
542
|
+
/** @type {Array<{config:{rules:RulesConfig},loc:Position}>} */
|
|
543
|
+
const configs = [];
|
|
544
|
+
this.getInlineConfigNodes().forEach((comment) => {
|
|
545
|
+
const { label, value } = commentParser.parseDirective(comment.value);
|
|
546
|
+
if (label === "eslint") {
|
|
547
|
+
const parseResult = commentParser.parseJSONLikeConfig(value);
|
|
548
|
+
if (parseResult.ok) configs.push({
|
|
549
|
+
config: { rules: parseResult.config },
|
|
550
|
+
loc: comment.position
|
|
551
|
+
});
|
|
552
|
+
else problems.push({
|
|
553
|
+
ruleId: null,
|
|
554
|
+
message:
|
|
555
|
+
/** @type {{ok: false, error: { message: string }}} */ parseResult.error.message,
|
|
556
|
+
loc: comment.position
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
return {
|
|
561
|
+
configs,
|
|
562
|
+
problems
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Traverse the source code and return the steps that were taken.
|
|
567
|
+
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
|
|
568
|
+
*/
|
|
569
|
+
traverse() {
|
|
570
|
+
if (this.#steps) return this.#steps.values();
|
|
571
|
+
/** @type {Array<VisitNodeStep>} */
|
|
572
|
+
const steps = this.#steps = [];
|
|
573
|
+
/**
|
|
574
|
+
* Recursively visits a node and its children.
|
|
575
|
+
* @param {Node} node The node to visit.
|
|
576
|
+
* @param {Parent} [parent] The parent of the node.
|
|
577
|
+
* @returns {void}
|
|
578
|
+
*/
|
|
579
|
+
const visit = (node, parent) => {
|
|
580
|
+
this.#parents.set(node, parent);
|
|
581
|
+
steps.push(new VisitNodeStep({
|
|
582
|
+
target: node,
|
|
583
|
+
phase: 1,
|
|
584
|
+
args: [node, parent]
|
|
585
|
+
}));
|
|
586
|
+
if (node.type === "html") this.#htmlNodes.push(node);
|
|
587
|
+
if ("children" in node) {
|
|
588
|
+
const parentNode = node;
|
|
589
|
+
parentNode.children.forEach((child) => {
|
|
590
|
+
visit(child, parentNode);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
steps.push(new VisitNodeStep({
|
|
594
|
+
target: node,
|
|
595
|
+
phase: 2,
|
|
596
|
+
args: [node, parent]
|
|
597
|
+
}));
|
|
598
|
+
};
|
|
599
|
+
visit(this.ast);
|
|
600
|
+
return steps.values();
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
// vendor/src/language/markdown-language.js
|
|
604
|
+
/**
|
|
605
|
+
* @fileoverview The MarkdownLanguage class.
|
|
606
|
+
* @author Nicholas C. Zakas
|
|
607
|
+
*/
|
|
608
|
+
/**
|
|
609
|
+
* @import { Language, File, ParseResult, OkParseResult } from "@eslint/core";
|
|
610
|
+
* @import { Node, Root } from "mdast";
|
|
611
|
+
* @import { Parser } from "satorigear";
|
|
612
|
+
* @import { MarkdownLanguageOptions, MarkdownLanguageContext } from "../types.js";
|
|
613
|
+
* @typedef {"commonmark"|"gfm"} ParserMode
|
|
614
|
+
*/
|
|
615
|
+
/**
|
|
616
|
+
* The cache of parsers.
|
|
617
|
+
* @type {Map<string, Parser>}
|
|
618
|
+
*/
|
|
619
|
+
const parsers = /* @__PURE__ */ new Map();
|
|
620
|
+
/**
|
|
621
|
+
* Create parser options based on `mode` and `languageOptions`.
|
|
622
|
+
* @param {ParserMode} mode The markdown parser mode.
|
|
623
|
+
* @param {MarkdownLanguageOptions} languageOptions Language options.
|
|
624
|
+
* @returns {Parser} Satorigear parser.
|
|
625
|
+
*/
|
|
626
|
+
function createParser$1(mode, languageOptions) {
|
|
627
|
+
const key = [
|
|
628
|
+
mode,
|
|
629
|
+
languageOptions.frontmatter,
|
|
630
|
+
languageOptions.math
|
|
631
|
+
].join("+");
|
|
632
|
+
if (parsers.has(key)) return parsers.get(key);
|
|
633
|
+
const gfm = mode === "gfm";
|
|
634
|
+
const parser = createParser({ features: {
|
|
635
|
+
footnote: gfm,
|
|
636
|
+
frontmatter: languageOptions.frontmatter && { marker: languageOptions.frontmatter === "toml" ? "+" : "-" },
|
|
637
|
+
math: languageOptions.math,
|
|
638
|
+
strikethrough: gfm,
|
|
639
|
+
table: gfm,
|
|
640
|
+
taskList: gfm
|
|
641
|
+
} });
|
|
642
|
+
parsers.set(key, parser);
|
|
643
|
+
return parser;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Markdown Language Object
|
|
647
|
+
* @implements {Language<{ LangOptions: MarkdownLanguageOptions; Code: MarkdownSourceCode; RootNode: Root; Node: Node }>}
|
|
648
|
+
*/
|
|
649
|
+
var MarkdownLanguage = class {
|
|
650
|
+
/**
|
|
651
|
+
* The type of file to read.
|
|
652
|
+
* @type {"text"}
|
|
653
|
+
*/
|
|
654
|
+
fileType = "text";
|
|
655
|
+
/**
|
|
656
|
+
* The line number at which the parser starts counting.
|
|
657
|
+
* @type {0|1}
|
|
658
|
+
*/
|
|
659
|
+
lineStart = 1;
|
|
660
|
+
/**
|
|
661
|
+
* The column number at which the parser starts counting.
|
|
662
|
+
* @type {0|1}
|
|
663
|
+
*/
|
|
664
|
+
columnStart = 1;
|
|
665
|
+
/**
|
|
666
|
+
* The name of the key that holds the type of the node.
|
|
667
|
+
* @type {string}
|
|
668
|
+
*/
|
|
669
|
+
nodeTypeKey = "type";
|
|
670
|
+
/**
|
|
671
|
+
* Default language options. User-defined options are merged with this object.
|
|
672
|
+
* @type {MarkdownLanguageOptions}
|
|
673
|
+
*/
|
|
674
|
+
defaultLanguageOptions = {
|
|
675
|
+
frontmatter: false,
|
|
676
|
+
math: false
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* The Markdown parser mode.
|
|
680
|
+
* @type {ParserMode}
|
|
681
|
+
*/
|
|
682
|
+
#mode = "commonmark";
|
|
683
|
+
/**
|
|
684
|
+
* Creates a new instance.
|
|
685
|
+
* @param {Object} options The options to use for this instance.
|
|
686
|
+
* @param {ParserMode} [options.mode] The Markdown parser mode to use.
|
|
687
|
+
*/
|
|
688
|
+
constructor({ mode } = {}) {
|
|
689
|
+
if (mode) this.#mode = mode;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Validates the language options.
|
|
693
|
+
* @param {MarkdownLanguageOptions} languageOptions The language options to validate.
|
|
694
|
+
* @returns {void}
|
|
695
|
+
* @throws {Error} When the language options are invalid.
|
|
696
|
+
*/
|
|
697
|
+
validateLanguageOptions(languageOptions) {
|
|
698
|
+
const frontmatterOption = languageOptions?.frontmatter;
|
|
699
|
+
if (frontmatterOption !== void 0 && !(/* @__PURE__ */ new Set([
|
|
700
|
+
false,
|
|
701
|
+
"yaml",
|
|
702
|
+
"toml",
|
|
703
|
+
"json"
|
|
704
|
+
])).has(frontmatterOption)) throw new Error(`Invalid language option value \`${String(frontmatterOption)}\` for frontmatter. Expected one of \`false\`, \`"yaml"\`, \`"toml"\`, or \`"json"\`.`);
|
|
705
|
+
const mathOption = languageOptions?.math;
|
|
706
|
+
if (mathOption !== void 0 && typeof mathOption !== "boolean") throw new Error(`Invalid language option value \`${String(mathOption)}\` for math. Expected a boolean.`);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Parses the given file into an AST.
|
|
710
|
+
* @param {File} file The virtual file to parse.
|
|
711
|
+
* @param {MarkdownLanguageContext} context The options to use for parsing.
|
|
712
|
+
* @returns {ParseResult<Root>} The result of parsing.
|
|
713
|
+
*/
|
|
714
|
+
parse(file, context) {
|
|
715
|
+
const text = file.body;
|
|
716
|
+
try {
|
|
717
|
+
return {
|
|
718
|
+
ok: true,
|
|
719
|
+
ast: createParser$1(this.#mode, context?.languageOptions).parse(text)
|
|
720
|
+
};
|
|
721
|
+
} catch (ex) {
|
|
722
|
+
return {
|
|
723
|
+
ok: false,
|
|
724
|
+
errors: [ex]
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Creates a new `MarkdownSourceCode` object from the given information.
|
|
730
|
+
* @param {File} file The virtual file to create a `MarkdownSourceCode` object from.
|
|
731
|
+
* @param {OkParseResult<Root>} parseResult The result returned from `parse()`.
|
|
732
|
+
* @returns {MarkdownSourceCode} The new `MarkdownSourceCode` object.
|
|
733
|
+
*/
|
|
734
|
+
createSourceCode(file, parseResult) {
|
|
735
|
+
return new MarkdownSourceCode({
|
|
736
|
+
text: file.body,
|
|
737
|
+
ast: parseResult.ast
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
// vendor/src/rules/fenced-code-language.js
|
|
742
|
+
/**
|
|
743
|
+
* @fileoverview Rule to enforce languages for fenced code.
|
|
744
|
+
* @author Nicholas C. Zakas
|
|
745
|
+
*/
|
|
746
|
+
/**
|
|
747
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
748
|
+
* @typedef {"missingLanguage" | "disallowedLanguage"} FencedCodeLanguageMessageIds
|
|
749
|
+
* @typedef {[{ required?: string[] }]} FencedCodeLanguageOptions
|
|
750
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: FencedCodeLanguageOptions, MessageIds: FencedCodeLanguageMessageIds }>} FencedCodeLanguageRuleDefinition
|
|
751
|
+
*/
|
|
752
|
+
const fencedCodeCharacters = /* @__PURE__ */ new Set(["`", "~"]);
|
|
753
|
+
var fenced_code_language_default = {
|
|
754
|
+
meta: {
|
|
755
|
+
type: "problem",
|
|
756
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
757
|
+
docs: {
|
|
758
|
+
recommended: true,
|
|
759
|
+
description: "Require languages for fenced code blocks",
|
|
760
|
+
dialects: ["CommonMark", "GFM"],
|
|
761
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/fenced-code-language.md"
|
|
762
|
+
},
|
|
763
|
+
messages: {
|
|
764
|
+
missingLanguage: "Missing code block language.",
|
|
765
|
+
disallowedLanguage: "Code block language \"{{lang}}\" is not allowed."
|
|
766
|
+
},
|
|
767
|
+
schema: [{
|
|
768
|
+
type: "object",
|
|
769
|
+
properties: { required: {
|
|
770
|
+
type: "array",
|
|
771
|
+
items: { type: "string" },
|
|
772
|
+
uniqueItems: true
|
|
773
|
+
} },
|
|
774
|
+
additionalProperties: false
|
|
775
|
+
}],
|
|
776
|
+
defaultOptions: [{ required: [] }]
|
|
777
|
+
},
|
|
778
|
+
create(context) {
|
|
779
|
+
const required = new Set(context.options[0].required);
|
|
780
|
+
const { sourceCode } = context;
|
|
781
|
+
return { code(node) {
|
|
782
|
+
if (!node.lang) {
|
|
783
|
+
if (!fencedCodeCharacters.has(sourceCode.text[node.position.start.offset])) return;
|
|
784
|
+
/** @type {number} */
|
|
785
|
+
let openingCodeFenceEndOffset;
|
|
786
|
+
for (openingCodeFenceEndOffset = node.position.start.offset; fencedCodeCharacters.has(sourceCode.text[openingCodeFenceEndOffset]); openingCodeFenceEndOffset++);
|
|
787
|
+
context.report({
|
|
788
|
+
loc: {
|
|
789
|
+
start: node.position.start,
|
|
790
|
+
end: {
|
|
791
|
+
line: node.position.start.line,
|
|
792
|
+
column: node.position.start.column + openingCodeFenceEndOffset - node.position.start.offset
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
messageId: "missingLanguage"
|
|
796
|
+
});
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (required.size && !required.has(node.lang)) {
|
|
800
|
+
const langIndex = sourceCode.getText(node).indexOf(node.lang);
|
|
801
|
+
context.report({
|
|
802
|
+
loc: {
|
|
803
|
+
start: node.position.start,
|
|
804
|
+
end: {
|
|
805
|
+
line: node.position.start.line,
|
|
806
|
+
column: node.position.start.column + langIndex + node.lang.length
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
messageId: "disallowedLanguage",
|
|
810
|
+
data: { lang: node.lang }
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
} };
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
// vendor/src/rules/fenced-code-meta.js
|
|
817
|
+
/**
|
|
818
|
+
* @fileoverview Rule to require or disallow metadata for fenced code blocks.
|
|
819
|
+
* @author TKDev7
|
|
820
|
+
*/
|
|
821
|
+
/**
|
|
822
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
823
|
+
* @typedef {"missingMetadata" | "disallowedMetadata"} FencedCodeMetaMessageIds
|
|
824
|
+
* @typedef {["always" | "never"]} FencedCodeMetaOptions
|
|
825
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: FencedCodeMetaOptions, MessageIds: FencedCodeMetaMessageIds }>} FencedCodeMetaRuleDefinition
|
|
826
|
+
*/
|
|
827
|
+
var fenced_code_meta_default = {
|
|
828
|
+
meta: {
|
|
829
|
+
type: "problem",
|
|
830
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
831
|
+
docs: {
|
|
832
|
+
recommended: false,
|
|
833
|
+
description: "Require or disallow metadata for fenced code blocks",
|
|
834
|
+
dialects: ["CommonMark", "GFM"],
|
|
835
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/fenced-code-meta.md"
|
|
836
|
+
},
|
|
837
|
+
messages: {
|
|
838
|
+
missingMetadata: "Missing code block metadata.",
|
|
839
|
+
disallowedMetadata: "Code block metadata is not allowed."
|
|
840
|
+
},
|
|
841
|
+
schema: [{ enum: ["always", "never"] }],
|
|
842
|
+
defaultOptions: ["always"]
|
|
843
|
+
},
|
|
844
|
+
create(context) {
|
|
845
|
+
const [mode] = context.options;
|
|
846
|
+
const { sourceCode } = context;
|
|
847
|
+
return { code(node) {
|
|
848
|
+
const fenceLineText = sourceCode.lines[node.position.start.line - 1].slice(node.position.start.column - 1);
|
|
849
|
+
if (mode === "always") {
|
|
850
|
+
if (node.lang && !node.meta) {
|
|
851
|
+
const langIndex = fenceLineText.indexOf(node.lang);
|
|
852
|
+
context.report({
|
|
853
|
+
loc: {
|
|
854
|
+
start: node.position.start,
|
|
855
|
+
end: {
|
|
856
|
+
line: node.position.start.line,
|
|
857
|
+
column: node.position.start.column + langIndex + node.lang.length
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
messageId: "missingMetadata"
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (node.meta) {
|
|
866
|
+
const metaIndex = fenceLineText.lastIndexOf(node.meta);
|
|
867
|
+
context.report({
|
|
868
|
+
loc: {
|
|
869
|
+
start: {
|
|
870
|
+
line: node.position.start.line,
|
|
871
|
+
column: node.position.start.column + metaIndex
|
|
872
|
+
},
|
|
873
|
+
end: {
|
|
874
|
+
line: node.position.start.line,
|
|
875
|
+
column: node.position.start.column + metaIndex + node.meta.trimEnd().length
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
messageId: "disallowedMetadata"
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
} };
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
// vendor/src/rules/heading-increment.js
|
|
885
|
+
/**
|
|
886
|
+
* @fileoverview Rule to enforce heading levels increment by one.
|
|
887
|
+
* @author Nicholas C. Zakas
|
|
888
|
+
*/
|
|
889
|
+
/**
|
|
890
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
891
|
+
* @typedef {"skippedHeading"} HeadingIncrementMessageIds
|
|
892
|
+
* @typedef {[{ frontmatterTitle?: string }]} HeadingIncrementOptions
|
|
893
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: HeadingIncrementOptions, MessageIds: HeadingIncrementMessageIds }>} HeadingIncrementRuleDefinition
|
|
894
|
+
*/
|
|
895
|
+
var heading_increment_default = {
|
|
896
|
+
meta: {
|
|
897
|
+
type: "problem",
|
|
898
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
899
|
+
docs: {
|
|
900
|
+
recommended: true,
|
|
901
|
+
description: "Enforce heading levels increment by one",
|
|
902
|
+
dialects: ["CommonMark", "GFM"],
|
|
903
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/heading-increment.md"
|
|
904
|
+
},
|
|
905
|
+
messages: { skippedHeading: "Heading level skipped from {{fromLevel}} to {{toLevel}}." },
|
|
906
|
+
schema: [{
|
|
907
|
+
type: "object",
|
|
908
|
+
properties: { frontmatterTitle: { type: "string" } },
|
|
909
|
+
additionalProperties: false
|
|
910
|
+
}],
|
|
911
|
+
defaultOptions: [{ frontmatterTitle: "^(?!\\s*['\"]title[:=]['\"])\\s*\\{?\\s*['\"]?title['\"]?\\s*[:=]" }]
|
|
912
|
+
},
|
|
913
|
+
create(context) {
|
|
914
|
+
const [{ frontmatterTitle }] = context.options;
|
|
915
|
+
const titlePattern = frontmatterTitle === "" ? null : new RegExp(frontmatterTitle, "iu");
|
|
916
|
+
let lastHeadingDepth = 0;
|
|
917
|
+
return {
|
|
918
|
+
yaml(node) {
|
|
919
|
+
if (frontmatterHasTitle(node.value, titlePattern)) lastHeadingDepth = 1;
|
|
920
|
+
},
|
|
921
|
+
toml(node) {
|
|
922
|
+
if (frontmatterHasTitle(node.value, titlePattern)) lastHeadingDepth = 1;
|
|
923
|
+
},
|
|
924
|
+
json(node) {
|
|
925
|
+
if (frontmatterHasTitle(node.value, titlePattern)) lastHeadingDepth = 1;
|
|
926
|
+
},
|
|
927
|
+
heading(node) {
|
|
928
|
+
if (lastHeadingDepth > 0 && node.depth > lastHeadingDepth + 1) context.report({
|
|
929
|
+
loc: node.position,
|
|
930
|
+
messageId: "skippedHeading",
|
|
931
|
+
data: {
|
|
932
|
+
fromLevel: lastHeadingDepth,
|
|
933
|
+
toLevel: node.depth
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
lastHeadingDepth = node.depth;
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
// vendor/src/rules/no-bare-urls.js
|
|
942
|
+
/**
|
|
943
|
+
* @fileoverview Rule to prevent bare URLs in Markdown.
|
|
944
|
+
* @author xbinaryx
|
|
945
|
+
*/
|
|
946
|
+
/**
|
|
947
|
+
* @import { Link, Html } from "mdast";
|
|
948
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
949
|
+
* @typedef {"bareUrl"} NoBareUrlsMessageIds
|
|
950
|
+
* @typedef {[]} NoBareUrlsOptions
|
|
951
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoBareUrlsOptions, MessageIds: NoBareUrlsMessageIds }>} NoBareUrlsRuleDefinition
|
|
952
|
+
*/
|
|
953
|
+
const htmlTagNamePattern = /^<(?<tagName>[^!>][^/\s>]*)/u;
|
|
954
|
+
const linkTextSpecialCharacterPattern = /[[\]\\*_~`$&|]/gu;
|
|
955
|
+
const linkDestinationSpecialCharacterPattern = /[\\()&|]/gu;
|
|
956
|
+
/**
|
|
957
|
+
* Parses an HTML tag to extract its name and closing status
|
|
958
|
+
* @param {string} tagText The HTML tag text to parse
|
|
959
|
+
* @returns {{ name: string, isClosing: boolean } | null} Object containing tag name and closing status, or null if not a valid tag
|
|
960
|
+
*/
|
|
961
|
+
function parseHtmlTag(tagText) {
|
|
962
|
+
const match = tagText.match(htmlTagNamePattern);
|
|
963
|
+
if (match) {
|
|
964
|
+
const tagName = match.groups.tagName.toLowerCase();
|
|
965
|
+
const isClosing = tagName.startsWith("/");
|
|
966
|
+
return {
|
|
967
|
+
name: isClosing ? tagName.slice(1) : tagName,
|
|
968
|
+
isClosing
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
var no_bare_urls_default = {
|
|
974
|
+
meta: {
|
|
975
|
+
type: "problem",
|
|
976
|
+
languages: ["markdown/gfm"],
|
|
977
|
+
docs: {
|
|
978
|
+
description: "Disallow bare URLs",
|
|
979
|
+
dialects: ["GFM"],
|
|
980
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-bare-urls.md"
|
|
981
|
+
},
|
|
982
|
+
fixable: "code",
|
|
983
|
+
messages: { bareUrl: "Unexpected bare URL. Use autolink (<URL>) or link ([text](URL)) instead." }
|
|
984
|
+
},
|
|
985
|
+
create(context) {
|
|
986
|
+
const { sourceCode } = context;
|
|
987
|
+
/**
|
|
988
|
+
* This array is used to store all `Link` nodes for the final report.
|
|
989
|
+
* @type {Array<Link>}
|
|
990
|
+
*/
|
|
991
|
+
const linkNodes = [];
|
|
992
|
+
/**
|
|
993
|
+
* This array is used to store `Link` nodes that are estimated to be between opening and closing HTML tags.
|
|
994
|
+
* @type {Array<Link>}
|
|
995
|
+
*/
|
|
996
|
+
const tempLinkNodes = [];
|
|
997
|
+
/** @type {string | null} */
|
|
998
|
+
let lastTagName = null;
|
|
999
|
+
/**
|
|
1000
|
+
* Resets `tempLinkNodes` and `lastTagName`
|
|
1001
|
+
* @returns {void}
|
|
1002
|
+
*/
|
|
1003
|
+
function reset() {
|
|
1004
|
+
tempLinkNodes.length = 0;
|
|
1005
|
+
lastTagName = null;
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
":matches(heading, paragraph, tableCell) html"(node) {
|
|
1009
|
+
const tagInfo = parseHtmlTag(node.value);
|
|
1010
|
+
if (!tagInfo) return;
|
|
1011
|
+
if (!tagInfo.isClosing && lastTagName === null) lastTagName = tagInfo.name;
|
|
1012
|
+
if (tagInfo.isClosing && lastTagName === tagInfo.name) reset();
|
|
1013
|
+
},
|
|
1014
|
+
":matches(heading, paragraph, tableCell) link"(node) {
|
|
1015
|
+
if (lastTagName !== null) tempLinkNodes.push(node);
|
|
1016
|
+
else linkNodes.push(node);
|
|
1017
|
+
},
|
|
1018
|
+
"heading:exit"() {
|
|
1019
|
+
linkNodes.push(...tempLinkNodes);
|
|
1020
|
+
reset();
|
|
1021
|
+
},
|
|
1022
|
+
"paragraph:exit"() {
|
|
1023
|
+
linkNodes.push(...tempLinkNodes);
|
|
1024
|
+
reset();
|
|
1025
|
+
},
|
|
1026
|
+
"tableCell:exit"() {
|
|
1027
|
+
linkNodes.push(...tempLinkNodes);
|
|
1028
|
+
reset();
|
|
1029
|
+
},
|
|
1030
|
+
"root:exit"() {
|
|
1031
|
+
for (const linkNode of linkNodes) {
|
|
1032
|
+
const text = sourceCode.getText(linkNode);
|
|
1033
|
+
const { url } = linkNode;
|
|
1034
|
+
if (url === text || url === `http://${text}` || url === `mailto:${text}`) context.report({
|
|
1035
|
+
node: linkNode,
|
|
1036
|
+
messageId: "bareUrl",
|
|
1037
|
+
fix(fixer) {
|
|
1038
|
+
if (url !== `http://${text}`) return fixer.replaceText(linkNode, `<${text}>`);
|
|
1039
|
+
const escapedLinkText = text.replace(linkTextSpecialCharacterPattern, "\\$&");
|
|
1040
|
+
const escapedLinkDestination = url.replace(linkDestinationSpecialCharacterPattern, "\\$&");
|
|
1041
|
+
return fixer.replaceText(linkNode, `[${escapedLinkText}](${escapedLinkDestination})`);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
// vendor/src/rules/no-duplicate-definitions.js
|
|
1050
|
+
/**
|
|
1051
|
+
* @fileoverview Rule to prevent duplicate definitions in Markdown.
|
|
1052
|
+
* @author 루밀LuMir(lumirlumir)
|
|
1053
|
+
*/
|
|
1054
|
+
/**
|
|
1055
|
+
* @import { Definition, FootnoteDefinition } from "mdast";
|
|
1056
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1057
|
+
* @typedef {"duplicateDefinition" | "duplicateFootnoteDefinition"} NoDuplicateDefinitionsMessageIds
|
|
1058
|
+
* @typedef {[{ allowDefinitions?: string[], allowFootnoteDefinitions?: string[], checkFootnoteDefinitions?: boolean }]} NoDuplicateDefinitionsOptions
|
|
1059
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoDuplicateDefinitionsOptions, MessageIds: NoDuplicateDefinitionsMessageIds }>} NoDuplicateDefinitionsRuleDefinition
|
|
1060
|
+
*/
|
|
1061
|
+
var no_duplicate_definitions_default = {
|
|
1062
|
+
meta: {
|
|
1063
|
+
type: "problem",
|
|
1064
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1065
|
+
docs: {
|
|
1066
|
+
recommended: true,
|
|
1067
|
+
description: "Disallow duplicate definitions",
|
|
1068
|
+
dialects: ["CommonMark", "GFM"],
|
|
1069
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-duplicate-definitions.md"
|
|
1070
|
+
},
|
|
1071
|
+
messages: {
|
|
1072
|
+
duplicateDefinition: "Unexpected duplicate definition `{{ identifier }}` (label: `{{ label }}`) found. First defined at line {{ firstLine }} (label: `{{ firstLabel }}`).",
|
|
1073
|
+
duplicateFootnoteDefinition: "Unexpected duplicate footnote definition `{{ identifier }}` (label: `{{ label }}`) found. First defined at line {{ firstLine }} (label: `{{ firstLabel }}`)."
|
|
1074
|
+
},
|
|
1075
|
+
schema: [{
|
|
1076
|
+
type: "object",
|
|
1077
|
+
properties: {
|
|
1078
|
+
allowDefinitions: {
|
|
1079
|
+
type: "array",
|
|
1080
|
+
items: { type: "string" },
|
|
1081
|
+
uniqueItems: true
|
|
1082
|
+
},
|
|
1083
|
+
allowFootnoteDefinitions: {
|
|
1084
|
+
type: "array",
|
|
1085
|
+
items: { type: "string" },
|
|
1086
|
+
uniqueItems: true
|
|
1087
|
+
},
|
|
1088
|
+
checkFootnoteDefinitions: { type: "boolean" }
|
|
1089
|
+
},
|
|
1090
|
+
additionalProperties: false
|
|
1091
|
+
}],
|
|
1092
|
+
defaultOptions: [{
|
|
1093
|
+
allowDefinitions: ["//"],
|
|
1094
|
+
allowFootnoteDefinitions: [],
|
|
1095
|
+
checkFootnoteDefinitions: true
|
|
1096
|
+
}]
|
|
1097
|
+
},
|
|
1098
|
+
create(context) {
|
|
1099
|
+
const allowDefinitions = new Set(context.options[0].allowDefinitions.map(normalizeIdentifier$1));
|
|
1100
|
+
const allowFootnoteDefinitions = new Set(context.options[0].allowFootnoteDefinitions.map(normalizeIdentifier$1));
|
|
1101
|
+
const [{ checkFootnoteDefinitions }] = context.options;
|
|
1102
|
+
/** @type {Map<string, Definition>} */
|
|
1103
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
1104
|
+
/** @type {Map<string, FootnoteDefinition>} */
|
|
1105
|
+
const footnoteDefinitions = /* @__PURE__ */ new Map();
|
|
1106
|
+
return {
|
|
1107
|
+
definition(node) {
|
|
1108
|
+
if (allowDefinitions.has(node.identifier)) return;
|
|
1109
|
+
if (definitions.has(node.identifier)) {
|
|
1110
|
+
const firstDefinitionNode = definitions.get(node.identifier);
|
|
1111
|
+
context.report({
|
|
1112
|
+
node,
|
|
1113
|
+
messageId: "duplicateDefinition",
|
|
1114
|
+
data: {
|
|
1115
|
+
identifier: node.identifier,
|
|
1116
|
+
label: node.label.trim(),
|
|
1117
|
+
firstLine: firstDefinitionNode.position.start.line,
|
|
1118
|
+
firstLabel: firstDefinitionNode.label.trim()
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
} else definitions.set(node.identifier, node);
|
|
1122
|
+
},
|
|
1123
|
+
footnoteDefinition(node) {
|
|
1124
|
+
if (!checkFootnoteDefinitions || allowFootnoteDefinitions.has(node.identifier)) return;
|
|
1125
|
+
if (footnoteDefinitions.has(node.identifier)) {
|
|
1126
|
+
const firstFootnoteDefinitionNode = footnoteDefinitions.get(node.identifier);
|
|
1127
|
+
context.report({
|
|
1128
|
+
node,
|
|
1129
|
+
messageId: "duplicateFootnoteDefinition",
|
|
1130
|
+
data: {
|
|
1131
|
+
identifier: node.identifier,
|
|
1132
|
+
label: node.label,
|
|
1133
|
+
firstLine: firstFootnoteDefinitionNode.position.start.line,
|
|
1134
|
+
firstLabel: firstFootnoteDefinitionNode.label
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
} else footnoteDefinitions.set(node.identifier, node);
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
// vendor/src/rules/no-duplicate-headings.js
|
|
1143
|
+
/**
|
|
1144
|
+
* @fileoverview Rule to prevent duplicate headings in Markdown.
|
|
1145
|
+
* @author Nicholas C. Zakas
|
|
1146
|
+
*/
|
|
1147
|
+
/**
|
|
1148
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1149
|
+
* @typedef {"duplicateHeading"} NoDuplicateHeadingsMessageIds
|
|
1150
|
+
* @typedef {[{ checkSiblingsOnly?: boolean }]} NoDuplicateHeadingsOptions
|
|
1151
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoDuplicateHeadingsOptions, MessageIds: NoDuplicateHeadingsMessageIds }>} NoDuplicateHeadingsRuleDefinition
|
|
1152
|
+
*/
|
|
1153
|
+
var no_duplicate_headings_default = {
|
|
1154
|
+
meta: {
|
|
1155
|
+
type: "problem",
|
|
1156
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1157
|
+
docs: {
|
|
1158
|
+
description: "Disallow duplicate headings in the same document",
|
|
1159
|
+
dialects: ["CommonMark", "GFM"],
|
|
1160
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-duplicate-headings.md"
|
|
1161
|
+
},
|
|
1162
|
+
messages: { duplicateHeading: "Duplicate heading \"{{text}}\" found." },
|
|
1163
|
+
schema: [{
|
|
1164
|
+
type: "object",
|
|
1165
|
+
properties: { checkSiblingsOnly: { type: "boolean" } },
|
|
1166
|
+
additionalProperties: false
|
|
1167
|
+
}],
|
|
1168
|
+
defaultOptions: [{ checkSiblingsOnly: false }]
|
|
1169
|
+
},
|
|
1170
|
+
create(context) {
|
|
1171
|
+
const [{ checkSiblingsOnly }] = context.options;
|
|
1172
|
+
/** @type {Map<number, Set<string>>} */
|
|
1173
|
+
const headingsByLevel = checkSiblingsOnly ? /* @__PURE__ */ new Map([
|
|
1174
|
+
[1, /* @__PURE__ */ new Set()],
|
|
1175
|
+
[2, /* @__PURE__ */ new Set()],
|
|
1176
|
+
[3, /* @__PURE__ */ new Set()],
|
|
1177
|
+
[4, /* @__PURE__ */ new Set()],
|
|
1178
|
+
[5, /* @__PURE__ */ new Set()],
|
|
1179
|
+
[6, /* @__PURE__ */ new Set()]
|
|
1180
|
+
]) : /* @__PURE__ */ new Map([[1, /* @__PURE__ */ new Set()]]);
|
|
1181
|
+
let lastLevel = 1;
|
|
1182
|
+
let currentLevelHeadings = headingsByLevel.get(lastLevel);
|
|
1183
|
+
/** @type {string} */
|
|
1184
|
+
let headingChildrenSequence;
|
|
1185
|
+
/** @type {string} */
|
|
1186
|
+
let headingText;
|
|
1187
|
+
return {
|
|
1188
|
+
heading(node) {
|
|
1189
|
+
headingChildrenSequence = "";
|
|
1190
|
+
headingText = "";
|
|
1191
|
+
if (checkSiblingsOnly) {
|
|
1192
|
+
const currentLevel = node.depth;
|
|
1193
|
+
if (currentLevel < lastLevel) for (let level = lastLevel; level > currentLevel; level--) headingsByLevel.get(level).clear();
|
|
1194
|
+
lastLevel = currentLevel;
|
|
1195
|
+
currentLevelHeadings = headingsByLevel.get(currentLevel);
|
|
1196
|
+
}
|
|
1197
|
+
},
|
|
1198
|
+
"heading *"({ type, value }) {
|
|
1199
|
+
if (value) {
|
|
1200
|
+
headingChildrenSequence += `[${type},${value}]`;
|
|
1201
|
+
if (type !== "html") headingText += value;
|
|
1202
|
+
} else headingChildrenSequence += `[${type}]`;
|
|
1203
|
+
},
|
|
1204
|
+
"heading:exit"(node) {
|
|
1205
|
+
if (currentLevelHeadings.has(headingChildrenSequence)) context.report({
|
|
1206
|
+
loc: node.position,
|
|
1207
|
+
messageId: "duplicateHeading",
|
|
1208
|
+
data: { text: headingText }
|
|
1209
|
+
});
|
|
1210
|
+
else currentLevelHeadings.add(headingChildrenSequence);
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
// vendor/src/rules/no-empty-definitions.js
|
|
1216
|
+
/**
|
|
1217
|
+
* @fileoverview Rule to prevent empty definitions in Markdown.
|
|
1218
|
+
* @author Pixel998
|
|
1219
|
+
*/
|
|
1220
|
+
/**
|
|
1221
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1222
|
+
* @typedef {"emptyDefinition" | "emptyFootnoteDefinition"} NoEmptyDefinitionsMessageIds
|
|
1223
|
+
* @typedef {[{ allowDefinitions?: string[], allowFootnoteDefinitions?: string[], checkFootnoteDefinitions?: boolean }]} NoEmptyDefinitionsOptions
|
|
1224
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoEmptyDefinitionsOptions, MessageIds: NoEmptyDefinitionsMessageIds }>} NoEmptyDefinitionsRuleDefinition
|
|
1225
|
+
*/
|
|
1226
|
+
/**
|
|
1227
|
+
* Checks if a string contains only HTML comments.
|
|
1228
|
+
* @param {string} value The input string to check.
|
|
1229
|
+
* @returns {boolean} True if the string contains only HTML comments, false otherwise.
|
|
1230
|
+
*/
|
|
1231
|
+
function isOnlyComments(value) {
|
|
1232
|
+
return value.replace(htmlCommentPattern, "").trim().length === 0;
|
|
1233
|
+
}
|
|
1234
|
+
var no_empty_definitions_default = {
|
|
1235
|
+
meta: {
|
|
1236
|
+
type: "problem",
|
|
1237
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1238
|
+
docs: {
|
|
1239
|
+
recommended: true,
|
|
1240
|
+
description: "Disallow empty definitions",
|
|
1241
|
+
dialects: ["CommonMark", "GFM"],
|
|
1242
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-empty-definitions.md"
|
|
1243
|
+
},
|
|
1244
|
+
messages: {
|
|
1245
|
+
emptyDefinition: "Unexpected empty definition `{{ identifier }}` (label: `{{ label }}`) found.",
|
|
1246
|
+
emptyFootnoteDefinition: "Unexpected empty footnote definition `{{ identifier }}` (label: `{{ label }}`) found."
|
|
1247
|
+
},
|
|
1248
|
+
schema: [{
|
|
1249
|
+
type: "object",
|
|
1250
|
+
properties: {
|
|
1251
|
+
allowDefinitions: {
|
|
1252
|
+
type: "array",
|
|
1253
|
+
items: { type: "string" },
|
|
1254
|
+
uniqueItems: true
|
|
1255
|
+
},
|
|
1256
|
+
allowFootnoteDefinitions: {
|
|
1257
|
+
type: "array",
|
|
1258
|
+
items: { type: "string" },
|
|
1259
|
+
uniqueItems: true
|
|
1260
|
+
},
|
|
1261
|
+
checkFootnoteDefinitions: { type: "boolean" }
|
|
1262
|
+
},
|
|
1263
|
+
additionalProperties: false
|
|
1264
|
+
}],
|
|
1265
|
+
defaultOptions: [{
|
|
1266
|
+
allowDefinitions: ["//"],
|
|
1267
|
+
allowFootnoteDefinitions: [],
|
|
1268
|
+
checkFootnoteDefinitions: true
|
|
1269
|
+
}]
|
|
1270
|
+
},
|
|
1271
|
+
create(context) {
|
|
1272
|
+
const allowDefinitions = new Set(context.options[0].allowDefinitions.map(normalizeIdentifier$1));
|
|
1273
|
+
const allowFootnoteDefinitions = new Set(context.options[0].allowFootnoteDefinitions.map(normalizeIdentifier$1));
|
|
1274
|
+
const [{ checkFootnoteDefinitions }] = context.options;
|
|
1275
|
+
return {
|
|
1276
|
+
definition(node) {
|
|
1277
|
+
if ((!node.url || node.url === "#") && !allowDefinitions.has(node.identifier)) context.report({
|
|
1278
|
+
loc: node.position,
|
|
1279
|
+
messageId: "emptyDefinition",
|
|
1280
|
+
data: {
|
|
1281
|
+
identifier: node.identifier,
|
|
1282
|
+
label: node.label.trim()
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
},
|
|
1286
|
+
footnoteDefinition(node) {
|
|
1287
|
+
if (checkFootnoteDefinitions && !allowFootnoteDefinitions.has(node.identifier) && (node.children.length === 0 || node.children.every((child) => child.type === "html" && isOnlyComments(child.value)))) context.report({
|
|
1288
|
+
loc: node.position,
|
|
1289
|
+
messageId: "emptyFootnoteDefinition",
|
|
1290
|
+
data: {
|
|
1291
|
+
identifier: node.identifier,
|
|
1292
|
+
label: node.label
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
// vendor/src/rules/no-empty-images.js
|
|
1300
|
+
/**
|
|
1301
|
+
* @fileoverview Rule to prevent empty images in Markdown.
|
|
1302
|
+
* @author 루밀LuMir(lumirlumir)
|
|
1303
|
+
*/
|
|
1304
|
+
/**
|
|
1305
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1306
|
+
* @typedef {"emptyImage"} NoEmptyImagesMessageIds
|
|
1307
|
+
* @typedef {[]} NoEmptyImagesOptions
|
|
1308
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoEmptyImagesOptions, MessageIds: NoEmptyImagesMessageIds }>} NoEmptyImagesRuleDefinition
|
|
1309
|
+
*/
|
|
1310
|
+
var no_empty_images_default = {
|
|
1311
|
+
meta: {
|
|
1312
|
+
type: "problem",
|
|
1313
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1314
|
+
docs: {
|
|
1315
|
+
recommended: true,
|
|
1316
|
+
description: "Disallow empty images",
|
|
1317
|
+
dialects: ["CommonMark", "GFM"],
|
|
1318
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-empty-images.md"
|
|
1319
|
+
},
|
|
1320
|
+
messages: { emptyImage: "Unexpected empty image found." }
|
|
1321
|
+
},
|
|
1322
|
+
create(context) {
|
|
1323
|
+
return { image(node) {
|
|
1324
|
+
if (!node.url || node.url === "#") context.report({
|
|
1325
|
+
loc: node.position,
|
|
1326
|
+
messageId: "emptyImage"
|
|
1327
|
+
});
|
|
1328
|
+
} };
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
// vendor/src/rules/no-empty-links.js
|
|
1332
|
+
/**
|
|
1333
|
+
* @fileoverview Rule to prevent empty links in Markdown.
|
|
1334
|
+
* @author Nicholas C. Zakas
|
|
1335
|
+
*/
|
|
1336
|
+
/**
|
|
1337
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1338
|
+
* @typedef {"emptyLink"} NoEmptyLinksMessageIds
|
|
1339
|
+
* @typedef {[]} NoEmptyLinksOptions
|
|
1340
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoEmptyLinksOptions, MessageIds: NoEmptyLinksMessageIds }>} NoEmptyLinksRuleDefinition
|
|
1341
|
+
*/
|
|
1342
|
+
var no_empty_links_default = {
|
|
1343
|
+
meta: {
|
|
1344
|
+
type: "problem",
|
|
1345
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1346
|
+
docs: {
|
|
1347
|
+
recommended: true,
|
|
1348
|
+
description: "Disallow empty links",
|
|
1349
|
+
dialects: ["CommonMark", "GFM"],
|
|
1350
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-empty-links.md"
|
|
1351
|
+
},
|
|
1352
|
+
messages: { emptyLink: "Unexpected empty link found." }
|
|
1353
|
+
},
|
|
1354
|
+
create(context) {
|
|
1355
|
+
return { link(node) {
|
|
1356
|
+
if (!node.url || node.url === "#") context.report({
|
|
1357
|
+
loc: node.position,
|
|
1358
|
+
messageId: "emptyLink"
|
|
1359
|
+
});
|
|
1360
|
+
} };
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
// vendor/src/rules/no-html.js
|
|
1364
|
+
/**
|
|
1365
|
+
* @fileoverview Rule to disallow HTML inside of content.
|
|
1366
|
+
* @author Nicholas C. Zakas
|
|
1367
|
+
*/
|
|
1368
|
+
/**
|
|
1369
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1370
|
+
* @typedef {"disallowedElement"} NoHtmlMessageIds
|
|
1371
|
+
* @typedef {[{ allowed?: string[], allowedIgnoreCase?: boolean }]} NoHtmlOptions
|
|
1372
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoHtmlOptions, MessageIds: NoHtmlMessageIds }>} NoHtmlRuleDefinition
|
|
1373
|
+
*/
|
|
1374
|
+
const htmlTagPattern$1 = /<(?<tagName>[a-z0-9]+(?:-[a-z0-9]+)*)(?:\s(?:[^>"']|"[^"]*"|'[^']*')*)?\/?>/giu;
|
|
1375
|
+
var no_html_default = {
|
|
1376
|
+
meta: {
|
|
1377
|
+
type: "problem",
|
|
1378
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1379
|
+
docs: {
|
|
1380
|
+
description: "Disallow HTML tags",
|
|
1381
|
+
dialects: ["CommonMark", "GFM"],
|
|
1382
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-html.md"
|
|
1383
|
+
},
|
|
1384
|
+
messages: { disallowedElement: "HTML element \"{{name}}\" is not allowed." },
|
|
1385
|
+
schema: [{
|
|
1386
|
+
type: "object",
|
|
1387
|
+
properties: {
|
|
1388
|
+
allowed: {
|
|
1389
|
+
type: "array",
|
|
1390
|
+
items: { type: "string" },
|
|
1391
|
+
uniqueItems: true
|
|
1392
|
+
},
|
|
1393
|
+
allowedIgnoreCase: { type: "boolean" }
|
|
1394
|
+
},
|
|
1395
|
+
additionalProperties: false
|
|
1396
|
+
}],
|
|
1397
|
+
defaultOptions: [{
|
|
1398
|
+
allowed: [],
|
|
1399
|
+
allowedIgnoreCase: false
|
|
1400
|
+
}]
|
|
1401
|
+
},
|
|
1402
|
+
create(context) {
|
|
1403
|
+
const { sourceCode } = context;
|
|
1404
|
+
const [{ allowed, allowedIgnoreCase }] = context.options;
|
|
1405
|
+
/**
|
|
1406
|
+
* Normalize a tag name based on the `allowedIgnoreCase` option.
|
|
1407
|
+
* @param {string} tagName The tag name to normalize.
|
|
1408
|
+
* @returns {string} The normalized tag name.
|
|
1409
|
+
*/
|
|
1410
|
+
function normalizeTagName(tagName) {
|
|
1411
|
+
return allowedIgnoreCase ? tagName.toLowerCase() : tagName;
|
|
1412
|
+
}
|
|
1413
|
+
const allowedElements = new Set(allowed.map(normalizeTagName));
|
|
1414
|
+
return { html(node) {
|
|
1415
|
+
const text = stripHtmlComments(sourceCode.getText(node));
|
|
1416
|
+
/** @type {RegExpExecArray | null} */
|
|
1417
|
+
let match;
|
|
1418
|
+
while ((match = htmlTagPattern$1.exec(text)) !== null) {
|
|
1419
|
+
const fullMatch = match[0];
|
|
1420
|
+
const { tagName } = match.groups;
|
|
1421
|
+
const firstNewlineIndex = fullMatch.search(lineEndingPattern);
|
|
1422
|
+
const startOffset = match.index + node.position.start.offset;
|
|
1423
|
+
const endOffset = startOffset + (firstNewlineIndex === -1 ? fullMatch.length : firstNewlineIndex);
|
|
1424
|
+
if (!allowedElements.has(normalizeTagName(tagName))) context.report({
|
|
1425
|
+
loc: {
|
|
1426
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
1427
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
1428
|
+
},
|
|
1429
|
+
messageId: "disallowedElement",
|
|
1430
|
+
data: { name: tagName }
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
} };
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
// vendor/src/rules/no-invalid-label-refs.js
|
|
1437
|
+
/**
|
|
1438
|
+
* @fileoverview Rule to prevent non-complaint link references.
|
|
1439
|
+
* @author Nicholas C. Zakas
|
|
1440
|
+
*/
|
|
1441
|
+
/**
|
|
1442
|
+
* @import { Position } from "unist";
|
|
1443
|
+
* @import { Text } from "mdast";
|
|
1444
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1445
|
+
* @import { MarkdownSourceCode } from "../language/markdown-source-code.js";
|
|
1446
|
+
* @typedef {"invalidLabelRef"} NoInvalidLabelRefsMessageIds
|
|
1447
|
+
* @typedef {[]} NoInvalidLabelRefsOptions
|
|
1448
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoInvalidLabelRefsOptions, MessageIds: NoInvalidLabelRefsMessageIds }>} NoInvalidLabelRefsRuleDefinition
|
|
1449
|
+
*/
|
|
1450
|
+
/** matches i.e., `[foo][bar]` */
|
|
1451
|
+
const labelPattern = /\]\[([^\]]+)\]/u;
|
|
1452
|
+
/**
|
|
1453
|
+
* Finds missing references in a node.
|
|
1454
|
+
* @param {Text} node The node to check.
|
|
1455
|
+
* @param {MarkdownSourceCode} sourceCode The Markdown source code object.
|
|
1456
|
+
* @returns {Array<{label:string,position:Position}>} The missing references.
|
|
1457
|
+
*/
|
|
1458
|
+
function findInvalidLabelReferences(node, sourceCode) {
|
|
1459
|
+
const nodeText = sourceCode.getText(node);
|
|
1460
|
+
const docText = sourceCode.text;
|
|
1461
|
+
const invalid = [];
|
|
1462
|
+
let startIndex = 0;
|
|
1463
|
+
while (startIndex < nodeText.length) {
|
|
1464
|
+
const match = nodeText.slice(startIndex).match(labelPattern);
|
|
1465
|
+
if (!match) break;
|
|
1466
|
+
if (!illegalShorthandTailPattern.test(match[0])) {
|
|
1467
|
+
startIndex += match.index + match[0].length;
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
const startOffset = startIndex + match.index + node.position.start.offset;
|
|
1471
|
+
const endOffset = startOffset + match[0].length;
|
|
1472
|
+
const lastOpenBracketIndex = docText.lastIndexOf("[", startOffset);
|
|
1473
|
+
if (lastOpenBracketIndex === -1) {
|
|
1474
|
+
startIndex += match.index + match[0].length;
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
const label = docText.slice(lastOpenBracketIndex, endOffset).match(/!?\[([^\]]+)\]/u)[1];
|
|
1478
|
+
invalid.push({
|
|
1479
|
+
label: label.trim(),
|
|
1480
|
+
position: {
|
|
1481
|
+
start: sourceCode.getLocFromIndex(startOffset + 1),
|
|
1482
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
startIndex += match.index + match[0].length;
|
|
1486
|
+
}
|
|
1487
|
+
return invalid;
|
|
1488
|
+
}
|
|
1489
|
+
var no_invalid_label_refs_default = {
|
|
1490
|
+
meta: {
|
|
1491
|
+
type: "problem",
|
|
1492
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1493
|
+
docs: {
|
|
1494
|
+
recommended: true,
|
|
1495
|
+
description: "Disallow invalid label references",
|
|
1496
|
+
dialects: ["CommonMark", "GFM"],
|
|
1497
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-invalid-label-refs.md"
|
|
1498
|
+
},
|
|
1499
|
+
messages: { invalidLabelRef: "Label reference '{{label}}' is invalid due to white space between [ and ]." }
|
|
1500
|
+
},
|
|
1501
|
+
create(context) {
|
|
1502
|
+
const { sourceCode } = context;
|
|
1503
|
+
return { text(node) {
|
|
1504
|
+
const invalidReferences = findInvalidLabelReferences(node, sourceCode);
|
|
1505
|
+
for (const invalidReference of invalidReferences) context.report({
|
|
1506
|
+
loc: invalidReference.position,
|
|
1507
|
+
messageId: "invalidLabelRef",
|
|
1508
|
+
data: { label: invalidReference.label }
|
|
1509
|
+
});
|
|
1510
|
+
} };
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
// vendor/src/rules/no-missing-atx-heading-space.js
|
|
1514
|
+
/**
|
|
1515
|
+
* @fileoverview Rule to ensure there is a space after hash on ATX style headings in Markdown.
|
|
1516
|
+
* @author Sweta Tanwar (@SwetaTanwar)
|
|
1517
|
+
*/
|
|
1518
|
+
/**
|
|
1519
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1520
|
+
* @typedef {"missingSpace"} NoMissingAtxHeadingSpaceMessageIds
|
|
1521
|
+
* @typedef {[{ checkClosedHeadings?: boolean }]} NoMissingAtxHeadingSpaceOptions
|
|
1522
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoMissingAtxHeadingSpaceOptions, MessageIds: NoMissingAtxHeadingSpaceMessageIds }>} NoMissingAtxHeadingSpaceRuleDefinition
|
|
1523
|
+
*/
|
|
1524
|
+
const leadingAtxHeadingHashPattern = /(?:^|(?<=[\r\n]))(?<hashes>#{1,6})(?:[^# \t]|$)/gu;
|
|
1525
|
+
const trailingAtxHeadingHashPattern = /(?<![ \t])(?<spaces>[ \t]*)(?<=(?<!\\)(?:\\{2})*)(?<hashes>#+)[ \t]*$/u;
|
|
1526
|
+
var no_missing_atx_heading_space_default = {
|
|
1527
|
+
meta: {
|
|
1528
|
+
type: "problem",
|
|
1529
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1530
|
+
docs: {
|
|
1531
|
+
recommended: true,
|
|
1532
|
+
description: "Disallow headings without a space after the hash characters",
|
|
1533
|
+
dialects: ["CommonMark", "GFM"],
|
|
1534
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-missing-atx-heading-space.md"
|
|
1535
|
+
},
|
|
1536
|
+
fixable: "whitespace",
|
|
1537
|
+
messages: { missingSpace: "Missing space {{position}} hash(es) on ATX style heading." },
|
|
1538
|
+
schema: [{
|
|
1539
|
+
type: "object",
|
|
1540
|
+
properties: { checkClosedHeadings: { type: "boolean" } },
|
|
1541
|
+
additionalProperties: false
|
|
1542
|
+
}],
|
|
1543
|
+
defaultOptions: [{ checkClosedHeadings: false }]
|
|
1544
|
+
},
|
|
1545
|
+
create(context) {
|
|
1546
|
+
const { sourceCode } = context;
|
|
1547
|
+
const [{ checkClosedHeadings }] = context.options;
|
|
1548
|
+
return {
|
|
1549
|
+
heading(node) {
|
|
1550
|
+
if (!checkClosedHeadings) return;
|
|
1551
|
+
const text = sourceCode.getText(node);
|
|
1552
|
+
const match = trailingAtxHeadingHashPattern.exec(text);
|
|
1553
|
+
if (match === null || match.index === 0) return;
|
|
1554
|
+
const { spaces, hashes } = match.groups;
|
|
1555
|
+
if (spaces.length > 0) return;
|
|
1556
|
+
const startOffset = node.position.start.offset + match.index;
|
|
1557
|
+
const endOffset = startOffset + hashes.length;
|
|
1558
|
+
context.report({
|
|
1559
|
+
loc: {
|
|
1560
|
+
start: sourceCode.getLocFromIndex(startOffset - 1),
|
|
1561
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
1562
|
+
},
|
|
1563
|
+
messageId: "missingSpace",
|
|
1564
|
+
data: { position: "before" },
|
|
1565
|
+
fix(fixer) {
|
|
1566
|
+
return fixer.insertTextBeforeRange([startOffset, startOffset + 1], " ");
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
},
|
|
1570
|
+
paragraph(node) {
|
|
1571
|
+
const text = sourceCode.getText(node);
|
|
1572
|
+
/** @type {RegExpExecArray | null} */
|
|
1573
|
+
let match;
|
|
1574
|
+
while ((match = leadingAtxHeadingHashPattern.exec(text)) !== null) {
|
|
1575
|
+
const { hashes } = match.groups;
|
|
1576
|
+
const startOffset = node.position.start.offset + match.index;
|
|
1577
|
+
const endOffset = startOffset + hashes.length;
|
|
1578
|
+
context.report({
|
|
1579
|
+
loc: {
|
|
1580
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
1581
|
+
end: sourceCode.getLocFromIndex(endOffset + 1)
|
|
1582
|
+
},
|
|
1583
|
+
messageId: "missingSpace",
|
|
1584
|
+
data: { position: "after" },
|
|
1585
|
+
fix(fixer) {
|
|
1586
|
+
return fixer.insertTextAfterRange([endOffset - 1, endOffset], " ");
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
// vendor/src/rules/no-missing-label-refs.js
|
|
1595
|
+
/**
|
|
1596
|
+
* @fileoverview Rule to prevent missing label references in Markdown.
|
|
1597
|
+
* @author Nicholas C. Zakas
|
|
1598
|
+
*/
|
|
1599
|
+
/**
|
|
1600
|
+
* @import { Position } from "unist";
|
|
1601
|
+
* @import { Text } from "mdast";
|
|
1602
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1603
|
+
* @import { MarkdownSourceCode } from "../language/markdown-source-code.js";
|
|
1604
|
+
* @typedef {"notFound"} NoMissingLabelRefsMessageIds
|
|
1605
|
+
* @typedef {[{ allowLabels?: string[] }]} NoMissingLabelRefsOptions
|
|
1606
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoMissingLabelRefsOptions, MessageIds: NoMissingLabelRefsMessageIds }>} NoMissingLabelRefsRuleDefinition
|
|
1607
|
+
*/
|
|
1608
|
+
/**
|
|
1609
|
+
* Finds missing references in a node.
|
|
1610
|
+
* @param {Text} node The node to check.
|
|
1611
|
+
* @param {MarkdownSourceCode} sourceCode The Markdown source code object.
|
|
1612
|
+
* @returns {Array<{label:string,position:Position}>} The missing references.
|
|
1613
|
+
*/
|
|
1614
|
+
function findMissingReferences(node, sourceCode) {
|
|
1615
|
+
/** @type {Array<{label:string,position:Position}>} */
|
|
1616
|
+
const missing = [];
|
|
1617
|
+
const nodeText = sourceCode.getText(node);
|
|
1618
|
+
/**
|
|
1619
|
+
* Matches substrings like `"[foo]"`, `"[]"`, `"[foo][bar]"`, `"[foo][]"`, `"[][bar]"`, or `"[][]"`.
|
|
1620
|
+
* `left` is the content between the first brackets. It can be empty.
|
|
1621
|
+
* `right` is the content between the second brackets. It can be empty, and it can be undefined.
|
|
1622
|
+
*/
|
|
1623
|
+
const labelPattern = /(?<=(?<!\\)(?:\\{2})*)\[(?<left>(?:\\.|[^[\]\\])*)\](?:\[(?<right>(?:\\.|[^\]\\])*)\])?/dgu;
|
|
1624
|
+
/** @type {RegExpExecArray | null} */
|
|
1625
|
+
let match;
|
|
1626
|
+
while (match = labelPattern.exec(nodeText)) {
|
|
1627
|
+
if (illegalShorthandTailPattern.test(match[0])) continue;
|
|
1628
|
+
const { left, right } = match.groups;
|
|
1629
|
+
if (!left && !right) continue;
|
|
1630
|
+
let label, labelIndices;
|
|
1631
|
+
if (right) {
|
|
1632
|
+
label = right;
|
|
1633
|
+
labelIndices = match.indices.groups.right;
|
|
1634
|
+
} else {
|
|
1635
|
+
label = left;
|
|
1636
|
+
labelIndices = match.indices.groups.left;
|
|
1637
|
+
}
|
|
1638
|
+
const startOffset = labelIndices[0] + node.position.start.offset;
|
|
1639
|
+
const endOffset = labelIndices[1] + node.position.start.offset;
|
|
1640
|
+
missing.push({
|
|
1641
|
+
label: label.trim(),
|
|
1642
|
+
position: {
|
|
1643
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
1644
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
return missing;
|
|
1649
|
+
}
|
|
1650
|
+
var no_missing_label_refs_default = {
|
|
1651
|
+
meta: {
|
|
1652
|
+
type: "problem",
|
|
1653
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1654
|
+
docs: {
|
|
1655
|
+
recommended: true,
|
|
1656
|
+
description: "Disallow missing label references",
|
|
1657
|
+
dialects: ["CommonMark", "GFM"],
|
|
1658
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-missing-label-refs.md"
|
|
1659
|
+
},
|
|
1660
|
+
schema: [{
|
|
1661
|
+
type: "object",
|
|
1662
|
+
properties: { allowLabels: {
|
|
1663
|
+
type: "array",
|
|
1664
|
+
items: { type: "string" },
|
|
1665
|
+
uniqueItems: true
|
|
1666
|
+
} },
|
|
1667
|
+
additionalProperties: false
|
|
1668
|
+
}],
|
|
1669
|
+
defaultOptions: [{ allowLabels: [] }],
|
|
1670
|
+
messages: { notFound: "Label reference '{{label}}' not found." }
|
|
1671
|
+
},
|
|
1672
|
+
create(context) {
|
|
1673
|
+
const { sourceCode } = context;
|
|
1674
|
+
const allowLabels = new Set(context.options[0].allowLabels);
|
|
1675
|
+
/** @type {Array<{label:string,position:Position}>} */
|
|
1676
|
+
let allMissingReferences = [];
|
|
1677
|
+
return {
|
|
1678
|
+
"root:exit"() {
|
|
1679
|
+
for (const missingReference of allMissingReferences) context.report({
|
|
1680
|
+
loc: missingReference.position,
|
|
1681
|
+
messageId: "notFound",
|
|
1682
|
+
data: { label: missingReference.label }
|
|
1683
|
+
});
|
|
1684
|
+
},
|
|
1685
|
+
text(node) {
|
|
1686
|
+
const missingReferences = findMissingReferences(node, sourceCode);
|
|
1687
|
+
for (const missingReference of missingReferences) if (!allowLabels.has(missingReference.label)) allMissingReferences.push(missingReference);
|
|
1688
|
+
},
|
|
1689
|
+
definition(node) {
|
|
1690
|
+
allMissingReferences = allMissingReferences.filter((missingReference) => missingReference.label !== node.identifier);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
// vendor/src/rules/no-missing-link-fragments.js
|
|
1696
|
+
/**
|
|
1697
|
+
* @fileoverview Rule to ensure link fragments (URLs that start with #) reference valid headings
|
|
1698
|
+
* @author Sweta Tanwar (@SwetaTanwar)
|
|
1699
|
+
*/
|
|
1700
|
+
/**
|
|
1701
|
+
* @import { Definition, Link } from "mdast";
|
|
1702
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1703
|
+
* @typedef {"invalidFragment"} NoMissingLinkFragmentsMessageIds
|
|
1704
|
+
* @typedef {[{ ignoreCase?: boolean; allowPattern?: string }]} NoMissingLinkFragmentsOptions
|
|
1705
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoMissingLinkFragmentsOptions, MessageIds: NoMissingLinkFragmentsMessageIds }>} NoMissingLinkFragmentsRuleDefinition
|
|
1706
|
+
*/
|
|
1707
|
+
const githubLineReferencePattern = /^L\d+(?:C\d+)?(?:-L\d+(?:C\d+)?)?$/u;
|
|
1708
|
+
const customHeadingIdPattern = /\{#(?<id>[^}\s]+)\}\s*$/u;
|
|
1709
|
+
const htmlHeadingPattern = /<h(?<depth>[1-6])[^>]*>(?<children>[\s\S]*?)<\/h\k<depth>\s*>/giu;
|
|
1710
|
+
const htmlIdNamePattern = /(?<!<)<[^>]+\s(?:id|name)\s*=\s*["']?(?<id>[^"'\s>]+)["']?/giu;
|
|
1711
|
+
const htmlTagPattern = /<\/?[a-z0-9]+(?:-[a-z0-9]+)*(?:\s(?:[^>"']|"[^"]*"|'[^']*')*)?(?:\/\s*)?>/giu;
|
|
1712
|
+
var no_missing_link_fragments_default = {
|
|
1713
|
+
meta: {
|
|
1714
|
+
type: "problem",
|
|
1715
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1716
|
+
docs: {
|
|
1717
|
+
recommended: true,
|
|
1718
|
+
description: "Disallow link fragments that do not reference valid headings",
|
|
1719
|
+
dialects: ["CommonMark", "GFM"],
|
|
1720
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-missing-link-fragments.md"
|
|
1721
|
+
},
|
|
1722
|
+
schema: [{
|
|
1723
|
+
type: "object",
|
|
1724
|
+
properties: {
|
|
1725
|
+
ignoreCase: { type: "boolean" },
|
|
1726
|
+
allowPattern: { type: "string" }
|
|
1727
|
+
},
|
|
1728
|
+
additionalProperties: false
|
|
1729
|
+
}],
|
|
1730
|
+
messages: { invalidFragment: "Link fragment '#{{fragment}}' does not reference a heading or anchor in this document." },
|
|
1731
|
+
defaultOptions: [{
|
|
1732
|
+
ignoreCase: true,
|
|
1733
|
+
allowPattern: ""
|
|
1734
|
+
}]
|
|
1735
|
+
},
|
|
1736
|
+
create(context) {
|
|
1737
|
+
const [{ allowPattern, ignoreCase }] = context.options;
|
|
1738
|
+
const allowPatternOrNull = allowPattern ? new RegExp(allowPattern, "u") : null;
|
|
1739
|
+
const fragmentIds = /* @__PURE__ */ new Set(["top"]);
|
|
1740
|
+
const slugger = new GithubSlugger();
|
|
1741
|
+
/** @type {Array<Definition | Link>} */
|
|
1742
|
+
const relevantNodes = [];
|
|
1743
|
+
/** @type {string} */
|
|
1744
|
+
let headingText;
|
|
1745
|
+
return {
|
|
1746
|
+
heading() {
|
|
1747
|
+
headingText = "";
|
|
1748
|
+
},
|
|
1749
|
+
"heading *:not(html)"({ value }) {
|
|
1750
|
+
headingText += value ?? "";
|
|
1751
|
+
},
|
|
1752
|
+
"heading:exit"() {
|
|
1753
|
+
const customIdMatch = headingText.match(customHeadingIdPattern);
|
|
1754
|
+
const id = customIdMatch ? customIdMatch.groups.id : headingText;
|
|
1755
|
+
fragmentIds.add(slugger.slug(id));
|
|
1756
|
+
},
|
|
1757
|
+
html(node) {
|
|
1758
|
+
const htmlTextWithoutComments = stripHtmlComments(node.value);
|
|
1759
|
+
for (const match of htmlTextWithoutComments.matchAll(htmlIdNamePattern)) {
|
|
1760
|
+
const { id } = match.groups;
|
|
1761
|
+
fragmentIds.add(slugger.slug(id));
|
|
1762
|
+
}
|
|
1763
|
+
for (const match of htmlTextWithoutComments.matchAll(htmlHeadingPattern)) {
|
|
1764
|
+
const { children } = match.groups;
|
|
1765
|
+
const id = children.replace(htmlTagPattern, "");
|
|
1766
|
+
fragmentIds.add(slugger.slug(id));
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
"definition, link"(node) {
|
|
1770
|
+
const { url } = node;
|
|
1771
|
+
if (url === "" || url === "#" || !url.startsWith("#")) return;
|
|
1772
|
+
relevantNodes.push(node);
|
|
1773
|
+
},
|
|
1774
|
+
"root:exit"() {
|
|
1775
|
+
for (const node of relevantNodes) {
|
|
1776
|
+
const fragment = node.url.slice(1);
|
|
1777
|
+
let decodedFragment = fragment;
|
|
1778
|
+
try {
|
|
1779
|
+
decodedFragment = decodeURIComponent(fragment);
|
|
1780
|
+
} catch {}
|
|
1781
|
+
if (allowPatternOrNull?.test(decodedFragment) || githubLineReferencePattern.test(decodedFragment)) continue;
|
|
1782
|
+
const normalizedFragment = ignoreCase ? decodedFragment.toLowerCase() : decodedFragment;
|
|
1783
|
+
if (!fragmentIds.has(normalizedFragment)) context.report({
|
|
1784
|
+
loc: node.position,
|
|
1785
|
+
messageId: "invalidFragment",
|
|
1786
|
+
data: { fragment }
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
// vendor/src/rules/no-multiple-h1.js
|
|
1794
|
+
/**
|
|
1795
|
+
* @fileoverview Rule to enforce at most one H1 heading in Markdown.
|
|
1796
|
+
* @author Pixel998
|
|
1797
|
+
*/
|
|
1798
|
+
/**
|
|
1799
|
+
* @import { Yaml } from "mdast";
|
|
1800
|
+
* @import { MarkdownRuleDefinition, Toml, Json } from "../types.js";
|
|
1801
|
+
* @typedef {"multipleH1"} NoMultipleH1MessageIds
|
|
1802
|
+
* @typedef {[{ frontmatterTitle?: string }]} NoMultipleH1Options
|
|
1803
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoMultipleH1Options, MessageIds: NoMultipleH1MessageIds }>} NoMultipleH1RuleDefinition
|
|
1804
|
+
*/
|
|
1805
|
+
const h1TagPattern = /<h1[^>]*>[\s\S]*?<\/h1\s*>/giu;
|
|
1806
|
+
var no_multiple_h1_default = {
|
|
1807
|
+
meta: {
|
|
1808
|
+
type: "problem",
|
|
1809
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1810
|
+
docs: {
|
|
1811
|
+
recommended: true,
|
|
1812
|
+
description: "Disallow multiple H1 headings in the same document",
|
|
1813
|
+
dialects: ["CommonMark", "GFM"],
|
|
1814
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-multiple-h1.md"
|
|
1815
|
+
},
|
|
1816
|
+
messages: { multipleH1: "Unexpected additional H1 heading found." },
|
|
1817
|
+
schema: [{
|
|
1818
|
+
type: "object",
|
|
1819
|
+
properties: { frontmatterTitle: { type: "string" } },
|
|
1820
|
+
additionalProperties: false
|
|
1821
|
+
}],
|
|
1822
|
+
defaultOptions: [{ frontmatterTitle: "^(?!\\s*['\"]title[:=]['\"])\\s*\\{?\\s*['\"]?title['\"]?\\s*[:=]" }]
|
|
1823
|
+
},
|
|
1824
|
+
create(context) {
|
|
1825
|
+
const { sourceCode } = context;
|
|
1826
|
+
const [{ frontmatterTitle }] = context.options;
|
|
1827
|
+
const titlePattern = frontmatterTitle === "" ? null : new RegExp(frontmatterTitle, "iu");
|
|
1828
|
+
let h1Count = 0;
|
|
1829
|
+
return {
|
|
1830
|
+
"yaml, toml, json"(node) {
|
|
1831
|
+
if (frontmatterHasTitle(node.value, titlePattern)) h1Count++;
|
|
1832
|
+
},
|
|
1833
|
+
html(node) {
|
|
1834
|
+
const text = stripHtmlComments(node.value);
|
|
1835
|
+
/** @type {RegExpExecArray | null} */
|
|
1836
|
+
let match;
|
|
1837
|
+
while ((match = h1TagPattern.exec(text)) !== null) {
|
|
1838
|
+
h1Count++;
|
|
1839
|
+
if (h1Count > 1) {
|
|
1840
|
+
const startOffset = match.index + node.position.start.offset;
|
|
1841
|
+
const endOffset = startOffset + match[0].length;
|
|
1842
|
+
context.report({
|
|
1843
|
+
loc: {
|
|
1844
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
1845
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
1846
|
+
},
|
|
1847
|
+
messageId: "multipleH1"
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
},
|
|
1852
|
+
heading(node) {
|
|
1853
|
+
if (node.depth === 1) {
|
|
1854
|
+
h1Count++;
|
|
1855
|
+
if (h1Count > 1) context.report({
|
|
1856
|
+
loc: node.position,
|
|
1857
|
+
messageId: "multipleH1"
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
// vendor/src/rules/no-reference-like-urls.js
|
|
1865
|
+
/**
|
|
1866
|
+
* @fileoverview Rule to enforce reference-style links when URL matches a defined identifier.
|
|
1867
|
+
* @author TKDev7
|
|
1868
|
+
*/
|
|
1869
|
+
/**
|
|
1870
|
+
* @import { Image, Link } from "mdast";
|
|
1871
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1872
|
+
* @typedef {"referenceLikeUrl"} NoReferenceLikeUrlsMessageIds
|
|
1873
|
+
* @typedef {[]} NoReferenceLikeUrlsOptions
|
|
1874
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReferenceLikeUrlsOptions, MessageIds: NoReferenceLikeUrlsMessageIds }>} NoReferenceLikeUrlsRuleDefinition
|
|
1875
|
+
*/
|
|
1876
|
+
/**
|
|
1877
|
+
* Pattern to match both inline links: `[text](url)` and images: ``, with optional title.
|
|
1878
|
+
* The nested group is `\((?:\\[\s\S]|[^()\\])*\)`, not `\([\s\S]*\)`, so that it cannot overlap the
|
|
1879
|
+
* other alternatives. Overlapping alternatives cause catastrophic backtracking.
|
|
1880
|
+
*/
|
|
1881
|
+
const linkOrImagePattern = /^!?\[(?<label>(?:\\.|[^()\\]|\((?:\\[\s\S]|[^()\\])*\))*?)\]\((?<destination>[ \t]*\r?\n?(?<![ \t])[ \t]*(?:<[^>]*>|[^ \t()]+))(?:[ \t]*\r?\n?(?<![ \t])[ \t]*(?:"[^"]*"|'[^']*'|\([^)]*\)))?[ \t]*\r?\n?(?<![ \t])[ \t]*\)$/u;
|
|
1882
|
+
var no_reference_like_urls_default = {
|
|
1883
|
+
meta: {
|
|
1884
|
+
type: "problem",
|
|
1885
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1886
|
+
docs: {
|
|
1887
|
+
recommended: true,
|
|
1888
|
+
description: "Disallow URLs that match defined reference identifiers",
|
|
1889
|
+
dialects: ["CommonMark", "GFM"],
|
|
1890
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-reference-like-urls.md"
|
|
1891
|
+
},
|
|
1892
|
+
fixable: "code",
|
|
1893
|
+
messages: { referenceLikeUrl: "Unexpected resource {{type}} ('{{prefix}}[text](url)') with URL that matches a definition identifier. Use '[text][id]' syntax instead." }
|
|
1894
|
+
},
|
|
1895
|
+
create(context) {
|
|
1896
|
+
const { sourceCode } = context;
|
|
1897
|
+
/** @type {Set<string>} */
|
|
1898
|
+
const definitionIdentifiers = /* @__PURE__ */ new Set();
|
|
1899
|
+
/** @type {Array<Image | Link>} */
|
|
1900
|
+
const relevantNodes = [];
|
|
1901
|
+
return {
|
|
1902
|
+
definition(node) {
|
|
1903
|
+
definitionIdentifiers.add(node.identifier);
|
|
1904
|
+
},
|
|
1905
|
+
"image, link"(node) {
|
|
1906
|
+
relevantNodes.push(node);
|
|
1907
|
+
},
|
|
1908
|
+
"root:exit"() {
|
|
1909
|
+
for (const node of relevantNodes) {
|
|
1910
|
+
const text = sourceCode.getText(node);
|
|
1911
|
+
const match = linkOrImagePattern.exec(text);
|
|
1912
|
+
if (match !== null) {
|
|
1913
|
+
const { label, destination } = match.groups;
|
|
1914
|
+
const { type, title } = node;
|
|
1915
|
+
const prefix = type === "image" ? "!" : "";
|
|
1916
|
+
const url = normalizeIdentifier$1(destination);
|
|
1917
|
+
if (definitionIdentifiers.has(url)) context.report({
|
|
1918
|
+
loc: node.position,
|
|
1919
|
+
messageId: "referenceLikeUrl",
|
|
1920
|
+
data: {
|
|
1921
|
+
type,
|
|
1922
|
+
prefix
|
|
1923
|
+
},
|
|
1924
|
+
fix(fixer) {
|
|
1925
|
+
if (title) return null;
|
|
1926
|
+
return fixer.replaceText(node, `${prefix}[${label}][${destination}]`);
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
// vendor/src/rules/no-reversed-media-syntax.js
|
|
1936
|
+
/**
|
|
1937
|
+
* @fileoverview Rule to prevent reversed link and image syntax in Markdown.
|
|
1938
|
+
* @author xbinaryx
|
|
1939
|
+
*/
|
|
1940
|
+
/**
|
|
1941
|
+
* @import { Heading, Paragraph, TableCell, FootnoteReference, Html, Image, ImageReference, InlineCode, LinkReference } from "mdast";
|
|
1942
|
+
* @import { InlineMath } from "satorigear";
|
|
1943
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
1944
|
+
* @typedef {"reversedSyntax"} NoReversedMediaSyntaxMessageIds
|
|
1945
|
+
* @typedef {[]} NoReversedMediaSyntaxOptions
|
|
1946
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReversedMediaSyntaxOptions, MessageIds: NoReversedMediaSyntaxMessageIds }>} NoReversedMediaSyntaxRuleDefinition
|
|
1947
|
+
*/
|
|
1948
|
+
/**
|
|
1949
|
+
* Matches reversed link/image syntax like `(text)[url]`, ignoring escaped characters like `\(text\)[url]`.
|
|
1950
|
+
* The nested group is `\((?:\\[\s\S]|[^()\\])*\)`, not `\([\s\S]*\)`, so that it cannot overlap the
|
|
1951
|
+
* other alternatives. Overlapping alternatives cause catastrophic backtracking.
|
|
1952
|
+
*/
|
|
1953
|
+
const reversedPattern = /(?<=(?<!\\)(?:\\{2})*)\((?<label>(?:\\.|[^()\\]|\((?:\\[\s\S]|[^()\\])*\))*)\)\[(?<url>(?:\\.|[^\]\\\r\n])*)\](?!\()/gu;
|
|
1954
|
+
var no_reversed_media_syntax_default = {
|
|
1955
|
+
meta: {
|
|
1956
|
+
type: "problem",
|
|
1957
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
1958
|
+
docs: {
|
|
1959
|
+
recommended: true,
|
|
1960
|
+
description: "Disallow reversed link and image syntax",
|
|
1961
|
+
dialects: ["CommonMark", "GFM"],
|
|
1962
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-reversed-media-syntax.md"
|
|
1963
|
+
},
|
|
1964
|
+
fixable: "code",
|
|
1965
|
+
messages: { reversedSyntax: "Unexpected reversed syntax found. Use [label](URL) syntax instead." }
|
|
1966
|
+
},
|
|
1967
|
+
create(context) {
|
|
1968
|
+
const { sourceCode } = context;
|
|
1969
|
+
/** @type {string[]} */
|
|
1970
|
+
let buffer;
|
|
1971
|
+
/** @type {number} */
|
|
1972
|
+
let nodeStartOffset;
|
|
1973
|
+
return {
|
|
1974
|
+
"heading, paragraph, tableCell"(node) {
|
|
1975
|
+
buffer = sourceCode.getText(node).split("");
|
|
1976
|
+
nodeStartOffset = node.position.start.offset;
|
|
1977
|
+
},
|
|
1978
|
+
":matches(heading, paragraph, tableCell) :matches(footnoteReference, html, image, imageReference, inlineCode, linkReference, inlineMath)"(node) {
|
|
1979
|
+
const [startOffset, endOffset] = sourceCode.getRange(node);
|
|
1980
|
+
for (let i = startOffset; i < endOffset; i++) buffer[i - nodeStartOffset] = " ";
|
|
1981
|
+
},
|
|
1982
|
+
":matches(heading, paragraph, tableCell):exit"() {
|
|
1983
|
+
const maskedText = buffer.join("");
|
|
1984
|
+
/** @type {RegExpExecArray | null} */
|
|
1985
|
+
let match;
|
|
1986
|
+
while ((match = reversedPattern.exec(maskedText)) !== null) {
|
|
1987
|
+
const { label, url } = match.groups;
|
|
1988
|
+
const startOffset = match.index + nodeStartOffset;
|
|
1989
|
+
const endOffset = startOffset + match[0].length;
|
|
1990
|
+
const labelStartOffset = startOffset + 1;
|
|
1991
|
+
const labelEndOffset = labelStartOffset + label.length;
|
|
1992
|
+
const urlStartOffset = labelEndOffset + 2;
|
|
1993
|
+
const urlEndOffset = urlStartOffset + url.length;
|
|
1994
|
+
context.report({
|
|
1995
|
+
loc: {
|
|
1996
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
1997
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
1998
|
+
},
|
|
1999
|
+
messageId: "reversedSyntax",
|
|
2000
|
+
fix(fixer) {
|
|
2001
|
+
return fixer.replaceTextRange([startOffset, endOffset], `[${sourceCode.text.slice(labelStartOffset, labelEndOffset)}](${sourceCode.text.slice(urlStartOffset, urlEndOffset)})`);
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
// vendor/src/rules/no-space-in-emphasis.js
|
|
2010
|
+
/**
|
|
2011
|
+
* @fileoverview Rule to prevent spaces around emphasis markers in Markdown.
|
|
2012
|
+
* @author Pixel998
|
|
2013
|
+
*/
|
|
2014
|
+
/**
|
|
2015
|
+
* @import { SourceRange } from "@eslint/core";
|
|
2016
|
+
* @import { Heading, Paragraph, TableCell, Text } from "mdast";
|
|
2017
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
2018
|
+
* @typedef {"spaceInEmphasis"} NoSpaceInEmphasisMessageIds
|
|
2019
|
+
* @typedef {[{ checkStrikethrough?: boolean }]} NoSpaceInEmphasisOptions
|
|
2020
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoSpaceInEmphasisOptions, MessageIds: NoSpaceInEmphasisMessageIds }>} NoSpaceInEmphasisRuleDefinition
|
|
2021
|
+
*/
|
|
2022
|
+
const whitespacePattern = /[ \t]/u;
|
|
2023
|
+
/**
|
|
2024
|
+
* Creates a marker pattern based on whether strikethrough should be included.
|
|
2025
|
+
* @param {boolean} checkStrikethrough Whether to include strikethrough markers.
|
|
2026
|
+
* @returns {RegExp} The marker pattern.
|
|
2027
|
+
*/
|
|
2028
|
+
function createMarkerPattern(checkStrikethrough) {
|
|
2029
|
+
return checkStrikethrough ? /(?<=(?<!\\)(?:\\{2})*)(?:\*\*\*|\*\*|\*|___|__|_|~~|~)/gu : /(?<=(?<!\\)(?:\\{2})*)(?:\*\*\*|\*\*|\*|___|__|_)/gu;
|
|
2030
|
+
}
|
|
2031
|
+
var no_space_in_emphasis_default = {
|
|
2032
|
+
meta: {
|
|
2033
|
+
type: "problem",
|
|
2034
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
2035
|
+
docs: {
|
|
2036
|
+
recommended: true,
|
|
2037
|
+
description: "Disallow spaces around emphasis markers",
|
|
2038
|
+
dialects: ["CommonMark", "GFM"],
|
|
2039
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-space-in-emphasis.md"
|
|
2040
|
+
},
|
|
2041
|
+
fixable: "whitespace",
|
|
2042
|
+
messages: { spaceInEmphasis: "Unexpected space around emphasis marker." },
|
|
2043
|
+
schema: [{
|
|
2044
|
+
type: "object",
|
|
2045
|
+
properties: { checkStrikethrough: { type: "boolean" } },
|
|
2046
|
+
additionalProperties: false
|
|
2047
|
+
}],
|
|
2048
|
+
defaultOptions: [{ checkStrikethrough: false }]
|
|
2049
|
+
},
|
|
2050
|
+
create(context) {
|
|
2051
|
+
const { sourceCode } = context;
|
|
2052
|
+
const [{ checkStrikethrough }] = context.options;
|
|
2053
|
+
const markerPattern = createMarkerPattern(checkStrikethrough);
|
|
2054
|
+
/** @type {string[]} */
|
|
2055
|
+
let buffer;
|
|
2056
|
+
/**
|
|
2057
|
+
* Reports a surrounding-space violation if present.
|
|
2058
|
+
* @param {number} checkIndex Character index to test for whitespace.
|
|
2059
|
+
* @param {number} highlightStartIndex Start index for highlighting.
|
|
2060
|
+
* @param {number} highlightEndIndex End index for highlighting.
|
|
2061
|
+
* @returns {void}
|
|
2062
|
+
*/
|
|
2063
|
+
function reportWhitespace(checkIndex, highlightStartIndex, highlightEndIndex) {
|
|
2064
|
+
if (whitespacePattern.test(sourceCode.text[checkIndex])) context.report({
|
|
2065
|
+
loc: {
|
|
2066
|
+
start: sourceCode.getLocFromIndex(highlightStartIndex),
|
|
2067
|
+
end: sourceCode.getLocFromIndex(highlightEndIndex)
|
|
2068
|
+
},
|
|
2069
|
+
messageId: "spaceInEmphasis",
|
|
2070
|
+
fix(fixer) {
|
|
2071
|
+
return fixer.removeRange([checkIndex, checkIndex + 1]);
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
return {
|
|
2076
|
+
"heading, paragraph, tableCell"(node) {
|
|
2077
|
+
const [startOffset, endOffset] = sourceCode.getRange(node);
|
|
2078
|
+
buffer = new Array(endOffset - startOffset).fill(" ");
|
|
2079
|
+
},
|
|
2080
|
+
":matches(heading, paragraph, tableCell) > text"(node) {
|
|
2081
|
+
const [startOffset, endOffset] = sourceCode.getRange(node);
|
|
2082
|
+
const parentNodeStartOffset = sourceCode.getParent(node).position.start.offset;
|
|
2083
|
+
for (let i = startOffset; i < endOffset; i++) buffer[i - parentNodeStartOffset] = sourceCode.text[i];
|
|
2084
|
+
},
|
|
2085
|
+
":matches(heading, paragraph, tableCell):exit"(node) {
|
|
2086
|
+
const maskedText = buffer.join("");
|
|
2087
|
+
/** @type {Map<string, SourceRange[]>} */
|
|
2088
|
+
const markerGroups = /* @__PURE__ */ new Map();
|
|
2089
|
+
/** @type {RegExpExecArray | null} */
|
|
2090
|
+
let match;
|
|
2091
|
+
while ((match = markerPattern.exec(maskedText)) !== null) {
|
|
2092
|
+
const marker = match[0];
|
|
2093
|
+
const startOffset = match.index + node.position.start.offset;
|
|
2094
|
+
const endOffset = startOffset + marker.length;
|
|
2095
|
+
if (!markerGroups.has(marker)) markerGroups.set(marker, []);
|
|
2096
|
+
markerGroups.get(marker).push([startOffset, endOffset]);
|
|
2097
|
+
}
|
|
2098
|
+
for (const group of markerGroups.values()) for (let i = 0; i < group.length - 1; i += 2) {
|
|
2099
|
+
const startMarker = group[i];
|
|
2100
|
+
reportWhitespace(startMarker[1], startMarker[0], startMarker[1] + 2);
|
|
2101
|
+
const endMarker = group[i + 1];
|
|
2102
|
+
reportWhitespace(endMarker[0] - 1, endMarker[0] - 2, endMarker[1]);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
// vendor/src/rules/no-unused-definitions.js
|
|
2109
|
+
/**
|
|
2110
|
+
* @fileoverview Rule to prevent unused definitions in Markdown.
|
|
2111
|
+
* @author 루밀LuMir(lumirlumir)
|
|
2112
|
+
*/
|
|
2113
|
+
/**
|
|
2114
|
+
* @import { Definition, FootnoteDefinition } from "mdast";
|
|
2115
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
2116
|
+
* @typedef {"unusedDefinition" | "unusedFootnoteDefinition"} NoUnusedDefinitionsMessageIds
|
|
2117
|
+
* @typedef {[{ allowDefinitions?: string[], allowFootnoteDefinitions?: string[], checkFootnoteDefinitions?: boolean }]} NoUnusedDefinitionsOptions
|
|
2118
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoUnusedDefinitionsOptions, MessageIds: NoUnusedDefinitionsMessageIds }>} NoUnusedDefinitionsRuleDefinition
|
|
2119
|
+
*/
|
|
2120
|
+
var no_unused_definitions_default = {
|
|
2121
|
+
meta: {
|
|
2122
|
+
type: "problem",
|
|
2123
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
2124
|
+
docs: {
|
|
2125
|
+
recommended: true,
|
|
2126
|
+
description: "Disallow unused definitions",
|
|
2127
|
+
dialects: ["CommonMark", "GFM"],
|
|
2128
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-unused-definitions.md"
|
|
2129
|
+
},
|
|
2130
|
+
messages: {
|
|
2131
|
+
unusedDefinition: "Unexpected unused definition `{{ identifier }}` (label: `{{ label }}`) found.",
|
|
2132
|
+
unusedFootnoteDefinition: "Unexpected unused footnote definition `{{ identifier }}` (label: `{{ label }}`) found."
|
|
2133
|
+
},
|
|
2134
|
+
schema: [{
|
|
2135
|
+
type: "object",
|
|
2136
|
+
properties: {
|
|
2137
|
+
allowDefinitions: {
|
|
2138
|
+
type: "array",
|
|
2139
|
+
items: { type: "string" },
|
|
2140
|
+
uniqueItems: true
|
|
2141
|
+
},
|
|
2142
|
+
allowFootnoteDefinitions: {
|
|
2143
|
+
type: "array",
|
|
2144
|
+
items: { type: "string" },
|
|
2145
|
+
uniqueItems: true
|
|
2146
|
+
},
|
|
2147
|
+
checkFootnoteDefinitions: { type: "boolean" }
|
|
2148
|
+
},
|
|
2149
|
+
additionalProperties: false
|
|
2150
|
+
}],
|
|
2151
|
+
defaultOptions: [{
|
|
2152
|
+
allowDefinitions: ["//"],
|
|
2153
|
+
allowFootnoteDefinitions: [],
|
|
2154
|
+
checkFootnoteDefinitions: true
|
|
2155
|
+
}]
|
|
2156
|
+
},
|
|
2157
|
+
create(context) {
|
|
2158
|
+
const allowDefinitions = new Set(context.options[0].allowDefinitions.map(normalizeIdentifier$1));
|
|
2159
|
+
const allowFootnoteDefinitions = new Set(context.options[0].allowFootnoteDefinitions.map(normalizeIdentifier$1));
|
|
2160
|
+
const [{ checkFootnoteDefinitions }] = context.options;
|
|
2161
|
+
/** @type {Set<string>} Set to track used identifiers */
|
|
2162
|
+
const usedIdentifiers = /* @__PURE__ */ new Set();
|
|
2163
|
+
/** @type {Set<string>} Set to track used footnote identifiers */
|
|
2164
|
+
const usedFootnoteIdentifiers = /* @__PURE__ */ new Set();
|
|
2165
|
+
/** @type {Set<Definition>} */
|
|
2166
|
+
const definitions = /* @__PURE__ */ new Set();
|
|
2167
|
+
/** @type {Set<FootnoteDefinition>} */
|
|
2168
|
+
const footnoteDefinitions = /* @__PURE__ */ new Set();
|
|
2169
|
+
return {
|
|
2170
|
+
imageReference(node) {
|
|
2171
|
+
usedIdentifiers.add(node.identifier);
|
|
2172
|
+
},
|
|
2173
|
+
linkReference(node) {
|
|
2174
|
+
usedIdentifiers.add(node.identifier);
|
|
2175
|
+
},
|
|
2176
|
+
footnoteReference(node) {
|
|
2177
|
+
usedFootnoteIdentifiers.add(node.identifier);
|
|
2178
|
+
},
|
|
2179
|
+
definition(node) {
|
|
2180
|
+
if (allowDefinitions.has(node.identifier)) return;
|
|
2181
|
+
definitions.add(node);
|
|
2182
|
+
},
|
|
2183
|
+
footnoteDefinition(node) {
|
|
2184
|
+
if (!checkFootnoteDefinitions || allowFootnoteDefinitions.has(node.identifier)) return;
|
|
2185
|
+
footnoteDefinitions.add(node);
|
|
2186
|
+
},
|
|
2187
|
+
"root:exit"() {
|
|
2188
|
+
for (const definition of definitions) if (!usedIdentifiers.has(definition.identifier)) context.report({
|
|
2189
|
+
node: definition,
|
|
2190
|
+
messageId: "unusedDefinition",
|
|
2191
|
+
data: {
|
|
2192
|
+
identifier: definition.identifier,
|
|
2193
|
+
label: definition.label.trim()
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
for (const footnoteDefinition of footnoteDefinitions) if (!usedFootnoteIdentifiers.has(footnoteDefinition.identifier)) context.report({
|
|
2197
|
+
node: footnoteDefinition,
|
|
2198
|
+
messageId: "unusedFootnoteDefinition",
|
|
2199
|
+
data: {
|
|
2200
|
+
identifier: footnoteDefinition.identifier,
|
|
2201
|
+
label: footnoteDefinition.label
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
// vendor/src/rules/require-alt-text.js
|
|
2209
|
+
/**
|
|
2210
|
+
* @fileoverview Rule to require alternative text for images in Markdown.
|
|
2211
|
+
* @author Pixel998
|
|
2212
|
+
*/
|
|
2213
|
+
/**
|
|
2214
|
+
* @import { Image, ImageReference } from "mdast";
|
|
2215
|
+
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
2216
|
+
* @typedef {"altTextRequired"} RequireAltTextMessageIds
|
|
2217
|
+
* @typedef {[]} RequireAltTextOptions
|
|
2218
|
+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: RequireAltTextOptions, MessageIds: RequireAltTextMessageIds }>} RequireAltTextRuleDefinition
|
|
2219
|
+
*/
|
|
2220
|
+
const imgTagPattern = /<img(?:\s(?:[^>"']|"[^"]*"|'[^']*')*)?\/?>/giu;
|
|
2221
|
+
const ariaHiddenTruePattern = /[\s"']aria-hidden\s*=\s*(?:"true"|'true'|true(?=\s|\/?>))/iu;
|
|
2222
|
+
/**
|
|
2223
|
+
* Creates a regex to match HTML attributes
|
|
2224
|
+
* @param {string} name The attribute name to match
|
|
2225
|
+
* @returns {RegExp} Regular expression for matching the attribute
|
|
2226
|
+
*/
|
|
2227
|
+
function getHtmlAttributeRe(name) {
|
|
2228
|
+
return new RegExp(`\\s${name}(?:\\s*=\\s*['"]([^'"]*)['"])?`, "iu");
|
|
2229
|
+
}
|
|
2230
|
+
// vendor/src/build/rules.js
|
|
2231
|
+
var rules_default = {
|
|
2232
|
+
"fenced-code-language": fenced_code_language_default,
|
|
2233
|
+
"fenced-code-meta": fenced_code_meta_default,
|
|
2234
|
+
"heading-increment": heading_increment_default,
|
|
2235
|
+
"no-bare-urls": no_bare_urls_default,
|
|
2236
|
+
"no-duplicate-definitions": no_duplicate_definitions_default,
|
|
2237
|
+
"no-duplicate-headings": no_duplicate_headings_default,
|
|
2238
|
+
"no-empty-definitions": no_empty_definitions_default,
|
|
2239
|
+
"no-empty-images": no_empty_images_default,
|
|
2240
|
+
"no-empty-links": no_empty_links_default,
|
|
2241
|
+
"no-html": no_html_default,
|
|
2242
|
+
"no-invalid-label-refs": no_invalid_label_refs_default,
|
|
2243
|
+
"no-missing-atx-heading-space": no_missing_atx_heading_space_default,
|
|
2244
|
+
"no-missing-label-refs": no_missing_label_refs_default,
|
|
2245
|
+
"no-missing-link-fragments": no_missing_link_fragments_default,
|
|
2246
|
+
"no-multiple-h1": no_multiple_h1_default,
|
|
2247
|
+
"no-reference-like-urls": no_reference_like_urls_default,
|
|
2248
|
+
"no-reversed-media-syntax": no_reversed_media_syntax_default,
|
|
2249
|
+
"no-space-in-emphasis": no_space_in_emphasis_default,
|
|
2250
|
+
"no-unused-definitions": no_unused_definitions_default,
|
|
2251
|
+
"require-alt-text": {
|
|
2252
|
+
meta: {
|
|
2253
|
+
type: "problem",
|
|
2254
|
+
languages: ["markdown/commonmark", "markdown/gfm"],
|
|
2255
|
+
docs: {
|
|
2256
|
+
recommended: true,
|
|
2257
|
+
description: "Require alternative text for images",
|
|
2258
|
+
dialects: ["CommonMark", "GFM"],
|
|
2259
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/require-alt-text.md"
|
|
2260
|
+
},
|
|
2261
|
+
messages: { altTextRequired: "Alternative text for image is required." }
|
|
2262
|
+
},
|
|
2263
|
+
create(context) {
|
|
2264
|
+
const { sourceCode } = context;
|
|
2265
|
+
return {
|
|
2266
|
+
"image, imageReference"(node) {
|
|
2267
|
+
if (node.alt.trim().length === 0) context.report({
|
|
2268
|
+
loc: node.position,
|
|
2269
|
+
messageId: "altTextRequired"
|
|
2270
|
+
});
|
|
2271
|
+
},
|
|
2272
|
+
html(node) {
|
|
2273
|
+
const text = stripHtmlComments(sourceCode.getText(node));
|
|
2274
|
+
/** @type {RegExpExecArray | null} */
|
|
2275
|
+
let match;
|
|
2276
|
+
while ((match = imgTagPattern.exec(text)) !== null) {
|
|
2277
|
+
const imgTag = match[0];
|
|
2278
|
+
if (ariaHiddenTruePattern.test(imgTag)) continue;
|
|
2279
|
+
const altMatch = imgTag.match(getHtmlAttributeRe("alt"));
|
|
2280
|
+
if (!altMatch || altMatch[1] && altMatch[1].trim().length === 0 && altMatch[1].length > 0) {
|
|
2281
|
+
const startOffset = match.index + node.position.start.offset;
|
|
2282
|
+
const endOffset = startOffset + imgTag.length;
|
|
2283
|
+
context.report({
|
|
2284
|
+
loc: {
|
|
2285
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
2286
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
2287
|
+
},
|
|
2288
|
+
messageId: "altTextRequired"
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
},
|
|
2296
|
+
"table-column-count": {
|
|
2297
|
+
meta: {
|
|
2298
|
+
type: "problem",
|
|
2299
|
+
languages: ["markdown/gfm"],
|
|
2300
|
+
docs: {
|
|
2301
|
+
description: "Disallow data rows in a GitHub Flavored Markdown table from having more cells than the header row",
|
|
2302
|
+
dialects: ["GFM"],
|
|
2303
|
+
url: "https://github.com/eslint/markdown/blob/main/docs/rules/table-column-count.md"
|
|
2304
|
+
},
|
|
2305
|
+
messages: {
|
|
2306
|
+
extraCells: "Table column count mismatch (Expected: {{expectedCells}}, Actual: {{actualCells}}), extra data starting here will be ignored.",
|
|
2307
|
+
missingCells: "Table column count mismatch (Expected: {{expectedCells}}, Actual: {{actualCells}}), row might be missing data."
|
|
2308
|
+
},
|
|
2309
|
+
schema: [{
|
|
2310
|
+
type: "object",
|
|
2311
|
+
properties: { checkMissingCells: { type: "boolean" } },
|
|
2312
|
+
additionalProperties: false
|
|
2313
|
+
}],
|
|
2314
|
+
defaultOptions: [{ checkMissingCells: false }]
|
|
2315
|
+
},
|
|
2316
|
+
create(context) {
|
|
2317
|
+
const [{ checkMissingCells }] = context.options;
|
|
2318
|
+
return { table(node) {
|
|
2319
|
+
if (node.children.length < 1) return;
|
|
2320
|
+
const expectedCellsLength = node.children[0].children.length;
|
|
2321
|
+
for (let i = 1; i < node.children.length; i++) {
|
|
2322
|
+
const currentRow = node.children[i];
|
|
2323
|
+
const actualCellsLength = currentRow.children.length;
|
|
2324
|
+
const lastActualCellNode = currentRow.children[actualCellsLength - 1];
|
|
2325
|
+
if (actualCellsLength > expectedCellsLength) {
|
|
2326
|
+
const firstExtraCellNode = currentRow.children[expectedCellsLength];
|
|
2327
|
+
context.report({
|
|
2328
|
+
loc: {
|
|
2329
|
+
start: firstExtraCellNode.position.start,
|
|
2330
|
+
end: lastActualCellNode.position.end
|
|
2331
|
+
},
|
|
2332
|
+
messageId: "extraCells",
|
|
2333
|
+
data: {
|
|
2334
|
+
actualCells: actualCellsLength,
|
|
2335
|
+
expectedCells: expectedCellsLength
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
} else if (checkMissingCells && actualCellsLength < expectedCellsLength) context.report({
|
|
2339
|
+
loc: {
|
|
2340
|
+
start: {
|
|
2341
|
+
column: lastActualCellNode.position.end.column - 1,
|
|
2342
|
+
line: lastActualCellNode.position.end.line
|
|
2343
|
+
},
|
|
2344
|
+
end: currentRow.position.end
|
|
2345
|
+
},
|
|
2346
|
+
messageId: "missingCells",
|
|
2347
|
+
data: {
|
|
2348
|
+
actualCells: actualCellsLength,
|
|
2349
|
+
expectedCells: expectedCellsLength
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
} };
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
export { processor as a, MarkdownSourceCode as i, MarkdownLanguage as n, InlineConfigComment as r, rules_default as t };
|