eslint-plugin-markdown-preferences 0.17.0 → 0.19.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
@@ -73,13 +73,19 @@ function getListItemMarker(sourceCode, node) {
73
73
  if (matchDot) return {
74
74
  kind: ".",
75
75
  raw: matchDot[0],
76
- sequence: Number(matchDot[1])
76
+ sequence: {
77
+ value: Number(matchDot[1]),
78
+ raw: matchDot[1]
79
+ }
77
80
  };
78
81
  const matchParen = /^(\d+)\)/.exec(text);
79
82
  return {
80
83
  kind: ")",
81
84
  raw: matchParen[0],
82
- sequence: Number(matchParen[1])
85
+ sequence: {
86
+ value: Number(matchParen[1]),
87
+ raw: matchParen[1]
88
+ }
83
89
  };
84
90
  }
85
91
  /**
@@ -124,58 +130,162 @@ function getSourceLocationFromRange(sourceCode, node, range) {
124
130
  };
125
131
  }
126
132
 
133
+ //#endregion
134
+ //#region src/utils/unicode.ts
135
+ /**
136
+ * Check if the string is whitespace
137
+ */
138
+ function isWhitespace(string) {
139
+ return /^[\p{Zs}\t\n\f\r]+$/u.test(string);
140
+ }
141
+ /**
142
+ * Check if the string is a space or tab
143
+ */
144
+ function isSpaceOrTab(string) {
145
+ return /^[\t ]+$/u.test(string);
146
+ }
147
+ /**
148
+ * Check if the character is a punctuation character
149
+ */
150
+ function isPunctuation(char) {
151
+ return /^[\p{P}\p{S}]+$/u.test(char);
152
+ }
153
+
127
154
  //#endregion
128
155
  //#region src/utils/atx-heading.ts
129
156
  /**
130
- * Parse the closing sequence of an ATX heading.
157
+ * Parse the ATX heading.
131
158
  */
