eslint-plugin-markdown-preferences 0.22.0 → 0.24.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";
@@ -186,47 +186,48 @@ function parseATXHeading(sourceCode, node) {
186
186
  }
187
187
  }
188
188
  };
189
- const spaceAfterOpening = {
190
- text: parsedOpening.rawAfter,
191
- range: [openingSequence.range[1], openingSequence.range[1] + parsedOpening.rawAfter.length],
192
- loc: {
193
- start: openingSequence.loc.end,
194
- end: {
195
- line: openingSequence.loc.end.line,
196
- column: openingSequence.loc.end.column + parsedOpening.rawAfter.length
197
- }
198
- }
189
+ const contentLocStart = {
190
+ line: openingSequence.loc.end.line,
191
+ column: openingSequence.loc.end.column + parsedOpening.after.length
199
192
  };
200
193
  const parsedClosing = parseATXHeadingClosingSequenceFromText(text);
201
194
  if (parsedClosing == null) {
202
- const textAfterOpening = sourceCode.text.slice(spaceAfterOpening.range[1], range[1]);
195
+ const textAfterOpening = sourceCode.text.slice(openingSequence.range[1] + parsedOpening.after.length, range[1]);
203
196
  const contentText$1 = textAfterOpening.trimEnd();
197
+ const contentRange$1 = [openingSequence.range[1] + parsedOpening.after.length, openingSequence.range[1] + parsedOpening.after.length + contentText$1.length];
198
+ const contentLocEnd = {
199
+ line: loc.end.line,
200
+ column: loc.end.column - (textAfterOpening.length - contentText$1.length)
201
+ };
202
+ const after = contentText$1 === textAfterOpening ? null : {
203
+ text: textAfterOpening.slice(contentText$1.length),
204
+ range: [contentRange$1[1], range[1]],
205
+ loc: {
206
+ start: contentLocEnd,
207
+ end: loc.end
208
+ }
209
+ };
204
210
  return {
205
- openingSequence: {
206
- ...openingSequence,
207
- raws: { spaceAfter: spaceAfterOpening }
208
- },
211
+ openingSequence,
209
212
  content: {
210
213
  text: contentText$1,
211
- range: [spaceAfterOpening.range[1], spaceAfterOpening.range[1] + contentText$1.length],
214
+ range: contentRange$1,
212
215
  loc: {
213
- start: spaceAfterOpening.loc.end,
214
- end: {
215
- line: loc.end.line,
216
- column: loc.end.column - (textAfterOpening.length - contentText$1.length)
217
- }
216
+ start: contentLocStart,
217
+ end: contentLocEnd
218
218
  }
219
219
  },
220
- closingSequence: null
220
+ closingSequence: null,
221
+ after
221
222
  };
222
223
  }
223
224
  const spaceAfterClosing = {
224
- text: parsedClosing.rawAfter,
225
- range: [range[1] - parsedClosing.rawAfter.length, range[1]],
225
+ text: parsedClosing.after,
226
+ range: [range[1] - parsedClosing.after.length, range[1]],
226
227
  loc: {
227
228
  start: {
228
229
  line: loc.end.line,
229
- column: loc.end.column - parsedClosing.rawAfter.length
230
+ column: loc.end.column - parsedClosing.after.length
230
231
  },
231
232
  end: loc.end
232
233
  }
@@ -242,38 +243,23 @@ function parseATXHeading(sourceCode, node) {
242
243
  end: spaceAfterClosing.loc.start
243
244
  }
244
245
  };
245
- const spaceBeforeClosing = {
246
- text: parsedClosing.rawBefore,
247
- range: [closingSequence.range[0] - parsedClosing.rawBefore.length, closingSequence.range[0]],
248
- loc: {
249
- start: {
250
- line: closingSequence.loc.start.line,
251
- column: closingSequence.loc.start.column - parsedClosing.rawBefore.length
252
- },
253
- end: closingSequence.loc.start
254
- }
255
- };
256
- const contentText = sourceCode.text.slice(spaceAfterOpening.range[1], spaceBeforeClosing.range[0]);
246
+ const contentRange = [openingSequence.range[1] + parsedOpening.after.length, closingSequence.range[0] - parsedClosing.before.length];
247
+ const contentText = sourceCode.text.slice(...contentRange);
257
248
  return {
258
- openingSequence: {
259
- ...openingSequence,
260
- raws: { spaceAfter: spaceAfterOpening }
261
- },
249
+ openingSequence,
262
250
  content: {
263
251
  text: contentText,
264
- range: [spaceAfterOpening.range[1], spaceBeforeClosing.range[0]],
252
+ range: contentRange,
265
253
  loc: {
266
- start: spaceAfterOpening.loc.end,
267
- end: spaceBeforeClosing.loc.start
254
+ start: contentLocStart,
255
+ end: {
256
+ line: closingSequence.loc.start.line,
257
+ column: closingSequence.loc.start.column - parsedClosing.before.length
258
+ }
268
259
  }
269
260
  },
270
- closingSequence: {
271
- ...closingSequence,
272
- raws: {
273
- spaceBefore: spaceBeforeClosing,
274
- spaceAfter: spaceAfterClosing
275
- }
276
- }
261
+ closingSequence,
262
+ after: spaceAfterClosing.range[0] < spaceAfterClosing.range[1] ? spaceAfterClosing : null
277
263
  };
278
264
  }
279
265
  /**
@@ -287,7 +273,7 @@ function parseATXHeadingOpeningSequenceFromText(text) {
287
273
  if (afterOffset === openingSequenceAfterOffset || afterOffset >= text.length) return null;
288
274
  return {
289
275
  openingSequence: text.slice(0, openingSequenceAfterOffset),
290
- rawAfter: text.slice(openingSequenceAfterOffset, afterOffset)
276
+ after: text.slice(openingSequenceAfterOffset, afterOffset)
291
277
  };
292
278
  /**
293
279
  * Skip whitespace characters at the start of the text.
@@ -310,9 +296,9 @@ function parseATXHeadingClosingSequenceFromText(text) {
310
296
  const beforeOffset = skipEndWhitespace(closingSequenceBeforeOffset);
311
297
  if (beforeOffset === closingSequenceBeforeOffset || beforeOffset < 0) return null;
312
298
  return {
313
- rawBefore: text.slice(beforeOffset + 1, closingSequenceBeforeOffset + 1),
299
+ before: text.slice(beforeOffset + 1, closingSequenceBeforeOffset + 1),
314
300
  closingSequence: text.slice(closingSequenceBeforeOffset + 1, trimmedEndOffset),
315
- rawAfter: text.slice(trimmedEndOffset)
301
+ after: text.slice(trimmedEndOffset)
316
302
  };
317
303
  /**
318
304
  * Skip whitespace characters at the end of the text.
@@ -577,16 +563,16 @@ var atx_heading_closing_sequence_default = createRule("atx-heading-closing-seque
577
563
  context.report({
578
564
  node,
579
565
  loc: {
580
- start: parsed.closingSequence.raws.spaceBefore.loc.start,
566
+ start: parsed.content.loc.end,
581
567
  end: parsed.closingSequence.loc.end
582
568
  },
583
569
  messageId: "forbidClosing",
584
570
  *fix(fixer) {
585
- const removeRange = [parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.range[1]];
571
+ const removeRange = [parsed.content.range[1], parsed.closingSequence.range[1]];
586
572
  const newHeadingText = sourceCode.text.slice(sourceCode.getRange(node)[0], removeRange[0]);
587
573
  const newHeadingParsed = parseATXHeadingClosingSequenceFromText(newHeadingText);
588
574
  if (newHeadingParsed) {
589
- const escapeIndex = removeRange[0] - newHeadingParsed.rawAfter.length - 1;
575
+ const escapeIndex = removeRange[0] - newHeadingParsed.after.length - 1;
590
576
  yield fixer.insertTextBeforeRange([escapeIndex, escapeIndex], "\\");
591
577
  }
592
578
  yield fixer.removeRange(removeRange);
@@ -614,12 +600,28 @@ function getBlockquoteLevelFromLine(sourceCode, lineNumber) {
614
600
  const lineText = sourceCode.lines[lineNumber - 1];
615
601
  let prefix = "";
616
602
  let level = 0;
603
+ let width = 0;
604
+ let leadingMarkerOffset = 0;
617
605
  const blockquoteMarkers = /* @__PURE__ */ new Map();
618
606
  for (const c of lineText) {
619
607
  if (c === ">") {
608
+ if (width - leadingMarkerOffset > 3) break;
609
+ leadingMarkerOffset = width + 1;
620
610
  level++;
621
- blockquoteMarkers.set(level, { index: prefix.length });
622
- } else if (c.trim()) break;
611
+ blockquoteMarkers.set(level, { loc: {
612
+ start: {
613
+ line: lineNumber,
614
+ column: prefix.length + 1
615
+ },
616
+ end: {
617
+ line: lineNumber,
618
+ column: prefix.length + 2
619
+ }
620
+ } });
621
+ } else if (!isSpaceOrTab(c)) break;
622
+ if (c === " ") width += 4 - width % 4;
623
+ else width++;
624
+ if (c !== ">" && prefix.at(-1) === ">") leadingMarkerOffset++;
623
625
  prefix += c;
624
626
  }
