eslint-plugin-md-style 0.1.0 → 0.3.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/dist/index.mjs CHANGED
@@ -1,18 +1,8 @@
1
1
  import markdown, { MarkdownLanguage } from "@eslint/markdown";
2
-
3
- //#region src/utils/index.ts
4
- function createRule({ create, defaultOptions, meta }) {
5
- return {
6
- create,
7
- meta: {
8
- defaultOptions,
9
- ...meta
10
- }
11
- };
2
+ //#region src/parser/ast.ts
3
+ function isObject(val) {
4
+ return !!val && typeof val === "object";
12
5
  }
13
-
14
- //#endregion
15
- //#region src/utils/ast.ts
16
6
  /**
17
7
  * Checks whether an unknown value behaves like an mdast parent node.
18
8
  *
@@ -20,30 +10,13 @@ function createRule({ create, defaultOptions, meta }) {
20
10
  * not expose mdast-specific types.
21
11
  */
22
12
  function hasChildren(node) {
23
- return !!node && typeof node === "object" && "children" in node && Array.isArray(node.children);
24
- }
25
- function isTableCell(node) {
26
- return node.type === "tableCell";
13
+ return isObject(node) && "children" in node && Array.isArray(node.children);
27
14
  }
28
- const INLINE_ELEMENT_TYPES = new Set([
29
- "link",
30
- "image",
31
- "inlineCode",
32
- "emphasis",
33
- "strong"
34
- ]);
35
- /**
36
- * Checks whether a phrasing node is one of the selected inline element targets.
37
- */
38
- function isInlineElement(node) {
39
- return !!node && INLINE_ELEMENT_TYPES.has(node.type);
15
+ function isCodeNode(node) {
16
+ return node?.type === "code";
40
17
  }
41
- /**
42
- * Checks whether the current inline element is nested inside another selected inline element.
43
- */
44
- function isNestedInlineElement(nodeContext) {
45
- const { parent } = nodeContext;
46
- return isInlineElement(parent);
18
+ function isTableCell(node) {
19
+ return node?.type === "tableCell";
47
20
  }
48
21
  /**
49
22
  * Extracts the plain-text value of a phrasing node.
@@ -52,7 +25,7 @@ function isNestedInlineElement(nodeContext) {
52
25
  function getNodeValue(node) {
53
26
  if (!node) return;
54
27
  if ("value" in node) return node.value;
55
- if (hasChildren(node)) return node.children.map(getNodeValue).join("") || void 0;
28
+ if (hasChildren(node)) return node.children.map(getNodeValue).join("");
56
29
  }
57
30
  /**
58
31
  * Gets the start and end offsets for a node.
@@ -80,7 +53,6 @@ function getNodeContext(context, node) {
80
53
  current: node
81
54
  };
82
55
  const currentIndex = parent.children.findIndex((child) => child === node);
83
- /* v8 ignore if -- @preserve */
84
56
  if (currentIndex === -1) return {
85
57
  parent,
86
58
  prev: void 0,
@@ -95,18 +67,606 @@ function getNodeContext(context, node) {
95
67
  };
96
68
  }
97
69
  /**
98
- * Gets the character adjacent to the start or end of a string.
70
+ * Returns the current node and adjacent siblings from a known children array.
71
+ * Useful when iterating a tokenized child list directly without an ESLint ancestor context.
72
+ */
73
+ function getNodeContextByParent(childrenNodes, currentIndex) {
74
+ return {
75
+ prev: childrenNodes[currentIndex - 1],
76
+ current: childrenNodes[currentIndex],
77
+ next: childrenNodes[currentIndex + 1]
78
+ };
79
+ }
80
+ /**
81
+ * Gets the first or last visible character of a string after trimming
82
+ * surrounding whitespace.
99
83
  */
100
84
  function getAdjacentChar(str, position) {
101
85
  if (!str) return void 0;
102
86
  str = str.trim();
103
87
  return position === "head" ? str[0] : str[str.length - 1];
104
88
  }
105
-
89
+ //#endregion
90
+ //#region src/parser/custom-container/parse.ts
91
+ /**
92
+ * Parses top-level text into container, blank-line, and text nodes.
93
+ */
94
+ function parseCustomContainers(value) {
95
+ const state = {
96
+ splits: splitLines(value),
97
+ offset: 0
98
+ };
99
+ const nodes = [];
100
+ while (state.splits.length) {
101
+ const item = state.splits[0];
102
+ const previous = nodes.at(-1);
103
+ if (isOpenTag(item)) {
104
+ nodes.push(parseCustomContainer(state, 1, previous));
105
+ continue;
106
+ }
107
+ const consumed = consume(state);
108
+ if (isBlank(consumed.value)) {
109
+ const blankNode = parseBlankNode({
110
+ newline: consumed.value,
111
+ prevNode: previous
112
+ });
113
+ if (blankNode) nodes.push(blankNode);
114
+ continue;
115
+ }
116
+ nodes.push({
117
+ type: "text",
118
+ value: consumed.value,
119
+ position: consumed.position
120
+ });
121
+ }
122
+ return nodes;
123
+ }
124
+ /**
125
+ * Parses one container by consuming its opening tag, nested containers, content,
126
+ * and a closing fence whose marker is at least as wide as the opening fence.
127
+ */
128
+ function parseCustomContainer(state, depth, parentNode) {
129
+ const children = [];
130
+ const raw = state.splits[0];
131
+ const openTag = parseOpenTag(raw, parentNode);
132
+ consume(state);
133
+ children.push(openTag);
134
+ while (state.splits.length) {
135
+ const item = state.splits[0];
136
+ const previous = children.at(-1);
137
+ if (parseOpenTag(item, previous)) {
138
+ children.push(parseCustomContainer(state, depth + 1, previous));
139
+ continue;
140
+ }
141
+ const closeTag = parseCloseTag(item, previous);
142
+ const consumed = consume(state);
143
+ if (closeTag && closeTag.markerLength >= openTag.markerLength) {
144
+ children.push(closeTag);
145
+ break;
146
+ }
147
+ if (isBlank(consumed.value)) {
148
+ const blankNode = parseBlankNode({
149
+ newline: consumed.value,
150
+ prevNode: previous
151
+ });
152
+ if (blankNode) children.push(blankNode);
153
+ continue;
154
+ }
155
+ children.push({
156
+ type: "text",
157
+ value: consumed.value,
158
+ position: consumed.position
159
+ });
160
+ }
161
+ return {
162
+ type: "custom-container",
163
+ depth,
164
+ children,
165
+ position: {
166
+ start: openTag.position.start,
167
+ end: children.at(-1)?.position.end ?? openTag.position.end
168
+ }
169
+ };
170
+ }
171
+ /**
172
+ * Consumes the next split and advances its source offset.
173
+ */
174
+ function consume(state) {
175
+ const value = state.splits.shift();
176
+ const start = state.offset;
177
+ state.offset += value.length;
178
+ return {
179
+ value,
180
+ position: {
181
+ start,
182
+ end: state.offset
183
+ }
184
+ };
185
+ }
186
+ /**
187
+ * Splits text and keeps line-break tokens so their exact source ranges survive parsing.
188
+ */
189
+ function splitLines(value) {
190
+ return value.split(/(\r?\n)/).filter(Boolean);
191
+ }
192
+ /**
193
+ * Parses an opening custom-container tag and calculates its source ranges.
194
+ */
195
+ function parseOpenTag(raw, prevNode) {
196
+ const match = raw.match(CUSTOM_CONTAINER_OPEN_MARKER_RE);
197
+ /* v8 ignore if -- @preserve */
198
+ if (!match) return null;
199
+ const [_, rawMarker, type, titleAndAttrs] = match;
200
+ const tag = rawMarker + type;
201
+ const marker = rawMarker.trim();
202
+ const offset = prevNode?.position.end || 0;
203
+ const valueStart = offset + tag.length - type.length;
204
+ const value = {
205
+ content: type,
206
+ start: valueStart,
207
+ end: valueStart + type.length
208
+ };
209
+ const titleAndAttrsValue = titleAndAttrs?.trim() || "";
210
+ const attributeMatch = titleAndAttrsValue.match(CUSTOM_CONTAINER_ATTRS_RE);
211
+ const attrs = attributeMatch ? {
212
+ content: attributeMatch[1].slice(1, -1),
213
+ raw: attributeMatch[1]
214
+ } : void 0;
215
+ const title = (attributeMatch ? titleAndAttrsValue.slice(0, attributeMatch.index).trim() : titleAndAttrsValue) || void 0;
216
+ return {
217
+ type: "open",
218
+ markerLength: marker.length,
219
+ raw,
220
+ value,
221
+ title,
222
+ ...attrs ? { attribute: attrs } : {},
223
+ position: {
224
+ start: offset,
225
+ end: offset + raw.length
226
+ }
227
+ };
228
+ }
229
+ /**
230
+ * Parses the exact closing marker,
231
+ * `:::` and calculates its source range.
232
+ */
233
+ function parseCloseTag(raw, prevNode) {
234
+ const match = raw.match(CUSTOM_CONTAINER_CLOSE_MARKER_STRICT_RE);
235
+ /* v8 ignore if -- @preserve */
236
+ if (!match) return null;
237
+ const marker = match[0].match(/:{3,}/)?.[0] ?? "";
238
+ const start = prevNode?.position.end || 0;
239
+ const end = start + raw.length;
240
+ return {
241
+ type: "close",
242
+ markerLength: marker.length,
243
+ raw,
244
+ value: {
245
+ content: raw,
246
+ start,
247
+ end
248
+ },
249
+ position: {
250
+ start,
251
+ end
252
+ }
253
+ };
254
+ }
255
+ /**
256
+ * Adds a blank-line node
257
+ * merging it with the preceding blank line when possible.
258
+ */
259
+ function parseBlankNode(opts) {
260
+ const { newline = "\n", prevNode } = opts || {};
261
+ if (prevNode?.type === "blank") {
262
+ prevNode.value += newline;
263
+ prevNode.position.end += newline.length;
264
+ return null;
265
+ }
266
+ const start = prevNode?.position.end || 0;
267
+ return {
268
+ type: "blank",
269
+ value: newline,
270
+ position: {
271
+ start,
272
+ end: start + newline.length
273
+ }
274
+ };
275
+ }
276
+ const CUSTOM_CONTAINER_TYPE_SET = /* @__PURE__ */ new Set([
277
+ "info",
278
+ "tip",
279
+ "warning",
280
+ "danger",
281
+ "details",
282
+ "raw",
283
+ "code-group",
284
+ "v-pre",
285
+ "tabs"
286
+ ]);
287
+ const CUSTOM_CONTAINER_MARKER_PATTERN = String.raw`(^ {0,3}:{3,}[ \t]*)`;
288
+ const CUSTOM_CONTAINER_TYPE_PATTERN = String.raw`([\w-]+)(?=$|[ \t]|\r?\n)`;
289
+ const CUSTOM_CONTAINER_TITLE_AND_ATTRS_PATTERN = String.raw`([ \t][^\r\n]*)?`;
290
+ const CUSTOM_CONTAINER_CLOSE_MARKER_PATTERN = String.raw` {0,3}:{3,}[ \t]*`;
291
+ const CUSTOM_CONTAINER_OPEN_MARKER_RE = new RegExp(`${CUSTOM_CONTAINER_MARKER_PATTERN}${CUSTOM_CONTAINER_TYPE_PATTERN}${CUSTOM_CONTAINER_TITLE_AND_ATTRS_PATTERN}`);
292
+ const CUSTOM_CONTAINER_CLOSE_MARKER_RE = new RegExp(`^\r?\n${CUSTOM_CONTAINER_CLOSE_MARKER_PATTERN}$`);
293
+ const CUSTOM_CONTAINER_CLOSE_MARKER_STRICT_RE = new RegExp(`^${CUSTOM_CONTAINER_CLOSE_MARKER_PATTERN}$`);
294
+ const CUSTOM_CONTAINER_ATTRS_RE = /(\{[^{}]+\})\s*$/;
295
+ /**
296
+ * Checks whether a value is one of the custom-container types supported by VitePress.
297
+ */
298
+ function isCustomContainerType(value) {
299
+ return CUSTOM_CONTAINER_TYPE_SET.has(value);
300
+ }
301
+ function isOpenTag(value) {
302
+ return CUSTOM_CONTAINER_OPEN_MARKER_RE.test(value);
303
+ }
304
+ function isBlank(value) {
305
+ return value === "\n" || value === "\r\n";
306
+ }
307
+ /**
308
+ * Checks whether adjacent text is a custom container marker on the next line.
309
+ *
310
+ * @see https://vitepress.dev/guide/markdown#custom-containers
311
+ * @example `::: info` -> true (open tag)
312
+ * @example `:::: tip` -> true
313
+ * @example `:::` -> false (close tag)
314
+ */
315
+ function isCustomContainerMarker(str) {
316
+ if (!str) return false;
317
+ return CUSTOM_CONTAINER_CLOSE_MARKER_RE.test(str) || CUSTOM_CONTAINER_OPEN_MARKER_RE.test(str);
318
+ }
319
+ const isCustomContainerNode = (node) => !!node && node.type === "custom-container";
320
+ const isBlankNode = (node) => !!node && node.type === "blank";
321
+ const isOpenNode = (node) => !!node && node.type === "open";
322
+ const isCloseNode = (node) => !!node && node.type === "close";
323
+ /**
324
+ * Ignore `Custom-containerNode` in `CodeNode` & `InlineCodeNode`.
325
+ * Collects source offsets for fenced code blocks and inline code nodes.
326
+ */
327
+ function getCodeNodeRanges(node) {
328
+ const ranges = [];
329
+ function visit(current) {
330
+ if (!isObject(current)) return;
331
+ if (isCodeNode(current)) {
332
+ const position = current.position;
333
+ if (!position) return;
334
+ const start = position.start?.offset;
335
+ const end = position.end?.offset;
336
+ if (start !== void 0 && end !== void 0) ranges.push({
337
+ start,
338
+ end
339
+ });
340
+ }
341
+ if (hasChildren(current)) for (const child of current.children) visit(child);
342
+ }
343
+ visit(node);
344
+ return ranges;
345
+ }
346
+ //#endregion
347
+ //#region src/utils/index.ts
348
+ function createRule({ create, defaultOptions, meta }) {
349
+ return {
350
+ create,
351
+ meta: {
352
+ defaultOptions,
353
+ ...meta
354
+ }
355
+ };
356
+ }
357
+ //#endregion
358
+ //#region src/rules/padding-around-custom-container/analyzs.ts
359
+ /**
360
+ * Analyzes custom containers and returns de-duplicated padding issues.
361
+ */
362
+ function getNodesIssues(nodes, opts) {
363
+ const { offset = 0, ignoreRanges = [], mode = "loose" } = opts || {};
364
+ const issues = [];
365
+ /**
366
+ * Recursively analyzes every custom-container level in a parsed document.
367
+ */
368
+ function analyzeLevel(nodes, opts) {
369
+ for (let index = 0; index < nodes.length; index++) {
370
+ const container = nodes[index];
371
+ if (!isCustomContainerNode(container)) continue;
372
+ const boundary = getContainerBoundary(container);
373
+ if (!boundary) continue;
374
+ analyzeInnerBoundary(container.children, boundary, opts);
375
+ analyzeOuterBoundary(nodes, index, opts);
376
+ analyzeLevel(container.children, opts);
377
+ }
378
+ }
379
+ analyzeLevel(nodes, {
380
+ offset,
381
+ issues,
382
+ mode
383
+ });
384
+ return dedupeIssues(issues).filter((issue) => !ignoreRanges.some((range) => issue.start < range.end && issue.end > range.start));
385
+ }
386
+ /**
387
+ * Checks the blank lines immediately inside a container's opening and closing tags.
388
+ */
389
+ function analyzeInnerBoundary(children, containerBoundary, opts) {
390
+ const { openIndex, closeIndex } = containerBoundary;
391
+ const first = children[openIndex + 1];
392
+ checkInnerSide(first, {
393
+ direction: 1,
394
+ children,
395
+ ...opts
396
+ });
397
+ const last = children[closeIndex - 1];
398
+ if (last !== first) checkInnerSide(last, {
399
+ direction: -1,
400
+ children,
401
+ ...opts
402
+ });
403
+ }
404
+ /**
405
+ * Checks one side of a container's inner boundary for the selected mode.
406
+ */
407
+ function checkInnerSide(node, opts) {
408
+ const { direction, children, offset } = opts;
409
+ if (isBlankNode(node)) {
410
+ const lineBreak = getLineBreak(node.value);
411
+ const expected = opts.mode === "loose" ? `${lineBreak}${lineBreak}` : lineBreak;
412
+ if (node.value !== expected) opts.issues.push({
413
+ start: offset + node.position.start,
414
+ end: offset + node.position.end,
415
+ replacement: expected,
416
+ messageId: node.value.length < expected.length ? MESSAGE_IDS$4.missing : MESSAGE_IDS$4.unexpected
417
+ });
418
+ return;
419
+ }
420
+ if (opts.mode === "loose" && node && !isOpenNode(node) && !isCloseNode(node)) {
421
+ const insertion = direction === 1 ? node.position.start : node.position.end;
422
+ opts.issues.push({
423
+ start: offset + insertion,
424
+ end: offset + insertion,
425
+ replacement: getLineBreakFromChildren(children) + getLineBreakFromChildren(children),
426
+ messageId: MESSAGE_IDS$4.missing
427
+ });
428
+ }
429
+ }
430
+ /**
431
+ * Finds the first opening tag and last closing tag for a custom container.
432
+ */
433
+ function getContainerBoundary(container) {
434
+ const openIndex = container.children.findIndex(isOpenNode);
435
+ const closeIndex = container.children.findLastIndex(isCloseNode);
436
+ if (openIndex === -1 || closeIndex <= openIndex) return;
437
+ return {
438
+ openIndex,
439
+ closeIndex
440
+ };
441
+ }
442
+ /**
443
+ * Checks both sides of a container for the required external blank line.
444
+ */
445
+ function analyzeOuterBoundary(children, boundaryIndex, opts) {
446
+ checkOuterSide(children, {
447
+ direction: -1,
448
+ boundaryIndex,
449
+ ...opts
450
+ });
451
+ checkOuterSide(children, {
452
+ direction: 1,
453
+ boundaryIndex,
454
+ ...opts
455
+ });
456
+ }
457
+ /**
458
+ * Checks one side of a container and records insertion or normalization issues.
459
+ */
460
+ function checkOuterSide(children, opts) {
461
+ const { boundaryIndex, direction, offset, issues } = opts;
462
+ const blankIndex = boundaryIndex + direction;
463
+ const blank = children[blankIndex];
464
+ const contentIndex = isBlankNode(blank) ? blankIndex + direction : blankIndex;
465
+ const content = children[contentIndex];
466
+ if (!isExternalContent(content, {
467
+ index: contentIndex,
468
+ length: children.length
469
+ })) return;
470
+ if (!isBlankNode(blank)) {
471
+ const insertion = direction === -1 ? children[boundaryIndex].position.start : children[boundaryIndex].position.end;
472
+ issues.push({
473
+ start: offset + insertion,
474
+ end: offset + insertion,
475
+ replacement: getLineBreakFromChildren(children),
476
+ messageId: MESSAGE_IDS$4.missing
477
+ });
478
+ return;
479
+ }
480
+ const lineBreak = getLineBreak(blank.value);
481
+ const expected = `${lineBreak}${lineBreak}`;
482
+ if (blank.value === expected) return;
483
+ issues.push({
484
+ start: offset + blank.position.start,
485
+ end: offset + blank.position.end,
486
+ replacement: expected,
487
+ messageId: blank.value.length < expected.length ? MESSAGE_IDS$4.missing : MESSAGE_IDS$4.unexpected
488
+ });
489
+ }
490
+ /**
491
+ * Returns whether a node is content outside the container's own boundary tags.
492
+ */
493
+ function isExternalContent(node, opts) {
494
+ if (!node || isBlankNode(node)) return false;
495
+ const { index, length } = opts;
496
+ if (index === 0 && isOpenNode(node)) return false;
497
+ if (index === length - 1 && isCloseNode(node)) return false;
498
+ return true;
499
+ }
500
+ /**
501
+ * Removes issues that target the same range with the same replacement.
502
+ */
503
+ function dedupeIssues(issues) {
504
+ const seen = /* @__PURE__ */ new Set();
505
+ return issues.filter((edit) => {
506
+ const key = `${edit.start}:${edit.end}:${edit.replacement}`;
507
+ if (seen.has(key)) return false;
508
+ seen.add(key);
509
+ return true;
510
+ });
511
+ }
512
+ /**
513
+ * Returns the line-break sequence used by a string.
514
+ */
515
+ function getLineBreak(value) {
516
+ return value.includes("\r\n") ? "\r\n" : "\n";
517
+ }
518
+ /**
519
+ * Uses the first blank child to infer a container's line-break sequence.
520
+ */
521
+ function getLineBreakFromChildren(children) {
522
+ const blank = children.find((child) => isBlankNode(child));
523
+ return isBlankNode(blank) ? getLineBreak(blank.value) : "\n";
524
+ }
525
+ //#endregion
526
+ //#region src/rules/padding-around-custom-container/index.ts
527
+ const RULE_NAME$6 = "padding-around-custom-container";
528
+ const MESSAGE_IDS$4 = {
529
+ missing: "missing",
530
+ unexpected: "unexpected"
531
+ };
532
+ var padding_around_custom_container_default = createRule({
533
+ name: RULE_NAME$6,
534
+ meta: {
535
+ type: "layout",
536
+ docs: { description: "Enforce padding around VitePress custom containers." },
537
+ messages: {
538
+ missing: "A custom container must be separated from surrounding content by one blank line.",
539
+ unexpected: "Unexpected blank lines around a custom container."
540
+ },
541
+ fixable: "whitespace",
542
+ schema: [{ enum: ["compact", "loose"] }]
543
+ },
544
+ defaultOptions: ["loose"],
545
+ create(context) {
546
+ return { root(node) {
547
+ const { position, start } = getNodePosition(node);
548
+ /* v8 ignore if -- @preserve */
549
+ if (!position) return;
550
+ const source = context.sourceCode.text;
551
+ const ignoreRanges = getCodeNodeRanges(node);
552
+ const nodes = parseCustomContainers(source.slice(start));
553
+ const [mode] = context.options;
554
+ const issues = getNodesIssues(nodes, {
555
+ offset: start,
556
+ ignoreRanges,
557
+ mode
558
+ });
559
+ for (const { start, end, messageId, replacement } of issues) context.report({
560
+ node,
561
+ messageId,
562
+ loc: {
563
+ start: context.sourceCode.getLocFromIndex(start),
564
+ end: context.sourceCode.getLocFromIndex(end)
565
+ },
566
+ fix: (fixer) => fixer.replaceTextRange([start, end], replacement)
567
+ });
568
+ } };
569
+ }
570
+ });
571
+ //#endregion
572
+ //#region src/rules/space-around-custom-container/analyzs.ts
573
+ /**
574
+ * Finds custom-container marker lines and normalizes their surrounding spaces.
575
+ *
576
+ * Opening markers use exactly one space between the fence and the type,
577
+ * while closing markers contain only the fence. Leading indentation is removed.
578
+ * Ranges in `ignoredRanges` (for example fenced code blocks) are skipped.
579
+ */
580
+ function getSourceIssues(source, ignoredRanges = []) {
581
+ const issues = [];
582
+ function visit(nodes) {
583
+ for (const node of nodes) {
584
+ if (!isCustomContainerNode(node)) continue;
585
+ for (const child of node.children) if (isOpenNode(child) || isCloseNode(child)) analyzeMarker(child, ignoredRanges, issues);
586
+ else if (isCustomContainerNode(child)) visit([child]);
587
+ }
588
+ }
589
+ visit(parseCustomContainers(source));
590
+ return issues;
591
+ }
592
+ /**
593
+ * Adds a spacing issue for one parsed opening or closing marker.
594
+ */
595
+ function analyzeMarker(tag, ignoredRanges, issues) {
596
+ if (ignoredRanges.some((range) => tag.position.start < range.end && tag.position.end > range.start)) return;
597
+ const replacement = normalizedMarker(tag);
598
+ if (tag.raw === replacement) return;
599
+ issues.push({
600
+ ...tag.position,
601
+ replacement,
602
+ messageId: getMarkerMessageId(tag)
603
+ });
604
+ }
605
+ /**
606
+ * Returns the normalized source text for a parsed container marker.
607
+ */
608
+ function normalizedMarker(tag) {
609
+ const fence = ":".repeat(tag.markerLength);
610
+ if (isCloseNode(tag)) return fence;
611
+ const valueStart = tag.value.start - tag.position.start;
612
+ return `${fence} ${tag.raw.slice(valueStart).trim()}`;
613
+ }
614
+ /**
615
+ * Classifies the spacing problem represented by a parsed marker.
616
+ */
617
+ function getMarkerMessageId(tag) {
618
+ if (tag.raw.length !== tag.raw.trimStart().length) return MESSAGE_IDS$3.unexpectedIndentation;
619
+ if (isCloseNode(tag)) return MESSAGE_IDS$3.unexpectedTrailingSpace;
620
+ if (tag.raw.slice(tag.markerLength, tag.value.start - tag.position.start).length === 0) return MESSAGE_IDS$3.missingSeparator;
621
+ return MESSAGE_IDS$3.unexpectedSeparator;
622
+ }
623
+ //#endregion
624
+ //#region src/rules/space-around-custom-container/index.ts
625
+ const RULE_NAME$5 = "space-around-custom-container";
626
+ const MESSAGE_IDS$3 = {
627
+ unexpectedIndentation: "unexpectedIndentation",
628
+ missingSeparator: "missingSeparator",
629
+ unexpectedSeparator: "unexpectedSeparator",
630
+ unexpectedTrailingSpace: "unexpectedTrailingSpace"
631
+ };
632
+ var space_around_custom_container_default = createRule({
633
+ name: RULE_NAME$5,
634
+ meta: {
635
+ type: "layout",
636
+ docs: { description: "Enforce spacing around VitePress custom-container markers." },
637
+ messages: {
638
+ unexpectedIndentation: "Do not indent a custom-container marker.",
639
+ missingSeparator: "Add one space between the opening fence and its type.",
640
+ unexpectedSeparator: "Use exactly one space between the opening fence and its type.",
641
+ unexpectedTrailingSpace: "Remove trailing spaces from a custom-container closing marker."
642
+ },
643
+ fixable: "whitespace",
644
+ schema: []
645
+ },
646
+ defaultOptions: [],
647
+ create(context) {
648
+ return { root(node) {
649
+ const { position } = getNodePosition(node);
650
+ /* v8 ignore if -- @preserve */
651
+ if (!position) return;
652
+ const source = context.sourceCode.text;
653
+ const issues = getSourceIssues(source, getCodeNodeRanges(node));
654
+ for (const { start, end, messageId, replacement } of issues) context.report({
655
+ node,
656
+ messageId,
657
+ loc: {
658
+ start: context.sourceCode.getLocFromIndex(start),
659
+ end: context.sourceCode.getLocFromIndex(end)
660
+ },
661
+ fix: (fixer) => fixer.replaceTextRange([start, end], replacement)
662
+ });
663
+ } };
664
+ }
665
+ });
106
666
  //#endregion
107
667
  //#region src/utils/anchor.ts
108
668
  /**
109
- * Match the trailing anchor-like fragment from a heading string.
669
+ * Match the trailing anchor-like fragment from a heading or adjacent text.
110
670
  * @example `中文标题 {#Chinese-Title}` -> `{#Chinese-Title}`
111
671
  * @example `使用 describe #Grouping Tests` -> `#Grouping Tests`
112
672
  */
@@ -126,52 +686,15 @@ function getLikeAnchor(str) {
126
686
  if (str === void 0) return null;
127
687
  const match = getLikeAnchorMatch(str);
128
688
  if (!match) return null;
129
- const rawLikeAnchor = match.replace(/(\{|\})/g, "").replace(/^#/, "").trimStart();
689
+ const rawLikeAnchor = match.replace(/[{}]/g, "").replace(/^#/, "").trimStart();
130
690
  return {
131
691
  isLikeAnchor: rawLikeAnchor.includes(" "),
132
692
  rawLikeAnchor
133
693
  };
134
694
  }
135
- /**
136
- * Check if the string has an anchor.
137
- * @example: {#chinese-anchor}
138
- */
139
- function isStrictAnchor(str) {
140
- return /\s\{#[a-z0-9]+(?:-[a-z0-9]+)*\}/.test(str);
141
- }
142
- /**
143
- * Check whether the string contains CJK Han characters.
144
- */
145
- function hasChinese(str) {
146
- return /[\u4E00-\u9FA5]/.test(str);
147
- }
148
- /**
149
- * Normalize raw anchor text into the strict anchor format content.
150
- * - lowercase all letters
151
- * - convert spaces to `-`
152
- * - remove unsupported characters
153
- * - trim leading/trailing `-`
154
- */
155
- function normalizeAnchor(anchor) {
156
- return anchor.toLowerCase().replace(/[\s.]/g, "-").replace(/[^a-z0-9_-]/g, "").replace(/^-+|-+$/g, "");
157
- }
158
- /**
159
- * Count wrapper characters contributed by the trailing like-anchor fragment.
160
- * The value is the length difference between the raw matched fragment and the
161
- * cleaned anchor text returned by `getLikeAnchor`.
162
- * @example `# 中文标题 {#Chinese-Title}` -> 3
163
- * @example `## 使用 \`describe\` 编组测试 #Grouping Tests with \`describe\`` -> 1
164
- */
165
- function calcAnchorPositionCompensate(content) {
166
- const match = getLikeAnchorMatch(content);
167
- const anchor = getLikeAnchor(content);
168
- if (!match || !anchor) return 0;
169
- return match.length - anchor.rawLikeAnchor.length;
170
- }
171
-
172
695
  //#endregion
173
696
  //#region src/utils/punctuation.ts
174
- const OPENING_PAIRED_PUNCTUATION = new Set([
697
+ const OPENING_PAIRED_PUNCTUATION = /* @__PURE__ */ new Set([
175
698
  "(",
176
699
  "[",
177
700
  "{",
@@ -182,7 +705,7 @@ const OPENING_PAIRED_PUNCTUATION = new Set([
182
705
  "“",
183
706
  "‘"
184
707
  ]);
185
- const CLOSING_PAIRED_PUNCTUATION = new Set([
708
+ const CLOSING_PAIRED_PUNCTUATION = /* @__PURE__ */ new Set([
186
709
  ")",
187
710
  "]",
188
711
  "}",
@@ -208,6 +731,16 @@ function isFullwidthPunctuation(str) {
208
731
  if (!str || str.length !== 1) return false;
209
732
  return /^[\u3001-\u303F\uFE10-\uFE1F\uFE30-\uFE4F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65“”‘’…]$/u.test(str);
210
733
  }
734
+ const HALFWIDTH_PUNCTUATION_RE = /^\p{P}$/u;
735
+ /**
736
+ * Checks whether the character is halfwidth punctuation.
737
+ * @example `,` -> true
738
+ * @example `,` -> false
739
+ */
740
+ function isHalfwidthPunctuation(str) {
741
+ if (!str || str.length !== 1) return false;
742
+ return str.charCodeAt(0) <= 126 && HALFWIDTH_PUNCTUATION_RE.test(str);
743
+ }
211
744
  const DASH_PUNCTUATION_RE = /^[-\u2013\u2014\u2212]$/u;
212
745
  /**
213
746
  * Checks whether the character is hyphen-like punctuation.
@@ -240,7 +773,6 @@ function hasPunctuation(str, position = "head") {
240
773
  if (position === "head") return isPunctuation(str[0]);
241
774
  else return isPunctuation(str[str.length - 1]);
242
775
  }
243
-
244
776
  //#endregion
245
777
  //#region src/utils/space.ts
246
778
  /**
@@ -273,54 +805,8 @@ function getWhiteSpace(str, position = "head") {
273
805
  };
274
806
  }
275
807
  }
276
- /**
277
- * Gets whitespace and punctuation information for text adjacent to a link or inline code node.
278
- */
279
- function getSpaceContext(nodeContext) {
280
- const { prev, next } = nodeContext;
281
- const prevValue = getNodeValue(prev);
282
- const nextValue = getNodeValue(next);
283
- return {
284
- prev: {
285
- value: prevValue,
286
- whiteSpace: getWhiteSpace(prevValue, "tail"),
287
- hasPunctuation: hasPunctuation(prevValue, "tail"),
288
- punctuationType: isFullwidthPunctuation(getAdjacentChar(prevValue, "tail")) ? "full" : "half"
289
- },
290
- next: {
291
- value: nextValue,
292
- whiteSpace: getWhiteSpace(nextValue),
293
- hasPunctuation: hasPunctuation(nextValue),
294
- punctuationType: isFullwidthPunctuation(getAdjacentChar(nextValue, "head")) ? "full" : "half"
295
- }
296
- };
297
- }
298
-
299
808
  //#endregion
300
- //#region src/utils/inline-element.ts
301
- const INLINE_SPACE_MESSAGE_IDS = {
302
- missingSpaceBefore: "missingSpaceBefore",
303
- missingSpaceAfter: "missingSpaceAfter",
304
- multipleSpacesBefore: "multipleSpacesBefore",
305
- multipleSpacesAfter: "multipleSpacesAfter",
306
- multipleSpacesAfterPunctuation: "multipleSpacesAfterPunctuation",
307
- unexpectedSpaceBefore: "unexpectedSpaceBefore",
308
- unexpectedSpaceAfter: "unexpectedSpaceAfter"
309
- };
310
- /**
311
- * Checks whether adjacent text is a custom container marker on the next line.
312
- *
313
- * @deprecated Temporary workaround to prevent space-between-link from reporting
314
- * false positives on custom containers. Remove this and handle the case in a
315
- * dedicated custom container rule when one exists.
316
- * @see https://vitepress.dev/guide/markdown#custom-containers
317
- * @example `\n:::` -> true
318
- * @example `\n::::` -> true
319
- * @example `:::` -> false
320
- */
321
- function isCustomContainerMarker(str) {
322
- return /^[ \t]*\n[ \t]*:{3,}[ \t]*$/u.test(str || "");
323
- }
809
+ //#region src/rules/space-around-inline-element/analyze.ts
324
810
  /**
325
811
  * Validates whether a spacing run contains exactly one required space.
326
812
  */
@@ -334,18 +820,18 @@ function validateSingleRequiredSpace(count, missingSpaceMessageId, multipleSpace
334
820
  function validateBeforePunctuation(context) {
335
821
  const adjacentChar = getAdjacentChar(context.value, "tail");
336
822
  if (OPENING_PAIRED_PUNCTUATION.has(adjacentChar || "") || isSlashPunctuation(adjacentChar)) {
337
- if (context.whiteSpace.count > 0) return INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceBefore;
823
+ if (context.whiteSpace.count > 0) return "unexpected-space-before";
338
824
  return;
339
825
  }
340
- if (context.punctuationType === "half") return validateSingleRequiredSpace(context.whiteSpace.count, INLINE_SPACE_MESSAGE_IDS.missingSpaceBefore, INLINE_SPACE_MESSAGE_IDS.multipleSpacesAfterPunctuation);
341
- if (context.whiteSpace.count > 0) return INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceBefore;
826
+ if (context.punctuationType === "half") return validateSingleRequiredSpace(context.whiteSpace.count, "missing-space-before", "multiple-spaces-after-punctuation");
827
+ if (context.whiteSpace.count > 0) return "unexpected-space-before";
342
828
  }
343
829
  /**
344
830
  * Validates the spacing between the previous node and the current inline node.
345
831
  */
346
832
  function validateSpaceBeforeNode(context) {
347
833
  if (context.hasPunctuation) return validateBeforePunctuation(context);
348
- return validateSingleRequiredSpace(context.whiteSpace.count, INLINE_SPACE_MESSAGE_IDS.missingSpaceBefore, INLINE_SPACE_MESSAGE_IDS.multipleSpacesBefore);
834
+ return validateSingleRequiredSpace(context.whiteSpace.count, "missing-space-before", "multiple-spaces-before");
349
835
  }
350
836
  /**
351
837
  * Validates spacing after an inline node when the next character is punctuation.
@@ -353,16 +839,55 @@ function validateSpaceBeforeNode(context) {
353
839
  function validateSpaceAfterPunctuation(context) {
354
840
  const adjacentChar = getAdjacentChar(context.value, "head");
355
841
  if (getLikeAnchor(context.value) || isCustomContainerMarker(context.value)) return;
356
- if (CLOSING_PAIRED_PUNCTUATION.has(adjacentChar || "") && context.whiteSpace.count > 0) return INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceAfter;
357
- if (context.punctuationType === "half" && OPENING_PAIRED_PUNCTUATION.has(adjacentChar || "") || isDashPunctuation(adjacentChar)) return validateSingleRequiredSpace(context.whiteSpace.count, INLINE_SPACE_MESSAGE_IDS.missingSpaceAfter, INLINE_SPACE_MESSAGE_IDS.multipleSpacesAfter);
358
- if (context.whiteSpace.count > 0) return INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceAfter;
842
+ if (CLOSING_PAIRED_PUNCTUATION.has(adjacentChar || "") && context.whiteSpace.count > 0) return "unexpected-space-after";
843
+ if (context.punctuationType === "half" && OPENING_PAIRED_PUNCTUATION.has(adjacentChar || "") || isDashPunctuation(adjacentChar)) return validateSingleRequiredSpace(context.whiteSpace.count, "missing-space-after", "multiple-spaces-after");
844
+ if (context.whiteSpace.count > 0) return "unexpected-space-after";
359
845
  }
360
846
  /**
361
847
  * Validates the spacing between the current inline node and the next node.
362
848
  */
363
849
  function validateSpaceAfterNode(context) {
364
850
  if (context.hasPunctuation) return validateSpaceAfterPunctuation(context);
365
- return validateSingleRequiredSpace(context.whiteSpace.count, INLINE_SPACE_MESSAGE_IDS.missingSpaceAfter, INLINE_SPACE_MESSAGE_IDS.multipleSpacesAfter);
851
+ return validateSingleRequiredSpace(context.whiteSpace.count, "missing-space-after", "multiple-spaces-after");
852
+ }
853
+ const INLINE_ELEMENT_TYPES = /* @__PURE__ */ new Set([
854
+ "link",
855
+ "image",
856
+ "inlineCode",
857
+ "emphasis",
858
+ "strong"
859
+ ]);
860
+ /**
861
+ * Checks whether a phrasing node is one of the selected inline element targets.
862
+ */
863
+ function isInlineElement(node) {
864
+ return !!node && INLINE_ELEMENT_TYPES.has(node.type);
865
+ }
866
+ /**
867
+ * Checks whether the current inline element is nested inside another selected inline element.
868
+ */
869
+ function isNestedInlineElement(nodeContext) {
870
+ const { parent } = nodeContext;
871
+ return isInlineElement(parent);
872
+ }
873
+ function getSpaceContext(nodeContext) {
874
+ const { prev, next } = nodeContext;
875
+ const prevValue = getNodeValue(prev);
876
+ const nextValue = getNodeValue(next);
877
+ return {
878
+ prev: {
879
+ value: prevValue,
880
+ whiteSpace: getWhiteSpace(prevValue, "tail"),
881
+ hasPunctuation: hasPunctuation(prevValue, "tail"),
882
+ punctuationType: isFullwidthPunctuation(getAdjacentChar(prevValue, "tail")) ? "full" : "half"
883
+ },
884
+ next: {
885
+ value: nextValue,
886
+ whiteSpace: getWhiteSpace(nextValue),
887
+ hasPunctuation: hasPunctuation(nextValue),
888
+ punctuationType: isFullwidthPunctuation(getAdjacentChar(nextValue, "head")) ? "full" : "half"
889
+ }
890
+ };
366
891
  }
367
892
  /**
368
893
  * Validates spacing around an inline element inside a table cell.
@@ -401,16 +926,71 @@ function validateSpace(nodeContext) {
401
926
  if (parent && isTableCell(parent)) return validateTableCellSpace(nodeContext);
402
927
  return validateDefaultSpace(nodeContext);
403
928
  }
404
-
405
929
  //#endregion
406
- //#region src/rules/space-between-inline-element/index.ts
407
- const RULE_NAME$1 = "space-between-inline-element";
408
- const BEFORE_INLINE_ELEMENT_MESSAGE_IDS = new Set([
409
- INLINE_SPACE_MESSAGE_IDS.missingSpaceBefore,
410
- INLINE_SPACE_MESSAGE_IDS.multipleSpacesBefore,
411
- INLINE_SPACE_MESSAGE_IDS.multipleSpacesAfterPunctuation,
412
- INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceBefore
930
+ //#region src/rules/space-around-inline-element/index.ts
931
+ const RULE_NAME$4 = "space-around-inline-element";
932
+ const MESSAGE_IDS$2 = {
933
+ missingSpaceBefore: "missingSpaceBefore",
934
+ missingSpaceAfter: "missingSpaceAfter",
935
+ multipleSpacesBefore: "multipleSpacesBefore",
936
+ multipleSpacesAfter: "multipleSpacesAfter",
937
+ multipleSpacesAfterPunctuation: "multipleSpacesAfterPunctuation",
938
+ unexpectedSpaceBefore: "unexpectedSpaceBefore",
939
+ unexpectedSpaceAfter: "unexpectedSpaceAfter"
940
+ };
941
+ const ISSUE_TO_MESSAGE_ID = {
942
+ "missing-space-before": MESSAGE_IDS$2.missingSpaceBefore,
943
+ "missing-space-after": MESSAGE_IDS$2.missingSpaceAfter,
944
+ "multiple-spaces-before": MESSAGE_IDS$2.multipleSpacesBefore,
945
+ "multiple-spaces-after": MESSAGE_IDS$2.multipleSpacesAfter,
946
+ "multiple-spaces-after-punctuation": MESSAGE_IDS$2.multipleSpacesAfterPunctuation,
947
+ "unexpected-space-before": MESSAGE_IDS$2.unexpectedSpaceBefore,
948
+ "unexpected-space-after": MESSAGE_IDS$2.unexpectedSpaceAfter
949
+ };
950
+ const BEFORE_INLINE_ELEMENT_MESSAGE_IDS = /* @__PURE__ */ new Set([
951
+ MESSAGE_IDS$2.missingSpaceBefore,
952
+ MESSAGE_IDS$2.multipleSpacesBefore,
953
+ MESSAGE_IDS$2.multipleSpacesAfterPunctuation,
954
+ MESSAGE_IDS$2.unexpectedSpaceBefore
413
955
  ]);
956
+ var space_around_inline_element_default = createRule({
957
+ name: RULE_NAME$4,
958
+ meta: {
959
+ type: "layout",
960
+ docs: { description: "Enforce spacing around Markdown inline elements." },
961
+ messages: {
962
+ missingSpaceBefore: "A space is required before the inline element.",
963
+ missingSpaceAfter: "A space is required after the inline element.",
964
+ multipleSpacesBefore: "Use exactly one space before the inline element.",
965
+ multipleSpacesAfter: "Use exactly one space after the inline element.",
966
+ multipleSpacesAfterPunctuation: "Use one space after punctuation.",
967
+ unexpectedSpaceBefore: "Do not add a space between punctuation and the inline element.",
968
+ unexpectedSpaceAfter: "Do not add a space between the inline element and punctuation."
969
+ },
970
+ fixable: "whitespace",
971
+ schema: []
972
+ },
973
+ defaultOptions: [],
974
+ create(context) {
975
+ return {
976
+ link(node) {
977
+ checkInlineElement(context, node);
978
+ },
979
+ image(node) {
980
+ checkInlineElement(context, node);
981
+ },
982
+ inlineCode(node) {
983
+ checkInlineElement(context, node);
984
+ },
985
+ emphasis(node) {
986
+ checkInlineElement(context, node);
987
+ },
988
+ strong(node) {
989
+ checkInlineElement(context, node);
990
+ }
991
+ };
992
+ }
993
+ });
414
994
  /**
415
995
  * Checks one selected inline element and reports the fix range around it.
416
996
  */
@@ -421,11 +1001,12 @@ function checkInlineElement(context, node) {
421
1001
  const nodeContext = getNodeContext(context, node);
422
1002
  if (isNestedInlineElement(nodeContext)) return;
423
1003
  const spaceContext = getSpaceContext(nodeContext);
424
- const messageId = validateSpace(nodeContext);
425
- if (!messageId) return;
1004
+ const issue = validateSpace(nodeContext);
1005
+ if (!issue) return;
1006
+ const messageId = ISSUE_TO_MESSAGE_ID[issue];
426
1007
  if (BEFORE_INLINE_ELEMENT_MESSAGE_IDS.has(messageId) && spaceContext.prev) {
427
1008
  const { count } = spaceContext.prev.whiteSpace;
428
- const replaceText = messageId === INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceBefore ? "" : " ";
1009
+ const replaceText = messageId === MESSAGE_IDS$2.unexpectedSpaceBefore ? "" : " ";
429
1010
  context.report({
430
1011
  node,
431
1012
  messageId,
@@ -437,7 +1018,7 @@ function checkInlineElement(context, node) {
437
1018
  }
438
1019
  if (spaceContext.next) {
439
1020
  const { count } = spaceContext.next.whiteSpace;
440
- const replaceText = messageId === INLINE_SPACE_MESSAGE_IDS.unexpectedSpaceAfter ? "" : " ";
1021
+ const replaceText = messageId === MESSAGE_IDS$2.unexpectedSpaceAfter ? "" : " ";
441
1022
  context.report({
442
1023
  node,
443
1024
  messageId,
@@ -447,58 +1028,443 @@ function checkInlineElement(context, node) {
447
1028
  });
448
1029
  }
449
1030
  }
450
- var space_between_inline_element_default = createRule({
451
- name: RULE_NAME$1,
1031
+ //#endregion
1032
+ //#region src/utils/text/tokenizer.ts
1033
+ const TEXT_TYPE = {
1034
+ "cjk": "cjk",
1035
+ "latin": "latin",
1036
+ "number": "number",
1037
+ "space": "space",
1038
+ "newline": "newline",
1039
+ "fullwidth-punctuation": "fullwidth-punctuation",
1040
+ "halfwidth-punctuation": "halfwidth-punctuation",
1041
+ "dash": "dash",
1042
+ "symbol": "symbol",
1043
+ "emoji": "emoji",
1044
+ "invisible": "invisible",
1045
+ "other": "other"
1046
+ };
1047
+ const CJK_RE = /^\p{Script=Han}$|^\p{Script=Hiragana}$|^\p{Script=Katakana}$|^\p{Script=Hangul}$/u;
1048
+ const LATIN_RE = /^\p{Script=Latin}$/u;
1049
+ const NUMBER_RE = /^\p{Number}$/u;
1050
+ const SYMBOL_RE = /^\p{Symbol}$/u;
1051
+ const EMOJI_RE = /^\p{Extended_Pictographic}$/u;
1052
+ const SPACE_RE = /^[\t\v\f \u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]$/u;
1053
+ const NEWLINE_RE = /^[\n\r\u2028\u2029]$/u;
1054
+ const INVISIBLE_CODE_POINTS = /* @__PURE__ */ new Set([
1055
+ 173,
1056
+ 847,
1057
+ 1564,
1058
+ 4447,
1059
+ 4448,
1060
+ 6068,
1061
+ 6069,
1062
+ 6158,
1063
+ 65279,
1064
+ 65440
1065
+ ]);
1066
+ /**
1067
+ * Checks whether the character is an invisible Unicode control or filler.
1068
+ * These characters affect rendering, cursor movement, or text direction but
1069
+ * should not be treated as visible spacing, punctuation, or symbols.
1070
+ */
1071
+ function isInvisible(char) {
1072
+ const codePoint = char.codePointAt(0);
1073
+ return codePoint != null && (INVISIBLE_CODE_POINTS.has(codePoint) || codePoint >= 8203 && codePoint <= 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8288 && codePoint <= 8303);
1074
+ }
1075
+ function isNumber(char, prev) {
1076
+ return NUMBER_RE.test(char) || prev?.type === TEXT_TYPE.number && (char === "." || char === "%");
1077
+ }
1078
+ function isLatinWordType(type) {
1079
+ if (!type) return false;
1080
+ return type === "latin";
1081
+ }
1082
+ const TEXT_TYPE_MATCHERS = [
1083
+ {
1084
+ type: TEXT_TYPE.newline,
1085
+ test: (char) => NEWLINE_RE.test(char)
1086
+ },
1087
+ {
1088
+ type: TEXT_TYPE.space,
1089
+ test: (char) => SPACE_RE.test(char)
1090
+ },
1091
+ {
1092
+ type: TEXT_TYPE.invisible,
1093
+ test: (char) => isInvisible(char)
1094
+ },
1095
+ {
1096
+ type: TEXT_TYPE.cjk,
1097
+ test: (char) => CJK_RE.test(char)
1098
+ },
1099
+ {
1100
+ type: TEXT_TYPE.latin,
1101
+ test: (char) => LATIN_RE.test(char)
1102
+ },
1103
+ {
1104
+ type: TEXT_TYPE.number,
1105
+ test: isNumber
1106
+ },
1107
+ {
1108
+ type: TEXT_TYPE.dash,
1109
+ test: (char) => isDashPunctuation(char)
1110
+ },
1111
+ {
1112
+ type: TEXT_TYPE["fullwidth-punctuation"],
1113
+ test: (char) => isFullwidthPunctuation(char)
1114
+ },
1115
+ {
1116
+ type: TEXT_TYPE["halfwidth-punctuation"],
1117
+ test: (char) => isHalfwidthPunctuation(char)
1118
+ },
1119
+ {
1120
+ type: TEXT_TYPE.emoji,
1121
+ test: (char) => EMOJI_RE.test(char)
1122
+ },
1123
+ {
1124
+ type: TEXT_TYPE.symbol,
1125
+ test: (char) => SYMBOL_RE.test(char)
1126
+ }
1127
+ ];
1128
+ const DEFAULT_START_POINT = {
1129
+ line: 1,
1130
+ column: 1,
1131
+ offset: 0
1132
+ };
1133
+ function advancePoint(point, char) {
1134
+ const { line, column, offset = 0 } = point;
1135
+ if (NEWLINE_RE.test(char)) return {
1136
+ line: line + 1,
1137
+ column: 1,
1138
+ offset: offset + char.length
1139
+ };
1140
+ return {
1141
+ line,
1142
+ column: column + char.length,
1143
+ offset: offset + char.length
1144
+ };
1145
+ }
1146
+ /**
1147
+ * Classifies a single Unicode code point for Markdown text style rules.
1148
+ */
1149
+ function getTextType(char, prev) {
1150
+ for (const matcher of TEXT_TYPE_MATCHERS) if (matcher.test(char, prev)) return matcher.type;
1151
+ return TEXT_TYPE.other;
1152
+ }
1153
+ /**
1154
+ * Tokenizes text into consecutive typed runs while preserving source positions.
1155
+ */
1156
+ function tokenizeText(value, start = DEFAULT_START_POINT) {
1157
+ const tokens = [];
1158
+ let point = start;
1159
+ let prevToken;
1160
+ for (const char of value) {
1161
+ const type = getTextType(char, prevToken);
1162
+ const tokenStart = point;
1163
+ const tokenEnd = advancePoint(point, char);
1164
+ if (prevToken?.type === type) {
1165
+ prevToken.value += char;
1166
+ prevToken.position.end = tokenEnd;
1167
+ } else {
1168
+ prevToken = {
1169
+ type,
1170
+ value: char,
1171
+ position: {
1172
+ start: tokenStart,
1173
+ end: tokenEnd
1174
+ }
1175
+ };
1176
+ tokens.push(prevToken);
1177
+ }
1178
+ point = tokenEnd;
1179
+ }
1180
+ return tokens;
1181
+ }
1182
+ /**
1183
+ * Converts an mdast text node into a tokenized text AST with normalized source positions.
1184
+ */
1185
+ function buildTextNodeAst(node) {
1186
+ const start = node.position?.start;
1187
+ const end = node.position?.end;
1188
+ const position = {
1189
+ start: {
1190
+ line: start?.line ?? DEFAULT_START_POINT.line,
1191
+ column: start?.column ?? DEFAULT_START_POINT.column,
1192
+ offset: start?.offset ?? DEFAULT_START_POINT.offset
1193
+ },
1194
+ end: {
1195
+ line: end?.line ?? DEFAULT_START_POINT.line,
1196
+ column: end?.column ?? DEFAULT_START_POINT.column + node.value.length,
1197
+ offset: end?.offset ?? DEFAULT_START_POINT.offset + node.value.length
1198
+ }
1199
+ };
1200
+ return {
1201
+ type: "text",
1202
+ value: node.value,
1203
+ position,
1204
+ children: tokenizeText(node.value, position.start),
1205
+ node
1206
+ };
1207
+ }
1208
+ //#endregion
1209
+ //#region src/rules/shared/text-boundary-spacing.ts
1210
+ /**
1211
+ * Normalizes an existing space token around the target token type.
1212
+ * When multiple spaces are collapsed, the redundant side is recorded so the
1213
+ * caller can choose the most specific lint message.
1214
+ */
1215
+ function processSpaceToken(ctx, result, isTargetType) {
1216
+ const { prev, current, next } = ctx;
1217
+ /* v8 ignore if -- @preserve */
1218
+ if (!current) return;
1219
+ const cjkToTarget = prev?.type === TEXT_TYPE.cjk && isTargetType(next?.type);
1220
+ const targetToCjk = isTargetType(prev?.type) && next?.type === TEXT_TYPE.cjk;
1221
+ const targets = isTargetType(prev?.type) && isTargetType(next?.type);
1222
+ const hasUnexpectedSpaces = current.value.length !== 1;
1223
+ if (hasUnexpectedSpaces) {
1224
+ if (cjkToTarget || targets) result.unexpectedBefore = true;
1225
+ if (targetToCjk || targets) result.unexpectedAfter = true;
1226
+ }
1227
+ if (cjkToTarget || targetToCjk || hasUnexpectedSpaces) result.fixed += " ";
1228
+ else result.fixed += current.value;
1229
+ }
1230
+ /**
1231
+ * Inserts missing spaces before or after the target token when it directly
1232
+ * touches adjacent CJK text.
1233
+ */
1234
+ function processTargetToken(ctx, result) {
1235
+ const { prev, current, next } = ctx;
1236
+ /* v8 ignore if -- @preserve */
1237
+ if (!current) return;
1238
+ if (prev?.type === TEXT_TYPE.cjk) {
1239
+ result.fixed += " ";
1240
+ result.missingBefore = true;
1241
+ }
1242
+ result.fixed += current.value;
1243
+ if (next?.type === TEXT_TYPE.cjk) {
1244
+ result.fixed += " ";
1245
+ result.missingAfter = true;
1246
+ }
1247
+ }
1248
+ /**
1249
+ * Chooses the most specific shared boundary-space message id from the
1250
+ * collected missing or redundant space flags.
1251
+ */
1252
+ function getBoundarySpaceMessageId(boundary) {
1253
+ if (boundary.missingBefore && boundary.missingAfter) return "missingSpacesAround";
1254
+ if (boundary.unexpectedBefore && boundary.unexpectedAfter) return "unexpectedSpaceAround";
1255
+ if (boundary.missingBefore) return "missingSpaceBefore";
1256
+ if (boundary.missingAfter) return "missingSpaceAfter";
1257
+ if (boundary.unexpectedBefore) return "unexpectedSpaceBefore";
1258
+ return "unexpectedSpaceAfter";
1259
+ }
1260
+ /**
1261
+ * Rebuilds a text node with normalized spacing between CJK text and a target
1262
+ * token class such as Latin words or numbers.
1263
+ */
1264
+ function fixBoundarySpace(node, isTargetType) {
1265
+ const { children } = buildTextNodeAst(node);
1266
+ const result = {
1267
+ fixed: "",
1268
+ missingBefore: false,
1269
+ missingAfter: false,
1270
+ unexpectedBefore: false,
1271
+ unexpectedAfter: false
1272
+ };
1273
+ for (let i = 0; i < children.length; i += 1) {
1274
+ const ctx = getNodeContextByParent(children, i);
1275
+ /* v8 ignore if -- @preserve */
1276
+ if (!ctx.current) continue;
1277
+ if (ctx.current.type === TEXT_TYPE.space) processSpaceToken(ctx, result, isTargetType);
1278
+ else if (isTargetType(ctx.current.type)) processTargetToken(ctx, result);
1279
+ else result.fixed += ctx.current.value;
1280
+ }
1281
+ return result;
1282
+ }
1283
+ var space_around_number_default = createRule({
1284
+ name: "space-around-number",
452
1285
  meta: {
453
1286
  type: "layout",
454
- docs: { description: "Enforce spacing around Markdown inline elements." },
1287
+ docs: { description: "Enforce a single space between CJK characters and numbers." },
455
1288
  messages: {
456
- missingSpaceBefore: "A space is required before the inline element.",
457
- missingSpaceAfter: "A space is required after the inline element.",
458
- multipleSpacesBefore: "Use exactly one space before the inline element.",
459
- multipleSpacesAfter: "Use exactly one space after the inline element.",
460
- multipleSpacesAfterPunctuation: "Use one space after punctuation.",
461
- unexpectedSpaceBefore: "Do not add a space between punctuation and the inline element.",
462
- unexpectedSpaceAfter: "Do not add a space between the inline element and punctuation."
1289
+ missingSpaceBefore: "Add a space before the number.",
1290
+ missingSpaceAfter: "Add a space after the number.",
1291
+ missingSpacesAround: "Add spaces before and after the number.",
1292
+ unexpectedSpaceBefore: "Remove the unexpected space before the number.",
1293
+ unexpectedSpaceAfter: "Remove the unexpected space after the number.",
1294
+ unexpectedSpaceAround: "Remove the unexpected spaces around the number."
463
1295
  },
464
1296
  fixable: "whitespace",
465
1297
  schema: []
466
1298
  },
467
1299
  defaultOptions: [],
468
1300
  create(context) {
469
- return {
470
- link(node) {
471
- checkInlineElement(context, node);
472
- },
473
- image(node) {
474
- checkInlineElement(context, node);
475
- },
476
- inlineCode(node) {
477
- checkInlineElement(context, node);
478
- },
479
- emphasis(node) {
480
- checkInlineElement(context, node);
481
- },
482
- strong(node) {
483
- checkInlineElement(context, node);
1301
+ return { text(node) {
1302
+ const { fixed, missingBefore, missingAfter, unexpectedBefore, unexpectedAfter } = fixBoundarySpace(node, (type) => type === "number");
1303
+ if (fixed === node.value) return;
1304
+ context.report({
1305
+ node,
1306
+ messageId: getBoundarySpaceMessageId({
1307
+ missingBefore,
1308
+ missingAfter,
1309
+ unexpectedBefore,
1310
+ unexpectedAfter
1311
+ }),
1312
+ fix: (fixer) => fixer.replaceText(node, fixed)
1313
+ });
1314
+ } };
1315
+ }
1316
+ });
1317
+ var space_around_word_default = createRule({
1318
+ name: "space-around-word",
1319
+ meta: {
1320
+ type: "layout",
1321
+ docs: { description: "Enforce a single space between CJK characters and Latin words." },
1322
+ messages: {
1323
+ missingSpaceBefore: "Add a space before the word.",
1324
+ missingSpaceAfter: "Add a space after the word.",
1325
+ missingSpacesAround: "Add spaces before and after the word.",
1326
+ unexpectedSpaceBefore: "Remove the unexpected space before the word.",
1327
+ unexpectedSpaceAfter: "Remove the unexpected space after the word.",
1328
+ unexpectedSpaceAround: "Remove the unexpected spaces around the word."
1329
+ },
1330
+ fixable: "whitespace",
1331
+ schema: []
1332
+ },
1333
+ defaultOptions: [],
1334
+ create(context) {
1335
+ return { text(node) {
1336
+ const { fixed, missingBefore, missingAfter, unexpectedBefore, unexpectedAfter } = fixBoundarySpace(node, isLatinWordType);
1337
+ if (fixed === node.value) return;
1338
+ context.report({
1339
+ node,
1340
+ messageId: getBoundarySpaceMessageId({
1341
+ missingBefore,
1342
+ missingAfter,
1343
+ unexpectedBefore,
1344
+ unexpectedAfter
1345
+ }),
1346
+ fix(fixer) {
1347
+ return fixer.replaceText(node, fixed);
1348
+ }
1349
+ });
1350
+ } };
1351
+ }
1352
+ });
1353
+ //#endregion
1354
+ //#region src/rules/valid-custom-container-type/index.ts
1355
+ const RULE_NAME$1 = "valid-custom-container-type";
1356
+ const MESSAGE_IDS$1 = {
1357
+ invalidType: "invalidType",
1358
+ invalidTypeCase: "invalidTypeCase"
1359
+ };
1360
+ var valid_custom_container_type_default = createRule({
1361
+ name: RULE_NAME$1,
1362
+ meta: {
1363
+ type: "problem",
1364
+ docs: { description: "Require custom containers to use a supported type." },
1365
+ messages: {
1366
+ invalidType: "Invalid custom container type \"{{type}}\". Use info, tip, warning, danger, details, raw, code-group, v-pre, or tabs.",
1367
+ invalidTypeCase: "Custom container type \"{{type}}\" must be lowercase."
1368
+ },
1369
+ fixable: "code",
1370
+ schema: []
1371
+ },
1372
+ defaultOptions: [],
1373
+ create(context) {
1374
+ return { text(node) {
1375
+ const { position, start: startPoint } = getNodePosition(node);
1376
+ /* v8 ignore if -- @preserve */
1377
+ if (!position) return;
1378
+ for (const tag of getOpeningTags(parseCustomContainers(node.value))) {
1379
+ const issue = getTypeIssue(tag.value.content);
1380
+ if (!issue) continue;
1381
+ const normalizedType = "normalizedType" in issue ? issue.normalizedType : void 0;
1382
+ const start = startPoint + tag.value.start;
1383
+ const end = startPoint + tag.value.end;
1384
+ context.report({
1385
+ node,
1386
+ messageId: issue.messageId,
1387
+ data: { type: tag.value.content },
1388
+ loc: {
1389
+ start: context.sourceCode.getLocFromIndex(start),
1390
+ end: context.sourceCode.getLocFromIndex(end)
1391
+ },
1392
+ fix: normalizedType ? (fixer) => fixer.replaceTextRange([start, end], normalizedType) : void 0
1393
+ });
484
1394
  }
485
- };
1395
+ } };
486
1396
  }
487
1397
  });
488
-
1398
+ function* getOpeningTags(nodes) {
1399
+ for (const node of nodes) {
1400
+ if (node.type !== "custom-container") continue;
1401
+ yield* getOpeningTagsFromContainer(node);
1402
+ }
1403
+ }
1404
+ function* getOpeningTagsFromContainer(container) {
1405
+ for (const child of container.children) if (child.type === "custom-container") yield* getOpeningTagsFromContainer(child);
1406
+ else if (child.type === "open") yield child;
1407
+ }
1408
+ function getTypeIssue(type) {
1409
+ if (isCustomContainerType(type)) return null;
1410
+ const normalizedType = type.toLowerCase();
1411
+ if (isCustomContainerType(normalizedType)) return {
1412
+ messageId: MESSAGE_IDS$1.invalidTypeCase,
1413
+ normalizedType
1414
+ };
1415
+ return { messageId: MESSAGE_IDS$1.invalidType };
1416
+ }
1417
+ //#endregion
1418
+ //#region src/rules/valid-heading-anchor/anchor.ts
1419
+ /**
1420
+ * Check if the string has an anchor.
1421
+ * @example: {#chinese-anchor}
1422
+ */
1423
+ function isStrictAnchor(str) {
1424
+ return /\s\{#[a-z0-9]+(?:-[a-z0-9]+)*\}/.test(str);
1425
+ }
1426
+ /**
1427
+ * Check whether the string contains CJK Han characters.
1428
+ */
1429
+ function hasChinese(str) {
1430
+ return /\p{Script=Han}/u.test(str);
1431
+ }
1432
+ /**
1433
+ * Normalize raw anchor text into the strict anchor format content.
1434
+ * - lowercase all letters
1435
+ * - convert spaces to `-`
1436
+ * - remove unsupported characters
1437
+ * - trim leading/trailing `-`
1438
+ */
1439
+ function normalizeAnchor(anchor) {
1440
+ return anchor.toLowerCase().replace(/[\s.]+/g, "-").replace(/[^a-z0-9_-]/g, "").replace(/^-+|-+$/g, "");
1441
+ }
1442
+ /**
1443
+ * Count wrapper characters contributed by the trailing like-anchor fragment.
1444
+ * The value is the length difference between the raw matched fragment and the
1445
+ * cleaned anchor text returned by `getLikeAnchor`.
1446
+ * @example `# 中文标题 {#Chinese-Title}` -> 3
1447
+ * @example `## 使用 \`describe\` 编组测试 #Grouping Tests with \`describe\`` -> 1
1448
+ */
1449
+ function calcAnchorPositionCompensate(content) {
1450
+ const match = getLikeAnchorMatch(content);
1451
+ const anchor = getLikeAnchor(content);
1452
+ if (!match || !anchor) return 0;
1453
+ return match.length - anchor.rawLikeAnchor.length;
1454
+ }
489
1455
  //#endregion
490
- //#region src/utils/markdown.ts
1456
+ //#region src/parser/markdown.ts
491
1457
  const language = new MarkdownLanguage({ mode: "gfm" });
492
1458
  /**
493
1459
  * Parses Markdown with the same GFM language implementation used by the
494
1460
  * plugin tests and returns both the mdast tree and ESLint SourceCode wrapper.
495
1461
  */
496
- function parseMarkdown(markdown$1) {
1462
+ function parseMarkdown(markdown) {
497
1463
  const file = {
498
1464
  path: "test.md",
499
1465
  physicalPath: "test.md",
500
1466
  bom: false,
501
- body: markdown$1
1467
+ body: markdown
502
1468
  };
503
1469
  const parseResult = language.parse(file, { languageOptions: {
504
1470
  ...language.defaultLanguageOptions,
@@ -511,18 +1477,16 @@ function parseMarkdown(markdown$1) {
511
1477
  sourceCode: language.createSourceCode(file, parseResult)
512
1478
  };
513
1479
  }
514
-
515
1480
  //#endregion
516
- //#region src/utils/heading.ts
1481
+ //#region src/rules/valid-heading-anchor/frontmatter.ts
517
1482
  /**
518
1483
  * Returns true when the Markdown document starts with YAML frontmatter.
519
1484
  */
520
- function hasFrontmatter(markdown$1, prevNode) {
521
- if (prevNode?.type === "thematicBreak") markdown$1 = `---\n${markdown$1}`;
522
- const { ast } = parseMarkdown(markdown$1);
1485
+ function hasFrontmatter(markdown, prevNode) {
1486
+ if (prevNode?.type === "thematicBreak") markdown = `---\n${markdown}`;
1487
+ const { ast } = parseMarkdown(markdown);
523
1488
  return ast.children[0]?.type === "yaml";
524
1489
  }
525
-
526
1490
  //#endregion
527
1491
  //#region src/rules/valid-heading-anchor/index.ts
528
1492
  const RULE_NAME = "valid-heading-anchor";
@@ -530,58 +1494,60 @@ const MESSAGE_IDS = {
530
1494
  missingAnchor: "missingAnchor",
531
1495
  invalidHeadingAnchor: "invalidHeadingAnchor"
532
1496
  };
533
- var valid_heading_anchor_default = createRule({
534
- name: RULE_NAME,
535
- meta: {
536
- type: "layout",
537
- docs: { description: "Require strict lowercase anchors for headings that contain CJK text." },
538
- messages: {
539
- missingAnchor: "Non-ASCII heading must have an anchor in the format \"{#lowercase-anchor}\".",
540
- invalidHeadingAnchor: "Anchor must use lowercase letters and valid characters only."
1497
+ //#endregion
1498
+ //#region src/rules/index.ts
1499
+ const rules = {
1500
+ "padding-around-custom-container": padding_around_custom_container_default,
1501
+ "space-around-inline-element": space_around_inline_element_default,
1502
+ "space-around-number": space_around_number_default,
1503
+ "space-around-word": space_around_word_default,
1504
+ "space-around-custom-container": space_around_custom_container_default,
1505
+ "valid-custom-container-type": valid_custom_container_type_default,
1506
+ "valid-heading-anchor": createRule({
1507
+ name: RULE_NAME,
1508
+ meta: {
1509
+ type: "layout",
1510
+ docs: { description: "Require strict lowercase anchors for headings that contain CJK text." },
1511
+ messages: {
1512
+ missingAnchor: "Non-ASCII heading must have an anchor in the format \"{#lowercase-anchor}\".",
1513
+ invalidHeadingAnchor: "Anchor must use lowercase letters and valid characters only."
1514
+ },
1515
+ fixable: "whitespace",
1516
+ schema: []
541
1517
  },
542
- fixable: "whitespace",
543
- schema: []
544
- },
545
- defaultOptions: [],
546
- create(context) {
547
- return { heading(node) {
548
- const { position, start, end } = getNodePosition(node);
549
- /* v8 ignore if -- @preserve */
550
- if (!position) return;
551
- const source = context.sourceCode.text.slice(start, end);
552
- if (isStrictAnchor(source) || !hasChinese(source)) return;
553
- if (hasFrontmatter(source, getNodeContext(context, node).prev)) return;
554
- const liked = getLikeAnchor(source);
555
- if (!liked) {
1518
+ defaultOptions: [],
1519
+ create(context) {
1520
+ return { heading(node) {
1521
+ const { position, start, end } = getNodePosition(node);
1522
+ /* v8 ignore if -- @preserve */
1523
+ if (!position) return;
1524
+ const source = context.sourceCode.text.slice(start, end);
1525
+ if (isStrictAnchor(source) || !hasChinese(source)) return;
1526
+ if (hasFrontmatter(source, getNodeContext(context, node).prev)) return;
1527
+ const liked = getLikeAnchor(source);
1528
+ if (!liked) {
1529
+ context.report({
1530
+ node,
1531
+ messageId: MESSAGE_IDS.missingAnchor
1532
+ });
1533
+ return;
1534
+ }
1535
+ const { rawLikeAnchor, isLikeAnchor } = liked;
1536
+ const compensate = calcAnchorPositionCompensate(source);
1537
+ const remainingContent = source.slice(0, -rawLikeAnchor.length - compensate).trim();
1538
+ const anchor = normalizeAnchor(rawLikeAnchor);
1539
+ if (rawLikeAnchor === anchor) return;
556
1540
  context.report({
557
1541
  node,
558
- messageId: MESSAGE_IDS.missingAnchor
1542
+ messageId: isLikeAnchor ? MESSAGE_IDS.missingAnchor : MESSAGE_IDS.invalidHeadingAnchor,
1543
+ fix(fixer) {
1544
+ return fixer.replaceTextRange([start, end], `${remainingContent} {#${anchor}}`);
1545
+ }
559
1546
  });
560
- return;
561
- }
562
- const { rawLikeAnchor, isLikeAnchor } = liked;
563
- const compensate = calcAnchorPositionCompensate(source);
564
- const remainingContent = source.slice(0, -rawLikeAnchor.length - compensate).trim();
565
- const anchor = normalizeAnchor(rawLikeAnchor);
566
- if (rawLikeAnchor === anchor) return;
567
- context.report({
568
- node,
569
- messageId: isLikeAnchor ? MESSAGE_IDS.missingAnchor : MESSAGE_IDS.invalidHeadingAnchor,
570
- fix(fixer) {
571
- return fixer.replaceTextRange([start, end], `${remainingContent} {#${anchor}}`);
572
- }
573
- });
574
- } };
575
- }
576
- });
577
-
578
- //#endregion
579
- //#region src/rules/index.ts
580
- const rules = {
581
- "space-between-inline-element": space_between_inline_element_default,
582
- "valid-heading-anchor": valid_heading_anchor_default
1547
+ } };
1548
+ }
1549
+ })
583
1550
  };
584
-
585
1551
  //#endregion
586
1552
  //#region src/index.ts
587
1553
  const plugin = {
@@ -589,15 +1555,20 @@ const plugin = {
589
1555
  processors: markdown.processors,
590
1556
  languages: { gfm: new MarkdownLanguage({ mode: "gfm" }) }
591
1557
  };
592
- const allRuleEntries = Object.keys(rules).map((ruleName) => [`md-style/${ruleName}`, "error"]);
593
- const recommendedRules = Object.fromEntries(allRuleEntries);
594
- const allRules = Object.fromEntries(allRuleEntries);
1558
+ const recommendedRules = {
1559
+ "md-style/valid-heading-anchor": "error",
1560
+ "md-style/space-around-inline-element": "error",
1561
+ "md-style/padding-around-custom-container": "error",
1562
+ "md-style/valid-custom-container-type": "error"
1563
+ };
1564
+ const allRules = Object.fromEntries(Object.keys(rules).map((ruleName) => [`md-style/${ruleName}`, "error"]));
595
1565
  const configs = {
596
1566
  recommended: {
597
1567
  name: "md-style/recommended",
598
1568
  files: ["**/*.md"],
599
1569
  plugins: { "md-style": plugin },
600
1570
  language: "md-style/gfm",
1571
+ languageOptions: { frontmatter: "yaml" },
601
1572
  rules: recommendedRules
602
1573
  },
603
1574
  all: {
@@ -605,11 +1576,10 @@ const configs = {
605
1576
  files: ["**/*.md"],
606
1577
  plugins: { "md-style": plugin },
607
1578
  language: "md-style/gfm",
1579
+ languageOptions: { frontmatter: "yaml" },
608
1580
  rules: allRules
609
1581
  }
610
1582
  };
611
1583
  const mdStylePlugin = Object.assign(plugin, { configs });
612
- var src_default = mdStylePlugin;
613
-
614
1584
  //#endregion
615
- export { configs, src_default as default, plugin };
1585
+ export { configs, mdStylePlugin as default, plugin };