eslint-plugin-markdown-preferences 0.24.0 → 0.26.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
@@ -36,6 +36,26 @@ function getParent(sourceCode, node) {
36
36
  return sourceCode.getParent(node);
37
37
  }
38
38
  /**
39
+ * Get the previous sibling of a node.
40
+ */
41
+ function getPrevSibling(sourceCode, node) {
42
+ const parent = getParent(sourceCode, node);
43
+ if (!parent) return null;
44
+ const index = parent.children.indexOf(node);
45
+ if (index <= 0) return null;
46
+ return parent.children[index - 1];
47
+ }
48
+ /**
49
+ * Get the next sibling of a node.
50
+ */
51
+ function getNextSibling(sourceCode, node) {
52
+ const parent = getParent(sourceCode, node);
53
+ if (!parent) return null;
54
+ const index = parent.children.indexOf(node);
55
+ if (index < 0 || index >= parent.children.length - 1) return null;
56
+ return parent.children[index + 1];
57
+ }
58
+ /**
39
59
  * Get the kind of heading.
40
60
  */
41
61
  function getHeadingKind(sourceCode, node) {
@@ -109,13 +129,20 @@ function getThematicBreakMarker(sourceCode, node) {
109
129
  * Get the source location from a range in a node.
110
130
  */
111
131
  function getSourceLocationFromRange(sourceCode, node, range) {
112
- const [nodeStart] = sourceCode.getRange(node);
132
+ const nodeRange = sourceCode.getRange(node);
133
+ const loc = sourceCode.getLoc(node);
134
+ if (nodeRange[1] <= range[0]) return getSourceLocationFromRangeAndSourcePosition(sourceCode, nodeRange[1], loc.end, range);
135
+ return getSourceLocationFromRangeAndSourcePosition(sourceCode, nodeRange[0], loc.start, range);
136
+ }
137
+ /**
138
+ * Get the source location from a range
139
+ */
140
+ function getSourceLocationFromRangeAndSourcePosition(sourceCode, startIndex, startLoc, range) {
113
141
  let startLine, startColumn;
114
- if (nodeStart <= range[0]) {
115
- const loc = sourceCode.getLoc(node);
116
- const beforeLines = sourceCode.text.slice(nodeStart, range[0]).split(/\n/u);
117
- startLine = loc.start.line + beforeLines.length - 1;
118
- startColumn = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
142
+ if (startIndex <= range[0]) {
143
+ const beforeLines = sourceCode.text.slice(startIndex, range[0]).split(/\n/u);
144
+ startLine = startLoc.line + beforeLines.length - 1;
145
+ startColumn = (beforeLines.length === 1 ? startLoc.column : 1) + (beforeLines.at(-1) || "").length;
119
146
  } else {
120
147
  const beforeLines = sourceCode.text.slice(0, range[0]).split(/\n/u);
121
148
  startLine = beforeLines.length;
@@ -364,15 +391,23 @@ function getParsedLines(sourceCode) {
364
391
  }
365
392
 
366
393
  //#endregion
367
- //#region src/utils/get-text-width.ts
394
+ //#region src/utils/text-width.ts
368
395
  let segmenter;
369
396
  /**
370
397
  * Get the width of a text string.
371
398
  */
372
- function getTextWidth(text) {
373
- if (!text.includes(" ")) return stringWidth(text);
399
+ function getTextWidth(text, start = 0, end = text.length) {
400
+ if (!text.includes(" ")) return stringWidth(text.slice(start, end));
374
401
  if (!segmenter) segmenter = new Intl.Segmenter("en");
375
- let width = 0;
402
+ const prefixWidth = getTextWidthBySegment(text.slice(0, start), 0);
403
+ return getTextWidthBySegment(text.slice(start, end), prefixWidth);
404
+ }
405
+ /**
406
+ * Get the width of a text string by segmenter.
407
+ */
408
+ function getTextWidthBySegment(text, startWidth) {
409
+ if (!segmenter) segmenter = new Intl.Segmenter("en");
410
+ let width = startWidth;
376
411
  for (const { segment: c } of segmenter.segment(text)) if (c === " ") width += 4 - width % 4;
377
412
  else width += stringWidth(c);
378
413
  return width;
@@ -386,7 +421,7 @@ var atx_heading_closing_sequence_length_default = createRule("atx-heading-closin
386
421
  docs: {
387
422
  description: "enforce consistent length for the closing sequence (trailing #s) in ATX headings.",
388
423
  categories: ["standard"],
389
- listCategory: "Stylistic"
424
+ listCategory: "Decorative"
390
425
  },
391
426
  fixable: "code",
392
427
  hasSuggestions: false,
@@ -524,7 +559,7 @@ var atx_heading_closing_sequence_default = createRule("atx-heading-closing-seque
524
559
  docs: {
525
560
  description: "enforce consistent use of closing sequence in ATX headings.",
526
561
  categories: ["standard"],
527
- listCategory: "Stylistic"
562
+ listCategory: "Decorative"
528
563
  },
529
564
  fixable: "code",
530
565
  hasSuggestions: false,
@@ -700,7 +735,7 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
700
735
  docs: {
701
736
  description: "enforce consistent alignment of blockquote markers",
702
737
  categories: ["recommended", "standard"],
703
- listCategory: "Stylistic"
738
+ listCategory: "Whitespace"
704
739
  },
705
740
  fixable: "whitespace",
706
741
  hasSuggestions: false,
@@ -783,7 +818,7 @@ function getOtherMarker(unavailableMarker) {
783
818
  /**
784
819
  * Parse rule options.
785
820
  */
786
- function parseOptions$3(options) {
821
+ function parseOptions$5(options) {
787
822
  const primary = options.primary || "-";
788
823
  const secondary = options.secondary || getOtherMarker(primary);
789
824
  if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
@@ -815,7 +850,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
815
850
  docs: {
816
851
  description: "enforce consistent bullet list (unordered list) marker style",
817
852
  categories: ["standard"],
818
- listCategory: "Stylistic"
853
+ listCategory: "Notation"
819
854
  },
820
855
  fixable: "code",
821
856
  hasSuggestions: false,
@@ -851,7 +886,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
851
886
  },
852
887
  create(context) {
853
888
  const sourceCode = context.sourceCode;
854
- const options = parseOptions$3(context.options[0] || {});
889
+ const options = parseOptions$5(context.options[0] || {});
855
890
  let containerStack = {
856
891
  node: sourceCode.ast,
857
892
  level: 1,
@@ -1114,7 +1149,7 @@ var code_fence_length_default = createRule("code-fence-length", {
1114
1149
  docs: {
1115
1150
  description: "enforce consistent code fence length in fenced code blocks.",
1116
1151
  categories: ["standard"],
1117
- listCategory: "Stylistic"
1152
+ listCategory: "Decorative"
1118
1153
  },
1119
1154
  fixable: "code",
1120
1155
  hasSuggestions: false,
@@ -1251,7 +1286,7 @@ var code_fence_style_default = createRule("code-fence-style", {
1251
1286
  docs: {
1252
1287
  description: "enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks.",
1253
1288
  categories: ["standard"],
1254
- listCategory: "Stylistic"
1289
+ listCategory: "Notation"
1255
1290
  },
1256
1291
  fixable: "code",
1257
1292
  hasSuggestions: false,
@@ -1292,41 +1327,265 @@ var code_fence_style_default = createRule("code-fence-style", {
1292
1327
 
1293
1328
  //#endregion
1294
1329
  //#region src/rules/definitions-last.ts
1330
+ /**
1331
+ * Parse options with defaults.
1332
+ */
1333
+ function parseOptions$4(options) {
1334
+ const linkDefinitionPlacement = {
1335
+ referencedFromSingleSection: options?.linkDefinitionPlacement?.referencedFromSingleSection || "document-last",
1336
+ referencedFromMultipleSections: options?.linkDefinitionPlacement?.referencedFromMultipleSections || "document-last"
1337
+ };
1338
+ const footnoteDefinitionPlacement = {
1339
+ referencedFromSingleSection: options?.footnoteDefinitionPlacement?.referencedFromSingleSection || "document-last",
1340
+ referencedFromMultipleSections: options?.footnoteDefinitionPlacement?.referencedFromMultipleSections || "document-last"
1341
+ };
1342
+ return {
1343
+ linkDefinitionPlacement,
1344
+ footnoteDefinitionPlacement
1345
+ };
1346
+ }
1295
1347
  var definitions_last_default = createRule("definitions-last", {
1296
1348
  meta: {
1297
1349
  type: "layout",
1298
1350
  docs: {
1299
1351
  description: "require link definitions and footnote definitions to be placed at the end of the document",
1300
1352
  categories: [],
1301
- listCategory: "Stylistic"
1353
+ listCategory: "Notation"
1302
1354
  },
1303
1355
  fixable: "code",
1304
1356
  hasSuggestions: false,
1305
- schema: [],
1306
- messages: {}
1357
+ schema: [{
1358
+ type: "object",
1359
+ properties: {
1360
+ linkDefinitionPlacement: {
1361
+ type: "object",
1362
+ properties: {
1363
+ referencedFromSingleSection: {
1364
+ type: "string",
1365
+ enum: ["document-last", "section-last"]
1366
+ },
1367
+ referencedFromMultipleSections: {
1368
+ type: "string",
1369
+ enum: [
1370
+ "document-last",
1371
+ "first-reference-section-last",
1372
+ "last-reference-section-last"
1373
+ ]
1374
+ }
1375
+ },
1376
+ additionalProperties: false
1377
+ },
1378
+ footnoteDefinitionPlacement: {
1379
+ type: "object",
1380
+ properties: {
1381
+ referencedFromSingleSection: {
1382
+ type: "string",
1383
+ enum: ["document-last", "section-last"]
1384
+ },
1385
+ referencedFromMultipleSections: {
1386
+ type: "string",
1387
+ enum: [
1388
+ "document-last",
1389
+ "first-reference-section-last",
1390
+ "last-reference-section-last"
1391
+ ]
1392
+ }
1393
+ },
1394
+ additionalProperties: false
1395
+ }
1396
+ },
1397
+ additionalProperties: false
1398
+ }],
1399
+ messages: {
1400
+ definitionsDocumentLast: "Definition or footnote definition should be placed at the end of the document.",
1401
+ definitionsSectionLast: "Definition or footnote definition should be placed at the end of the section ({{at}}).",
1402
+ definitionsLastSectionLast: "Definition or footnote definition should be placed at the end of the last section (the end of the document)."
1403
+ }
1307
1404
  },
1308
1405
  create(context) {
1309
1406
  const sourceCode = context.sourceCode;
1310
- const lastNonDefinition = sourceCode.ast.children.findLast((node) => node.type !== "definition" && node.type !== "footnoteDefinition" && !(node.type === "html" && (node.value.startsWith("<!--") || node.value.startsWith("<script") || node.value.startsWith("<style"))));
1311
- if (!lastNonDefinition) return {};
1312
- const lastNonDefinitionRange = sourceCode.getRange(lastNonDefinition);
1313
- return { "definition, footnoteDefinition"(node) {
1407
+ const options = parseOptions$4(context.options[0]);
1408
+ /**
1409
+ * Determine whether a node can be placed as the last node of the document or a section.
1410
+ */
1411
+ function canBePlacedLastNode(node) {
1412
+ return node.type === "definition" || node.type === "footnoteDefinition" || node.type === "html" && (node.value.startsWith("<!--") || node.value.startsWith("<script") || node.value.startsWith("<style"));
1413
+ }
1414
+ const beforeLastNode = sourceCode.ast.children.findLast((node) => !canBePlacedLastNode(node));
1415
+ if (!beforeLastNode) return {};
1416
+ let lastSection = {
1417
+ heading: null,
1418
+ linkReferenceIds: /* @__PURE__ */ new Set(),
1419
+ footnoteReferenceIds: /* @__PURE__ */ new Set(),
1420
+ nextHeading: null
1421
+ };
1422
+ const sections = [lastSection];
1423
+ const definitions = [];
1424
+ /**
1425
+ * Get the expected placement of a definition or footnote definition node.
1426
+ */
1427
+ function getExpectedPlacement(node) {
1428
+ let referencedFromSingleSection;
1429
+ let referencedFromMultipleSections;
1430
+ let referenceIdsNs;
1431
+ if (node.type === "definition") {
1432
+ ({referencedFromSingleSection, referencedFromMultipleSections} = options.linkDefinitionPlacement);
1433
+ referenceIdsNs = "linkReferenceIds";
1434
+ } else if (node.type === "footnoteDefinition") {
1435
+ ({referencedFromSingleSection, referencedFromMultipleSections} = options.footnoteDefinitionPlacement);
1436
+ referenceIdsNs = "footnoteReferenceIds";
1437
+ } else return { type: "document-last" };
1438
+ if (referencedFromSingleSection === "document-last" && referencedFromSingleSection === referencedFromMultipleSections) return { type: "document-last" };
1439
+ const referencedSections = [];
1440
+ for (const section of sections) {
1441
+ if (!section[referenceIdsNs].has(node.identifier)) continue;
1442
+ referencedSections.push(section);
1443
+ if (referencedSections.length > 1 && referencedFromMultipleSections !== "last-reference-section-last") return {
1444
+ type: referencedFromMultipleSections,
1445
+ section: referencedSections[0]
1446
+ };
1447
+ }
1448
+ if (referencedSections.length === 0) return { type: "document-last" };
1449
+ if (referencedSections.length === 1) return {
1450
+ type: referencedFromSingleSection,
1451
+ section: referencedSections[0]
1452
+ };
1453
+ return {
1454
+ type: referencedFromMultipleSections,
1455
+ section: referencedSections[referencedSections.length - 1]
1456
+ };
1457
+ }
1458
+ /**
1459
+ * Verify the position of a definition or footnote definition node.
1460
+ */
1461
+ function verifyDefinitionPosition(node) {
1462
+ const expectedPlacement = getExpectedPlacement(node);
1463
+ if (expectedPlacement.type === "document-last") verifyDefinitionOnDocumentLast(node, "definitionsDocumentLast");
1464
+ else if (expectedPlacement.type === "section-last" || expectedPlacement.type === "first-reference-section-last" || expectedPlacement.type === "last-reference-section-last") if (!expectedPlacement.section.nextHeading) verifyDefinitionOnDocumentLast(node, "definitionsLastSectionLast");
1465
+ else verifyDefinitionOnSectionLast(node, expectedPlacement.section.nextHeading);
1466
+ }
1467
+ /**
1468
+ * Verify that a definition or footnote definition node is at the end of the document.
1469
+ */
1470
+ function verifyDefinitionOnDocumentLast(node, messageId) {
1471
+ if (!beforeLastNode) return;
1314
1472
  const range = sourceCode.getRange(node);
1315
- if (lastNonDefinitionRange[1] <= range[0]) return;
1473
+ if (sourceCode.getRange(beforeLastNode)[1] <= range[0]) return;
1316
1474
  context.report({
1317
1475
  node,
1318
- message: "Definition or footnote definition should be placed at the end of the document.",
1319
- *fix(fixer) {
1320
- let rangeStart = range[0];
1321
- for (let index = range[0] - 1; index >= 0; index--) {
1322
- if (sourceCode.text[index].trim()) break;
1323
- rangeStart = index;
1324
- }
1325
- yield fixer.removeRange([rangeStart, range[1]]);
1326
- yield fixer.insertTextAfterRange(lastNonDefinitionRange, sourceCode.text.slice(rangeStart, range[1]));
1476
+ messageId,
1477
+ fix(fixer) {
1478
+ return fixToMoveFromBeforeLastOfSectionToLastOfSection(fixer, beforeLastNode, node);
1327
1479
  }
1328
1480
  });
1329
- } };
1481
+ }
1482
+ /**
1483
+ * Verify that a definition or footnote definition node is at the end of its section.
1484
+ */
1485
+ function verifyDefinitionOnSectionLast(node, nextSectionHeading) {
1486
+ const beforeLastOfSectionNode = getSectionBeforeLastNode(nextSectionHeading);
1487
+ if (!beforeLastOfSectionNode) return;
1488
+ const range = sourceCode.getRange(node);
1489
+ const beforeLastOfSectionRange = sourceCode.getRange(beforeLastOfSectionNode);
1490
+ const nextSectionHeadingRange = sourceCode.getRange(nextSectionHeading);
1491
+ if (beforeLastOfSectionRange[1] <= range[0] && range[1] <= nextSectionHeadingRange[0]) return;
1492
+ const expectedStartLine = sourceCode.getLoc(beforeLastOfSectionNode).end.line + 1;
1493
+ const expectedEndLine = sourceCode.getLoc(nextSectionHeading).start.line - 1;
1494
+ context.report({
1495
+ node,
1496
+ messageId: "definitionsSectionLast",
1497
+ data: { at: expectedStartLine === expectedEndLine ? `L${expectedStartLine}` : `between L${expectedStartLine} and L${expectedEndLine}` },
1498
+ fix(fixer) {
1499
+ if (range[0] < beforeLastOfSectionRange[1]) return fixToMoveFromBeforeLastOfSectionToLastOfSection(fixer, beforeLastOfSectionNode, node);
1500
+ return fixToMoveFromAfterLastOfSectionToLastOfSection(fixer, nextSectionHeading, node);
1501
+ }
1502
+ });
1503
+ }
1504
+ /**
1505
+ * Get the node before the last node of a section.
1506
+ */
1507
+ function getSectionBeforeLastNode(nextSectionHeading) {
1508
+ let candidate = getPrevSibling(sourceCode, nextSectionHeading);
1509
+ while (candidate && canBePlacedLastNode(candidate)) candidate = getPrevSibling(sourceCode, candidate);
1510
+ return candidate;
1511
+ }
1512
+ /**
1513
+ * Fixer to move a definition or footnote definition node from before the last of the document/section
1514
+ * to the last of the document/section.
1515
+ */
1516
+ function fixToMoveFromBeforeLastOfSectionToLastOfSection(fixer, prev, node) {
1517
+ const next = getNextSibling(sourceCode, prev);
1518
+ return fixToMove(fixer, prev, next, node);
1519
+ }
1520
+ /**
1521
+ * Fixer to move a definition or footnote definition node from after the last of the document/section
1522
+ * to the last of the document/section.
1523
+ */
1524
+ function fixToMoveFromAfterLastOfSectionToLastOfSection(fixer, next, node) {
1525
+ const prev = getPrevSibling(sourceCode, next);
1526
+ if (!prev) return null;
1527
+ return fixToMove(fixer, prev, next, node);
1528
+ }
1529
+ /**
1530
+ * Fixer to move a definition or footnote definition node to after the prev node.
1531
+ */
1532
+ function* fixToMove(fixer, prev, next, node) {
1533
+ const range = sourceCode.getRange(node);
1534
+ const loc = sourceCode.getLoc(node);
1535
+ const lineStart = range[0] - loc.start.column + 1;
1536
+ let rangeStart = lineStart;
1537
+ let lineFeeds = 0;
1538
+ for (let index = rangeStart - 1; index >= 0; index--) {
1539
+ const c = sourceCode.text[index];
1540
+ if (c.trim()) break;
1541
+ rangeStart = index;
1542
+ if (c === "\n") lineFeeds++;
1543
+ }
1544
+ yield fixer.removeRange([rangeStart, range[1]]);
1545
+ let insertText = sourceCode.text.slice(rangeStart, lineStart) + sourceCode.text.slice(...range);
1546
+ if (prev.type === "footnoteDefinition" && node.type !== "footnoteDefinition" && lineFeeds <= 1) insertText = `\n${insertText}`;
1547
+ if (next && node.type === "footnoteDefinition" && next.type !== "footnoteDefinition") {
1548
+ const prevLoc = sourceCode.getLoc(prev);
1549
+ const nextLoc = sourceCode.getLoc(next);
1550
+ if (!(prevLoc.end.line + 1 < nextLoc.start.line)) insertText = `${insertText}\n`;
1551
+ }
1552
+ yield fixer.insertTextAfter(prev, insertText);
1553
+ }
1554
+ const containerStack = [];
1555
+ return {
1556
+ "blockquote, listItem, footnoteDefinition"(node) {
1557
+ containerStack.push(node);
1558
+ },
1559
+ "blockquote, listItem, footnoteDefinition:exit"() {
1560
+ containerStack.pop();
1561
+ },
1562
+ heading(node) {
1563
+ if (containerStack.length > 0) return;
1564
+ lastSection.nextHeading = node;
1565
+ lastSection = {
1566
+ heading: node,
1567
+ linkReferenceIds: /* @__PURE__ */ new Set(),
1568
+ footnoteReferenceIds: /* @__PURE__ */ new Set(),
1569
+ nextHeading: null
1570
+ };
1571
+ sections.push(lastSection);
1572
+ },
1573
+ linkReference(node) {
1574
+ lastSection.linkReferenceIds.add(node.identifier);
1575
+ },
1576
+ imageReference(node) {
1577
+ lastSection.linkReferenceIds.add(node.identifier);
1578
+ },
1579
+ footnoteReference(node) {
1580
+ lastSection.footnoteReferenceIds.add(node.identifier);
1581
+ },
1582
+ "definition, footnoteDefinition"(node) {
1583
+ definitions.push(node);
1584
+ },
1585
+ "root:exit"() {
1586
+ for (const node of definitions) verifyDefinitionPosition(node);
1587
+ }
1588
+ };
1330
1589
  }
1331
1590
  });
1332
1591
 
@@ -3422,7 +3681,7 @@ var emphasis_delimiters_style_default = createRule("emphasis-delimiters-style",
3422
3681
  docs: {
3423
3682
  description: "enforce a consistent delimiter style for emphasis and strong emphasis",
3424
3683
  categories: ["standard"],
3425
- listCategory: "Stylistic"
3684
+ listCategory: "Notation"
3426
3685
  },
3427
3686
  fixable: "code",
3428
3687
  hasSuggestions: false,
@@ -3604,7 +3863,7 @@ var hard_linebreak_style_default = createRule("hard-linebreak-style", {
3604
3863
  docs: {
3605
3864
  description: "enforce consistent hard linebreak style.",
3606
3865
  categories: ["recommended", "standard"],
3607
- listCategory: "Stylistic"
3866
+ listCategory: "Notation"
3608
3867
  },
3609
3868
  fixable: "code",
3610
3869
  hasSuggestions: false,
@@ -5037,7 +5296,7 @@ function parseListItem(sourceCode, node) {
5037
5296
  /**
5038
5297
  * Parse options.
5039
5298
  */
5040
- function parseOptions$2(options) {
5299
+ function parseOptions$3(options) {
5041
5300
  const listItems = options?.listItems;
5042
5301
  return { listItems: {
5043
5302
  first: listItems?.first ?? 1,
@@ -5051,7 +5310,7 @@ var indent_default = createRule("indent", {
5051
5310
  docs: {
5052
5311
  description: "enforce consistent indentation in Markdown files",
5053
5312
  categories: ["standard"],
5054
- listCategory: "Stylistic"
5313
+ listCategory: "Whitespace"
5055
5314
  },
5056
5315
  fixable: "whitespace",
5057
5316
  hasSuggestions: false,
@@ -5090,7 +5349,7 @@ var indent_default = createRule("indent", {
5090
5349
  },
5091
5350
  create(context) {
5092
5351
  const sourceCode = context.sourceCode;
5093
- const options = parseOptions$2(context.options[0]);
5352
+ const options = parseOptions$3(context.options[0]);
5094
5353
  class AbsBlockStack {
5095
5354
  violations = [];
5096
5355
  getCurrentBlockquote() {
@@ -6039,7 +6298,7 @@ var level1_heading_style_default = createRule("level1-heading-style", {
6039
6298
  docs: {
6040
6299
  description: "enforce consistent style for level 1 headings",
6041
6300
  categories: ["standard"],
6042
- listCategory: "Stylistic"
6301
+ listCategory: "Notation"
6043
6302
  },
6044
6303
  fixable: "code",
6045
6304
  hasSuggestions: false,
@@ -6116,7 +6375,7 @@ var level2_heading_style_default = createRule("level2-heading-style", {
6116
6375
  docs: {
6117
6376
  description: "enforce consistent style for level 2 headings",
6118
6377
  categories: ["standard"],
6119
- listCategory: "Stylistic"
6378
+ listCategory: "Notation"
6120
6379
  },
6121
6380
  fixable: "code",
6122
6381
  hasSuggestions: false,
@@ -6193,7 +6452,7 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6193
6452
  docs: {
6194
6453
  description: "enforce linebreaks after opening and before closing link brackets",
6195
6454
  categories: ["standard"],
6196
- listCategory: "Stylistic"
6455
+ listCategory: "Whitespace"
6197
6456
  },
6198
6457
  fixable: "whitespace",
6199
6458
  hasSuggestions: false,
@@ -6218,11 +6477,11 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6218
6477
  },
6219
6478
  create(context) {
6220
6479
  const sourceCode = context.sourceCode;
6221
- const optionProvider = parseOptions$4(context.options[0]);
6480
+ const optionProvider = parseOptions$6(context.options[0]);
6222
6481
  /**
6223
6482
  * Parse the options.
6224
6483
  */
6225
- function parseOptions$4(option) {
6484
+ function parseOptions$6(option) {
6226
6485
  const newline = option?.newline ?? "never";
6227
6486
  const multiline = option?.multiline ?? false;
6228
6487
  return (bracketsRange) => {
@@ -6353,7 +6612,7 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6353
6612
  /**
6354
6613
  * The basic option for links and images.
6355
6614
  */
6356
- function parseOptions$1(option) {
6615
+ function parseOptions$2(option) {
6357
6616
  const space = option?.space ?? "never";
6358
6617
  const imagesInLinks = option?.imagesInLinks;
6359
6618
  return {
@@ -6382,7 +6641,7 @@ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6382
6641
  docs: {
6383
6642
  description: "enforce consistent spacing inside link brackets",
6384
6643
  categories: ["standard"],
6385
- listCategory: "Stylistic"
6644
+ listCategory: "Whitespace"
6386
6645
  },
6387
6646
  fixable: "whitespace",
6388
6647
  hasSuggestions: false,
@@ -6403,7 +6662,7 @@ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6403
6662
  },
6404
6663
  create(context) {
6405
6664
  const sourceCode = context.sourceCode;
6406
- const options = parseOptions$1(context.options[0]);
6665
+ const options = parseOptions$2(context.options[0]);
6407
6666
  /**
6408
6667
  * Verify the space after the opening bracket and before the closing bracket.
6409
6668
  */
@@ -6546,7 +6805,7 @@ var link_destination_style_default = createRule("link-destination-style", {
6546
6805
  docs: {
6547
6806
  description: "enforce a consistent style for link destinations",
6548
6807
  categories: ["standard"],
6549
- listCategory: "Stylistic"
6808
+ listCategory: "Notation"
6550
6809
  },
6551
6810
  fixable: "code",
6552
6811
  hasSuggestions: false,
@@ -6671,7 +6930,7 @@ var link_paren_newline_default = createRule("link-paren-newline", {
6671
6930
  docs: {
6672
6931
  description: "enforce linebreaks after opening and before closing link parentheses",
6673
6932
  categories: ["standard"],
6674
- listCategory: "Stylistic"
6933
+ listCategory: "Whitespace"
6675
6934
  },
6676
6935
  fixable: "whitespace",
6677
6936
  hasSuggestions: false,
@@ -6696,11 +6955,11 @@ var link_paren_newline_default = createRule("link-paren-newline", {
6696
6955
  },
6697
6956
  create(context) {
6698
6957
  const sourceCode = context.sourceCode;
6699
- const optionProvider = parseOptions$4(context.options[0]);
6958
+ const optionProvider = parseOptions$6(context.options[0]);
6700
6959
  /**
6701
6960
  * Parse the options.
6702
6961
  */
6703
- function parseOptions$4(option) {
6962
+ function parseOptions$6(option) {
6704
6963
  const newline = option?.newline ?? "never";
6705
6964
  const multiline = option?.multiline ?? false;
6706
6965
  return (openingParenIndex, closingParenIndex) => {
@@ -6815,7 +7074,7 @@ var link_paren_spacing_default = createRule("link-paren-spacing", {
6815
7074
  docs: {
6816
7075
  description: "enforce consistent spacing inside link parentheses",
6817
7076
  categories: ["standard"],
6818
- listCategory: "Stylistic"
7077
+ listCategory: "Whitespace"
6819
7078
  },
6820
7079
  fixable: "whitespace",
6821
7080
  hasSuggestions: false,
@@ -6950,7 +7209,7 @@ var link_title_style_default = createRule("link-title-style", {
6950
7209
  docs: {
6951
7210
  description: "enforce a consistent style for link titles",
6952
7211
  categories: ["standard"],
6953
- listCategory: "Stylistic"
7212
+ listCategory: "Notation"
6954
7213
  },
6955
7214
  fixable: "code",
6956
7215
  hasSuggestions: false,
@@ -7032,7 +7291,7 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
7032
7291
  docs: {
7033
7292
  description: "enforce consistent alignment of list markers",
7034
7293
  categories: ["recommended", "standard"],
7035
- listCategory: "Stylistic"
7294
+ listCategory: "Whitespace"
7036
7295
  },
7037
7296
  fixable: "whitespace",
7038
7297
  hasSuggestions: false,
@@ -7131,7 +7390,7 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
7131
7390
  docs: {
7132
7391
  description: "disallow laziness in blockquotes",
7133
7392
  categories: ["recommended", "standard"],
7134
- listCategory: "Stylistic"
7393
+ listCategory: "Decorative"
7135
7394
  },
7136
7395
  fixable: void 0,
7137
7396
  hasSuggestions: true,
@@ -7230,6 +7489,193 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
7230
7489
  }
7231
7490
  });
7232
7491
 
7492
+ //#endregion
7493
+ //#region src/utils/table.ts
7494
+ /**
7495
+ * Parse the table.
7496
+ */
7497
+ function parseTable(sourceCode, node) {
7498
+ const headerRow = parseTableRow(sourceCode, node.children[0]);
7499
+ if (!headerRow) return null;
7500
+ const delimiterRow = parseTableDelimiterRow(sourceCode, node);
7501
+ if (!delimiterRow) return null;
7502
+ const bodyRows = [];
7503
+ for (const child of node.children.slice(1)) {
7504
+ const bodyRow = parseTableRow(sourceCode, child);
7505
+ if (!bodyRow) return null;
7506
+ bodyRows.push(bodyRow);
7507
+ }
7508
+ return {
7509
+ headerRow,
7510
+ delimiterRow,
7511
+ bodyRows
7512
+ };
7513
+ }
7514
+ /**
7515
+ * Parse the table delimiter row.
7516
+ */
7517
+ function parseTableDelimiterRow(sourceCode, node) {
7518
+ const headerRow = node.children[0];
7519
+ const headerRange = sourceCode.getRange(headerRow);
7520
+ const delimiterEndIndex = node.children.length > 1 ? sourceCode.getRange(node.children[1])[0] : sourceCode.getRange(node)[1];
7521
+ const delimiterText = sourceCode.text.slice(headerRange[1], delimiterEndIndex);
7522
+ const parsed = parseTableDelimiterRowFromText(delimiterText);
7523
+ if (!parsed) return null;
7524
+ const delimiters = parsed.delimiters.map((d) => {
7525
+ let leadingPipe = null;
7526
+ if (d.leadingPipe) {
7527
+ const leadingPipeRange = [headerRange[1] + d.leadingPipe.range[0], headerRange[1] + d.leadingPipe.range[1]];
7528
+ leadingPipe = {
7529
+ text: d.leadingPipe.text,
7530
+ range: leadingPipeRange,
7531
+ loc: getSourceLocationFromRange(sourceCode, headerRow, leadingPipeRange)
7532
+ };
7533
+ }
7534
+ const delimiterRange = [headerRange[1] + d.delimiter.range[0], headerRange[1] + d.delimiter.range[1]];
7535
+ return {
7536
+ leadingPipe,
7537
+ delimiter: {
7538
+ text: d.delimiter.text,
7539
+ align: d.delimiter.text.startsWith(":") ? d.delimiter.text.endsWith(":") ? "center" : "left" : d.delimiter.text.endsWith(":") ? "right" : "none",
7540
+ range: delimiterRange,
7541
+ loc: getSourceLocationFromRange(sourceCode, headerRow, delimiterRange)
7542
+ }
7543
+ };
7544
+ });
7545
+ let trailingPipe = null;
7546
+ if (parsed.trailingPipe) {
7547
+ const trailingPipeRange = [headerRange[1] + parsed.trailingPipe.range[0], headerRange[1] + parsed.trailingPipe.range[1]];
7548
+ trailingPipe = {
7549
+ text: parsed.trailingPipe.text,
7550
+ range: trailingPipeRange,
7551
+ loc: getSourceLocationFromRange(sourceCode, headerRow, trailingPipeRange)
7552
+ };
7553
+ }
7554
+ const firstToken = delimiters[0].leadingPipe ?? delimiters[0].delimiter;
7555
+ const lastToken = trailingPipe ?? delimiters[delimiters.length - 1].delimiter;
7556
+ return {
7557
+ delimiters,
7558
+ trailingPipe,
7559
+ range: [firstToken.range[0], lastToken.range[1]],
7560
+ loc: {
7561
+ start: firstToken.loc.start,
7562
+ end: lastToken.loc.end
7563
+ }
7564
+ };
7565
+ }
7566
+ /**
7567
+ * Parse the table row.
7568
+ */
7569
+ function parseTableRow(sourceCode, node) {
7570
+ const cells = [];
7571
+ let trailingPipe = null;
7572
+ for (const cell of node.children) {
7573
+ const cellRange = sourceCode.getRange(cell);
7574
+ const cellLoc = sourceCode.getLoc(cell);
7575
+ const leadingPipe = sourceCode.text[cellRange[0]] === "|" ? {
7576
+ text: "|",
7577
+ range: [cellRange[0], cellRange[0] + 1],
7578
+ loc: {
7579
+ start: cellLoc.start,
7580
+ end: {
7581
+ line: cellLoc.start.line,
7582
+ column: cellLoc.start.column + 1
7583
+ }
7584
+ }
7585
+ } : null;
7586
+ if (trailingPipe && leadingPipe) return null;
7587
+ let parsedCell = null;
7588
+ if (cell.children.length > 0) {
7589
+ const firstChild = cell.children[0];
7590
+ const lastChild = cell.children[cell.children.length - 1];
7591
+ parsedCell = {
7592
+ range: [sourceCode.getRange(firstChild)[0], sourceCode.getRange(lastChild)[1]],
7593
+ loc: {
7594
+ start: sourceCode.getLoc(firstChild).start,
7595
+ end: sourceCode.getLoc(lastChild).end
7596
+ }
7597
+ };
7598
+ }
7599
+ cells.push({
7600
+ leadingPipe,
7601
+ cell: parsedCell
7602
+ });
7603
+ trailingPipe = sourceCode.text[cellRange[1] - 1] === "|" ? {
7604
+ text: "|",
7605
+ range: [cellRange[1] - 1, cellRange[1]],
7606
+ loc: {
7607
+ start: {
7608
+ line: cellLoc.end.line,
7609
+ column: cellLoc.end.column - 1
7610
+ },
7611
+ end: cellLoc.end
7612
+ }
7613
+ } : null;
7614
+ }
7615
+ const firstToken = cells[0].leadingPipe ?? cells[0].cell;
7616
+ const lastToken = trailingPipe ?? cells[cells.length - 1].cell ?? cells[cells.length - 1].leadingPipe;
7617
+ return {
7618
+ cells,
7619
+ trailingPipe,
7620
+ range: [firstToken.range[0], lastToken.range[1]],
7621
+ loc: {
7622
+ start: firstToken.loc.start,
7623
+ end: lastToken.loc.end
7624
+ }
7625
+ };
7626
+ }
7627
+ /**
7628
+ * Parse the table delimiter row from the text.
7629
+ */
7630
+ function parseTableDelimiterRowFromText(text) {
7631
+ const cursor = new ForwardCharacterCursor(text);
7632
+ cursor.skipSpaces();
7633
+ while (cursor.curr() === ">") {
7634
+ cursor.next();
7635
+ cursor.skipSpaces();
7636
+ }
7637
+ const delimiters = [];
7638
+ let pipe = consumePipe();
7639
+ while (!cursor.finished()) {
7640
+ const delimiterStart = cursor.currIndex();
7641
+ cursor.skipUntilEnd((c) => c === "|" || isSpaceOrTab(c) || c === "\n" || c === "\r");
7642
+ const delimiterRange = [delimiterStart, cursor.currIndex()];
7643
+ const delimiterText = text.slice(...delimiterRange);
7644
+ if (!/^:?-+:?$/u.test(delimiterText)) return null;
7645
+ if (delimiters.length > 0 && pipe == null) return null;
7646
+ delimiters.push({
7647
+ leadingPipe: pipe,
7648
+ delimiter: {
7649
+ text: delimiterText,
7650
+ range: delimiterRange
7651
+ }
7652
+ });
7653
+ pipe = consumePipe();
7654
+ }
7655
+ return {
7656
+ delimiters,
7657
+ trailingPipe: pipe
7658
+ };
7659
+ /**
7660
+ * Consume a pipe if exists.
7661
+ */
7662
+ function consumePipe() {
7663
+ cursor.skipSpaces();
7664
+ if (cursor.curr() === "|") {
7665
+ const pipeStart = cursor.currIndex();
7666
+ cursor.next();
7667
+ const pipeRange = [pipeStart, cursor.currIndex()];
7668
+ const result = {
7669
+ text: text.slice(...pipeRange),
7670
+ range: pipeRange
7671
+ };
7672
+ cursor.skipSpaces();
7673
+ return result;
7674
+ }
7675
+ return null;
7676
+ }
7677
+ }
7678
+
7233
7679
  //#endregion
7234
7680
  //#region src/rules/no-multi-spaces.ts
7235
7681
  var no_multi_spaces_default = createRule("no-multi-spaces", {
@@ -7238,7 +7684,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7238
7684
  docs: {
7239
7685
  description: "disallow multiple spaces",
7240
7686
  categories: ["standard"],
7241
- listCategory: "Stylistic"
7687
+ listCategory: "Whitespace"
7242
7688
  },
7243
7689
  fixable: "whitespace",
7244
7690
  hasSuggestions: false,
@@ -7258,7 +7704,8 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7258
7704
  linkReference: verifyLinkReference,
7259
7705
  listItem: verifyListItem,
7260
7706
  blockquote: processBlockquote,
7261
- text: verifyText
7707
+ text: verifyText,
7708
+ table: verifyTable
7262
7709
  };
7263
7710
  /**
7264
7711
  * Verify a text node.
@@ -7267,6 +7714,14 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7267
7714
  verifyTextInNode(node);
7268
7715
  }
7269
7716
  /**
7717
+ * Verify a table node.
7718
+ */
7719
+ function verifyTable(node) {
7720
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
7721
+ if (!parsedDelimiterRow) return;
7722
+ verifyTextInRange(node, parsedDelimiterRow.range);
7723
+ }
7724
+ /**
7270
7725
  * Verify a definition node.
7271
7726
  */
7272
7727
  function verifyLinkDefinition(node) {
@@ -7442,7 +7897,7 @@ var no_multiple_empty_lines_default = createRule("no-multiple-empty-lines", {
7442
7897
  docs: {
7443
7898
  description: "disallow multiple empty lines in Markdown files.",
7444
7899
  categories: ["standard"],
7445
- listCategory: "Stylistic"
7900
+ listCategory: "Whitespace"
7446
7901
  },
7447
7902
  fixable: "whitespace",
7448
7903
  hasSuggestions: false,
@@ -7601,7 +8056,7 @@ var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebrea
7601
8056
  docs: {
7602
8057
  description: "disallow text backslash at the end of a line.",
7603
8058
  categories: ["recommended", "standard"],
7604
- listCategory: "Stylistic"
8059
+ listCategory: "Notation"
7605
8060
  },
7606
8061
  fixable: void 0,
7607
8062
  hasSuggestions: true,
@@ -7646,7 +8101,7 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
7646
8101
  docs: {
7647
8102
  description: "disallow trailing whitespace at the end of lines in Markdown files.",
7648
8103
  categories: ["standard"],
7649
- listCategory: "Stylistic"
8104
+ listCategory: "Whitespace"
7650
8105
  },
7651
8106
  fixable: "whitespace",
7652
8107
  hasSuggestions: false,
@@ -7772,7 +8227,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
7772
8227
  docs: {
7773
8228
  description: "enforce that ordered list markers use sequential numbers",
7774
8229
  categories: ["standard"],
7775
- listCategory: "Stylistic"
8230
+ listCategory: "Decorative"
7776
8231
  },
7777
8232
  fixable: "code",
7778
8233
  hasSuggestions: true,
@@ -7986,7 +8441,7 @@ function markerToKind(marker) {
7986
8441
  /**
7987
8442
  * Parse rule options.
7988
8443
  */
7989
- function parseOptions(options) {
8444
+ function parseOptions$1(options) {
7990
8445
  const prefer = markerToKind(options.prefer) || ".";
7991
8446
  const overrides = (options.overrides ?? []).map((override) => {
7992
8447
  const preferForOverride = markerToKind(override.prefer) || ".";
@@ -8013,7 +8468,7 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
8013
8468
  docs: {
8014
8469
  description: "enforce consistent ordered list marker style",
8015
8470
  categories: ["standard"],
8016
- listCategory: "Stylistic"
8471
+ listCategory: "Notation"
8017
8472
  },
8018
8473
  fixable: "code",
8019
8474
  hasSuggestions: false,
@@ -8047,7 +8502,7 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
8047
8502
  },
8048
8503
  create(context) {
8049
8504
  const sourceCode = context.sourceCode;
8050
- const options = parseOptions(context.options[0] || {});
8505
+ const options = parseOptions$1(context.options[0] || {});
8051
8506
  let containerStack = {
8052
8507
  node: sourceCode.ast,
8053
8508
  level: 1,
@@ -8232,7 +8687,7 @@ var padding_line_between_blocks_default = createRule("padding-line-between-block
8232
8687
  docs: {
8233
8688
  description: "require or disallow padding lines between blocks",
8234
8689
  categories: ["standard"],
8235
- listCategory: "Stylistic"
8690
+ listCategory: "Whitespace"
8236
8691
  },
8237
8692
  fixable: "whitespace",
8238
8693
  hasSuggestions: false,
@@ -8430,7 +8885,7 @@ var prefer_autolinks_default = createRule("prefer-autolinks", {
8430
8885
  docs: {
8431
8886
  description: "enforce the use of autolinks for URLs",
8432
8887
  categories: ["recommended", "standard"],
8433
- listCategory: "Stylistic"
8888
+ listCategory: "Notation"
8434
8889
  },
8435
8890
  fixable: "code",
8436
8891
  hasSuggestions: false,
@@ -8468,7 +8923,7 @@ var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks",
8468
8923
  docs: {
8469
8924
  description: "enforce the use of fenced code blocks over indented code blocks",
8470
8925
  categories: ["recommended", "standard"],
8471
- listCategory: "Stylistic"
8926
+ listCategory: "Notation"
8472
8927
  },
8473
8928
  fixable: "code",
8474
8929
  hasSuggestions: false,
@@ -8698,7 +9153,7 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
8698
9153
  docs: {
8699
9154
  description: "enforce using link reference definitions instead of inline links",
8700
9155
  categories: [],
8701
- listCategory: "Stylistic"
9156
+ listCategory: "Notation"
8702
9157
  },
8703
9158
  fixable: "code",
8704
9159
  hasSuggestions: false,
@@ -8977,9 +9432,9 @@ var setext_heading_underline_length_default = createRule("setext-heading-underli
8977
9432
  docs: {
8978
9433
  description: "enforce setext heading underline length",
8979
9434
  categories: ["standard"],
8980
- listCategory: "Stylistic"
9435
+ listCategory: "Decorative"
8981
9436
  },
8982
- fixable: "whitespace",
9437
+ fixable: "code",
8983
9438
  schema: [{
8984
9439
  type: "object",
8985
9440
  properties: {
@@ -9215,7 +9670,7 @@ var sort_definitions_default = createRule("sort-definitions", {
9215
9670
  docs: {
9216
9671
  description: "enforce a specific order for link definitions and footnote definitions",
9217
9672
  categories: ["standard"],
9218
- listCategory: "Stylistic"
9673
+ listCategory: "Decorative"
9219
9674
  },
9220
9675
  fixable: "code",
9221
9676
  hasSuggestions: false,
@@ -9488,7 +9943,7 @@ var strikethrough_delimiters_style_default = createRule("strikethrough-delimiter
9488
9943
  docs: {
9489
9944
  description: "enforce a consistent delimiter style for strikethrough",
9490
9945
  categories: ["standard"],
9491
- listCategory: "Stylistic"
9946
+ listCategory: "Notation"
9492
9947
  },
9493
9948
  fixable: "code",
9494
9949
  hasSuggestions: false,
@@ -9655,6 +10110,759 @@ var table_header_casing_default = createRule("table-header-casing", {
9655
10110
  }
9656
10111
  });
9657
10112
 
10113
+ //#endregion
10114
+ //#region src/rules/table-leading-trailing-pipes.ts
10115
+ var table_leading_trailing_pipes_default = createRule("table-leading-trailing-pipes", {
10116
+ meta: {
10117
+ type: "layout",
10118
+ docs: {
10119
+ description: "enforce consistent use of leading and trailing pipes in tables.",
10120
+ categories: ["standard"],
10121
+ listCategory: "Decorative"
10122
+ },
10123
+ fixable: "code",
10124
+ hasSuggestions: false,
10125
+ schema: [{ anyOf: [{ enum: ["always", "never"] }, {
10126
+ type: "object",
10127
+ properties: {
10128
+ leading: { enum: ["always", "never"] },
10129
+ trailing: { enum: ["always", "never"] }
10130
+ },
10131
+ additionalProperties: false
10132
+ }] }],
10133
+ messages: {
10134
+ missingLeadingPipe: "Table line should start with a leading pipe.",
10135
+ unexpectedLeadingPipe: "Table line should not start with a leading pipe.",
10136
+ missingTrailingPipe: "Table line should end with a trailing pipe.",
10137
+ unexpectedTrailingPipe: "Table line should not end with a trailing pipe."
10138
+ }
10139
+ },
10140
+ create(context) {
10141
+ const sourceCode = context.sourceCode;
10142
+ const preferOption = context.options[0] ?? "always";
10143
+ const leadingOption = typeof preferOption === "string" ? preferOption : preferOption.leading ?? "always";
10144
+ const trailingOption = typeof preferOption === "string" ? preferOption : preferOption.trailing ?? "always";
10145
+ /**
10146
+ * Verify the table pipes
10147
+ */
10148
+ function verifyTablePipes(node) {
10149
+ for (const row of node.children) verifyTableRowPipes(row);
10150
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
10151
+ if (parsedDelimiterRow) verifyTableLinePipes(parsedDelimiterRow.range, parsedDelimiterRow.loc, parsedDelimiterRow.delimiters.length);
10152
+ }
10153
+ /**
10154
+ * Verify the table row pipes
10155
+ */
10156
+ function verifyTableRowPipes(node) {
10157
+ const loc = sourceCode.getLoc(node);
10158
+ const range = sourceCode.getRange(node);
10159
+ verifyTableLinePipes(range, loc, node.children.length);
10160
+ }
10161
+ /**
10162
+ * Verify the table line pipes
10163
+ */
10164
+ function verifyTableLinePipes(lineContentRange, lineLocation, columnCount) {
10165
+ verifyTableLeadingPipe(lineContentRange, lineLocation, columnCount);
10166
+ verifyTableTrailingPipe(lineContentRange, lineLocation, columnCount);
10167
+ }
10168
+ /**
10169
+ * Verify the table leading pipe
10170
+ */
10171
+ function verifyTableLeadingPipe(lineContentRange, lineLocation, columnCount) {
10172
+ if (leadingOption === "always") {
10173
+ if (sourceCode.text.startsWith("|", lineContentRange[0])) return;
10174
+ context.report({
10175
+ messageId: "missingLeadingPipe",
10176
+ loc: lineLocation.start,
10177
+ fix(fixer) {
10178
+ return fixer.insertTextBeforeRange(lineContentRange, "| ");
10179
+ }
10180
+ });
10181
+ } else if (leadingOption === "never") {
10182
+ if (columnCount < 2) {
10183
+ if (!(trailingOption === "always" && sourceCode.text.endsWith("|", lineContentRange[1]))) return;
10184
+ }
10185
+ if (!sourceCode.text.startsWith("|", lineContentRange[0])) return;
10186
+ let endIndex = lineContentRange[0] + 1;
10187
+ while (endIndex < lineContentRange[1] && isSpaceOrTab(sourceCode.text[endIndex])) endIndex++;
10188
+ context.report({
10189
+ messageId: "unexpectedLeadingPipe",
10190
+ loc: {
10191
+ start: lineLocation.start,
10192
+ end: {
10193
+ line: lineLocation.start.line,
10194
+ column: lineLocation.start.column + (endIndex - lineContentRange[0])
10195
+ }
10196
+ },
10197
+ fix(fixer) {
10198
+ return fixer.removeRange([lineContentRange[0], endIndex]);
10199
+ }
10200
+ });
10201
+ }
10202
+ }
10203
+ /**
10204
+ * Verify the table trailing pipe
10205
+ */
10206
+ function verifyTableTrailingPipe(lineContentRange, lineLocation, columnCount) {
10207
+ if (trailingOption === "always") {
10208
+ if (sourceCode.text.endsWith("|", lineContentRange[1])) return;
10209
+ context.report({
10210
+ messageId: "missingTrailingPipe",
10211
+ loc: lineLocation.end,
10212
+ fix(fixer) {
10213
+ return fixer.insertTextAfterRange(lineContentRange, " |");
10214
+ }
10215
+ });
10216
+ } else if (trailingOption === "never") {
10217
+ if (columnCount < 2) {
10218
+ if (!(leadingOption === "always" && sourceCode.text.startsWith("|", lineContentRange[0]))) return;
10219
+ }
10220
+ if (!sourceCode.text.endsWith("|", lineContentRange[1])) return;
10221
+ let startIndex = lineContentRange[1] - 1;
10222
+ while (startIndex - 1 > lineContentRange[0] && isSpaceOrTab(sourceCode.text[startIndex - 1])) startIndex--;
10223
+ context.report({
10224
+ messageId: "unexpectedTrailingPipe",
10225
+ loc: {
10226
+ start: {
10227
+ line: lineLocation.end.line,
10228
+ column: lineLocation.end.column - (lineContentRange[1] - startIndex)
10229
+ },
10230
+ end: lineLocation.end
10231
+ },
10232
+ fix(fixer) {
10233
+ return fixer.removeRange([startIndex, lineContentRange[1]]);
10234
+ }
10235
+ });
10236
+ }
10237
+ }
10238
+ return { table(node) {
10239
+ verifyTablePipes(node);
10240
+ } };
10241
+ }
10242
+ });
10243
+
10244
+ //#endregion
10245
+ //#region src/rules/table-pipe-alignment.ts
10246
+ var table_pipe_alignment_default = createRule("table-pipe-alignment", {
10247
+ meta: {
10248
+ type: "layout",
10249
+ docs: {
10250
+ description: "enforce consistent alignment of table pipes",
10251
+ categories: ["standard"],
10252
+ listCategory: "Decorative"
10253
+ },
10254
+ fixable: "code",
10255
+ hasSuggestions: false,
10256
+ schema: [{
10257
+ type: "object",
10258
+ properties: { column: { enum: ["minimum", "consistent"] } },
10259
+ additionalProperties: false
10260
+ }],
10261
+ messages: {
10262
+ addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
10263
+ removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
10264
+ }
10265
+ },
10266
+ create(context) {
10267
+ const sourceCode = context.sourceCode;
10268
+ const columnOption = (context.options[0] || {}).column || "minimum";
10269
+ class TableContext {
10270
+ rows;
10271
+ columnCount;
10272
+ _cacheHasSpaceBetweenContentAndTrailingPipe = /* @__PURE__ */ new Map();
10273
+ _cacheExpectedPipePosition = /* @__PURE__ */ new Map();
10274
+ constructor(parsed) {
10275
+ const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
10276
+ for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
10277
+ this.rows = rows;
10278
+ let columnCount = 0;
10279
+ for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
10280
+ this.columnCount = columnCount;
10281
+ }
10282
+ /**
10283
+ * Get the expected pipe position for the index
10284
+ */
10285
+ getExpectedPipePosition(pipeIndex) {
10286
+ let v = this._cacheExpectedPipePosition.get(pipeIndex);
10287
+ if (v !== void 0) return v;
10288
+ v = this._computeExpectedPipePositionWithoutCache(pipeIndex);
10289
+ this._cacheExpectedPipePosition.set(pipeIndex, v);
10290
+ return v;
10291
+ }
10292
+ /**
10293
+ * Check if there is at least one space between content and trailing pipe
10294
+ * for the index
10295
+ *
10296
+ * This is used to determine if the pipe should be aligned with a space before it.
10297
+ */
10298
+ hasSpaceBetweenContentAndTrailingPipe(pipeIndex) {
10299
+ if (pipeIndex === 0) return false;
10300
+ let v = this._cacheHasSpaceBetweenContentAndTrailingPipe.get(pipeIndex);
10301
+ if (v != null) return v;
10302
+ v = this._hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex);
10303
+ this._cacheHasSpaceBetweenContentAndTrailingPipe.set(pipeIndex, v);
10304
+ return v;
10305
+ }
10306
+ /**
10307
+ * Get the expected pipe position for the index
10308
+ */
10309
+ _computeExpectedPipePositionWithoutCache(pipeIndex) {
10310
+ if (pipeIndex === 0) {
10311
+ const firstCell = this.rows[0].cells[0];
10312
+ const firstToken = firstCell.leadingPipe ?? firstCell.content;
10313
+ if (!firstToken) return null;
10314
+ return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
10315
+ }
10316
+ if (columnOption === "minimum") return this.getMinimumPipePosition(pipeIndex);
10317
+ else if (columnOption === "consistent") {
10318
+ const columnIndex = pipeIndex - 1;
10319
+ for (const row of this.rows) {
10320
+ if (row.cells.length <= columnIndex) continue;
10321
+ const cell = row.cells[columnIndex];
10322
+ if (cell.type === "delimiter" || !cell.trailingPipe) continue;
10323
+ const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
10324
+ return Math.max(width, this.getMinimumPipePosition(pipeIndex) || 0);
10325
+ }
10326
+ }
10327
+ return null;
10328
+ }
10329
+ /**
10330
+ * Get the minimum pipe position for the index
10331
+ */
10332
+ getMinimumPipePosition(pipeIndex) {
10333
+ const needSpaceBeforePipe = this.hasSpaceBetweenContentAndTrailingPipe(pipeIndex);
10334
+ let maxWidth = 0;
10335
+ const columnIndex = pipeIndex - 1;
10336
+ for (const row of this.rows) {
10337
+ if (row.cells.length <= columnIndex) continue;
10338
+ const cell = row.cells[columnIndex];
10339
+ let width;
10340
+ if (cell.type === "delimiter") {
10341
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10342
+ width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
10343
+ } else {
10344
+ if (!cell.content) continue;
10345
+ width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
10346
+ }
10347
+ if (needSpaceBeforePipe) width += 1;
10348
+ maxWidth = Math.max(maxWidth, width);
10349
+ }
10350
+ return maxWidth;
10351
+ }
10352
+ /**
10353
+ * Check if there is at least one space between content and trailing pipe
10354
+ */
10355
+ _hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex) {
10356
+ const columnIndex = pipeIndex - 1;
10357
+ for (const row of this.rows) {
10358
+ if (row.cells.length <= columnIndex) continue;
10359
+ const cell = row.cells[columnIndex];
10360
+ if (!cell.trailingPipe) continue;
10361
+ let content;
10362
+ if (cell.type === "delimiter") content = cell.delimiter;
10363
+ else {
10364
+ if (!cell.content) continue;
10365
+ content = cell.content;
10366
+ }
10367
+ if (content.range[1] < cell.trailingPipe.range[0]) continue;
10368
+ return false;
10369
+ }
10370
+ return true;
10371
+ }
10372
+ }
10373
+ /**
10374
+ * Verify the table pipes
10375
+ */
10376
+ function verifyTablePipes(table) {
10377
+ const targetRows = [...table.rows];
10378
+ for (const row of targetRows) for (let pipeIndex = 0; pipeIndex <= table.columnCount; pipeIndex++) if (!verifyRowPipe(row, pipeIndex, table)) break;
10379
+ }
10380
+ /**
10381
+ * Verify the pipe in the row
10382
+ */
10383
+ function verifyRowPipe(row, pipeIndex, table) {
10384
+ let cellIndex;
10385
+ let pipe;
10386
+ if (pipeIndex === 0) {
10387
+ cellIndex = 0;
10388
+ pipe = "leadingPipe";
10389
+ } else {
10390
+ cellIndex = pipeIndex - 1;
10391
+ pipe = "trailingPipe";
10392
+ }
10393
+ if (row.cells.length <= cellIndex) return true;
10394
+ const cell = row.cells[cellIndex];
10395
+ const pipeToken = cell[pipe];
10396
+ if (!pipeToken) return true;
10397
+ return verifyPipe(pipeToken, pipeIndex, table, cell);
10398
+ }
10399
+ /**
10400
+ * Verify the pipe position
10401
+ */
10402
+ function verifyPipe(pipe, pipeIndex, table, cell) {
10403
+ const expected = table.getExpectedPipePosition(pipeIndex);
10404
+ if (expected == null) return true;
10405
+ const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
10406
+ const diff = expected - actual;
10407
+ if (diff === 0) return true;
10408
+ context.report({
10409
+ loc: pipe.loc,
10410
+ messageId: diff > 0 ? "addSpaces" : "removeSpaces",
10411
+ data: {
10412
+ expected: String(expected),
10413
+ count: String(Math.abs(diff)),
10414
+ plural: Math.abs(diff) === 1 ? "" : "s"
10415
+ },
10416
+ fix(fixer) {
10417
+ if (diff > 0) {
10418
+ if (pipeIndex === 0 || cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
10419
+ return fixer.insertTextAfterRange([cell.delimiter.range[0], cell.delimiter.range[0] + 1], "-".repeat(diff));
10420
+ }
10421
+ const baseEdit = fixRemoveSpaces();
10422
+ if (baseEdit) return baseEdit;
10423
+ if (pipeIndex === 0 || cell.type === "cell") return null;
10424
+ const beforeDelimiter = sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1);
10425
+ const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
10426
+ const newLength = expected - widthBeforeDelimiter;
10427
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10428
+ const spaceAfter = table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? " " : "";
10429
+ if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
10430
+ const delimiterPrefix = cell.align === "left" || cell.align === "center" ? ":" : "";
10431
+ const delimiterSuffix = (cell.align === "right" || cell.align === "center" ? ":" : "") + spaceAfter;
10432
+ const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
10433
+ return fixer.replaceTextRange([cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
10434
+ /**
10435
+ * Fixer to remove spaces before the pipe
10436
+ */
10437
+ function fixRemoveSpaces() {
10438
+ const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
10439
+ const trimmedBeforePipe = beforePipe.trimEnd();
10440
+ const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
10441
+ const widthBeforePipe = getTextWidth(trimmedBeforePipe);
10442
+ const newSpacesLength = expected - widthBeforePipe;
10443
+ if (newSpacesLength < (table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? 1 : 0)) return null;
10444
+ return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
10445
+ }
10446
+ }
10447
+ });
10448
+ return false;
10449
+ }
10450
+ /**
10451
+ * Get the minimum delimiter length based on alignment
10452
+ */
10453
+ function getMinimumDelimiterLength(align) {
10454
+ return align === "none" ? 1 : align === "center" ? 3 : 2;
10455
+ }
10456
+ return { table(node) {
10457
+ const parsed = parseTable(sourceCode, node);
10458
+ if (!parsed) return;
10459
+ verifyTablePipes(new TableContext(parsed));
10460
+ } };
10461
+ }
10462
+ });
10463
+ /**
10464
+ * Convert a parsed table row to row data
10465
+ */
10466
+ function parsedTableRowToRowData(parsedRow) {
10467
+ return { cells: parsedRow.cells.map((cell, index) => {
10468
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10469
+ return {
10470
+ type: "cell",
10471
+ leadingPipe: cell.leadingPipe,
10472
+ content: cell.cell,
10473
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10474
+ };
10475
+ }) };
10476
+ }
10477
+ /**
10478
+ * Convert a parsed table delimiter row to row data
10479
+ */
10480
+ function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
10481
+ return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
10482
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10483
+ return {
10484
+ type: "delimiter",
10485
+ leadingPipe: cell.leadingPipe,
10486
+ delimiter: cell.delimiter,
10487
+ align: cell.delimiter.align,
10488
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10489
+ };
10490
+ }) };
10491
+ }
10492
+
10493
+ //#endregion
10494
+ //#region src/rules/table-pipe-spacing.ts
10495
+ /**
10496
+ * Parsed options
10497
+ */
10498
+ function parseOptions(options) {
10499
+ const spaceOption = options?.space;
10500
+ const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10501
+ const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10502
+ const cellAlignOption = options?.cellAlign;
10503
+ const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10504
+ const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10505
+ const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10506
+ const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10507
+ return {
10508
+ leadingSpace,
10509
+ trailingSpace,
10510
+ cellAlignByDelimiter: {
10511
+ none: adjustAlign(defaultDelimiterCellAlign),
10512
+ left: adjustAlign(leftAlignmentDelimiterCellAlign),
10513
+ center: adjustAlign(centerAlignmentDelimiterCellAlign),
10514
+ right: adjustAlign(rightAlignmentDelimiterCellAlign)
10515
+ }
10516
+ };
10517
+ /**
10518
+ * Adjust the alignment option based on the spacing options.
10519
+ */
10520
+ function adjustAlign(align) {
10521
+ if (align === "left") {
10522
+ if (trailingSpace === "always") return "left";
10523
+ return "ignore";
10524
+ }
10525
+ if (align === "center") {
10526
+ if (leadingSpace === "always" && trailingSpace === "always") return "center";
10527
+ return "ignore";
10528
+ }
10529
+ if (align === "right") {
10530
+ if (leadingSpace === "always") return "right";
10531
+ return "ignore";
10532
+ }
10533
+ return align;
10534
+ }
10535
+ }
10536
+ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
10537
+ meta: {
10538
+ type: "layout",
10539
+ docs: {
10540
+ description: "enforce consistent spacing around table pipes",
10541
+ categories: ["standard"],
10542
+ listCategory: "Whitespace"
10543
+ },
10544
+ fixable: "whitespace",
10545
+ hasSuggestions: false,
10546
+ schema: [{
10547
+ type: "object",
10548
+ properties: {
10549
+ space: { anyOf: [{ enum: ["always", "never"] }, {
10550
+ type: "object",
10551
+ properties: {
10552
+ leading: { enum: ["always", "never"] },
10553
+ trailing: { enum: ["always", "never"] }
10554
+ },
10555
+ additionalProperties: false
10556
+ }] },
10557
+ cellAlign: { anyOf: [{ enum: [
10558
+ "left",
10559
+ "center",
10560
+ "right"
10561
+ ] }, {
10562
+ type: "object",
10563
+ properties: {
10564
+ defaultDelimiter: { enum: [
10565
+ "left",
10566
+ "center",
10567
+ "right",
10568
+ "ignore"
10569
+ ] },
10570
+ leftAlignmentDelimiter: { enum: [
10571
+ "left",
10572
+ "center",
10573
+ "right",
10574
+ "ignore"
10575
+ ] },
10576
+ centerAlignmentDelimiter: { enum: [
10577
+ "left",
10578
+ "center",
10579
+ "right",
10580
+ "ignore"
10581
+ ] },
10582
+ rightAlignmentDelimiter: { enum: [
10583
+ "left",
10584
+ "center",
10585
+ "right",
10586
+ "ignore"
10587
+ ] }
10588
+ },
10589
+ additionalProperties: false
10590
+ }] }
10591
+ },
10592
+ additionalProperties: false
10593
+ }],
10594
+ messages: {
10595
+ expectedSpaceBefore: "Expected 1 space before \"|\".",
10596
+ expectedNoSpaceBefore: "Expected no space before \"|\".",
10597
+ expectedSpaceAfter: "Expected 1 space after \"|\".",
10598
+ expectedNoSpaceAfter: "Expected no space after \"|\".",
10599
+ expectedAlignLeft: "Expected 1 space after \"|\" for left-aligned column.",
10600
+ expectedNoSpaceAlignLeft: "Expected no space after \"|\" for left-aligned column.",
10601
+ expectedAlignRight: "Expected 1 space before \"|\" for right-aligned column.",
10602
+ expectedNoSpaceAlignRight: "Expected no space before \"|\" for right-aligned column.",
10603
+ expectedAlignCenter: "Expected the number of spaces before and after the content to be the same or differ by 1 at most for center-aligned column."
10604
+ }
10605
+ },
10606
+ create(context) {
10607
+ const sourceCode = context.sourceCode;
10608
+ const options = parseOptions(context.options[0]);
10609
+ /**
10610
+ * Verify for the leading pipe.
10611
+ */
10612
+ function verifyLeadingPipe(pipe, nextToken) {
10613
+ if (options.leadingSpace === "always") {
10614
+ if (pipe.range[1] < nextToken.range[0]) return true;
10615
+ context.report({
10616
+ loc: pipe.loc,
10617
+ messageId: "expectedSpaceAfter",
10618
+ fix(fixer) {
10619
+ return fixer.insertTextAfterRange(pipe.range, " ");
10620
+ }
10621
+ });
10622
+ return false;
10623
+ } else if (options.leadingSpace === "never") {
10624
+ if (pipe.range[1] === nextToken.range[0]) return true;
10625
+ context.report({
10626
+ loc: {
10627
+ start: pipe.loc.end,
10628
+ end: nextToken.loc.start
10629
+ },
10630
+ messageId: "expectedNoSpaceAfter",
10631
+ fix(fixer) {
10632
+ return fixer.removeRange([pipe.range[1], nextToken.range[0]]);
10633
+ }
10634
+ });
10635
+ return false;
10636
+ }
10637
+ return true;
10638
+ }
10639
+ /**
10640
+ * Verify for the trailing pipe.
10641
+ */
10642
+ function verifyTrailingPipe(prevToken, pipe) {
10643
+ if (options.trailingSpace === "always") {
10644
+ if (prevToken.range[1] < pipe.range[0]) return true;
10645
+ context.report({
10646
+ loc: pipe.loc,
10647
+ messageId: "expectedSpaceBefore",
10648
+ fix(fixer) {
10649
+ return fixer.insertTextBeforeRange(pipe.range, " ");
10650
+ }
10651
+ });
10652
+ return false;
10653
+ } else if (options.trailingSpace === "never") {
10654
+ if (prevToken.range[1] === pipe.range[0]) return true;
10655
+ context.report({
10656
+ loc: {
10657
+ start: prevToken.loc.end,
10658
+ end: pipe.loc.start
10659
+ },
10660
+ messageId: "expectedNoSpaceBefore",
10661
+ fix(fixer) {
10662
+ return fixer.removeRange([prevToken.range[1], pipe.range[0]]);
10663
+ }
10664
+ });
10665
+ return false;
10666
+ }
10667
+ return true;
10668
+ }
10669
+ /**
10670
+ * Verify for the alignment of the pipe according to the delimiter alignment.
10671
+ * Return "leading" if the leading pipe is reported, "trailing" if the trailing pipe is reported, "both" if both are reported, or null if nothing is reported.
10672
+ */
10673
+ function verifyAlignPipe({ leadingPipe, content, trailingPipe }, cellAlign) {
10674
+ if (!leadingPipe || !trailingPipe || !content) return null;
10675
+ const lineText = sourceCode.lines[leadingPipe.loc.start.line - 1];
10676
+ if (cellAlign === "left") {
10677
+ const expectedWidth = options.leadingSpace === "always" ? 1 : 0;
10678
+ if (getLeadingSpacesWidth() === expectedWidth) return null;
10679
+ context.report({
10680
+ loc: leadingPipe.range[1] < content.range[0] ? {
10681
+ start: leadingPipe.loc.end,
10682
+ end: content.loc.start
10683
+ } : leadingPipe.loc,
10684
+ messageId: expectedWidth >= 1 ? "expectedAlignLeft" : "expectedNoSpaceAlignLeft",
10685
+ *fix(fixer) {
10686
+ const cellWidth = getCellWidth();
10687
+ const contentTextWidth = getContentTextWidth();
10688
+ const newLeadingSpaces = " ".repeat(expectedWidth);
10689
+ const newTrailingSpaces = " ".repeat(Math.max(cellWidth - contentTextWidth - expectedWidth, 0));
10690
+ const contentText = getNormalizedContentText();
10691
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10692
+ }
10693
+ });
10694
+ return "leading";
10695
+ } else if (cellAlign === "right") {
10696
+ const expectedWidth = options.trailingSpace === "always" ? 1 : 0;
10697
+ if (getTrailingSpacesWidth() === expectedWidth) return null;
10698
+ context.report({
10699
+ loc: content.range[1] < trailingPipe.range[0] ? {
10700
+ start: content.loc.end,
10701
+ end: trailingPipe.loc.start
10702
+ } : trailingPipe.loc,
10703
+ messageId: expectedWidth >= 1 ? "expectedAlignRight" : "expectedNoSpaceAlignRight",
10704
+ *fix(fixer) {
10705
+ const cellWidth = getCellWidth();
10706
+ const contentTextWidth = getContentTextWidth();
10707
+ const newLeadingSpaces = " ".repeat(Math.max(cellWidth - contentTextWidth - expectedWidth, 0));
10708
+ const newTrailingSpaces = " ".repeat(expectedWidth);
10709
+ const contentText = getNormalizedContentText();
10710
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10711
+ }
10712
+ });
10713
+ return "trailing";
10714
+ } else if (cellAlign === "center") {
10715
+ const leadingSpacesWidth = getLeadingSpacesWidth();
10716
+ const trailingSpacesWidth = getTrailingSpacesWidth();
10717
+ if (leadingSpacesWidth === trailingSpacesWidth || leadingSpacesWidth + 1 === trailingSpacesWidth) return null;
10718
+ const leadingReportLoc = leadingPipe.range[1] < content.range[0] ? {
10719
+ start: leadingPipe.loc.end,
10720
+ end: content.loc.start
10721
+ } : leadingPipe.loc;
10722
+ const trailingReportLoc = content.range[1] < trailingPipe.range[0] ? {
10723
+ start: content.loc.end,
10724
+ end: trailingPipe.loc.start
10725
+ } : trailingPipe.loc;
10726
+ for (const reportLoc of [leadingReportLoc, trailingReportLoc]) context.report({
10727
+ loc: reportLoc,
10728
+ messageId: "expectedAlignCenter",
10729
+ *fix(fixer) {
10730
+ const cellWidth = getCellWidth();
10731
+ const contentTextWidth = getContentTextWidth();
10732
+ const spacesLength = cellWidth - contentTextWidth;
10733
+ const leadingSpacesLength = Math.floor(spacesLength / 2);
10734
+ const trailingSpacesLength = spacesLength - leadingSpacesLength;
10735
+ const newLeadingSpaces = " ".repeat(leadingSpacesLength);
10736
+ const newTrailingSpaces = " ".repeat(trailingSpacesLength);
10737
+ const contentText = getNormalizedContentText();
10738
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10739
+ }
10740
+ });
10741
+ return "both";
10742
+ }
10743
+ return null;
10744
+ /**
10745
+ * Get the width of the leading spaces in the cell.
10746
+ */
10747
+ function getLeadingSpacesWidth() {
10748
+ return getTextWidth(lineText, leadingPipe.loc.end.column - 1, content.loc.start.column - 1);
10749
+ }
10750
+ /**
10751
+ * Get the width of the trailing spaces in the cell.
10752
+ */
10753
+ function getTrailingSpacesWidth() {
10754
+ return getTextWidth(lineText, content.loc.end.column - 1, trailingPipe.loc.start.column - 1);
10755
+ }
10756
+ /**
10757
+ * Get the width of the whole cell (including leading and trailing spaces)
10758
+ */
10759
+ function getCellWidth() {
10760
+ return getTextWidth(lineText, leadingPipe.loc.end.column - 1, trailingPipe.loc.start.column - 1);
10761
+ }
10762
+ /**
10763
+ * Get the width of the content text (excluding leading and trailing spaces)
10764
+ */
10765
+ function getContentTextWidth() {
10766
+ return getTextWidth(lineText, content.loc.start.column - 1, content.loc.end.column - 1);
10767
+ }
10768
+ /**
10769
+ * Get the normalized content text (with normalized spaces)
10770
+ */
10771
+ function getNormalizedContentText() {
10772
+ const prefixWidth = getWidth(lineText.slice(0, content.loc.start.column - 1));
10773
+ let result = "";
10774
+ for (const c of lineText.slice(content.loc.start.column - 1, content.loc.end.column - 1)) if (c === " ") result += " ".repeat(4 - (prefixWidth + result.length) % 4);
10775
+ else result += c;
10776
+ return result;
10777
+ }
10778
+ }
10779
+ /**
10780
+ * Convert a parsed table row to cell data list
10781
+ */
10782
+ function parsedTableRowToCellDataList(parsedRow) {
10783
+ return parsedRow.cells.map((cell, index) => {
10784
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10785
+ return {
10786
+ type: "cell",
10787
+ leadingPipe: cell.leadingPipe,
10788
+ content: cell.cell,
10789
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10790
+ };
10791
+ });
10792
+ }
10793
+ /**
10794
+ * Convert a parsed table delimiter row to delimiter data list
10795
+ */
10796
+ function parsedTableDelimiterRowToDelimiterDataList(parsedDelimiterRow) {
10797
+ return parsedDelimiterRow.delimiters.map((cell, index) => {
10798
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10799
+ return {
10800
+ type: "delimiter",
10801
+ leadingPipe: cell.leadingPipe,
10802
+ content: cell.delimiter,
10803
+ align: cell.delimiter.align,
10804
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10805
+ };
10806
+ });
10807
+ }
10808
+ return { table(node) {
10809
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
10810
+ const delimiters = parsedDelimiterRow && parsedTableDelimiterRowToDelimiterDataList(parsedDelimiterRow);
10811
+ for (const row of node.children) {
10812
+ const parsedRow = parseTableRow(sourceCode, row);
10813
+ if (!parsedRow) continue;
10814
+ const cells = parsedTableRowToCellDataList(parsedRow);
10815
+ for (let columnIndex = 0; columnIndex < cells.length; columnIndex++) {
10816
+ const cell = cells[columnIndex];
10817
+ const delimiter = delimiters && columnIndex < delimiters.length ? delimiters[columnIndex] : null;
10818
+ const alignReportedPoint = delimiter ? verifyAlignPipe(cell, options.cellAlignByDelimiter[delimiter.align]) : null;
10819
+ if (alignReportedPoint === "both") continue;
10820
+ if (cell.leadingPipe && alignReportedPoint !== "leading") {
10821
+ if (options.leadingSpace !== "never" || cell.content) {
10822
+ const nextToken = getNextToken(cells, columnIndex);
10823
+ if (nextToken) verifyLeadingPipe(cell.leadingPipe, nextToken);
10824
+ }
10825
+ }
10826
+ if (cell.trailingPipe && options.trailingSpace !== "never" && alignReportedPoint !== "trailing") {
10827
+ const prevToken = getPrevToken(cells, columnIndex);
10828
+ if (prevToken) verifyTrailingPipe(prevToken, cell.trailingPipe);
10829
+ }
10830
+ }
10831
+ }
10832
+ if (!delimiters) return;
10833
+ for (let columnIndex = 0; columnIndex < delimiters.length; columnIndex++) {
10834
+ const delimiter = delimiters[columnIndex];
10835
+ const alignReportedPoint = verifyAlignPipe(delimiter, options.cellAlignByDelimiter[delimiter.align]);
10836
+ if (alignReportedPoint === "both") continue;
10837
+ if (delimiter.leadingPipe && alignReportedPoint !== "leading") verifyLeadingPipe(delimiter.leadingPipe, delimiter.content);
10838
+ if (delimiter.trailingPipe && alignReportedPoint !== "trailing") verifyTrailingPipe(delimiter.content, delimiter.trailingPipe);
10839
+ }
10840
+ } };
10841
+ /**
10842
+ * Get the next token (pipe or cell) after the given column index.
10843
+ */
10844
+ function getNextToken(cells, columnIndex) {
10845
+ for (let i = columnIndex; i < cells.length; i++) {
10846
+ const cell = cells[i];
10847
+ const token = cell.content ?? cell.trailingPipe;
10848
+ if (token) return token;
10849
+ }
10850
+ return null;
10851
+ }
10852
+ /**
10853
+ * Get the prev token (pipe or cell) after the given column index.
10854
+ */
10855
+ function getPrevToken(cells, columnIndex) {
10856
+ for (let i = columnIndex; i >= 0; i--) {
10857
+ const cell = cells[i];
10858
+ const token = cell.content ?? cell.leadingPipe;
10859
+ if (token) return token;
10860
+ }
10861
+ return null;
10862
+ }
10863
+ }
10864
+ });
10865
+
9658
10866
  //#endregion
9659
10867
  //#region src/rules/thematic-break-character-style.ts
9660
10868
  var thematic_break_character_style_default = createRule("thematic-break-character-style", {
@@ -9663,7 +10871,7 @@ var thematic_break_character_style_default = createRule("thematic-break-characte
9663
10871
  docs: {
9664
10872
  description: "enforce consistent character style for thematic breaks (horizontal rules) in Markdown.",
9665
10873
  categories: ["standard"],
9666
- listCategory: "Stylistic"
10874
+ listCategory: "Notation"
9667
10875
  },
9668
10876
  fixable: "code",
9669
10877
  hasSuggestions: false,
@@ -9742,7 +10950,7 @@ var thematic_break_length_default = createRule("thematic-break-length", {
9742
10950
  docs: {
9743
10951
  description: "enforce consistent length for thematic breaks (horizontal rules) in Markdown.",
9744
10952
  categories: ["standard"],
9745
- listCategory: "Stylistic"
10953
+ listCategory: "Decorative"
9746
10954
  },
9747
10955
  fixable: "code",
9748
10956
  hasSuggestions: false,
@@ -9808,7 +11016,7 @@ var thematic_break_sequence_pattern_default = createRule("thematic-break-sequenc
9808
11016
  docs: {
9809
11017
  description: "enforce consistent repeating patterns for thematic breaks (horizontal rules) in Markdown.",
9810
11018
  categories: ["standard"],
9811
- listCategory: "Stylistic"
11019
+ listCategory: "Decorative"
9812
11020
  },
9813
11021
  fixable: "code",
9814
11022
  hasSuggestions: false,
@@ -9905,6 +11113,9 @@ const rules$1 = [
9905
11113
  sort_definitions_default,
9906
11114
  strikethrough_delimiters_style_default,
9907
11115
  table_header_casing_default,
11116
+ table_leading_trailing_pipes_default,
11117
+ table_pipe_alignment_default,
11118
+ table_pipe_spacing_default,
9908
11119
  thematic_break_character_style_default,
9909
11120
  thematic_break_length_default,
9910
11121
  thematic_break_sequence_pattern_default
@@ -9912,7 +11123,7 @@ const rules$1 = [
9912
11123
 
9913
11124
  //#endregion
9914
11125
  //#region src/configs/recommended.ts
9915
- var recommended_exports = __export({
11126
+ var recommended_exports = /* @__PURE__ */ __export({
9916
11127
  files: () => files$1,
9917
11128
  language: () => language$1,
9918
11129
  languageOptions: () => languageOptions$1,
@@ -9942,7 +11153,7 @@ const rules$3 = {
9942
11153
 
9943
11154
  //#endregion
9944
11155
  //#region src/configs/standard.ts
9945
- var standard_exports = __export({
11156
+ var standard_exports = /* @__PURE__ */ __export({
9946
11157
  files: () => files,
9947
11158
  language: () => language,
9948
11159
  languageOptions: () => languageOptions,
@@ -9993,6 +11204,9 @@ const rules$2 = {
9993
11204
  "markdown-preferences/setext-heading-underline-length": "error",
9994
11205
  "markdown-preferences/sort-definitions": "error",
9995
11206
  "markdown-preferences/strikethrough-delimiters-style": "error",
11207
+ "markdown-preferences/table-leading-trailing-pipes": "error",
11208
+ "markdown-preferences/table-pipe-alignment": "error",
11209
+ "markdown-preferences/table-pipe-spacing": "error",
9996
11210
  "markdown-preferences/thematic-break-character-style": "error",
9997
11211
  "markdown-preferences/thematic-break-length": "error",
9998
11212
  "markdown-preferences/thematic-break-sequence-pattern": "error"
@@ -10000,12 +11214,12 @@ const rules$2 = {
10000
11214
 
10001
11215
  //#endregion
10002
11216
  //#region src/meta.ts
10003
- var meta_exports = __export({
11217
+ var meta_exports = /* @__PURE__ */ __export({
10004
11218
  name: () => name,
10005
11219
  version: () => version
10006
11220
  });
10007
11221
  const name = "eslint-plugin-markdown-preferences";
10008
- const version = "0.24.0";
11222
+ const version = "0.26.0";
10009
11223
 
10010
11224
  //#endregion
10011
11225
  //#region src/index.ts