625
627
  const result = {
@@ -632,6 +634,64 @@ function getBlockquoteLevelFromLine(sourceCode, lineNumber) {
632
634
  return result;
633
635
  }
634
636
 
637
+ //#endregion
638
+ //#region src/utils/width.ts
639
+ /**
640
+ * Get the visual width of the string.
641
+ */
642
+ function getWidth(str) {
643
+ let width = 0;
644
+ for (const c of str) if (c === " ") width += 4 - width % 4;
645
+ else width++;
646
+ return width;
647
+ }
648
+ /**
649
+ * Get a slice of the string by visual width.
650
+ */
651
+ function sliceWidth(str, start, end) {
652
+ const buffer = [...str];
653
+ let width = 0;
654
+ let c;
655
+ while (c = buffer.shift()) {
656
+ if (start <= width) {
657
+ buffer.unshift(c);
658
+ break;
659
+ }
660
+ if (c === " ") width += 4 - width % 4;
661
+ else width++;
662
+ }
663
+ if (buffer.length === 0) return "";
664
+ let result = " ".repeat(width - start);
665
+ if (end == null) return `${result}${buffer.join("")}`;
666
+ while (c = buffer.shift()) {
667
+ let newWidth;
668
+ if (c === " ") newWidth = width + 4 - width % 4;
669
+ else newWidth = width + 1;
670
+ if (end < newWidth) {
671
+ buffer.unshift(c);
672
+ break;
673
+ }
674
+ result += c;
675
+ width = newWidth;
676
+ }
677
+ if (buffer.length === 0) return result;
678
+ result += " ".repeat(end - width);
679
+ return result;
680
+ }
681
+ /**
682
+ * Get the character at the visual width.
683
+ */
684
+ function atWidth(str, target) {
685
+ let width = 0;
686
+ for (const c of str) {
687
+ if (target === width) return c;
688
+ if (target < width) return " ";
689
+ if (c === " ") width += 4 - width % 4;
690
+ else width++;
691
+ }
692
+ return null;
693
+ }
694
+
635
695
  //#endregion
636
696
  //#region src/rules/blockquote-marker-alignment.ts
637
697
  var blockquote_marker_alignment_default = createRule("blockquote-marker-alignment", {
@@ -669,38 +729,33 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
669
729
  const endLine = loc.end.line;
670
730
  const base = getBlockquoteLevelFromLine(sourceCode, startLine).blockquoteMarkers.get(blockquoteLevel);
671
731
  if (!base) return;
732
+ const baseBeforeMarker = sourceCode.lines[startLine - 1].slice(0, base.loc.start.column - 1);
733
+ const baseIndentWidth = getWidth(baseBeforeMarker);
672
734
  for (let lineNumber = startLine + 1; lineNumber <= endLine; lineNumber++) {
673
735
  const marker = getBlockquoteLevelFromLine(sourceCode, lineNumber).blockquoteMarkers.get(blockquoteLevel);
674
736
  if (!marker) continue;
675
- if (base.index === marker.index) continue;
737
+ const indentWidth = getWidth(sourceCode.lines[lineNumber - 1].slice(0, marker.loc.start.column - 1));
738
+ if (baseIndentWidth === indentWidth) continue;
676
739
  blockquoteStack.reported = true;
677
740
  context.report({
678
741
  node,
679
- loc: {
680
- start: {
681
- line: lineNumber,
682
- column: marker.index + 1
683
- },
684
- end: {
685
- line: lineNumber,
686
- column: marker.index + 2
687
- }
688
- },
742
+ loc: marker.loc,
689
743
  messageId: "inconsistentAlignment",
690
744
  fix(fixer) {
691
745
  const line = getParsedLines(sourceCode).get(lineNumber);
692
- if (marker.index < base.index) {
693
- const addSpaces = " ".repeat(base.index - marker.index);
694
- return fixer.insertTextBeforeRange([line.range[0] + marker.index, line.range[0] + marker.index], addSpaces);
746
+ if (indentWidth < baseIndentWidth) {
747
+ const addSpaces = " ".repeat(baseIndentWidth - indentWidth);
748
+ return fixer.insertTextBeforeRange([line.range[0] + marker.loc.start.column - 1, line.range[0] + marker.loc.start.column - 1], addSpaces);
695
749
  }
696
- if (blockquoteLevel === 1) {
697
- const expectedSpaces = " ".repeat(base.index);
698
- return fixer.replaceTextRange([line.range[0], line.range[0] + marker.index], expectedSpaces);
750
+ let newBeforeMarker = line.text.slice(0, marker.loc.start.column - 1);
751
+ while (getWidth(newBeforeMarker) > baseIndentWidth) {
752
+ const last = newBeforeMarker.at(-1);
753
+ if (last && isWhitespace(last)) newBeforeMarker = newBeforeMarker.slice(0, -1);
754
+ else return null;
699
755
  }
700
- if (line.text.slice(0, line.range[0] + marker.index).includes(" ")) return null;
701
- let removeStartIndex = marker.index;
702
- for (; removeStartIndex > base.index; removeStartIndex--) if (line.text[removeStartIndex - 1] !== " ") break;
703
- return fixer.removeRange([line.range[0] + removeStartIndex, line.range[0] + marker.index]);
756
+ if (getWidth(newBeforeMarker) < baseIndentWidth) newBeforeMarker += " ".repeat(baseIndentWidth - getWidth(newBeforeMarker));
757
+ if (!baseBeforeMarker.includes(">") || baseBeforeMarker === newBeforeMarker) return fixer.replaceTextRange([line.range[0], line.range[0] + marker.loc.start.column - 1], newBeforeMarker);
758
+ return null;
704
759
  }
705
760
  });
706
761
  }
@@ -728,7 +783,7 @@ function getOtherMarker(unavailableMarker) {
728
783
  /**
729
784
  * Parse rule options.
730
785
  */
731
- function parseOptions$2(options) {
786
+ function parseOptions$3(options) {
732
787
  const primary = options.primary || "-";
733
788
  const secondary = options.secondary || getOtherMarker(primary);
734
789
  if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
@@ -796,7 +851,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
796
851
  },
797
852
  create(context) {
798
853
  const sourceCode = context.sourceCode;
799
- const options = parseOptions$2(context.options[0] || {});
854
+ const options = parseOptions$3(context.options[0] || {});
800
855
  let containerStack = {
801
856
  node: sourceCode.ast,
802
857
  level: 1,
@@ -4348,395 +4403,341 @@ var heading_casing_default = createRule("heading-casing", {
4348
4403
  });
4349
4404
 
4350
4405
  //#endregion
4351
- //#region src/utils/setext-heading.ts
4406
+ //#region src/utils/character-cursor.ts
4407
+ var CharacterCursor = class {
4408
+ text;
4409
+ constructor(text) {
4410
+ this.text = text;
4411
+ }
4412
+ curr() {
4413
+ return this.text[this.index];
4414
+ }
4415
+ currIndex() {
4416
+ return this.index;
4417
+ }
4418
+ setCurrIndex(index) {
4419
+ this.index = index;
4420
+ }
4421
+ isWhitespace(index) {
4422
+ if (index >= this.text.length) return false;
4423
+ const ch = this.text[index];
4424
+ if (isWhitespace(ch)) return true;
4425
+ if (ch !== ">") return false;
4426
+ const prefix = [ch];
4427
+ for (let prev = index - 1; prev >= 0; prev--) {
4428
+ const prevCh$1 = this.text[prev];
4429
+ if (prevCh$1 === "\n") break;
4430
+ if (isSpaceOrTab(prevCh$1) || prevCh$1 === ">") {
4431
+ prefix.unshift(prevCh$1);
4432
+ continue;
4433
+ }
4434
+ return false;
4435
+ }
4436
+ let width = 0;
4437
+ let leadingMarkerOffset = 0;
4438
+ let prevCh;
4439
+ for (const currCh of prefix) {
4440
+ if (currCh === ">") {
4441
+ if (width - leadingMarkerOffset > 3) return false;
4442
+ leadingMarkerOffset = width + 1;
4443
+ }
4444
+ if (currCh === " ") width += 4 - width % 4;
4445
+ else width++;
4446
+ if (prevCh === ">" && isSpaceOrTab(currCh)) leadingMarkerOffset++;
4447
+ prevCh = currCh;
4448
+ }
4449
+ return true;
4450
+ }
4451
+ };
4452
+ var ForwardCharacterCursor = class extends CharacterCursor {
4453
+ index;
4454
+ constructor(text) {
4455
+ super(text);
4456
+ this.index = 0;
4457
+ }
4458
+ next() {
4459
+ this.index++;
4460
+ return this.text[this.index];
4461
+ }
4462
+ finished() {
4463
+ return this.index >= this.text.length;
4464
+ }
4465
+ skipSpaces() {
4466
+ while (this.index < this.text.length && this.isWhitespace(this.index)) this.index++;
4467
+ }
4468
+ /**
4469
+ * Skip until the end by the given condition
4470
+ */
4471
+ skipUntilEnd(checkEnd) {
4472
+ while (this.index < this.text.length) {
4473
+ const c = this.text[this.index];
4474
+ if (checkEnd(c, this.index)) return true;
4475
+ this.index++;
4476
+ if (c !== "\\") continue;
4477
+ if (this.index < this.text.length && (this.text[this.index] === "\\" || checkEnd(this.text[this.index], this.index)) && !isWhitespace(this.text[this.index])) this.index++;
4478
+ }
4479
+ return false;
4480
+ }
4481
+ };
4482
+ var BackwardCharacterCursor = class extends CharacterCursor {
4483
+ index;
4484
+ constructor(text) {
4485
+ super(text);
4486
+ this.index = text.length - 1;
4487
+ }
4488
+ prev() {
4489
+ this.index--;
4490
+ return this.text[this.index];
4491
+ }
4492
+ finished() {
4493
+ return this.index < 0;
4494
+ }
4495
+ skipSpaces() {
4496
+ while (this.index >= 0 && this.isWhitespace(this.index)) this.index--;
4497
+ }
4498
+ /**
4499
+ * Skip until the start by the given condition
4500
+ */
4501
+ skipUntilStart(checkStart) {
4502
+ while (this.index >= 0) {
4503
+ const c = this.text[this.index];
4504
+ if (checkStart(c, this.index)) {
4505
+ if (this.index > 1 && !isWhitespace(c)) {
4506
+ let escapeText = "";
4507
+ while (this.text.endsWith(`${escapeText}\\`, this.index)) escapeText += "\\";
4508
+ if (escapeText.length % 2 === 1) {
4509
+ this.index -= escapeText.length + 1;
4510
+ continue;
4511
+ }
4512
+ }
4513
+ return true;
4514
+ }
4515
+ this.index--;
4516
+ }
4517
+ return false;
4518
+ }
4519
+ };
4520
+
4521
+ //#endregion
4522
+ //#region src/utils/link-definition.ts
4352
4523
  /**
4353
- * Parse the setext heading.
4524
+ * Parse the link definition.
4354
4525
  */
4355
- function parseSetextHeading(sourceCode, node) {
4356
- if (getHeadingKind(sourceCode, node) !== "setext") return null;
4357
- const lines = getParsedLines(sourceCode);
4358
- const contentLines = [];
4359
- const nodeLoc = sourceCode.getLoc(node);
4360
- for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
4361
- const content = parseContent(lines.get(lineNumber));
4362
- contentLines.push(content);
4526
+ function parseLinkDefinition(sourceCode, node) {
4527
+ const text = sourceCode.getText(node);
4528
+ const parsed = parseLinkDefinitionFromText(text);
4529
+ if (!parsed) return null;
4530
+ const nodeRange = sourceCode.getRange(node);
4531
+ const labelRange = [nodeRange[0] + parsed.label.range[0], nodeRange[0] + parsed.label.range[1]];
4532
+ const destinationRange = [nodeRange[0] + parsed.destination.range[0], nodeRange[0] + parsed.destination.range[1]];
4533
+ return {
4534
+ label: {
4535
+ text: parsed.label.text,
4536
+ range: labelRange,
4537
+ loc: getSourceLocationFromRange(sourceCode, node, labelRange)
4538
+ },
4539
+ destination: {
4540
+ type: parsed.destination.type,
4541
+ text: parsed.destination.text,
4542
+ range: destinationRange,
4543
+ loc: getSourceLocationFromRange(sourceCode, node, destinationRange)
4544
+ },
4545
+ title: parsed.title ? {
4546
+ type: parsed.title.type,
4547
+ text: parsed.title.text,
4548
+ range: [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]],
4549
+ loc: getSourceLocationFromRange(sourceCode, node, [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]])
4550
+ } : null
4551
+ };
4552
+ }
4553
+ /**
4554
+ * Parse the link definition from the given text.
4555
+ */
4556
+ function parseLinkDefinitionFromText(text) {
4557
+ if (!text.startsWith("[")) return null;
4558
+ const cursor = new ForwardCharacterCursor(text);
4559
+ const labelStartIndex = 0;
4560
+ cursor.next();
4561
+ if (!cursor.skipUntilEnd((c) => c === "]")) return null;
4562
+ cursor.next();
4563
+ const labelRange = [labelStartIndex, cursor.currIndex()];
4564
+ if (!text.slice(labelRange[0] + 1, labelRange[1] - 1).trim()) return null;
4565
+ if (cursor.curr() !== ":") return null;
4566
+ const label = {
4567
+ text: text.slice(...labelRange),
4568
+ range: labelRange
4569
+ };
4570
+ cursor.next();
4571
+ cursor.skipSpaces();
4572
+ let destination;
4573
+ const destinationStartIndex = cursor.currIndex();
4574
+ if (cursor.curr() === "<") {
4575
+ cursor.next();
4576
+ if (!cursor.skipUntilEnd((c) => c === ">")) return null;
4577
+ cursor.next();
4578
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4579
+ destination = {
4580
+ type: "pointy-bracketed",
4581
+ text: text.slice(...destinationRange),
4582
+ range: destinationRange
4583
+ };
4584
+ } else {
4585
+ if (cursor.finished()) return null;
4586
+ cursor.skipUntilEnd((c, i) => cursor.isWhitespace(i) || isAsciiControlCharacter(c));
4587
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4588
+ destination = {
4589
+ type: "bare",
4590
+ text: text.slice(...destinationRange),
4591
+ range: destinationRange
4592
+ };
4363
4593
  }
4364
- const underline = parseUnderline(lines.get(nodeLoc.end.line));
4365
- if (!underline) return null;
4594
+ cursor.skipSpaces();
4595
+ if (cursor.finished()) return {
4596
+ label,
4597
+ destination,
4598
+ title: null
4599
+ };
4600
+ let title;
4601
+ const titleStartIndex = cursor.currIndex();
4602
+ const startChar = cursor.curr();
4603
+ if (startChar === "'" || startChar === "\"" || startChar === "(") {
4604
+ cursor.next();
4605
+ const endChar = startChar === "(" ? ")" : startChar;
4606
+ if (!cursor.skipUntilEnd((c) => c === endChar)) return null;
4607
+ cursor.next();
4608
+ const titleRange = [titleStartIndex, cursor.currIndex()];
4609
+ title = {
4610
+ type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
4611
+ text: text.slice(...titleRange),
4612
+ range: titleRange
4613
+ };
4614
+ } else return null;
4615
+ cursor.skipSpaces();
4616
+ if (!cursor.finished()) return null;
4366
4617
  return {
4367
- contentLines,
4368
- underline
4618
+ label,
4619
+ destination,
4620
+ title
4369
4621
  };
4370
4622
  }
4623
+
4624
+ //#endregion
4625
+ //#region src/utils/link.ts
4371
4626
  /**
4372
- * Parse the content line of a setext heading.
4627
+ * Parse the inline link.
4373
4628
  */
4374
- function parseContent(line) {
4375
- let prefix = "";
4376
- let spaceBefore = "";
4377
- let suffix = "";
4378
- for (let index = 0; index < line.text.length; index++) {
4379
- const c = line.text[index];
4380
- if (!c.trim()) {
4381
- spaceBefore += c;
4382
- continue;
4383
- }
4384
- if (c === ">" && spaceBefore.length < 4) {
4385
- prefix += spaceBefore + c;
4386
- spaceBefore = "";
4387
- continue;
4388
- }
4389
- suffix = line.text.slice(index);
4390
- break;
4629
+ function parseInlineLink(sourceCode, node) {
4630
+ if (getLinkKind(sourceCode, node) !== "inline") return null;
4631
+ const nodeRange = sourceCode.getRange(node);
4632
+ let textRange;
4633
+ if (node.children.length === 0) textRange = [nodeRange[0], sourceCode.text.indexOf("]", nodeRange[0]) + 1];
4634
+ else {
4635
+ const lastChildRange = sourceCode.getRange(node.children[node.children.length - 1]);
4636
+ textRange = [nodeRange[0], sourceCode.text.indexOf("]", lastChildRange[1]) + 1];
4391
4637
  }
4392
- const content = suffix.trimEnd();
4393
- const spaceAfter = suffix.slice(content.length);
4638
+ const parsed = parseInlineLinkDestAndTitleFromText(sourceCode.text.slice(textRange[1], nodeRange[1]));
4639
+ if (!parsed) return null;
4640
+ const openingParenRange = [textRange[1] + parsed.openingParen.range[0], textRange[1] + parsed.openingParen.range[1]];
4641
+ const destinationRange = [textRange[1] + parsed.destination.range[0], textRange[1] + parsed.destination.range[1]];
4642
+ const closingParenRange = [textRange[1] + parsed.closingParen.range[0], textRange[1] + parsed.closingParen.range[1]];
4394
4643
  return {
4395
- text: content,
4396
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
4397
- loc: {
4398
- start: {
4399
- line: line.line,
4400
- column: prefix.length + spaceBefore.length + 1
4401
- },
4402
- end: {
4403
- line: line.line,
4404
- column: prefix.length + spaceBefore.length + content.length + 1
4405
- }
4644
+ text: {
4645
+ range: textRange,
4646
+ loc: getSourceLocationFromRange(sourceCode, node, textRange)
4406
4647
  },
4407
- raws: {
4408
- prefix,
4409
- spaceBefore,
4410
- spaceAfter
4648
+ openingParen: {
4649
+ range: openingParenRange,
4650
+ loc: getSourceLocationFromRange(sourceCode, node, openingParenRange)
4651
+ },
4652
+ destination: {
4653
+ type: parsed.destination.type,
4654
+ text: parsed.destination.text,
4655
+ range: destinationRange,
4656
+ loc: getSourceLocationFromRange(sourceCode, node, destinationRange)
4657
+ },
4658
+ title: parsed.title ? {
4659
+ type: parsed.title.type,
4660
+ text: parsed.title.text,
4661
+ range: [textRange[1] + parsed.title.range[0], textRange[1] + parsed.title.range[1]],
4662
+ loc: getSourceLocationFromRange(sourceCode, node, [textRange[1] + parsed.title.range[0], textRange[1] + parsed.title.range[1]])
4663
+ } : null,
4664
+ closingParen: {
4665
+ range: closingParenRange,
4666
+ loc: getSourceLocationFromRange(sourceCode, node, closingParenRange)
4411
4667
  }
4412
4668
  };
4413
4669
  }
4414
4670
  /**
4415
- * Parse the underline of a setext heading.
4416
- */
4417
- function parseUnderline(line) {
4418
- let marker = null;
4419
- let underlineText = "";
4420
- let prefix = "";
4421
- let spaceBefore = "";
4422
- let spaceAfter = "";
4423
- for (let index = line.text.length - 1; index >= 0; index--) {
4424
- const c = line.text[index];
4425
- if (!marker) {
4426
- if (c === "=" || c === "-") {
4427
- underlineText = c + underlineText;
4428
- marker = c;
4429
- } else if (!c.trim()) spaceAfter = c + spaceAfter;
4430
- else return null;
4431
- continue;
4432
- }
4433
- if (c === marker) {
4434
- underlineText = c + spaceBefore + underlineText;
4435
- spaceBefore = "";
4436
- } else if (!c.trim()) spaceBefore = c + spaceBefore;
4437
- else {
4438
- prefix = line.text.slice(0, index + 1);
4439
- break;
4440
- }
4441
- }
4442
- if (!marker) return null;
4443
- const underlineLoc = {
4444
- start: {
4445
- line: line.line,
4446
- column: prefix.length + spaceBefore.length + 1
4447
- },
4448
- end: {
4449
- line: line.line,
4450
- column: prefix.length + spaceBefore.length + underlineText.length + 1
4451
- }
4452
- };
4453
- return {
4454
- text: underlineText,
4455
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
4456
- loc: underlineLoc,
4457
- marker,
4458
- raws: {
4459
- prefix,
4460
- spaceBefore,
4461
- spaceAfter
4462
- }
4463
- };
4464
- }
4465
-
4466
- //#endregion
4467
- //#region src/rules/level1-heading-style.ts
4468
- var level1_heading_style_default = createRule("level1-heading-style", {
4469
- meta: {
4470
- type: "layout",
4471
- docs: {
4472
- description: "enforce consistent style for level 1 headings",
4473
- categories: ["standard"],
4474
- listCategory: "Stylistic"
4475
- },
4476
- fixable: "code",
4477
- hasSuggestions: false,
4478
- schema: [{
4479
- type: "object",
4480
- properties: {
4481
- style: { enum: ["atx", "setext"] },
4482
- allowMultilineSetext: { type: "boolean" }
4483
- },
4484
- additionalProperties: false
4485
- }],
4486
- messages: {
4487
- expectedAtx: "Expected ATX style heading (# Heading).",
4488
- expectedSetext: "Expected Setext style heading (Heading\\n======).",
4489
- multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4490
- }
4491
- },
4492
- create(context) {
4493
- const sourceCode = context.sourceCode;
4494
- const opt = context.options[0] || {};
4495
- const style = opt.style ?? "atx";
4496
- const allowMultilineSetext = opt.allowMultilineSetext;
4497
- return { heading(node) {
4498
- if (node.depth !== 1) return;
4499
- const headingKind = getHeadingKind(sourceCode, node);
4500
- if (style === "atx") {
4501
- if (headingKind !== "setext") return;
4502
- const parsed = parseSetextHeading(sourceCode, node);
4503
- if (!parsed) return;
4504
- if (parsed.contentLines.length > 1) {
4505
- if (allowMultilineSetext) return;
4506
- context.report({
4507
- node,
4508
- messageId: "multilineSetextNotAllowed"
4509
- });
4510
- return;
4511
- }
4512
- context.report({
4513
- node,
4514
- messageId: "expectedAtx",
4515
- *fix(fixer) {
4516
- const heading = parsed.contentLines[0];
4517
- yield fixer.insertTextBeforeRange(heading.range, "# ");
4518
- const lines = getParsedLines(sourceCode);
4519
- yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4520
- }
4521
- });
4522
- } else if (style === "setext") {
4523
- if (headingKind !== "atx" || node.children.length === 0) return;
4524
- context.report({
4525
- node,
4526
- messageId: "expectedSetext",
4527
- *fix(fixer) {
4528
- const parsed = parseATXHeading(sourceCode, node);
4529
- if (!parsed) return;
4530
- yield fixer.removeRange([parsed.openingSequence.range[0], parsed.openingSequence.raws.spaceAfter.range[1]]);
4531
- if (parsed.closingSequence) yield fixer.removeRange([parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.raws.spaceAfter.range[1]]);
4532
- const lines = getParsedLines(sourceCode);
4533
- const underline = "=".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4534
- const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
4535
- yield fixer.insertTextAfter(node, appendingText);
4536
- }
4537
- });
4538
- }
4539
- } };
4540
- }
4541
- });
4542
-
4543
- //#endregion
4544
- //#region src/rules/level2-heading-style.ts
4545
- var level2_heading_style_default = createRule("level2-heading-style", {
4546
- meta: {
4547
- type: "layout",
4548
- docs: {
4549
- description: "enforce consistent style for level 2 headings",
4550
- categories: ["standard"],
4551
- listCategory: "Stylistic"
4552
- },
4553
- fixable: "code",
4554
- hasSuggestions: false,
4555
- schema: [{
4556
- type: "object",
4557
- properties: {
4558
- style: { enum: ["atx", "setext"] },
4559
- allowMultilineSetext: { type: "boolean" }
4560
- },
4561
- additionalProperties: false
4562
- }],
4563
- messages: {
4564
- expectedAtx: "Expected ATX style heading (## Heading).",
4565
- expectedSetext: "Expected Setext style heading (Heading\\n------).",
4566
- multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4567
- }
4568
- },
4569
- create(context) {
4570
- const sourceCode = context.sourceCode;
4571
- const opt = context.options[0] || {};
4572
- const style = opt.style ?? "atx";
4573
- const allowMultilineSetext = opt.allowMultilineSetext;
4574
- return { heading(node) {
4575
- if (node.depth !== 2) return;
4576
- const headingKind = getHeadingKind(sourceCode, node);
4577
- if (style === "atx") {
4578
- if (headingKind !== "setext") return;
4579
- const parsed = parseSetextHeading(sourceCode, node);
4580
- if (!parsed) return;
4581
- if (parsed.contentLines.length > 1) {
4582
- if (allowMultilineSetext) return;
4583
- context.report({
4584
- node,
4585
- messageId: "multilineSetextNotAllowed"
4586
- });
4587
- return;
4588
- }
4589
- context.report({
4590
- node,
4591
- messageId: "expectedAtx",
4592
- *fix(fixer) {
4593
- const heading = parsed.contentLines[0];
4594
- yield fixer.insertTextBeforeRange(heading.range, "## ");
4595
- const lines = getParsedLines(sourceCode);
4596
- yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4597
- }
4598
- });
4599
- } else if (style === "setext") {
4600
- if (headingKind !== "atx" || node.children.length === 0) return;
4601
- context.report({
4602
- node,
4603
- messageId: "expectedSetext",
4604
- *fix(fixer) {
4605
- const parsed = parseATXHeading(sourceCode, node);
4606
- if (!parsed) return;
4607
- yield fixer.removeRange([parsed.openingSequence.range[0], parsed.openingSequence.raws.spaceAfter.range[1]]);
4608
- if (parsed.closingSequence) yield fixer.removeRange([parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.raws.spaceAfter.range[1]]);
4609
- const lines = getParsedLines(sourceCode);
4610
- const underline = "-".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4611
- const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
4612
- yield fixer.insertTextAfter(node, appendingText);
4613
- }
4614
- });
4615
- }
4616
- } };
4617
- }
4618
- });
4619
-
4620
- //#endregion
4621
- //#region src/utils/link.ts
4622
- /**
4623
- * Parse the inline link.
4624
- */
4625
- function parseInlineLink(sourceCode, node) {
4626
- if (getLinkKind(sourceCode, node) !== "inline") return null;
4627
- const nodeRange = sourceCode.getRange(node);
4628
- let textRange;
4629
- if (node.children.length === 0) textRange = [nodeRange[0], sourceCode.text.indexOf("]", nodeRange[0]) + 1];
4630
- else {
4631
- const lastChildRange = sourceCode.getRange(node.children[node.children.length - 1]);
4632
- textRange = [nodeRange[0], sourceCode.text.indexOf("]", lastChildRange[1]) + 1];
4633
- }
4634
- const parsed = parseInlineLinkDestAndTitleFromText(sourceCode.text.slice(textRange[1], nodeRange[1]));
4635
- if (!parsed) return null;
4636
- const destinationRange = [textRange[1] + parsed.destination.range[0], textRange[1] + parsed.destination.range[1]];
4637
- return {
4638
- text: {
4639
- range: textRange,
4640
- loc: getSourceLocationFromRange(sourceCode, node, textRange)
4641
- },
4642
- destination: {
4643
- type: parsed.destination.type,
4644
- text: parsed.destination.text,
4645
- range: destinationRange,
4646
- loc: getSourceLocationFromRange(sourceCode, node, destinationRange)
4647
- },
4648
- title: parsed.title ? {
4649
- type: parsed.title.type,
4650
- text: parsed.title.text,
4651
- range: [textRange[1] + parsed.title.range[0], textRange[1] + parsed.title.range[1]],
4652
- loc: getSourceLocationFromRange(sourceCode, node, [textRange[1] + parsed.title.range[0], textRange[1] + parsed.title.range[1]])
4653
- } : null
4654
- };
4655
- }
4656
- /**
4657
- * Parse the inline link destination and link title from the given text.
4671
+ * Parse the inline link destination and link title from the given text.
4658
4672
  */
4659
4673
  function parseInlineLinkDestAndTitleFromText(text) {
4660
- let index = 0;
4661
- if (text[index] !== "(") return null;
4662
- index++;
4663
- skipSpaces();
4674
+ if (!text.startsWith("(")) return null;
4675
+ const cursor = new ForwardCharacterCursor(text);
4676
+ cursor.next();
4677
+ cursor.skipSpaces();
4664
4678
  let destination;
4665
- const destinationStartIndex = index;
4666
- if (text[index] === "<") {
4667
- index++;
4668
- if (!skipUntilEnd((c) => c === ">")) return null;
4669
- index++;
4670
- const destinationRange = [destinationStartIndex, index];
4679
+ const destinationStartIndex = cursor.currIndex();
4680
+ if (cursor.curr() === "<") {
4681
+ cursor.next();
4682
+ if (!cursor.skipUntilEnd((c) => c === ">")) return null;
4683
+ cursor.next();
4684
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4671
4685
  destination = {
4672
4686
  type: "pointy-bracketed",
4673
4687
  text: text.slice(...destinationRange),
4674
4688
  range: destinationRange
4675
4689
  };
4676
4690
  } else {
4677
- if (text.length <= index) return null;
4678
- skipUntilEnd((c) => isWhitespace(c) || isAsciiControlCharacter(c) || c === ")");
4679
- const destinationRange = [destinationStartIndex, index];
4691
+ if (cursor.finished()) return null;
4692
+ cursor.skipUntilEnd((c, i) => cursor.isWhitespace(i) || isAsciiControlCharacter(c) || c === ")");
4693
+ const destinationRange = [destinationStartIndex, cursor.currIndex()];
4680
4694
  destination = {
4681
4695
  type: "bare",
4682
4696
  text: text.slice(...destinationRange),
4683
4697
  range: destinationRange
4684
4698
  };
4685
4699
  }
4686
- skipSpaces();
4687
- if (text[index] === ")") {
4688
- index++;
4689
- skipSpaces();
4690
- if (index < text.length) return null;
4700
+ cursor.skipSpaces();
4701
+ if (cursor.curr() === ")") {
4702
+ const closingParenStartIndex$1 = cursor.currIndex();
4703
+ cursor.next();
4704
+ cursor.skipSpaces();
4705
+ if (!cursor.finished()) return null;
4691
4706
  return {
4707
+ openingParen: { range: [0, 1] },
4692
4708
  destination,
4693
- title: null
4709
+ title: null,
4710
+ closingParen: { range: [closingParenStartIndex$1, closingParenStartIndex$1 + 1] }
4694
4711
  };
4695
4712
  }
4696
- if (text.length <= index) return null;
4713
+ if (cursor.finished()) return null;
4697
4714
  let title;
4698
- const titleStartIndex = index;
4699
- const startChar = text[index];
4715
+ const titleStartIndex = cursor.currIndex();
4716
+ const startChar = cursor.curr();
4700
4717
  if (startChar === "'" || startChar === "\"" || startChar === "(") {
4701
- index++;
4718
+ cursor.next();
4702
4719
  const endChar = startChar === "(" ? ")" : startChar;
4703
- if (!skipUntilEnd((c) => c === endChar)) return null;
4704
- index++;
4705
- const titleRange = [titleStartIndex, index];
4720
+ if (!cursor.skipUntilEnd((c) => c === endChar)) return null;
4721
+ cursor.next();
4722
+ const titleRange = [titleStartIndex, cursor.currIndex()];
4706
4723
  title = {
4707
4724
  type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
4708
4725
  text: text.slice(...titleRange),
4709
4726
  range: titleRange
4710
4727
  };
4711
4728
  } else return null;
4712
- skipSpaces();
4713
- if (text[index] !== ")") return null;
4714
- index++;
4715
- skipSpaces();
4716
- if (index < text.length) return null;
4729
+ cursor.skipSpaces();
4730
+ if (cursor.curr() !== ")") return null;
4731
+ const closingParenStartIndex = cursor.currIndex();
4732
+ cursor.next();
4733
+ cursor.skipSpaces();
4734
+ if (!cursor.finished()) return null;
4717
4735
  return {
4736
+ openingParen: { range: [0, 1] },
4718
4737
  destination,
4719
- title
4738
+ title,
4739
+ closingParen: { range: [closingParenStartIndex, closingParenStartIndex + 1] }
4720
4740
  };
4721
- /**
4722
- * Skip spaces
4723
- */
4724
- function skipSpaces() {
4725
- while (index < text.length && isWhitespace(text[index])) index++;
4726
- }
4727
- /**
4728
- * Skip until the end by the given condition
4729
- */
4730
- function skipUntilEnd(checkEnd) {
4731
- while (index < text.length) {
4732
- const c = text[index];
4733
- if (checkEnd(c)) return true;
4734
- index++;
4735
- if (c !== "\\") continue;
4736
- if (index < text.length && (text[index] === "\\" || checkEnd(text[index])) && !isWhitespace(text[index])) index++;
4737
- }
4738
- return false;
4739
- }
4740
4741
  }
4741
4742
 
4742
4743
  //#endregion
@@ -4808,12 +4809,18 @@ function parseImage(sourceCode, node) {
4808
4809
  if (!parsed) return null;
4809
4810
  const nodeRange = sourceCode.getRange(node);
4810
4811
  const textRange = [nodeRange[0] + parsed.text.range[0], nodeRange[0] + parsed.text.range[1]];
4812
+ const openingParenRange = [nodeRange[0] + parsed.openingParen.range[0], nodeRange[0] + parsed.openingParen.range[1]];
4811
4813
  const destinationRange = [nodeRange[0] + parsed.destination.range[0], nodeRange[0] + parsed.destination.range[1]];
4814
+ const closingParenRange = [nodeRange[0] + parsed.closingParen.range[0], nodeRange[0] + parsed.closingParen.range[1]];
4812
4815
  return {
4813
4816
  text: {
4814
4817
  range: textRange,
4815
4818
  loc: getSourceLocationFromRange(sourceCode, node, textRange)
4816
4819
  },
4820
+ openingParen: {
4821
+ range: openingParenRange,
4822
+ loc: getSourceLocationFromRange(sourceCode, node, openingParenRange)
4823
+ },
4817
4824
  destination: {
4818
4825
  type: parsed.destination.type,
4819
4826
  text: parsed.destination.text,
@@ -4825,7 +4832,11 @@ function parseImage(sourceCode, node) {
4825
4832
  text: parsed.title.text,
4826
4833
  range: [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]],
4827
4834
  loc: getSourceLocationFromRange(sourceCode, node, [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]])
4828
- } : null
4835
+ } : null,
4836
+ closingParen: {
4837
+ range: closingParenRange,
4838
+ loc: getSourceLocationFromRange(sourceCode, node, closingParenRange)
4839
+ }
4829
4840
  };
4830
4841
  }
4831
4842
  /**
@@ -4833,87 +4844,64 @@ function parseImage(sourceCode, node) {
4833
4844
  */
4834
4845
  function parseImageFromText(text) {
4835
4846
  if (!text.startsWith("![")) return null;
4836
- let index = text.length - 1;
4837
- skipSpaces();
4838
- if (text[index] !== ")") return null;
4839
- index--;
4840
- skipSpaces();
4847
+ const cursor = new BackwardCharacterCursor(text);
4848
+ cursor.skipSpaces();
4849
+ if (cursor.curr() !== ")") return null;
4850
+ const closingParenStartIndex = cursor.currIndex();
4851
+ cursor.prev();
4852
+ cursor.skipSpaces();
4841
4853
  let title = null;
4842
- const titleEndIndex = index + 1;
4843
- const endChar = text[index];
4854
+ const titleEndIndex = cursor.currIndex() + 1;
4855
+ const endChar = cursor.curr();
4844
4856
  if (endChar === "'" || endChar === "\"" || endChar === ")") {
4845
- index--;
4857
+ cursor.prev();
4846
4858
  const startChar = endChar === ")" ? "(" : endChar;
4847
- if (skipUntilStart((c) => c === startChar)) {
4848
- const titleRange = [index, titleEndIndex];
4849
- index--;
4859
+ if (cursor.skipUntilStart((c) => c === startChar)) {
4860
+ const titleRange = [cursor.currIndex(), titleEndIndex];
4861
+ cursor.prev();
4850
4862
  title = {
4851
4863
  type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
4852
4864
  text: text.slice(...titleRange),
4853
4865
  range: titleRange
4854
4866
  };
4855
- skipSpaces();
4867
+ cursor.skipSpaces();
4856
4868
  }
4857
4869
  }
4858
- if (title == null) index = titleEndIndex - 1;
4870
+ if (title == null) cursor.setCurrIndex(titleEndIndex - 1);
4859
4871
  let destination;
4860
- const destinationEndIndex = index + 1;
4861
- if (text[index] === ">") {
4862
- index--;
4863
- if (!skipUntilStart((c) => c === "<")) return null;
4864
- const destinationRange = [index, destinationEndIndex];
4865
- index--;
4872
+ const destinationEndIndex = cursor.currIndex() + 1;
4873
+ if (cursor.curr() === ">") {
4874
+ cursor.prev();
4875
+ if (!cursor.skipUntilStart((c) => c === "<")) return null;
4876
+ const destinationRange = [cursor.currIndex(), destinationEndIndex];
4877
+ cursor.prev();
4866
4878
  destination = {
4867
4879
  type: "pointy-bracketed",
4868
4880
  text: text.slice(...destinationRange),
4869
4881
  range: destinationRange
4870
4882
  };
4871
4883
  } else {
4872
- if (text.length <= index) return null;
4873
- skipUntilStart((c) => isWhitespace(c) || isAsciiControlCharacter(c) || c === "(");
4874
- const destinationRange = [index + 1, destinationEndIndex];
4884
+ if (cursor.finished()) return null;
4885
+ cursor.skipUntilStart((c, i) => cursor.isWhitespace(i) || isAsciiControlCharacter(c) || c === "(");
4886
+ const destinationRange = [cursor.currIndex() + 1, destinationEndIndex];
4875
4887
  destination = {
4876
4888
  type: "bare",
4877
4889
  text: text.slice(...destinationRange),
4878
4890
  range: destinationRange
4879
4891
  };
4880
4892
  }
4881
- skipSpaces();
4882
- if (text[index] !== "(") return null;
4883
- index--;
4884
- if (text[index] !== "]") return null;
4893
+ cursor.skipSpaces();
4894
+ if (cursor.curr() !== "(") return null;
4895
+ const openingParenStartIndex = cursor.currIndex();
4896
+ if (cursor.prev() !== "]") return null;
4897
+ const textRange = [1, cursor.currIndex() + 1];
4885
4898
  return {
4886
- text: { range: [1, index + 1] },
4899
+ openingParen: { range: [openingParenStartIndex, openingParenStartIndex + 1] },
4900
+ text: { range: textRange },
4887
4901
  destination,
4888
- title
4902
+ title,
4903
+ closingParen: { range: [closingParenStartIndex, closingParenStartIndex + 1] }
4889
4904
  };
4890
- /**
4891
- * Skip spaces
4892
- */
4893
- function skipSpaces() {
4894
- while (index >= 0 && isWhitespace(text[index])) index--;
4895
- }
4896
- /**
4897
- * Skip until the start by the given condition
4898
- */
4899
- function skipUntilStart(checkStart) {
4900
- while (index >= 0) {
4901
- const c = text[index];
4902
- if (checkStart(c)) {
4903
- if (index > 1 && !isWhitespace(c)) {
4904
- let escapeText = "";
4905
- while (text.endsWith(`${escapeText}\\`, index)) escapeText += "\\";
4906
- if (escapeText.length % 2 === 1) {
4907
- index -= escapeText.length + 1;
4908
- continue;
4909
- }
4910
- }
4911
- return true;
4912
- }
4913
- index--;
4914
- }
4915
- return false;
4916
- }
4917
4905
  }
4918
4906
 
4919
4907
  //#endregion
@@ -4973,21 +4961,20 @@ function parseImageReference(sourceCode, node) {
4973
4961
  */
4974
4962
  function parseFullImageReferenceFromText(text) {
4975
4963
  if (!text.startsWith("![")) return null;
4976
- let index = text.length - 1;
4977
- skipSpaces();
4978
- if (text[index] !== "]") return null;
4979
- const labelEndIndex = index + 1;
4980
- index--;
4981
- skipSpaces();
4982
- if (!skipUntilStart((c) => c === "[")) return null;
4983
- const labelRange = [index, labelEndIndex];
4984
- index--;
4964
+ const cursor = new BackwardCharacterCursor(text);
4965
+ cursor.skipSpaces();
4966
+ if (cursor.curr() !== "]") return null;
4967
+ const labelEndIndex = cursor.currIndex() + 1;
4968
+ cursor.prev();
4969
+ cursor.skipSpaces();
4970
+ if (!cursor.skipUntilStart((c) => c === "[")) return null;
4971
+ const labelRange = [cursor.currIndex(), labelEndIndex];
4985
4972
  const label = {
4986
4973
  text: text.slice(...labelRange),
4987
4974
  range: labelRange
4988
4975
  };
4989
- if (text[index] !== "]") return null;
4990
- const textRange = [1, index + 1];
4976
+ if (cursor.prev() !== "]") return null;
4977
+ const textRange = [1, cursor.currIndex() + 1];
4991
4978
  return {
4992
4979
  text: {
4993
4980
  text: text.slice(...textRange),
@@ -4995,156 +4982,1208 @@ function parseFullImageReferenceFromText(text) {
4995
4982
  },
4996
4983
  label
4997
4984
  };
4998
- /**
4999
- * Skip spaces
5000
- */
5001
- function skipSpaces() {
5002
- while (index >= 0 && isWhitespace(text[index])) index--;
5003
- }
5004
- /**
5005
- * Skip until the start by the given condition
5006
- */
5007
- function skipUntilStart(checkStart) {
5008
- while (index >= 0) {
5009
- const c = text[index];
5010
- if (checkStart(c)) {
5011
- if (index > 1) {
5012
- let escapeText = "";
5013
- while (text.endsWith(`${escapeText}\\`, index)) escapeText += "\\";
5014
- if (escapeText.length % 2 === 1) {
5015
- index -= escapeText.length + 1;
5016
- continue;
5017
- }
5018
- }
5019
- return true;
5020
- }
5021
- index--;
5022
- }
5023
- return false;
5024
- }
5025
4985
  }
5026
4986
 
5027
4987
  //#endregion
5028
- //#region src/utils/link-definition.ts
4988
+ //#region src/utils/list-item.ts
5029
4989
  /**
5030
- * Parse the link definition.
4990
+ * Parse the list item.
5031
4991
  */
5032
- function parseLinkDefinition(sourceCode, node) {
5033
- const text = sourceCode.getText(node);
5034
- const parsed = parseLinkDefinitionFromText(text);
5035
- if (!parsed) return null;
4992
+ function parseListItem(sourceCode, node) {
4993
+ const marker = getListItemMarker(sourceCode, node);
5036
4994
  const nodeRange = sourceCode.getRange(node);
5037
- const labelRange = [nodeRange[0] + parsed.label.range[0], nodeRange[0] + parsed.label.range[1]];
5038
- const destinationRange = [nodeRange[0] + parsed.destination.range[0], nodeRange[0] + parsed.destination.range[1]];
5039
- return {
5040
- label: {
5041
- text: parsed.label.text,
5042
- range: labelRange,
5043
- loc: getSourceLocationFromRange(sourceCode, node, labelRange)
5044
- },
5045
- destination: {
5046
- type: parsed.destination.type,
5047
- text: parsed.destination.text,
5048
- range: destinationRange,
5049
- loc: getSourceLocationFromRange(sourceCode, node, destinationRange)
5050
- },
5051
- title: parsed.title ? {
5052
- type: parsed.title.type,
5053
- text: parsed.title.text,
5054
- range: [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]],
5055
- loc: getSourceLocationFromRange(sourceCode, node, [nodeRange[0] + parsed.title.range[0], nodeRange[0] + parsed.title.range[1]])
5056
- } : null
4995
+ const markerRange = [nodeRange[0], nodeRange[0] + marker.raw.length];
4996
+ const parsedMarker = marker.kind === "." || marker.kind === ")" ? {
4997
+ kind: marker.kind,
4998
+ text: marker.raw,
4999
+ value: marker.sequence.value,
5000
+ range: markerRange,
5001
+ loc: getSourceLocationFromRange(sourceCode, node, markerRange)
5002
+ } : {
5003
+ kind: marker.kind,
5004
+ text: marker.raw,
5005
+ range: markerRange,
5006
+ loc: getSourceLocationFromRange(sourceCode, node, markerRange)
5057
5007
  };
5058
- }
5059
- /**
5060
- * Parse the link definition from the given text.
5061
- */
5062
- function parseLinkDefinitionFromText(text) {
5063
- let index = 0;
5064
- if (text[index] !== "[") return null;
5065
- const labelStartIndex = index;
5066
- index++;
5067
- if (!skipUntilEnd((c) => c === "]")) return null;
5068
- index++;
5069
- const labelRange = [labelStartIndex, index];
5070
- if (!text.slice(labelRange[0] + 1, labelRange[1] - 1).trim()) return null;
5071
- if (text[index] !== ":") return null;
5072
- const label = {
5073
- text: text.slice(...labelRange),
5074
- range: labelRange
5008
+ if (node.checked == null) return {
5009
+ marker: parsedMarker,
5010
+ taskListItemMarker: null
5075
5011
  };
5076
- index++;
5077
- skipSpaces();
5078
- let destination;
5079
- const destinationStartIndex = index;
5080
- if (text[index] === "<") {
5081
- index++;
5082
- if (!skipUntilEnd((c) => c === ">")) return null;
5083
- index++;
5084
- const destinationRange = [destinationStartIndex, index];
5085
- destination = {
5086
- type: "pointy-bracketed",
5087
- text: text.slice(...destinationRange),
5088
- range: destinationRange
5089
- };
5090
- } else {
5091
- if (text.length <= index) return null;
5092
- skipUntilEnd((c) => isWhitespace(c) || isAsciiControlCharacter(c));
5093
- const destinationRange = [destinationStartIndex, index];
5094
- destination = {
5095
- type: "bare",
5096
- text: text.slice(...destinationRange),
5097
- range: destinationRange
5012
+ for (let index = nodeRange[0] + marker.raw.length; index < nodeRange[1]; index++) {
5013
+ const c = sourceCode.text[index];
5014
+ if (isSpaceOrTab(c)) continue;
5015
+ if (c !== "[") break;
5016
+ const middle = sourceCode.text[index + 1];
5017
+ if (middle !== "x" && middle !== " ") break;
5018
+ if (sourceCode.text[index + 2] !== "]") break;
5019
+ const taskListItemMarkerRange = [index, index + 3];
5020
+ return {
5021
+ marker: parsedMarker,
5022
+ taskListItemMarker: {
5023
+ text: sourceCode.text.slice(...taskListItemMarkerRange),
5024
+ range: taskListItemMarkerRange,
5025
+ loc: getSourceLocationFromRange(sourceCode, node, taskListItemMarkerRange)
5026
+ }
5098
5027
  };
5099
5028
  }
5100
- skipSpaces();
5101
- if (text.length <= index) return {
5102
- label,
5103
- destination,
5104
- title: null
5105
- };
5106
- let title;
5107
- const titleStartIndex = index;
5108
- const startChar = text[index];
5109
- if (startChar === "'" || startChar === "\"" || startChar === "(") {
5110
- index++;
5111
- const endChar = startChar === "(" ? ")" : startChar;
5112
- if (!skipUntilEnd((c) => c === endChar)) return null;
5113
- index++;
5114
- const titleRange = [titleStartIndex, index];
5115
- title = {
5116
- type: startChar === "'" ? "single-quoted" : startChar === "\"" ? "double-quoted" : "parenthesized",
5117
- text: text.slice(...titleRange),
5118
- range: titleRange
5119
- };
5120
- } else return null;
5121
- skipSpaces();
5122
- if (index < text.length) return null;
5123
5029
  return {
5124
- label,
5125
- destination,
5126
- title
5030
+ marker: parsedMarker,
5031
+ taskListItemMarker: null
5127
5032
  };
5128
- /**
5129
- * Skip spaces
5130
- */
5131
- function skipSpaces() {
5132
- while (index < text.length && isWhitespace(text[index])) index++;
5133
- }
5134
- /**
5135
- * Skip until the end by the given condition
5136
- */
5137
- function skipUntilEnd(checkEnd) {
5138
- while (index < text.length) {
5139
- const c = text[index];
5140
- if (checkEnd(c)) return true;
5141
- index++;
5142
- if (c !== "\\") continue;
5143
- if (index < text.length && (text[index] === "\\" || checkEnd(text[index])) && !isWhitespace(text[index])) index++;
5033
+ }
5034
+
5035
+ //#endregion
5036
+ //#region src/rules/indent.ts
5037
+ /**
5038
+ * Parse options.
5039
+ */
5040
+ function parseOptions$2(options) {
5041
+ const listItems = options?.listItems;
5042
+ return { listItems: {
5043
+ first: listItems?.first ?? 1,
5044
+ other: listItems?.other ?? "first",
5045
+ relativeTo: listItems?.relativeTo ?? "taskListMarkerEnd"
5046
+ } };
5047
+ }
5048
+ var indent_default = createRule("indent", {
5049
+ meta: {
5050
+ type: "layout",
5051
+ docs: {
5052
+ description: "enforce consistent indentation in Markdown files",
5053
+ categories: ["standard"],
5054
+ listCategory: "Stylistic"
5055
+ },
5056
+ fixable: "whitespace",
5057
+ hasSuggestions: false,
5058
+ schema: [{
5059
+ type: "object",
5060
+ properties: { listItems: {
5061
+ type: "object",
5062
+ properties: {
5063
+ first: { anyOf: [{ const: "ignore" }, {
5064
+ type: "number",
5065
+ minimum: 1
5066
+ }] },
5067
+ other: { anyOf: [{ enum: ["first", "minimum"] }, {
5068
+ type: "number",
5069
+ minimum: 1
5070
+ }] },
5071
+ relativeTo: { enum: [
5072
+ "markerStart",
5073
+ "markerEnd",
5074
+ "taskListMarkerStart",
5075
+ "taskListMarkerEnd"
5076
+ ] }
5077
+ },
5078
+ additionalProperties: false
5079
+ } },
5080
+ additionalProperties: false
5081
+ }],
5082
+ messages: {
5083
+ wrongIndentation: "Expected indentation of {{expected}} but found {{actual}}.",
5084
+ wrongIndentationFromBlockquoteMarker: "Expected indentation of {{expected}} from the blockquote marker but found {{actual}}.",
5085
+ wrongIndentationFromListMarker: "Expected indentation of {{expected}} from the list marker but found {{actual}}.",
5086
+ wrongIndentationWithTab: "Expected indentation of {{expected}} but found {{actual}} (tab width is 4).",
5087
+ wrongIndentationFromBlockquoteMarkerWithTab: "Expected indentation of {{expected}} from the blockquote marker but found {{actual}} (tab width is 4).",
5088
+ wrongIndentationFromListMarkerWithTab: "Expected indentation of {{expected}} from the list marker but found {{actual}} (tab width is 4)."
5089
+ }
5090
+ },
5091
+ create(context) {
5092
+ const sourceCode = context.sourceCode;
5093
+ const options = parseOptions$2(context.options[0]);
5094
+ class AbsBlockStack {
5095
+ violations = [];
5096
+ getCurrentBlockquote() {
5097
+ let block = this;
5098
+ while (block) {
5099
+ if (block.type === "blockquote") return block;
5100
+ block = block.upper;
5101
+ }
5102
+ return null;
5103
+ }
5104
+ getCurrentListItemAtLine(lineNumber) {
5105
+ let block = this;
5106
+ while (block) {
5107
+ if (block.type === "blockquote") return null;
5108
+ if (block.type === "listItem") {
5109
+ if (sourceCode.getLoc(block.node).start.line === lineNumber) return block;
5110
+ }
5111
+ block = block.upper;
5112
+ }
5113
+ return null;
5114
+ }
5115
+ }
5116
+ class RootStack extends AbsBlockStack {
5117
+ type = "root";
5118
+ node;
5119
+ upper = null;
5120
+ constructor(node) {
5121
+ super();
5122
+ this.node = node;
5123
+ }
5124
+ getExpectedIndent() {
5125
+ return 0;
5126
+ }
5127
+ }
5128
+ class BlockquoteStack extends AbsBlockStack {
5129
+ type = "blockquote";
5130
+ node;
5131
+ upper;
5132
+ _expectedIndent;
5133
+ _markerIndent;
5134
+ constructor(node) {
5135
+ super();
5136
+ this.node = node;
5137
+ this.upper = blockStack;
5138
+ }
5139
+ getExpectedIndent() {
5140
+ if (this._expectedIndent == null) this._expectedIndent = this.getMarkerIndent() + 1 + 1;
5141
+ return this._expectedIndent;
5142
+ }
5143
+ getMarkerIndent() {
5144
+ if (this._markerIndent == null) {
5145
+ const loc = sourceCode.getLoc(this.node);
5146
+ this._markerIndent = getWidth(sourceCode.lines[loc.start.line - 1].slice(0, loc.start.column - 1));
5147
+ }
5148
+ return this._markerIndent;
5149
+ }
5150
+ }
5151
+ class ListItemStack extends AbsBlockStack {
5152
+ type = "listItem";
5153
+ node;
5154
+ upper;
5155
+ _expectedIndentForFirstLine;
5156
+ _expectedIndentForOtherLines;
5157
+ _parsed;
5158
+ nodeLoc;
5159
+ constructor(node) {
5160
+ super();
5161
+ this.node = node;
5162
+ this.upper = blockStack;
5163
+ this.nodeLoc = sourceCode.getLoc(this.node);
5164
+ }
5165
+ getParsedListItem() {
5166
+ return this._parsed ??= parseListItem(sourceCode, this.node);
5167
+ }
5168
+ getExpectedIndent({ lineNumber, block }) {
5169
+ const loc = this.nodeLoc;
5170
+ if (lineNumber === loc.start.line) return this.getExpectedIndentForFirstLine();
5171
+ const expected = this.getExpectedIndentAfterFirstLine();
5172
+ if (expected === "ignore") return "ignore";
5173
+ if (!block) return expected;
5174
+ const actualFirstLineIndent = this.getActualFirstLineIndent({ withoutTaskListMarker: true });
5175
+ if (actualFirstLineIndent != null) return Math.min(expected, actualFirstLineIndent + 3);
5176
+ return expected;
5177
+ }
5178
+ getExpectedIndentForFirstLine() {
5179
+ if (options.listItems.first === "ignore") return "ignore";
5180
+ if (this._expectedIndentForFirstLine != null) return this._expectedIndentForFirstLine;
5181
+ const loc = this.nodeLoc;
5182
+ const parsed = this.getParsedListItem();
5183
+ const lineText = sourceCode.lines[loc.start.line - 1];
5184
+ if (options.listItems.relativeTo === "markerStart") {
5185
+ const baseIndent = getWidth(lineText.slice(0, loc.start.column - 1));
5186
+ return this._expectedIndentForFirstLine = Math.max(baseIndent + options.listItems.first, baseIndent + parsed.marker.text.length + 1);
5187
+ }
5188
+ if (options.listItems.relativeTo === "taskListMarkerEnd" && parsed.taskListItemMarker) return this._expectedIndentForFirstLine = getWidth(lineText.slice(0, parsed.taskListItemMarker.loc.end.column - 1)) + options.listItems.first;
5189
+ return this._expectedIndentForFirstLine = getWidth(lineText.slice(0, parsed.marker.loc.end.column - 1)) + options.listItems.first;
5190
+ }
5191
+ getExpectedIndentAfterFirstLine() {
5192
+ if (this._expectedIndentForOtherLines != null) return this._expectedIndentForOtherLines;
5193
+ const loc = this.nodeLoc;
5194
+ if (options.listItems.other === "first") {
5195
+ const firstLineIndent = this.getExpectedIndentForFirstLine();
5196
+ if (firstLineIndent === "ignore") {
5197
+ const actualFirstLineIndent = this.getActualFirstLineIndent();
5198
+ if (actualFirstLineIndent != null) return this._expectedIndentForOtherLines = actualFirstLineIndent;
5199
+ for (const child of this.node.children) {
5200
+ const childLoc = sourceCode.getLoc(child);
5201
+ if (loc.start.line < childLoc.start.line) return this._expectedIndentForOtherLines = getWidth(sourceCode.lines[childLoc.start.line - 1].slice(0, childLoc.start.column - 1));
5202
+ }
5203
+ }
5204
+ return this._expectedIndentForOtherLines = firstLineIndent;
5205
+ }
5206
+ if (options.listItems.other === "minimum") return this._expectedIndentForOtherLines = this.getMinimumLineIndent();
5207
+ const lineText = sourceCode.lines[loc.start.line - 1];
5208
+ if (options.listItems.relativeTo === "markerStart") {
5209
+ const baseIndent = getWidth(lineText.slice(0, loc.start.column - 1));
5210
+ const minimumLineIndent = this.getMinimumLineIndent();
5211
+ return this._expectedIndentForOtherLines = Math.max(baseIndent + options.listItems.other, minimumLineIndent);
5212
+ }
5213
+ const parsed = this.getParsedListItem();
5214
+ if (options.listItems.relativeTo === "taskListMarkerEnd" && parsed.taskListItemMarker) return this._expectedIndentForOtherLines = getWidth(lineText.slice(0, parsed.taskListItemMarker.loc.end.column - 1)) + options.listItems.other;
5215
+ return this._expectedIndentForOtherLines = getWidth(lineText.slice(0, parsed.marker.loc.end.column - 1)) + options.listItems.other;
5216
+ }
5217
+ getMinimumLineIndent() {
5218
+ const actualFirstLineIndent = this.getActualFirstLineIndent({ withoutTaskListMarker: true });
5219
+ if (actualFirstLineIndent != null) return this._expectedIndentForOtherLines = actualFirstLineIndent;
5220
+ const parsed = this.getParsedListItem();
5221
+ const lineText = sourceCode.lines[parsed.marker.loc.end.line - 1];
5222
+ return getWidth(lineText.slice(0, parsed.marker.loc.end.column - 1)) + 1;
5223
+ }
5224
+ getActualFirstLineIndent({ withoutTaskListMarker = false } = {}) {
5225
+ const parsed = this.getParsedListItem();
5226
+ const markerEndPos = withoutTaskListMarker ? parsed.marker.loc.end : parsed.taskListItemMarker?.loc.end ?? parsed.marker.loc.end;
5227
+ const lineText = sourceCode.lines[markerEndPos.line - 1];
5228
+ const afterMarkerText = lineText.slice(markerEndPos.column - 1);
5229
+ const trimmedAfterMarkerText = afterMarkerText.trimStart();
5230
+ if (trimmedAfterMarkerText) {
5231
+ const afterMarkerSpacesLength = afterMarkerText.length - trimmedAfterMarkerText.length;
5232
+ return getWidth(lineText.slice(0, markerEndPos.column - 1 + afterMarkerSpacesLength));
5233
+ }
5234
+ return null;
5235
+ }
5236
+ }
5237
+ class FootnoteDefinitionStack extends AbsBlockStack {
5238
+ type = "footnoteDefinition";
5239
+ node;
5240
+ upper;
5241
+ _expectedIndent;
5242
+ constructor(node) {
5243
+ super();
5244
+ this.node = node;
5245
+ this.upper = blockStack;
5246
+ }
5247
+ getExpectedIndent() {
5248
+ if (this._expectedIndent == null) {
5249
+ const loc = sourceCode.getLoc(this.node);
5250
+ this._expectedIndent = getWidth(sourceCode.lines[loc.start.line - 1].slice(0, loc.start.column - 1)) + 4;
5251
+ }
5252
+ return this._expectedIndent;
5253
+ }
5254
+ }
5255
+ class LinkStack extends AbsBlockStack {
5256
+ type = "link";
5257
+ node;
5258
+ upper;
5259
+ nodeLoc;
5260
+ constructor(node) {
5261
+ super();
5262
+ this.node = node;
5263
+ this.upper = blockStack;
5264
+ this.nodeLoc = sourceCode.getLoc(this.node);
5265
+ }
5266
+ getExpectedIndent(ctx) {
5267
+ const loc = this.nodeLoc;
5268
+ if (ctx.lineNumber === loc.start.line) return this.upper.getExpectedIndent(ctx);
5269
+ const base = this.upper.getExpectedIndent({
5270
+ lineNumber: loc.start.line,
5271
+ block: ctx.block
5272
+ });
5273
+ if (base === "ignore") return "ignore";
5274
+ return base + 2;
5275
+ }
5276
+ }
5277
+ let blockStack = new RootStack(sourceCode.ast);
5278
+ const reportedLocations = Object.create(null);
5279
+ /**
5280
+ * Reported locations (line and column) to avoid duplicate reports.
5281
+ */
5282
+ function reportIncorrectIndent(violation) {
5283
+ const reportedColumns = reportedLocations[violation.loc.start.line] ??= /* @__PURE__ */ new Set();
5284
+ if (reportedColumns.has(violation.loc.start.column)) return;
5285
+ reportedColumns.add(violation.loc.start.column);
5286
+ blockStack.violations.push(violation);
5287
+ }
5288
+ /**
5289
+ * Flush violations to the context.
5290
+ */
5291
+ function flushViolations({ violations }) {
5292
+ for (const violation of violations) context.report({
5293
+ loc: violation.loc,
5294
+ messageId: violation.messageId,
5295
+ data: {
5296
+ expected: String(violation.data.expected),
5297
+ actual: String(violation.data.actual)
5298
+ },
5299
+ fix(fixer) {
5300
+ const result = [];
5301
+ for (const { fix } of violations) result.push(...fix(fixer));
5302
+ return result;
5303
+ }
5304
+ });
5305
+ }
5306
+ return {
5307
+ thematicBreak: verifyThematicBreak,
5308
+ heading: verifyHeading,
5309
+ code: verifyCodeBlock,
5310
+ html: verifyHtml,
5311
+ definition: verifyLinkDefinition,
5312
+ table: verifyTable,
5313
+ list: verifyList,
5314
+ inlineCode: verifyInlineCode,
5315
+ emphasis: verifyEmphasisOrStrongOrDelete,
5316
+ strong: verifyEmphasisOrStrongOrDelete,
5317
+ delete: verifyEmphasisOrStrongOrDelete,
5318
+ image: verifyImage,
5319
+ imageReference: verifyImageReference,
5320
+ footnoteReference: verifyInline,
5321
+ break: verifyInline,
5322
+ text: verifyText,
5323
+ blockquote(node) {
5324
+ verifyBlockquote(node);
5325
+ blockStack = new BlockquoteStack(node);
5326
+ },
5327
+ listItem(node) {
5328
+ blockStack = new ListItemStack(node);
5329
+ },
5330
+ footnoteDefinition(node) {
5331
+ verifyFootnoteDefinition(node);
5332
+ blockStack = new FootnoteDefinitionStack(node);
5333
+ },
5334
+ link(node) {
5335
+ verifyLink(node);
5336
+ blockStack = new LinkStack(node);
5337
+ },
5338
+ linkReference(node) {
5339
+ verifyLinkReference(node);
5340
+ blockStack = new LinkStack(node);
5341
+ },
5342
+ "blockquote, listItem, footnoteDefinition, link, linkReference:exit"() {
5343
+ flushViolations(blockStack);
5344
+ blockStack = blockStack.upper;
5345
+ },
5346
+ "root:exit"() {
5347
+ flushViolations(blockStack);
5348
+ }
5349
+ };
5350
+ /**
5351
+ * Verify a thematic break node.
5352
+ */
5353
+ function verifyThematicBreak(node) {
5354
+ const loc = sourceCode.getLoc(node);
5355
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5356
+ lineNumber,
5357
+ block: true
5358
+ }));
5359
+ }
5360
+ /**
5361
+ * Verify a heading node.
5362
+ */
5363
+ function verifyHeading(node) {
5364
+ const loc = sourceCode.getLoc(node);
5365
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5366
+ lineNumber,
5367
+ block: true
5368
+ }));
5369
+ }
5370
+ /**
5371
+ * Verify a code block node.
5372
+ */
5373
+ function verifyCodeBlock(node) {
5374
+ if (getCodeBlockKind(sourceCode, node) === "indented") return;
5375
+ const loc = sourceCode.getLoc(node);
5376
+ verifyLinesIndent([loc.start.line, loc.end.line], (lineNumber) => blockStack.getExpectedIndent({
5377
+ lineNumber,
5378
+ block: true
5379
+ }), additionalFixes);
5380
+ /**
5381
+ * Additional fixes for code block content lines.
5382
+ */
5383
+ function* additionalFixes(fixer, info) {
5384
+ if (info.loc.start.line !== loc.start.line) return;
5385
+ for (let lineNumber = loc.start.line + 1; lineNumber < loc.end.line; lineNumber++) {
5386
+ const line = getParsedLines(sourceCode).get(lineNumber);
5387
+ if (!line) continue;
5388
+ if (info.expectedIndentWidth > info.actualIndentWidth) {
5389
+ const before = sliceWidth(line.text, 0, info.actualIndentWidth);
5390
+ const after = sliceWidth(line.text, info.actualIndentWidth);
5391
+ const diffWidth = info.expectedIndentWidth - info.actualIndentWidth;
5392
+ yield fixer.replaceTextRange([line.range[0], line.range[0] + line.text.length], before + " ".repeat(diffWidth) + after);
5393
+ } else {
5394
+ let before = sliceWidth(line.text, 0, info.expectedIndentWidth);
5395
+ let between = sliceWidth(line.text, info.expectedIndentWidth, info.actualIndentWidth);
5396
+ const after = sliceWidth(line.text, info.actualIndentWidth);
5397
+ while (between && !isSpaceOrTab(between)) {
5398
+ before += between[0];
5399
+ between = between.slice(1);
5400
+ }
5401
+ yield fixer.replaceTextRange([line.range[0], line.range[0] + line.text.length], before + after);
5402
+ }
5403
+ }
5404
+ }
5405
+ }
5406
+ /**
5407
+ * Verify an HTML node.
5408
+ */
5409
+ function verifyHtml(node) {
5410
+ const loc = sourceCode.getLoc(node);
5411
+ if (!inlineToBeChecked(loc.start)) return;
5412
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5413
+ lineNumber,
5414
+ block: true
5415
+ }));
5416
+ }
5417
+ /**
5418
+ * Verify a link definition node.
5419
+ */
5420
+ function verifyLinkDefinition(node) {
5421
+ const parsed = parseLinkDefinition(sourceCode, node);
5422
+ if (!parsed) return;
5423
+ const loc = sourceCode.getLoc(node);
5424
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber, column) => {
5425
+ const baseIndent = blockStack.getExpectedIndent({
5426
+ lineNumber,
5427
+ block: true
5428
+ });
5429
+ if (baseIndent === "ignore") return "ignore";
5430
+ if (lineNumber <= loc.start.line) return baseIndent;
5431
+ if (lineNumber < parsed.label.loc.end.line) return baseIndent + 2;
5432
+ if (lineNumber === parsed.label.loc.end.line) {
5433
+ const line = getParsedLines(sourceCode).get(lineNumber);
5434
+ if (!line) return baseIndent;
5435
+ const between = line.text.slice(column - 1, parsed.label.loc.end.column - 2);
5436
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5437
+ }
5438
+ if (lineNumber <= parsed.destination.loc.end.line) return baseIndent + 4;
5439
+ if (!parsed.title) return baseIndent;
5440
+ if (lineNumber <= parsed.title.loc.start.line) return baseIndent + 4;
5441
+ if (lineNumber < parsed.title.loc.end.line) return baseIndent + 6;
5442
+ if (lineNumber === parsed.title.loc.end.line) {
5443
+ const line = getParsedLines(sourceCode).get(lineNumber);
5444
+ if (!line) return baseIndent;
5445
+ const between = line.text.slice(column - 1, parsed.title.loc.end.column - 2);
5446
+ return !between || isWhitespace(between) ? baseIndent + 4 : baseIndent + 6;
5447
+ }
5448
+ return baseIndent;
5449
+ });
5450
+ }
5451
+ /**
5452
+ * Verify a blockquote node.
5453
+ */
5454
+ function verifyBlockquote(node) {
5455
+ const loc = sourceCode.getLoc(node);
5456
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5457
+ lineNumber,
5458
+ block: true
5459
+ }));
5460
+ }
5461
+ /**
5462
+ * Verify a table node.
5463
+ */
5464
+ function verifyTable(node) {
5465
+ const loc = sourceCode.getLoc(node);
5466
+ verifyLinesIndent(lineNumbersFromRange(loc.start.line, loc.end.line), (lineNumber) => blockStack.getExpectedIndent({
5467
+ lineNumber,
5468
+ block: true
5469
+ }));
5470
+ }
5471
+ /**
5472
+ * Verify a list node.
5473
+ */
5474
+ function verifyList(node) {
5475
+ const loc = sourceCode.getLoc(node);
5476
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5477
+ lineNumber,
5478
+ block: true
5479
+ }), additionalFixes);
5480
+ /**
5481
+ * Additional fixes for list item lines.
5482
+ */
5483
+ function* additionalFixes(fixer, info) {
5484
+ const [firstItem, ...otherItems] = node.children;
5485
+ yield* fixAfterFirstLine(firstItem, info.actualIndentWidth);
5486
+ for (const listItem of otherItems) {
5487
+ const listItemLoc = sourceCode.getLoc(listItem);
5488
+ const listItemIndentWidth = getWidth(sourceCode.lines[listItemLoc.start.line - 1].slice(0, listItemLoc.start.column - 1));
5489
+ if (listItemIndentWidth === info.expectedIndentWidth) continue;
5490
+ yield getFixForLine(listItemLoc.start.line, listItemIndentWidth);
5491
+ yield* fixAfterFirstLine(listItem, listItemIndentWidth);
5492
+ }
5493
+ /**
5494
+ * Get fixes for lines after the first line of the list item.
5495
+ */
5496
+ function* fixAfterFirstLine(item, actualIndentWidth) {
5497
+ const itemLoc = sourceCode.getLoc(item);
5498
+ for (let lineNumber = itemLoc.start.line + 1; lineNumber <= itemLoc.end.line; lineNumber++) yield getFixForLine(lineNumber, actualIndentWidth);
5499
+ }
5500
+ /**
5501
+ * Get a fix for a line.
5502
+ */
5503
+ function getFixForLine(lineNumber, actualIndentWidth) {
5504
+ const line = getParsedLines(sourceCode).get(lineNumber);
5505
+ if (info.expectedIndentWidth > actualIndentWidth) {
5506
+ const before$1 = sliceWidth(line.text, 0, actualIndentWidth);
5507
+ const diffWidth = info.expectedIndentWidth - actualIndentWidth;
5508
+ return fixer.insertTextAfterRange([line.range[0], line.range[0] + before$1.length], " ".repeat(diffWidth));
5509
+ }
5510
+ let before = sliceWidth(line.text, 0, info.expectedIndentWidth);
5511
+ let between = sliceWidth(line.text, info.expectedIndentWidth, actualIndentWidth);
5512
+ while (between && !isSpaceOrTab(between)) {
5513
+ before += between[0];
5514
+ between = between.slice(1);
5515
+ }
5516
+ return fixer.replaceTextRange([line.range[0], line.range[0] + before.length + between.length], before);
5517
+ }
5518
+ }
5519
+ }
5520
+ /**
5521
+ * Verify a footnote definition node.
5522
+ */
5523
+ function verifyFootnoteDefinition(node) {
5524
+ const loc = sourceCode.getLoc(node);
5525
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5526
+ lineNumber,
5527
+ block: true
5528
+ }));
5529
+ }
5530
+ /**
5531
+ * Verify an inline code node.
5532
+ */
5533
+ function verifyInlineCode(node) {
5534
+ const loc = sourceCode.getLoc(node);
5535
+ if (!inlineToBeChecked(loc.start)) return;
5536
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5537
+ lineNumber,
5538
+ block: false
5539
+ }));
5540
+ }
5541
+ /**
5542
+ * Verify an emphasis, strong, or delete node.
5543
+ */
5544
+ function verifyEmphasisOrStrongOrDelete(node) {
5545
+ const loc = sourceCode.getLoc(node);
5546
+ if (!inlineToBeChecked(loc.start)) return;
5547
+ verifyLinesIndent([loc.start.line], (lineNumber) => blockStack.getExpectedIndent({
5548
+ lineNumber,
5549
+ block: false
5550
+ }));
5551
+ }
5552
+ /**
5553
+ * Verify a link node.
5554
+ */
5555
+ function verifyLink(node) {
5556
+ const loc = sourceCode.getLoc(node);
5557
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5558
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5559
+ const kind = getLinkKind(sourceCode, node);
5560
+ if (kind === "autolink" || kind === "gfm-autolink") verifyLinesIndent(lines, (lineNumber) => blockStack.getExpectedIndent({
5561
+ lineNumber,
5562
+ block: false
5563
+ }));
5564
+ else if (kind === "inline") {
5565
+ const parsed = parseInlineLink(sourceCode, node);
5566
+ if (!parsed) return;
5567
+ const lastChild = node.children.at(-1);
5568
+ if (lastChild && parsed.text.loc.start.line < parsed.text.loc.end.line) {
5569
+ const lastChildLoc = sourceCode.getLoc(lastChild);
5570
+ const lastChildEndLine = lastChild.type === "text" && lastChild.value.endsWith("\n") ? lastChildLoc.end.line - 1 : lastChildLoc.end.line;
5571
+ lines = lines.filter((lineNumber) => lineNumber <= parsed.text.loc.start.line || lastChildEndLine < lineNumber);
5572
+ }
5573
+ verifyLinesIndent(lines, (lineNumber, column) => {
5574
+ const baseIndent = blockStack.getExpectedIndent({
5575
+ lineNumber,
5576
+ block: false
5577
+ });
5578
+ if (baseIndent === "ignore") return "ignore";
5579
+ if (lineNumber <= loc.start.line) return baseIndent;
5580
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5581
+ if (lineNumber <= parsed.openingParen.loc.end.line) return baseIndent;
5582
+ if (lineNumber <= parsed.destination.loc.end.line) return baseIndent + 2;
5583
+ if (!parsed.title) return baseIndent;
5584
+ if (lineNumber <= parsed.title.loc.start.line) return baseIndent + 2;
5585
+ if (lineNumber < parsed.title.loc.end.line) return baseIndent + 4;
5586
+ if (lineNumber === parsed.title.loc.end.line) {
5587
+ const line = getParsedLines(sourceCode).get(lineNumber);
5588
+ if (!line) return baseIndent;
5589
+ const between = line.text.slice(column - 1, parsed.title.loc.end.column - 2);
5590
+ return !between || isWhitespace(between) ? baseIndent + 2 : baseIndent + 4;
5591
+ }
5592
+ return baseIndent;
5593
+ });
5594
+ }
5595
+ }
5596
+ /**
5597
+ * Verify a link reference node.
5598
+ */
5599
+ function verifyLinkReference(node) {
5600
+ const loc = sourceCode.getLoc(node);
5601
+ const parsed = parseLinkReference(sourceCode, node);
5602
+ if (!parsed) return;
5603
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5604
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5605
+ const lastChild = node.children.at(-1);
5606
+ if (lastChild && parsed.text.loc.start.line < parsed.text.loc.end.line) {
5607
+ const lastChildLoc = sourceCode.getLoc(lastChild);
5608
+ const lastChildEndLine = lastChild.type === "text" && lastChild.value.endsWith("\n") ? lastChildLoc.end.line - 1 : lastChildLoc.end.line;
5609
+ lines = lines.filter((lineNumber) => lineNumber <= parsed.text.loc.start.line || lastChildEndLine < lineNumber);
5610
+ }
5611
+ verifyLinesIndent(lines, (lineNumber, column) => {
5612
+ const baseIndent = blockStack.getExpectedIndent({
5613
+ lineNumber,
5614
+ block: false
5615
+ });
5616
+ if (baseIndent === "ignore") return "ignore";
5617
+ if (lineNumber <= loc.start.line) return baseIndent;
5618
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5619
+ if (!parsed.label) return baseIndent;
5620
+ if (lineNumber <= parsed.label.loc.start.line) return baseIndent;
5621
+ if (lineNumber < parsed.label.loc.end.line) return baseIndent + 2;
5622
+ if (lineNumber === parsed.label.loc.end.line) {
5623
+ const line = getParsedLines(sourceCode).get(lineNumber);
5624
+ if (!line) return baseIndent;
5625
+ const between = line.text.slice(column - 1, parsed.label.loc.end.column - 2);
5626
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5627
+ }
5628
+ return baseIndent;
5629
+ });
5630
+ }
5631
+ /**
5632
+ * Verify an image node.
5633
+ */
5634
+ function verifyImage(node) {
5635
+ const loc = sourceCode.getLoc(node);
5636
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5637
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5638
+ const parsed = parseImage(sourceCode, node);
5639
+ if (!parsed) return;
5640
+ verifyLinesIndent(lines, (lineNumber, column) => {
5641
+ const baseIndent = blockStack.getExpectedIndent({
5642
+ lineNumber,
5643
+ block: false
5644
+ });
5645
+ if (baseIndent === "ignore") return "ignore";
5646
+ if (lineNumber <= loc.start.line) return baseIndent;
5647
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5648
+ if (lineNumber === parsed.text.loc.end.line) {
5649
+ const line = getParsedLines(sourceCode).get(lineNumber);
5650
+ if (!line) return baseIndent;
5651
+ const between = line.text.slice(column - 1, parsed.text.loc.end.column - 2);
5652
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5653
+ }
5654
+ if (lineNumber <= parsed.openingParen.loc.end.line) return baseIndent;
5655
+ if (lineNumber <= parsed.destination.loc.end.line) return baseIndent + 2;
5656
+ if (!parsed.title) return baseIndent;
5657
+ if (lineNumber <= parsed.title.loc.start.line) return baseIndent + 2;
5658
+ if (lineNumber < parsed.title.loc.end.line) return baseIndent + 4;
5659
+ if (lineNumber === parsed.title.loc.end.line) {
5660
+ const line = getParsedLines(sourceCode).get(lineNumber);
5661
+ if (!line) return baseIndent;
5662
+ const between = line.text.slice(column - 1, parsed.title.loc.end.column - 2);
5663
+ return !between || isWhitespace(between) ? baseIndent + 2 : baseIndent + 4;
5664
+ }
5665
+ return baseIndent;
5666
+ });
5667
+ }
5668
+ /**
5669
+ * Verify an image reference node.
5670
+ */
5671
+ function verifyImageReference(node) {
5672
+ const loc = sourceCode.getLoc(node);
5673
+ const parsed = parseImageReference(sourceCode, node);
5674
+ if (!parsed) return;
5675
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5676
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5677
+ verifyLinesIndent(lines, (lineNumber, column) => {
5678
+ const baseIndent = blockStack.getExpectedIndent({
5679
+ lineNumber,
5680
+ block: false
5681
+ });
5682
+ if (baseIndent === "ignore") return "ignore";
5683
+ if (lineNumber <= loc.start.line) return baseIndent;
5684
+ if (lineNumber < parsed.text.loc.end.line) return baseIndent + 2;
5685
+ if (lineNumber === parsed.text.loc.end.line) {
5686
+ const line = getParsedLines(sourceCode).get(lineNumber);
5687
+ if (!line) return baseIndent;
5688
+ const between = line.text.slice(column - 1, parsed.text.loc.end.column - 2);
5689
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5690
+ }
5691
+ if (!parsed.label) return baseIndent;
5692
+ if (lineNumber <= parsed.label.loc.start.line) return baseIndent;
5693
+ if (lineNumber < parsed.label.loc.end.line) return baseIndent + 2;
5694
+ if (lineNumber === parsed.label.loc.end.line) {
5695
+ const line = getParsedLines(sourceCode).get(lineNumber);
5696
+ if (!line) return baseIndent;
5697
+ const between = line.text.slice(column - 1, parsed.label.loc.end.column - 2);
5698
+ return !between || isWhitespace(between) ? baseIndent : baseIndent + 2;
5699
+ }
5700
+ return baseIndent;
5701
+ });
5702
+ }
5703
+ /**
5704
+ * Verify a text node.
5705
+ */
5706
+ function verifyText(node) {
5707
+ const loc = sourceCode.getLoc(node);
5708
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5709
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5710
+ if (node.value.endsWith("\n")) lines = lines.slice(0, -1);
5711
+ verifyLinesIndent(lines, (lineNumber) => blockStack.getExpectedIndent({
5712
+ lineNumber,
5713
+ block: false
5714
+ }));
5715
+ }
5716
+ /**
5717
+ * Verify an inline node.
5718
+ */
5719
+ function verifyInline(node) {
5720
+ const loc = sourceCode.getLoc(node);
5721
+ let lines = lineNumbersFromRange(loc.start.line, loc.end.line);
5722
+ if (!inlineToBeChecked(loc.start)) lines = lines.slice(1);
5723
+ verifyLinesIndent(lines, (lineNumber) => blockStack.getExpectedIndent({
5724
+ lineNumber,
5725
+ block: false
5726
+ }));
5727
+ }
5728
+ /**
5729
+ * Check whether the inline node should be checked.
5730
+ */
5731
+ function inlineToBeChecked(position) {
5732
+ const blockquote = blockStack.getCurrentBlockquote();
5733
+ const listItem = blockStack.getCurrentListItemAtLine(position.line);
5734
+ const lineText = sourceCode.lines[position.line - 1];
5735
+ if (listItem) {
5736
+ const parsed = listItem.getParsedListItem();
5737
+ const indentText = lineText.slice((parsed.taskListItemMarker?.loc ?? parsed.marker.loc).end.column - 1, position.column - 1);
5738
+ if (indentText && !isSpaceOrTab(indentText)) return false;
5739
+ } else if (blockquote) {
5740
+ if (atWidth(lineText, blockquote.getMarkerIndent()) !== ">") return false;
5741
+ const indentText = sliceWidth(lineText.slice(0, position.column - 1), blockquote.getMarkerIndent() + 1);
5742
+ if (indentText && !isSpaceOrTab(indentText)) return false;
5743
+ } else {
5744
+ const indentText = lineText.slice(0, position.column - 1);
5745
+ if (indentText && !isSpaceOrTab(indentText)) return false;
5746
+ }
5747
+ return true;
5748
+ }
5749
+ /**
5750
+ * Get line numbers from the range.
5751
+ */
5752
+ function lineNumbersFromRange(lineNumberFrom, lineNumberTo) {
5753
+ const lines = [];
5754
+ for (let lineNumber = lineNumberFrom; lineNumber <= lineNumberTo; lineNumber++) lines.push(lineNumber);
5755
+ return lines;
5756
+ }
5757
+ /**
5758
+ * Verify the indentation of the lines.
5759
+ */
5760
+ function verifyLinesIndent(lineNumbers, expectedIndentWidthProvider, additionalFixes) {
5761
+ const blockquote = blockStack.getCurrentBlockquote();
5762
+ if (!blockquote) {
5763
+ verifyLinesIndentFromRoot(lineNumbers, expectedIndentWidthProvider, additionalFixes);
5764
+ return;
5765
+ }
5766
+ verifyLinesIndentFromBlockquoteMarker(lineNumbers, blockquote, expectedIndentWidthProvider, additionalFixes);
5767
+ }
5768
+ /**
5769
+ * Verify the indentation of the lines from the root.
5770
+ */
5771
+ function verifyLinesIndentFromRoot(lineNumbers, expectedIndentWidthProvider, additionalFixes) {
5772
+ const reports = [];
5773
+ for (const lineNumber of lineNumbers) {
5774
+ const line = getParsedLines(sourceCode).get(lineNumber);
5775
+ if (!line) return;
5776
+ const listItem = blockStack.getCurrentListItemAtLine(lineNumber);
5777
+ if (!listItem) {
5778
+ const indentText = /^\s*/u.exec(line.text)[0];
5779
+ const actualIndentWidth = getWidth(indentText);
5780
+ const expectedIndentWidth = expectedIndentWidthProvider(lineNumber, 1);
5781
+ if (expectedIndentWidth === "ignore") continue;
5782
+ if (actualIndentWidth === expectedIndentWidth) continue;
5783
+ reports.push({
5784
+ loc: {
5785
+ start: {
5786
+ line: line.line,
5787
+ column: 1
5788
+ },
5789
+ end: {
5790
+ line: line.line,
5791
+ column: indentText.length + 1
5792
+ }
5793
+ },
5794
+ messageId: indentText.includes(" ") ? "wrongIndentationWithTab" : "wrongIndentation",
5795
+ data: {
5796
+ expected: expectedIndentWidth,
5797
+ actual: actualIndentWidth
5798
+ },
5799
+ fix(fixer) {
5800
+ return fixer.replaceTextRange([line.range[0], line.range[0] + indentText.length], " ".repeat(expectedIndentWidth));
5801
+ },
5802
+ expectedIndentWidth,
5803
+ actualIndentWidth
5804
+ });
5805
+ continue;
5806
+ }
5807
+ const report = verifyLineIndentFromListItemMarker(line, listItem, expectedIndentWidthProvider);
5808
+ if (!report) continue;
5809
+ reports.push(report);
5810
+ }
5811
+ if (!reports.length) return;
5812
+ for (const report of reports) reportIncorrectIndent({
5813
+ loc: report.loc,
5814
+ messageId: report.messageId,
5815
+ data: report.data,
5816
+ *fix(fixer) {
5817
+ yield report.fix(fixer);
5818
+ if (additionalFixes) yield* additionalFixes(fixer, report) ?? [];
5819
+ }
5820
+ });
5821
+ }
5822
+ /**
5823
+ * Verify the indentation of the lines from the blockquote marker.
5824
+ */
5825
+ function verifyLinesIndentFromBlockquoteMarker(lineNumbers, blockquote, expectedIndentWidthProvider, additionalFixes) {
5826
+ const blockquoteLoc = sourceCode.getLoc(blockquote.node);
5827
+ const reports = [];
5828
+ let cannotFix = false;
5829
+ for (const lineNumber of lineNumbers) {
5830
+ const line = getParsedLines(sourceCode).get(lineNumber);
5831
+ if (!line) return;
5832
+ if (atWidth(line.text, blockquote.getMarkerIndent()) !== ">") {
5833
+ cannotFix = true;
5834
+ continue;
5835
+ }
5836
+ const listItem = blockStack.getCurrentListItemAtLine(lineNumber);
5837
+ if (!listItem) {
5838
+ const before = line.text.slice(0, blockquoteLoc.start.column);
5839
+ const after = line.text.slice(blockquoteLoc.start.column);
5840
+ const indentText = /^\s*/u.exec(after)[0];
5841
+ const actualIndentWidth = getWidth(before + indentText);
5842
+ const expectedIndentWidth = expectedIndentWidthProvider(lineNumber, blockquoteLoc.start.column + 1);
5843
+ if (expectedIndentWidth === "ignore") continue;
5844
+ if (actualIndentWidth === expectedIndentWidth) continue;
5845
+ const linePrefixWidth = getWidth(before);
5846
+ reports.push({
5847
+ loc: {
5848
+ start: {
5849
+ line: line.line,
5850
+ column: blockquoteLoc.start.column + 1
5851
+ },
5852
+ end: {
5853
+ line: line.line,
5854
+ column: blockquoteLoc.start.column + 1 + indentText.length
5855
+ }
5856
+ },
5857
+ messageId: indentText.includes(" ") ? "wrongIndentationFromBlockquoteMarkerWithTab" : "wrongIndentationFromBlockquoteMarker",
5858
+ data: {
5859
+ expected: expectedIndentWidth - linePrefixWidth,
5860
+ actual: actualIndentWidth - linePrefixWidth
5861
+ },
5862
+ fix(fixer) {
5863
+ return fixer.replaceTextRange([line.range[0] + blockquoteLoc.start.column, line.range[0] + blockquoteLoc.start.column + indentText.length], " ".repeat(expectedIndentWidth - linePrefixWidth));
5864
+ },
5865
+ expectedIndentWidth,
5866
+ actualIndentWidth
5867
+ });
5868
+ continue;
5869
+ }
5870
+ const report = verifyLineIndentFromListItemMarker(line, listItem, expectedIndentWidthProvider);
5871
+ if (!report) continue;
5872
+ reports.push(report);
5873
+ }
5874
+ if (!reports.length) return;
5875
+ for (const report of reports) reportIncorrectIndent({
5876
+ loc: report.loc,
5877
+ messageId: report.messageId,
5878
+ data: report.data,
5879
+ *fix(fixer) {
5880
+ if (cannotFix) return;
5881
+ yield report.fix(fixer);
5882
+ if (additionalFixes) yield* additionalFixes(fixer, report) ?? [];
5883
+ }
5884
+ });
5885
+ }
5886
+ /**
5887
+ * Verify the indentation of the line from the list item marker.
5888
+ */
5889
+ function verifyLineIndentFromListItemMarker(line, listItem, expectedIndentWidthProvider) {
5890
+ const parsed = listItem.getParsedListItem();
5891
+ const markerAfterColumn = (parsed.taskListItemMarker?.loc ?? parsed.marker.loc).end.column;
5892
+ const before = line.text.slice(0, markerAfterColumn - 1);
5893
+ const after = line.text.slice(markerAfterColumn - 1);
5894
+ const indentText = /^\s*/u.exec(after)[0];
5895
+ const actualIndentWidth = getWidth(before + indentText);
5896
+ const expectedIndentWidth = expectedIndentWidthProvider(line.line, markerAfterColumn);
5897
+ if (expectedIndentWidth === "ignore") return null;
5898
+ if (actualIndentWidth === expectedIndentWidth) return null;
5899
+ const linePrefixWidth = getWidth(before);
5900
+ const range = [line.range[0] + before.length, line.range[0] + before.length + indentText.length];
5901
+ return {
5902
+ loc: getSourceLocationFromRange(sourceCode, listItem.node, range),
5903
+ messageId: indentText.includes(" ") ? "wrongIndentationFromListMarkerWithTab" : "wrongIndentationFromListMarker",
5904
+ data: {
5905
+ expected: expectedIndentWidth - linePrefixWidth,
5906
+ actual: actualIndentWidth - linePrefixWidth
5907
+ },
5908
+ fix(fixer) {
5909
+ return fixer.replaceTextRange(range, " ".repeat(expectedIndentWidth - linePrefixWidth));
5910
+ },
5911
+ expectedIndentWidth,
5912
+ actualIndentWidth
5913
+ };
5914
+ }
5915
+ }
5916
+ });
5917
+
5918
+ //#endregion
5919
+ //#region src/utils/setext-heading.ts
5920
+ /**
5921
+ * Parse the setext heading.
5922
+ */
5923
+ function parseSetextHeading(sourceCode, node) {
5924
+ if (getHeadingKind(sourceCode, node) !== "setext") return null;
5925
+ const lines = getParsedLines(sourceCode);
5926
+ const contentLines = [];
5927
+ const nodeLoc = sourceCode.getLoc(node);
5928
+ for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
5929
+ const content = parseContent(lines.get(lineNumber));
5930
+ contentLines.push(content);
5931
+ }
5932
+ const underline = parseUnderline(lines.get(nodeLoc.end.line));
5933
+ if (!underline) return null;
5934
+ return {
5935
+ contentLines,
5936
+ underline
5937
+ };
5938
+ }
5939
+ /**
5940
+ * Parse the content line of a setext heading.
5941
+ */
5942
+ function parseContent(line) {
5943
+ let prefix = "";
5944
+ let spaceBefore = "";
5945
+ let suffix = "";
5946
+ for (let index = 0; index < line.text.length; index++) {
5947
+ const c = line.text[index];
5948
+ if (!c.trim()) {
5949
+ spaceBefore += c;
5950
+ continue;
5951
+ }
5952
+ if (c === ">" && spaceBefore.length < 4) {
5953
+ prefix += spaceBefore + c;
5954
+ spaceBefore = "";
5955
+ continue;
5956
+ }
5957
+ suffix = line.text.slice(index);
5958
+ break;
5959
+ }
5960
+ const content = suffix.trimEnd();
5961
+ const spaceAfter = suffix.slice(content.length);
5962
+ return {
5963
+ text: content,
5964
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
5965
+ loc: {
5966
+ start: {
5967
+ line: line.line,
5968
+ column: prefix.length + spaceBefore.length + 1
5969
+ },
5970
+ end: {
5971
+ line: line.line,
5972
+ column: prefix.length + spaceBefore.length + content.length + 1
5973
+ }
5974
+ },
5975
+ raws: {
5976
+ prefix,
5977
+ spaceBefore,
5978
+ spaceAfter
5979
+ }
5980
+ };
5981
+ }
5982
+ /**
5983
+ * Parse the underline of a setext heading.
5984
+ */
5985
+ function parseUnderline(line) {
5986
+ let marker = null;
5987
+ let underlineText = "";
5988
+ let prefix = "";
5989
+ let spaceBefore = "";
5990
+ let spaceAfter = "";
5991
+ for (let index = line.text.length - 1; index >= 0; index--) {
5992
+ const c = line.text[index];
5993
+ if (!marker) {
5994
+ if (c === "=" || c === "-") {
5995
+ underlineText = c + underlineText;
5996
+ marker = c;
5997
+ } else if (!c.trim()) spaceAfter = c + spaceAfter;
5998
+ else return null;
5999
+ continue;
6000
+ }
6001
+ if (c === marker) {
6002
+ underlineText = c + spaceBefore + underlineText;
6003
+ spaceBefore = "";
6004
+ } else if (!c.trim()) spaceBefore = c + spaceBefore;
6005
+ else {
6006
+ prefix = line.text.slice(0, index + 1);
6007
+ break;
6008
+ }
6009
+ }
6010
+ if (!marker) return null;
6011
+ const underlineLoc = {
6012
+ start: {
6013
+ line: line.line,
6014
+ column: prefix.length + spaceBefore.length + 1
6015
+ },
6016
+ end: {
6017
+ line: line.line,
6018
+ column: prefix.length + spaceBefore.length + underlineText.length + 1
6019
+ }
6020
+ };
6021
+ return {
6022
+ text: underlineText,
6023
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
6024
+ loc: underlineLoc,
6025
+ marker,
6026
+ raws: {
6027
+ prefix,
6028
+ spaceBefore,
6029
+ spaceAfter
6030
+ }
6031
+ };
6032
+ }
6033
+
6034
+ //#endregion
6035
+ //#region src/rules/level1-heading-style.ts
6036
+ var level1_heading_style_default = createRule("level1-heading-style", {
6037
+ meta: {
6038
+ type: "layout",
6039
+ docs: {
6040
+ description: "enforce consistent style for level 1 headings",
6041
+ categories: ["standard"],
6042
+ listCategory: "Stylistic"
6043
+ },
6044
+ fixable: "code",
6045
+ hasSuggestions: false,
6046
+ schema: [{
6047
+ type: "object",
6048
+ properties: {
6049
+ style: { enum: ["atx", "setext"] },
6050
+ allowMultilineSetext: { type: "boolean" }
6051
+ },
6052
+ additionalProperties: false
6053
+ }],
6054
+ messages: {
6055
+ expectedAtx: "Expected ATX style heading (# Heading).",
6056
+ expectedSetext: "Expected Setext style heading (Heading\\n======).",
6057
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
6058
+ }
6059
+ },
6060
+ create(context) {
6061
+ const sourceCode = context.sourceCode;
6062
+ const opt = context.options[0] || {};
6063
+ const style = opt.style ?? "atx";
6064
+ const allowMultilineSetext = opt.allowMultilineSetext;
6065
+ return { heading(node) {
6066
+ if (node.depth !== 1) return;
6067
+ const headingKind = getHeadingKind(sourceCode, node);
6068
+ if (style === "atx") {
6069
+ if (headingKind !== "setext") return;
6070
+ const parsed = parseSetextHeading(sourceCode, node);
6071
+ if (!parsed) return;
6072
+ if (parsed.contentLines.length > 1) {
6073
+ if (allowMultilineSetext) return;
6074
+ context.report({
6075
+ node,
6076
+ messageId: "multilineSetextNotAllowed"
6077
+ });
6078
+ return;
6079
+ }
6080
+ context.report({
6081
+ node,
6082
+ messageId: "expectedAtx",
6083
+ *fix(fixer) {
6084
+ const heading = parsed.contentLines[0];
6085
+ yield fixer.insertTextBeforeRange(heading.range, "# ");
6086
+ const lines = getParsedLines(sourceCode);
6087
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
6088
+ }
6089
+ });
6090
+ } else if (style === "setext") {
6091
+ if (headingKind !== "atx" || node.children.length === 0) return;
6092
+ context.report({
6093
+ node,
6094
+ messageId: "expectedSetext",
6095
+ *fix(fixer) {
6096
+ const parsed = parseATXHeading(sourceCode, node);
6097
+ if (!parsed) return;
6098
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.content.range[0]]);
6099
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.content.range[1], parsed.after?.range[1] ?? parsed.closingSequence.range[1]]);
6100
+ const lines = getParsedLines(sourceCode);
6101
+ const underline = "=".repeat(Math.max(getTextWidth(parsed.content.text), 3));
6102
+ const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
6103
+ yield fixer.insertTextAfter(node, appendingText);
6104
+ }
6105
+ });
6106
+ }
6107
+ } };
6108
+ }
6109
+ });
6110
+
6111
+ //#endregion
6112
+ //#region src/rules/level2-heading-style.ts
6113
+ var level2_heading_style_default = createRule("level2-heading-style", {
6114
+ meta: {
6115
+ type: "layout",
6116
+ docs: {
6117
+ description: "enforce consistent style for level 2 headings",
6118
+ categories: ["standard"],
6119
+ listCategory: "Stylistic"
6120
+ },
6121
+ fixable: "code",
6122
+ hasSuggestions: false,
6123
+ schema: [{
6124
+ type: "object",
6125
+ properties: {
6126
+ style: { enum: ["atx", "setext"] },
6127
+ allowMultilineSetext: { type: "boolean" }
6128
+ },
6129
+ additionalProperties: false
6130
+ }],
6131
+ messages: {
6132
+ expectedAtx: "Expected ATX style heading (## Heading).",
6133
+ expectedSetext: "Expected Setext style heading (Heading\\n------).",
6134
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
5144
6135
  }
5145
- return false;
6136
+ },
6137
+ create(context) {
6138
+ const sourceCode = context.sourceCode;
6139
+ const opt = context.options[0] || {};
6140
+ const style = opt.style ?? "atx";
6141
+ const allowMultilineSetext = opt.allowMultilineSetext;
6142
+ return { heading(node) {
6143
+ if (node.depth !== 2) return;
6144
+ const headingKind = getHeadingKind(sourceCode, node);
6145
+ if (style === "atx") {
6146
+ if (headingKind !== "setext") return;
6147
+ const parsed = parseSetextHeading(sourceCode, node);
6148
+ if (!parsed) return;
6149
+ if (parsed.contentLines.length > 1) {
6150
+ if (allowMultilineSetext) return;
6151
+ context.report({
6152
+ node,
6153
+ messageId: "multilineSetextNotAllowed"
6154
+ });
6155
+ return;
6156
+ }
6157
+ context.report({
6158
+ node,
6159
+ messageId: "expectedAtx",
6160
+ *fix(fixer) {
6161
+ const heading = parsed.contentLines[0];
6162
+ yield fixer.insertTextBeforeRange(heading.range, "## ");
6163
+ const lines = getParsedLines(sourceCode);
6164
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
6165
+ }
6166
+ });
6167
+ } else if (style === "setext") {
6168
+ if (headingKind !== "atx" || node.children.length === 0) return;
6169
+ context.report({
6170
+ node,
6171
+ messageId: "expectedSetext",
6172
+ *fix(fixer) {
6173
+ const parsed = parseATXHeading(sourceCode, node);
6174
+ if (!parsed) return;
6175
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.content.range[0]]);
6176
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.content.range[1], parsed.after?.range[1] ?? parsed.closingSequence.range[1]]);
6177
+ const lines = getParsedLines(sourceCode);
6178
+ const underline = "-".repeat(Math.max(getTextWidth(parsed.content.text), 3));
6179
+ const appendingText = `\n${lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1)}${underline}`;
6180
+ yield fixer.insertTextAfter(node, appendingText);
6181
+ }
6182
+ });
6183
+ }
6184
+ } };
5146
6185
  }
