eslint-plugin-markdown-preferences 0.23.0 → 0.25.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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { __export } from "./chunk-Cl8Af3a2.js";
1
+ import { __export } from "./chunk-CTAAG5j7.js";
2
2
  import stringWidth from "string-width";
3
3
  import emojiRegex from "emoji-regex-xs";
4
4
  import path from "node:path";
@@ -109,13 +109,20 @@ function getThematicBreakMarker(sourceCode, node) {
109
109
  * Get the source location from a range in a node.
110
110
  */
111
111
  function getSourceLocationFromRange(sourceCode, node, range) {
112
- const [nodeStart] = sourceCode.getRange(node);
112
+ const nodeRange = sourceCode.getRange(node);
113
+ const loc = sourceCode.getLoc(node);
114
+ if (nodeRange[1] <= range[0]) return getSourceLocationFromRangeAndSourcePosition(sourceCode, nodeRange[1], loc.end, range);
115
+ return getSourceLocationFromRangeAndSourcePosition(sourceCode, nodeRange[0], loc.start, range);
116
+ }
117
+ /**
118
+ * Get the source location from a range
119
+ */
120
+ function getSourceLocationFromRangeAndSourcePosition(sourceCode, startIndex, startLoc, range) {
113
121
  let startLine, startColumn;
114
- if (nodeStart <= range[0]) {
115
- const loc = sourceCode.getLoc(node);
116
- const beforeLines = sourceCode.text.slice(nodeStart, range[0]).split(/\n/u);
117
- startLine = loc.start.line + beforeLines.length - 1;
118
- startColumn = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
122
+ if (startIndex <= range[0]) {
123
+ const beforeLines = sourceCode.text.slice(startIndex, range[0]).split(/\n/u);
124
+ startLine = startLoc.line + beforeLines.length - 1;
125
+ startColumn = (beforeLines.length === 1 ? startLoc.column : 1) + (beforeLines.at(-1) || "").length;
119
126
  } else {
120
127
  const beforeLines = sourceCode.text.slice(0, range[0]).split(/\n/u);
121
128
  startLine = beforeLines.length;
@@ -364,15 +371,23 @@ function getParsedLines(sourceCode) {
364
371
  }
365
372
 
366
373
  //#endregion
367
- //#region src/utils/get-text-width.ts
374
+ //#region src/utils/text-width.ts
368
375
  let segmenter;
369
376
  /**
370
377
  * Get the width of a text string.
371
378
  */
372
- function getTextWidth(text) {
373
- if (!text.includes(" ")) return stringWidth(text);
379
+ function getTextWidth(text, start = 0, end = text.length) {
380
+ if (!text.includes(" ")) return stringWidth(text.slice(start, end));
374
381
  if (!segmenter) segmenter = new Intl.Segmenter("en");
375
- let width = 0;
382
+ const prefixWidth = getTextWidthBySegment(text.slice(0, start), 0);
383
+ return getTextWidthBySegment(text.slice(start, end), prefixWidth);
384
+ }
385
+ /**
386
+ * Get the width of a text string by segmenter.
387
+ */
388
+ function getTextWidthBySegment(text, startWidth) {
389
+ if (!segmenter) segmenter = new Intl.Segmenter("en");
390
+ let width = startWidth;
376
391
  for (const { segment: c } of segmenter.segment(text)) if (c === " ") width += 4 - width % 4;
377
392
  else width += stringWidth(c);
378
393
  return width;
@@ -386,7 +401,7 @@ var atx_heading_closing_sequence_length_default = createRule("atx-heading-closin
386
401
  docs: {
387
402
  description: "enforce consistent length for the closing sequence (trailing #s) in ATX headings.",
388
403
  categories: ["standard"],
389
- listCategory: "Stylistic"
404
+ listCategory: "Decorative"
390
405
  },
391
406
  fixable: "code",
392
407
  hasSuggestions: false,
@@ -524,7 +539,7 @@ var atx_heading_closing_sequence_default = createRule("atx-heading-closing-seque
524
539
  docs: {
525
540
  description: "enforce consistent use of closing sequence in ATX headings.",
526
541
  categories: ["standard"],
527
- listCategory: "Stylistic"
542
+ listCategory: "Decorative"
528
543
  },
529
544
  fixable: "code",
530
545
  hasSuggestions: false,
@@ -600,12 +615,28 @@ function getBlockquoteLevelFromLine(sourceCode, lineNumber) {
600
615
  const lineText = sourceCode.lines[lineNumber - 1];
601
616
  let prefix = "";
602
617
  let level = 0;
618
+ let width = 0;
619
+ let leadingMarkerOffset = 0;
603
620
  const blockquoteMarkers = /* @__PURE__ */ new Map();
604
621
  for (const c of lineText) {
605
622
  if (c === ">") {
623
+ if (width - leadingMarkerOffset > 3) break;
624
+ leadingMarkerOffset = width + 1;
606
625
  level++;
607
- blockquoteMarkers.set(level, { index: prefix.length });
608
- } else if (c.trim()) break;
626
+ blockquoteMarkers.set(level, { loc: {
627
+ start: {
628
+ line: lineNumber,
629
+ column: prefix.length + 1
630
+ },
631
+ end: {
632
+ line: lineNumber,
633
+ column: prefix.length + 2
634
+ }
635
+ } });
636
+ } else if (!isSpaceOrTab(c)) break;
637
+ if (c === " ") width += 4 - width % 4;
638
+ else width++;
639
+ if (c !== ">" && prefix.at(-1) === ">") leadingMarkerOffset++;
609
640
  prefix += c;
610
641
  }
611
642
  const result = {
@@ -618,6 +649,64 @@ function getBlockquoteLevelFromLine(sourceCode, lineNumber) {
618
649
  return result;
619
650
  }
620
651
 
652
+ //#endregion
653
+ //#region src/utils/width.ts
654
+ /**
655
+ * Get the visual width of the string.
656
+ */
657
+ function getWidth(str) {
658
+ let width = 0;
659
+ for (const c of str) if (c === " ") width += 4 - width % 4;
660
+ else width++;
661
+ return width;
662
+ }
663
+ /**
664
+ * Get a slice of the string by visual width.
665
+ */
666
+ function sliceWidth(str, start, end) {
667
+ const buffer = [...str];
668
+ let width = 0;
669
+ let c;
670
+ while (c = buffer.shift()) {
671
+ if (start <= width) {
672
+ buffer.unshift(c);
673
+ break;
674
+ }
675
+ if (c === " ") width += 4 - width % 4;
676
+ else width++;
677
+ }
678
+ if (buffer.length === 0) return "";
679
+ let result = " ".repeat(width - start);
680
+ if (end == null) return `${result}${buffer.join("")}`;
681
+ while (c = buffer.shift()) {
682
+ let newWidth;
683
+ if (c === " ") newWidth = width + 4 - width % 4;
684
+ else newWidth = width + 1;
685
+ if (end < newWidth) {
686
+ buffer.unshift(c);
687
+ break;
688
+ }
689
+ result += c;
690
+ width = newWidth;
691
+ }
692
+ if (buffer.length === 0) return result;
693
+ result += " ".repeat(end - width);
694
+ return result;
695
+ }
696
+ /**
697
+ * Get the character at the visual width.
698
+ */
699
+ function atWidth(str, target) {
700
+ let width = 0;
701
+ for (const c of str) {
702
+ if (target === width) return c;
703
+ if (target < width) return " ";
704
+ if (c === " ") width += 4 - width % 4;
705
+ else width++;
706
+ }
707
+ return null;
708
+ }
709
+
621
710
  //#endregion
622
711
  //#region src/rules/blockquote-marker-alignment.ts
623
712
  var blockquote_marker_alignment_default = createRule("blockquote-marker-alignment", {
@@ -626,7 +715,7 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
626
715
  docs: {
627
716
  description: "enforce consistent alignment of blockquote markers",
628
717
  categories: ["recommended", "standard"],
629
- listCategory: "Stylistic"
718
+ listCategory: "Whitespace"
630
719
  },
631
720
  fixable: "whitespace",
632
721
  hasSuggestions: false,
@@ -655,38 +744,33 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
655
744
  const endLine = loc.end.line;
656
745
  const base = getBlockquoteLevelFromLine(sourceCode, startLine).blockquoteMarkers.get(blockquoteLevel);
657
746
  if (!base) return;
747
+ const baseBeforeMarker = sourceCode.lines[startLine - 1].slice(0, base.loc.start.column - 1);
748
+ const baseIndentWidth = getWidth(baseBeforeMarker);
658
749
  for (let lineNumber = startLine + 1; lineNumber <= endLine; lineNumber++) {
659
750
  const marker = getBlockquoteLevelFromLine(sourceCode, lineNumber).blockquoteMarkers.get(blockquoteLevel);
660
751
  if (!marker) continue;
661
- if (base.index === marker.index) continue;
752
+ const indentWidth = getWidth(sourceCode.lines[lineNumber - 1].slice(0, marker.loc.start.column - 1));
753
+ if (baseIndentWidth === indentWidth) continue;
662
754
  blockquoteStack.reported = true;
663
755
  context.report({
664
756
  node,
665
- loc: {
666
- start: {
667
- line: lineNumber,
668
- column: marker.index + 1
669
- },
670
- end: {
671
- line: lineNumber,
672
- column: marker.index + 2
673
- }
674
- },
757
+ loc: marker.loc,
675
758
  messageId: "inconsistentAlignment",
676
759
  fix(fixer) {
677
760
  const line = getParsedLines(sourceCode).get(lineNumber);
678
- if (marker.index < base.index) {
679
- const addSpaces = " ".repeat(base.index - marker.index);
680
- return fixer.insertTextBeforeRange([line.range[0] + marker.index, line.range[0] + marker.index], addSpaces);
761
+ if (indentWidth < baseIndentWidth) {
762
+ const addSpaces = " ".repeat(baseIndentWidth - indentWidth);
763
+ return fixer.insertTextBeforeRange([line.range[0] + marker.loc.start.column - 1, line.range[0] + marker.loc.start.column - 1], addSpaces);
681
764
  }
682
- if (blockquoteLevel === 1) {
683
- const expectedSpaces = " ".repeat(base.index);
684
- return fixer.replaceTextRange([line.range[0], line.range[0] + marker.index], expectedSpaces);
765
+ let newBeforeMarker = line.text.slice(0, marker.loc.start.column - 1);
766
+ while (getWidth(newBeforeMarker) > baseIndentWidth) {
767
+ const last = newBeforeMarker.at(-1);
768
+ if (last && isWhitespace(last)) newBeforeMarker = newBeforeMarker.slice(0, -1);
769
+ else return null;
685
770
  }
686
- if (line.text.slice(0, line.range[0] + marker.index).includes(" ")) return null;
687
- let removeStartIndex = marker.index;
688
- for (; removeStartIndex > base.index; removeStartIndex--) if (line.text[removeStartIndex - 1] !== " ") break;
689
- return fixer.removeRange([line.range[0] + removeStartIndex, line.range[0] + marker.index]);
771
+ if (getWidth(newBeforeMarker) < baseIndentWidth) newBeforeMarker += " ".repeat(baseIndentWidth - getWidth(newBeforeMarker));
772
+ if (!baseBeforeMarker.includes(">") || baseBeforeMarker === newBeforeMarker) return fixer.replaceTextRange([line.range[0], line.range[0] + marker.loc.start.column - 1], newBeforeMarker);
773
+ return null;
690
774
  }
691
775
  });
692
776
  }
@@ -714,7 +798,7 @@ function getOtherMarker(unavailableMarker) {
714
798
  /**
715
799
  * Parse rule options.
716
800
  */
717
- function parseOptions$2(options) {
801
+ function parseOptions$4(options) {
718
802
  const primary = options.primary || "-";
719
803
  const secondary = options.secondary || getOtherMarker(primary);
720
804
  if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
@@ -746,7 +830,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
746
830
  docs: {
747
831
  description: "enforce consistent bullet list (unordered list) marker style",
748
832
  categories: ["standard"],
749
- listCategory: "Stylistic"
833
+ listCategory: "Notation"
750
834
  },
751
835
  fixable: "code",
752
836
  hasSuggestions: false,
@@ -782,7 +866,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
782
866
  },
783
867
  create(context) {
784
868
  const sourceCode = context.sourceCode;
785
- const options = parseOptions$2(context.options[0] || {});
869
+ const options = parseOptions$4(context.options[0] || {});
786
870
  let containerStack = {
787
871
  node: sourceCode.ast,
788
872
  level: 1,
@@ -1045,7 +1129,7 @@ var code_fence_length_default = createRule("code-fence-length", {
1045
1129
  docs: {
1046
1130
  description: "enforce consistent code fence length in fenced code blocks.",
1047
1131
  categories: ["standard"],
1048
- listCategory: "Stylistic"
1132
+ listCategory: "Decorative"
1049
1133
  },
1050
1134
  fixable: "code",
1051
1135
  hasSuggestions: false,
@@ -1182,7 +1266,7 @@ var code_fence_style_default = createRule("code-fence-style", {
1182
1266
  docs: {
1183
1267
  description: "enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks.",
1184
1268
  categories: ["standard"],
1185
- listCategory: "Stylistic"
1269
+ listCategory: "Notation"
1186
1270
  },
1187
1271
  fixable: "code",
1188
1272
  hasSuggestions: false,
@@ -1229,7 +1313,7 @@ var definitions_last_default = createRule("definitions-last", {
1229
1313
  docs: {
1230
1314
  description: "require link definitions and footnote definitions to be placed at the end of the document",
1231
1315
  categories: [],
1232
- listCategory: "Stylistic"
1316
+ listCategory: "Notation"
1233
1317
  },
1234
1318
  fixable: "code",
1235
1319
  hasSuggestions: false,
@@ -3353,7 +3437,7 @@ var emphasis_delimiters_style_default = createRule("emphasis-delimiters-style",
3353
3437
  docs: {
3354
3438
  description: "enforce a consistent delimiter style for emphasis and strong emphasis",
3355
3439
  categories: ["standard"],
3356
- listCategory: "Stylistic"
3440
+ listCategory: "Notation"
3357
3441
  },
3358
3442
  fixable: "code",
3359
3443
  hasSuggestions: false,
@@ -3535,7 +3619,7 @@ var hard_linebreak_style_default = createRule("hard-linebreak-style", {
3535
3619
  docs: {
3536
3620
  description: "enforce consistent hard linebreak style.",
3537
3621
  categories: ["recommended", "standard"],
3538
- listCategory: "Stylistic"
3622
+ listCategory: "Notation"
3539
3623
  },
3540
3624
  fixable: "code",
3541
3625
  hasSuggestions: false,
@@ -4334,275 +4418,224 @@ var heading_casing_default = createRule("heading-casing", {
4334
4418
  });
4335
4419
 
4336
4420
  //#endregion
4337
- //#region src/utils/setext-heading.ts
4338
- /**
4339
- * Parse the setext heading.
4340
- */
4341
- function parseSetextHeading(sourceCode, node) {
4342
- if (getHeadingKind(sourceCode, node) !== "setext") return null;
4343
- const lines = getParsedLines(sourceCode);
4344
- const contentLines = [];
4345
- const nodeLoc = sourceCode.getLoc(node);
4346
- for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
4347
- const content = parseContent(lines.get(lineNumber));
4348
- contentLines.push(content);
4421
+ //#region src/utils/character-cursor.ts
4422
+ var CharacterCursor = class {
4423
+ text;
4424
+ constructor(text) {
4425
+ this.text = text;
4349
4426
  }
4350
- const underline = parseUnderline(lines.get(nodeLoc.end.line));
4351
- if (!underline) return null;
4352
- return {
4353
- contentLines,
4354
- underline
4355
- };
4356
- }
4357
- /**
4358
- * Parse the content line of a setext heading.
4359
- */
4360
- function parseContent(line) {
4361
- let prefix = "";
4362
- let spaceBefore = "";
4363
- let suffix = "";
4364
- for (let index = 0; index < line.text.length; index++) {
4365
- const c = line.text[index];
4366
- if (!c.trim()) {
4367
- spaceBefore += c;
4368
- continue;
4427
+ curr() {
4428
+ return this.text[this.index];
4429
+ }
4430
+ currIndex() {
4431
+ return this.index;
4432
+ }
4433
+ setCurrIndex(index) {
4434
+ this.index = index;
4435
+ }
4436
+ isWhitespace(index) {
4437
+ if (index >= this.text.length) return false;
4438
+ const ch = this.text[index];
4439
+ if (isWhitespace(ch)) return true;
4440
+ if (ch !== ">") return false;
4441
+ const prefix = [ch];
4442
+ for (let prev = index - 1; prev >= 0; prev--) {
4443
+ const prevCh$1 = this.text[prev];
4444
+ if (prevCh$1 === "\n") break;
4445
+ if (isSpaceOrTab(prevCh$1) || prevCh$1 === ">") {
4446
+ prefix.unshift(prevCh$1);
4447
+ continue;
4448
+ }
4449
+ return false;
4369
4450
  }
4370
- if (c === ">" && spaceBefore.length < 4) {
4371
- prefix += spaceBefore + c;
4372
- spaceBefore = "";
4373
- continue;
4451
+ let width = 0;
4452
+ let leadingMarkerOffset = 0;
4453
+ let prevCh;
4454
+ for (const currCh of prefix) {
4455
+ if (currCh === ">") {
4456
+ if (width - leadingMarkerOffset > 3) return false;
4457
+ leadingMarkerOffset = width + 1;
4458
+ }
4459
+ if (currCh === " ") width += 4 - width % 4;
4460
+ else width++;
4461
+ if (prevCh === ">" && isSpaceOrTab(currCh)) leadingMarkerOffset++;
4462
+ prevCh = currCh;
4374
4463
  }
4375
- suffix = line.text.slice(index);
4376
- break;
4464
+ return true;
4377
4465
  }
4378
- const content = suffix.trimEnd();
4379
- const spaceAfter = suffix.slice(content.length);
4380
- return {
4381
- text: content,
4382
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
4383
- loc: {
4384
- start: {
4385
- line: line.line,
4386
- column: prefix.length + spaceBefore.length + 1
4387
- },
4388
- end: {
4389
- line: line.line,
4390
- column: prefix.length + spaceBefore.length + content.length + 1
4466
+ };
4467
+ var ForwardCharacterCursor = class extends CharacterCursor {
4468
+ index;
4469
+ constructor(text) {
4470
+ super(text);
4471
+ this.index = 0;
4472
+ }
4473
+ next() {
4474
+ this.index++;
4475
+ return this.text[this.index];
4476
+ }
4477
+ finished() {
4478
+ return this.index >= this.text.length;
4479
+ }
4480
+ skipSpaces() {
4481
+ while (this.index < this.text.length && this.isWhitespace(this.index)) this.index++;
4482
+ }
4483
+ /**
4484
+ * Skip until the end by the given condition
4485
+ */
4486
+ skipUntilEnd(checkEnd) {
4487
+ while (this.index < this.text.length) {
4488
+ const c = this.text[this.index];
4489
+ if (checkEnd(c, this.index)) return true;
4490
+ this.index++;
4491
+ if (c !== "\\") continue;
4492
+ if (this.index < this.text.length && (this.text[this.index] === "\\" || checkEnd(this.text[this.index], this.index)) && !isWhitespace(this.text[this.index])) this.index++;
4493
+ }
4494
+ return false;
4495
+ }
4496
+ };
4497
+ var BackwardCharacterCursor = class extends CharacterCursor {
4498
+ index;
4499
+ constructor(text) {
4500
+ super(text);
4501
+ this.index = text.length - 1;
4502
+ }
4503
+ prev() {
4504
+ this.index--;
4505
+ return this.text[this.index];
4506
+ }
4507
+ finished() {
4508
+ return this.index < 0;
4509
+ }
4510
+ skipSpaces() {
4511
+ while (this.index >= 0 && this.isWhitespace(this.index)) this.index--;
4512
+ }
4513
+ /**
4514
+ * Skip until the start by the given condition
4515
+ */
4516
+ skipUntilStart(checkStart) {
4517
+ while (this.index >= 0) {
4518
+ const c = this.text[this.index];
4519
+ if (checkStart(c, this.index)) {
4520
+ if (this.index > 1 && !isWhitespace(c)) {
4521
+ let escapeText = "";
4522
+ while (this.text.endsWith(`${escapeText}\\`, this.index)) escapeText += "\\";
4523
+ if (escapeText.length % 2 === 1) {
4524
+ this.index -= escapeText.length + 1;
4525
+ continue;
4526
+ }
4527
+ }
4528
+ return true;
4391
4529
  }
4392
- },
4393
- raws: {
4394
- prefix,
4395
- spaceBefore,
4396
- spaceAfter
4530
+ this.index--;
4397
4531
  }
4532
+ return false;
4533
+ }
4534
+ };
4535
+
4536
+ //#endregion
4537
+ //#region src/utils/link-definition.ts
4538
+ /**
4539
+ * Parse the link definition.
4540
+ */
4541
+ function parseLinkDefinition(sourceCode, node) {
4542
+ const text = sourceCode.getText(node);
4543
+ const parsed = parseLinkDefinitionFromText(text);
4544
+ if (!parsed) return null;
4545
+ const nodeRange = sourceCode.getRange(node);
4546
+ const labelRange = [nodeRange[0] + parsed.label.range[0], nodeRange[0] + parsed.label.range[1]];
4547
+ const destinationRange = [nodeRange[0] + parsed.destination.range[0], nodeRange[0] + parsed.destination.range[1]];
4548
+ return {
4549
+ label: {
4550
+ text: parsed.label.text,
4551
+ range: labelRange,
4552
+ loc: getSourceLocationFromRange(sourceCode, node, labelRange)
4553
+ },
4554
+ destination: {
4555
+ type: parsed.destination.type,
4556
+ text: parsed.destination.text,
4557
+ range: destinationRange,
4558
+ loc: getSourceLocationFromRange(sourceCode, node, destinationRange)
4559
+ },
4560
+ title: parsed.title ? {
4561
+ type: parsed.title.type,
4562
+ text: parsed.title.text,
4563
+ range: [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]],
4564
+ loc: getSourceLocationFromRange(sourceCode, node, [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]])
4565
+ } : null
4398
4566
  };
4399
4567
  }
4400
4568
  /**
4401
- * Parse the underline of a setext heading.
4569
+ * Parse the link definition from the given text.
4402
4570
  */
4403
- function parseUnderline(line) {
4404
- let marker = null;
4405
- let underlineText = "";
4406
- let prefix = "";
4407
- let spaceBefore = "";
4408
- let spaceAfter = "";
4409
- for (let index = line.text.length - 1; index >= 0; index--) {
4410
- const c = line.text[index];
4411
- if (!marker) {
4412
- if (c === "=" || c === "-") {
4413
- underlineText = c + underlineText;
4414
- marker = c;
4415
- } else if (!c.trim()) spaceAfter = c + spaceAfter;
4416
- else return null;
4417
- continue;
4418
- }
4419
- if (c === marker) {
4420
- underlineText = c + spaceBefore + underlineText;
4421
- spaceBefore = "";
4422
- } else if (!c.trim()) spaceBefore = c + spaceBefore;
4423
- else {
4424
- prefix = line.text.slice(0, index + 1);
4425
- break;
4426
- }
4571
+ function parseLinkDefinitionFromText(text) {
4572
+ if (!text.startsWith("[")) return null;
4573
+ const cursor = new ForwardCharacterCursor(text);
4574
+ const labelStartIndex = 0;
4575
+ cursor.next();
4576
+ if (!cursor.skipUntilEnd((c) => c === "]")) return null;
4577
+ cursor.next();
4578
+ const labelRange = [labelStartIndex, cursor.currIndex()];
4579
+ if (!text.slice(labelRange[0] + 1, labelRange[1] - 1).trim()) return null;
4580
+ if (cursor.curr() !== ":") return null;
4581
+ const label = {
4582
+ text: text.slice(...labelRange),
4583
+ range: labelRange
4584
+ };
4585
+ cursor.next();
4586
+ cursor.skipSpaces();
4587
+ let destination;
4588
+ const destinationStartIndex = cursor.currIndex();
4589
+ if (cursor.curr() === "<") {
4590
+ cursor.next();
4591
+ if (!cursor.skipUntilEnd((c) => c === ">")) return null;
4592
+ cursor.next();
4593
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4594
+ destination = {
4595
+ type: "pointy-bracketed",
4596
+ text: text.slice(...destinationRange),
4597
+ range: destinationRange
4598
+ };
4599
+ } else {
4600
+ if (cursor.finished()) return null;
4601
+ cursor.skipUntilEnd((c, i) => cursor.isWhitespace(i) || isAsciiControlCharacter(c));
4602
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4603
+ destination = {
4604
+ type: "bare",
4605
+ text: text.slice(...destinationRange),
4606
+ range: destinationRange
4607
+ };
4427
4608
  }
4428
- if (!marker) return null;
4429
- const underlineLoc = {
4430
- start: {
4431
- line: line.line,
4432
- column: prefix.length + spaceBefore.length + 1
4433
- },
4434
- end: {
4435
- line: line.line,
4436
- column: prefix.length + spaceBefore.length + underlineText.length + 1
4437
- }
4609
+ cursor.skipSpaces();
4610
+ if (cursor.finished()) return {
4611
+ label,
4612
+ destination,
4613
+ title: null
4438
4614
  };
4615
+ let title;
4616
+ const titleStartIndex = cursor.currIndex();
4617
+ const startChar = cursor.curr();
4618
+ if (startChar === "'" || startChar === "\"" || startChar === "(") {
4619
+ cursor.next();
4620
+ const endChar = startChar === "(" ? ")" : startChar;
4621
+ if (!cursor.skipUntilEnd((c) => c === endChar)) return null;
4622
+ cursor.next();
4623
+ const titleRange = [titleStartIndex, cursor.currIndex()];
4624
+ title = {
4625
+ type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
4626
+ text: text.slice(...titleRange),
4627
+ range: titleRange
4628
+ };
4629
+ } else return null;
4630
+ cursor.skipSpaces();
4631
+ if (!cursor.finished()) return null;
4439
4632
  return {
4440
- text: underlineText,
4441
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
4442
- loc: underlineLoc,
4443
- marker,
4444
- raws: {
4445
- prefix,
4446
- spaceBefore,
4447
- spaceAfter
4448
- }
4633
+ label,
4634
+ destination,
4635
+ title
4449
4636
  };
4450
4637
  }
4451
4638
 
4452
- //#endregion
4453
- //#region src/rules/level1-heading-style.ts
4454
- var level1_heading_style_default = createRule("level1-heading-style", {
4455
- meta: {
4456
- type: "layout",
4457
- docs: {
4458
- description: "enforce consistent style for level 1 headings",
4459
- categories: ["standard"],
4460
- listCategory: "Stylistic"
4461
- },
4462
- fixable: "code",
4463
- hasSuggestions: false,
4464
- schema: [{
4465
- type: "object",
4466
- properties: {
4467
- style: { enum: ["atx", "setext"] },
4468
- allowMultilineSetext: { type: "boolean" }
4469
- },
4470
- additionalProperties: false
4471
- }],
4472
- messages: {
4473
- expectedAtx: "Expected ATX style heading (# Heading).",
4474
- expectedSetext: "Expected Setext style heading (Heading\\n======).",
4475
- multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4476
- }
4477
- },
4478
- create(context) {
4479
- const sourceCode = context.sourceCode;
4480
- const opt = context.options[0] || {};
4481
- const style = opt.style ?? "atx";
4482
- const allowMultilineSetext = opt.allowMultilineSetext;
4483
- return { heading(node) {
4484
- if (node.depth !== 1) return;
4485
- const headingKind = getHeadingKind(sourceCode, node);
4486
- if (style === "atx") {
4487
- if (headingKind !== "setext") return;
4488
- const parsed = parseSetextHeading(sourceCode, node);
4489
- if (!parsed) return;
4490
- if (parsed.contentLines.length > 1) {
4491
- if (allowMultilineSetext) return;
4492
- context.report({
4493
- node,
4494
- messageId: "multilineSetextNotAllowed"
4495
- });
4496
- return;
4497
- }
4498
- context.report({
4499
- node,
4500
- messageId: "expectedAtx",
4501
- *fix(fixer) {
4502
- const heading = parsed.contentLines[0];
4503
- yield fixer.insertTextBeforeRange(heading.range, "# ");
4504
- const lines = getParsedLines(sourceCode);
4505
- yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4506
- }
4507
- });
4508
- } else if (style === "setext") {
4509
- if (headingKind !== "atx" || node.children.length === 0) return;
4510
- context.report({
4511
- node,
4512
- messageId: "expectedSetext",
4513
- *fix(fixer) {
4514
- const parsed = parseATXHeading(sourceCode, node);
4515
- if (!parsed) return;
4516
- yield fixer.removeRange([parsed.openingSequence.range[0], parsed.content.range[0]]);
4517
- if (parsed.closingSequence) yield fixer.removeRange([parsed.content.range[1], parsed.after?.range[1] ?? parsed.closingSequence.range[1]]);
4518
- const lines = getParsedLines(sourceCode);
4519
- const underline = "=".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4520
- const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
4521
- yield fixer.insertTextAfter(node, appendingText);
4522
- }
4523
- });
4524
- }
4525
- } };
4526
- }
4527
- });
4528
-
4529
- //#endregion
4530
- //#region src/rules/level2-heading-style.ts
4531
- var level2_heading_style_default = createRule("level2-heading-style", {
4532
- meta: {
4533
- type: "layout",
4534
- docs: {
4535
- description: "enforce consistent style for level 2 headings",
4536
- categories: ["standard"],
4537
- listCategory: "Stylistic"
4538
- },
4539
- fixable: "code",
4540
- hasSuggestions: false,
4541
- schema: [{
4542
- type: "object",
4543
- properties: {
4544
- style: { enum: ["atx", "setext"] },
4545
- allowMultilineSetext: { type: "boolean" }
4546
- },
4547
- additionalProperties: false
4548
- }],
4549
- messages: {
4550
- expectedAtx: "Expected ATX style heading (## Heading).",
4551
- expectedSetext: "Expected Setext style heading (Heading\\n------).",
4552
- multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4553
- }
4554
- },
4555
- create(context) {
4556
- const sourceCode = context.sourceCode;
4557
- const opt = context.options[0] || {};
4558
- const style = opt.style ?? "atx";
4559
- const allowMultilineSetext = opt.allowMultilineSetext;
4560
- return { heading(node) {
4561
- if (node.depth !== 2) return;
4562
- const headingKind = getHeadingKind(sourceCode, node);
4563
- if (style === "atx") {
4564
- if (headingKind !== "setext") return;
4565
- const parsed = parseSetextHeading(sourceCode, node);
4566
- if (!parsed) return;
4567
- if (parsed.contentLines.length > 1) {
4568
- if (allowMultilineSetext) return;
4569
- context.report({
4570
- node,
4571
- messageId: "multilineSetextNotAllowed"
4572
- });
4573
- return;
4574
- }
4575
- context.report({
4576
- node,
4577
- messageId: "expectedAtx",
4578
- *fix(fixer) {
4579
- const heading = parsed.contentLines[0];
4580
- yield fixer.insertTextBeforeRange(heading.range, "## ");
4581
- const lines = getParsedLines(sourceCode);
4582
- yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4583
- }
4584
- });
4585
- } else if (style === "setext") {
4586
- if (headingKind !== "atx" || node.children.length === 0) return;
4587
- context.report({
4588
- node,
4589
- messageId: "expectedSetext",
4590
- *fix(fixer) {
4591
- const parsed = parseATXHeading(sourceCode, node);
4592
- if (!parsed) return;
4593
- yield fixer.removeRange([parsed.openingSequence.range[0], parsed.content.range[0]]);
4594
- if (parsed.closingSequence) yield fixer.removeRange([parsed.content.range[1], parsed.after?.range[1] ?? parsed.closingSequence.range[1]]);
4595
- const lines = getParsedLines(sourceCode);
4596
- const underline = "-".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4597
- const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
4598
- yield fixer.insertTextAfter(node, appendingText);
4599
- }
4600
- });
4601
- }
4602
- } };
4603
- }
4604
- });
4605
-
4606
4639
  //#endregion
4607
4640
  //#region src/utils/link.ts
4608
4641
  /**
@@ -4653,92 +4686,73 @@ function parseInlineLink(sourceCode, node) {
4653
4686
  * Parse the inline link destination and link title from the given text.
4654
4687
  */
4655
4688
  function parseInlineLinkDestAndTitleFromText(text) {
4656
- let index = 0;
4657
- if (text[index] !== "(") return null;
4658
- index++;
4659
- skipSpaces();
4689
+ if (!text.startsWith("(")) return null;
4690
+ const cursor = new ForwardCharacterCursor(text);
4691
+ cursor.next();
4692
+ cursor.skipSpaces();
4660
4693
  let destination;
4661
- const destinationStartIndex = index;
4662
- if (text[index] === "<") {
4663
- index++;
4664
- if (!skipUntilEnd((c) => c === ">")) return null;
4665
- index++;
4666
- const destinationRange = [destinationStartIndex, index];
4694
+ const destinationStartIndex = cursor.currIndex();
4695
+ if (cursor.curr() === "<") {
4696
+ cursor.next();
4697
+ if (!cursor.skipUntilEnd((c) => c === ">")) return null;
4698
+ cursor.next();
4699
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4667
4700
  destination = {
4668
4701
  type: "pointy-bracketed",
4669
4702
  text: text.slice(...destinationRange),
4670
4703
  range: destinationRange
4671
4704
  };
4672
4705
  } else {
4673
- if (text.length <= index) return null;
4674
- skipUntilEnd((c) => isWhitespace(c) || isAsciiControlCharacter(c) || c === ")");
4675
- const destinationRange = [destinationStartIndex, index];
4706
+ if (cursor.finished()) return null;
4707
+ cursor.skipUntilEnd((c, i) => cursor.isWhitespace(i) || isAsciiControlCharacter(c) || c === ")");
4708
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4676
4709
  destination = {
4677
4710
  type: "bare",
4678
4711
  text: text.slice(...destinationRange),
4679
4712
  range: destinationRange
4680
4713
  };
4681
4714
  }
4682
- skipSpaces();
4683
- if (text[index] === ")") {
4684
- const closingParenStartIndex$1 = index;
4685
- index++;
4686
- skipSpaces();
4687
- if (index < text.length) return null;
4715
+ cursor.skipSpaces();
4716
+ if (cursor.curr() === ")") {
4717
+ const closingParenStartIndex$1 = cursor.currIndex();
4718
+ cursor.next();
4719
+ cursor.skipSpaces();
4720
+ if (!cursor.finished()) return null;
4688
4721
  return {
4689
4722
  openingParen: { range: [0, 1] },
4690
4723
  destination,
4691
4724
  title: null,
4692
- closingParen: { range: [closingParenStartIndex$1, index] }
4725
+ closingParen: { range: [closingParenStartIndex$1, closingParenStartIndex$1 + 1] }
4693
4726
  };
4694
4727
  }
4695
- if (text.length <= index) return null;
4728
+ if (cursor.finished()) return null;
4696
4729
  let title;
4697
- const titleStartIndex = index;
4698
- const startChar = text[index];
4730
+ const titleStartIndex = cursor.currIndex();
4731
+ const startChar = cursor.curr();
4699
4732
  if (startChar === "'" || startChar === "\"" || startChar === "(") {
4700
- index++;
4733
+ cursor.next();
4701
4734
  const endChar = startChar === "(" ? ")" : startChar;
4702
- if (!skipUntilEnd((c) => c === endChar)) return null;
4703
- index++;
4704
- const titleRange = [titleStartIndex, index];
4735
+ if (!cursor.skipUntilEnd((c) => c === endChar)) return null;
4736
+ cursor.next();
4737
+ const titleRange = [titleStartIndex, cursor.currIndex()];
4705
4738
  title = {
4706
4739
  type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
4707
4740
  text: text.slice(...titleRange),
4708
4741
  range: titleRange
4709
4742
  };
4710
4743
  } else return null;
4711
- skipSpaces();
4712
- if (text[index] !== ")") return null;
4713
- const closingParenStartIndex = index;
4714
- index++;
4715
- skipSpaces();
4716
- if (index < text.length) return null;
4744
+ cursor.skipSpaces();
4745
+ if (cursor.curr() !== ")") return null;
4746
+ const closingParenStartIndex = cursor.currIndex();
4747
+ cursor.next();
4748
+ cursor.skipSpaces();
4749
+ if (!cursor.finished()) return null;
4717
4750
  return {
4718
4751
  openingParen: { range: [0, 1] },
4719
4752
  destination,
4720
4753
  title,
4721
- closingParen: { range: [closingParenStartIndex, index] }
4754
+ closingParen: { range: [closingParenStartIndex, closingParenStartIndex + 1] }
4722
4755
  };
4723
- /**
4724
- * Skip spaces
4725
- */
4726
- function skipSpaces() {
4727
- while (index < text.length && isWhitespace(text[index])) index++;
4728
- }
4729
- /**
4730
- * Skip until the end by the given condition
4731
- */
4732
- function skipUntilEnd(checkEnd) {
4733
- while (index < text.length) {
4734
- const c = text[index];
4735
- if (checkEnd(c)) return true;
4736
- index++;
4737
- if (c !== "\\") continue;
4738
- if (index < text.length && (text[index] === "\\" || checkEnd(text[index])) && !isWhitespace(text[index])) index++;
4739
- }
4740
- return false;
4741
- }
4742
4756
  }
4743
4757
 
4744
4758
  //#endregion
@@ -4845,58 +4859,57 @@ function parseImage(sourceCode, node) {
4845
4859
  */
4846
4860
  function parseImageFromText(text) {
4847
4861
  if (!text.startsWith("![")) return null;
4848
- let index = text.length - 1;
4849
- skipSpaces();
4850
- if (text[index] !== ")") return null;
4851
- const closingParenStartIndex = index;
4852
- index--;
4853
- skipSpaces();
4862
+ const cursor = new BackwardCharacterCursor(text);
4863
+ cursor.skipSpaces();
4864
+ if (cursor.curr() !== ")") return null;
4865
+ const closingParenStartIndex = cursor.currIndex();
4866
+ cursor.prev();
4867
+ cursor.skipSpaces();
4854
4868
  let title = null;
4855
- const titleEndIndex = index + 1;
4856
- const endChar = text[index];
4869
+ const titleEndIndex = cursor.currIndex() + 1;
4870
+ const endChar = cursor.curr();
4857
4871
  if (endChar === "'" || endChar === "\"" || endChar === ")") {
4858
- index--;
4872
+ cursor.prev();
4859
4873
  const startChar = endChar === ")" ? "(" : endChar;
4860
- if (skipUntilStart((c) => c === startChar)) {
4861
- const titleRange = [index, titleEndIndex];
4862
- index--;
4874
+ if (cursor.skipUntilStart((c) => c === startChar)) {
4875
+ const titleRange = [cursor.currIndex(), titleEndIndex];
4876
+ cursor.prev();
4863
4877
  title = {
4864
4878
  type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
4865
4879
  text: text.slice(...titleRange),
4866
4880
  range: titleRange
4867
4881
  };
4868
- skipSpaces();
4882
+ cursor.skipSpaces();
4869
4883
  }
4870
4884
  }
4871
- if (title == null) index = titleEndIndex - 1;
4885
+ if (title == null) cursor.setCurrIndex(titleEndIndex - 1);
4872
4886
  let destination;
4873
- const destinationEndIndex = index + 1;
4874
- if (text[index] === ">") {
4875
- index--;
4876
- if (!skipUntilStart((c) => c === "<")) return null;
4877
- const destinationRange = [index, destinationEndIndex];
4878
- index--;
4887
+ const destinationEndIndex = cursor.currIndex() + 1;
4888
+ if (cursor.curr() === ">") {
4889
+ cursor.prev();
4890
+ if (!cursor.skipUntilStart((c) => c === "<")) return null;
4891
+ const destinationRange = [cursor.currIndex(), destinationEndIndex];
4892
+ cursor.prev();
4879
4893
  destination = {
4880
4894
  type: "pointy-bracketed",
4881
4895
  text: text.slice(...destinationRange),
4882
4896
  range: destinationRange
4883
4897
  };
4884
4898
  } else {
4885
- if (text.length <= index) return null;
4886
- skipUntilStart((c) => isWhitespace(c) || isAsciiControlCharacter(c) || c === "(");
4887
- const destinationRange = [index + 1, destinationEndIndex];
4899
+ if (cursor.finished()) return null;
4900
+ cursor.skipUntilStart((c, i) => cursor.isWhitespace(i) || isAsciiControlCharacter(c) || c === "(");
4901
+ const destinationRange = [cursor.currIndex() + 1, destinationEndIndex];
4888
4902
  destination = {
4889
4903
  type: "bare",
4890
4904
  text: text.slice(...destinationRange),
4891
4905
  range: destinationRange
4892
4906
  };
4893
4907
  }
4894
- skipSpaces();
4895
- if (text[index] !== "(") return null;
4896
- const openingParenStartIndex = index;
4897
- index--;
4898
- if (text[index] !== "]") return null;
4899
- const textRange = [1, index + 1];
4908
+ cursor.skipSpaces();
4909
+ if (cursor.curr() !== "(") return null;
4910
+ const openingParenStartIndex = cursor.currIndex();
4911
+ if (cursor.prev() !== "]") return null;
4912
+ const textRange = [1, cursor.currIndex() + 1];
4900
4913
  return {
4901
4914
  openingParen: { range: [openingParenStartIndex, openingParenStartIndex + 1] },
4902
4915
  text: { range: textRange },
@@ -4904,33 +4917,6 @@ function parseImageFromText(text) {
4904
4917
  title,
4905
4918
  closingParen: { range: [closingParenStartIndex, closingParenStartIndex + 1] }
4906
4919
  };
4907
- /**
4908
- * Skip spaces
4909
- */
4910
- function skipSpaces() {
4911
- while (index >= 0 && isWhitespace(text[index])) index--;
4912
- }
4913
- /**
4914
- * Skip until the start by the given condition
4915
- */
4916
- function skipUntilStart(checkStart) {
4917
- while (index >= 0) {
4918
- const c = text[index];
4919
- if (checkStart(c)) {
4920
- if (index > 1 && !isWhitespace(c)) {
4921
- let escapeText = "";
4922
- while (text.endsWith(`${escapeText}\\`, index)) escapeText += "\\";
4923
- if (escapeText.length % 2 === 1) {
4924
- index -= escapeText.length + 1;
4925
- continue;
4926
- }
4927
- }
4928
- return true;
4929
- }
4930
- index--;
4931
- }
4932
- return false;
4933
- }
4934
4920
  }
4935
4921
 
4936
4922
  //#endregion
@@ -4990,21 +4976,20 @@ function parseImageReference(sourceCode, node) {
4990
4976
  */
4991
4977
  function parseFullImageReferenceFromText(text) {
4992
4978
  if (!text.startsWith("![")) return null;
4993
- let index = text.length - 1;
4994
- skipSpaces();
4995
- if (text[index] !== "]") return null;
4996
- const labelEndIndex = index + 1;
4997
- index--;
4998
- skipSpaces();
4999
- if (!skipUntilStart((c) => c === "[")) return null;
5000
- const labelRange = [index, labelEndIndex];
5001
- index--;
4979
+ const cursor = new BackwardCharacterCursor(text);
4980
+ cursor.skipSpaces();
4981
+ if (cursor.curr() !== "]") return null;
4982
+ const labelEndIndex = cursor.currIndex() + 1;
4983
+ cursor.prev();
4984
+ cursor.skipSpaces();
4985
+ if (!cursor.skipUntilStart((c) => c === "[")) return null;
4986
+ const labelRange = [cursor.currIndex(), labelEndIndex];
5002
4987
  const label = {
5003
4988
  text: text.slice(...labelRange),
5004
4989
  range: labelRange
5005
4990
  };
5006
- if (text[index] !== "]") return null;
5007
- const textRange = [1, index + 1];
4991
+ if (cursor.prev() !== "]") return null;
4992
+ const textRange = [1, cursor.currIndex() + 1];
5008
4993
  return {
5009
4994
  text: {
5010
4995
  text: text.slice(...textRange),
@@ -5012,386 +4997,1438 @@ function parseFullImageReferenceFromText(text) {
5012
4997
  },
5013
4998
  label
5014
4999
  };
5015
- /**
5016
- * Skip spaces
5017
- */
5018
- function skipSpaces() {
5019
- while (index >= 0 && isWhitespace(text[index])) index--;
5020
- }
5021
- /**
5022
- * Skip until the start by the given condition
5023
- */
5024
- function skipUntilStart(checkStart) {
5025
- while (index >= 0) {
5026
- const c = text[index];
5027
- if (checkStart(c)) {
5028
- if (index > 1) {
5029
- let escapeText = "";
5030
- while (text.endsWith(`${escapeText}\\`, index)) escapeText += "\\";
5031
- if (escapeText.length % 2 === 1) {
5032
- index -= escapeText.length + 1;
5033
- continue;
5034
- }
5035
- }
5036
- return true;
5037
- }
5038
- index--;
5039
- }
5040
- return false;
5041
- }
5042
5000
  }
5043
5001
 
5044
5002
  //#endregion
5045
- //#region src/utils/link-definition.ts
5003
+ //#region src/utils/list-item.ts
5046
5004
  /**
5047
- * Parse the link definition.
5005
+ * Parse the list item.
5048
5006
  */
5049
- function parseLinkDefinition(sourceCode, node) {
5050
- const text = sourceCode.getText(node);
5051
- const parsed = parseLinkDefinitionFromText(text);
5052
- if (!parsed) return null;
5007
+ function parseListItem(sourceCode, node) {
5008
+ const marker = getListItemMarker(sourceCode, node);
5053
5009
  const nodeRange = sourceCode.getRange(node);
5054
- const labelRange = [nodeRange[0] + parsed.label.range[0], nodeRange[0] + parsed.label.range[1]];
5055
- const destinationRange = [nodeRange[0] + parsed.destination.range[0], nodeRange[0] + parsed.destination.range[1]];
5056
- return {
5057
- label: {
5058
- text: parsed.label.text,
5059
- range: labelRange,
5060
- loc: getSourceLocationFromRange(sourceCode, node, labelRange)
5061
- },
5062
- destination: {
5063
- type: parsed.destination.type,
5064
- text: parsed.destination.text,
5065
- range: destinationRange,
5066
- loc: getSourceLocationFromRange(sourceCode, node, destinationRange)
5067
- },
5068
- title: parsed.title ? {
5069
- type: parsed.title.type,
5070
- text: parsed.title.text,
5071
- range: [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]],
5072
- loc: getSourceLocationFromRange(sourceCode, node, [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]])
5073
- } : null
5010
+ const markerRange = [nodeRange[0], nodeRange[0] + marker.raw.length];
5011
+ const parsedMarker = marker.kind === "." || marker.kind === ")" ? {
5012
+ kind: marker.kind,
5013
+ text: marker.raw,
5014
+ value: marker.sequence.value,
5015
+ range: markerRange,
5016
+ loc: getSourceLocationFromRange(sourceCode, node, markerRange)
5017
+ } : {
5018
+ kind: marker.kind,
5019
+ text: marker.raw,
5020
+ range: markerRange,
5021
+ loc: getSourceLocationFromRange(sourceCode, node, markerRange)
5074
5022
  };
5075
- }
5076
- /**
5077
- * Parse the link definition from the given text.
5078
- */
5079
- function parseLinkDefinitionFromText(text) {
5080
- let index = 0;
5081
- if (text[index] !== "[") return null;
5082
- const labelStartIndex = index;
5083
- index++;
5084
- if (!skipUntilEnd((c) => c === "]")) return null;
5085
- index++;
5086
- const labelRange = [labelStartIndex, index];
5087
- if (!text.slice(labelRange[0] + 1, labelRange[1] - 1).trim()) return null;
5088
- if (text[index] !== ":") return null;
5089
- const label = {
5090
- text: text.slice(...labelRange),
5091
- range: labelRange
5023
+ if (node.checked == null) return {
5024
+ marker: parsedMarker,
5025
+ taskListItemMarker: null
5092
5026
  };
5093
- index++;
5094
- skipSpaces();
5095
- let destination;
5096
- const destinationStartIndex = index;
5097
- if (text[index] === "<") {
5098
- index++;
5099
- if (!skipUntilEnd((c) => c === ">")) return null;
5100
- index++;
5101
- const destinationRange = [destinationStartIndex, index];
5102
- destination = {
5103
- type: "pointy-bracketed",
5104
- text: text.slice(...destinationRange),
5105
- range: destinationRange
5106
- };
5107
- } else {
5108
- if (text.length <= index) return null;
5109
- skipUntilEnd((c) => isWhitespace(c) || isAsciiControlCharacter(c));
5110
- const destinationRange = [destinationStartIndex, index];
5111
- destination = {
5112
- type: "bare",
5113
- text: text.slice(...destinationRange),
5114
- range: destinationRange
5027
+ for (let index = nodeRange[0] + marker.raw.length; index < nodeRange[1]; index++) {
5028
+ const c = sourceCode.text[index];
5029
+ if (isSpaceOrTab(c)) continue;
5030
+ if (c !== "[") break;
5031
+ const middle = sourceCode.text[index + 1];
5032
+ if (middle !== "x" && middle !== " ") break;
5033
+ if (sourceCode.text[index + 2] !== "]") break;
5034
+ const taskListItemMarkerRange = [index, index + 3];
5035
+ return {
5036
+ marker: parsedMarker,
5037
+ taskListItemMarker: {
5038
+ text: sourceCode.text.slice(...taskListItemMarkerRange),
5039
+ range: taskListItemMarkerRange,
5040
+ loc: getSourceLocationFromRange(sourceCode, node, taskListItemMarkerRange)
5041
+ }
5115
5042
  };
5116
5043
  }
5117
- skipSpaces();
5118
- if (text.length <= index) return {
5119
- label,
5120
- destination,
5121
- title: null
5122
- };
5123
- let title;
5124
- const titleStartIndex = index;
5125
- const startChar = text[index];
5126
- if (startChar === "'" || startChar === "\"" || startChar === "(") {
5127
- index++;
5128
- const endChar = startChar === "(" ? ")" : startChar;
5129
- if (!skipUntilEnd((c) => c === endChar)) return null;
5130
- index++;
5131
- const titleRange = [titleStartIndex, index];
5132
- title = {
5133
- type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
5134
- text: text.slice(...titleRange),
5135
- range: titleRange
5136
- };
5137
- } else return null;
5138
- skipSpaces();
5139
- if (index < text.length) return null;
5140
5044
  return {
5141
- label,
5142
- destination,
5143
- title
5045
+ marker: parsedMarker,
5046
+ taskListItemMarker: null
5144
5047
  };
5145
- /**
5146
- * Skip spaces
5147
- */
5148
- function skipSpaces() {
5149
- while (index < text.length && isWhitespace(text[index])) index++;
5150
- }
5151
- /**
5152
- * Skip until the end by the given condition
5153
- */
5154
- function skipUntilEnd(checkEnd) {
5155
- while (index < text.length) {
5156
- const c = text[index];
5157
- if (checkEnd(c)) return true;
5158
- index++;
5159
- if (c !== "\\") continue;
5160
- if (index < text.length && (text[index] === "\\" || checkEnd(text[index])) && !isWhitespace(text[index])) index++;
5161
- }
5162
- return false;
5163
- }
5164
5048
  }
5165
5049
 
5166
5050
  //#endregion
5167
- //#region src/rules/link-bracket-newline.ts
5168
- var link_bracket_newline_default = createRule("link-bracket-newline", {
5051
+ //#region src/rules/indent.ts
5052
+ /**
5053
+ * Parse options.
5054
+ */
5055
+ function parseOptions$3(options) {
5056
+ const listItems = options?.listItems;
5057
+ return { listItems: {
5058
+ first: listItems?.first ?? 1,
5059
+ other: listItems?.other ?? "first",
5060
+ relativeTo: listItems?.relativeTo ?? "taskListMarkerEnd"
5061
+ } };
5062
+ }
5063
+ var indent_default = createRule("indent", {
5169
5064
  meta: {
5170
5065
  type: "layout",
5171
5066
  docs: {
5172
- description: "enforce linebreaks after opening and before closing link brackets",
5067
+ description: "enforce consistent indentation in Markdown files",
5173
5068
  categories: ["standard"],
5174
- listCategory: "Stylistic"
5069
+ listCategory: "Whitespace"
5175
5070
  },
5176
5071
  fixable: "whitespace",
5177
5072
  hasSuggestions: false,
5178
5073
  schema: [{
5179
5074
  type: "object",
5180
- properties: {
5181
- newline: { enum: [
5182
- "always",
5183
- "never",
5184
- "consistent"
5185
- ] },
5186
- multiline: { type: "boolean" }
5187
- },
5075
+ properties: { listItems: {
5076
+ type: "object",
5077
+ properties: {
5078
+ first: { anyOf: [{ const: "ignore" }, {
5079
+ type: "number",
5080
+ minimum: 1
5081
+ }] },
5082
+ other: { anyOf: [{ enum: ["first", "minimum"] }, {
5083
+ type: "number",
5084
+ minimum: 1
5085
+ }] },
5086
+ relativeTo: { enum: [
5087
+ "markerStart",
5088
+ "markerEnd",
5089
+ "taskListMarkerStart",
5090
+ "taskListMarkerEnd"
5091
+ ] }
5092
+ },
5093
+ additionalProperties: false
5094
+ } },
5188
5095
  additionalProperties: false
5189
5096
  }],
5190
5097
  messages: {
5191
- expectedNewlineAfterOpeningBracket: "Expected a linebreak after this opening bracket.",
5192
- unexpectedNewlineAfterOpeningBracket: "Unexpected linebreak after this opening bracket.",
5193
- expectedNewlineBeforeClosingBracket: "Expected a linebreak before this closing bracket.",
5194
- unexpectedNewlineBeforeClosingBracket: "Unexpected linebreak before this closing bracket."
5098
+ wrongIndentation: "Expected indentation of {{expected}} but found {{actual}}.",
5099
+ wrongIndentationFromBlockquoteMarker: "Expected indentation of {{expected}} from the blockquote marker but found {{actual}}.",
5100
+ wrongIndentationFromListMarker: "Expected indentation of {{expected}} from the list marker but found {{actual}}.",
5101
+ wrongIndentationWithTab: "Expected indentation of {{expected}} but found {{actual}} (tab width is 4).",
5102
+ wrongIndentationFromBlockquoteMarkerWithTab: "Expected indentation of {{expected}} from the blockquote marker but found {{actual}} (tab width is 4).",
5103
+ wrongIndentationFromListMarkerWithTab: "Expected indentation of {{expected}} from the list marker but found {{actual}} (tab width is 4)."
5195
5104
  }
5196
5105
  },
5197
5106
  create(context) {
5198
5107
  const sourceCode = context.sourceCode;
5199
- const optionProvider = parseOptions$3(context.options[0]);
5200
- /**
5201
- * Parse the options.
5202
- */
5203
- function parseOptions$3(option) {
5204
- const newline = option?.newline ?? "never";
5205
- const multiline = option?.multiline ?? false;
5206
- return (bracketsRange) => {
5207
- if (multiline) {
5208
- if (sourceCode.text.slice(bracketsRange[0] + 1, bracketsRange[1] - 1).trim().includes("\n")) return "always";
5108
+ const options = parseOptions$3(context.options[0]);
5109
+ class AbsBlockStack {
5110
+ violations = [];
5111
+ getCurrentBlockquote() {
5112
+ let block = this;
5113
+ while (block) {
5114
+ if (block.type === "blockquote") return block;
5115
+ block = block.upper;
5209
5116
  }
5210
- return newline;
5211
- };
5117
+ return null;
5118
+ }
5119
+ getCurrentListItemAtLine(lineNumber) {
5120
+ let block = this;
5121
+ while (block) {
5122
+ if (block.type === "blockquote") return null;
5123
+ if (block.type === "listItem") {
5124
+ if (sourceCode.getLoc(block.node).start.line === lineNumber) return block;
5125
+ }
5126
+ block = block.upper;
5127
+ }
5128
+ return null;
5129
+ }
5212
5130
  }
5213
- /**
5214
- * Verify the newline around the brackets.
5215
- */
5216
- function verifyNewlineAroundBrackets(node, bracketsRange) {
5217
- const newline = optionProvider(bracketsRange);
5218
- const openingBracketIndex = bracketsRange[0];
5219
- const spaceAfterOpeningBracket = getSpaceAfterOpeningBracket(openingBracketIndex);
5220
- verifyNewlineAfterOpeningBracket(node, spaceAfterOpeningBracket, openingBracketIndex, newline);
5221
- const closingBracketIndex = bracketsRange[1] - 1;
5222
- const newlineOptionBeforeClosingBracket = newline === "consistent" ? getSpaceAfterOpeningBracket(bracketsRange[0]).includes("\n") ? "always" : "never" : newline;
5223
- verifyNewlineBeforeClosingBracket(node, getSpaceBeforeClosingBracket(closingBracketIndex), openingBracketIndex, closingBracketIndex, newlineOptionBeforeClosingBracket);
5131
+ class RootStack extends AbsBlockStack {
5132
+ type = "root";
5133
+ node;
5134
+ upper = null;
5135
+ constructor(node) {
5136
+ super();
5137
+ this.node = node;
5138
+ }
5139
+ getExpectedIndent() {
5140
+ return 0;
5141
+ }
5224
5142
  }
5225
- /**
5226
- * Verify the newline after the opening bracket and before the closing bracket.
5227
- */
5228
- function verifyNewlineAfterOpeningBracket(node, spaceAfterOpeningBracket, openingBracketIndex, newline) {
5229
- if (newline === "always") {
5230
- if (spaceAfterOpeningBracket.includes("\n")) return;
5231
- const loc = getSourceLocationFromRange(sourceCode, node, [openingBracketIndex, openingBracketIndex + 1]);
5232
- context.report({
5233
- node,
5234
- loc,
5235
- messageId: "expectedNewlineAfterOpeningBracket",
5236
- fix: (fixer) => fixer.insertTextAfterRange([openingBracketIndex, openingBracketIndex + 1], `\n${" ".repeat(loc.start.column)}${spaceAfterOpeningBracket}`)
5237
- });
5238
- } else if (newline === "never") {
5239
- if (!spaceAfterOpeningBracket.includes("\n")) return;
5240
- context.report({
5241
- node,
5242
- loc: getSourceLocationFromRange(sourceCode, node, [openingBracketIndex + 1, openingBracketIndex + 1 + spaceAfterOpeningBracket.length]),
5243
- messageId: "unexpectedNewlineAfterOpeningBracket",
5244
- fix: (fixer) => fixer.replaceTextRange([openingBracketIndex + 1, openingBracketIndex + 1 + spaceAfterOpeningBracket.length], " ")
5245
- });
5143
+ class BlockquoteStack extends AbsBlockStack {
5144
+ type = "blockquote";
5145
+ node;
5146
+ upper;
5147
+ _expectedIndent;
5148
+ _markerIndent;
5149
+ constructor(node) {
5150
+ super();
5151
+ this.node = node;
5152
+ this.upper = blockStack;
5153
+ }
5154
+ getExpectedIndent() {
5155
+ if (this._expectedIndent == null) this._expectedIndent = this.getMarkerIndent() + 1 + 1;
5156
+ return this._expectedIndent;
5157
+ }
5158
+ getMarkerIndent() {
5159
+ if (this._markerIndent == null) {
5160
+ const loc = sourceCode.getLoc(this.node);
5161
+ this._markerIndent = getWidth(sourceCode.lines[loc.start.line - 1].slice(0, loc.start.column - 1));
5162
+ }
5163
+ return this._markerIndent;
5246
5164
  }
5247
5165
  }
5248
- /**
5249
- * Verify the newline before the closing bracket.
5250
- */
5251
- function verifyNewlineBeforeClosingBracket(node, spaceBeforeClosingBracket, openingBracketIndex, closingBracketIndex, newline) {
5252
- if (openingBracketIndex + 1 === closingBracketIndex - spaceBeforeClosingBracket.length) return;
5253
- if (newline === "always") {
5254
- if (spaceBeforeClosingBracket.includes("\n")) return;
5255
- context.report({
5256
- node,
5257
- loc: getSourceLocationFromRange(sourceCode, node, [closingBracketIndex, closingBracketIndex + 1]),
5258
- messageId: "expectedNewlineBeforeClosingBracket",
5259
- fix: (fixer) => {
5260
- const openingBracketLoc = getSourceLocationFromRange(sourceCode, node, [openingBracketIndex, openingBracketIndex + 1]);
5261
- return fixer.insertTextBeforeRange([closingBracketIndex, closingBracketIndex + 1], `\n${" ".repeat(openingBracketLoc.start.column)}`);
5166
+ class ListItemStack extends AbsBlockStack {
5167
+ type = "listItem";
5168
+ node;
5169
+ upper;
5170
+ _expectedIndentForFirstLine;
5171
+ _expectedIndentForOtherLines;
5172
+ _parsed;
5173
+ nodeLoc;
5174
+ constructor(node) {
5175
+ super();
5176
+ this.node = node;
5177
+ this.upper = blockStack;
5178
+ this.nodeLoc = sourceCode.getLoc(this.node);
5179
+ }
5180
+ getParsedListItem() {
5181
+ return this._parsed ??= parseListItem(sourceCode, this.node);
5182
+ }
5183
+ getExpectedIndent({ lineNumber, block }) {
5184
+ const loc = this.nodeLoc;
5185
+ if (lineNumber === loc.start.line) return this.getExpectedIndentForFirstLine();
5186
+ const expected = this.getExpectedIndentAfterFirstLine();
5187
+ if (expected === "ignore") return "ignore";
5188
+ if (!block) return expected;
5189
+ const actualFirstLineIndent = this.getActualFirstLineIndent({ withoutTaskListMarker: true });
5190
+ if (actualFirstLineIndent != null) return Math.min(expected, actualFirstLineIndent + 3);
5191
+ return expected;
5192
+ }
5193
+ getExpectedIndentForFirstLine() {
5194
+ if (options.listItems.first === "ignore") return "ignore";
5195
+ if (this._expectedIndentForFirstLine != null) return this._expectedIndentForFirstLine;
5196
+ const loc = this.nodeLoc;
5197
+ const parsed = this.getParsedListItem();
5198
+ const lineText = sourceCode.lines[loc.start.line - 1];
5199
+ if (options.listItems.relativeTo === "markerStart") {
5200
+ const baseIndent = getWidth(lineText.slice(0, loc.start.column - 1));
5201
+ return this._expectedIndentForFirstLine = Math.max(baseIndent + options.listItems.first, baseIndent + parsed.marker.text.length + 1);
5202
+ }
5203
+ if (options.listItems.relativeTo === "taskListMarkerEnd" && parsed.taskListItemMarker) return this._expectedIndentForFirstLine = getWidth(lineText.slice(0, parsed.taskListItemMarker.loc.end.column - 1)) + options.listItems.first;
5204
+ return this._expectedIndentForFirstLine = getWidth(lineText.slice(0, parsed.marker.loc.end.column - 1)) + options.listItems.first;
5205
+ }
5206
+ getExpectedIndentAfterFirstLine() {
5207
+ if (this._expectedIndentForOtherLines != null) return this._expectedIndentForOtherLines;
5208
+ const loc = this.nodeLoc;
5209
+ if (options.listItems.other === "first") {
5210
+ const firstLineIndent = this.getExpectedIndentForFirstLine();
5211
+ if (firstLineIndent === "ignore") {
5212
+ const actualFirstLineIndent = this.getActualFirstLineIndent();
5213
+ if (actualFirstLineIndent != null) return this._expectedIndentForOtherLines = actualFirstLineIndent;
5214
+ for (const child of this.node.children) {
5215
+ const childLoc = sourceCode.getLoc(child);
5216
+ if (loc.start.line < childLoc.start.line) return this._expectedIndentForOtherLines = getWidth(sourceCode.lines[childLoc.start.line - 1].slice(0, childLoc.start.column - 1));
5217
+ }
5262
5218
  }
5219
+ return this._expectedIndentForOtherLines = firstLineIndent;
5220
+ }
5221
+ if (options.listItems.other === "minimum") return this._expectedIndentForOtherLines = this.getMinimumLineIndent();
5222
+ const lineText = sourceCode.lines[loc.start.line - 1];
5223
+ if (options.listItems.relativeTo === "markerStart") {
5224
+ const baseIndent = getWidth(lineText.slice(0, loc.start.column - 1));
5225
+ const minimumLineIndent = this.getMinimumLineIndent();
5226
+ return this._expectedIndentForOtherLines = Math.max(baseIndent + options.listItems.other, minimumLineIndent);
5227
+ }
5228
+ const parsed = this.getParsedListItem();
5229
+ if (options.listItems.relativeTo === "taskListMarkerEnd" && parsed.taskListItemMarker) return this._expectedIndentForOtherLines = getWidth(lineText.slice(0, parsed.taskListItemMarker.loc.end.column - 1)) + options.listItems.other;
5230
+ return this._expectedIndentForOtherLines = getWidth(lineText.slice(0, parsed.marker.loc.end.column - 1)) + options.listItems.other;
5231
+ }
5232
+ getMinimumLineIndent() {
5233
+ const actualFirstLineIndent = this.getActualFirstLineIndent({ withoutTaskListMarker: true });
5234
+ if (actualFirstLineIndent != null) return this._expectedIndentForOtherLines = actualFirstLineIndent;
5235
+ const parsed = this.getParsedListItem();
5236
+ const lineText = sourceCode.lines[parsed.marker.loc.end.line - 1];
5237
+ return getWidth(lineText.slice(0, parsed.marker.loc.end.column - 1)) + 1;
5238
+ }
5239
+ getActualFirstLineIndent({ withoutTaskListMarker = false } = {}) {
5240
+ const parsed = this.getParsedListItem();
5241
+ const markerEndPos = withoutTaskListMarker ? parsed.marker.loc.end : parsed.taskListItemMarker?.loc.end ?? parsed.marker.loc.end;
5242
+ const lineText = sourceCode.lines[markerEndPos.line - 1];
5243
+ const afterMarkerText = lineText.slice(markerEndPos.column - 1);
5244
+ const trimmedAfterMarkerText = afterMarkerText.trimStart();
5245
+ if (trimmedAfterMarkerText) {
5246
+ const afterMarkerSpacesLength = afterMarkerText.length - trimmedAfterMarkerText.length;
5247
+ return getWidth(lineText.slice(0, markerEndPos.column - 1 + afterMarkerSpacesLength));
5248
+ }
5249
+ return null;
5250
+ }
5251
+ }
5252
+ class FootnoteDefinitionStack extends AbsBlockStack {
5253
+ type = "footnoteDefinition";
5254
+ node;
5255
+ upper;
5256
+ _expectedIndent;
5257
+ constructor(node) {
5258
+ super();
5259
+ this.node = node;
5260
+ this.upper = blockStack;
5261
+ }
5262
+ getExpectedIndent() {
5263
+ if (this._expectedIndent == null) {
5264
+ const loc = sourceCode.getLoc(this.node);
5265
+ this._expectedIndent = getWidth(sourceCode.lines[loc.start.line - 1].slice(0, loc.start.column - 1)) + 4;
5266
+ }
5267
+ return this._expectedIndent;
5268
+ }
5269
+ }
5270
+ class LinkStack extends AbsBlockStack {
5271
+ type = "link";
5272
+ node;
5273
+ upper;
5274
+ nodeLoc;
5275
+ constructor(node) {
5276
+ super();
5277
+ this.node = node;
5278
+ this.upper = blockStack;
5279
+ this.nodeLoc = sourceCode.getLoc(this.node);
5280
+ }
5281
+ getExpectedIndent(ctx) {
5282
+ const loc = this.nodeLoc;
5283
+ if (ctx.lineNumber === loc.start.line) return this.upper.getExpectedIndent(ctx);
5284
+ const base = this.upper.getExpectedIndent({
5285
+ lineNumber: loc.start.line,
5286
+ block: ctx.block
5263
5287
  });
5264
- } else if (newline === "never") {
5265
- if (!spaceBeforeClosingBracket.includes("\n")) return;
5266
- context.report({
5267
- node,
5268
- loc: getSourceLocationFromRange(sourceCode, node, [closingBracketIndex - spaceBeforeClosingBracket.length, closingBracketIndex]),
5269
- messageId: "unexpectedNewlineBeforeClosingBracket",
5270
- fix: (fixer) => fixer.replaceTextRange([closingBracketIndex - spaceBeforeClosingBracket.length, closingBracketIndex], " ")
5271
- });
5288
+ if (base === "ignore") return "ignore";
5289
+ return base + 2;
5272
5290
  }
5273
5291
  }
5292
+ let blockStack = new RootStack(sourceCode.ast);
5293
+ const reportedLocations = Object.create(null);
5294
+ /**
5295
+ * Reported locations (line and column) to avoid duplicate reports.
5296
+ */
5297
+ function reportIncorrectIndent(violation) {
5298
+ const reportedColumns = reportedLocations[violation.loc.start.line] ??= /* @__PURE__ */ new Set();
5299
+ if (reportedColumns.has(violation.loc.start.column)) return;
5300
+ reportedColumns.add(violation.loc.start.column);
5301
+ blockStack.violations.push(violation);
5302
+ }
5303
+ /**
5304
+ * Flush violations to the context.
5305
+ */
5306
+ function flushViolations({ violations }) {
5307
+ for (const violation of violations) context.report({
5308
+ loc: violation.loc,
5309
+ messageId: violation.messageId,
5310
+ data: {
5311
+ expected: String(violation.data.expected),
5312
+ actual: String(violation.data.actual)
5313
+ },
5314
+ fix(fixer) {
5315
+ const result = [];
5316
+ for (const { fix } of violations) result.push(...fix(fixer));
5317
+ return result;
5318
+ }
5319
+ });
5320
+ }
5274
5321
  return {
5322
+ thematicBreak: verifyThematicBreak,
5323
+ heading: verifyHeading,
5324
+ code: verifyCodeBlock,
5325
+ html: verifyHtml,
5326
+ definition: verifyLinkDefinition,
5327
+ table: verifyTable,
5328
+ list: verifyList,
5329
+ inlineCode: verifyInlineCode,
5330
+ emphasis: verifyEmphasisOrStrongOrDelete,
5331
+ strong: verifyEmphasisOrStrongOrDelete,
5332
+ delete: verifyEmphasisOrStrongOrDelete,
5333
+ image: verifyImage,
5334
+ imageReference: verifyImageReference,
5335
+ footnoteReference: verifyInline,
5336
+ break: verifyInline,
5337
+ text: verifyText,
5338
+ blockquote(node) {
5339
+ verifyBlockquote(node);
5340
+ blockStack = new BlockquoteStack(node);
5341
+ },
5342
+ listItem(node) {
5343
+ blockStack = new ListItemStack(node);
5344
+ },
5345
+ footnoteDefinition(node) {
5346
+ verifyFootnoteDefinition(node);
5347
+ blockStack = new FootnoteDefinitionStack(node);
5348
+ },
5275
5349
  link(node) {
5276
- if (getLinkKind(sourceCode, node) !== "inline") return;
5277
- const parsed = parseInlineLink(sourceCode, node);
5278
- if (!parsed) return;
5279
- verifyNewlineAroundBrackets(node, parsed.text.range);
5350
+ verifyLink(node);
5351
+ blockStack = new LinkStack(node);
5280
5352
  },
5281
5353
  linkReference(node) {
5282
- const parsed = parseLinkReference(sourceCode, node);
5283
- if (!parsed) return;
5284
- verifyNewlineAroundBrackets(node, parsed.text.range);
5285
- if (parsed.label?.type === "full") verifyNewlineAroundBrackets(node, parsed.label.range);
5286
- },
5287
- image(node) {
5288
- const parsed = parseImage(sourceCode, node);
5289
- if (!parsed) return;
5290
- verifyNewlineAroundBrackets(node, parsed.text.range);
5354
+ verifyLinkReference(node);
5355
+ blockStack = new LinkStack(node);
5291
5356
  },
5292
- imageReference(node) {
5293
- const parsed = parseImageReference(sourceCode, node);
5294
- if (!parsed) return;
5295
- verifyNewlineAroundBrackets(node, parsed.text.range);
5296
- if (parsed.label?.type === "full") verifyNewlineAroundBrackets(node, parsed.label.range);
5357
+ "blockquote, listItem, footnoteDefinition, link, linkReference:exit"() {
5358
+ flushViolations(blockStack);
5359
+ blockStack = blockStack.upper;
5297
5360
  },
5298
- definition(node) {
5299
- const parsed = parseLinkDefinition(sourceCode, node);
5300
- if (!parsed) return;
5301
- verifyNewlineAroundBrackets(node, parsed.label.range);
5361
+ "root:exit"() {
5362
+ flushViolations(blockStack);
5302
5363
  }
5303
5364
  };
5304
5365
  /**
5305
- * Get the spaces after the opening bracket.
5366
+ * Verify a thematic break node.
5306
5367
  */
5307
- function getSpaceAfterOpeningBracket(openingBracketIndex) {
5308
- for (let i = openingBracketIndex + 1; i < sourceCode.text.length; i++) {
5309
- const char = sourceCode.text[i];
5310
- if (isWhitespace(char)) continue;
5311
- return sourceCode.text.slice(openingBracketIndex + 1, i);
5312
- }
5313
- return sourceCode.text.slice(openingBracketIndex + 1);
5368
+ function verifyThematicBreak(node) {
5369
+ const loc = sourceCode.getLoc(node);
5370
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5371
+ lineNumber,
5372
+ block: true
5373
+ }));
5314
5374
  }
5315
5375
  /**
5316
- * Get the spaces before the closing bracket.
5376
+ * Verify a heading node.
5317
5377
  */
5318
- function getSpaceBeforeClosingBracket(closingBracketIndex) {
5319
- for (let i = closingBracketIndex - 1; i >= 0; i--) {
5320
- const char = sourceCode.text[i];
5321
- if (isWhitespace(char)) continue;
5322
- return sourceCode.text.slice(i + 1, closingBracketIndex);
5323
- }
5324
- return sourceCode.text.slice(0, closingBracketIndex);
5325
- }
5326
- }
5327
- });
5328
-
5329
- //#endregion
5330
- //#region src/rules/link-bracket-spacing.ts
5331
- /**
5332
- * The basic option for links and images.
5333
- */
5334
- function parseOptions$1(option) {
5335
- const space = option?.space ?? "never";
5336
- const imagesInLinks = option?.imagesInLinks;
5337
- return {
5338
- space,
5339
- spaceForText: getOptionForText
5340
- };
5341
- /**
5342
- * Get the spacing option for the given node.
5343
- */
5344
- function getOptionForText(node) {
5345
- if (imagesInLinks != null) {
5346
- const children = [...node.children];
5347
- let child;
5348
- while (children.length && (child = children[0]) && child.type === "text" && isWhitespace(child.value)) children.shift();
5349
- while (children.length && (child = children[children.length - 1]) && child.type === "text" && isWhitespace(child.value)) children.pop();
5350
- const loneChild = children.length === 1 ? children[0] : null;
5351
- if (loneChild?.type === "image" || loneChild?.type === "imageReference") return imagesInLinks ? "always" : "never";
5352
- }
5353
- if (node.children.length === 0 || node.children.length === 1 && node.children[0]?.type === "text" && isWhitespace(node.children[0].value)) return "ignore";
5354
- return space;
5355
- }
5356
- }
5357
- var link_bracket_spacing_default = createRule("link-bracket-spacing", {
5358
- meta: {
5359
- type: "layout",
5360
- docs: {
5361
- description: "enforce consistent spacing inside link brackets",
5362
- categories: ["standard"],
5363
- listCategory: "Stylistic"
5364
- },
5365
- fixable: "whitespace",
5366
- hasSuggestions: false,
5367
- schema: [{
5368
- type: "object",
5369
- properties: {
5370
- space: { enum: ["always", "never"] },
5371
- imagesInLinks: { type: "boolean" }
5372
- },
5373
- additionalProperties: false
5374
- }],
5375
- messages: {
5376
- unexpectedSpaceAfterOpeningBracket: "Unexpected space after opening bracket.",
5377
- expectedSpaceAfterOpeningBracket: "Expected space after opening bracket.",
5378
- unexpectedSpaceBeforeClosingBracket: "Unexpected space before closing bracket.",
5379
- expectedSpaceBeforeClosingBracket: "Expected space before closing bracket."
5378
+ function verifyHeading(node) {
5379
+ const loc = sourceCode.getLoc(node);
5380
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5381
+ lineNumber,
5382
+ block: true
5383
+ }));
5380
5384
  }
5381
- },
5382
- create(context) {
5383
- const sourceCode = context.sourceCode;
5384
- const options = parseOptions$1(context.options[0]);
5385
5385
  /**
5386
- * Verify the space after the opening bracket and before the closing bracket.
5386
+ * Verify a code block node.
5387
5387
  */
5388
- function verifySpaceAfterOpeningBracket(node, openingBracketIndex, spaceOption) {
5389
- const space = getSpaceAfterOpeningBracket(openingBracketIndex);
5390
- if (space.includes("\n")) return;
5391
- if (spaceOption === "always") {
5392
- if (space.length > 0) return;
5393
- context.report({
5394
- node,
5388
+ function verifyCodeBlock(node) {
5389
+ if (getCodeBlockKind(sourceCode, node) === "indented") return;
5390
+ const loc = sourceCode.getLoc(node);
5391
+ verifyLinesIndent([loc.start.line, loc.end.line], (lineNumber) => blockStack.getExpectedIndent({
5392
+ lineNumber,
5393
+ block: true
5394
+ }), additionalFixes);
5395
+ /**
5396
+ * Additional fixes for code block content lines.
5397
+ */
5398
+ function* additionalFixes(fixer, info) {
5399
+ if (info.loc.start.line !== loc.start.line) return;
5400
+ for (let lineNumber = loc.start.line + 1; lineNumber < loc.end.line; lineNumber++) {
5401
+ const line = getParsedLines(sourceCode).get(lineNumber);
5402
+ if (!line) continue;
5403
+ if (info.expectedIndentWidth > info.actualIndentWidth) {
5404
+ const before = sliceWidth(line.text, 0, info.actualIndentWidth);
5405
+ const after = sliceWidth(line.text, info.actualIndentWidth);
5406
+ const diffWidth = info.expectedIndentWidth - info.actualIndentWidth;
5407
+ yield fixer.replaceTextRange([line.range[0], line.range[0] + line.text.length], before + " ".repeat(diffWidth) + after);
5408
+ } else {
5409
+ let before = sliceWidth(line.text, 0, info.expectedIndentWidth);
5410
+ let between = sliceWidth(line.text, info.expectedIndentWidth, info.actualIndentWidth);
5411
+ const after = sliceWidth(line.text, info.actualIndentWidth);
5412
+ while (between && !isSpaceOrTab(between)) {
5413
+ before += between[0];
5414
+ between = between.slice(1);
5415
+ }
5416
+ yield fixer.replaceTextRange([line.range[0], line.range[0] + line.text.length], before + after);
5417
+ }
5418
+ }
5419
+ }
5420
+ }
5421
+ /**
5422
+ * Verify an HTML node.
5423
+ */
5424
+ function verifyHtml(node) {
5425
+ const loc = sourceCode.getLoc(node);
5426
+ if (!inlineToBeChecked(loc.start)) return;
5427
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5428
+ lineNumber,
5429
+ block: true
5430
+ }));
5431
+ }
5432
+ /**
5433
+ * Verify a link definition node.
5434
+ */
5435
+ function verifyLinkDefinition(node) {
5436
+ const parsed = parseLinkDefinition(sourceCode, node);
5437
+ if (!parsed) return;
5438
+ const loc = sourceCode.getLoc(node);
5439
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber, column) => {
5440
+ const baseIndent = blockStack.getExpectedIndent({
5441
+ lineNumber,
5442
+ block: true
5443
+ });
5444
+ if (baseIndent === "ignore") return "ignore";
5445
+ if (lineNumber <= loc.start.line) return baseIndent;
5446
+ if (lineNumber < parsed.label.loc.end.line) return baseIndent + 2;
5447
+ if (lineNumber === parsed.label.loc.end.line) {
5448
+ const line = getParsedLines(sourceCode).get(lineNumber);
5449
+ if (!line) return baseIndent;
5450
+ const between = line.text.slice(column - 1, parsed.label.loc.end.column - 2);
5451
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5452
+ }
5453
+ if (lineNumber <= parsed.destination.loc.end.line) return baseIndent + 4;
5454
+ if (!parsed.title) return baseIndent;
5455
+ if (lineNumber <= parsed.title.loc.start.line) return baseIndent + 4;
5456
+ if (lineNumber < parsed.title.loc.end.line) return baseIndent + 6;
5457
+ if (lineNumber === parsed.title.loc.end.line) {
5458
+ const line = getParsedLines(sourceCode).get(lineNumber);
5459
+ if (!line) return baseIndent;
5460
+ const between = line.text.slice(column - 1, parsed.title.loc.end.column - 2);
5461
+ return !between || isWhitespace(between) ? baseIndent + 4 : baseIndent + 6;
5462
+ }
5463
+ return baseIndent;
5464
+ });
5465
+ }
5466
+ /**
5467
+ * Verify a blockquote node.
5468
+ */
5469
+ function verifyBlockquote(node) {
5470
+ const loc = sourceCode.getLoc(node);
5471
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5472
+ lineNumber,
5473
+ block: true
5474
+ }));
5475
+ }
5476
+ /**
5477
+ * Verify a table node.
5478
+ */
5479
+ function verifyTable(node) {
5480
+ const loc = sourceCode.getLoc(node);
5481
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5482
+ lineNumber,
5483
+ block: true
5484
+ }));
5485
+ }
5486
+ /**
5487
+ * Verify a list node.
5488
+ */
5489
+ function verifyList(node) {
5490
+ const loc = sourceCode.getLoc(node);
5491
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5492
+ lineNumber,
5493
+ block: true
5494
+ }), additionalFixes);
5495
+ /**
5496
+ * Additional fixes for list item lines.
5497
+ */
5498
+ function* additionalFixes(fixer, info) {
5499
+ const [firstItem, ...otherItems] = node.children;
5500
+ yield* fixAfterFirstLine(firstItem, info.actualIndentWidth);
5501
+ for (const listItem of otherItems) {
5502
+ const listItemLoc = sourceCode.getLoc(listItem);
5503
+ const listItemIndentWidth = getWidth(sourceCode.lines[listItemLoc.start.line - 1].slice(0, listItemLoc.start.column - 1));
5504
+ if (listItemIndentWidth === info.expectedIndentWidth) continue;
5505
+ yield getFixForLine(listItemLoc.start.line, listItemIndentWidth);
5506
+ yield* fixAfterFirstLine(listItem, listItemIndentWidth);
5507
+ }
5508
+ /**
5509
+ * Get fixes for lines after the first line of the list item.
5510
+ */
5511
+ function* fixAfterFirstLine(item, actualIndentWidth) {
5512
+ const itemLoc = sourceCode.getLoc(item);
5513
+ for (let lineNumber = itemLoc.start.line + 1; lineNumber <= itemLoc.end.line; lineNumber++) yield getFixForLine(lineNumber, actualIndentWidth);
5514
+ }
5515
+ /**
5516
+ * Get a fix for a line.
5517
+ */
5518
+ function getFixForLine(lineNumber, actualIndentWidth) {
5519
+ const line = getParsedLines(sourceCode).get(lineNumber);
5520
+ if (info.expectedIndentWidth > actualIndentWidth) {
5521
+ const before$1 = sliceWidth(line.text, 0, actualIndentWidth);
5522
+ const diffWidth = info.expectedIndentWidth - actualIndentWidth;
5523
+ return fixer.insertTextAfterRange([line.range[0], line.range[0] + before$1.length], " ".repeat(diffWidth));
5524
+ }
5525
+ let before = sliceWidth(line.text, 0, info.expectedIndentWidth);
5526
+ let between = sliceWidth(line.text, info.expectedIndentWidth, actualIndentWidth);
5527
+ while (between && !isSpaceOrTab(between)) {
5528
+ before += between[0];
5529
+ between = between.slice(1);
5530
+ }
5531
+ return fixer.replaceTextRange([line.range[0], line.range[0] + before.length + between.length], before);
5532
+ }
5533
+ }
5534
+ }
5535
+ /**
5536
+ * Verify a footnote definition node.
5537
+ */
5538
+ function verifyFootnoteDefinition(node) {
5539
+ const loc = sourceCode.getLoc(node);
5540
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5541
+ lineNumber,
5542
+ block: true
5543
+ }));
5544
+ }
5545
+ /**
5546
+ * Verify an inline code node.
5547
+ */
5548
+ function verifyInlineCode(node) {
5549
+ const loc = sourceCode.getLoc(node);
5550
+ if (!inlineToBeChecked(loc.start)) return;
5551
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5552
+ lineNumber,
5553
+ block: false
5554
+ }));
5555
+ }
5556
+ /**
5557
+ * Verify an emphasis, strong, or delete node.
5558
+ */
5559
+ function verifyEmphasisOrStrongOrDelete(node) {
5560
+ const loc = sourceCode.getLoc(node);
5561
+ if (!inlineToBeChecked(loc.start)) return;
5562
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5563
+ lineNumber,
5564
+ block: false
5565
+ }));
5566
+ }
5567
+ /**
5568
+ * Verify a link node.
5569
+ */
5570
+ function verifyLink(node) {
5571
+ const loc = sourceCode.getLoc(node);
5572
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5573
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5574
+ const kind = getLinkKind(sourceCode, node);
5575
+ if (kind === "autolink" || kind === "gfm-autolink") verifyLinesIndent(lines, (lineNumber) => blockStack.getExpectedIndent({
5576
+ lineNumber,
5577
+ block: false
5578
+ }));
5579
+ else if (kind === "inline") {
5580
+ const parsed = parseInlineLink(sourceCode, node);
5581
+ if (!parsed) return;
5582
+ const lastChild = node.children.at(-1);
5583
+ if (lastChild && parsed.text.loc.start.line < parsed.text.loc.end.line) {
5584
+ const lastChildLoc = sourceCode.getLoc(lastChild);
5585
+ const lastChildEndLine = lastChild.type === "text" && lastChild.value.endsWith("\n") ? lastChildLoc.end.line - 1 : lastChildLoc.end.line;
5586
+ lines = lines.filter((lineNumber) => lineNumber <= parsed.text.loc.start.line || lastChildEndLine < lineNumber);
5587
+ }
5588
+ verifyLinesIndent(lines, (lineNumber, column) => {
5589
+ const baseIndent = blockStack.getExpectedIndent({
5590
+ lineNumber,
5591
+ block: false
5592
+ });
5593
+ if (baseIndent === "ignore") return "ignore";
5594
+ if (lineNumber <= loc.start.line) return baseIndent;
5595
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5596
+ if (lineNumber <= parsed.openingParen.loc.end.line) return baseIndent;
5597
+ if (lineNumber <= parsed.destination.loc.end.line) return baseIndent + 2;
5598
+ if (!parsed.title) return baseIndent;
5599
+ if (lineNumber <= parsed.title.loc.start.line) return baseIndent + 2;
5600
+ if (lineNumber < parsed.title.loc.end.line) return baseIndent + 4;
5601
+ if (lineNumber === parsed.title.loc.end.line) {
5602
+ const line = getParsedLines(sourceCode).get(lineNumber);
5603
+ if (!line) return baseIndent;
5604
+ const between = line.text.slice(column - 1, parsed.title.loc.end.column - 2);
5605
+ return !between || isWhitespace(between) ? baseIndent + 2 : baseIndent + 4;
5606
+ }
5607
+ return baseIndent;
5608
+ });
5609
+ }
5610
+ }
5611
+ /**
5612
+ * Verify a link reference node.
5613
+ */
5614
+ function verifyLinkReference(node) {
5615
+ const loc = sourceCode.getLoc(node);
5616
+ const parsed = parseLinkReference(sourceCode, node);
5617
+ if (!parsed) return;
5618
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5619
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5620
+ const lastChild = node.children.at(-1);
5621
+ if (lastChild && parsed.text.loc.start.line < parsed.text.loc.end.line) {
5622
+ const lastChildLoc = sourceCode.getLoc(lastChild);
5623
+ const lastChildEndLine = lastChild.type === "text" && lastChild.value.endsWith("\n") ? lastChildLoc.end.line - 1 : lastChildLoc.end.line;
5624
+ lines = lines.filter((lineNumber) => lineNumber <= parsed.text.loc.start.line || lastChildEndLine < lineNumber);
5625
+ }
5626
+ verifyLinesIndent(lines, (lineNumber, column) => {
5627
+ const baseIndent = blockStack.getExpectedIndent({
5628
+ lineNumber,
5629
+ block: false
5630
+ });
5631
+ if (baseIndent === "ignore") return "ignore";
5632
+ if (lineNumber <= loc.start.line) return baseIndent;
5633
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5634
+ if (!parsed.label) return baseIndent;
5635
+ if (lineNumber <= parsed.label.loc.start.line) return baseIndent;
5636
+ if (lineNumber < parsed.label.loc.end.line) return baseIndent + 2;
5637
+ if (lineNumber === parsed.label.loc.end.line) {
5638
+ const line = getParsedLines(sourceCode).get(lineNumber);
5639
+ if (!line) return baseIndent;
5640
+ const between = line.text.slice(column - 1, parsed.label.loc.end.column - 2);
5641
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5642
+ }
5643
+ return baseIndent;
5644
+ });
5645
+ }
5646
+ /**
5647
+ * Verify an image node.
5648
+ */
5649
+ function verifyImage(node) {
5650
+ const loc = sourceCode.getLoc(node);
5651
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5652
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5653
+ const parsed = parseImage(sourceCode, node);
5654
+ if (!parsed) return;
5655
+ verifyLinesIndent(lines, (lineNumber, column) => {
5656
+ const baseIndent = blockStack.getExpectedIndent({
5657
+ lineNumber,
5658
+ block: false
5659
+ });
5660
+ if (baseIndent === "ignore") return "ignore";
5661
+ if (lineNumber <= loc.start.line) return baseIndent;
5662
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5663
+ if (lineNumber === parsed.text.loc.end.line) {
5664
+ const line = getParsedLines(sourceCode).get(lineNumber);
5665
+ if (!line) return baseIndent;
5666
+ const between = line.text.slice(column - 1, parsed.text.loc.end.column - 2);
5667
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5668
+ }
5669
+ if (lineNumber <= parsed.openingParen.loc.end.line) return baseIndent;
5670
+ if (lineNumber <= parsed.destination.loc.end.line) return baseIndent + 2;
5671
+ if (!parsed.title) return baseIndent;
5672
+ if (lineNumber <= parsed.title.loc.start.line) return baseIndent + 2;
5673
+ if (lineNumber < parsed.title.loc.end.line) return baseIndent + 4;
5674
+ if (lineNumber === parsed.title.loc.end.line) {
5675
+ const line = getParsedLines(sourceCode).get(lineNumber);
5676
+ if (!line) return baseIndent;
5677
+ const between = line.text.slice(column - 1, parsed.title.loc.end.column - 2);
5678
+ return !between || isWhitespace(between) ? baseIndent + 2 : baseIndent + 4;
5679
+ }
5680
+ return baseIndent;
5681
+ });
5682
+ }
5683
+ /**
5684
+ * Verify an image reference node.
5685
+ */
5686
+ function verifyImageReference(node) {
5687
+ const loc = sourceCode.getLoc(node);
5688
+ const parsed = parseImageReference(sourceCode, node);
5689
+ if (!parsed) return;
5690
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5691
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5692
+ verifyLinesIndent(lines, (lineNumber, column) => {
5693
+ const baseIndent = blockStack.getExpectedIndent({
5694
+ lineNumber,
5695
+ block: false
5696
+ });
5697
+ if (baseIndent === "ignore") return "ignore";
5698
+ if (lineNumber <= loc.start.line) return baseIndent;
5699
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5700
+ if (lineNumber === parsed.text.loc.end.line) {
5701
+ const line = getParsedLines(sourceCode).get(lineNumber);
5702
+ if (!line) return baseIndent;
5703
+ const between = line.text.slice(column - 1, parsed.text.loc.end.column - 2);
5704
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5705
+ }
5706
+ if (!parsed.label) return baseIndent;
5707
+ if (lineNumber <= parsed.label.loc.start.line) return baseIndent;
5708
+ if (lineNumber < parsed.label.loc.end.line) return baseIndent + 2;
5709
+ if (lineNumber === parsed.label.loc.end.line) {
5710
+ const line = getParsedLines(sourceCode).get(lineNumber);
5711
+ if (!line) return baseIndent;
5712
+ const between = line.text.slice(column - 1, parsed.label.loc.end.column - 2);
5713
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5714
+ }
5715
+ return baseIndent;
5716
+ });
5717
+ }
5718
+ /**
5719
+ * Verify a text node.
5720
+ */
5721
+ function verifyText(node) {
5722
+ const loc = sourceCode.getLoc(node);
5723
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5724
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5725
+ if (node.value.endsWith("\n")) lines = lines.slice(0, -1);
5726
+ verifyLinesIndent(lines, (lineNumber) => blockStack.getExpectedIndent({
5727
+ lineNumber,
5728
+ block: false
5729
+ }));
5730
+ }
5731
+ /**
5732
+ * Verify an inline node.
5733
+ */
5734
+ function verifyInline(node) {
5735
+ const loc = sourceCode.getLoc(node);
5736
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5737
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5738
+ verifyLinesIndent(lines, (lineNumber) => blockStack.getExpectedIndent({
5739
+ lineNumber,
5740
+ block: false
5741
+ }));
5742
+ }
5743
+ /**
5744
+ * Check whether the inline node should be checked.
5745
+ */
5746
+ function inlineToBeChecked(position) {
5747
+ const blockquote = blockStack.getCurrentBlockquote();
5748
+ const listItem = blockStack.getCurrentListItemAtLine(position.line);
5749
+ const lineText = sourceCode.lines[position.line - 1];
5750
+ if (listItem) {
5751
+ const parsed = listItem.getParsedListItem();
5752
+ const indentText = lineText.slice((parsed.taskListItemMarker?.loc ?? parsed.marker.loc).end.column - 1, position.column - 1);
5753
+ if (indentText && !isSpaceOrTab(indentText)) return false;
5754
+ } else if (blockquote) {
5755
+ if (atWidth(lineText, blockquote.getMarkerIndent()) !== ">") return false;
5756
+ const indentText = sliceWidth(lineText.slice(0, position.column - 1), blockquote.getMarkerIndent() + 1);
5757
+ if (indentText && !isSpaceOrTab(indentText)) return false;
5758
+ } else {
5759
+ const indentText = lineText.slice(0, position.column - 1);
5760
+ if (indentText && !isSpaceOrTab(indentText)) return false;
5761
+ }
5762
+ return true;
5763
+ }
5764
+ /**
5765
+ * Get line numbers from the range.
5766
+ */
5767
+ function lineNumbersFromRange(lineNumberFrom, lineNumberTo) {
5768
+ const lines = [];
5769
+ for (let lineNumber = lineNumberFrom; lineNumber <= lineNumberTo; lineNumber++) lines.push(lineNumber);
5770
+ return lines;
5771
+ }
5772
+ /**
5773
+ * Verify the indentation of the lines.
5774
+ */
5775
+ function verifyLinesIndent(lineNumbers, expectedIndentWidthProvider, additionalFixes) {
5776
+ const blockquote = blockStack.getCurrentBlockquote();
5777
+ if (!blockquote) {
5778
+ verifyLinesIndentFromRoot(lineNumbers, expectedIndentWidthProvider, additionalFixes);
5779
+ return;
5780
+ }
5781
+ verifyLinesIndentFromBlockquoteMarker(lineNumbers, blockquote, expectedIndentWidthProvider, additionalFixes);
5782
+ }
5783
+ /**
5784
+ * Verify the indentation of the lines from the root.
5785
+ */
5786
+ function verifyLinesIndentFromRoot(lineNumbers, expectedIndentWidthProvider, additionalFixes) {
5787
+ const reports = [];
5788
+ for (const lineNumber of lineNumbers) {
5789
+ const line = getParsedLines(sourceCode).get(lineNumber);
5790
+ if (!line) return;
5791
+ const listItem = blockStack.getCurrentListItemAtLine(lineNumber);
5792
+ if (!listItem) {
5793
+ const indentText = /^\s*/u.exec(line.text)[0];
5794
+ const actualIndentWidth = getWidth(indentText);
5795
+ const expectedIndentWidth = expectedIndentWidthProvider(lineNumber, 1);
5796
+ if (expectedIndentWidth === "ignore") continue;
5797
+ if (actualIndentWidth === expectedIndentWidth) continue;
5798
+ reports.push({
5799
+ loc: {
5800
+ start: {
5801
+ line: line.line,
5802
+ column: 1
5803
+ },
5804
+ end: {
5805
+ line: line.line,
5806
+ column: indentText.length + 1
5807
+ }
5808
+ },
5809
+ messageId: indentText.includes(" ") ? "wrongIndentationWithTab" : "wrongIndentation",
5810
+ data: {
5811
+ expected: expectedIndentWidth,
5812
+ actual: actualIndentWidth
5813
+ },
5814
+ fix(fixer) {
5815
+ return fixer.replaceTextRange([line.range[0], line.range[0] + indentText.length], " ".repeat(expectedIndentWidth));
5816
+ },
5817
+ expectedIndentWidth,
5818
+ actualIndentWidth
5819
+ });
5820
+ continue;
5821
+ }
5822
+ const report = verifyLineIndentFromListItemMarker(line, listItem, expectedIndentWidthProvider);
5823
+ if (!report) continue;
5824
+ reports.push(report);
5825
+ }
5826
+ if (!reports.length) return;
5827
+ for (const report of reports) reportIncorrectIndent({
5828
+ loc: report.loc,
5829
+ messageId: report.messageId,
5830
+ data: report.data,
5831
+ *fix(fixer) {
5832
+ yield report.fix(fixer);
5833
+ if (additionalFixes) yield* additionalFixes(fixer, report) ?? [];
5834
+ }
5835
+ });
5836
+ }
5837
+ /**
5838
+ * Verify the indentation of the lines from the blockquote marker.
5839
+ */
5840
+ function verifyLinesIndentFromBlockquoteMarker(lineNumbers, blockquote, expectedIndentWidthProvider, additionalFixes) {
5841
+ const blockquoteLoc = sourceCode.getLoc(blockquote.node);
5842
+ const reports = [];
5843
+ let cannotFix = false;
5844
+ for (const lineNumber of lineNumbers) {
5845
+ const line = getParsedLines(sourceCode).get(lineNumber);
5846
+ if (!line) return;
5847
+ if (atWidth(line.text, blockquote.getMarkerIndent()) !== ">") {
5848
+ cannotFix = true;
5849
+ continue;
5850
+ }
5851
+ const listItem = blockStack.getCurrentListItemAtLine(lineNumber);
5852
+ if (!listItem) {
5853
+ const before = line.text.slice(0, blockquoteLoc.start.column);
5854
+ const after = line.text.slice(blockquoteLoc.start.column);
5855
+ const indentText = /^\s*/u.exec(after)[0];
5856
+ const actualIndentWidth = getWidth(before + indentText);
5857
+ const expectedIndentWidth = expectedIndentWidthProvider(lineNumber, blockquoteLoc.start.column + 1);
5858
+ if (expectedIndentWidth === "ignore") continue;
5859
+ if (actualIndentWidth === expectedIndentWidth) continue;
5860
+ const linePrefixWidth = getWidth(before);
5861
+ reports.push({
5862
+ loc: {
5863
+ start: {
5864
+ line: line.line,
5865
+ column: blockquoteLoc.start.column + 1
5866
+ },
5867
+ end: {
5868
+ line: line.line,
5869
+ column: blockquoteLoc.start.column + 1 + indentText.length
5870
+ }
5871
+ },
5872
+ messageId: indentText.includes(" ") ? "wrongIndentationFromBlockquoteMarkerWithTab" : "wrongIndentationFromBlockquoteMarker",
5873
+ data: {
5874
+ expected: expectedIndentWidth - linePrefixWidth,
5875
+ actual: actualIndentWidth - linePrefixWidth
5876
+ },
5877
+ fix(fixer) {
5878
+ return fixer.replaceTextRange([line.range[0] + blockquoteLoc.start.column, line.range[0] + blockquoteLoc.start.column + indentText.length], " ".repeat(expectedIndentWidth - linePrefixWidth));
5879
+ },
5880
+ expectedIndentWidth,
5881
+ actualIndentWidth
5882
+ });
5883
+ continue;
5884
+ }
5885
+ const report = verifyLineIndentFromListItemMarker(line, listItem, expectedIndentWidthProvider);
5886
+ if (!report) continue;
5887
+ reports.push(report);
5888
+ }
5889
+ if (!reports.length) return;
5890
+ for (const report of reports) reportIncorrectIndent({
5891
+ loc: report.loc,
5892
+ messageId: report.messageId,
5893
+ data: report.data,
5894
+ *fix(fixer) {
5895
+ if (cannotFix) return;
5896
+ yield report.fix(fixer);
5897
+ if (additionalFixes) yield* additionalFixes(fixer, report) ?? [];
5898
+ }
5899
+ });
5900
+ }
5901
+ /**
5902
+ * Verify the indentation of the line from the list item marker.
5903
+ */
5904
+ function verifyLineIndentFromListItemMarker(line, listItem, expectedIndentWidthProvider) {
5905
+ const parsed = listItem.getParsedListItem();
5906
+ const markerAfterColumn = (parsed.taskListItemMarker?.loc ?? parsed.marker.loc).end.column;
5907
+ const before = line.text.slice(0, markerAfterColumn - 1);
5908
+ const after = line.text.slice(markerAfterColumn - 1);
5909
+ const indentText = /^\s*/u.exec(after)[0];
5910
+ const actualIndentWidth = getWidth(before + indentText);
5911
+ const expectedIndentWidth = expectedIndentWidthProvider(line.line, markerAfterColumn);
5912
+ if (expectedIndentWidth === "ignore") return null;
5913
+ if (actualIndentWidth === expectedIndentWidth) return null;
5914
+ const linePrefixWidth = getWidth(before);
5915
+ const range = [line.range[0] + before.length, line.range[0] + before.length + indentText.length];
5916
+ return {
5917
+ loc: getSourceLocationFromRange(sourceCode, listItem.node, range),
5918
+ messageId: indentText.includes(" ") ? "wrongIndentationFromListMarkerWithTab" : "wrongIndentationFromListMarker",
5919
+ data: {
5920
+ expected: expectedIndentWidth - linePrefixWidth,
5921
+ actual: actualIndentWidth - linePrefixWidth
5922
+ },
5923
+ fix(fixer) {
5924
+ return fixer.replaceTextRange(range, " ".repeat(expectedIndentWidth - linePrefixWidth));
5925
+ },
5926
+ expectedIndentWidth,
5927
+ actualIndentWidth
5928
+ };
5929
+ }
5930
+ }
5931
+ });
5932
+
5933
+ //#endregion
5934
+ //#region src/utils/setext-heading.ts
5935
+ /**
5936
+ * Parse the setext heading.
5937
+ */
5938
+ function parseSetextHeading(sourceCode, node) {
5939
+ if (getHeadingKind(sourceCode, node) !== "setext") return null;
5940
+ const lines = getParsedLines(sourceCode);
5941
+ const contentLines = [];
5942
+ const nodeLoc = sourceCode.getLoc(node);
5943
+ for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
5944
+ const content = parseContent(lines.get(lineNumber));
5945
+ contentLines.push(content);
5946
+ }
5947
+ const underline = parseUnderline(lines.get(nodeLoc.end.line));
5948
+ if (!underline) return null;
5949
+ return {
5950
+ contentLines,
5951
+ underline
5952
+ };
5953
+ }
5954
+ /**
5955
+ * Parse the content line of a setext heading.
5956
+ */
5957
+ function parseContent(line) {
5958
+ let prefix = "";
5959
+ let spaceBefore = "";
5960
+ let suffix = "";
5961
+ for (let index = 0; index < line.text.length; index++) {
5962
+ const c = line.text[index];
5963
+ if (!c.trim()) {
5964
+ spaceBefore += c;
5965
+ continue;
5966
+ }
5967
+ if (c === ">" && spaceBefore.length < 4) {
5968
+ prefix += spaceBefore + c;
5969
+ spaceBefore = "";
5970
+ continue;
5971
+ }
5972
+ suffix = line.text.slice(index);
5973
+ break;
5974
+ }
5975
+ const content = suffix.trimEnd();
5976
+ const spaceAfter = suffix.slice(content.length);
5977
+ return {
5978
+ text: content,
5979
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
5980
+ loc: {
5981
+ start: {
5982
+ line: line.line,
5983
+ column: prefix.length + spaceBefore.length + 1
5984
+ },
5985
+ end: {
5986
+ line: line.line,
5987
+ column: prefix.length + spaceBefore.length + content.length + 1
5988
+ }
5989
+ },
5990
+ raws: {
5991
+ prefix,
5992
+ spaceBefore,
5993
+ spaceAfter
5994
+ }
5995
+ };
5996
+ }
5997
+ /**
5998
+ * Parse the underline of a setext heading.
5999
+ */
6000
+ function parseUnderline(line) {
6001
+ let marker = null;
6002
+ let underlineText = "";
6003
+ let prefix = "";
6004
+ let spaceBefore = "";
6005
+ let spaceAfter = "";
6006
+ for (let index = line.text.length - 1; index >= 0; index--) {
6007
+ const c = line.text[index];
6008
+ if (!marker) {
6009
+ if (c === "=" || c === "-") {
6010
+ underlineText = c + underlineText;
6011
+ marker = c;
6012
+ } else if (!c.trim()) spaceAfter = c + spaceAfter;
6013
+ else return null;
6014
+ continue;
6015
+ }
6016
+ if (c === marker) {
6017
+ underlineText = c + spaceBefore + underlineText;
6018
+ spaceBefore = "";
6019
+ } else if (!c.trim()) spaceBefore = c + spaceBefore;
6020
+ else {
6021
+ prefix = line.text.slice(0, index + 1);
6022
+ break;
6023
+ }
6024
+ }
6025
+ if (!marker) return null;
6026
+ const underlineLoc = {
6027
+ start: {
6028
+ line: line.line,
6029
+ column: prefix.length + spaceBefore.length + 1
6030
+ },
6031
+ end: {
6032
+ line: line.line,
6033
+ column: prefix.length + spaceBefore.length + underlineText.length + 1
6034
+ }
6035
+ };
6036
+ return {
6037
+ text: underlineText,
6038
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
6039
+ loc: underlineLoc,
6040
+ marker,
6041
+ raws: {
6042
+ prefix,
6043
+ spaceBefore,
6044
+ spaceAfter
6045
+ }
6046
+ };
6047
+ }
6048
+
6049
+ //#endregion
6050
+ //#region src/rules/level1-heading-style.ts
6051
+ var level1_heading_style_default = createRule("level1-heading-style", {
6052
+ meta: {
6053
+ type: "layout",
6054
+ docs: {
6055
+ description: "enforce consistent style for level 1 headings",
6056
+ categories: ["standard"],
6057
+ listCategory: "Notation"
6058
+ },
6059
+ fixable: "code",
6060
+ hasSuggestions: false,
6061
+ schema: [{
6062
+ type: "object",
6063
+ properties: {
6064
+ style: { enum: ["atx", "setext"] },
6065
+ allowMultilineSetext: { type: "boolean" }
6066
+ },
6067
+ additionalProperties: false
6068
+ }],
6069
+ messages: {
6070
+ expectedAtx: "Expected ATX style heading (# Heading).",
6071
+ expectedSetext: "Expected Setext style heading (Heading\\n======).",
6072
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
6073
+ }
6074
+ },
6075
+ create(context) {
6076
+ const sourceCode = context.sourceCode;
6077
+ const opt = context.options[0] || {};
6078
+ const style = opt.style ?? "atx";
6079
+ const allowMultilineSetext = opt.allowMultilineSetext;
6080
+ return { heading(node) {
6081
+ if (node.depth !== 1) return;
6082
+ const headingKind = getHeadingKind(sourceCode, node);
6083
+ if (style === "atx") {
6084
+ if (headingKind !== "setext") return;
6085
+ const parsed = parseSetextHeading(sourceCode, node);
6086
+ if (!parsed) return;
6087
+ if (parsed.contentLines.length > 1) {
6088
+ if (allowMultilineSetext) return;
6089
+ context.report({
6090
+ node,
6091
+ messageId: "multilineSetextNotAllowed"
6092
+ });
6093
+ return;
6094
+ }
6095
+ context.report({
6096
+ node,
6097
+ messageId: "expectedAtx",
6098
+ *fix(fixer) {
6099
+ const heading = parsed.contentLines[0];
6100
+ yield fixer.insertTextBeforeRange(heading.range, "# ");
6101
+ const lines = getParsedLines(sourceCode);
6102
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
6103
+ }
6104
+ });
6105
+ } else if (style === "setext") {
6106
+ if (headingKind !== "atx" || node.children.length === 0) return;
6107
+ context.report({
6108
+ node,
6109
+ messageId: "expectedSetext",
6110
+ *fix(fixer) {
6111
+ const parsed = parseATXHeading(sourceCode, node);
6112
+ if (!parsed) return;
6113
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.content.range[0]]);
6114
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.content.range[1], parsed.after?.range[1] ?? parsed.closingSequence.range[1]]);
6115
+ const lines = getParsedLines(sourceCode);
6116
+ const underline = "=".repeat(Math.max(getTextWidth(parsed.content.text), 3));
6117
+ const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
6118
+ yield fixer.insertTextAfter(node, appendingText);
6119
+ }
6120
+ });
6121
+ }
6122
+ } };
6123
+ }
6124
+ });
6125
+
6126
+ //#endregion
6127
+ //#region src/rules/level2-heading-style.ts
6128
+ var level2_heading_style_default = createRule("level2-heading-style", {
6129
+ meta: {
6130
+ type: "layout",
6131
+ docs: {
6132
+ description: "enforce consistent style for level 2 headings",
6133
+ categories: ["standard"],
6134
+ listCategory: "Notation"
6135
+ },
6136
+ fixable: "code",
6137
+ hasSuggestions: false,
6138
+ schema: [{
6139
+ type: "object",
6140
+ properties: {
6141
+ style: { enum: ["atx", "setext"] },
6142
+ allowMultilineSetext: { type: "boolean" }
6143
+ },
6144
+ additionalProperties: false
6145
+ }],
6146
+ messages: {
6147
+ expectedAtx: "Expected ATX style heading (## Heading).",
6148
+ expectedSetext: "Expected Setext style heading (Heading\\n------).",
6149
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
6150
+ }
6151
+ },
6152
+ create(context) {
6153
+ const sourceCode = context.sourceCode;
6154
+ const opt = context.options[0] || {};
6155
+ const style = opt.style ?? "atx";
6156
+ const allowMultilineSetext = opt.allowMultilineSetext;
6157
+ return { heading(node) {
6158
+ if (node.depth !== 2) return;
6159
+ const headingKind = getHeadingKind(sourceCode, node);
6160
+ if (style === "atx") {
6161
+ if (headingKind !== "setext") return;
6162
+ const parsed = parseSetextHeading(sourceCode, node);
6163
+ if (!parsed) return;
6164
+ if (parsed.contentLines.length > 1) {
6165
+ if (allowMultilineSetext) return;
6166
+ context.report({
6167
+ node,
6168
+ messageId: "multilineSetextNotAllowed"
6169
+ });
6170
+ return;
6171
+ }
6172
+ context.report({
6173
+ node,
6174
+ messageId: "expectedAtx",
6175
+ *fix(fixer) {
6176
+ const heading = parsed.contentLines[0];
6177
+ yield fixer.insertTextBeforeRange(heading.range, "## ");
6178
+ const lines = getParsedLines(sourceCode);
6179
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
6180
+ }
6181
+ });
6182
+ } else if (style === "setext") {
6183
+ if (headingKind !== "atx" || node.children.length === 0) return;
6184
+ context.report({
6185
+ node,
6186
+ messageId: "expectedSetext",
6187
+ *fix(fixer) {
6188
+ const parsed = parseATXHeading(sourceCode, node);
6189
+ if (!parsed) return;
6190
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.content.range[0]]);
6191
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.content.range[1], parsed.after?.range[1] ?? parsed.closingSequence.range[1]]);
6192
+ const lines = getParsedLines(sourceCode);
6193
+ const underline = "-".repeat(Math.max(getTextWidth(parsed.content.text), 3));
6194
+ const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
6195
+ yield fixer.insertTextAfter(node, appendingText);
6196
+ }
6197
+ });
6198
+ }
6199
+ } };
6200
+ }
6201
+ });
6202
+
6203
+ //#endregion
6204
+ //#region src/rules/link-bracket-newline.ts
6205
+ var link_bracket_newline_default = createRule("link-bracket-newline", {
6206
+ meta: {
6207
+ type: "layout",
6208
+ docs: {
6209
+ description: "enforce linebreaks after opening and before closing link brackets",
6210
+ categories: ["standard"],
6211
+ listCategory: "Whitespace"
6212
+ },
6213
+ fixable: "whitespace",
6214
+ hasSuggestions: false,
6215
+ schema: [{
6216
+ type: "object",
6217
+ properties: {
6218
+ newline: { enum: [
6219
+ "always",
6220
+ "never",
6221
+ "consistent"
6222
+ ] },
6223
+ multiline: { type: "boolean" }
6224
+ },
6225
+ additionalProperties: false
6226
+ }],
6227
+ messages: {
6228
+ expectedNewlineAfterOpeningBracket: "Expected a linebreak after this opening bracket.",
6229
+ unexpectedNewlineAfterOpeningBracket: "Unexpected linebreak after this opening bracket.",
6230
+ expectedNewlineBeforeClosingBracket: "Expected a linebreak before this closing bracket.",
6231
+ unexpectedNewlineBeforeClosingBracket: "Unexpected linebreak before this closing bracket."
6232
+ }
6233
+ },
6234
+ create(context) {
6235
+ const sourceCode = context.sourceCode;
6236
+ const optionProvider = parseOptions$5(context.options[0]);
6237
+ /**
6238
+ * Parse the options.
6239
+ */
6240
+ function parseOptions$5(option) {
6241
+ const newline = option?.newline ?? "never";
6242
+ const multiline = option?.multiline ?? false;
6243
+ return (bracketsRange) => {
6244
+ if (multiline) {
6245
+ if (sourceCode.text.slice(bracketsRange[0] + 1, bracketsRange[1] - 1).trim().includes("\n")) return "always";
6246
+ }
6247
+ return newline;
6248
+ };
6249
+ }
6250
+ /**
6251
+ * Verify the newline around the brackets.
6252
+ */
6253
+ function verifyNewlineAroundBrackets(node, bracketsRange) {
6254
+ const newline = optionProvider(bracketsRange);
6255
+ const openingBracketIndex = bracketsRange[0];
6256
+ const spaceAfterOpeningBracket = getSpaceAfterOpeningBracket(openingBracketIndex);
6257
+ verifyNewlineAfterOpeningBracket(node, spaceAfterOpeningBracket, openingBracketIndex, newline);
6258
+ const closingBracketIndex = bracketsRange[1] - 1;
6259
+ const newlineOptionBeforeClosingBracket = newline === "consistent" ? getSpaceAfterOpeningBracket(bracketsRange[0]).includes("\n") ? "always" : "never" : newline;
6260
+ verifyNewlineBeforeClosingBracket(node, getSpaceBeforeClosingBracket(closingBracketIndex), openingBracketIndex, closingBracketIndex, newlineOptionBeforeClosingBracket);
6261
+ }
6262
+ /**
6263
+ * Verify the newline after the opening bracket and before the closing bracket.
6264
+ */
6265
+ function verifyNewlineAfterOpeningBracket(node, spaceAfterOpeningBracket, openingBracketIndex, newline) {
6266
+ if (newline === "always") {
6267
+ if (spaceAfterOpeningBracket.includes("\n")) return;
6268
+ const loc = getSourceLocationFromRange(sourceCode, node, [openingBracketIndex, openingBracketIndex + 1]);
6269
+ context.report({
6270
+ node,
6271
+ loc,
6272
+ messageId: "expectedNewlineAfterOpeningBracket",
6273
+ fix: (fixer) => fixer.insertTextAfterRange([openingBracketIndex, openingBracketIndex + 1], `\n${" ".repeat(loc.start.column)}${spaceAfterOpeningBracket}`)
6274
+ });
6275
+ } else if (newline === "never") {
6276
+ if (!spaceAfterOpeningBracket.includes("\n")) return;
6277
+ context.report({
6278
+ node,
6279
+ loc: getSourceLocationFromRange(sourceCode, node, [openingBracketIndex + 1, openingBracketIndex + 1 + spaceAfterOpeningBracket.length]),
6280
+ messageId: "unexpectedNewlineAfterOpeningBracket",
6281
+ fix: (fixer) => fixer.replaceTextRange([openingBracketIndex + 1, openingBracketIndex + 1 + spaceAfterOpeningBracket.length], " ")
6282
+ });
6283
+ }
6284
+ }
6285
+ /**
6286
+ * Verify the newline before the closing bracket.
6287
+ */
6288
+ function verifyNewlineBeforeClosingBracket(node, spaceBeforeClosingBracket, openingBracketIndex, closingBracketIndex, newline) {
6289
+ if (openingBracketIndex + 1 === closingBracketIndex - spaceBeforeClosingBracket.length) return;
6290
+ if (newline === "always") {
6291
+ if (spaceBeforeClosingBracket.includes("\n")) return;
6292
+ context.report({
6293
+ node,
6294
+ loc: getSourceLocationFromRange(sourceCode, node, [closingBracketIndex, closingBracketIndex + 1]),
6295
+ messageId: "expectedNewlineBeforeClosingBracket",
6296
+ fix: (fixer) => {
6297
+ const openingBracketLoc = getSourceLocationFromRange(sourceCode, node, [openingBracketIndex, openingBracketIndex + 1]);
6298
+ return fixer.insertTextBeforeRange([closingBracketIndex, closingBracketIndex + 1], `\n${" ".repeat(openingBracketLoc.start.column)}`);
6299
+ }
6300
+ });
6301
+ } else if (newline === "never") {
6302
+ if (!spaceBeforeClosingBracket.includes("\n")) return;
6303
+ context.report({
6304
+ node,
6305
+ loc: getSourceLocationFromRange(sourceCode, node, [closingBracketIndex - spaceBeforeClosingBracket.length, closingBracketIndex]),
6306
+ messageId: "unexpectedNewlineBeforeClosingBracket",
6307
+ fix: (fixer) => fixer.replaceTextRange([closingBracketIndex - spaceBeforeClosingBracket.length, closingBracketIndex], " ")
6308
+ });
6309
+ }
6310
+ }
6311
+ return {
6312
+ link(node) {
6313
+ if (getLinkKind(sourceCode, node) !== "inline") return;
6314
+ const parsed = parseInlineLink(sourceCode, node);
6315
+ if (!parsed) return;
6316
+ verifyNewlineAroundBrackets(node, parsed.text.range);
6317
+ },
6318
+ linkReference(node) {
6319
+ const parsed = parseLinkReference(sourceCode, node);
6320
+ if (!parsed) return;
6321
+ verifyNewlineAroundBrackets(node, parsed.text.range);
6322
+ if (parsed.label?.type === "full") verifyNewlineAroundBrackets(node, parsed.label.range);
6323
+ },
6324
+ image(node) {
6325
+ const parsed = parseImage(sourceCode, node);
6326
+ if (!parsed) return;
6327
+ verifyNewlineAroundBrackets(node, parsed.text.range);
6328
+ },
6329
+ imageReference(node) {
6330
+ const parsed = parseImageReference(sourceCode, node);
6331
+ if (!parsed) return;
6332
+ verifyNewlineAroundBrackets(node, parsed.text.range);
6333
+ if (parsed.label?.type === "full") verifyNewlineAroundBrackets(node, parsed.label.range);
6334
+ },
6335
+ definition(node) {
6336
+ const parsed = parseLinkDefinition(sourceCode, node);
6337
+ if (!parsed) return;
6338
+ verifyNewlineAroundBrackets(node, parsed.label.range);
6339
+ }
6340
+ };
6341
+ /**
6342
+ * Get the spaces after the opening bracket.
6343
+ */
6344
+ function getSpaceAfterOpeningBracket(openingBracketIndex) {
6345
+ for (let i = openingBracketIndex + 1; i < sourceCode.text.length; i++) {
6346
+ const char = sourceCode.text[i];
6347
+ if (isWhitespace(char)) continue;
6348
+ return sourceCode.text.slice(openingBracketIndex + 1, i);
6349
+ }
6350
+ return sourceCode.text.slice(openingBracketIndex + 1);
6351
+ }
6352
+ /**
6353
+ * Get the spaces before the closing bracket.
6354
+ */
6355
+ function getSpaceBeforeClosingBracket(closingBracketIndex) {
6356
+ for (let i = closingBracketIndex - 1; i >= 0; i--) {
6357
+ const char = sourceCode.text[i];
6358
+ if (isWhitespace(char)) continue;
6359
+ return sourceCode.text.slice(i + 1, closingBracketIndex);
6360
+ }
6361
+ return sourceCode.text.slice(0, closingBracketIndex);
6362
+ }
6363
+ }
6364
+ });
6365
+
6366
+ //#endregion
6367
+ //#region src/rules/link-bracket-spacing.ts
6368
+ /**
6369
+ * The basic option for links and images.
6370
+ */
6371
+ function parseOptions$2(option) {
6372
+ const space = option?.space ?? "never";
6373
+ const imagesInLinks = option?.imagesInLinks;
6374
+ return {
6375
+ space,
6376
+ spaceForText: getOptionForText
6377
+ };
6378
+ /**
6379
+ * Get the spacing option for the given node.
6380
+ */
6381
+ function getOptionForText(node) {
6382
+ if (imagesInLinks != null) {
6383
+ const children = [...node.children];
6384
+ let child;
6385
+ while (children.length && (child = children[0]) && child.type === "text" && isWhitespace(child.value)) children.shift();
6386
+ while (children.length && (child = children[children.length - 1]) && child.type === "text" && isWhitespace(child.value)) children.pop();
6387
+ const loneChild = children.length === 1 ? children[0] : null;
6388
+ if (loneChild?.type === "image" || loneChild?.type === "imageReference") return imagesInLinks ? "always" : "never";
6389
+ }
6390
+ if (node.children.length === 0 || node.children.length === 1 && node.children[0]?.type === "text" && isWhitespace(node.children[0].value)) return "ignore";
6391
+ return space;
6392
+ }
6393
+ }
6394
+ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6395
+ meta: {
6396
+ type: "layout",
6397
+ docs: {
6398
+ description: "enforce consistent spacing inside link brackets",
6399
+ categories: ["standard"],
6400
+ listCategory: "Whitespace"
6401
+ },
6402
+ fixable: "whitespace",
6403
+ hasSuggestions: false,
6404
+ schema: [{
6405
+ type: "object",
6406
+ properties: {
6407
+ space: { enum: ["always", "never"] },
6408
+ imagesInLinks: { type: "boolean" }
6409
+ },
6410
+ additionalProperties: false
6411
+ }],
6412
+ messages: {
6413
+ unexpectedSpaceAfterOpeningBracket: "Unexpected space after opening bracket.",
6414
+ expectedSpaceAfterOpeningBracket: "Expected space after opening bracket.",
6415
+ unexpectedSpaceBeforeClosingBracket: "Unexpected space before closing bracket.",
6416
+ expectedSpaceBeforeClosingBracket: "Expected space before closing bracket."
6417
+ }
6418
+ },
6419
+ create(context) {
6420
+ const sourceCode = context.sourceCode;
6421
+ const options = parseOptions$2(context.options[0]);
6422
+ /**
6423
+ * Verify the space after the opening bracket and before the closing bracket.
6424
+ */
6425
+ function verifySpaceAfterOpeningBracket(node, openingBracketIndex, spaceOption) {
6426
+ const space = getSpaceAfterOpeningBracket(openingBracketIndex);
6427
+ if (space.includes("\n")) return;
6428
+ if (spaceOption === "always") {
6429
+ if (space.length > 0) return;
6430
+ context.report({
6431
+ node,
5395
6432
  loc: getSourceLocationFromRange(sourceCode, node, [openingBracketIndex, openingBracketIndex + 1]),
5396
6433
  messageId: "expectedSpaceAfterOpeningBracket",
5397
6434
  fix: (fixer) => fixer.insertTextAfterRange([openingBracketIndex, openingBracketIndex + 1], " ")
@@ -5524,7 +6561,7 @@ var link_destination_style_default = createRule("link-destination-style", {
5524
6561
  docs: {
5525
6562
  description: "enforce a consistent style for link destinations",
5526
6563
  categories: ["standard"],
5527
- listCategory: "Stylistic"
6564
+ listCategory: "Notation"
5528
6565
  },
5529
6566
  fixable: "code",
5530
6567
  hasSuggestions: false,
@@ -5649,7 +6686,7 @@ var link_paren_newline_default = createRule("link-paren-newline", {
5649
6686
  docs: {
5650
6687
  description: "enforce linebreaks after opening and before closing link parentheses",
5651
6688
  categories: ["standard"],
5652
- listCategory: "Stylistic"
6689
+ listCategory: "Whitespace"
5653
6690
  },
5654
6691
  fixable: "whitespace",
5655
6692
  hasSuggestions: false,
@@ -5674,11 +6711,11 @@ var link_paren_newline_default = createRule("link-paren-newline", {
5674
6711
  },
5675
6712
  create(context) {
5676
6713
  const sourceCode = context.sourceCode;
5677
- const optionProvider = parseOptions$3(context.options[0]);
6714
+ const optionProvider = parseOptions$5(context.options[0]);
5678
6715
  /**
5679
6716
  * Parse the options.
5680
6717
  */
5681
- function parseOptions$3(option) {
6718
+ function parseOptions$5(option) {
5682
6719
  const newline = option?.newline ?? "never";
5683
6720
  const multiline = option?.multiline ?? false;
5684
6721
  return (openingParenIndex, closingParenIndex) => {
@@ -5793,7 +6830,7 @@ var link_paren_spacing_default = createRule("link-paren-spacing", {
5793
6830
  docs: {
5794
6831
  description: "enforce consistent spacing inside link parentheses",
5795
6832
  categories: ["standard"],
5796
- listCategory: "Stylistic"
6833
+ listCategory: "Whitespace"
5797
6834
  },
5798
6835
  fixable: "whitespace",
5799
6836
  hasSuggestions: false,
@@ -5928,7 +6965,7 @@ var link_title_style_default = createRule("link-title-style", {
5928
6965
  docs: {
5929
6966
  description: "enforce a consistent style for link titles",
5930
6967
  categories: ["standard"],
5931
- listCategory: "Stylistic"
6968
+ listCategory: "Notation"
5932
6969
  },
5933
6970
  fixable: "code",
5934
6971
  hasSuggestions: false,
@@ -6010,7 +7047,7 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
6010
7047
  docs: {
6011
7048
  description: "enforce consistent alignment of list markers",
6012
7049
  categories: ["recommended", "standard"],
6013
- listCategory: "Stylistic"
7050
+ listCategory: "Whitespace"
6014
7051
  },
6015
7052
  fixable: "whitespace",
6016
7053
  hasSuggestions: false,
@@ -6044,10 +7081,22 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
6044
7081
  const items = listNode.children;
6045
7082
  if (items.length <= 1) return;
6046
7083
  const referenceMarkerLocation = getMarkerLocation(items[0]);
7084
+ const expectedWidth = getWidth(sourceCode.lines[referenceMarkerLocation.line - 1].slice(0, referenceMarkerLocation[alignPositionName]));
6047
7085
  for (const item of items.slice(1)) {
6048
7086
  const markerLocation = getMarkerLocation(item);
6049
- const diff = markerLocation[alignPositionName] - referenceMarkerLocation[alignPositionName];
7087
+ const actualWidth = getWidth(sourceCode.lines[markerLocation.line - 1].slice(0, markerLocation[alignPositionName]));
7088
+ const diff = actualWidth - expectedWidth;
6050
7089
  if (diff === 0) continue;
7090
+ const messageData = alignPositionName === "start" ? {
7091
+ expected: String(expectedWidth),
7092
+ actual: String(actualWidth)
7093
+ } : (() => {
7094
+ const start = getWidth(sourceCode.lines[markerLocation.line - 1].slice(0, markerLocation.start));
7095
+ return {
7096
+ expected: String(start - diff),
7097
+ actual: String(start)
7098
+ };
7099
+ })();
6051
7100
  context.report({
6052
7101
  node: item,
6053
7102
  loc: {
@@ -6061,10 +7110,7 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
6061
7110
  }
6062
7111
  },
6063
7112
  messageId: "incorrectAlignment",
6064
- data: {
6065
- expected: String(markerLocation.start - diff),
6066
- actual: String(markerLocation.start)
6067
- },
7113
+ data: messageData,
6068
7114
  fix(fixer) {
6069
7115
  const lines = getParsedLines(sourceCode);
6070
7116
  const line = lines.get(markerLocation.line);
@@ -6072,13 +7118,17 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
6072
7118
  const addSpaces = " ".repeat(-diff);
6073
7119
  return fixer.insertTextBeforeRange([line.range[0] + markerLocation.start, line.range[0] + markerLocation.start], addSpaces);
6074
7120
  }
6075
- const itemBefore = line.text.slice(0, markerLocation.start - diff);
6076
- if (itemBefore.includes(" ")) return null;
6077
- if (lines.get(referenceMarkerLocation.line).text.slice(0, referenceMarkerLocation.start) === itemBefore) {
6078
- const removeEndIndex = line.range[0] + markerLocation.start;
6079
- const removeStartIndex = removeEndIndex - diff;
6080
- return fixer.removeRange([removeStartIndex, removeEndIndex]);
7121
+ const beforeItemMarker = line.text.slice(0, markerLocation.start);
7122
+ const newWidth = getWidth(beforeItemMarker) - diff;
7123
+ let newBeforeItemMarker = beforeItemMarker;
7124
+ while (getWidth(newBeforeItemMarker) > newWidth) {
7125
+ const last = newBeforeItemMarker.at(-1);
7126
+ if (last && isWhitespace(last)) newBeforeItemMarker = newBeforeItemMarker.slice(0, -1);
7127
+ else return null;
6081
7128
  }
7129
+ if (getWidth(newBeforeItemMarker) < newWidth) newBeforeItemMarker += " ".repeat(newWidth - getWidth(newBeforeItemMarker));
7130
+ const referenceBeforeItemMarker = lines.get(referenceMarkerLocation.line).text.slice(0, referenceMarkerLocation.start);
7131
+ if (!referenceBeforeItemMarker.includes(">") || referenceBeforeItemMarker === newBeforeItemMarker) return fixer.replaceTextRange([line.range[0], line.range[0] + markerLocation.start], newBeforeItemMarker);
6082
7132
  return null;
6083
7133
  }
6084
7134
  });
@@ -6096,7 +7146,7 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
6096
7146
  docs: {
6097
7147
  description: "disallow laziness in blockquotes",
6098
7148
  categories: ["recommended", "standard"],
6099
- listCategory: "Stylistic"
7149
+ listCategory: "Decorative"
6100
7150
  },
6101
7151
  fixable: void 0,
6102
7152
  hasSuggestions: true,
@@ -6193,7 +7243,194 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
6193
7243
  reportInvalidLines(invalidLines, base);
6194
7244
  } };
6195
7245
  }
6196
- });
7246
+ });
7247
+
7248
+ //#endregion
7249
+ //#region src/utils/table.ts
7250
+ /**
7251
+ * Parse the table.
7252
+ */
7253
+ function parseTable(sourceCode, node) {
7254
+ const headerRow = parseTableRow(sourceCode, node.children[0]);
7255
+ if (!headerRow) return null;
7256
+ const delimiterRow = parseTableDelimiterRow(sourceCode, node);
7257
+ if (!delimiterRow) return null;
7258
+ const bodyRows = [];
7259
+ for (const child of node.children.slice(1)) {
7260
+ const bodyRow = parseTableRow(sourceCode, child);
7261
+ if (!bodyRow) return null;
7262
+ bodyRows.push(bodyRow);
7263
+ }
7264
+ return {
7265
+ headerRow,
7266
+ delimiterRow,
7267
+ bodyRows
7268
+ };
7269
+ }
7270
+ /**
7271
+ * Parse the table delimiter row.
7272
+ */
7273
+ function parseTableDelimiterRow(sourceCode, node) {
7274
+ const headerRow = node.children[0];
7275
+ const headerRange = sourceCode.getRange(headerRow);
7276
+ const delimiterEndIndex = node.children.length > 1 ? sourceCode.getRange(node.children[1])[0] : sourceCode.getRange(node)[1];
7277
+ const delimiterText = sourceCode.text.slice(headerRange[1], delimiterEndIndex);
7278
+ const parsed = parseTableDelimiterRowFromText(delimiterText);
7279
+ if (!parsed) return null;
7280
+ const delimiters = parsed.delimiters.map((d) => {
7281
+ let leadingPipe = null;
7282
+ if (d.leadingPipe) {
7283
+ const leadingPipeRange = [headerRange[1] + d.leadingPipe.range[0], headerRange[1] + d.leadingPipe.range[1]];
7284
+ leadingPipe = {
7285
+ text: d.leadingPipe.text,
7286
+ range: leadingPipeRange,
7287
+ loc: getSourceLocationFromRange(sourceCode, headerRow, leadingPipeRange)
7288
+ };
7289
+ }
7290
+ const delimiterRange = [headerRange[1] + d.delimiter.range[0], headerRange[1] + d.delimiter.range[1]];
7291
+ return {
7292
+ leadingPipe,
7293
+ delimiter: {
7294
+ text: d.delimiter.text,
7295
+ align: d.delimiter.text.startsWith(":") ? d.delimiter.text.endsWith(":") ? "center" : "left" : d.delimiter.text.endsWith(":") ? "right" : "none",
7296
+ range: delimiterRange,
7297
+ loc: getSourceLocationFromRange(sourceCode, headerRow, delimiterRange)
7298
+ }
7299
+ };
7300
+ });
7301
+ let trailingPipe = null;
7302
+ if (parsed.trailingPipe) {
7303
+ const trailingPipeRange = [headerRange[1] + parsed.trailingPipe.range[0], headerRange[1] + parsed.trailingPipe.range[1]];
7304
+ trailingPipe = {
7305
+ text: parsed.trailingPipe.text,
7306
+ range: trailingPipeRange,
7307
+ loc: getSourceLocationFromRange(sourceCode, headerRow, trailingPipeRange)
7308
+ };
7309
+ }
7310
+ const firstToken = delimiters[0].leadingPipe ?? delimiters[0].delimiter;
7311
+ const lastToken = trailingPipe ?? delimiters[delimiters.length - 1].delimiter;
7312
+ return {
7313
+ delimiters,
7314
+ trailingPipe,
7315
+ range: [firstToken.range[0], lastToken.range[1]],
7316
+ loc: {
7317
+ start: firstToken.loc.start,
7318
+ end: lastToken.loc.end
7319
+ }
7320
+ };
7321
+ }
7322
+ /**
7323
+ * Parse the table row.
7324
+ */
7325
+ function parseTableRow(sourceCode, node) {
7326
+ const cells = [];
7327
+ let trailingPipe = null;
7328
+ for (const cell of node.children) {
7329
+ const cellRange = sourceCode.getRange(cell);
7330
+ const cellLoc = sourceCode.getLoc(cell);
7331
+ const leadingPipe = sourceCode.text[cellRange[0]] === "|" ? {
7332
+ text: "|",
7333
+ range: [cellRange[0], cellRange[0] + 1],
7334
+ loc: {
7335
+ start: cellLoc.start,
7336
+ end: {
7337
+ line: cellLoc.start.line,
7338
+ column: cellLoc.start.column + 1
7339
+ }
7340
+ }
7341
+ } : null;
7342
+ if (trailingPipe && leadingPipe) return null;
7343
+ let parsedCell = null;
7344
+ if (cell.children.length > 0) {
7345
+ const firstChild = cell.children[0];
7346
+ const lastChild = cell.children[cell.children.length - 1];
7347
+ parsedCell = {
7348
+ range: [sourceCode.getRange(firstChild)[0], sourceCode.getRange(lastChild)[1]],
7349
+ loc: {
7350
+ start: sourceCode.getLoc(firstChild).start,
7351
+ end: sourceCode.getLoc(lastChild).end
7352
+ }
7353
+ };
7354
+ }
7355
+ cells.push({
7356
+ leadingPipe,
7357
+ cell: parsedCell
7358
+ });
7359
+ trailingPipe = sourceCode.text[cellRange[1] - 1] === "|" ? {
7360
+ text: "|",
7361
+ range: [cellRange[1] - 1, cellRange[1]],
7362
+ loc: {
7363
+ start: {
7364
+ line: cellLoc.end.line,
7365
+ column: cellLoc.end.column - 1
7366
+ },
7367
+ end: cellLoc.end
7368
+ }
7369
+ } : null;
7370
+ }
7371
+ const firstToken = cells[0].leadingPipe ?? cells[0].cell;
7372
+ const lastToken = trailingPipe ?? cells[cells.length - 1].cell ?? cells[cells.length - 1].leadingPipe;
7373
+ return {
7374
+ cells,
7375
+ trailingPipe,
7376
+ range: [firstToken.range[0], lastToken.range[1]],
7377
+ loc: {
7378
+ start: firstToken.loc.start,
7379
+ end: lastToken.loc.end
7380
+ }
7381
+ };
7382
+ }
7383
+ /**
7384
+ * Parse the table delimiter row from the text.
7385
+ */
7386
+ function parseTableDelimiterRowFromText(text) {
7387
+ const cursor = new ForwardCharacterCursor(text);
7388
+ cursor.skipSpaces();
7389
+ while (cursor.curr() === ">") {
7390
+ cursor.next();
7391
+ cursor.skipSpaces();
7392
+ }
7393
+ const delimiters = [];
7394
+ let pipe = consumePipe();
7395
+ while (!cursor.finished()) {
7396
+ const delimiterStart = cursor.currIndex();
7397
+ cursor.skipUntilEnd((c) => c === "|" || isSpaceOrTab(c) || c === "\n" || c === "\r");
7398
+ const delimiterRange = [delimiterStart, cursor.currIndex()];
7399
+ const delimiterText = text.slice(...delimiterRange);
7400
+ if (!/^:?-+:?$/u.test(delimiterText)) return null;
7401
+ if (delimiters.length > 0 && pipe == null) return null;
7402
+ delimiters.push({
7403
+ leadingPipe: pipe,
7404
+ delimiter: {
7405
+ text: delimiterText,
7406
+ range: delimiterRange
7407
+ }
7408
+ });
7409
+ pipe = consumePipe();
7410
+ }
7411
+ return {
7412
+ delimiters,
7413
+ trailingPipe: pipe
7414
+ };
7415
+ /**
7416
+ * Consume a pipe if exists.
7417
+ */
7418
+ function consumePipe() {
7419
+ cursor.skipSpaces();
7420
+ if (cursor.curr() === "|") {
7421
+ const pipeStart = cursor.currIndex();
7422
+ cursor.next();
7423
+ const pipeRange = [pipeStart, cursor.currIndex()];
7424
+ const result = {
7425
+ text: text.slice(...pipeRange),
7426
+ range: pipeRange
7427
+ };
7428
+ cursor.skipSpaces();
7429
+ return result;
7430
+ }
7431
+ return null;
7432
+ }
7433
+ }
6197
7434
 
6198
7435
  //#endregion
6199
7436
  //#region src/rules/no-multi-spaces.ts
@@ -6203,7 +7440,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6203
7440
  docs: {
6204
7441
  description: "disallow multiple spaces",
6205
7442
  categories: ["standard"],
6206
- listCategory: "Stylistic"
7443
+ listCategory: "Whitespace"
6207
7444
  },
6208
7445
  fixable: "whitespace",
6209
7446
  hasSuggestions: false,
@@ -6212,6 +7449,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6212
7449
  },
6213
7450
  create(context) {
6214
7451
  const sourceCode = context.sourceCode;
7452
+ let codeText = sourceCode.text;
6215
7453
  return {
6216
7454
  definition: verifyLinkDefinition,
6217
7455
  footnoteDefinition: verifyFootnoteDefinition,
@@ -6221,7 +7459,9 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6221
7459
  link: verifyLink,
6222
7460
  linkReference: verifyLinkReference,
6223
7461
  listItem: verifyListItem,
6224
- text: verifyText
7462
+ blockquote: processBlockquote,
7463
+ text: verifyText,
7464
+ table: verifyTable
6225
7465
  };
6226
7466
  /**
6227
7467
  * Verify a text node.
@@ -6230,6 +7470,14 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6230
7470
  verifyTextInNode(node);
6231
7471
  }
6232
7472
  /**
7473
+ * Verify a table node.
7474
+ */
7475
+ function verifyTable(node) {
7476
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
7477
+ if (!parsedDelimiterRow) return;
7478
+ verifyTextInRange(node, parsedDelimiterRow.range);
7479
+ }
7480
+ /**
6233
7481
  * Verify a definition node.
6234
7482
  */
6235
7483
  function verifyLinkDefinition(node) {
@@ -6294,7 +7542,13 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6294
7542
  * Verify a list item node
6295
7543
  */
6296
7544
  function verifyListItem(node) {
6297
- verifyTextOutsideChildren(node);
7545
+ const nodeRange = sourceCode.getRange(node);
7546
+ const parsed = parseListItem(sourceCode, node);
7547
+ if (parsed.taskListItemMarker) verifyTextInRange(node, [nodeRange[0], parsed.taskListItemMarker.range[0]]);
7548
+ let newCodeText = codeText.slice(0, parsed.marker.range[0]) + " ".repeat(parsed.marker.range[1] - parsed.marker.range[0]);
7549
+ if (parsed.taskListItemMarker) newCodeText += codeText.slice(parsed.marker.range[1], parsed.taskListItemMarker.range[0]) + " ".repeat(parsed.taskListItemMarker.range[1] - parsed.taskListItemMarker.range[0]) + codeText.slice(parsed.taskListItemMarker.range[1]);
7550
+ else newCodeText += codeText.slice(parsed.marker.range[1]);
7551
+ codeText = newCodeText;
6298
7552
  }
6299
7553
  /**
6300
7554
  * Verify spaces in a node
@@ -6304,6 +7558,26 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6304
7558
  verifyTextInRange(node, nodeRange);
6305
7559
  }
6306
7560
  /**
7561
+ * Process a blockquote node
7562
+ */
7563
+ function processBlockquote(node) {
7564
+ const nodeRange = sourceCode.getRange(node);
7565
+ let newCodeText = "";
7566
+ let inIndent = true;
7567
+ for (let index = nodeRange[0]; index < nodeRange[1]; index++) {
7568
+ const c = codeText[index];
7569
+ if (c === "\n") {
7570
+ inIndent = true;
7571
+ continue;
7572
+ }
7573
+ if (isWhitespace(c)) continue;
7574
+ if (c === ">" && inIndent) newCodeText += `${codeText.slice(newCodeText.length, index)} `;
7575
+ inIndent = false;
7576
+ }
7577
+ newCodeText += codeText.slice(newCodeText.length);
7578
+ codeText = newCodeText;
7579
+ }
7580
+ /**
6307
7581
  * Verify spaces in a node excluding children
6308
7582
  */
6309
7583
  function verifyTextOutsideChildren(node) {
@@ -6322,14 +7596,14 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6322
7596
  */
6323
7597
  function verifyTextInRange(node, textRange) {
6324
7598
  const nodeRange = sourceCode.getRange(node);
6325
- const text = sourceCode.text.slice(...textRange);
7599
+ const text = codeText.slice(...textRange);
6326
7600
  const reSpaces = /\s{2,}|\n/gu;
6327
7601
  let match;
6328
7602
  while ((match = reSpaces.exec(text)) !== null) {
6329
7603
  const spaces = match[0];
6330
7604
  if (spaces.includes("\n")) {
6331
7605
  let c = "";
6332
- while ((c = text[reSpaces.lastIndex]) && (c === ">" || !c.trim())) reSpaces.lastIndex++;
7606
+ while ((c = text[reSpaces.lastIndex]) && isWhitespace(c)) reSpaces.lastIndex++;
6333
7607
  continue;
6334
7608
  }
6335
7609
  if (spaces.length < 2) continue;
@@ -6339,7 +7613,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6339
7613
  if (nodeRange[0] === range[0]) {
6340
7614
  let isIndentation = true;
6341
7615
  for (let index = nodeRange[0] - 1; index >= 0; index--) {
6342
- const c = sourceCode.text[index];
7616
+ const c = codeText[index];
6343
7617
  if (c === "\n") break;
6344
7618
  if (isWhitespace(c)) continue;
6345
7619
  isIndentation = false;
@@ -6349,8 +7623,8 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6349
7623
  }
6350
7624
  if (nodeRange[1] === range[1]) {
6351
7625
  let isTrailingSpaces = true;
6352
- for (let index = nodeRange[1]; index < sourceCode.text.length; index++) {
6353
- const c = sourceCode.text[index];
7626
+ for (let index = nodeRange[1]; index < codeText.length; index++) {
7627
+ const c = codeText[index];
6354
7628
  if (c === "\n") break;
6355
7629
  if (isWhitespace(c)) continue;
6356
7630
  isTrailingSpaces = false;
@@ -6379,7 +7653,7 @@ var no_multiple_empty_lines_default = createRule("no-multiple-empty-lines", {
6379
7653
  docs: {
6380
7654
  description: "disallow multiple empty lines in Markdown files.",
6381
7655
  categories: ["standard"],
6382
- listCategory: "Stylistic"
7656
+ listCategory: "Whitespace"
6383
7657
  },
6384
7658
  fixable: "whitespace",
6385
7659
  hasSuggestions: false,
@@ -6538,7 +7812,7 @@ var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebrea
6538
7812
  docs: {
6539
7813
  description: "disallow text backslash at the end of a line.",
6540
7814
  categories: ["recommended", "standard"],
6541
- listCategory: "Stylistic"
7815
+ listCategory: "Notation"
6542
7816
  },
6543
7817
  fixable: void 0,
6544
7818
  hasSuggestions: true,
@@ -6583,7 +7857,7 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
6583
7857
  docs: {
6584
7858
  description: "disallow trailing whitespace at the end of lines in Markdown files.",
6585
7859
  categories: ["standard"],
6586
- listCategory: "Stylistic"
7860
+ listCategory: "Whitespace"
6587
7861
  },
6588
7862
  fixable: "whitespace",
6589
7863
  hasSuggestions: false,
@@ -6709,7 +7983,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
6709
7983
  docs: {
6710
7984
  description: "enforce that ordered list markers use sequential numbers",
6711
7985
  categories: ["standard"],
6712
- listCategory: "Stylistic"
7986
+ listCategory: "Decorative"
6713
7987
  },
6714
7988
  fixable: "code",
6715
7989
  hasSuggestions: true,
@@ -6923,7 +8197,7 @@ function markerToKind(marker) {
6923
8197
  /**
6924
8198
  * Parse rule options.
6925
8199
  */
6926
- function parseOptions(options) {
8200
+ function parseOptions$1(options) {
6927
8201
  const prefer = markerToKind(options.prefer) || ".";
6928
8202
  const overrides = (options.overrides ?? []).map((override) => {
6929
8203
  const preferForOverride = markerToKind(override.prefer) || ".";
@@ -6950,7 +8224,7 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
6950
8224
  docs: {
6951
8225
  description: "enforce consistent ordered list marker style",
6952
8226
  categories: ["standard"],
6953
- listCategory: "Stylistic"
8227
+ listCategory: "Notation"
6954
8228
  },
6955
8229
  fixable: "code",
6956
8230
  hasSuggestions: false,
@@ -6984,7 +8258,7 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
6984
8258
  },
6985
8259
  create(context) {
6986
8260
  const sourceCode = context.sourceCode;
6987
- const options = parseOptions(context.options[0] || {});
8261
+ const options = parseOptions$1(context.options[0] || {});
6988
8262
  let containerStack = {
6989
8263
  node: sourceCode.ast,
6990
8264
  level: 1,
@@ -7169,7 +8443,7 @@ var padding_line_between_blocks_default = createRule("padding-line-between-block
7169
8443
  docs: {
7170
8444
  description: "require or disallow padding lines between blocks",
7171
8445
  categories: ["standard"],
7172
- listCategory: "Stylistic"
8446
+ listCategory: "Whitespace"
7173
8447
  },
7174
8448
  fixable: "whitespace",
7175
8449
  hasSuggestions: false,
@@ -7367,7 +8641,7 @@ var prefer_autolinks_default = createRule("prefer-autolinks", {
7367
8641
  docs: {
7368
8642
  description: "enforce the use of autolinks for URLs",
7369
8643
  categories: ["recommended", "standard"],
7370
- listCategory: "Stylistic"
8644
+ listCategory: "Notation"
7371
8645
  },
7372
8646
  fixable: "code",
7373
8647
  hasSuggestions: false,
@@ -7405,7 +8679,7 @@ var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks",
7405
8679
  docs: {
7406
8680
  description: "enforce the use of fenced code blocks over indented code blocks",
7407
8681
  categories: ["recommended", "standard"],
7408
- listCategory: "Stylistic"
8682
+ listCategory: "Notation"
7409
8683
  },
7410
8684
  fixable: "code",
7411
8685
  hasSuggestions: false,
@@ -7635,7 +8909,7 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
7635
8909
  docs: {
7636
8910
  description: "enforce using link reference definitions instead of inline links",
7637
8911
  categories: [],
7638
- listCategory: "Stylistic"
8912
+ listCategory: "Notation"
7639
8913
  },
7640
8914
  fixable: "code",
7641
8915
  hasSuggestions: false,
@@ -7914,9 +9188,9 @@ var setext_heading_underline_length_default = createRule("setext-heading-underli
7914
9188
  docs: {
7915
9189
  description: "enforce setext heading underline length",
7916
9190
  categories: ["standard"],
7917
- listCategory: "Stylistic"
9191
+ listCategory: "Decorative"
7918
9192
  },
7919
- fixable: "whitespace",
9193
+ fixable: "code",
7920
9194
  schema: [{
7921
9195
  type: "object",
7922
9196
  properties: {
@@ -8152,7 +9426,7 @@ var sort_definitions_default = createRule("sort-definitions", {
8152
9426
  docs: {
8153
9427
  description: "enforce a specific order for link definitions and footnote definitions",
8154
9428
  categories: ["standard"],
8155
- listCategory: "Stylistic"
9429
+ listCategory: "Decorative"
8156
9430
  },
8157
9431
  fixable: "code",
8158
9432
  hasSuggestions: false,
@@ -8425,7 +9699,7 @@ var strikethrough_delimiters_style_default = createRule("strikethrough-delimiter
8425
9699
  docs: {
8426
9700
  description: "enforce a consistent delimiter style for strikethrough",
8427
9701
  categories: ["standard"],
8428
- listCategory: "Stylistic"
9702
+ listCategory: "Notation"
8429
9703
  },
8430
9704
  fixable: "code",
8431
9705
  hasSuggestions: false,
@@ -8444,151 +9718,869 @@ var strikethrough_delimiters_style_default = createRule("strikethrough-delimiter
8444
9718
  const actualDelimiter = ["~~", "~"].find((d) => sourceCode.text.startsWith(d, range[0]) && sourceCode.text.endsWith(d, range[1]));
8445
9719
  if (!actualDelimiter || actualDelimiter === delimiter) return;
8446
9720
  context.report({
8447
- node,
8448
- messageId: "wrongDelimiter",
9721
+ node,
9722
+ messageId: "wrongDelimiter",
9723
+ data: {
9724
+ expected: delimiter,
9725
+ actual: actualDelimiter
9726
+ },
9727
+ fix(fixer) {
9728
+ return [fixer.replaceTextRange([range[0], range[0] + actualDelimiter.length], delimiter), fixer.replaceTextRange([range[1] - actualDelimiter.length, range[1]], delimiter)];
9729
+ }
9730
+ });
9731
+ } };
9732
+ }
9733
+ });
9734
+
9735
+ //#endregion
9736
+ //#region src/rules/table-header-casing.ts
9737
+ var table_header_casing_default = createRule("table-header-casing", {
9738
+ meta: {
9739
+ type: "suggestion",
9740
+ fixable: "code",
9741
+ docs: {
9742
+ description: "enforce consistent casing in table header cells.",
9743
+ categories: [],
9744
+ listCategory: "Preference"
9745
+ },
9746
+ schema: [{
9747
+ type: "object",
9748
+ properties: {
9749
+ style: { enum: ["Title Case", "Sentence case"] },
9750
+ preserveWords: {
9751
+ type: "array",
9752
+ items: { type: "string" },
9753
+ description: "Words that should be preserved as-is (case-insensitive matching)"
9754
+ },
9755
+ ignorePatterns: {
9756
+ type: "array",
9757
+ items: { type: "string" },
9758
+ description: "Regular expression patterns for words to ignore during casing checks"
9759
+ },
9760
+ minorWords: {
9761
+ type: "array",
9762
+ items: { type: "string" },
9763
+ description: "Words that should not be capitalized in Title Case (unless they're the first or last word)"
9764
+ }
9765
+ },
9766
+ additionalProperties: false
9767
+ }],
9768
+ messages: {
9769
+ expectedTitleCase: "Expected \"{{actual}}\" to be \"{{expected}}\" (Title Case).",
9770
+ expectedTitleCaseMinorWord: "Expected \"{{actual}}\" to be \"{{expected}}\" (Title Case - minor word).",
9771
+ expectedSentenceCase: "Expected \"{{actual}}\" to be \"{{expected}}\" (Sentence case).",
9772
+ expectedPreserveWord: "Expected \"{{actual}}\" to be \"{{expected}}\" (preserved word)."
9773
+ }
9774
+ },
9775
+ create(context) {
9776
+ const sourceCode = context.sourceCode;
9777
+ const caseStyle = context.options[0]?.style || "Title Case";
9778
+ const preserveWordsOption = parsePreserveWordsOption(context.options[0]?.preserveWords || defaultPreserveWords);
9779
+ const minorWords = context.options[0]?.minorWords || defaultMinorWords;
9780
+ const ignorePatterns = (context.options[0]?.ignorePatterns || [
9781
+ "/^v\\d+/u",
9782
+ "/\\w+\\.[a-z\\d]+$/u",
9783
+ "/\\w+(?:API|Api)$/u",
9784
+ "/\\w+(?:SDK|Sdk)$/u",
9785
+ "/\\w+(?:CLI|Cli)$/u"
9786
+ ]).map((pattern) => {
9787
+ if (isRegExp(pattern)) return toRegExp(pattern);
9788
+ try {
9789
+ return new RegExp(pattern, "v");
9790
+ } catch {}
9791
+ try {
9792
+ return new RegExp(pattern, "u");
9793
+ } catch {}
9794
+ return new RegExp(pattern);
9795
+ });
9796
+ /**
9797
+ * Check text node and report word-level errors
9798
+ */
9799
+ function checkTextNode(node, firstNode, lastNode) {
9800
+ const text = sourceCode.getText(node);
9801
+ const wordAndOffsets = parseWordsFromText(text, firstNode, lastNode);
9802
+ const processed = /* @__PURE__ */ new Set();
9803
+ for (let index = 0; index < wordAndOffsets.length; index++) {
9804
+ if (processed.has(index)) continue;
9805
+ processed.add(index);
9806
+ const wordAndOffset = wordAndOffsets[index];
9807
+ if (wordAndOffset.punctuation) continue;
9808
+ if (ignorePatterns.some((pattern) => pattern.test(wordAndOffset.word))) continue;
9809
+ const preservePhrase = preserveWordsOption.findPreservePhrase((function* () {
9810
+ const firstWord = wordAndOffsets[index];
9811
+ if (firstWord.punctuation) return;
9812
+ yield firstWord.word;
9813
+ for (let next = index + 1; next < wordAndOffsets.length; next++) yield wordAndOffsets[next].word;
9814
+ })());
9815
+ if (preservePhrase) {
9816
+ for (let wordIndex = 0; wordIndex < preservePhrase.length; wordIndex++) {
9817
+ processed.add(index + wordIndex);
9818
+ verifyWord(wordAndOffsets[index + wordIndex], preservePhrase[wordIndex], "preserved");
9819
+ }
9820
+ continue;
9821
+ }
9822
+ const preserveWordList = preserveWordsOption.findPreserveWord(wordAndOffset.word);
9823
+ if (preserveWordList) {
9824
+ if (!preserveWordList.some((w) => w === wordAndOffset.word)) verifyWord(wordAndOffset, preserveWordList[0], "preserved");
9825
+ continue;
9826
+ }
9827
+ const expectedWordResult = convertWordCasing(wordAndOffset, {
9828
+ caseStyle,
9829
+ minorWords
9830
+ });
9831
+ verifyWord(wordAndOffset, expectedWordResult.word, expectedWordResult.isMinorWord ? "minor" : "normal");
9832
+ }
9833
+ /**
9834
+ * Verify a single word against the expected casing
9835
+ */
9836
+ function verifyWord(wordAndOffset, expectedWord, wordType = "normal") {
9837
+ const { word, offset } = wordAndOffset;
9838
+ if (word === expectedWord) return;
9839
+ const [nodeStart] = sourceCode.getRange(node);
9840
+ const range = [nodeStart + offset, nodeStart + offset + word.length];
9841
+ context.report({
9842
+ node,
9843
+ messageId: wordType === "preserved" ? "expectedPreserveWord" : caseStyle === "Title Case" ? wordType === "minor" ? "expectedTitleCaseMinorWord" : "expectedTitleCase" : "expectedSentenceCase",
9844
+ data: {
9845
+ actual: word,
9846
+ expected: expectedWord
9847
+ },
9848
+ loc: getSourceLocationFromRange(sourceCode, node, range),
9849
+ fix(fixer) {
9850
+ return fixer.replaceTextRange(range, expectedWord);
9851
+ }
9852
+ });
9853
+ }
9854
+ }
9855
+ return { table(node) {
9856
+ if (!node.children.length) return;
9857
+ const headerRow = node.children[0];
9858
+ if (headerRow.type !== "tableRow") return;
9859
+ for (const cell of headerRow.children) {
9860
+ const children = cell.children.filter((child) => child.type !== "text" || child.value.trim());
9861
+ children.forEach((child, i) => {
9862
+ if (child.type === "text") checkTextNode(child, i === 0, i === children.length - 1);
9863
+ });
9864
+ }
9865
+ } };
9866
+ }
9867
+ });
9868
+
9869
+ //#endregion
9870
+ //#region src/rules/table-leading-trailing-pipes.ts
9871
+ var table_leading_trailing_pipes_default = createRule("table-leading-trailing-pipes", {
9872
+ meta: {
9873
+ type: "layout",
9874
+ docs: {
9875
+ description: "enforce consistent use of leading and trailing pipes in tables.",
9876
+ categories: ["standard"],
9877
+ listCategory: "Decorative"
9878
+ },
9879
+ fixable: "code",
9880
+ hasSuggestions: false,
9881
+ schema: [{ anyOf: [{ enum: ["always", "never"] }, {
9882
+ type: "object",
9883
+ properties: {
9884
+ leading: { enum: ["always", "never"] },
9885
+ trailing: { enum: ["always", "never"] }
9886
+ },
9887
+ additionalProperties: false
9888
+ }] }],
9889
+ messages: {
9890
+ missingLeadingPipe: "Table line should start with a leading pipe.",
9891
+ unexpectedLeadingPipe: "Table line should not start with a leading pipe.",
9892
+ missingTrailingPipe: "Table line should end with a trailing pipe.",
9893
+ unexpectedTrailingPipe: "Table line should not end with a trailing pipe."
9894
+ }
9895
+ },
9896
+ create(context) {
9897
+ const sourceCode = context.sourceCode;
9898
+ const preferOption = context.options[0] ?? "always";
9899
+ const leadingOption = typeof preferOption === "string" ? preferOption : preferOption.leading ?? "always";
9900
+ const trailingOption = typeof preferOption === "string" ? preferOption : preferOption.trailing ?? "always";
9901
+ /**
9902
+ * Verify the table pipes
9903
+ */
9904
+ function verifyTablePipes(node) {
9905
+ for (const row of node.children) verifyTableRowPipes(row);
9906
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
9907
+ if (parsedDelimiterRow) verifyTableLinePipes(parsedDelimiterRow.range, parsedDelimiterRow.loc, parsedDelimiterRow.delimiters.length);
9908
+ }
9909
+ /**
9910
+ * Verify the table row pipes
9911
+ */
9912
+ function verifyTableRowPipes(node) {
9913
+ const loc = sourceCode.getLoc(node);
9914
+ const range = sourceCode.getRange(node);
9915
+ verifyTableLinePipes(range, loc, node.children.length);
9916
+ }
9917
+ /**
9918
+ * Verify the table line pipes
9919
+ */
9920
+ function verifyTableLinePipes(lineContentRange, lineLocation, columnCount) {
9921
+ verifyTableLeadingPipe(lineContentRange, lineLocation, columnCount);
9922
+ verifyTableTrailingPipe(lineContentRange, lineLocation, columnCount);
9923
+ }
9924
+ /**
9925
+ * Verify the table leading pipe
9926
+ */
9927
+ function verifyTableLeadingPipe(lineContentRange, lineLocation, columnCount) {
9928
+ if (leadingOption === "always") {
9929
+ if (sourceCode.text.startsWith("|", lineContentRange[0])) return;
9930
+ context.report({
9931
+ messageId: "missingLeadingPipe",
9932
+ loc: lineLocation.start,
9933
+ fix(fixer) {
9934
+ return fixer.insertTextBeforeRange(lineContentRange, "| ");
9935
+ }
9936
+ });
9937
+ } else if (leadingOption === "never") {
9938
+ if (columnCount < 2) {
9939
+ if (!(trailingOption === "always" && sourceCode.text.endsWith("|", lineContentRange[1]))) return;
9940
+ }
9941
+ if (!sourceCode.text.startsWith("|", lineContentRange[0])) return;
9942
+ let endIndex = lineContentRange[0] + 1;
9943
+ while (endIndex < lineContentRange[1] && isSpaceOrTab(sourceCode.text[endIndex])) endIndex++;
9944
+ context.report({
9945
+ messageId: "unexpectedLeadingPipe",
9946
+ loc: {
9947
+ start: lineLocation.start,
9948
+ end: {
9949
+ line: lineLocation.start.line,
9950
+ column: lineLocation.start.column + (endIndex - lineContentRange[0])
9951
+ }
9952
+ },
9953
+ fix(fixer) {
9954
+ return fixer.removeRange([lineContentRange[0], endIndex]);
9955
+ }
9956
+ });
9957
+ }
9958
+ }
9959
+ /**
9960
+ * Verify the table trailing pipe
9961
+ */
9962
+ function verifyTableTrailingPipe(lineContentRange, lineLocation, columnCount) {
9963
+ if (trailingOption === "always") {
9964
+ if (sourceCode.text.endsWith("|", lineContentRange[1])) return;
9965
+ context.report({
9966
+ messageId: "missingTrailingPipe",
9967
+ loc: lineLocation.end,
9968
+ fix(fixer) {
9969
+ return fixer.insertTextAfterRange(lineContentRange, " |");
9970
+ }
9971
+ });
9972
+ } else if (trailingOption === "never") {
9973
+ if (columnCount < 2) {
9974
+ if (!(leadingOption === "always" && sourceCode.text.startsWith("|", lineContentRange[0]))) return;
9975
+ }
9976
+ if (!sourceCode.text.endsWith("|", lineContentRange[1])) return;
9977
+ let startIndex = lineContentRange[1] - 1;
9978
+ while (startIndex - 1 > lineContentRange[0] && isSpaceOrTab(sourceCode.text[startIndex - 1])) startIndex--;
9979
+ context.report({
9980
+ messageId: "unexpectedTrailingPipe",
9981
+ loc: {
9982
+ start: {
9983
+ line: lineLocation.end.line,
9984
+ column: lineLocation.end.column - (lineContentRange[1] - startIndex)
9985
+ },
9986
+ end: lineLocation.end
9987
+ },
9988
+ fix(fixer) {
9989
+ return fixer.removeRange([startIndex, lineContentRange[1]]);
9990
+ }
9991
+ });
9992
+ }
9993
+ }
9994
+ return { table(node) {
9995
+ verifyTablePipes(node);
9996
+ } };
9997
+ }
9998
+ });
9999
+
10000
+ //#endregion
10001
+ //#region src/rules/table-pipe-alignment.ts
10002
+ var table_pipe_alignment_default = createRule("table-pipe-alignment", {
10003
+ meta: {
10004
+ type: "layout",
10005
+ docs: {
10006
+ description: "enforce consistent alignment of table pipes",
10007
+ categories: ["standard"],
10008
+ listCategory: "Decorative"
10009
+ },
10010
+ fixable: "code",
10011
+ hasSuggestions: false,
10012
+ schema: [{
10013
+ type: "object",
10014
+ properties: { column: { enum: ["minimum", "consistent"] } },
10015
+ additionalProperties: false
10016
+ }],
10017
+ messages: {
10018
+ addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
10019
+ removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
10020
+ }
10021
+ },
10022
+ create(context) {
10023
+ const sourceCode = context.sourceCode;
10024
+ const columnOption = (context.options[0] || {}).column || "minimum";
10025
+ /**
10026
+ * Verify the table pipes
10027
+ */
10028
+ function verifyTablePipes(rows) {
10029
+ let columnCount = 0;
10030
+ for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
10031
+ let targetRows = [...rows];
10032
+ for (let pipeIndex = 0; pipeIndex <= columnCount; pipeIndex++) {
10033
+ const expected = getExpectedPipePosition(rows, pipeIndex);
10034
+ if (expected == null) continue;
10035
+ const unreportedRows = [];
10036
+ for (const row of targetRows) if (verifyRowPipe(row, pipeIndex, expected)) unreportedRows.push(row);
10037
+ targetRows = unreportedRows;
10038
+ if (targetRows.length === 0) break;
10039
+ }
10040
+ }
10041
+ /**
10042
+ * Verify the pipe in the row
10043
+ */
10044
+ function verifyRowPipe(row, pipeIndex, expected) {
10045
+ let cellIndex;
10046
+ let pipe;
10047
+ if (pipeIndex === 0) {
10048
+ cellIndex = 0;
10049
+ pipe = "leadingPipe";
10050
+ } else {
10051
+ cellIndex = pipeIndex - 1;
10052
+ pipe = "trailingPipe";
10053
+ }
10054
+ if (row.cells.length <= cellIndex) return true;
10055
+ const cell = row.cells[cellIndex];
10056
+ const pipeToken = cell[pipe];
10057
+ if (!pipeToken) return true;
10058
+ return verifyPipe(pipeToken, expected, {
10059
+ cell,
10060
+ pipeIndex
10061
+ });
10062
+ }
10063
+ /**
10064
+ * Verify the pipe position
10065
+ */
10066
+ function verifyPipe(pipe, expected, ctx) {
10067
+ const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
10068
+ const diff = expected - actual;
10069
+ if (diff === 0) return true;
10070
+ context.report({
10071
+ loc: pipe.loc,
10072
+ messageId: diff > 0 ? "addSpaces" : "removeSpaces",
8449
10073
  data: {
8450
- expected: delimiter,
8451
- actual: actualDelimiter
10074
+ expected: String(expected),
10075
+ count: String(Math.abs(diff)),
10076
+ plural: Math.abs(diff) === 1 ? "" : "s"
8452
10077
  },
8453
10078
  fix(fixer) {
8454
- return [fixer.replaceTextRange([range[0], range[0] + actualDelimiter.length], delimiter), fixer.replaceTextRange([range[1] - actualDelimiter.length, range[1]], delimiter)];
10079
+ if (diff > 0) {
10080
+ if (ctx.pipeIndex === 0 || ctx.cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
10081
+ return fixer.insertTextAfterRange([ctx.cell.delimiter.range[0], ctx.cell.delimiter.range[0] + 1], "-".repeat(diff));
10082
+ }
10083
+ const baseEdit = fixRemoveSpaces();
10084
+ if (baseEdit) return baseEdit;
10085
+ if (ctx.pipeIndex === 0 || ctx.cell.type === "cell") return null;
10086
+ const beforeDelimiter = sourceCode.lines[ctx.cell.delimiter.loc.start.line - 1].slice(0, ctx.cell.delimiter.loc.start.column - 1);
10087
+ const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
10088
+ const newLength = expected - widthBeforeDelimiter;
10089
+ const minimumDelimiterLength = getMinimumDelimiterLength(ctx.cell.align);
10090
+ const spaceAfter = isNeedSpaceAfterContent(ctx.cell) ? " " : "";
10091
+ if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
10092
+ const delimiterPrefix = ctx.cell.align === "left" || ctx.cell.align === "center" ? ":" : "";
10093
+ const delimiterSuffix = (ctx.cell.align === "right" || ctx.cell.align === "center" ? ":" : "") + spaceAfter;
10094
+ const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
10095
+ return fixer.replaceTextRange([ctx.cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
10096
+ /**
10097
+ * Fixer to remove spaces before the pipe
10098
+ */
10099
+ function fixRemoveSpaces() {
10100
+ const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
10101
+ const trimmedBeforePipe = beforePipe.trimEnd();
10102
+ const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
10103
+ const widthBeforePipe = getTextWidth(trimmedBeforePipe);
10104
+ const newSpacesLength = expected - widthBeforePipe;
10105
+ if (newSpacesLength < (ctx.pipeIndex > 0 && isNeedSpaceAfterContent(ctx.cell) ? 1 : 0)) return null;
10106
+ return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
10107
+ }
8455
10108
  }
8456
10109
  });
10110
+ return false;
10111
+ }
10112
+ /**
10113
+ * Get the expected pipe position for the index
10114
+ */
10115
+ function getExpectedPipePosition(rows, pipeIndex) {
10116
+ if (pipeIndex === 0) {
10117
+ const firstCell = rows[0].cells[0];
10118
+ const firstToken = firstCell.leadingPipe ?? firstCell.content;
10119
+ if (!firstToken) return null;
10120
+ return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
10121
+ }
10122
+ if (columnOption === "minimum") return getMinimumPipePosition(rows, pipeIndex);
10123
+ else if (columnOption === "consistent") {
10124
+ const columnIndex = pipeIndex - 1;
10125
+ for (const row of rows) {
10126
+ if (row.cells.length <= columnIndex) continue;
10127
+ const cell = row.cells[columnIndex];
10128
+ if (cell.type === "delimiter" || !cell.trailingPipe) continue;
10129
+ const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
10130
+ return Math.max(width, getMinimumPipePosition(rows, pipeIndex) || 0);
10131
+ }
10132
+ }
10133
+ return null;
10134
+ }
10135
+ /**
10136
+ * Get the minimum pipe position for the index
10137
+ */
10138
+ function getMinimumPipePosition(rows, pipeIndex) {
10139
+ let maxWidth = 0;
10140
+ const columnIndex = pipeIndex - 1;
10141
+ for (const row of rows) {
10142
+ if (row.cells.length <= columnIndex) continue;
10143
+ const cell = row.cells[columnIndex];
10144
+ let width;
10145
+ if (cell.type === "delimiter") {
10146
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10147
+ width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
10148
+ } else {
10149
+ if (!cell.content) continue;
10150
+ width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
10151
+ }
10152
+ if (isNeedSpaceAfterContent(cell)) width += 1;
10153
+ maxWidth = Math.max(maxWidth, width);
10154
+ }
10155
+ return maxWidth;
10156
+ }
10157
+ /**
10158
+ * Get the minimum delimiter length based on alignment
10159
+ */
10160
+ function getMinimumDelimiterLength(align) {
10161
+ return align === "none" ? 1 : align === "center" ? 3 : 2;
10162
+ }
10163
+ /**
10164
+ * Check if a cell needs a space after its content
10165
+ */
10166
+ function isNeedSpaceAfterContent(cell) {
10167
+ let content;
10168
+ if (cell.type === "delimiter") content = cell.delimiter;
10169
+ else {
10170
+ if (!cell.content) return false;
10171
+ content = cell.content;
10172
+ }
10173
+ return cell.trailingPipe && content.range[1] < cell.trailingPipe.range[0];
10174
+ }
10175
+ /**
10176
+ * Convert a parsed table row to row data
10177
+ */
10178
+ function parsedTableRowToRowData(parsedRow) {
10179
+ return { cells: parsedRow.cells.map((cell, index) => {
10180
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10181
+ return {
10182
+ type: "cell",
10183
+ leadingPipe: cell.leadingPipe,
10184
+ content: cell.cell,
10185
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10186
+ };
10187
+ }) };
10188
+ }
10189
+ /**
10190
+ * Convert a parsed table delimiter row to row data
10191
+ */
10192
+ function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
10193
+ return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
10194
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10195
+ return {
10196
+ type: "delimiter",
10197
+ leadingPipe: cell.leadingPipe,
10198
+ delimiter: cell.delimiter,
10199
+ align: cell.delimiter.align,
10200
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10201
+ };
10202
+ }) };
10203
+ }
10204
+ return { table(node) {
10205
+ const parsed = parseTable(sourceCode, node);
10206
+ if (!parsed) return;
10207
+ const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
10208
+ for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
10209
+ verifyTablePipes(rows);
8457
10210
  } };
8458
10211
  }
8459
10212
  });
8460
10213
 
8461
10214
  //#endregion
8462
- //#region src/rules/table-header-casing.ts
8463
- var table_header_casing_default = createRule("table-header-casing", {
10215
+ //#region src/rules/table-pipe-spacing.ts
10216
+ /**
10217
+ * Parsed options
10218
+ */
10219
+ function parseOptions(options) {
10220
+ const spaceOption = options?.space;
10221
+ const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10222
+ const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10223
+ const cellAlignOption = options?.cellAlign;
10224
+ const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10225
+ const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10226
+ const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10227
+ const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10228
+ return {
10229
+ leadingSpace,
10230
+ trailingSpace,
10231
+ cellAlignByDelimiter: {
10232
+ none: adjustAlign(defaultDelimiterCellAlign),
10233
+ left: adjustAlign(leftAlignmentDelimiterCellAlign),
10234
+ center: adjustAlign(centerAlignmentDelimiterCellAlign),
10235
+ right: adjustAlign(rightAlignmentDelimiterCellAlign)
10236
+ }
10237
+ };
10238
+ /**
10239
+ * Adjust the alignment option based on the spacing options.
10240
+ */
10241
+ function adjustAlign(align) {
10242
+ if (align === "left") {
10243
+ if (trailingSpace === "always") return "left";
10244
+ return "ignore";
10245
+ }
10246
+ if (align === "center") {
10247
+ if (leadingSpace === "always" && trailingSpace === "always") return "center";
10248
+ return "ignore";
10249
+ }
10250
+ if (align === "right") {
10251
+ if (leadingSpace === "always") return "right";
10252
+ return "ignore";
10253
+ }
10254
+ return align;
10255
+ }
10256
+ }
10257
+ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
8464
10258
  meta: {
8465
- type: "suggestion",
8466
- fixable: "code",
10259
+ type: "layout",
8467
10260
  docs: {
8468
- description: "enforce consistent casing in table header cells.",
8469
- categories: [],
8470
- listCategory: "Preference"
10261
+ description: "enforce consistent spacing around table pipes",
10262
+ categories: ["standard"],
10263
+ listCategory: "Whitespace"
8471
10264
  },
10265
+ fixable: "whitespace",
10266
+ hasSuggestions: false,
8472
10267
  schema: [{
8473
10268
  type: "object",
8474
10269
  properties: {
8475
- style: { enum: ["Title Case", "Sentence case"] },
8476
- preserveWords: {
8477
- type: "array",
8478
- items: { type: "string" },
8479
- description: "Words that should be preserved as-is (case-insensitive matching)"
8480
- },
8481
- ignorePatterns: {
8482
- type: "array",
8483
- items: { type: "string" },
8484
- description: "Regular expression patterns for words to ignore during casing checks"
8485
- },
8486
- minorWords: {
8487
- type: "array",
8488
- items: { type: "string" },
8489
- description: "Words that should not be capitalized in Title Case (unless they're the first or last word)"
8490
- }
10270
+ space: { anyOf: [{ enum: ["always", "never"] }, {
10271
+ type: "object",
10272
+ properties: {
10273
+ leading: { enum: ["always", "never"] },
10274
+ trailing: { enum: ["always", "never"] }
10275
+ },
10276
+ additionalProperties: false
10277
+ }] },
10278
+ cellAlign: { anyOf: [{ enum: [
10279
+ "left",
10280
+ "center",
10281
+ "right"
10282
+ ] }, {
10283
+ type: "object",
10284
+ properties: {
10285
+ defaultDelimiter: { enum: [
10286
+ "left",
10287
+ "center",
10288
+ "right",
10289
+ "ignore"
10290
+ ] },
10291
+ leftAlignmentDelimiter: { enum: [
10292
+ "left",
10293
+ "center",
10294
+ "right",
10295
+ "ignore"
10296
+ ] },
10297
+ centerAlignmentDelimiter: { enum: [
10298
+ "left",
10299
+ "center",
10300
+ "right",
10301
+ "ignore"
10302
+ ] },
10303
+ rightAlignmentDelimiter: { enum: [
10304
+ "left",
10305
+ "center",
10306
+ "right",
10307
+ "ignore"
10308
+ ] }
10309
+ },
10310
+ additionalProperties: false
10311
+ }] }
8491
10312
  },
8492
10313
  additionalProperties: false
8493
10314
  }],
8494
10315
  messages: {
8495
- expectedTitleCase: "Expected \"{{actual}}\" to be \"{{expected}}\" (Title Case).",
8496
- expectedTitleCaseMinorWord: "Expected \"{{actual}}\" to be \"{{expected}}\" (Title Case - minor word).",
8497
- expectedSentenceCase: "Expected \"{{actual}}\" to be \"{{expected}}\" (Sentence case).",
8498
- expectedPreserveWord: "Expected \"{{actual}}\" to be \"{{expected}}\" (preserved word)."
10316
+ expectedSpaceBefore: "Expected 1 space before \"|\".",
10317
+ expectedNoSpaceBefore: "Expected no space before \"|\".",
10318
+ expectedSpaceAfter: "Expected 1 space after \"|\".",
10319
+ expectedNoSpaceAfter: "Expected no space after \"|\".",
10320
+ expectedAlignLeft: "Expected 1 space after \"|\" for left-aligned column.",
10321
+ expectedNoSpaceAlignLeft: "Expected no space after \"|\" for left-aligned column.",
10322
+ expectedAlignRight: "Expected 1 space before \"|\" for right-aligned column.",
10323
+ expectedNoSpaceAlignRight: "Expected no space before \"|\" for right-aligned column.",
10324
+ expectedAlignCenter: "Expected the number of spaces before and after the content to be the same or differ by 1 at most for center-aligned column."
8499
10325
  }
8500
10326
  },
8501
10327
  create(context) {
8502
10328
  const sourceCode = context.sourceCode;
8503
- const caseStyle = context.options[0]?.style || "Title Case";
8504
- const preserveWordsOption = parsePreserveWordsOption(context.options[0]?.preserveWords || defaultPreserveWords);
8505
- const minorWords = context.options[0]?.minorWords || defaultMinorWords;
8506
- const ignorePatterns = (context.options[0]?.ignorePatterns || [
8507
- "/^v\\d+/u",
8508
- "/\\w+\\.[a-z\\d]+$/u",
8509
- "/\\w+(?:API|Api)$/u",
8510
- "/\\w+(?:SDK|Sdk)$/u",
8511
- "/\\w+(?:CLI|Cli)$/u"
8512
- ]).map((pattern) => {
8513
- if (isRegExp(pattern)) return toRegExp(pattern);
8514
- try {
8515
- return new RegExp(pattern, "v");
8516
- } catch {}
8517
- try {
8518
- return new RegExp(pattern, "u");
8519
- } catch {}
8520
- return new RegExp(pattern);
8521
- });
10329
+ const options = parseOptions(context.options[0]);
8522
10330
  /**
8523
- * Check text node and report word-level errors
10331
+ * Verify for the leading pipe.
8524
10332
  */
8525
- function checkTextNode(node, firstNode, lastNode) {
8526
- const text = sourceCode.getText(node);
8527
- const wordAndOffsets = parseWordsFromText(text, firstNode, lastNode);
8528
- const processed = /* @__PURE__ */ new Set();
8529
- for (let index = 0; index < wordAndOffsets.length; index++) {
8530
- if (processed.has(index)) continue;
8531
- processed.add(index);
8532
- const wordAndOffset = wordAndOffsets[index];
8533
- if (wordAndOffset.punctuation) continue;
8534
- if (ignorePatterns.some((pattern) => pattern.test(wordAndOffset.word))) continue;
8535
- const preservePhrase = preserveWordsOption.findPreservePhrase((function* () {
8536
- const firstWord = wordAndOffsets[index];
8537
- if (firstWord.punctuation) return;
8538
- yield firstWord.word;
8539
- for (let next = index + 1; next < wordAndOffsets.length; next++) yield wordAndOffsets[next].word;
8540
- })());
8541
- if (preservePhrase) {
8542
- for (let wordIndex = 0; wordIndex < preservePhrase.length; wordIndex++) {
8543
- processed.add(index + wordIndex);
8544
- verifyWord(wordAndOffsets[index + wordIndex], preservePhrase[wordIndex], "preserved");
10333
+ function verifyLeadingPipe(pipe, nextToken) {
10334
+ if (options.leadingSpace === "always") {
10335
+ if (pipe.range[1] < nextToken.range[0]) return true;
10336
+ context.report({
10337
+ loc: pipe.loc,
10338
+ messageId: "expectedSpaceAfter",
10339
+ fix(fixer) {
10340
+ return fixer.insertTextAfterRange(pipe.range, " ");
8545
10341
  }
8546
- continue;
8547
- }
8548
- const preserveWordList = preserveWordsOption.findPreserveWord(wordAndOffset.word);
8549
- if (preserveWordList) {
8550
- if (!preserveWordList.some((w) => w === wordAndOffset.word)) verifyWord(wordAndOffset, preserveWordList[0], "preserved");
8551
- continue;
8552
- }
8553
- const expectedWordResult = convertWordCasing(wordAndOffset, {
8554
- caseStyle,
8555
- minorWords
8556
10342
  });
8557
- verifyWord(wordAndOffset, expectedWordResult.word, expectedWordResult.isMinorWord ? "minor" : "normal");
10343
+ return false;
10344
+ } else if (options.leadingSpace === "never") {
10345
+ if (pipe.range[1] === nextToken.range[0]) return true;
10346
+ context.report({
10347
+ loc: {
10348
+ start: pipe.loc.end,
10349
+ end: nextToken.loc.start
10350
+ },
10351
+ messageId: "expectedNoSpaceAfter",
10352
+ fix(fixer) {
10353
+ return fixer.removeRange([pipe.range[1], nextToken.range[0]]);
10354
+ }
10355
+ });
10356
+ return false;
8558
10357
  }
8559
- /**
8560
- * Verify a single word against the expected casing
8561
- */
8562
- function verifyWord(wordAndOffset, expectedWord, wordType = "normal") {
8563
- const { word, offset } = wordAndOffset;
8564
- if (word === expectedWord) return;
8565
- const [nodeStart] = sourceCode.getRange(node);
8566
- const range = [nodeStart + offset, nodeStart + offset + word.length];
10358
+ return true;
10359
+ }
10360
+ /**
10361
+ * Verify for the trailing pipe.
10362
+ */
10363
+ function verifyTrailingPipe(prevToken, pipe) {
10364
+ if (options.trailingSpace === "always") {
10365
+ if (prevToken.range[1] < pipe.range[0]) return true;
8567
10366
  context.report({
8568
- node,
8569
- messageId: wordType === "preserved" ? "expectedPreserveWord" : caseStyle === "Title Case" ? wordType === "minor" ? "expectedTitleCaseMinorWord" : "expectedTitleCase" : "expectedSentenceCase",
8570
- data: {
8571
- actual: word,
8572
- expected: expectedWord
10367
+ loc: pipe.loc,
10368
+ messageId: "expectedSpaceBefore",
10369
+ fix(fixer) {
10370
+ return fixer.insertTextBeforeRange(pipe.range, " ");
10371
+ }
10372
+ });
10373
+ return false;
10374
+ } else if (options.trailingSpace === "never") {
10375
+ if (prevToken.range[1] === pipe.range[0]) return true;
10376
+ context.report({
10377
+ loc: {
10378
+ start: prevToken.loc.end,
10379
+ end: pipe.loc.start
8573
10380
  },
8574
- loc: getSourceLocationFromRange(sourceCode, node, range),
10381
+ messageId: "expectedNoSpaceBefore",
8575
10382
  fix(fixer) {
8576
- return fixer.replaceTextRange(range, expectedWord);
10383
+ return fixer.removeRange([prevToken.range[1], pipe.range[0]]);
8577
10384
  }
8578
10385
  });
10386
+ return false;
8579
10387
  }
10388
+ return true;
8580
10389
  }
8581
- return { table(node) {
8582
- if (!node.children.length) return;
8583
- const headerRow = node.children[0];
8584
- if (headerRow.type !== "tableRow") return;
8585
- for (const cell of headerRow.children) {
8586
- const children = cell.children.filter((child) => child.type !== "text" || child.value.trim());
8587
- children.forEach((child, i) => {
8588
- if (child.type === "text") checkTextNode(child, i === 0, i === children.length - 1);
10390
+ /**
10391
+ * Verify for the alignment of the pipe according to the delimiter alignment.
10392
+ * Return "leading" if the leading pipe is reported, "trailing" if the trailing pipe is reported, "both" if both are reported, or null if nothing is reported.
10393
+ */
10394
+ function verifyAlignPipe({ leadingPipe, content, trailingPipe }, cellAlign) {
10395
+ if (!leadingPipe || !trailingPipe || !content) return null;
10396
+ const lineText = sourceCode.lines[leadingPipe.loc.start.line - 1];
10397
+ if (cellAlign === "left") {
10398
+ const expectedWidth = options.leadingSpace === "always" ? 1 : 0;
10399
+ if (getLeadingSpacesWidth() === expectedWidth) return null;
10400
+ context.report({
10401
+ loc: leadingPipe.range[1] < content.range[0] ? {
10402
+ start: leadingPipe.loc.end,
10403
+ end: content.loc.start
10404
+ } : leadingPipe.loc,
10405
+ messageId: expectedWidth >= 1 ? "expectedAlignLeft" : "expectedNoSpaceAlignLeft",
10406
+ *fix(fixer) {
10407
+ const cellWidth = getCellWidth();
10408
+ const contentTextWidth = getContentTextWidth();
10409
+ const newLeadingSpaces = " ".repeat(expectedWidth);
10410
+ const newTrailingSpaces = " ".repeat(Math.max(cellWidth - contentTextWidth - expectedWidth, 0));
10411
+ const contentText = getNormalizedContentText();
10412
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10413
+ }
10414
+ });
10415
+ return "leading";
10416
+ } else if (cellAlign === "right") {
10417
+ const expectedWidth = options.trailingSpace === "always" ? 1 : 0;
10418
+ if (getTrailingSpacesWidth() === expectedWidth) return null;
10419
+ context.report({
10420
+ loc: content.range[1] < trailingPipe.range[0] ? {
10421
+ start: content.loc.end,
10422
+ end: trailingPipe.loc.start
10423
+ } : trailingPipe.loc,
10424
+ messageId: expectedWidth >= 1 ? "expectedAlignRight" : "expectedNoSpaceAlignRight",
10425
+ *fix(fixer) {
10426
+ const cellWidth = getCellWidth();
10427
+ const contentTextWidth = getContentTextWidth();
10428
+ const newLeadingSpaces = " ".repeat(Math.max(cellWidth - contentTextWidth - expectedWidth, 0));
10429
+ const newTrailingSpaces = " ".repeat(expectedWidth);
10430
+ const contentText = getNormalizedContentText();
10431
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10432
+ }
10433
+ });
10434
+ return "trailing";
10435
+ } else if (cellAlign === "center") {
10436
+ const leadingSpacesWidth = getLeadingSpacesWidth();
10437
+ const trailingSpacesWidth = getTrailingSpacesWidth();
10438
+ if (leadingSpacesWidth === trailingSpacesWidth || leadingSpacesWidth + 1 === trailingSpacesWidth) return null;
10439
+ const leadingReportLoc = leadingPipe.range[1] < content.range[0] ? {
10440
+ start: leadingPipe.loc.end,
10441
+ end: content.loc.start
10442
+ } : leadingPipe.loc;
10443
+ const trailingReportLoc = content.range[1] < trailingPipe.range[0] ? {
10444
+ start: content.loc.end,
10445
+ end: trailingPipe.loc.start
10446
+ } : trailingPipe.loc;
10447
+ for (const reportLoc of [leadingReportLoc, trailingReportLoc]) context.report({
10448
+ loc: reportLoc,
10449
+ messageId: "expectedAlignCenter",
10450
+ *fix(fixer) {
10451
+ const cellWidth = getCellWidth();
10452
+ const contentTextWidth = getContentTextWidth();
10453
+ const spacesLength = cellWidth - contentTextWidth;
10454
+ const leadingSpacesLength = Math.floor(spacesLength / 2);
10455
+ const trailingSpacesLength = spacesLength - leadingSpacesLength;
10456
+ const newLeadingSpaces = " ".repeat(leadingSpacesLength);
10457
+ const newTrailingSpaces = " ".repeat(trailingSpacesLength);
10458
+ const contentText = getNormalizedContentText();
10459
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10460
+ }
8589
10461
  });
10462
+ return "both";
10463
+ }
10464
+ return null;
10465
+ /**
10466
+ * Get the width of the leading spaces in the cell.
10467
+ */
10468
+ function getLeadingSpacesWidth() {
10469
+ return getTextWidth(lineText, leadingPipe.loc.end.column - 1, content.loc.start.column - 1);
10470
+ }
10471
+ /**
10472
+ * Get the width of the trailing spaces in the cell.
10473
+ */
10474
+ function getTrailingSpacesWidth() {
10475
+ return getTextWidth(lineText, content.loc.end.column - 1, trailingPipe.loc.start.column - 1);
10476
+ }
10477
+ /**
10478
+ * Get the width of the whole cell (including leading and trailing spaces)
10479
+ */
10480
+ function getCellWidth() {
10481
+ return getTextWidth(lineText, leadingPipe.loc.end.column - 1, trailingPipe.loc.start.column - 1);
10482
+ }
10483
+ /**
10484
+ * Get the width of the content text (excluding leading and trailing spaces)
10485
+ */
10486
+ function getContentTextWidth() {
10487
+ return getTextWidth(lineText, content.loc.start.column - 1, content.loc.end.column - 1);
10488
+ }
10489
+ /**
10490
+ * Get the normalized content text (with normalized spaces)
10491
+ */
10492
+ function getNormalizedContentText() {
10493
+ const prefixWidth = getWidth(lineText.slice(0, content.loc.start.column - 1));
10494
+ let result = "";
10495
+ for (const c of lineText.slice(content.loc.start.column - 1, content.loc.end.column - 1)) if (c === " ") result += " ".repeat(4 - (prefixWidth + result.length) % 4);
10496
+ else result += c;
10497
+ return result;
10498
+ }
10499
+ }
10500
+ /**
10501
+ * Convert a parsed table row to cell data list
10502
+ */
10503
+ function parsedTableRowToCellDataList(parsedRow) {
10504
+ return parsedRow.cells.map((cell, index) => {
10505
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10506
+ return {
10507
+ type: "cell",
10508
+ leadingPipe: cell.leadingPipe,
10509
+ content: cell.cell,
10510
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10511
+ };
10512
+ });
10513
+ }
10514
+ /**
10515
+ * Convert a parsed table delimiter row to delimiter data list
10516
+ */
10517
+ function parsedTableDelimiterRowToDelimiterDataList(parsedDelimiterRow) {
10518
+ return parsedDelimiterRow.delimiters.map((cell, index) => {
10519
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10520
+ return {
10521
+ type: "delimiter",
10522
+ leadingPipe: cell.leadingPipe,
10523
+ content: cell.delimiter,
10524
+ align: cell.delimiter.align,
10525
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10526
+ };
10527
+ });
10528
+ }
10529
+ return { table(node) {
10530
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
10531
+ const delimiters = parsedDelimiterRow && parsedTableDelimiterRowToDelimiterDataList(parsedDelimiterRow);
10532
+ for (const row of node.children) {
10533
+ const parsedRow = parseTableRow(sourceCode, row);
10534
+ if (!parsedRow) continue;
10535
+ const cells = parsedTableRowToCellDataList(parsedRow);
10536
+ for (let columnIndex = 0; columnIndex < cells.length; columnIndex++) {
10537
+ const cell = cells[columnIndex];
10538
+ const delimiter = delimiters && columnIndex < delimiters.length ? delimiters[columnIndex] : null;
10539
+ const alignReportedPoint = delimiter ? verifyAlignPipe(cell, options.cellAlignByDelimiter[delimiter.align]) : null;
10540
+ if (alignReportedPoint === "both") continue;
10541
+ if (cell.leadingPipe && alignReportedPoint !== "leading") {
10542
+ if (options.leadingSpace !== "never" || cell.content) {
10543
+ const nextToken = getNextToken(cells, columnIndex);
10544
+ if (nextToken) verifyLeadingPipe(cell.leadingPipe, nextToken);
10545
+ }
10546
+ }
10547
+ if (cell.trailingPipe && options.trailingSpace !== "never" && alignReportedPoint !== "trailing") {
10548
+ const prevToken = getPrevToken(cells, columnIndex);
10549
+ if (prevToken) verifyTrailingPipe(prevToken, cell.trailingPipe);
10550
+ }
10551
+ }
10552
+ }
10553
+ if (!delimiters) return;
10554
+ for (let columnIndex = 0; columnIndex < delimiters.length; columnIndex++) {
10555
+ const delimiter = delimiters[columnIndex];
10556
+ const alignReportedPoint = verifyAlignPipe(delimiter, options.cellAlignByDelimiter[delimiter.align]);
10557
+ if (alignReportedPoint === "both") continue;
10558
+ if (delimiter.leadingPipe && alignReportedPoint !== "leading") verifyLeadingPipe(delimiter.leadingPipe, delimiter.content);
10559
+ if (delimiter.trailingPipe && alignReportedPoint !== "trailing") verifyTrailingPipe(delimiter.content, delimiter.trailingPipe);
8590
10560
  }
8591
10561
  } };
10562
+ /**
10563
+ * Get the next token (pipe or cell) after the given column index.
10564
+ */
10565
+ function getNextToken(cells, columnIndex) {
10566
+ for (let i = columnIndex; i < cells.length; i++) {
10567
+ const cell = cells[i];
10568
+ const token = cell.content ?? cell.trailingPipe;
10569
+ if (token) return token;
10570
+ }
10571
+ return null;
10572
+ }
10573
+ /**
10574
+ * Get the prev token (pipe or cell) after the given column index.
10575
+ */
10576
+ function getPrevToken(cells, columnIndex) {
10577
+ for (let i = columnIndex; i >= 0; i--) {
10578
+ const cell = cells[i];
10579
+ const token = cell.content ?? cell.leadingPipe;
10580
+ if (token) return token;
10581
+ }
10582
+ return null;
10583
+ }
8592
10584
  }
8593
10585
  });
8594
10586
 
@@ -8600,7 +10592,7 @@ var thematic_break_character_style_default = createRule("thematic-break-characte
8600
10592
  docs: {
8601
10593
  description: "enforce consistent character style for thematic breaks (horizontal rules) in Markdown.",
8602
10594
  categories: ["standard"],
8603
- listCategory: "Stylistic"
10595
+ listCategory: "Notation"
8604
10596
  },
8605
10597
  fixable: "code",
8606
10598
  hasSuggestions: false,
@@ -8679,7 +10671,7 @@ var thematic_break_length_default = createRule("thematic-break-length", {
8679
10671
  docs: {
8680
10672
  description: "enforce consistent length for thematic breaks (horizontal rules) in Markdown.",
8681
10673
  categories: ["standard"],
8682
- listCategory: "Stylistic"
10674
+ listCategory: "Decorative"
8683
10675
  },
8684
10676
  fixable: "code",
8685
10677
  hasSuggestions: false,
@@ -8745,7 +10737,7 @@ var thematic_break_sequence_pattern_default = createRule("thematic-break-sequenc
8745
10737
  docs: {
8746
10738
  description: "enforce consistent repeating patterns for thematic breaks (horizontal rules) in Markdown.",
8747
10739
  categories: ["standard"],
8748
- listCategory: "Stylistic"
10740
+ listCategory: "Decorative"
8749
10741
  },
8750
10742
  fixable: "code",
8751
10743
  hasSuggestions: false,
@@ -8814,6 +10806,7 @@ const rules$1 = [
8814
10806
  emphasis_delimiters_style_default,
8815
10807
  hard_linebreak_style_default,
8816
10808
  heading_casing_default,
10809
+ indent_default,
8817
10810
  level1_heading_style_default,
8818
10811
  level2_heading_style_default,
8819
10812
  link_bracket_newline_default,
@@ -8841,6 +10834,9 @@ const rules$1 = [
8841
10834
  sort_definitions_default,
8842
10835
  strikethrough_delimiters_style_default,
8843
10836
  table_header_casing_default,
10837
+ table_leading_trailing_pipes_default,
10838
+ table_pipe_alignment_default,
10839
+ table_pipe_spacing_default,
8844
10840
  thematic_break_character_style_default,
8845
10841
  thematic_break_length_default,
8846
10842
  thematic_break_sequence_pattern_default
@@ -8848,8 +10844,7 @@ const rules$1 = [
8848
10844
 
8849
10845
  //#endregion
8850
10846
  //#region src/configs/recommended.ts
8851
- var recommended_exports = {};
8852
- __export(recommended_exports, {
10847
+ var recommended_exports = __export({
8853
10848
  files: () => files$1,
8854
10849
  language: () => language$1,
8855
10850
  languageOptions: () => languageOptions$1,
@@ -8879,8 +10874,7 @@ const rules$3 = {
8879
10874
 
8880
10875
  //#endregion
8881
10876
  //#region src/configs/standard.ts
8882
- var standard_exports = {};
8883
- __export(standard_exports, {
10877
+ var standard_exports = __export({
8884
10878
  files: () => files,
8885
10879
  language: () => language,
8886
10880
  languageOptions: () => languageOptions,
@@ -8907,6 +10901,7 @@ const rules$2 = {
8907
10901
  "markdown-preferences/code-fence-style": "error",
8908
10902
  "markdown-preferences/emphasis-delimiters-style": "error",
8909
10903
  "markdown-preferences/hard-linebreak-style": "error",
10904
+ "markdown-preferences/indent": "error",
8910
10905
  "markdown-preferences/level1-heading-style": "error",
8911
10906
  "markdown-preferences/level2-heading-style": "error",
8912
10907
  "markdown-preferences/link-bracket-newline": "error",
@@ -8930,6 +10925,9 @@ const rules$2 = {
8930
10925
  "markdown-preferences/setext-heading-underline-length": "error",
8931
10926
  "markdown-preferences/sort-definitions": "error",
8932
10927
  "markdown-preferences/strikethrough-delimiters-style": "error",
10928
+ "markdown-preferences/table-leading-trailing-pipes": "error",
10929
+ "markdown-preferences/table-pipe-alignment": "error",
10930
+ "markdown-preferences/table-pipe-spacing": "error",
8933
10931
  "markdown-preferences/thematic-break-character-style": "error",
8934
10932
  "markdown-preferences/thematic-break-length": "error",
8935
10933
  "markdown-preferences/thematic-break-sequence-pattern": "error"
@@ -8937,13 +10935,12 @@ const rules$2 = {
8937
10935
 
8938
10936
  //#endregion
8939
10937
  //#region src/meta.ts
8940
- var meta_exports = {};
8941
- __export(meta_exports, {
10938
+ var meta_exports = __export({
8942
10939
  name: () => name,
8943
10940
  version: () => version
8944
10941
  });
8945
10942
  const name = "eslint-plugin-markdown-preferences";
8946
- const version = "0.23.0";
10943
+ const version = "0.25.0";
8947
10944
 
8948
10945
  //#endregion
8949
10946
  //#region src/index.ts