132
- function parseATXHeadingClosingSequence(sourceCode, node) {
159
+ function parseATXHeading(sourceCode, node) {
133
160
  if (getHeadingKind(sourceCode, node) !== "atx") return null;
134
161
  const loc = sourceCode.getLoc(node);
135
162
  const range = sourceCode.getRange(node);
136
- const parsed = parseATXHeadingClosingSequenceFromText(sourceCode.text.slice(...range));
137
- if (parsed == null) return { closingSequence: null };
138
- const rawAfter = {
139
- text: parsed.rawAfter,
140
- range: [range[1] - parsed.rawAfter.length, range[1]],
163
+ const text = sourceCode.text.slice(...range);
164
+ const parsedOpening = parseATXHeadingOpeningSequenceFromText(text);
165
+ if (parsedOpening === null) return null;
166
+ const openingSequence = {
167
+ text: parsedOpening.openingSequence,
168
+ range: [range[0], range[0] + parsedOpening.openingSequence.length],
169
+ loc: {
170
+ start: loc.start,
171
+ end: {
172
+ line: loc.start.line,
173
+ column: loc.start.column + parsedOpening.openingSequence.length
174
+ }
175
+ }
176
+ };
177
+ const spaceAfterOpening = {
178
+ text: parsedOpening.rawAfter,
179
+ range: [openingSequence.range[1], openingSequence.range[1] + parsedOpening.rawAfter.length],
180
+ loc: {
181
+ start: openingSequence.loc.end,
182
+ end: {
183
+ line: openingSequence.loc.end.line,
184
+ column: openingSequence.loc.end.column + parsedOpening.rawAfter.length
185
+ }
186
+ }
187
+ };
188
+ const parsedClosing = parseATXHeadingClosingSequenceFromText(text);
189
+ if (parsedClosing == null) {
190
+ const textAfterOpening = sourceCode.text.slice(spaceAfterOpening.range[1], range[1]);
191
+ const contentText$1 = textAfterOpening.trimEnd();
192
+ return {
193
+ openingSequence: {
194
+ ...openingSequence,
195
+ raws: { spaceAfter: spaceAfterOpening }
196
+ },
197
+ content: {
198
+ text: contentText$1,
199
+ range: [spaceAfterOpening.range[1], spaceAfterOpening.range[1] + contentText$1.length],
200
+ loc: {
201
+ start: spaceAfterOpening.loc.end,
202
+ end: {
203
+ line: loc.end.line,
204
+ column: loc.end.column - (textAfterOpening.length - contentText$1.length)
205
+ }
206
+ }
207
+ },
208
+ closingSequence: null
209
+ };
210
+ }
211
+ const spaceAfterClosing = {
212
+ text: parsedClosing.rawAfter,
213
+ range: [range[1] - parsedClosing.rawAfter.length, range[1]],
141
214
  loc: {
142
215
  start: {
143
216
  line: loc.end.line,
144
- column: loc.end.column - parsed.rawAfter.length
217
+ column: loc.end.column - parsedClosing.rawAfter.length
145
218
  },
146
- end: {
147
- line: loc.end.line,
148
- column: loc.end.column
149
- }
219
+ end: loc.end
150
220
  }
151
221
  };
152
222
  const closingSequence = {
153
- text: parsed.closingSequence,
154
- range: [rawAfter.range[0] - parsed.closingSequence.length, rawAfter.range[0]],
223
+ text: parsedClosing.closingSequence,
224
+ range: [spaceAfterClosing.range[0] - parsedClosing.closingSequence.length, spaceAfterClosing.range[0]],
155
225
  loc: {
156
226
  start: {
157
- line: rawAfter.loc.start.line,
158
- column: rawAfter.loc.start.column - parsed.closingSequence.length
227
+ line: spaceAfterClosing.loc.start.line,
228
+ column: spaceAfterClosing.loc.start.column - parsedClosing.closingSequence.length
159
229
  },
160
- end: rawAfter.loc.start
230
+ end: spaceAfterClosing.loc.start
161
231
  }
162
232
  };
163
- const rawBefore = {
164
- text: parsed.rawBefore,
165
- range: [closingSequence.range[0] - parsed.rawBefore.length, closingSequence.range[0]],
233
+ const spaceBeforeClosing = {
234
+ text: parsedClosing.rawBefore,
235
+ range: [closingSequence.range[0] - parsedClosing.rawBefore.length, closingSequence.range[0]],
166
236
  loc: {
167
237
  start: {
168
238
  line: closingSequence.loc.start.line,
169
- column: closingSequence.loc.start.column - parsed.rawBefore.length
239
+ column: closingSequence.loc.start.column - parsedClosing.rawBefore.length
170
240
  },
171
241
  end: closingSequence.loc.start
172
242
  }
173
243
  };
244
+ const contentText = sourceCode.text.slice(spaceAfterOpening.range[1], spaceBeforeClosing.range[0]);
245
+ return {
246
+ openingSequence: {
247
+ ...openingSequence,
248
+ raws: { spaceAfter: spaceAfterOpening }
249
+ },
250
+ content: {
251
+ text: contentText,
252
+ range: [spaceAfterOpening.range[1], spaceBeforeClosing.range[0]],
253
+ loc: {
254
+ start: spaceAfterOpening.loc.end,
255
+ end: spaceBeforeClosing.loc.start
256
+ }
257
+ },
258
+ closingSequence: {
259
+ ...closingSequence,
260
+ raws: {
261
+ spaceBefore: spaceBeforeClosing,
262
+ spaceAfter: spaceAfterClosing
263
+ }
264
+ }
265
+ };
266
+ }
267
+ /**
268
+ * Parse the opening sequence from a text string.
269
+ */
270
+ function parseATXHeadingOpeningSequenceFromText(text) {
271
+ if (!text.startsWith("#")) return null;
272
+ let openingSequenceAfterOffset = 1;
273
+ while (openingSequenceAfterOffset < text.length && text[openingSequenceAfterOffset] === "#") openingSequenceAfterOffset++;
274
+ const afterOffset = skipWhitespace(openingSequenceAfterOffset);
275
+ if (afterOffset === openingSequenceAfterOffset || afterOffset >= text.length) return null;
174
276
  return {
175
- rawBefore,
176
- closingSequence,
177
- rawAfter
277
+ openingSequence: text.slice(0, openingSequenceAfterOffset),
278
+ rawAfter: text.slice(openingSequenceAfterOffset, afterOffset)
178
279
  };
280
+ /**
281
+ * Skip whitespace characters at the start of the text.
282
+ */
283
+ function skipWhitespace(index) {
284
+ let result = index;
285
+ let c;
286
+ while (result < text.length && (c = text[result]) && isSpaceOrTab(c)) result++;
287
+ return result;
288
+ }
179
289
  }
180
290
  /**
181
291
  * Parse the closing sequence from a text string.
@@ -198,7 +308,7 @@ function parseATXHeadingClosingSequenceFromText(text) {
198
308
  function skipEndWhitespace(index) {
199
309
  let result = index;
200
310
  let c;
201
- while (result >= 0 && (c = text[result]) && (c === " " || c === " ")) result--;
311
+ while (result >= 0 && (c = text[result]) && isSpaceOrTab(c)) result--;
202
312
  return result;
203
313
  }
204
314
  }
@@ -271,8 +381,8 @@ function getTextWidth(text) {
271
381
  }
272
382
 
273
383
  //#endregion
274
- //#region src/rules/atx-headings-closing-sequence-length.ts
275
- var atx_headings_closing_sequence_length_default = createRule("atx-headings-closing-sequence-length", {
384
+ //#region src/rules/atx-heading-closing-sequence-length.ts
385
+ var atx_heading_closing_sequence_length_default = createRule("atx-heading-closing-sequence-length", {
276
386
  meta: {
277
387
  type: "layout",
278
388
  docs: {
@@ -307,14 +417,11 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
307
417
  /**
308
418
  * Verify the closing sequence length of an ATX heading.
309
419
  */
310
- function verifyATXHeadingClosingSequenceLength(node, getExpected) {
311
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
312
- if (!parsed || parsed.closingSequence == null) return;
420
+ function verifyATXHeadingClosingSequenceLength(parsed, reportNode, expectedLength) {
313
421
  const actualLength = parsed.closingSequence.text.length;
314
- const expectedLength = getExpected(node, parsed);
315
422
  if (expectedLength === actualLength) return;
316
423
  context.report({
317
- node,
424
+ node: reportNode,
318
425
  loc: parsed.closingSequence.loc,
319
426
  messageId: "wrongClosingLength",
320
427
  data: {
@@ -326,40 +433,41 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
326
433
  }
327
434
  });
328
435
  }
329
- return option.mode === "match-opening" ? (() => {
330
- const getExpected = (node) => node.depth;
331
- return { heading(node) {
332
- verifyATXHeadingClosingSequenceLength(node, getExpected);
333
- } };
334
- })() : option.mode === "length" ? (() => {
436
+ if (option.mode === "match-opening") return { heading(node) {
437
+ const parsed = parseATXHeading(sourceCode, node);
438
+ if (!parsed || parsed.closingSequence == null) return;
439
+ verifyATXHeadingClosingSequenceLength(parsed, node, node.depth);
440
+ } };
441
+ if (option.mode === "length") {
335
442
  const expected = option.length || 2;
336
- const getExpected = () => expected;
337
443
  return { heading(node) {
338
- verifyATXHeadingClosingSequenceLength(node, getExpected);
444
+ const parsed = parseATXHeading(sourceCode, node);
445
+ if (!parsed || parsed.closingSequence == null) return;
446
+ verifyATXHeadingClosingSequenceLength(parsed, node, expected);
339
447
  } };
340
- })() : option.mode === "fixed-line-length" ? (() => {
448
+ }
449
+ if (option.mode === "fixed-line-length") {
341
450
  const totalLength = option.length || 80;
342
- const getExpected = (_node, parsed) => {
343
- return totalLength - getContentLength(parsed);
344
- };
345
451
  return { heading(node) {
346
- verifyATXHeadingClosingSequenceLength(node, getExpected);
452
+ const parsed = parseATXHeading(sourceCode, node);
453
+ if (!parsed || parsed.closingSequence == null) return;
454
+ verifyATXHeadingClosingSequenceLength(parsed, node, totalLength - getContentLength(parsed));
347
455
  } };
348
- })() : option.mode === "consistent" ? (() => {
349
- let getExpected = null;
456
+ }
457
+ if (option.mode === "consistent") {
458
+ let expectedLength = null;
350
459
  return { heading(node) {
351
- if (getExpected == null) {
352
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
353
- if (!parsed || parsed.closingSequence == null) return;
354
- const expected = parsed.closingSequence.text.length;
355
- getExpected = () => expected;
356
- } else verifyATXHeadingClosingSequenceLength(node, getExpected);
460
+ const parsed = parseATXHeading(sourceCode, node);
461
+ if (!parsed || parsed.closingSequence == null) return;
462
+ if (expectedLength == null) expectedLength = parsed.closingSequence.text.length;
463
+ else verifyATXHeadingClosingSequenceLength(parsed, node, expectedLength);
357
464
  } };
358
- })() : option.mode === "consistent-line-length" ? (() => {
465
+ }
466
+ if (option.mode === "consistent-line-length") {
359
467
  const headings = [];
360
468
  return {
361
469
  heading(node) {
362
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
470
+ const parsed = parseATXHeading(sourceCode, node);
363
471
  if (!parsed || !parsed.closingSequence) return;
364
472
  headings.push({
365
473
  node,
@@ -385,13 +493,11 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
385
493
  const lineLength = getLineLength(heading.parsed);
386
494
  if (mostLongContentHeading.contentLength < lineLength && lineLength < minLineLength) minLineLength = Math.min(minLineLength, lineLength);
387
495
  }
388
- const getExpected = (_node, parsed) => {
389
- return minLineLength - getContentLength(parsed);
390
- };
391
- for (const { node } of headings) verifyATXHeadingClosingSequenceLength(node, getExpected);
496
+ for (const heading of headings) verifyATXHeadingClosingSequenceLength(heading.parsed, heading.node, minLineLength - getContentLength(heading.parsed));
392
497
  }
393
498
  };
394
- })() : {};
499
+ }
500
+ return {};
395
501
  /**
396
502
  * Get the content length of the heading.
397
503
  */
@@ -414,8 +520,8 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
414
520
  });
415
521
 
416
522
  //#endregion
417
- //#region src/rules/atx-headings-closing-sequence.ts
418
- var atx_headings_closing_sequence_default = createRule("atx-headings-closing-sequence", {
523
+ //#region src/rules/atx-heading-closing-sequence.ts
524
+ var atx_heading_closing_sequence_default = createRule("atx-heading-closing-sequence", {
419
525
  meta: {
420
526
  type: "layout",
421
527
  docs: {
@@ -443,7 +549,7 @@ var atx_headings_closing_sequence_default = createRule("atx-headings-closing-seq
443
549
  if (opt.closingSequence === "always" || opt.closingSequence === "never") closingSequence = opt.closingSequence;
444
550
  }
445
551
  return { heading(node) {
446
- const parsed = parseATXHeadingClosingSequence(sourceCode, node);
552
+ const parsed = parseATXHeading(sourceCode, node);
447
553
  if (!parsed) return;
448
554
  if (closingSequence === "always") {
449
555
  if (parsed.closingSequence) return;
@@ -460,12 +566,12 @@ var atx_headings_closing_sequence_default = createRule("atx-headings-closing-seq
460
566
  context.report({
461
567
  node,
462
568
  loc: {
463
- start: parsed.rawBefore.loc.start,
569
+ start: parsed.closingSequence.raws.spaceBefore.loc.start,
464
570
  end: parsed.closingSequence.loc.end
465
571
  },
466
572
  messageId: "forbidClosing",
467
573
  *fix(fixer) {
468
- const removeRange = [parsed.rawBefore.range[0], parsed.closingSequence.range[1]];
574
+ const removeRange = [parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.range[1]];
469
575
  const newHeadingText = sourceCode.text.slice(sourceCode.getRange(node)[0], removeRange[0]);
470
576
  const newHeadingParsed = parseATXHeadingClosingSequenceFromText(newHeadingText);
471
577
  if (newHeadingParsed) {
@@ -597,6 +703,176 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
597
703
  }
598
704
  });
599
705
 
706
+ //#endregion
707
+ //#region src/rules/bullet-list-marker-style.ts
708
+ const MARKERS$1 = [
709
+ "-",
710
+ "*",
711
+ "+"
712
+ ];
713
+ /**
714
+ * Get the other marker.
715
+ */
716
+ function getOtherMarker(unavailableMarker) {
717
+ return MARKERS$1.find((mark) => unavailableMarker !== mark);
718
+ }
719
+ /**
720
+ * Parse rule options.
721
+ */
722
+ function parseOptions$1(options) {
723
+ const primary = options.primary || "-";
724
+ const secondary = options.secondary || getOtherMarker(primary);
725
+ if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
726
+ const overrides = (options.overrides ?? []).map((override, index) => {
727
+ const primaryForOverride = override.primary || "-";
728
+ const secondaryForOverride = override.secondary || getOtherMarker(primaryForOverride);
729
+ if (primaryForOverride === secondaryForOverride) throw new Error(`overrides[${index}]: \`primary\` and \`secondary\` cannot be the same (primary: "${primaryForOverride}", secondary: "${secondaryForOverride}").`);
730
+ return {
731
+ level: override.level,
732
+ parentMarker: override.parentMarker ?? "any",
733
+ primary: primaryForOverride,
734
+ secondary: secondaryForOverride
735
+ };
736
+ }).reverse();
737
+ return { get(level, parentMarker) {
738
+ for (const o of overrides) if ((o.level == null || o.level === level) && (o.parentMarker === "any" || o.parentMarker === parentMarker)) return {
739
+ primary: o.primary,
740
+ secondary: o.secondary
741
+ };
742
+ return {
743
+ primary,
744
+ secondary
745
+ };
746
+ } };
747
+ }
748
+ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
749
+ meta: {
750
+ type: "layout",
751
+ docs: {
752
+ description: "enforce consistent bullet list (unordered list) marker style",
753
+ categories: [],
754
+ listCategory: "Stylistic"
755
+ },
756
+ fixable: "code",
757
+ hasSuggestions: false,
758
+ schema: [{
759
+ type: "object",
760
+ properties: {
761
+ primary: { enum: MARKERS$1 },
762
+ secondary: { enum: [...MARKERS$1, "any"] },
763
+ overrides: {
764
+ type: "array",
765
+ items: {
766
+ type: "object",
767
+ properties: {
768
+ level: {
769
+ type: "integer",
770
+ minimum: 1
771
+ },
772
+ parentMarker: { enum: [
773
+ ...MARKERS$1,
774
+ "any",
775
+ "ordered"
776
+ ] },
777
+ primary: { enum: MARKERS$1 },
778
+ secondary: { enum: [...MARKERS$1, "any"] }
779
+ },
780
+ additionalProperties: false
781
+ }
782
+ }
783
+ },
784
+ additionalProperties: false
785
+ }],
786
+ messages: { unexpected: "Bullet list marker should be '{{marker}}'." }
787
+ },
788
+ create(context) {
789
+ const sourceCode = context.sourceCode;
790
+ const options = parseOptions$1(context.options[0] || {});
791
+ let containerStack = {
792
+ node: sourceCode.ast,
793
+ level: 1,
794
+ upper: null
795
+ };
796
+ /**
797
+ * Check bullet list marker style
798
+ */
799
+ function checkBulletList(node) {
800
+ let parentMarker;
801
+ if (containerStack.node.type === "listItem") {
802
+ const parentMarkerKind = getListItemMarker(sourceCode, containerStack.node).kind;
803
+ parentMarker = parentMarkerKind === "." || parentMarkerKind === ")" ? "ordered" : parentMarkerKind;
804
+ } else parentMarker = "top";
805
+ const { primary, secondary } = options.get(containerStack.level, parentMarker);
806
+ const nodeIndex = containerStack.node.children.indexOf(node);
807
+ if (nodeIndex === -1) return;
808
+ const prevNode = nodeIndex > 0 ? containerStack.node.children[nodeIndex - 1] : null;
809
+ const prevBulletList = prevNode && (prevNode.type === "list" ? prevNode : null);
810
+ const prevBulletListMarker = prevBulletList && getListItemMarker(sourceCode, prevBulletList);
811
+ const expectedMarker = prevBulletListMarker?.kind !== primary ? primary : secondary;
812
+ if (expectedMarker === "any") return;
813
+ const marker = getListItemMarker(sourceCode, node);
814
+ if (marker.kind === expectedMarker) return;
815
+ const loc = sourceCode.getLoc(node);
816
+ context.report({
817
+ node,
818
+ loc: {
819
+ start: loc.start,
820
+ end: {
821
+ line: loc.start.line,
822
+ column: loc.start.column + marker.raw.length
823
+ }
824
+ },
825
+ messageId: "unexpected",
826
+ data: { marker: expectedMarker },
827
+ *fix(fixer) {
828
+ if (prevNode?.type === "list" && prevBulletListMarker && (prevBulletListMarker.kind === "-" || prevBulletListMarker.kind === "*" || prevBulletListMarker.kind === "+")) yield fixMarker(prevNode.children[0], prevBulletListMarker.kind);
829
+ yield* fixMarkers(node, expectedMarker);
830
+ let prevMarker = expectedMarker;
831
+ for (let index = nodeIndex + 1; index < containerStack.node.children.length; index++) {
832
+ const nextNode = containerStack.node.children[index];
833
+ if (nextNode.type !== "list") break;
834
+ const nextMarker = getListItemMarker(sourceCode, nextNode);
835
+ if (nextMarker.kind !== prevMarker) break;
836
+ let expectedNextMarker = prevMarker === primary ? secondary : primary;
837
+ if (expectedNextMarker === "any") expectedNextMarker = getOtherMarker(prevMarker);
838
+ yield* fixMarkers(nextNode, expectedNextMarker);
839
+ prevMarker = expectedNextMarker;
840
+ }
841
+ /**
842
+ * Fix bullet list markers
843
+ */
844
+ function* fixMarkers(list, replacementMarker) {
845
+ for (const item of list.children) yield fixMarker(item, replacementMarker);
846
+ }
847
+ /**
848
+ * Fix bullet list item marker
849
+ */
850
+ function fixMarker(item, replacementMarker) {
851
+ const range = sourceCode.getRange(item);
852
+ return fixer.replaceTextRange([range[0], range[0] + 1], replacementMarker);
853
+ }
854
+ }
855
+ });
856
+ }
857
+ return {
858
+ list(node) {
859
+ if (node.ordered) return;
860
+ checkBulletList(node);
861
+ },
862
+ "root, blockquote, listItem, footnoteDefinition"(node) {
863
+ containerStack = {
864
+ node,
865
+ level: node.type === "listItem" ? containerStack.level + 1 : 1,
866
+ upper: containerStack
867
+ };
868
+ },
869
+ "root, blockquote, listItem, footnoteDefinition:exit"() {
870
+ containerStack = containerStack.upper;
871
+ }
872
+ };
873
+ }
874
+ });
875
+
600
876
  //#endregion
601
877
  //#region src/rules/canonical-code-block-language.ts
602
878
  const DEFAULT_LANGUAGES = {
@@ -2788,6 +3064,199 @@ var emoji_notation_default = createRule("emoji-notation", {
2788
3064
  }
2789
3065
  });
2790
3066
 
3067
+ //#endregion
3068
+ //#region src/rules/emphasis-delimiters-style.ts
3069
+ /**
3070
+ * Check if the emphasis/strong node is intraword (inside a word)
3071
+ * CommonMark: Intraword emphasis with _ is not allowed
3072
+ */
3073
+ function isIntrawordForUnderline(text, range) {
3074
+ const before = text[range[0] - 1];
3075
+ if (!isWhitespace(before) && !isPunctuation(before)) return true;
3076
+ const after = text[range[1]];
3077
+ if (!isWhitespace(after) && !isPunctuation(after)) return true;
3078
+ return false;
3079
+ }
3080
+ var emphasis_delimiters_style_default = createRule("emphasis-delimiters-style", {
3081
+ meta: {
3082
+ type: "layout",
3083
+ docs: {
3084
+ description: "enforce a consistent delimiter style for emphasis and strong emphasis",
3085
+ categories: [],
3086
+ listCategory: "Stylistic"
3087
+ },
3088
+ fixable: "code",
3089
+ hasSuggestions: false,
3090
+ schema: [{
3091
+ type: "object",
3092
+ properties: {
3093
+ emphasis: { enum: ["*", "_"] },
3094
+ strong: { enum: ["**", "__"] },
3095
+ strongEmphasis: { anyOf: [
3096
+ { enum: ["***", "___"] },
3097
+ {
3098
+ type: "object",
3099
+ properties: {
3100
+ outer: { const: "*" },
3101
+ inner: { const: "__" }
3102
+ },
3103
+ required: ["outer", "inner"],
3104
+ additionalProperties: false
3105
+ },
3106
+ {
3107
+ type: "object",
3108
+ properties: {
3109
+ outer: { const: "**" },
3110
+ inner: { const: "_" }
3111
+ },
3112
+ required: ["outer", "inner"],
3113
+ additionalProperties: false
3114
+ },
3115
+ {
3116
+ type: "object",
3117
+ properties: {
3118
+ outer: { const: "_" },
3119
+ inner: { const: "**" }
3120
+ },
3121
+ required: ["outer", "inner"],
3122
+ additionalProperties: false
3123
+ },
3124
+ {
3125
+ type: "object",
3126
+ properties: {
3127
+ outer: { const: "__" },
3128
+ inner: { const: "*" }
3129
+ },
3130
+ required: ["outer", "inner"],
3131
+ additionalProperties: false
3132
+ }
3133
+ ] }
3134
+ },
3135
+ additionalProperties: false
3136
+ }],
3137
+ messages: {
3138
+ wrongEmphasis: "Emphasis delimiter should be '{{expectedOpening}}' (was '{{actualOpening}}').",
3139
+ wrongStrong: "Strong emphasis delimiter should be '{{expectedOpening}}' (was '{{actualOpening}}').",
3140
+ wrongStrongEmphasis: "Delimiter for strong+emphasis should be '{{expectedOpening}}' (was '{{actualOpening}}').",
3141
+ wrongStrongEmphasisDelimiterPair: "Delimiters for strong+emphasis should be '{{expectedOpening}}' ... '{{expectedClosing}}' (was '{{actualOpening}}' ... '{{actualClosing}}')."
3142
+ }
3143
+ },
3144
+ create(context) {
3145
+ const sourceCode = context.sourceCode;
3146
+ const option = context.options[0] ?? {};
3147
+ const emphasisDelimiter = option.emphasis ?? "_";
3148
+ const emphasis = {
3149
+ opening: emphasisDelimiter,
3150
+ closing: emphasisDelimiter
3151
+ };
3152
+ const strongDelimiter = option.strong ?? "**";
3153
+ const strong = {
3154
+ opening: strongDelimiter,
3155
+ closing: strongDelimiter
3156
+ };
3157
+ const strongEmphasis = parseStrongEmphasis(emphasisDelimiter, strongDelimiter, option.strongEmphasis);
3158
+ const processed = /* @__PURE__ */ new Set();
3159
+ /**
3160
+ * Verify the delimiter of the node
3161
+ */
3162
+ function verifyDelimiter(node, expected, messageId) {
3163
+ const range = sourceCode.getRange(node);
3164
+ if (sourceCode.text.startsWith(expected.opening, range[0]) && sourceCode.text.endsWith(expected.closing, range[1])) return;
3165
+ if (sourceCode.text[range[0] - 1] === expected.opening[0] || sourceCode.text[range[1]] === expected.closing.at(-1) || sourceCode.text[range[0] + expected.opening.length] === expected.opening.at(-1) || sourceCode.text[range[1] - expected.closing.length - 1] === expected.closing[0]) return;
3166
+ if ((expected.opening.startsWith("_") || expected.closing.at(-1) === "_") && isIntrawordForUnderline(sourceCode.text, range)) return;
3167
+ const actual = {
3168
+ opening: sourceCode.text.slice(range[0], range[0] + expected.opening.length),
3169
+ closing: sourceCode.text.slice(range[1] - expected.closing.length, range[1])
3170
+ };
3171
+ context.report({
3172
+ node,
3173
+ messageId: expected.opening === expected.closing && actual.opening === actual.closing ? messageId : "wrongStrongEmphasisDelimiterPair",
3174
+ data: {
3175
+ expectedOpening: expected.opening,
3176
+ actualOpening: actual.opening,
3177
+ expectedClosing: expected.closing,
3178
+ actualClosing: actual.closing
3179
+ },
3180
+ fix(fixer) {
3181
+ return [fixer.replaceTextRange([range[0], range[0] + expected.opening.length], expected.opening), fixer.replaceTextRange([range[1] - expected.closing.length, range[1]], expected.closing)];
3182
+ }
3183
+ });
3184
+ }
3185
+ /**
3186
+ * Verify the emphasis node has the correct delimiter
3187
+ */
3188
+ function verifyEmphasis(node) {
3189
+ verifyDelimiter(node, emphasis, "wrongEmphasis");
3190
+ }
3191
+ /**
3192
+ * Verify the emphasis node has the correct delimiter
3193
+ */
3194
+ function verifyStrong(node) {
3195
+ verifyDelimiter(node, strong, "wrongStrong");
3196
+ }
3197
+ /**
3198
+ * Verify the strong emphasis node has the correct delimiter
3199
+ */
3200
+ function verifyStrongEmphasis(node) {
3201
+ verifyDelimiter(node, strongEmphasis, "wrongStrongEmphasis");
3202
+ }
3203
+ return {
3204
+ emphasis(node) {
3205
+ if (processed.has(node)) return;
3206
+ processed.add(node);
3207
+ if (node.children.length === 1 && node.children[0].type === "strong") {
3208
+ processed.add(node.children[0]);
3209
+ verifyStrongEmphasis(node);
3210
+ return;
3211
+ }
3212
+ verifyEmphasis(node);
3213
+ },
3214
+ strong(node) {
3215
+ if (processed.has(node)) return;
3216
+ processed.add(node);
3217
+ if (node.children.length === 1 && node.children[0].type === "emphasis") {
3218
+ processed.add(node.children[0]);
3219
+ verifyStrongEmphasis(node);
3220
+ return;
3221
+ }
3222
+ verifyStrong(node);
3223
+ }
3224
+ };
3225
+ }
3226
+ });
3227
+ /**
3228
+ * Parse strongEmphasis option to normalized object form
3229
+ */
3230
+ function parseStrongEmphasis(emphasis, strong, strongEmphasisOpt) {
3231
+ if (strongEmphasisOpt != null) {
3232
+ if (typeof strongEmphasisOpt === "string") return {
3233
+ opening: strongEmphasisOpt,
3234
+ closing: strongEmphasisOpt
3235
+ };
3236
+ return {
3237
+ opening: strongEmphasisOpt.outer + strongEmphasisOpt.inner,
3238
+ closing: strongEmphasisOpt.inner + strongEmphasisOpt.outer
3239
+ };
3240
+ }
3241
+ if (emphasis === "*") {
3242
+ if (strong === "**") return {
3243
+ opening: "***",
3244
+ closing: "***"
3245
+ };
3246
+ return {
3247
+ opening: "*__",
3248
+ closing: "__*"
3249
+ };
3250
+ } else if (strong === "**") return {
3251
+ opening: "**_",
3252
+ closing: "_**"
3253
+ };
3254
+ return {
3255
+ opening: "___",
3256
+ closing: "___"
3257
+ };
3258
+ }
3259
+
2791
3260
  //#endregion
2792
3261
  //#region src/rules/hard-linebreak-style.ts
2793
3262
  var hard_linebreak_style_default = createRule("hard-linebreak-style", {
@@ -3598,57 +4067,331 @@ var heading_casing_default = createRule("heading-casing", {
3598
4067
  });
3599
4068
 
3600
4069
  //#endregion
3601
- //#region src/rules/list-marker-alignment.ts
3602
- const ALIGN_TO_POSITION_NAME = {
3603
- left: "start",
3604
- right: "end"
3605
- };
3606
- var list_marker_alignment_default = createRule("list-marker-alignment", {
3607
- meta: {
3608
- type: "layout",
3609
- docs: {
3610
- description: "enforce consistent alignment of list markers",
3611
- categories: ["recommended"],
3612
- listCategory: "Stylistic"
3613
- },
3614
- fixable: "whitespace",
3615
- hasSuggestions: false,
3616
- schema: [{
3617
- type: "object",
3618
- properties: { align: { enum: ["left", "right"] } },
3619
- additionalProperties: false
3620
- }],
3621
- messages: { incorrectAlignment: "List marker alignment is inconsistent. Expected {{expected}} characters of indentation, but got {{actual}}." }
3622
- },
3623
- create(context) {
3624
- const sourceCode = context.sourceCode;
3625
- const alignPositionName = ALIGN_TO_POSITION_NAME[context.options[0]?.align ?? "left"];
3626
- /**
3627
- * Get the marker location of a list item
3628
- */
3629
- function getMarkerLocation(node) {
3630
- const start = sourceCode.getLoc(node).start;
3631
- const startColumnIndex = start.column - 1;
3632
- const marker = getListItemMarker(sourceCode, node);
3633
- return {
3634
- line: start.line,
3635
- start: startColumnIndex,
3636
- end: startColumnIndex + marker.raw.length
3637
- };
3638
- }
3639
- /**
3640
- * Check if list items have consistent alignment
3641
- */
3642
- function checkListAlignment(listNode) {
3643
- const items = listNode.children;
3644
- if (items.length <= 1) return;
3645
- const referenceMarkerLocation = getMarkerLocation(items[0]);
3646
- for (const item of items.slice(1)) {
3647
- const markerLocation = getMarkerLocation(item);
3648
- const diff = markerLocation[alignPositionName] - referenceMarkerLocation[alignPositionName];
3649
- if (diff === 0) continue;
3650
- context.report({
3651
- node: item,
4070
+ //#region src/utils/setext-heading.ts
4071
+ /**
4072
+ * Parse the setext heading.
4073
+ */
4074
+ function parseSetextHeading(sourceCode, node) {
4075
+ if (getHeadingKind(sourceCode, node) !== "setext") return null;
4076
+ const lines = getParsedLines(sourceCode);
4077
+ const contentLines = [];
4078
+ const nodeLoc = sourceCode.getLoc(node);
4079
+ for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
4080
+ const content = parseContent(lines.get(lineNumber));
4081
+ contentLines.push(content);
4082
+ }
4083
+ const underline = parseUnderline(lines.get(nodeLoc.end.line));
4084
+ if (!underline) return null;
4085
+ return {
4086
+ contentLines,
4087
+ underline
4088
+ };
4089
+ }
4090
+ /**
4091
+ * Parse the content line of a setext heading.
4092
+ */
4093
+ function parseContent(line) {
4094
+ let prefix = "";
4095
+ let spaceBefore = "";
4096
+ let suffix = "";
4097
+ for (let index = 0; index < line.text.length; index++) {
4098
+ const c = line.text[index];
4099
+ if (!c.trim()) {
4100
+ spaceBefore += c;
4101
+ continue;
4102
+ }
4103
+ if (c === ">" && spaceBefore.length < 4) {
4104
+ prefix += spaceBefore + c;
4105
+ spaceBefore = "";
4106
+ continue;
4107
+ }
4108
+ suffix = line.text.slice(index);
4109
+ break;
4110
+ }
4111
+ const content = suffix.trimEnd();
4112
+ const spaceAfter = suffix.slice(content.length);
4113
+ return {
4114
+ text: content,
4115
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
4116
+ loc: {
4117
+ start: {
4118
+ line: line.line,
4119
+ column: prefix.length + spaceBefore.length + 1
4120
+ },
4121
+ end: {
4122
+ line: line.line,
4123
+ column: prefix.length + spaceBefore.length + content.length + 1
4124
+ }
4125
+ },
4126
+ raws: {
4127
+ prefix,
4128
+ spaceBefore,
4129
+ spaceAfter
4130
+ }
4131
+ };
4132
+ }
4133
+ /**
4134
+ * Parse the underline of a setext heading.
4135
+ */
4136
+ function parseUnderline(line) {
4137
+ let marker = null;
4138
+ let underlineText = "";
4139
+ let prefix = "";
4140
+ let spaceBefore = "";
4141
+ let spaceAfter = "";
4142
+ for (let index = line.text.length - 1; index >= 0; index--) {
4143
+ const c = line.text[index];
4144
+ if (!marker) {
4145
+ if (c === "=" || c === "-") {
4146
+ underlineText = c + underlineText;
4147
+ marker = c;
4148
+ } else if (!c.trim()) spaceAfter = c + spaceAfter;
4149
+ else return null;
4150
+ continue;
4151
+ }
4152
+ if (c === marker) {
4153
+ underlineText = c + spaceBefore + underlineText;
4154
+ spaceBefore = "";
4155
+ } else if (!c.trim()) spaceBefore = c + spaceBefore;
4156
+ else {
4157
+ prefix = line.text.slice(0, index + 1);
4158
+ break;
4159
+ }
4160
+ }
4161
+ if (!marker) return null;
4162
+ const underlineLoc = {
4163
+ start: {
4164
+ line: line.line,
4165
+ column: prefix.length + spaceBefore.length + 1
4166
+ },
4167
+ end: {
4168
+ line: line.line,
4169
+ column: prefix.length + spaceBefore.length + underlineText.length + 1
4170
+ }
4171
+ };
4172
+ return {
4173
+ text: underlineText,
4174
+ range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
4175
+ loc: underlineLoc,
4176
+ marker,
4177
+ raws: {
4178
+ prefix,
4179
+ spaceBefore,
4180
+ spaceAfter
4181
+ }
4182
+ };
4183
+ }
4184
+
4185
+ //#endregion
4186
+ //#region src/rules/level1-heading-style.ts
4187
+ var level1_heading_style_default = createRule("level1-heading-style", {
4188
+ meta: {
4189
+ type: "layout",
4190
+ docs: {
4191
+ description: "enforce consistent style for level 1 headings",
4192
+ categories: [],
4193
+ listCategory: "Stylistic"
4194
+ },
4195
+ fixable: "code",
4196
+ hasSuggestions: false,
4197
+ schema: [{
4198
+ type: "object",
4199
+ properties: {
4200
+ style: { enum: ["atx", "setext"] },
4201
+ allowMultilineSetext: { type: "boolean" }
4202
+ },
4203
+ additionalProperties: false
4204
+ }],
4205
+ messages: {
4206
+ expectedAtx: "Expected ATX style heading (# Heading).",
4207
+ expectedSetext: "Expected Setext style heading (Heading\\n======).",
4208
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4209
+ }
4210
+ },
4211
+ create(context) {
4212
+ const sourceCode = context.sourceCode;
4213
+ const opt = context.options[0] || {};
4214
+ const style = opt.style ?? "atx";
4215
+ const allowMultilineSetext = opt.allowMultilineSetext;
4216
+ return { heading(node) {
4217
+ if (node.depth !== 1) return;
4218
+ const headingKind = getHeadingKind(sourceCode, node);
4219
+ if (style === "atx") {
4220
+ if (headingKind !== "setext") return;
4221
+ const parsed = parseSetextHeading(sourceCode, node);
4222
+ if (!parsed) return;
4223
+ const isMultiline = parsed.contentLines.length > 1;
4224
+ if (isMultiline) {
4225
+ if (allowMultilineSetext) return;
4226
+ context.report({
4227
+ node,
4228
+ messageId: "multilineSetextNotAllowed"
4229
+ });
4230
+ return;
4231
+ }
4232
+ context.report({
4233
+ node,
4234
+ messageId: "expectedAtx",
4235
+ *fix(fixer) {
4236
+ const heading = parsed.contentLines[0];
4237
+ yield fixer.insertTextBeforeRange(heading.range, "# ");
4238
+ const lines = getParsedLines(sourceCode);
4239
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4240
+ }
4241
+ });
4242
+ } else if (style === "setext") {
4243
+ if (headingKind !== "atx" || node.children.length === 0) return;
4244
+ context.report({
4245
+ node,
4246
+ messageId: "expectedSetext",
4247
+ *fix(fixer) {
4248
+ const parsed = parseATXHeading(sourceCode, node);
4249
+ if (!parsed) return;
4250
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.openingSequence.raws.spaceAfter.range[1]]);
4251
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.raws.spaceAfter.range[1]]);
4252
+ const lines = getParsedLines(sourceCode);
4253
+ const underline = "=".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4254
+ const prefix = lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1);
4255
+ const appendingText = `\n${prefix}${underline}`;
4256
+ yield fixer.insertTextAfter(node, appendingText);
4257
+ }
4258
+ });
4259
+ }
4260
+ } };
4261
+ }
4262
+ });
4263
+
4264
+ //#endregion
4265
+ //#region src/rules/level2-heading-style.ts
4266
+ var level2_heading_style_default = createRule("level2-heading-style", {
4267
+ meta: {
4268
+ type: "layout",
4269
+ docs: {
4270
+ description: "enforce consistent style for level 2 headings",
4271
+ categories: [],
4272
+ listCategory: "Stylistic"
4273
+ },
4274
+ fixable: "code",
4275
+ hasSuggestions: false,
4276
+ schema: [{
4277
+ type: "object",
4278
+ properties: {
4279
+ style: { enum: ["atx", "setext"] },
4280
+ allowMultilineSetext: { type: "boolean" }
4281
+ },
4282
+ additionalProperties: false
4283
+ }],
4284
+ messages: {
4285
+ expectedAtx: "Expected ATX style heading (## Heading).",
4286
+ expectedSetext: "Expected Setext style heading (Heading\\n------).",
4287
+ multilineSetextNotAllowed: "Multiline Setext headings are not allowed."
4288
+ }
4289
+ },
4290
+ create(context) {
4291
+ const sourceCode = context.sourceCode;
4292
+ const opt = context.options[0] || {};
4293
+ const style = opt.style ?? "atx";
4294
+ const allowMultilineSetext = opt.allowMultilineSetext;
4295
+ return { heading(node) {
4296
+ if (node.depth !== 2) return;
4297
+ const headingKind = getHeadingKind(sourceCode, node);
4298
+ if (style === "atx") {
4299
+ if (headingKind !== "setext") return;
4300
+ const parsed = parseSetextHeading(sourceCode, node);
4301
+ if (!parsed) return;
4302
+ const isMultiline = parsed.contentLines.length > 1;
4303
+ if (isMultiline) {
4304
+ if (allowMultilineSetext) return;
4305
+ context.report({
4306
+ node,
4307
+ messageId: "multilineSetextNotAllowed"
4308
+ });
4309
+ return;
4310
+ }
4311
+ context.report({
4312
+ node,
4313
+ messageId: "expectedAtx",
4314
+ *fix(fixer) {
4315
+ const heading = parsed.contentLines[0];
4316
+ yield fixer.insertTextBeforeRange(heading.range, "## ");
4317
+ const lines = getParsedLines(sourceCode);
4318
+ yield fixer.removeRange(lines.get(parsed.underline.loc.start.line).range);
4319
+ }
4320
+ });
4321
+ } else if (style === "setext") {
4322
+ if (headingKind !== "atx" || node.children.length === 0) return;
4323
+ context.report({
4324
+ node,
4325
+ messageId: "expectedSetext",
4326
+ *fix(fixer) {
4327
+ const parsed = parseATXHeading(sourceCode, node);
4328
+ if (!parsed) return;
4329
+ yield fixer.removeRange([parsed.openingSequence.range[0], parsed.openingSequence.raws.spaceAfter.range[1]]);
4330
+ if (parsed.closingSequence) yield fixer.removeRange([parsed.closingSequence.raws.spaceBefore.range[0], parsed.closingSequence.raws.spaceAfter.range[1]]);
4331
+ const lines = getParsedLines(sourceCode);
4332
+ const underline = "-".repeat(Math.max(getTextWidth(parsed.content.text), 3));
4333
+ const prefix = lines.get(parsed.openingSequence.loc.start.line).text.slice(0, parsed.openingSequence.loc.start.column - 1);
4334
+ const appendingText = `\n${prefix}${underline}`;
4335
+ yield fixer.insertTextAfter(node, appendingText);
4336
+ }
4337
+ });
4338
+ }
4339
+ } };
4340
+ }
4341
+ });
4342
+
4343
+ //#endregion
4344
+ //#region src/rules/list-marker-alignment.ts
4345
+ const ALIGN_TO_POSITION_NAME = {
4346
+ left: "start",
4347
+ right: "end"
4348
+ };
4349
+ var list_marker_alignment_default = createRule("list-marker-alignment", {
4350
+ meta: {
4351
+ type: "layout",
4352
+ docs: {
4353
+ description: "enforce consistent alignment of list markers",
4354
+ categories: ["recommended"],
4355
+ listCategory: "Stylistic"
4356
+ },
4357
+ fixable: "whitespace",
4358
+ hasSuggestions: false,
4359
+ schema: [{
4360
+ type: "object",
4361
+ properties: { align: { enum: ["left", "right"] } },
4362
+ additionalProperties: false
4363
+ }],
4364
+ messages: { incorrectAlignment: "List marker alignment is inconsistent. Expected {{expected}} characters of indentation, but got {{actual}}." }
4365
+ },
4366
+ create(context) {
4367
+ const sourceCode = context.sourceCode;
4368
+ const alignPositionName = ALIGN_TO_POSITION_NAME[context.options[0]?.align ?? "left"];
4369
+ /**
4370
+ * Get the marker location of a list item
4371
+ */
4372
+ function getMarkerLocation(node) {
4373
+ const start = sourceCode.getLoc(node).start;
4374
+ const startColumnIndex = start.column - 1;
4375
+ const marker = getListItemMarker(sourceCode, node);
4376
+ return {
4377
+ line: start.line,
4378
+ start: startColumnIndex,
4379
+ end: startColumnIndex + marker.raw.length
4380
+ };
4381
+ }
4382
+ /**
4383
+ * Check if list items have consistent alignment
4384
+ */
4385
+ function checkListAlignment(listNode) {
4386
+ const items = listNode.children;
4387
+ if (items.length <= 1) return;
4388
+ const referenceMarkerLocation = getMarkerLocation(items[0]);
4389
+ for (const item of items.slice(1)) {
4390
+ const markerLocation = getMarkerLocation(item);
4391
+ const diff = markerLocation[alignPositionName] - referenceMarkerLocation[alignPositionName];
4392
+ if (diff === 0) continue;
4393
+ context.report({
4394
+ node: item,
3652
4395
  loc: {
3653
4396
  start: {
3654
4397
  line: markerLocation.line,
@@ -4150,7 +4893,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
4150
4893
  messageId: "inconsistentStart",
4151
4894
  data: {
4152
4895
  expected: new Intl.ListFormat("en-US", { type: "disjunction" }).format(expected.map((n) => `'${n}'`)),
4153
- actual: String(marker.sequence)
4896
+ actual: marker.sequence.raw
4154
4897
  },
4155
4898
  fix: scope.last == null ? (fixer) => {
4156
4899
  const expectedMarker = `1${marker.kind}`;
@@ -4171,7 +4914,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
4171
4914
  const marker = getListItemMarker(sourceCode, item);
4172
4915
  if (marker.kind !== "." && marker.kind !== ")") continue;
4173
4916
  const expectedSequence = node.start + i;
4174
- if (marker.sequence !== expectedSequence) {
4917
+ if (marker.sequence.value !== expectedSequence) {
4175
4918
  const expectedMarker = `${expectedSequence}${marker.kind}`;
4176
4919
  context.report({
4177
4920
  node: item,
@@ -4306,6 +5049,174 @@ var ordered_list_marker_start_default = createRule("ordered-list-marker-start",
4306
5049
  }
4307
5050
  });
4308
5051
 
5052
+ //#endregion
5053
+ //#region src/rules/ordered-list-marker-style.ts
5054
+ const MARKER_KINDS = [".", ")"];
5055
+ const MARKERS = MARKER_KINDS.map((kind) => `n${kind}`);
5056
+ /**
5057
+ * Get the other marker kind.
5058
+ */
5059
+ function getOtherMarkerKind(unavailableMarker) {
5060
+ return MARKER_KINDS.find((mark) => unavailableMarker !== mark);
5061
+ }
5062
+ function markerToKind(marker) {
5063
+ if (marker === "n.") return ".";
5064
+ if (marker === "n)") return ")";
5065
+ return marker;
5066
+ }
5067
+ /**
5068
+ * Parse rule options.
5069
+ */
5070
+ function parseOptions(options) {
5071
+ const prefer = markerToKind(options.prefer) || ".";
5072
+ const overrides = (options.overrides ?? []).map((override) => {
5073
+ const preferForOverride = markerToKind(override.prefer) || ".";
5074
+ return {
5075
+ level: override.level,
5076
+ parentMarker: markerToKind(override.parentMarker) ?? "any",
5077
+ prefer: preferForOverride
5078
+ };
5079
+ }).reverse();
5080
+ return { get(level, parentMarker) {
5081
+ for (const o of overrides) if ((o.level == null || o.level === level) && (o.parentMarker === "any" || o.parentMarker === parentMarker)) return { prefer: o.prefer };
5082
+ return { prefer };
5083
+ } };
5084
+ }
5085
+ /**
5086
+ * Check if a item marker is an ordered list marker.
5087
+ */
5088
+ function isOrderedListItemMarker(itemMarker) {
5089
+ return itemMarker.kind === "." || itemMarker.kind === ")";
5090
+ }
5091
+ var ordered_list_marker_style_default = createRule("ordered-list-marker-style", {
5092
+ meta: {
5093
+ type: "layout",
5094
+ docs: {
5095
+ description: "enforce consistent ordered list marker style",
5096
+ categories: [],
5097
+ listCategory: "Stylistic"
5098
+ },
5099
+ fixable: "code",
5100
+ hasSuggestions: false,
5101
+ schema: [{
5102
+ type: "object",
5103
+ properties: {
5104
+ prefer: { enum: MARKERS },
5105
+ overrides: {
5106
+ type: "array",
5107
+ items: {
5108
+ type: "object",
5109
+ properties: {
5110
+ level: {
5111
+ type: "integer",
5112
+ minimum: 1
5113
+ },
5114
+ parentMarker: { enum: [
5115
+ ...MARKERS,
5116
+ "any",
5117
+ "bullet"
5118
+ ] },
5119
+ prefer: { enum: MARKERS }
5120
+ },
5121
+ additionalProperties: false
5122
+ }
5123
+ }
5124
+ },
5125
+ additionalProperties: false
5126
+ }],
5127
+ messages: { unexpected: "Ordered list marker should be '{{sequence}}{{markerKind}}'." }
5128
+ },
5129
+ create(context) {
5130
+ const sourceCode = context.sourceCode;
5131
+ const options = parseOptions(context.options[0] || {});
5132
+ let containerStack = {
5133
+ node: sourceCode.ast,
5134
+ level: 1,
5135
+ upper: null
5136
+ };
5137
+ /**
5138
+ * Check ordered list marker style
5139
+ */
5140
+ function checkOrderedList(node) {
5141
+ let parentMarker;
5142
+ if (containerStack.node.type === "listItem") {
5143
+ const parentMarkerKind = getListItemMarker(sourceCode, containerStack.node).kind;
5144
+ parentMarker = parentMarkerKind === "-" || parentMarkerKind === "*" || parentMarkerKind === "+" ? "bullet" : parentMarkerKind;
5145
+ } else parentMarker = "top";
5146
+ const { prefer } = options.get(containerStack.level, parentMarker);
5147
+ const nodeIndex = containerStack.node.children.indexOf(node);
5148
+ if (nodeIndex === -1) return;
5149
+ const prevNode = nodeIndex > 0 ? containerStack.node.children[nodeIndex - 1] : null;
5150
+ const prevBulletList = prevNode && (prevNode.type === "list" ? prevNode : null);
5151
+ const prevBulletListMarker = prevBulletList && getListItemMarker(sourceCode, prevBulletList);
5152
+ const expectedMarker = prevBulletListMarker?.kind !== prefer ? prefer : getOtherMarkerKind(prefer);
5153
+ const marker = getListItemMarker(sourceCode, node);
5154
+ if (marker.kind === expectedMarker || !isOrderedListItemMarker(marker)) return;
5155
+ const loc = sourceCode.getLoc(node);
5156
+ context.report({
5157
+ node,
5158
+ loc: {
5159
+ start: loc.start,
5160
+ end: {
5161
+ line: loc.start.line,
5162
+ column: loc.start.column + marker.raw.length
5163
+ }
5164
+ },
5165
+ messageId: "unexpected",
5166
+ data: {
5167
+ sequence: marker.sequence.raw,
5168
+ markerKind: expectedMarker
5169
+ },
5170
+ *fix(fixer) {
5171
+ if (prevNode?.type === "list" && prevBulletListMarker && isOrderedListItemMarker(prevBulletListMarker)) yield* fixMarker(prevNode.children[0], prevBulletListMarker.kind);
5172
+ yield* fixMarkers(node, expectedMarker);
5173
+ let prevMarker = expectedMarker;
5174
+ for (let index = nodeIndex + 1; index < containerStack.node.children.length; index++) {
5175
+ const nextNode = containerStack.node.children[index];
5176
+ if (nextNode.type !== "list") break;
5177
+ const nextMarker = getListItemMarker(sourceCode, nextNode);
5178
+ if (nextMarker.kind !== prevMarker) break;
5179
+ const expectedNextMarker = prevMarker === prefer ? getOtherMarkerKind(prefer) : prefer;
5180
+ yield* fixMarkers(nextNode, expectedNextMarker);
5181
+ prevMarker = expectedNextMarker;
5182
+ }
5183
+ /**
5184
+ * Fix ordered list markers
5185
+ */
5186
+ function* fixMarkers(list, replacementMarker) {
5187
+ for (const item of list.children) yield* fixMarker(item, replacementMarker);
5188
+ }
5189
+ /**
5190
+ * Fix ordered list item marker
5191
+ */
5192
+ function* fixMarker(item, replacementMarker) {
5193
+ const range = sourceCode.getRange(item);
5194
+ const itemMarker = getListItemMarker(sourceCode, item);
5195
+ if (!isOrderedListItemMarker(itemMarker)) return;
5196
+ yield fixer.replaceTextRange([range[0] + itemMarker.raw.length - 1, range[0] + itemMarker.raw.length], replacementMarker);
5197
+ }
5198
+ }
5199
+ });
5200
+ }
5201
+ return {
5202
+ list(node) {
5203
+ if (!node.ordered) return;
5204
+ checkOrderedList(node);
5205
+ },
5206
+ "root, blockquote, listItem, footnoteDefinition"(node) {
5207
+ containerStack = {
5208
+ node,
5209
+ level: node.type === "listItem" ? containerStack.level + 1 : 1,
5210
+ upper: containerStack
5211
+ };
5212
+ },
5213
+ "root, blockquote, listItem, footnoteDefinition:exit"() {
5214
+ containerStack = containerStack.upper;
5215
+ }
5216
+ };
5217
+ }
5218
+ });
5219
+
4309
5220
  //#endregion
4310
5221
  //#region src/rules/padding-line-between-blocks.ts
4311
5222
  /**
@@ -5123,122 +6034,6 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
5123
6034
  }
5124
6035
  });
5125
6036
 
5126
- //#endregion
5127
- //#region src/utils/setext-heading.ts
5128
- /**
5129
- * Parse the setext heading.
5130
- */
5131
- function parseSetextHeading(sourceCode, node) {
5132
- if (getHeadingKind(sourceCode, node) !== "setext") return null;
5133
- const lines = getParsedLines(sourceCode);
5134
- const contentLines = [];
5135
- const nodeLoc = sourceCode.getLoc(node);
5136
- for (let lineNumber = nodeLoc.start.line; lineNumber < nodeLoc.end.line; lineNumber++) {
5137
- const content = parseContent(lines.get(lineNumber));
5138
- contentLines.push(content);
5139
- }
5140
- const underline = parseUnderline(lines.get(nodeLoc.end.line));
5141
- if (!underline) return null;
5142
- return {
5143
- contentLines,
5144
- underline
5145
- };
5146
- }
5147
- /**
5148
- * Parse the content line of a setext heading.
5149
- */
5150
- function parseContent(line) {
5151
- let prefix = "";
5152
- let spaceBefore = "";
5153
- let suffix = "";
5154
- for (let index = 0; index < line.text.length; index++) {
5155
- const c = line.text[index];
5156
- if (!c.trim()) {
5157
- spaceBefore += c;
5158
- continue;
5159
- }
5160
- if (c === ">" && spaceBefore.length < 4) {
5161
- prefix += spaceBefore + c;
5162
- spaceBefore = "";
5163
- continue;
5164
- }
5165
- suffix = line.text.slice(index);
5166
- break;
5167
- }
5168
- const content = suffix.trimEnd();
5169
- const spaceAfter = suffix.slice(content.length);
5170
- return {
5171
- text: content,
5172
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
5173
- loc: {
5174
- start: {
5175
- line: line.line,
5176
- column: prefix.length + spaceBefore.length + 1
5177
- },
5178
- end: {
5179
- line: line.line,
5180
- column: prefix.length + spaceBefore.length + content.length + 1
5181
- }
5182
- },
5183
- raws: {
5184
- prefix,
5185
- spaceBefore,
5186
- spaceAfter
5187
- }
5188
- };
5189
- }
5190
- /**
5191
- * Parse the underline of a setext heading.
5192
- */
5193
- function parseUnderline(line) {
5194
- let marker = null;
5195
- let underlineText = "";
5196
- let prefix = "";
5197
- let spaceBefore = "";
5198
- let spaceAfter = "";
5199
- for (let index = line.text.length - 1; index >= 0; index--) {
5200
- const c = line.text[index];
5201
- if (!marker) {
5202
- if (c === "=" || c === "-") {
5203
- underlineText = c + underlineText;
5204
- marker = c;
5205
- } else if (!c.trim()) spaceAfter = c + spaceAfter;
5206
- else return null;
5207
- continue;
5208
- }
5209
- if (c === marker) {
5210
- underlineText = c + spaceBefore + underlineText;
5211
- spaceBefore = "";
5212
- } else if (!c.trim()) spaceBefore = c + spaceBefore;
5213
- else {
5214
- prefix = line.text.slice(0, index + 1);
5215
- break;
5216
- }
5217
- }
5218
- if (!marker) return null;
5219
- const underlineLoc = {
5220
- start: {
5221
- line: line.line,
5222
- column: prefix.length + spaceBefore.length + 1
5223
- },
5224
- end: {
5225
- line: line.line,
5226
- column: prefix.length + spaceBefore.length + underlineText.length + 1
5227
- }
5228
- };
5229
- return {
5230
- text: underlineText,
5231
- range: [line.range[0] + prefix.length + spaceBefore.length, line.range[1] - line.linebreak.length - spaceAfter.length],
5232
- loc: underlineLoc,
5233
- marker,
5234
- raws: {
5235
- prefix,
5236
- spaceBefore,
5237
- spaceAfter
5238
- }
5239
- };
5240
- }
5241
-
5242
6037
  //#endregion
5243
6038
  //#region src/rules/setext-heading-underline-length.ts
5244
6039
  var setext_heading_underline_length_default = createRule("setext-heading-underline-length", {
@@ -5602,7 +6397,7 @@ var sort_definitions_default = createRule("sort-definitions", {
5602
6397
  const last = group.at(-1);
5603
6398
  if (last && (node.type !== "definition" && node.type !== "footnoteDefinition" || sourceCode.getParent(node) !== sourceCode.getParent(last))) {
5604
6399
  const range = sourceCode.getRange(node);
5605
- const lastDefinitionRange = sourceCode.getRange(node);
6400
+ const lastDefinitionRange = sourceCode.getRange(last);
5606
6401
  if (lastDefinitionRange[1] <= range[0]) {
5607
6402
  verify(group);
5608
6403
  group.length = 0;
@@ -5760,6 +6555,48 @@ function normalizedURL(url) {
5760
6555
  return urlObj.href.endsWith("/") ? urlObj.href : `${urlObj.href}/`;
5761
6556
  }
5762
6557
 
6558
+ //#endregion
6559
+ //#region src/rules/strikethrough-delimiters-style.ts
6560
+ var strikethrough_delimiters_style_default = createRule("strikethrough-delimiters-style", {
6561
+ meta: {
6562
+ type: "layout",
6563
+ docs: {
6564
+ description: "enforce a consistent delimiter style for strikethrough",
6565
+ categories: [],
6566
+ listCategory: "Stylistic"
6567
+ },
6568
+ fixable: "code",
6569
+ hasSuggestions: false,
6570
+ schema: [{
6571
+ type: "object",
6572
+ properties: { delimiter: { enum: ["~", "~~"] } },
6573
+ additionalProperties: false
6574
+ }],
6575
+ messages: { wrongDelimiter: "Strikethrough delimiter should be '{{expected}}' (was '{{actual}}')." }
6576
+ },
6577
+ create(context) {
6578
+ const sourceCode = context.sourceCode;
6579
+ const option = context.options[0] ?? {};
6580
+ const delimiter = option.delimiter ?? "~~";
6581
+ return { delete(node) {
6582
+ const range = sourceCode.getRange(node);
6583
+ const actualDelimiter = ["~~", "~"].find((d) => sourceCode.text.startsWith(d, range[0]) && sourceCode.text.endsWith(d, range[1]));
6584
+ if (!actualDelimiter || actualDelimiter === delimiter) return;
6585
+ context.report({
6586
+ node,
6587
+ messageId: "wrongDelimiter",
6588
+ data: {
6589
+ expected: delimiter,
6590
+ actual: actualDelimiter
6591
+ },
6592
+ fix(fixer) {
6593
+ return [fixer.replaceTextRange([range[0], range[0] + actualDelimiter.length], delimiter), fixer.replaceTextRange([range[1] - actualDelimiter.length, range[1]], delimiter)];
6594
+ }
6595
+ });
6596
+ } };
6597
+ }
6598
+ });
6599
+
5763
6600
  //#endregion
5764
6601
  //#region src/rules/table-header-casing.ts
5765
6602
  var table_header_casing_default = createRule("table-header-casing", {
@@ -6108,14 +6945,18 @@ var thematic_break_sequence_pattern_default = createRule("thematic-break-sequenc
6108
6945
  //#endregion
6109
6946
  //#region src/utils/rules.ts
6110
6947
  const rules$1 = [
6111
- atx_headings_closing_sequence_length_default,
6112
- atx_headings_closing_sequence_default,
6948
+ atx_heading_closing_sequence_length_default,
6949
+ atx_heading_closing_sequence_default,
6113
6950
  blockquote_marker_alignment_default,
6951
+ bullet_list_marker_style_default,
6114
6952
  canonical_code_block_language_default,
6115
6953
  definitions_last_default,
6116
6954
  emoji_notation_default,
6955
+ emphasis_delimiters_style_default,
6117
6956
  hard_linebreak_style_default,
6118
6957
  heading_casing_default,
6958
+ level1_heading_style_default,
6959
+ level2_heading_style_default,
6119
6960
  list_marker_alignment_default,
6120
6961
  no_laziness_blockquotes_default,
6121
6962
  no_multiple_empty_lines_default,
@@ -6123,6 +6964,7 @@ const rules$1 = [
6123
6964
  no_trailing_spaces_default,
6124
6965
  ordered_list_marker_sequence_default,
6125
6966
  ordered_list_marker_start_default,
6967
+ ordered_list_marker_style_default,
6126
6968
  padding_line_between_blocks_default,
6127
6969
  prefer_autolinks_default,
6128
6970
  prefer_fenced_code_blocks_default,
@@ -6131,6 +6973,7 @@ const rules$1 = [
6131
6973
  prefer_linked_words_default,
6132
6974
  setext_heading_underline_length_default,
6133
6975
  sort_definitions_default,
6976
+ strikethrough_delimiters_style_default,
6134
6977
  table_header_casing_default,
6135
6978
  thematic_break_character_style_default,
6136
6979
  thematic_break_length_default,
@@ -6177,7 +7020,7 @@ __export(meta_exports, {
6177
7020
  version: () => version
6178
7021
  });
6179
7022
  const name = "eslint-plugin-markdown-preferences";
6180
- const version = "0.17.0";
7023
+ const version = "0.19.0";
6181
7024
 
6182
7025
  //#endregion
6183
7026
  //#region src/index.ts