5147
- }
6186
+ });
5148
6187
 
5149
6188
  //#endregion
5150
6189
  //#region src/rules/link-bracket-newline.ts
@@ -5179,11 +6218,11 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
5179
6218
  },
5180
6219
  create(context) {
5181
6220
  const sourceCode = context.sourceCode;
5182
- const optionProvider = parseOptions$3(context.options[0]);
6221
+ const optionProvider = parseOptions$4(context.options[0]);
5183
6222
  /**
5184
6223
  * Parse the options.
5185
6224
  */
5186
- function parseOptions$3(option) {
6225
+ function parseOptions$4(option) {
5187
6226
  const newline = option?.newline ?? "never";
5188
6227
  const multiline = option?.multiline ?? false;
5189
6228
  return (bracketsRange) => {
@@ -5624,6 +6663,265 @@ function hasLoneLastBackslash(text) {
5624
6663
  return escapeText.length % 2 === 1;
5625
6664
  }
5626
6665
 
6666
+ //#endregion
6667
+ //#region src/rules/link-paren-newline.ts
6668
+ var link_paren_newline_default = createRule("link-paren-newline", {
6669
+ meta: {
6670
+ type: "layout",
6671
+ docs: {
6672
+ description: "enforce linebreaks after opening and before closing link parentheses",
6673
+ categories: ["standard"],
6674
+ listCategory: "Stylistic"
6675
+ },
6676
+ fixable: "whitespace",
6677
+ hasSuggestions: false,
6678
+ schema: [{
6679
+ type: "object",
6680
+ properties: {
6681
+ newline: { enum: [
6682
+ "always",
6683
+ "never",
6684
+ "consistent"
6685
+ ] },
6686
+ multiline: { type: "boolean" }
6687
+ },
6688
+ additionalProperties: false
6689
+ }],
6690
+ messages: {
6691
+ expectedNewlineAfterOpeningParen: "Expected a linebreak after this opening parenthesis.",
6692
+ unexpectedNewlineAfterOpeningParen: "Unexpected linebreak after this opening parenthesis.",
6693
+ expectedNewlineBeforeClosingParen: "Expected a linebreak before this closing parenthesis.",
6694
+ unexpectedNewlineBeforeClosingParen: "Unexpected linebreak before this closing parenthesis."
6695
+ }
6696
+ },
6697
+ create(context) {
6698
+ const sourceCode = context.sourceCode;
6699
+ const optionProvider = parseOptions$4(context.options[0]);
6700
+ /**
6701
+ * Parse the options.
6702
+ */
6703
+ function parseOptions$4(option) {
6704
+ const newline = option?.newline ?? "never";
6705
+ const multiline = option?.multiline ?? false;
6706
+ return (openingParenIndex, closingParenIndex) => {
6707
+ if (multiline) {
6708
+ if (sourceCode.text.slice(openingParenIndex + 1, closingParenIndex).trim().includes("\n")) return "always";
6709
+ }
6710
+ return newline;
6711
+ };
6712
+ }
6713
+ /**
6714
+ * Verify the newline around the parentheses.
6715
+ */
6716
+ function verifyNewlineAroundParens(node, openingParenIndex, closingParenIndex) {
6717
+ const newline = optionProvider(openingParenIndex, closingParenIndex);
6718
+ const spaceAfterOpeningParen = getSpaceAfterOpeningParen(openingParenIndex);
6719
+ verifyNewlineAfterOpeningParen(node, spaceAfterOpeningParen, openingParenIndex, newline);
6720
+ const newlineOptionBeforeClosingParen = newline === "consistent" ? getSpaceAfterOpeningParen(openingParenIndex).includes("\n") ? "always" : "never" : newline;
6721
+ verifyNewlineBeforeClosingParen(node, getSpaceBeforeClosingParen(closingParenIndex), openingParenIndex, closingParenIndex, newlineOptionBeforeClosingParen);
6722
+ }
6723
+ /**
6724
+ * Verify the newline after the opening parenthesis and before the closing parenthesis.
6725
+ */
6726
+ function verifyNewlineAfterOpeningParen(node, spaceAfterOpeningParen, openingParenIndex, newline) {
6727
+ if (newline === "always") {
6728
+ if (spaceAfterOpeningParen.includes("\n")) return;
6729
+ const loc = getSourceLocationFromRange(sourceCode, node, [openingParenIndex, openingParenIndex + 1]);
6730
+ context.report({
6731
+ node,
6732
+ loc,
6733
+ messageId: "expectedNewlineAfterOpeningParen",
6734
+ fix: (fixer) => fixer.insertTextAfterRange([openingParenIndex, openingParenIndex + 1], `\n${" ".repeat(loc.start.column)}${spaceAfterOpeningParen}`)
6735
+ });
6736
+ } else if (newline === "never") {
6737
+ if (!spaceAfterOpeningParen.includes("\n")) return;
6738
+ context.report({
6739
+ node,
6740
+ loc: getSourceLocationFromRange(sourceCode, node, [openingParenIndex + 1, openingParenIndex + 1 + spaceAfterOpeningParen.length]),
6741
+ messageId: "unexpectedNewlineAfterOpeningParen",
6742
+ fix: (fixer) => fixer.replaceTextRange([openingParenIndex + 1, openingParenIndex + 1 + spaceAfterOpeningParen.length], " ")
6743
+ });
6744
+ }
6745
+ }
6746
+ /**
6747
+ * Verify the newline before the closing parenthesis.
6748
+ */
6749
+ function verifyNewlineBeforeClosingParen(node, spaceBeforeClosingParen, openingParenIndex, closingParenIndex, newline) {
6750
+ if (openingParenIndex + 1 === closingParenIndex - spaceBeforeClosingParen.length) return;
6751
+ if (newline === "always") {
6752
+ if (spaceBeforeClosingParen.includes("\n")) return;
6753
+ context.report({
6754
+ node,
6755
+ loc: getSourceLocationFromRange(sourceCode, node, [closingParenIndex, closingParenIndex + 1]),
6756
+ messageId: "expectedNewlineBeforeClosingParen",
6757
+ fix: (fixer) => {
6758
+ const openingParenLoc = getSourceLocationFromRange(sourceCode, node, [openingParenIndex, openingParenIndex + 1]);
6759
+ return fixer.insertTextBeforeRange([closingParenIndex, closingParenIndex + 1], `\n${" ".repeat(openingParenLoc.start.column)}`);
6760
+ }
6761
+ });
6762
+ } else if (newline === "never") {
6763
+ if (!spaceBeforeClosingParen.includes("\n")) return;
6764
+ context.report({
6765
+ node,
6766
+ loc: getSourceLocationFromRange(sourceCode, node, [closingParenIndex - spaceBeforeClosingParen.length, closingParenIndex]),
6767
+ messageId: "unexpectedNewlineBeforeClosingParen",
6768
+ fix: (fixer) => fixer.replaceTextRange([closingParenIndex - spaceBeforeClosingParen.length, closingParenIndex], " ")
6769
+ });
6770
+ }
6771
+ }
6772
+ return {
6773
+ link(node) {
6774
+ if (getLinkKind(sourceCode, node) !== "inline") return;
6775
+ const parsed = parseInlineLink(sourceCode, node);
6776
+ if (!parsed) return;
6777
+ verifyNewlineAroundParens(node, parsed.openingParen.range[0], parsed.closingParen.range[0]);
6778
+ },
6779
+ image(node) {
6780
+ const parsed = parseImage(sourceCode, node);
6781
+ if (!parsed) return;
6782
+ verifyNewlineAroundParens(node, parsed.openingParen.range[0], parsed.closingParen.range[0]);
6783
+ }
6784
+ };
6785
+ /**
6786
+ * Get the spaces after the opening parenthesis.
6787
+ */
6788
+ function getSpaceAfterOpeningParen(openingParenIndex) {
6789
+ for (let i = openingParenIndex + 1; i < sourceCode.text.length; i++) {
6790
+ const char = sourceCode.text[i];
6791
+ if (isWhitespace(char)) continue;
6792
+ return sourceCode.text.slice(openingParenIndex + 1, i);
6793
+ }
6794
+ return sourceCode.text.slice(openingParenIndex + 1);
6795
+ }
6796
+ /**
6797
+ * Get the spaces before the closing parenthesis.
6798
+ */
6799
+ function getSpaceBeforeClosingParen(closingParenIndex) {
6800
+ for (let i = closingParenIndex - 1; i >= 0; i--) {
6801
+ const char = sourceCode.text[i];
6802
+ if (isWhitespace(char)) continue;
6803
+ return sourceCode.text.slice(i + 1, closingParenIndex);
6804
+ }
6805
+ return sourceCode.text.slice(0, closingParenIndex);
6806
+ }
6807
+ }
6808
+ });
6809
+
6810
+ //#endregion
6811
+ //#region src/rules/link-paren-spacing.ts
6812
+ var link_paren_spacing_default = createRule("link-paren-spacing", {
6813
+ meta: {
6814
+ type: "layout",
6815
+ docs: {
6816
+ description: "enforce consistent spacing inside link parentheses",
6817
+ categories: ["standard"],
6818
+ listCategory: "Stylistic"
6819
+ },
6820
+ fixable: "whitespace",
6821
+ hasSuggestions: false,
6822
+ schema: [{
6823
+ type: "object",
6824
+ properties: { space: { enum: ["always", "never"] } },
6825
+ additionalProperties: false
6826
+ }],
6827
+ messages: {
6828
+ unexpectedSpaceAfterOpeningParen: "Unexpected space after opening parenthesis.",
6829
+ expectedSpaceAfterOpeningParen: "Expected space after opening parenthesis.",
6830
+ unexpectedSpaceBeforeClosingParen: "Unexpected space before closing parenthesis.",
6831
+ expectedSpaceBeforeClosingParen: "Expected space before closing parenthesis."
6832
+ }
6833
+ },
6834
+ create(context) {
6835
+ const sourceCode = context.sourceCode;
6836
+ const spaceOption = context.options[0]?.space || "never";
6837
+ /**
6838
+ * Verify the space after the opening paren and before the closing paren.
6839
+ */
6840
+ function verifySpaceAfterOpeningParen(node, openingParen) {
6841
+ const space = getSpaceAfterOpeningParen(openingParen);
6842
+ if (space.includes("\n")) return;
6843
+ if (spaceOption === "always") {
6844
+ if (space.length > 0) return;
6845
+ context.report({
6846
+ node,
6847
+ loc: openingParen.loc,
6848
+ messageId: "expectedSpaceAfterOpeningParen",
6849
+ fix: (fixer) => fixer.insertTextAfterRange(openingParen.range, " ")
6850
+ });
6851
+ } else if (spaceOption === "never") {
6852
+ if (space.length === 0) return;
6853
+ context.report({
6854
+ node,
6855
+ loc: getSourceLocationFromRange(sourceCode, node, [openingParen.range[1], openingParen.range[1] + space.length]),
6856
+ messageId: "unexpectedSpaceAfterOpeningParen",
6857
+ fix: (fixer) => fixer.removeRange([openingParen.range[1], openingParen.range[1] + space.length])
6858
+ });
6859
+ }
6860
+ }
6861
+ /**
6862
+ * Verify the space before the closing paren.
6863
+ */
6864
+ function verifySpaceBeforeClosingParen(node, closingParen) {
6865
+ const space = getSpaceBeforeClosingParen(closingParen);
6866
+ if (space.includes("\n")) return;
6867
+ if (spaceOption === "always") {
6868
+ if (space.length > 0) return;
6869
+ context.report({
6870
+ node,
6871
+ loc: closingParen.loc,
6872
+ messageId: "expectedSpaceBeforeClosingParen",
6873
+ fix: (fixer) => fixer.insertTextBeforeRange(closingParen.range, " ")
6874
+ });
6875
+ } else if (spaceOption === "never") {
6876
+ if (space.length === 0) return;
6877
+ context.report({
6878
+ node,
6879
+ loc: getSourceLocationFromRange(sourceCode, node, [closingParen.range[0] - space.length, closingParen.range[0]]),
6880
+ messageId: "unexpectedSpaceBeforeClosingParen",
6881
+ fix: (fixer) => fixer.removeRange([closingParen.range[0] - space.length, closingParen.range[0]])
6882
+ });
6883
+ }
6884
+ }
6885
+ return {
6886
+ link(node) {
6887
+ if (getLinkKind(sourceCode, node) !== "inline") return;
6888
+ const parsed = parseInlineLink(sourceCode, node);
6889
+ if (!parsed) return;
6890
+ verifySpaceAfterOpeningParen(node, parsed.openingParen);
6891
+ verifySpaceBeforeClosingParen(node, parsed.closingParen);
6892
+ },
6893
+ image(node) {
6894
+ const parsed = parseImage(sourceCode, node);
6895
+ if (!parsed) return;
6896
+ verifySpaceAfterOpeningParen(node, parsed.openingParen);
6897
+ verifySpaceBeforeClosingParen(node, parsed.closingParen);
6898
+ }
6899
+ };
6900
+ /**
6901
+ * Get the spaces after the opening paren.
6902
+ */
6903
+ function getSpaceAfterOpeningParen(openingParen) {
6904
+ for (let i = openingParen.range[1]; i < sourceCode.text.length; i++) {
6905
+ const char = sourceCode.text[i];
6906
+ if (isWhitespace(char)) continue;
6907
+ return sourceCode.text.slice(openingParen.range[1], i);
6908
+ }
6909
+ return sourceCode.text.slice(openingParen.range[1]);
6910
+ }
6911
+ /**
6912
+ * Get the spaces before the closing paren.
6913
+ */
6914
+ function getSpaceBeforeClosingParen(closingParen) {
6915
+ for (let i = closingParen.range[0] - 1; i >= 0; i--) {
6916
+ const char = sourceCode.text[i];
6917
+ if (isWhitespace(char)) continue;
6918
+ return sourceCode.text.slice(i + 1, closingParen.range[0]);
6919
+ }
6920
+ return sourceCode.text.slice(0, closingParen.range[0]);
6921
+ }
6922
+ }
6923
+ });
6924
+
5627
6925
  //#endregion
5628
6926
  //#region src/rules/link-title-style.ts
5629
6927
  const STYLES = {
@@ -5768,10 +7066,22 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
5768
7066
  const items = listNode.children;
5769
7067
  if (items.length <= 1) return;
5770
7068
  const referenceMarkerLocation = getMarkerLocation(items[0]);
7069
+ const expectedWidth = getWidth(sourceCode.lines[referenceMarkerLocation.line - 1].slice(0, referenceMarkerLocation[alignPositionName]));
5771
7070
  for (const item of items.slice(1)) {
5772
7071
  const markerLocation = getMarkerLocation(item);
5773
- const diff = markerLocation[alignPositionName] - referenceMarkerLocation[alignPositionName];
7072
+ const actualWidth = getWidth(sourceCode.lines[markerLocation.line - 1].slice(0, markerLocation[alignPositionName]));
7073
+ const diff = actualWidth - expectedWidth;
5774
7074
  if (diff === 0) continue;
7075
+ const messageData = alignPositionName === "start" ? {
7076
+ expected: String(expectedWidth),
7077
+ actual: String(actualWidth)
7078
+ } : (() => {
7079
+ const start = getWidth(sourceCode.lines[markerLocation.line - 1].slice(0, markerLocation.start));
7080
+ return {
7081
+ expected: String(start - diff),
7082
+ actual: String(start)
7083
+ };
7084
+ })();
5775
7085
  context.report({
5776
7086
  node: item,
5777
7087
  loc: {
@@ -5785,10 +7095,7 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
5785
7095
  }
5786
7096
  },
5787
7097
  messageId: "incorrectAlignment",
5788
- data: {
5789
- expected: String(markerLocation.start - diff),
5790
- actual: String(markerLocation.start)
5791
- },
7098
+ data: messageData,
5792
7099
  fix(fixer) {
5793
7100
  const lines = getParsedLines(sourceCode);
5794
7101
  const line = lines.get(markerLocation.line);
@@ -5796,13 +7103,17 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
5796
7103
  const addSpaces = " ".repeat(-diff);
5797
7104
  return fixer.insertTextBeforeRange([line.range[0] + markerLocation.start, line.range[0] + markerLocation.start], addSpaces);
5798
7105
  }
5799
- const itemBefore = line.text.slice(0, markerLocation.start - diff);
5800
- if (itemBefore.includes(" ")) return null;
5801
- if (lines.get(referenceMarkerLocation.line).text.slice(0, referenceMarkerLocation.start) === itemBefore) {
5802
- const removeEndIndex = line.range[0] + markerLocation.start;
5803
- const removeStartIndex = removeEndIndex - diff;
5804
- return fixer.removeRange([removeStartIndex, removeEndIndex]);
7106
+ const beforeItemMarker = line.text.slice(0, markerLocation.start);
7107
+ const newWidth = getWidth(beforeItemMarker) - diff;
7108
+ let newBeforeItemMarker = beforeItemMarker;
7109
+ while (getWidth(newBeforeItemMarker) > newWidth) {
7110
+ const last = newBeforeItemMarker.at(-1);
7111
+ if (last && isWhitespace(last)) newBeforeItemMarker = newBeforeItemMarker.slice(0, -1);
7112
+ else return null;
5805
7113
  }
7114
+ if (getWidth(newBeforeItemMarker) < newWidth) newBeforeItemMarker += " ".repeat(newWidth - getWidth(newBeforeItemMarker));
7115
+ const referenceBeforeItemMarker = lines.get(referenceMarkerLocation.line).text.slice(0, referenceMarkerLocation.start);
7116
+ if (!referenceBeforeItemMarker.includes(">") || referenceBeforeItemMarker === newBeforeItemMarker) return fixer.replaceTextRange([line.range[0], line.range[0] + markerLocation.start], newBeforeItemMarker);
5806
7117
  return null;
5807
7118
  }
5808
7119
  });
@@ -5936,6 +7247,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
5936
7247
  },
5937
7248
  create(context) {
5938
7249
  const sourceCode = context.sourceCode;
7250
+ let codeText = sourceCode.text;
5939
7251
  return {
5940
7252
  definition: verifyLinkDefinition,
5941
7253
  footnoteDefinition: verifyFootnoteDefinition,
@@ -5945,6 +7257,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
5945
7257
  link: verifyLink,
5946
7258
  linkReference: verifyLinkReference,
5947
7259
  listItem: verifyListItem,
7260
+ blockquote: processBlockquote,
5948
7261
  text: verifyText
5949
7262
  };
5950
7263
  /**
@@ -6018,7 +7331,13 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6018
7331
  * Verify a list item node
6019
7332
  */
6020
7333
  function verifyListItem(node) {
6021
- verifyTextOutsideChildren(node);
7334
+ const nodeRange = sourceCode.getRange(node);
7335
+ const parsed = parseListItem(sourceCode, node);
7336
+ if (parsed.taskListItemMarker) verifyTextInRange(node, [nodeRange[0], parsed.taskListItemMarker.range[0]]);
7337
+ let newCodeText = codeText.slice(0, parsed.marker.range[0]) + " ".repeat(parsed.marker.range[1] - parsed.marker.range[0]);
7338
+ 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]);
7339
+ else newCodeText += codeText.slice(parsed.marker.range[1]);
7340
+ codeText = newCodeText;
6022
7341
  }
6023
7342
  /**
6024
7343
  * Verify spaces in a node
@@ -6028,6 +7347,26 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6028
7347
  verifyTextInRange(node, nodeRange);
6029
7348
  }
6030
7349
  /**
7350
+ * Process a blockquote node
7351
+ */
7352
+ function processBlockquote(node) {
7353
+ const nodeRange = sourceCode.getRange(node);
7354
+ let newCodeText = "";
7355
+ let inIndent = true;
7356
+ for (let index = nodeRange[0]; index < nodeRange[1]; index++) {
7357
+ const c = codeText[index];
7358
+ if (c === "\n") {
7359
+ inIndent = true;
7360
+ continue;
7361
+ }
7362
+ if (isWhitespace(c)) continue;
7363
+ if (c === ">" && inIndent) newCodeText += `${codeText.slice(newCodeText.length, index)} `;
7364
+ inIndent = false;
7365
+ }
7366
+ newCodeText += codeText.slice(newCodeText.length);
7367
+ codeText = newCodeText;
7368
+ }
7369
+ /**
6031
7370
  * Verify spaces in a node excluding children
6032
7371
  */
6033
7372
  function verifyTextOutsideChildren(node) {
@@ -6046,14 +7385,14 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6046
7385
  */
6047
7386
  function verifyTextInRange(node, textRange) {
6048
7387
  const nodeRange = sourceCode.getRange(node);
6049
- const text = sourceCode.text.slice(...textRange);
7388
+ const text = codeText.slice(...textRange);
6050
7389
  const reSpaces = /\s{2,}|\n/gu;
6051
7390
  let match;
6052
7391
  while ((match = reSpaces.exec(text)) !== null) {
6053
7392
  const spaces = match[0];
6054
7393
  if (spaces.includes("\n")) {
6055
7394
  let c = "";
6056
- while ((c = text[reSpaces.lastIndex]) && (c === ">" || !c.trim())) reSpaces.lastIndex++;
7395
+ while ((c = text[reSpaces.lastIndex]) && isWhitespace(c)) reSpaces.lastIndex++;
6057
7396
  continue;
6058
7397
  }
6059
7398
  if (spaces.length < 2) continue;
@@ -6063,7 +7402,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6063
7402
  if (nodeRange[0] === range[0]) {
6064
7403
  let isIndentation = true;
6065
7404
  for (let index = nodeRange[0] - 1; index >= 0; index--) {
6066
- const c = sourceCode.text[index];
7405
+ const c = codeText[index];
6067
7406
  if (c === "\n") break;
6068
7407
  if (isWhitespace(c)) continue;
6069
7408
  isIndentation = false;
@@ -6073,8 +7412,8 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
6073
7412
  }
6074
7413
  if (nodeRange[1] === range[1]) {
6075
7414
  let isTrailingSpaces = true;
6076
- for (let index = nodeRange[1]; index < sourceCode.text.length; index++) {
6077
- const c = sourceCode.text[index];
7415
+ for (let index = nodeRange[1]; index < codeText.length; index++) {
7416
+ const c = codeText[index];
6078
7417
  if (c === "\n") break;
6079
7418
  if (isWhitespace(c)) continue;
6080
7419
  isTrailingSpaces = false;
@@ -8538,11 +9877,14 @@ const rules$1 = [
8538
9877
  emphasis_delimiters_style_default,
8539
9878
  hard_linebreak_style_default,
8540
9879
  heading_casing_default,
9880
+ indent_default,
8541
9881
  level1_heading_style_default,
8542
9882
  level2_heading_style_default,
8543
9883
  link_bracket_newline_default,
8544
9884
  link_bracket_spacing_default,
8545
9885
  link_destination_style_default,
9886
+ link_paren_newline_default,
9887
+ link_paren_spacing_default,
8546
9888
  link_title_style_default,
8547
9889
  list_marker_alignment_default,
8548
9890
  no_laziness_blockquotes_default,
@@ -8570,8 +9912,7 @@ const rules$1 = [
8570
9912
 
8571
9913
  //#endregion
8572
9914
  //#region src/configs/recommended.ts
8573
- var recommended_exports = {};
8574
- __export(recommended_exports, {
9915
+ var recommended_exports = __export({
8575
9916
  files: () => files$1,
8576
9917
  language: () => language$1,
8577
9918
  languageOptions: () => languageOptions$1,
@@ -8601,8 +9942,7 @@ const rules$3 = {
8601
9942
 
8602
9943
  //#endregion
8603
9944
  //#region src/configs/standard.ts
8604
- var standard_exports = {};
8605
- __export(standard_exports, {
9945
+ var standard_exports = __export({
8606
9946
  files: () => files,
8607
9947
  language: () => language,
8608
9948
  languageOptions: () => languageOptions,
@@ -8629,11 +9969,14 @@ const rules$2 = {
8629
9969
  "markdown-preferences/code-fence-style": "error",
8630
9970
  "markdown-preferences/emphasis-delimiters-style": "error",
8631
9971
  "markdown-preferences/hard-linebreak-style": "error",
9972
+ "markdown-preferences/indent": "error",
8632
9973
  "markdown-preferences/level1-heading-style": "error",
8633
9974
  "markdown-preferences/level2-heading-style": "error",
8634
9975
  "markdown-preferences/link-bracket-newline": "error",
8635
9976
  "markdown-preferences/link-bracket-spacing": "error",
8636
9977
  "markdown-preferences/link-destination-style": "error",
9978
+ "markdown-preferences/link-paren-newline": "error",
9979
+ "markdown-preferences/link-paren-spacing": "error",
8637
9980
  "markdown-preferences/link-title-style": "error",
8638
9981
  "markdown-preferences/list-marker-alignment": "error",
8639
9982
  "markdown-preferences/no-laziness-blockquotes": "error",
@@ -8657,13 +10000,12 @@ const rules$2 = {
8657
10000
 
8658
10001
  //#endregion
8659
10002
  //#region src/meta.ts
8660
- var meta_exports = {};
8661
- __export(meta_exports, {
10003
+ var meta_exports = __export({
8662
10004
  name: () => name,
8663
10005
  version: () => version
8664
10006
  });
8665
10007
  const name = "eslint-plugin-markdown-preferences";
8666
- const version = "0.22.0";
10008
+ const version = "0.24.0";
8667
10009
 
8668
10010
  //#endregion
8669
10011
  //#region src/index.ts